Cogs.Core
Parsing.cpp
1#include "Parsing.h"
2
3#include "Foundation/Logging/Logger.h"
4
5#include <glm/gtc/type_ptr.hpp>
6
7#include <algorithm>
8#include <ctime>
9#include <charconv>
10
11using namespace Cogs;
12
13namespace
14{
16
17 const bool traceParsing = false;
18
19 void parseOrdinalDate(int& day, int& month, int year) {
20 int dayCounts[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
21
22 if ((!(year % 4) && (year % 100)) || !(year % 400)) {
23 dayCounts[1] = 29;
24 }
25 for (int idx = 0; idx < 12; ++idx) {
26 month++;
27
28 if (day <= dayCounts[idx]) {
29 break;
30 }
31 day -= dayCounts[idx];
32 }
33 }
34}
35
36const size_t Cogs::Core::ParsedDataTypeSizes[] = {
37 0, // Unknown
38 0, // Object,
39 0, // String,
40 4, // Float
41 8, // Float2
42 12, // Float3
43 16, // Float4
44 64, // Float4x4
45 4, // Int
46 8, // Int2
47 12, // Int3
48 16, // Int4
49 4, // UInt
50 8, // UInt2
51 12, // UInt3
52 16, // UInt4
53 8, // Double
54 1, // Bool
55 0, // Texture2D
56 0, // ConstantBuffer
57 0, // Buffer
58};
59static_assert(std::size(Cogs::Core::ParsedDataTypeSizes) == (size_t(Cogs::Core::ParsedDataType::Buffer) + 1));
60
61const char * Cogs::Core::ParsedDataTypeNames[] = {
62 "Unknown",
63 "Object",
64 "string",
65 "float",
66 "float2",
67 "float3",
68 "float4",
69 "float4x4",
70 "int",
71 "int2",
72 "int3",
73 "int4",
74 "uint",
75 "uint2",
76 "uint3",
77 "uint4",
78 "double",
79 "bool",
80 "Texture2D",
81 "ConstantBuffer",
82 "Buffer",
83};
84static_assert(std::size(Cogs::Core::ParsedDataTypeNames) == (size_t(Cogs::Core::ParsedDataType::Buffer) + 1));
85
86Cogs::Core::ParsedValue::ParsedValue(const ParsedValue & other)
87{
88 key = other.key;
89 value = other.value;
90 type = other.type;
91 dimension = other.dimension;
92 valueHash = other.valueHash;
93 float4x4Value = other.float4x4Value;
94 expressions = other.expressions;
95 values = other.values;
96}
97
98Cogs::Core::ParsedValue::ParsedValue(ParsedValue && other) noexcept
99{
100 std::swap(key, other.key);
101 std::swap(value, other.value);
102 type = other.type;
103 dimension = other.dimension;
104 valueHash = other.valueHash;
105 float4x4Value = other.float4x4Value;
106 std::swap(expressions, other.expressions);
107 std::swap(values, other.values);
108}
109
110Cogs::Core::ParsedValue & Cogs::Core::ParsedValue::operator=(const ParsedValue & other)
111{
112 key = other.key;
113 value = other.value;
114 type = other.type;
115 dimension = other.dimension;
116 valueHash = other.valueHash;
117 float4x4Value = other.float4x4Value;
118 expressions = other.expressions;
119 values = other.values;
120
121 return *this;
122}
123
124
125bool Cogs::Core::ParsedValue::asBool(bool& rv, bool complain) const
126{
127 switch (type)
128 {
129 case Cogs::Core::ParsedDataType::String:
130 {
131 auto lc = value;
132 for (auto& c : lc) {
133 c = static_cast<decltype(lc)::value_type>(std::tolower(c));
134 }
135 switch (StringView(lc).hash())
136 {
137 case Cogs::hash("true"):
138 rv = true;
139 return true;
140 case Cogs::hash("false"):
141 rv = false;
142 return true;
143 default:
144 break;
145 }
146 break;
147 }
148 case Cogs::Core::ParsedDataType::Float:
149 rv = floatValue != 0.f;
150 return true;
151 break;
152
153 case Cogs::Core::ParsedDataType::Double:
154 rv = doubleValue != 0.f;
155 return true;
156 break;
157
158 case Cogs::Core::ParsedDataType::Int:
159 rv = intValue != 0;
160 return true;
161 break;
162 case Cogs::Core::ParsedDataType::UInt:
163 rv = intValue != 0;
164 return true;
165 break;
166
167 case Cogs::Core::ParsedDataType::Bool:
168 rv = boolValue;
169 return true;
170 break;
171
172 default:
173 break;
174 }
175
176 if (complain) {
177 LOG_ERROR(logger, "Unable to interpret '%s' as bool.", value.c_str());
178 }
179 return false;
180}
181
182bool Cogs::Core::ParsedValue::asFloat(float& rv, bool complain) const
183{
184 switch (type)
185 {
186 case Cogs::Core::ParsedDataType::Float:
187 rv = floatValue;
188 return true;
189 break;
190 case Cogs::Core::ParsedDataType::Double:
191 rv = static_cast<float>(doubleValue);
192 return true;
193 break;
194 case Cogs::Core::ParsedDataType::Int:
195 rv = static_cast<float>(intValue);
196 return true;
197 break;
198 case Cogs::Core::ParsedDataType::UInt:
199 rv = static_cast<float>(uintValue);
200 return true;
201 break;
202 case Cogs::Core::ParsedDataType::Bool:
203 rv = boolValue ? 1.f : 0.f;
204 return true;
205 break;
206 //case Cogs::Core::ParsedDataType::Double:
207 // break;
208 default:
209 break;
210 }
211 if (complain) {
212 LOG_ERROR(logger, "Unable to interpret '%s' as float.", value.c_str());
213 }
214 return false;
215}
216
217bool Cogs::Core::ParsedValue::asInt(int& rv, bool complain) const
218{
219 switch (type)
220 {
221 case Cogs::Core::ParsedDataType::Float:
222 rv = static_cast<int>(floatValue);
223 return true;
224 break;
225 case Cogs::Core::ParsedDataType::Double:
226 rv = static_cast<int>(doubleValue);
227 return true;
228 break;
229 case Cogs::Core::ParsedDataType::Int:
230 rv = intValue;
231 return true;
232 break;
233 case Cogs::Core::ParsedDataType::UInt:
234 rv = uintValue;
235 return true;
236 break;
237 case Cogs::Core::ParsedDataType::Bool:
238 rv = boolValue ? 1 : 0;
239 return true;
240 break;
241
242 default:
243 break;
244 }
245 if (complain) {
246 LOG_ERROR(logger, "Unable to interpret '%s' as int.", value.c_str());
247 }
248 return false;
249}
250
251
252
253bool Cogs::Core::matchChars(const char c, const StringView & chars)
254{
255 const size_t count = chars.length();
256 const char * const data = chars.data();
257
258 for (size_t i = 0; i < count; ++i) {
259 if (c == data[i]) return true;
260 }
261
262 return false;
263}
264
265void Cogs::Core::split(const StringView & str, const StringView & splits, TokenStream& tokens)
266{
267 const char * data = str.data();
268 const size_t len = str.size();
269 const char * const end = data + len;
270
271 const char * token = nullptr;
272
273 while (data != end) {
274 const bool isSplitter = matchChars(*data, splits);
275
276 if (!isSplitter && !token) {
277 token = data;
278 } else if (isSplitter && token) {
279 tokens.push_back(StringView(token, data - token));
280 token = nullptr;
281 }
282
283 ++data;
284 }
285
286 if (token) tokens.push_back(StringView(token, data - token));
287}
288
289void Cogs::Core::split(const StringView & str, const StringView & splits, const int grouper, TokenStream& tokens)
290{
291 const char * data = str.data();
292 const size_t len = str.size();
293 const char * const end = data + len;
294
295 const char * token = nullptr;
296
297 bool inGroup = false;
298 while (data != end) {
299
300 if (*data == grouper) {
301 inGroup = !inGroup;
302 }
303 const bool isSplitter = !inGroup && matchChars(*data, splits);
304
305 if (!isSplitter && !token) {
306 token = data;
307 }
308 else if (isSplitter && token) {
309 tokens.push_back(StringView(token, data - token));
310 token = nullptr;
311 }
312
313 ++data;
314 }
315
316 if (token) tokens.push_back(StringView(token, data - token));
317}
318
319
320
321Cogs::Core::TokenStream Cogs::Core::split(const StringView & str, const StringView & splits)
322{
323 TokenStream tokens;
324 split(str, splits, tokens);
325 return tokens;
326}
327
328Cogs::Core::TokenStream Cogs::Core::tokenize(const StringView & valueStr)
329{
330 TokenStream tokens;
331 tokens.reserve(8);
332
333 split(valueStr, " \t{},", tokens);
334 return tokens;
335}
336
337void Cogs::Core::tokenize(const StringView & valueStr, TokenStream & tokens)
338{
339 split(valueStr, " \t{},", tokens);
340}
341
342void Cogs::Core::parseStringValue(const StringView & value, ParsedValue & v)
343{
344 thread_local static std::vector<StringView> tokens;
345 tokens.clear();
346
347 split(value, " \t,{}[]", '$', tokens);
348
349 if (tokens.empty()) {
350 LOG_ERROR(logger, "Invalid value specification.");
351 return;
352 } else if (tokens.size() == 1) {
353 // Make string value available.
354 v.value = value.to_string();
355 if (tokens[0] == "true") {
356 v.type = ParsedDataType::Bool;
357 v.boolValue = true;
358 } else if (tokens[0] == "false") {
359 v.type = ParsedDataType::Bool;
360 v.boolValue = false;
361 } else {
362 v.type = ParsedDataType::String;
363 v.value = value.to_string();
364 }
365 return;
366 }
367
368 // Several tokens:
369 v.valueHash = tokens[1].hash();
370
371 size_t type_hash = tokens[0].hash();
372 tokens.erase(tokens.begin());
373 v.value = tokens[0].to_string();
374
375 switch (type_hash)
376 {
377 case Cogs::hash("string"):
378 v.type = ParsedDataType::String;
379 break;
380
381 case Cogs::hash("float"):
382 v.type = ParsedDataType::Float;
383 parseValues(v.expressions, &v.floatValue, tokens, 1);
384 break;
385 case Cogs::hash("float2"):
386 v.type = ParsedDataType::Float2;
387 parseValues(v.expressions, glm::value_ptr(v.float2Value), tokens, 2);
388 break;
389 case Cogs::hash("float3"):
390 v.type = ParsedDataType::Float3;
391 parseValues(v.expressions, glm::value_ptr(v.float3Value), tokens, 3);
392 break;
393 case Cogs::hash("float4"):
394 v.type = ParsedDataType::Float4;
395 parseValues(v.expressions, glm::value_ptr(v.float4Value), tokens, 4);
396 break;
397
398 case Cogs::hash("float4x4"):
399 v.type = ParsedDataType::Float4x4;
400 parseValues(v.expressions, glm::value_ptr(v.float4Value), tokens, 16);
401 break;
402
403 case Cogs::hash("int"):
404 v.type = ParsedDataType::Int;
405 parseValues(v.expressions, &v.intValue, tokens, 1);
406 break;
407 case Cogs::hash("int2"):
408 v.type = ParsedDataType::Int2;
409 parseValues(v.expressions, glm::value_ptr(v.int2Value), tokens, 2);
410 break;
411 case Cogs::hash("int3"):
412 v.type = ParsedDataType::Int3;
413 parseValues(v.expressions, glm::value_ptr(v.int3Value), tokens, 3);
414 break;
415 case Cogs::hash("int4"):
416 v.type = ParsedDataType::Int4;
417 parseValues(v.expressions, glm::value_ptr(v.int4Value), tokens, 4);
418 break;
419
420 case Cogs::hash("uint"):
421 v.type = ParsedDataType::UInt;
422 parseValues(v.expressions, &v.uintValue, tokens, 1);
423 break;
424 case Cogs::hash("uint2"):
425 v.type = ParsedDataType::UInt2;
426 parseValues(v.expressions, glm::value_ptr(v.uint2Value), tokens, 2);
427 break;
428 case Cogs::hash("uint3"):
429 v.type = ParsedDataType::UInt3;
430 parseValues(v.expressions, glm::value_ptr(v.uint3Value), tokens, 3);
431 break;
432 case Cogs::hash("uint4"):
433 v.type = ParsedDataType::UInt4;
434 parseValues(v.expressions, glm::value_ptr(v.uint4Value), tokens, 4);
435 break;
436
437 case Cogs::hash("double"):
438 v.type = ParsedDataType::Double;
439 parseValues(v.expressions, &v.doubleValue, tokens, 1);
440 break;
441
442 case Cogs::hash("bool"):
443 v.type = ParsedDataType::Bool;
444 parseValues(v.expressions, &v.boolValue, tokens, 1);
445 break;
446
447 case Cogs::hash("Texture"):
448 case Cogs::hash("Texture2D"):
449 v.type = ParsedDataType::Texture2D;
450 v.value = tokens[0].to_string();
451 for (size_t i = 1, n = tokens.size(); i < n; i++) {
452 switch (tokens[i].hashLowercase()) {
453 case Cogs::hash("depth"):
454 v.texture.flags |= ParsedValueTextureFlags::DepthTexture;
455 break;
456 case Cogs::hash("linear"):
457 v.texture.flags |= ParsedValueTextureFlags::LinearSampler;
458 break;
459 case Cogs::hash("nolinear"):
460 v.texture.flags &= ~ParsedValueTextureFlags::LinearSampler;
461 break;
462 case Cogs::hash("float"):
463 v.texture.dataType = ParsedDataType::Float;
464 break;
465 case Cogs::hash("float2"):
466 v.texture.dataType = ParsedDataType::Float2;
467 break;
468 case Cogs::hash("float3"):
469 v.texture.dataType = ParsedDataType::Float3;
470 break;
471 case Cogs::hash("float4"):
472 v.texture.dataType = ParsedDataType::Float4;
473 break;
474 case Cogs::hash("int"):
475 v.texture.dataType = ParsedDataType::Int;
476 break;
477 case Cogs::hash("int2"):
478 v.texture.dataType = ParsedDataType::Int2;
479 break;
480 case Cogs::hash("int3"):
481 v.texture.dataType = ParsedDataType::Int3;
482 break;
483 case Cogs::hash("int4"):
484 v.texture.dataType = ParsedDataType::Int4;
485 break;
486 case Cogs::hash("uint"):
487 v.texture.dataType = ParsedDataType::UInt;
488 break;
489 case Cogs::hash("uint2"):
490 v.texture.dataType = ParsedDataType::UInt2;
491 break;
492 case Cogs::hash("uint3"):
493 v.texture.dataType = ParsedDataType::UInt3;
494 break;
495 case Cogs::hash("uint4"):
496 v.texture.dataType = ParsedDataType::UInt4;
497 break;
498 case Cogs::hash("samples"):
499 if (i + 1 == n) {
500 LOG_ERROR(logger, "Could not determine number of samples");
501 } else {
502 i++;
503 int samples;
504 std::string_view sv = tokens[i].to_string_view();
505 auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), samples);
506 if (ec == std::errc()) {
507 v.texture.samples = samples;
508 }
509 else {
510 LOG_ERROR(logger, "Could not determine number of samples \"%.*s\" is not a number", StringViewFormat(tokens[i]));
511 }
512 }
513 }
514 }
515 break;
516 case Cogs::hash("Buffer"):
517 v.type = ParsedDataType::Buffer;
518 v.value = tokens[0].to_string();
519 for (size_t i = 1, n = tokens.size(); i < n; i++) {
520 switch (tokens[i].hashLowercase()) {
521 case Cogs::hash("structured"):
522 v.buffer.flags |= ParsedValueBufferFlags::Structured;
523 break;
524 case Cogs::hash("raw"):
525 v.buffer.flags &= ~ParsedValueBufferFlags::Structured;
526 break;
527 case Cogs::hash("float"):
528 v.buffer.dataType = ParsedDataType::Float;
529 break;
530 case Cogs::hash("float2"):
531 v.buffer.dataType = ParsedDataType::Float2;
532 break;
533 case Cogs::hash("float3"):
534 v.buffer.dataType = ParsedDataType::Float3;
535 break;
536 case Cogs::hash("float4"):
537 v.buffer.dataType = ParsedDataType::Float4;
538 break;
539 case Cogs::hash("int"):
540 v.buffer.dataType = ParsedDataType::Int;
541 break;
542 case Cogs::hash("int2"):
543 v.buffer.dataType = ParsedDataType::Int2;
544 break;
545 case Cogs::hash("int3"):
546 v.buffer.dataType = ParsedDataType::Int3;
547 break;
548 case Cogs::hash("int4"):
549 v.buffer.dataType = ParsedDataType::Int4;
550 break;
551 case Cogs::hash("uint"):
552 v.buffer.dataType = ParsedDataType::UInt;
553 break;
554 case Cogs::hash("uint2"):
555 v.buffer.dataType = ParsedDataType::UInt2;
556 break;
557 case Cogs::hash("uint3"):
558 v.buffer.dataType = ParsedDataType::UInt3;
559 break;
560 case Cogs::hash("uint4"):
561 v.buffer.dataType = ParsedDataType::UInt4;
562 break;
563 default:
564 LOG_WARNING(logger, "Unrecognized Buffer keyword \"%.*s\"", StringViewFormat(tokens[i]));
565 break;
566 }
567 }
568 break;
569 case Cogs::hash("ConstantBuffer"):
570 v.type = ParsedDataType::ConstantBuffer;
571 v.value = tokens[0].to_string();
572 break;
573 default:
574 v.type = ParsedDataType::String;
575 v.value = value.to_string();
576 break;
577 }
578}
579
580Cogs::TextureFormat Cogs::Core::parseTextureFormat(const StringView & format, Cogs::TextureFormat defaultFormat)
581{
582 if (format.empty()) return defaultFormat;
583
584 if (Cogs::TextureFormat textureFormat = Cogs::parseDataFormat(format); textureFormat != Cogs::TextureFormat::Unknown) {
585 return textureFormat;
586 }
587
588 LOG_ERROR(logger, "Error parsing texture format: %.*s. Reverting to default.", StringViewFormat(format));
589 return defaultFormat;
590}
591
592double Cogs::Core::parseDouble(const StringView & token, double defaultValue)
593{
594 if (!token.size()) return defaultValue;
595
596 return std::atof(token.to_string().c_str());
597}
598
599float Cogs::Core::parseFloat(const StringView & token, float defaultValue)
600{
601 return static_cast<float>(parseDouble(token, defaultValue));
602}
603
604int32_t Cogs::Core::parseInt(const StringView & token, int32_t defaultValue)
605{
606 if (!token.size()) return defaultValue;
607
608 if (token.size() > 2 && token[0] == '0' && (token[1] == 'x' || token[1] == 'X')) {
609 return std::strtol(token.to_string().c_str(), nullptr, 16);
610 } else {
611 return std::strtol(token.to_string().c_str(), nullptr, 10);
612 }
613}
614
615uint32_t Cogs::Core::parseUInt(const StringView & token, uint32_t defaultValue)
616{
617 if (!token.size()) return defaultValue;
618
619 if (token.size() > 2 && token[0] == '0' && (token[1] == 'x' || token[1] == 'X')) {
620 return std::strtoul(token.to_string().c_str(), nullptr, 16);
621 } else {
622 return std::strtoul(token.to_string().c_str(), nullptr, 10);
623 }
624}
625
626bool Cogs::Core::parseBool(const StringView & token, bool /*defaultValue*/)
627{
628 switch(token.hash()) {
629 case Cogs::hash("True"): [[fallthrough]];
630 case Cogs::hash("true"): [[fallthrough]];
631 case Cogs::hash("1"): [[fallthrough]];
632 case Cogs::hash("On"): [[fallthrough]];
633 case Cogs::hash("on"): return true;
634 }
635 return false;
636}
637
638bool Cogs::Core::parseBool_(bool& rv, const StringView& token)
639{
640 switch (token.hashLowercase()) {
641 case Cogs::hash("true"): [[fallthrough]];
642 case Cogs::hash("1"): [[fallthrough]];
643 case Cogs::hash("on"):
644 rv = true;
645 return true;
646
647 case Cogs::hash("false"): [[fallthrough]];
648 case Cogs::hash("0"): [[fallthrough]];
649 case Cogs::hash("off"):
650 rv = false;
651 return true;
652
653 default:
654 LOG_ERROR(logger, "Failed to parse \"%.*s\" as bool", StringViewFormat(token));
655 return false;
656 }
657}
658
659bool Cogs::Core::parseInt_(int32_t& rv, const StringView& token)
660{
661 if (!token.size()) return false;
662
663 std::from_chars_result rc = std::from_chars(token.data(),
664 token.data() + token.length(),
665 rv);
666 if (rc.ec != std::errc()) {
667 LOG_ERROR(logger, "Failed to parse \"%.*s\" as int: %s",
668 StringViewFormat(token),
669 std::make_error_code(rc.ec).message().c_str());
670 return false;
671 }
672 return true;
673}
674
675uint64_t Cogs::Core::parseISO8601(const StringView& iso8601) {
676 int tzOffset;
677 return parseISO8601(iso8601, tzOffset);
678}
679
680uint64_t Cogs::Core::parseISO8601(const StringView& iso8601, int& tzOffset) {
681 size_t t = iso8601.find("T");
682 std::tm tm = {};
683 uint32_t milliseconds = 0;
684
685 // Omitting T (yyyymmddhhmmss) is not supported.
686 assert (t != StringView::NoPosition);
687
688 // Durations are not supported.
689 assert(iso8601[0] != 'P');
690
691 // Time intervals are not supported.
692 assert(iso8601.find("/") == StringView::NoPosition);
693
694 StringView date = iso8601.substr(0, t);
695 StringView time = iso8601.substr(t + 1);
696 TokenStream tzTokens = split(time, "-+Z");
697 TokenStream dateTokens = split(date, "-");
698 TokenStream timeTokens = split(tzTokens[0], ":");
699
700 switch (dateTokens.size()) {
701 case 1: {
702 // TODO: Add support for week numbers
703 // yyyyWwwdd
704 assert(date.find("W") == StringView::NoPosition);
705
706 if (iso8601[0] == '-') {
707 // --mmdd...
708 tm.tm_mon = parseInt(dateTokens[0].substr(0, 2));
709 tm.tm_mday = parseInt(dateTokens[0].substr(2));
710 }
711 else if (dateTokens[0].size() == 8) {
712 // yyyymmdd...
713 tm.tm_year = parseInt(dateTokens[0].substr(0, 4));
714 tm.tm_mon = parseInt(dateTokens[0].substr(4, 2));
715 tm.tm_mday = parseInt(dateTokens[0].substr(6));
716 }
717 else if (dateTokens[0].size() == 7) {
718 // yyyyddd...
719 tm.tm_year = parseInt(dateTokens[0].substr(0, 4));
720 tm.tm_mday = parseInt(dateTokens[0].substr(4));
721
722 parseOrdinalDate(tm.tm_mday, tm.tm_mon, tm.tm_year);
723 }
724 break;
725 }
726 case 2: {
727 if (iso8601[0] == '-') {
728 // --mm-dd...
729 tm.tm_mon = parseInt(dateTokens[0]);
730 tm.tm_mday = parseInt(dateTokens[1]);
731 }
732 else {
733 // TODO: Add support for week numbers
734 // yyyy-Www
735 assert(dateTokens[1][0] != 'W');
736
737 if (dateTokens[1].size() == 3) {
738 // yyyy-ddd
739 tm.tm_year = parseInt(dateTokens[0]);
740 tm.tm_mday = parseInt(dateTokens[1]);
741
742 parseOrdinalDate(tm.tm_mday, tm.tm_mon, tm.tm_year);
743 }
744 else {
745 // yyyy-mm...
746 tm.tm_year = parseInt(dateTokens[0]);
747 tm.tm_mon = parseInt(dateTokens[1]);
748 }
749 }
750 break;
751 }
752 case 3: {
753 // TODO: Add support for dates with week numbers
754 // yyyy-Www-d
755 assert (dateTokens[1][0] != 'W');
756
757 // yyyy-mm-dd...
758 tm.tm_year = parseInt(dateTokens[0]);
759 tm.tm_mon = parseInt(dateTokens[1]);
760 tm.tm_mday = parseInt(dateTokens[2]);
761 break;
762 }
763 }
764
765 switch (timeTokens.size()) {
766 case 1: {
767 // hh[.hhhhh]...
768 // hhmm[.mmmmm]...
769 // hhmmss[.sssss]...
770 tm.tm_hour = parseInt(timeTokens[0].substr(0, 2));
771
772 if ((timeTokens[0][2] != '.') && (timeTokens[0][2] != ',')) {
773 tm.tm_min = parseInt(timeTokens[0].substr(2, 2));
774
775 if ((timeTokens[0][4] != '.') && (timeTokens[0][4] != ',')) {
776 tm.tm_sec = parseInt(timeTokens[0].substr(4, 2));
777
778 if ((timeTokens[0][6] == '.') || (timeTokens[0][6] == ',')) {
779 // .ssss ,ssss...
780 // TODO: Parse fractional seconds...
781 }
782 }
783 else {
784 // .mmmm ,mmmm...
785 // TODO: Parse fractional minutes...
786 }
787 }
788 else {
789 // .hhhh ,hhhh...
790 // TODO: Parse fractional hours...
791 }
792 break;
793 }
794 case 2: {
795 // hh:mm[.mmmmm]...
796
797 tm.tm_hour = parseInt(timeTokens[0]);
798 tm.tm_min = parseInt(timeTokens[1]);
799
800 if ((timeTokens[1][2] == '.') || (timeTokens[1][2] == ',')) {
801 // .mmmm ,mmmm...
802 // TODO: Parse fractional minutes...
803 }
804 break;
805 }
806 case 3: {
807 // hh:mm:ss[.sss]...
808
809 tm.tm_hour = parseInt(timeTokens[0]);
810 tm.tm_min = parseInt(timeTokens[1]);
811 tm.tm_sec = parseInt(timeTokens[2]);
812
813 if ((timeTokens[2][2] == '.') || (timeTokens[2][2] == ',')) {
814 // .ssss ,ssss...
815 // TODO: Parse fractional seconds...
816 }
817 break;
818 }
819 }
820 if (tzTokens.size() == 2) {
821 TokenStream parts = split(tzTokens[1], ":");
822 int tzHours = 0;
823 int tzMinutes = 0;
824
825 switch (parts.size()) {
826 case 1: {
827 tzHours = parseInt(parts[0].substr(0, 2));
828 tzMinutes = parseInt(parts[0].substr(2, 2));
829 break;
830 }
831 case 2: {
832 tzHours = parseInt(parts[0]);
833 tzMinutes = parseInt(parts[1]);
834 }
835 }
836 tzOffset = (tzHours * 3600) + (tzMinutes * 60) * 1000;
837
838 if (tzTokens[1][0] == '-') {
839 tzOffset = -tzOffset;
840 }
841 }
842 else {
843 tzOffset = 0;
844 }
845
846 if (tm.tm_year) {
847 tm.tm_year -= 1900;
848 }
849 else {
850 std::time_t now = std::time(0); // get time now
851
852 tm.tm_year = std::localtime(&now)->tm_year;
853 }
854 tm.tm_mon--;
855 tm.tm_isdst = -1;
856
857 // The following bullshit is necessary because there's no standard way to convert a
858 // UTC time into a Unix timestamp, so we have to calculate the difference between
859 // UTC and local time and then add that difference to the miscalculated time.
860 std::time_t basetime = mktime(&tm);
861 std::time_t diff;
862
863 tm = *std::localtime(&basetime);
864 tm.tm_isdst = -1;
865 diff = mktime(&tm);
866
867 tm = *std::gmtime(&basetime);
868 tm.tm_isdst = -1;
869 diff -= mktime(&tm);
870
871 return ((basetime + diff) * 1000) + milliseconds;
872}
873
874int Cogs::Core::parseEnum(const StringView & token, const Reflection::Type & type, int defaultValue)
875{
876 if (!type.isEnum()) {
877 return defaultValue;
878 }
879
880 thread_local static std::vector<StringView> tokens;
881 tokens.clear();
882 split(token, "| ", tokens);
883
884 if (tokens.size() > 1) {
885 int value = 0;
886 for (auto & t : tokens) {
887 auto enumerator = type.getEnumerator(t);
888
889 if (enumerator) {
890 value |= enumerator->getValue();
891 } else {
892 LOG_ERROR(logger, "Could not find enumerator \"%.*s\" in %s.", StringViewFormat(t), type.getName().c_str());
893 }
894 }
895
896 return value;
897 }
898
899 auto enumerator = type.getEnumerator(token);
900
901 if (enumerator) {
902 return enumerator->getValue();
903 } else {
904 LOG_ERROR(logger, "Could not find enumerator \"%.*s\" in %s.", StringViewFormat(token), type.getName().c_str());
905 }
906
907 return defaultValue;
908}
909
910namespace Cogs::Core
911{
912 bool isCharacter(char c)
913 {
914 return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
915 }
916 bool isNumber(char c)
917 {
918 return (c >= '0' && c <= '9');
919 }
920
921 int parseEnumFlags(const char *& current, const char * end, const Reflection::Type & type, int defaultValue)
922 {
923 int value = 0;
924 bool flip = false;
925 bool isLogicalAnd = false;
926 while (current != end) {
927 if (*current == '~') {
928 flip = true;
929 ++current;
930 } else if (*current == '(') {
931 auto scopeValue = parseEnumFlags(++current, end, type, defaultValue);
932
933 if (isLogicalAnd) {
934 value &= flip ? ~scopeValue : scopeValue;
935 } else {
936 value |= flip ? ~scopeValue : scopeValue;
937 }
938 } else if (*current == ')') {
939 ++current;
940 return value;
941 } else if (*current == '|') {
942 isLogicalAnd = false;
943 ++current;
944 } else if (*current == '&') {
945 ++current;
946 isLogicalAnd = true;
947 } else if (isNumber(*current)) {
948 char* endptr = nullptr;
949 value = strtoul(current, &endptr, 0);
950 current = endptr;
951 } else if (isCharacter(*current)) {
952 auto tokenFirst = current;
953 const char * tokenLast = tokenFirst;
954 const char * tokenCurrent = tokenFirst;
955
956 while (tokenCurrent && tokenCurrent != end) {
957 if (isCharacter(*tokenCurrent) || (tokenCurrent!=tokenFirst && isNumber(*tokenCurrent))) {
958 ++tokenCurrent;
959 tokenLast = tokenCurrent;
960 } else {
961 tokenCurrent = nullptr;
962 }
963 }
964
965 auto scopeValue = parseEnum(StringView(tokenFirst, tokenLast - tokenFirst), type, defaultValue);
966 if (isLogicalAnd) {
967 value &= flip ? ~scopeValue : scopeValue;
968 } else {
969 value |= flip ? ~scopeValue : scopeValue;
970 }
971 current = tokenLast;
972 } else if (current != end) {
973 ++current;
974 }
975 }
976
977 return value;
978 }
979}
980
981int Cogs::Core::parseEnumFlags(const StringView & token, const Reflection::Type & type, int defaultValue)
982{
983 if (token.empty()) return defaultValue;
984
985 auto begin = token.begin();
986 return parseEnumFlags(begin, token.end(), type, defaultValue);
987}
988
989namespace Cogs
990{
991 namespace Core
992 {
993 template<typename T, typename Func>
994 void parseValues(T * values, const TokenStream & tokens, size_t expected, Func f)
995 {
996 if (tokens.size() < expected) {
997 if (traceParsing) LOG_DEBUG(logger, "Expected %zu values, got %zu.\n", expected, tokens.size());
998 }
999
1000 const auto count = std::min(tokens.size(), expected);
1001
1002 for (size_t i = 0; i < count; ++i) {
1003 values[i] = f(tokens[i], T());
1004 }
1005 }
1006
1007 template<typename T, typename Func>
1008 void parseValues(std::vector<std::pair<size_t, std::string>>& expressions, T * values, const TokenStream & tokens, size_t expected, Func f)
1009 {
1010 if (tokens.size() < expected) {
1011 if (traceParsing) LOG_DEBUG(logger, "Expected %zu values, got %zu.\n", expected, tokens.size());
1012 }
1013
1014 const auto count = std::min(tokens.size(), expected);
1015
1016 for (size_t i = 0; i < count; ++i) {
1017 const auto & token = tokens[0];
1018
1019 if (!token.empty() && *token.begin() == '$') {
1020 auto end = token.find("$", 1);
1021 if (end != StringView::NoPosition) end = end - 1;
1022 expressions.push_back(std::make_pair(i, token.substr(1, end).to_string()));
1023
1024 values[i] = T();
1025 }
1026 else {
1027 values[i] = f(tokens[i], T());
1028 }
1029 }
1030 }
1031
1032
1033 }
1034}
1035
1036void Cogs::Core::parseQueryString(std::vector<ParsedValue>& attributes, StringView query)
1037{
1038 size_t a = 0;
1039 do {
1040 a = a + (a == 0 ? 0 : 1);
1041 auto b = query.find("&", a);
1042 auto attributeValue = query.substr(a, b != StringView::NoPosition ? b - a : b);
1043 if (attributeValue.length()) {
1044 auto sepLoc = attributeValue.find("=");
1045 auto key = attributeValue.substr(0, sepLoc);
1046
1047 ParsedValue attribute;
1048 attribute.key = key.to_string();
1049 if (sepLoc == StringView::NoPosition) {
1050 parseStringValue("true", attribute);
1051 }
1052 else {
1053 parseStringValue(attributeValue.substr(sepLoc + 1), attribute);
1054 }
1055 attributes.push_back(attribute);
1056 }
1057 a = b;
1058 } while (a != StringView::NoPosition);
1059}
1060
1061
1062void Cogs::Core::parseValues(float * values, const TokenStream & tokens, size_t expected)
1063{
1064 parseValues(values, tokens, expected, parseFloat);
1065}
1066
1067void Cogs::Core::parseValues(double * values, const TokenStream & tokens, size_t expected)
1068{
1069 parseValues(values, tokens, expected, parseDouble);
1070}
1071
1072void Cogs::Core::parseValues(int * values, const TokenStream & tokens, size_t expected)
1073{
1074 parseValues(values, tokens, expected, parseInt);
1075}
1076
1077void Cogs::Core::parseValues(uint32_t * values, const TokenStream & tokens, size_t expected)
1078{
1079 parseValues(values, tokens, expected, parseUInt);
1080}
1081
1082void Cogs::Core::parseValues(bool * values, const TokenStream & tokens, size_t expected)
1083{
1084 parseValues(values, tokens, expected, parseBool);
1085}
1086
1087
1088void Cogs::Core::parseValues(std::vector<std::pair<size_t, std::string>>& expressions, float * values, const TokenStream & tokens, size_t expected)
1089{
1090 parseValues(expressions, values, tokens, expected, parseFloat);
1091}
1092
1093void Cogs::Core::parseValues(std::vector<std::pair<size_t, std::string>>& expressions, double * values, const TokenStream & tokens, size_t expected)
1094{
1095 parseValues(expressions, values, tokens, expected, parseFloat);
1096}
1097
1098void Cogs::Core::parseValues(std::vector<std::pair<size_t, std::string>>& expressions, int * values, const TokenStream & tokens, size_t expected)
1099{
1100 parseValues(expressions, values, tokens, expected, parseInt);
1101}
1102
1103void Cogs::Core::parseValues(std::vector<std::pair<size_t, std::string>>& expressions, uint32_t * values, const TokenStream & tokens, size_t expected)
1104{
1105 parseValues(expressions, values, tokens, expected, parseUInt);
1106}
1107
1108void Cogs::Core::parseValues(std::vector<std::pair<size_t, std::string>>& expressions, bool * values, const TokenStream & tokens, size_t expected)
1109{
1110 parseValues(expressions, values, tokens, expected, parseBool);
1111}
1112
Log implementation class.
Definition: LogManager.h:140
Represents a discrete type definition, describing a native type class.
Definition: Type.h:89
const Enumerator * getEnumerator(const Name &name) const
Get a pointer to the enumerator with the given name.
Definition: Type.cpp:145
bool isEnum() const
Get if the type is an enumeration type.
Definition: Type.h:315
constexpr const Name & getName() const
Get the unique name of the type.
Definition: Type.h:198
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
constexpr const char * data() const noexcept
Get the sequence of characters referenced by the string view.
Definition: StringView.h:171
constexpr iterator begin() noexcept
Iterator to the beginning of the string.
Definition: StringView.h:100
constexpr size_t size() const noexcept
Get the size of the string.
Definition: StringView.h:178
constexpr iterator end() noexcept
Iterator to the end of the string.
Definition: StringView.h:103
constexpr size_t length() const noexcept
Get the length of the string.
Definition: StringView.h:185
constexpr bool empty() const noexcept
Check if the string is empty.
Definition: StringView.h:122
constexpr StringView substr(size_t offset, size_t count=NoPosition) const noexcept
Get the given sub string.
Definition: StringView.h:258
size_t hashLowercase(size_t hashValue=Cogs::hash()) const noexcept
Get the hash code of the string converted to lowercase.
Definition: StringView.cpp:13
static constexpr size_t NoPosition
No position.
Definition: StringView.h:43
size_t find(const StringView &other, size_t offset=0) const noexcept
Find the given string segment inside the string.
Definition: StringView.cpp:18
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
constexpr size_t hash() const noexcept
Get the hash code of the string.
Definition: StringView.h:200
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
bool parseBool_(bool &rv, const StringView &token)
Parse a bool putting return value in rv and returning whether or not parsing was successful.
Definition: Parsing.cpp:638
uint64_t COGSCORE_DLL_API parseISO8601(const StringView &iso8601)
Parse ISO 8601 timestamps.
Definition: Parsing.cpp:675
bool parseInt_(int32_t &rv, const StringView &token)
Parse an int putting return value in rv and returning whether or not parsing was successful.
Definition: Parsing.cpp:659
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
COGSFOUNDATION_API size_t hashLowercase(std::string_view str, size_t hashValue=Cogs::hash()) noexcept
Get the hash code of the string converted to lowercase.
Stores the parsed output of a key/value pair.
Definition: Parsing.h:40
std::string key
The input key.
Definition: Parsing.h:47
std::string value
The value is available for all non vector types.
Definition: Parsing.h:50
size_t dimension
This value is for external use.
Definition: Parsing.h:58
size_t valueHash
The hash is only set for values where the type is explicitly given.
Definition: Parsing.h:55
ParsedDataType type
The data type of the value. Determines type stored in union below.
Definition: Parsing.h:53
int getValue() const
Get the value of the enumerator.
Definition: Type.h:71
const char * c_str() const
Gets the name as a null-terminated string.
Definition: Name.h:116