1 /*
2     This file is part of Magnum.
3 
4     Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019
5               Vladimír Vondruš <mosra@centrum.cz>
6 
7     Permission is hereby granted, free of charge, to any person obtaining a
8     copy of this software and associated documentation files (the "Software"),
9     to deal in the Software without restriction, including without limitation
10     the rights to use, copy, modify, merge, publish, distribute, sublicense,
11     and/or sell copies of the Software, and to permit persons to whom the
12     Software is furnished to do so, subject to the following conditions:
13 
14     The above copyright notice and this permission notice shall be included
15     in all copies or substantial portions of the Software.
16 
17     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20     THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23     DEALINGS IN THE SOFTWARE.
24 */
25 
26 #include "Parsers.h"
27 
28 #include <cstring>
29 #include <limits>
30 #include <tuple>
31 #include <Corrade/Utility/Debug.h>
32 
33 #include "Magnum/OpenDdl/Type.h"
34 
35 #ifdef CORRADE_TARGET_APPLE
36 #include <climits>
37 #endif
38 
39 namespace Magnum { namespace OpenDdl { namespace Implementation {
40 
operator <<(Debug & debug,const ParseErrorType value)41 Debug& operator<<(Debug& debug, const ParseErrorType value) {
42     switch(value) {
43         #define _c(value) case ParseErrorType::value: return debug << "OpenDdl::ParseErrorType::" #value;
44         _c(NoError)
45         _c(InvalidEscapeSequence)
46         _c(InvalidIdentifier)
47         _c(InvalidName)
48         _c(InvalidCharacterLiteral)
49         _c(InvalidLiteral)
50         _c(InvalidPropertyValue)
51         _c(InvalidSubArraySize)
52         _c(LiteralOutOfRange)
53         _c(ExpectedIdentifier)
54         _c(ExpectedName)
55         _c(ExpectedLiteral)
56         _c(ExpectedSeparator)
57         _c(ExpectedListStart)
58         _c(ExpectedListEnd)
59         _c(ExpectedArraySizeEnd)
60         _c(ExpectedPropertyValue)
61         _c(ExpectedPropertyAssignment)
62         _c(ExpectedPropertyListEnd)
63         #undef _c
64     }
65 
66     return debug << "OpenDdl::ParseErrorType::(invalid)";
67 }
68 
69 namespace {
70 
71 /* Cannot use std::isnum(), std::isalpha() etc., because they depend on locale */
72 template<Int> constexpr bool isBaseN(char);
isBaseN(char c)73 template<> constexpr bool isBaseN<2>(char c) {
74     return c == '0' || c == '1';
75 }
isBaseN(char c)76 template<> constexpr bool isBaseN<8>(char c) {
77     return c >= '0' && c <= '7';
78 }
isBaseN(char c)79 template<> constexpr bool isBaseN<10>(char c) {
80     return c >= '0' && c <= '9';
81 }
isBaseN(char c)82 template<> constexpr bool isBaseN<16>(char c) {
83     return isBaseN<10>(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
84 }
isBinaryPrefix(char c)85 constexpr bool isBinaryPrefix(char c) {
86     return c == 'b' || c == 'o' || c == 'x' ||
87            c == 'B' || c == 'O' || c == 'X';
88 }
isAlpha(char c)89 constexpr bool isAlpha(char c) {
90     return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
91 }
isAlphaDecimal(char c)92 constexpr bool isAlphaDecimal(char c) {
93     return isAlpha(c) || isBaseN<10>(c);
94 }
95 
parseHex(const char * data)96 template<class T> inline T parseHex(const char* data) {
97     T out{};
98     for(std::size_t i = 0; i != sizeof(T)*2; ++i)
99         out |= ((data[i] >= 'a' ?
100             0xa - 'a' : (data[i] >= 'A' ?
101                 0xA - 'A' :
102                     0x0 - '0')) + data[i]) << (sizeof(T)*2 - i - 1)*8;
103     return out;
104 }
105 
equalsPrefix(const Containers::ArrayView<const char> data,const char * const prefix)106 inline bool equalsPrefix(const Containers::ArrayView<const char> data, const char* const prefix) {
107     return std::strncmp(data, prefix, data.size()) == 0;
108 }
109 
prefix(const Containers::ArrayView<const char> data,const char (& compare)[n])110 template<std::size_t n> inline const char* prefix(const Containers::ArrayView<const char> data, const char(&compare)[n]) {
111     /* Propagate errors */
112     if(!data) return nullptr;
113 
114     if(data.size() < n - 1) return nullptr;
115 
116     const char* const end = data + n - 1;
117     return equalsPrefix(data.prefix(end), compare) ? end : nullptr;
118 }
119 
extractWithoutUnderscore(const Containers::ArrayView<const char> data,std::string & buffer)120 void extractWithoutUnderscore(const Containers::ArrayView<const char> data, std::string& buffer) {
121     buffer.clear();
122     for(char c: data) if(c != '_') buffer += c;
123 }
124 
125 }
126 
equals(const Containers::ArrayView<const char> a,const Containers::ArrayView<const char> b)127 bool equals(const Containers::ArrayView<const char> a, const Containers::ArrayView<const char> b) {
128     return a.size() == b.size() && equalsPrefix(a, b);
129 }
130 
whitespace(const Containers::ArrayView<const char> data)131 const char* whitespace(const Containers::ArrayView<const char> data) {
132     /* Propagate error */
133     if(!data) return nullptr;
134 
135     const char* i = data;
136     while(i != data.end()) {
137         /* Whitespace */
138         if(*i <= 32) ++i;
139 
140         /* Comment */
141         else if(*i == '/' && i + 1 < data.end() && (i[1] == '*' || i[1] == '/'))
142         {
143             /* Single-line comment */
144             if(i[1] == '/') for(const char* j = i + 2; j != data.end(); ++j) {
145                 if(*j == '\n') {
146                     i = j + 1;
147                     break;
148                 }
149 
150             /* Multi-line comment */
151             } else for(const char* j = i + 2; j != data.end(); ++j) {
152                 if(*j == '*' && j + 1 != data.end() && *(j + 1) == '/') {
153                     i = j + 2;
154                     break;
155                 }
156             }
157         }
158 
159         /* Something else, done */
160         else break;
161     }
162 
163     return i;
164 }
165 
escapedChar(const Containers::ArrayView<const char> data,ParseError & error)166 std::pair<const char*, char> escapedChar(const Containers::ArrayView<const char> data, ParseError& error) {
167     /* Escape sequence is not standalone, thus we can't expect it anywhere */
168     CORRADE_INTERNAL_ASSERT(!data.empty() && *data == '\\');
169 
170     if(data.size() < 2) {
171         error = {ParseErrorType::InvalidEscapeSequence, data};
172         return {};
173     }
174 
175     /* Two-character escape */
176     switch(data[1]) {
177         case '\\': return {data + 2, '\\'};
178         case '\'': return {data + 2, '\\'};
179         case 'a':  return {data + 2, '\a'};
180         case 'b':  return {data + 2, '\b'};
181         case 'f':  return {data + 2, '\f'};
182         case 'n':  return {data + 2, '\n'};
183         case 'r':  return {data + 2, '\r'};
184         case 't':  return {data + 2, '\t'};
185         case 'v':  return {data + 2, '\v'};
186         case '?':
187         case '"':
188             return {data + 2, data[1]};
189     }
190 
191     /* Four-character escape */
192     if(data.size() >= 4 && data[1] == 'x' && isBaseN<16>(data[2]) && isBaseN<16>(data[3]))
193         return {data + 4, parseHex<char>(data + 2)};
194 
195     error = {ParseErrorType::InvalidEscapeSequence, data};
196     return {};
197 }
198 
escapedUnicode(const Containers::ArrayView<const char> data,std::string & out,ParseError & error)199 const char* escapedUnicode(const Containers::ArrayView<const char> data, std::string& out, ParseError& error) {
200     /* Escape sequence is not standalone, thus we can't expect it anywhere */
201     CORRADE_INTERNAL_ASSERT(!data.empty() && *data == '\\');
202 
203     if(data.size() < 2) {
204         error = {ParseErrorType::InvalidEscapeSequence, data};
205         return {};
206     }
207 
208     if(data.size() >= 6 && data[1] == 'u') {
209         Warning() << "Trade::OpenGexImporter::openData(): Unicode parsing not implemented";
210         out += '?';
211         return data + 6;
212     }
213 
214     if(data.size() >= 8 && data[1] == 'U') {
215         Warning() << "Trade::OpenGexImporter::openData(): Unicode parsing not implemented";
216         out += '?';
217         return data + 8;
218     }
219 
220     char result;
221     const char* end;
222     std::tie(end, result) = escapedChar(data, error);
223     out += result;
224     return end;
225 }
226 
identifier(const Containers::ArrayView<const char> data,ParseError & error)227 const char* identifier(const Containers::ArrayView<const char> data, ParseError& error) {
228     /* Propagate errors */
229     if(!data) return nullptr;
230 
231     if(data.empty()) {
232         error = {ParseErrorType::ExpectedIdentifier};
233         return nullptr;
234     }
235 
236     if(!isAlpha(*data) && *data != '_') {
237         error = {ParseErrorType::InvalidIdentifier, data};
238         return nullptr;
239     }
240 
241     const char* i = data + 1;
242     for(; i != data.end(); ++i) {
243         const char c = *i;
244 
245         if(!isAlphaDecimal(c) && c != '_') break;
246     }
247 
248     return i;
249 }
250 
boolLiteral(const Containers::ArrayView<const char> data,ParseError & error)251 std::pair<const char*, bool> boolLiteral(const Containers::ArrayView<const char> data, ParseError& error) {
252     /* Propagate errors */
253     if(!data) return {};
254 
255     if(const char* const end = prefix(data, "true")) return {end, true};
256     if(const char* const end = prefix(data, "false")) return {end, false};
257 
258     error = {ParseErrorType::InvalidLiteral, Type::Bool, data};
259     return {};
260 }
261 
characterLiteral(const Containers::ArrayView<const char> data,ParseError & error)262 std::pair<const char*, char> characterLiteral(const Containers::ArrayView<const char> data, ParseError& error) {
263     if(data.size() >= 3 && data[0] == '\'') {
264         /* Allowed ASCII character */
265         if(((data[1] >= 0x20 && data[1] < '\'') ||
266             (data[1] > '\'' && data[1] < '\\') ||
267             (data[1] > '\\' && data[1] <= 0x7e)) && data[2] == '\'')
268             return {data + 3, data[1]};
269 
270         /* Escaped char */
271         if(data[1] == '\\') {
272             const char* i;
273             char result;
274             std::tie(i, result) = escapedChar(data.suffix(1), error);
275             if(i && i != data.end() && *i == '\'') return {i + 1, result};
276         }
277     }
278 
279     error = {ParseErrorType::InvalidCharacterLiteral, data};
280     return {};
281 }
282 
283 namespace {
284 
285 template<class> struct IntegralType;
286 template<> struct IntegralType<Float> { typedef UnsignedInt Type; };
287 template<> struct IntegralType<Double> {
288     #ifndef CORRADE_TARGET_EMSCRIPTEN
289     typedef UnsignedLong Type;
290     #else
291     typedef UnsignedInt Type;
292     #endif
293 };
294 template<class T> using IntegralTypeFor = typename IntegralType<T>::Type;
295 
296 template<class> struct ExtractToType;
297 /** @todo isn't there something better for extracting to unsigned int on webgl? */
298 #ifndef CORRADE_TARGET_EMSCRIPTEN
299 template<> struct ExtractToType<UnsignedLong> {
300     typedef UnsignedLong Type;
extractMagnum::OpenDdl::Implementation::__anona1fcb7be0211::ExtractToType301     static UnsignedLong extract(const std::string& buffer, Int base) {
302         return std::stoul(buffer, nullptr, base);
303     }
304 };
305 template<> struct ExtractToType<UnsignedByte>: ExtractToType<UnsignedLong> {};
306 template<> struct ExtractToType<Byte>: ExtractToType<UnsignedLong> {};
307 template<> struct ExtractToType<UnsignedShort>: ExtractToType<UnsignedLong> {};
308 template<> struct ExtractToType<Short>: ExtractToType<UnsignedLong> {};
309 template<> struct ExtractToType<UnsignedInt>: ExtractToType<UnsignedLong> {};
310 template<> struct ExtractToType<Int>: ExtractToType<UnsignedLong> {};
311 template<> struct ExtractToType<Long>: ExtractToType<UnsignedLong> {};
312 #ifdef CORRADE_TARGET_APPLE
313 template<> struct ExtractToType<unsigned long>: ExtractToType<UnsignedLong> {};
314 template<> struct ExtractToType<long>: ExtractToType<UnsignedLong> {};
315 #endif
316 #else
317 template<> struct ExtractToType<UnsignedInt> {
318     typedef UnsignedInt Type;
extractMagnum::OpenDdl::Implementation::__anona1fcb7be0211::ExtractToType319     static UnsignedInt extract(const std::string& buffer, Int base) {
320         return std::stoi(buffer, nullptr, base);
321     }
322 };
323 template<> struct ExtractToType<UnsignedByte>: ExtractToType<UnsignedInt> {};
324 template<> struct ExtractToType<Byte>: ExtractToType<UnsignedInt> {};
325 template<> struct ExtractToType<UnsignedShort>: ExtractToType<UnsignedInt> {};
326 template<> struct ExtractToType<Short>: ExtractToType<UnsignedInt> {};
327 template<> struct ExtractToType<Int>: ExtractToType<UnsignedInt> {};
328 /* Emscripten 1.38.10 and newer has std::size_t defined as unsigned long, while
329    it was unsigned int before. We should support both cases,
330    integralLiteral<std::size_t>() is used in some places in the parsers. */
331 static_assert(sizeof(unsigned long) == 4, "unsigned long is not four bytes on Emscripten");
332 template<> struct ExtractToType<unsigned long>: ExtractToType<UnsignedInt> {};
333 #endif
334 template<class T> using ExtractedType = typename ExtractToType<T>::Type;
335 
336 template<> struct ExtractToType<Float> {
extractMagnum::OpenDdl::Implementation::__anona1fcb7be0211::ExtractToType337     static Float extract(const std::string& buffer) {
338         return std::stof(buffer);
339     }
340 };
341 template<> struct ExtractToType<Double> {
extractMagnum::OpenDdl::Implementation::__anona1fcb7be0211::ExtractToType342     static Double extract(const std::string& buffer) {
343         return std::stod(buffer);
344     }
345 };
346 
347 template<class> constexpr Type typeFor();
348 #define _c(T) template<> constexpr Type typeFor<T>() { return Type::T; }
349 _c(UnsignedByte)
_c(Byte)350 _c(Byte)
351 _c(UnsignedShort)
352 _c(Short)
353 _c(UnsignedInt)
354 _c(Int)
355 #ifndef CORRADE_TARGET_EMSCRIPTEN
356 _c(UnsignedLong)
357 _c(Long)
358 #else
359 /* Emscripten 1.38.10 and newer has std::size_t defined as unsigned long, while
360    it was unsigned int before. We should support both cases,
361    integralLiteral<std::size_t>() is used in some places in the parsers. */
362 static_assert(sizeof(unsigned long) == 4, "unsigned long is not four bytes on Emscripten");
363 template<> constexpr Type typeFor<unsigned long>() { return Type::UnsignedInt; }
364 #endif
365 _c(Float)
366 _c(Double)
367 #undef _c
368 #ifdef CORRADE_TARGET_APPLE
369 #if LONG_MAX == 0x7FFFFFFFFFFFFFFF
370 template<> constexpr Type typeFor<unsigned long>() { return Type::UnsignedLong; }
typeFor()371 template<> constexpr Type typeFor<long>() { return Type::Long; }
372 #elif LONG_MAX == 0x7FFFFFFF
373 template<> constexpr Type typeFor<unsigned long>() { return Type::UnsignedInt; }
374 template<> constexpr Type typeFor<long>() { return Type::Int; }
375 #elif !defined(LONG_MAX)
376 #error wat
377 #else
378 blah LONG_MAX i/ugh
379 //#error Unexpected size of builtin long type
380 #endif
381 #endif
382 
possiblyNumericCharacters(const Containers::ArrayView<const char> data)383 template<Int base> const char* possiblyNumericCharacters(const Containers::ArrayView<const char> data) {
384     /* Propagate errors */
385     if(!data) return {};
386 
387     const char* i = data;
388     while(i != data.end() && (isBaseN<base>(*i) || (i != data && *i == '_'))) ++i;
389 
390     return i;
391 }
392 
numericCharacters(const Containers::ArrayView<const char> data,ParseError & error)393 template<Int base, class T> const char* numericCharacters(const Containers::ArrayView<const char> data, ParseError& error) {
394     /* Propagate errors */
395     if(!data) return {};
396 
397     const char* const i = possiblyNumericCharacters<base>(data);
398 
399     if(i == data) {
400         error = {ParseErrorType::InvalidLiteral, typeFor<T>(), data};
401         return {};
402     }
403 
404     return i;
405 }
406 
baseNLiteral(const Containers::ArrayView<const char> data,std::string & buffer,ParseError & error)407 template<Int base, class T> std::pair<const char*, T> baseNLiteral(const Containers::ArrayView<const char> data, std::string& buffer, ParseError& error) {
408     /* Propagate errors */
409     if(!data) return {};
410 
411     const char* i = numericCharacters<base, T>(data, error);
412 
413     /* Propagate errors */
414     if(!i) return {};
415 
416     extractWithoutUnderscore(data.prefix(i), buffer);
417     const ExtractedType<T> out = ExtractToType<T>::extract(buffer, base);
418     if(out > ExtractedType<T>(std::numeric_limits<T>::max())) {
419         error = {ParseErrorType::LiteralOutOfRange, typeFor<T>(), data};
420         return {};
421     }
422 
423     return {i, T(out)};
424 }
425 
426 }
427 
integralLiteral(const Containers::ArrayView<const char> data,std::string & buffer,ParseError & error)428 template<class T> std::tuple<const char*, T, Int> integralLiteral(const Containers::ArrayView<const char> data, std::string& buffer, ParseError& error) {
429     /* Propagate errors */
430     if(!data) return {};
431 
432     if(data.empty()) {
433         error = {ParseErrorType::ExpectedLiteral, typeFor<T>(), data};
434         return {};
435     }
436 
437     const char* i = data;
438 
439     /* Sign */
440     T sign = T(1);
441     if(*i == '+') ++i;
442     else if(*i == '-') {
443         if(!std::is_signed<T>{}) {
444             error = {ParseErrorType::LiteralOutOfRange, typeFor<T>(), data};
445             return {};
446         }
447 
448         sign = T(-1);
449         ++i;
450     }
451 
452     T value;
453     Int base;
454 
455     /* Char literal */
456     if(i != data.end() && *i == '\'') {
457         base = 256;
458         std::tie(i, value) = characterLiteral(data.suffix(i), error);
459 
460     /* Binary/octal/hex literal */
461     } else if(i + 1 < data.end() && *i == '0' && isBinaryPrefix(i[1])) switch(i[1]) {
462         case 'x':
463         case 'X': {
464             base = 16;
465             std::tie(i, value) = baseNLiteral<16, T>(data.suffix(i + 2), buffer, error);
466             break;
467         }
468         case 'o':
469         case 'O': {
470             base = 8;
471             std::tie(i, value) = baseNLiteral<8, T>(data.suffix(i + 2), buffer, error);
472             break;
473         }
474         case 'b':
475         case 'B': {
476             base = 2;
477             std::tie(i, value) = baseNLiteral<2, T>(data.suffix(i + 2), buffer, error);
478             break;
479         }
480 
481         default: std::abort(); /* LCOV_EXCL_LINE */
482 
483     /* Decimal literal  */
484     } else {
485         base = 10;
486         std::tie(i, value) = baseNLiteral<10, T>(data.suffix(i), buffer, error);
487     }
488 
489     /** @todo C++14: use {} */
490     return std::make_tuple(i, sign*value, base);
491 }
492 
493 template std::tuple<const char*, UnsignedByte, Int> integralLiteral<UnsignedByte>(Containers::ArrayView<const char>, std::string&, ParseError&);
494 template std::tuple<const char*, Byte, Int> integralLiteral<Byte>(Containers::ArrayView<const char>, std::string&, ParseError&);
495 template std::tuple<const char*, UnsignedShort, Int> integralLiteral<UnsignedShort>(Containers::ArrayView<const char>, std::string&, ParseError&);
496 template std::tuple<const char*, Short, Int> integralLiteral<Short>(Containers::ArrayView<const char>, std::string&, ParseError&);
497 template std::tuple<const char*, UnsignedInt, Int> integralLiteral<UnsignedInt>(Containers::ArrayView<const char>, std::string&, ParseError&);
498 template std::tuple<const char*, Int, Int> integralLiteral<Int>(Containers::ArrayView<const char>, std::string&, ParseError&);
499 #ifdef CORRADE_TARGET_APPLE
500 template std::tuple<const char*, unsigned long, Int> integralLiteral<unsigned long>(Containers::ArrayView<const char>, std::string&, ParseError&);
501 template std::tuple<const char*, long, Int> integralLiteral<long>(Containers::ArrayView<const char>, std::string&, ParseError&);
502 #endif
503 #ifndef CORRADE_TARGET_EMSCRIPTEN
504 template std::tuple<const char*, UnsignedLong, Int> integralLiteral<UnsignedLong>(Containers::ArrayView<const char>, std::string&, ParseError&);
505 template std::tuple<const char*, Long, Int> integralLiteral<Long>(Containers::ArrayView<const char>, std::string&, ParseError&);
506 #else
507 /* Emscripten 1.38.10 and newer has std::size_t defined as unsigned long, while
508    it was unsigned int before. We should support both cases,
509    integralLiteral<std::size_t>() is used in some places in the parsers. */
510 static_assert(sizeof(unsigned long) == 4, "unsigned long is not four bytes on Emscripten");
511 template std::tuple<const char*, unsigned long, Int> integralLiteral<unsigned long>(Containers::ArrayView<const char>, std::string&, ParseError&);
512 #endif
513 
floatingPointLiteral(const Containers::ArrayView<const char> data,std::string & buffer,ParseError & error)514 template<class T> std::pair<const char*, T> floatingPointLiteral(const Containers::ArrayView<const char> data, std::string& buffer, ParseError& error) {
515     /* Propagate errors */
516     if(!data) return {};
517 
518     if(data.empty()) {
519         error = {ParseErrorType::ExpectedLiteral, Type::Float, data};
520         return {};
521     }
522 
523     const char* i = data;
524 
525     /* Sign */
526     T sign = T(1);
527     if(*i == '+') ++i;
528     else if(*i == '-') {
529         sign = T(-1);
530         ++i;
531     }
532 
533     /* Binary literal */
534     if(i + 1 < data.end() && *i == '0' && isBinaryPrefix(i[1])) {
535         IntegralTypeFor<T> integralValue;
536         switch(i[1]) {
537             case 'x':
538             case 'X': {
539                 std::tie(i, integralValue) = baseNLiteral<16, IntegralTypeFor<T>>(data.suffix(i + 2), buffer, error);
540                 break;
541             }
542             case 'o':
543             case 'O': {
544                 std::tie(i, integralValue) = baseNLiteral<8, IntegralTypeFor<T>>(data.suffix(i + 2), buffer, error);
545                 break;
546             }
547             case 'b':
548             case 'B': {
549                 std::tie(i, integralValue) = baseNLiteral<2, IntegralTypeFor<T>>(data.suffix(i + 2), buffer, error);
550                 break;
551             }
552 
553             default: std::abort(); /* LCOV_EXCL_LINE */
554         }
555 
556         const T floatValue = reinterpret_cast<T&>(integralValue);
557         return {i, sign*floatValue};
558     }
559 
560     /* Decimal before dot */
561     const char* const before = i;
562     i = possiblyNumericCharacters<10>(data.suffix(i));
563 
564     /* Dot and decimal after dot */
565     if(i && i + 1 < data.end() && *i == '.') {
566         i = possiblyNumericCharacters<10>(data.suffix(i + 1));
567 
568         /* Expecting at least .0 or 0. */
569         if(before + 1 == i) {
570             error = {ParseErrorType::InvalidLiteral, typeFor<T>(), data};
571             return {};
572         }
573 
574     /* Expecting at least one numeric character */
575     } else if(before == i) {
576         error = {ParseErrorType::InvalidLiteral, typeFor<T>(), data};
577         return {};
578     }
579 
580     /* Exponent etc */
581     if(i && i != data.end() && (*i == 'e' || *i == 'E')) {
582         ++i;
583 
584         /* Exponent sign */
585         if(*i == '+' || *i == '-') ++i;
586 
587         i = numericCharacters<10, T>(data.suffix(i), error);
588     }
589 
590     /* Propagate errors */
591     if(!i) return {};
592 
593     /** @todo verifying out-of-range */
594 
595     extractWithoutUnderscore(data.prefix(i), buffer);
596     return {i, ExtractToType<T>::extract(buffer)};
597 }
598 
599 template std::pair<const char*, Float> floatingPointLiteral<Float>(Containers::ArrayView<const char>, std::string&, ParseError&);
600 template std::pair<const char*, Double> floatingPointLiteral<Double>(Containers::ArrayView<const char>, std::string&, ParseError&);
601 
stringLiteral(const Containers::ArrayView<const char> data,ParseError & error)602 std::pair<const char*, std::string> stringLiteral(const Containers::ArrayView<const char> data, ParseError& error) {
603     /* Propagate errors */
604     if(!data) return {};
605 
606     if(data.empty() || *data != '"') {
607         error = {ParseErrorType::ExpectedLiteral, Type::String, data};
608         return {};
609     }
610 
611     std::string out;
612 
613     for(const char* i = data + 1; i != data.end(); ) {
614         const char c = *i;
615 
616         if(UnsignedByte(c) < 0x20) {
617             error = {ParseErrorType::InvalidLiteral, Type::String, i};
618             return {};
619         }
620 
621         /* Escaped character */
622         if(c == '\\')
623             i = escapedUnicode(data.suffix(i), out, error);
624 
625         /* End of string, try searching for continuation */
626         else if(c == '"') {
627             const char* j = whitespace(data.suffix(i + 1));
628 
629             /* Continuation not found, done */
630             if(j + 1 >= data.end() || *j != '"')
631                 return {j, out};
632 
633             i = j + 1;
634 
635         /* Any other character, append it to result */
636         } else {
637             out += c;
638             ++i;
639         }
640     }
641 
642     error = {ParseErrorType::LiteralOutOfRange, Type::String};
643     return {};
644 }
645 
nameLiteral(const Containers::ArrayView<const char> data,ParseError & error)646 std::pair<const char*, std::string> nameLiteral(const Containers::ArrayView<const char> data, ParseError& error) {
647     /* Propagate errors */
648     if(!data) return {};
649 
650     if(data.empty()) {
651         error = {ParseErrorType::ExpectedName, data};
652         return {};
653     }
654 
655     const char* i = data;
656     if(*i != '$' && *i != '%') {
657         error = {ParseErrorType::InvalidName, data};
658         return {};
659     }
660 
661     i = identifier(data.suffix(data + 1), error);
662     return {i, i ? std::string{data, std::size_t(i - data)} : std::string{}};
663 }
664 
referenceLiteral(const Containers::ArrayView<const char> data,ParseError & error)665 std::pair<const char*, Containers::ArrayView<const char>> referenceLiteral(const Containers::ArrayView<const char> data, ParseError& error) {
666     /* Propagate errors */
667     if(!data) return {};
668 
669     if(data.empty()) {
670         error = {ParseErrorType::ExpectedLiteral, Type::Reference};
671         return {};
672     }
673 
674     if(const char* const end = prefix(data, "null"))
675         return {end, nullptr};
676 
677     const char* i = data;
678     if(*i != '$' && *i != '%') {
679         error = {ParseErrorType::InvalidLiteral, Type::Reference, data};
680         return {};
681     }
682 
683     i = identifier(data.suffix(1), error);
684 
685     for(; i && i != data.end(); ) {
686         if(*i != '%') break;
687         i = identifier(data.suffix(i + 1), error);
688     }
689 
690     return {i, i ? data.prefix(i) : nullptr};
691 }
692 
possiblyTypeLiteral(const Containers::ArrayView<const char> data)693 std::pair<const char*, Type> possiblyTypeLiteral(const Containers::ArrayView<const char> data) {
694     #define _c(identifier, type) \
695         if(const char* const c = prefix(data, #identifier)) return {c, Type::type};
696     _c(bool, Bool)
697     _c(unsigned_int8, UnsignedByte)
698     _c(int8, Byte)
699     _c(unsigned_int16, UnsignedShort)
700     _c(int16, Short)
701     _c(unsigned_int32, UnsignedInt)
702     _c(int32, Int)
703     #ifndef CORRADE_TARGET_EMSCRIPTEN
704     _c(unsigned_int64, UnsignedLong)
705     _c(int64, Long)
706     #endif
707     /** @todo Half */
708     _c(float, Float)
709     _c(double, Double)
710     _c(string, String)
711     _c(ref, Reference)
712     _c(type, Type)
713     #undef _c
714 
715     return {};
716 }
717 
typeLiteral(const Containers::ArrayView<const char> data,ParseError & error)718 std::pair<const char*, Type> typeLiteral(const Containers::ArrayView<const char> data, ParseError& error) {
719     /* Propagate errors */
720     if(!data) return {};
721 
722     if(data.empty()) {
723         error = {ParseErrorType::ExpectedLiteral, Type::Type, data};
724         return {};
725     }
726 
727     const char* i;
728     Type type;
729     std::tie(i, type) = possiblyTypeLiteral(data);
730     if(i) return {i, type};
731 
732     error = {ParseErrorType::InvalidLiteral, Type::Type, data};
733     return {};
734 }
735 
propertyValue(const Containers::ArrayView<const char> data,bool & boolValue,Int & integerValue,Float & floatingPointValue,std::string & stringValue,Containers::ArrayView<const char> & referenceValue,Type & typeValue,std::string & buffer,ParseError & error)736 std::pair<const char*, InternalPropertyType> propertyValue(const Containers::ArrayView<const char> data, bool& boolValue, Int& integerValue, Float& floatingPointValue, std::string& stringValue, Containers::ArrayView<const char>& referenceValue, Type& typeValue, std::string& buffer, ParseError& error) {
737     /* Propagate errors */
738     if(!data) return {};
739 
740     if(data.empty()) {
741         error = {ParseErrorType::ExpectedPropertyValue};
742         return {};
743     }
744 
745     const char* i = data;
746 
747     /* String literal */
748     if(*i == '"') {
749         std::tie(i, stringValue) = stringLiteral(data, error);
750         return {i, InternalPropertyType::String};
751     }
752 
753     /* Reference literal */
754     if(*i == '%' || *i == '$') {
755         std::tie(i, referenceValue) = referenceLiteral(data, error);
756         return {i, InternalPropertyType::Reference};
757     }
758 
759     /* Numeric literal */
760     if((*i >= '0' && *i <= '9') || *i == '.' || *i == '\'') {
761         /* Float literal if there is dot */
762         for(const char* j = i; j != data.end(); ++j) {
763             if(*j == '.') {
764                 std::tie(i, floatingPointValue) = floatingPointLiteral<Float>(data, buffer, error);
765                 return {i, InternalPropertyType::Float};
766             }
767 
768             if(*j != '+' && *j != '-' && *j != '_' && !isBaseN<10>(*j)) break;
769         }
770 
771         /* Integer literal otherwise */
772         Int base;
773         std::tie(i, integerValue, base) = integralLiteral<Int>(data, buffer, error);
774         switch(base) {
775             case 2:
776             case 8:
777             case 16: return {i, InternalPropertyType::Binary};
778             case 10: return {i, InternalPropertyType::Integral};
779             case 256: return {i, InternalPropertyType::Character};
780         }
781 
782         std::abort(); /* LCOV_EXCL_LINE */
783     }
784 
785     /* Null reference literal */
786     if(const char* const end = prefix(data, "null")) {
787         stringValue = "null";
788         return {end, InternalPropertyType::Reference};
789     }
790 
791     /* Boolean literals */
792     if(const char* const end = prefix(data, "true")) {
793         boolValue = true;
794         return {end, InternalPropertyType::Bool};
795     }
796     if(const char* const end = prefix(data, "false")) {
797         boolValue = false;
798         return {end, InternalPropertyType::Bool};
799     }
800 
801     /* Possibly type literal */
802     std::tie(i, typeValue) = possiblyTypeLiteral(data);
803     if(i) return {i, InternalPropertyType::Type};
804 
805     error = {ParseErrorType::InvalidPropertyValue, data};
806     return {};
807 }
808 
809 }}}
810