1#include "Foundation/Logging/Logger.h"
2#include "Preprocessor.h"
3#include "Services/Variables.h"
4#include "Resources/ResourceStore.h"
39 bool hasBeenActive =
false;
40 bool hasSeenElse =
false;
48 std::vector<FlowState> stack;
50 const char* in =
nullptr;
51 const char* end =
nullptr;
62 bool isSpace(
const char c)
65 case ' ':
case '\t':
case '\v':
case '\f':
72 bool isDigit(
const char c)
74 return '0' <= c && c <=
'9';
77 bool isIdentifierCharacter(
const char c)
80 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
81 case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
82 case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
83 case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
85 case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
86 case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
87 case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
88 case 'Y':
case 'Z':
case '_':
95 void nextToken(ParseContext* pc)
97 pc->matchedToken = pc->currentToken;
99 if (pc->end <= pc->in) {
100 pc->currentToken.kind = Token::Kind::Eof;
104 auto * token_start = pc->in;
111 pc->currentToken.kind = Token::Kind::NewLine;
115 case ' ':
case '\t':
case '\v':
case '\f':
116 do { pc->in++; }
while (pc->in < pc->end && isSpace(*pc->in));
121 pc->currentToken.kind = (Token::Kind)*pc->in;
123 if (pc->in < pc->end && *pc->in ==
'#') {
124 pc->currentToken.kind = Token::Kind::ConcatTokens;
130 pc->currentToken.kind = (Token::Kind)*pc->in;
132 if (pc->in < pc->end && *pc->in ==
'|') {
133 pc->currentToken.kind = Token::Kind::LogicalOr;
139 pc->currentToken.kind = (Token::Kind)*pc->in;;
141 if (pc->in < pc->end && *pc->in ==
'&') {
142 pc->currentToken.kind = Token::Kind::LogicalAnd;
148 pc->currentToken.kind = (Token::Kind)*pc->in;;
150 if (pc->in < pc->end && *pc->in ==
'=') {
151 pc->currentToken.kind = Token::Kind::Equality;
157 pc->currentToken.kind = (Token::Kind)*pc->in;;
159 if (pc->in < pc->end && *pc->in ==
'=') {
160 pc->currentToken.kind = Token::Kind::LessThanEqual;
163 else if (pc->in < pc->end && *pc->in ==
'<') {
164 pc->currentToken.kind = Token::Kind::ShiftLeft;
170 pc->currentToken.kind = (Token::Kind)*pc->in;;
172 if (pc->in < pc->end && *pc->in ==
'=') {
173 pc->currentToken.kind = Token::Kind::GreaterThanEqual;
176 else if (pc->in < pc->end && *pc->in ==
'>') {
177 pc->currentToken.kind = Token::Kind::ShiftRight;
183 pc->currentToken.kind = (Token::Kind)*pc->in;;
185 if (pc->in < pc->end && *pc->in ==
'=') {
186 pc->currentToken.kind = Token::Kind::Inequality;
192 pc->currentToken.kind = (Token::Kind)
'/';
194 if (pc->in < pc->end) {
195 if (*pc->in ==
'/') {
196 do { pc->in++; }
while (pc->in < pc->end && *pc->in !=
'\n');
199 else if (*pc->in ==
'*') {
201 if (pc->in < pc->end) {
203 while (pc->in < pc->end) {
204 if (pc->in[-1] ==
'*' && pc->in[0] ==
'/')
break;
208 if (pc->end <= pc->in) {
209 LOG_ERROR(logger,
"EOF while scanning for end of /* */-comment.");
220 pc->currentToken.kind = Token::Kind::String;
222 while (pc->in < pc->end && *pc->in !=
'"') pc->in++;
223 if (pc->end <= pc->in) {
224 LOG_ERROR(logger,
"EOF while scanning for end of string.");
231 case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
232 pc->currentToken.kind = Token::Kind::Number;
234 while (pc->in < pc->end && isDigit(*pc->in)) { pc->in++; }
237 case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
238 case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
239 case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
241 case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
242 case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
243 case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
244 case 'Y':
case 'Z':
case '_':
245 pc->currentToken.kind = Token::Kind::Identifier;
246 do pc->in++;
while (pc->in < pc->end && isIdentifierCharacter(*pc->in));
250 pc->currentToken.kind = (Token::Kind)*pc->in;
255 pc->currentToken.text =
Cogs::StringView(token_start, pc->in - token_start);
258 bool isToken(ParseContext* pc, Token::Kind kind)
260 return pc->currentToken.kind == kind;
263 bool expectToken(ParseContext* pc, Token::Kind kind,
const char* what)
265 if (isToken(pc, kind)) {
269 LOG_ERROR(logger,
"%s [got %.*s]", what,
static_cast<int>(pc->currentToken.text.length()), pc->currentToken.text.data());
274 bool matchToken(ParseContext* pc, Token::Kind kind)
276 if (isToken(pc, kind)) {
283 void skipRestOfLine(ParseContext* pc)
285 while (!(isToken(pc, Token::Kind::NewLine) || isToken(pc, Token::Kind::Eof))) nextToken(pc);
288 void seeIdentifier(ParseContext* pc)
290 pc->pp->identifiersSeen.insert(Strings::add(pc->currentToken.text));
295 auto ref = Strings::add(identifier);
296 return pc->pp->definitions.find(ref) != pc->pp->definitions.end() ? 1 : 0;
301 bool positive =
true;
303 const auto * p =
string.data();
304 const auto * e = p +
string.size();
305 while (p < e && isSpace(*p)) { p++; }
306 if (p < e && p[0] ==
'+') { p++; }
307 else if (p < e && p[1] ==
'-') { positive =
false; p++; }
309 while (p < e &&
'0' <= *p && *p <=
'9') { any =
true; rv = 10 * rv + (*p++ -
'0'); }
311 LOG_WARNING(logger,
"Non-digit characters in '%.*s' when converting to number",
static_cast<int>(
string.length()),
string.data());
314 LOG_WARNING(logger,
"No digits in '%.*s' when converting to number",
static_cast<int>(
string.length()),
string.data());
316 return positive ? rv : -rv;
319 long parseExpression(ParseContext* pc);
321 long parsePrimaryExpression(ParseContext* pc)
323 if (matchToken(pc, Token::Kind::Identifier)) {
326 if (pc->matchedToken.text ==
"defined") {
327 if (matchToken(pc, (Token::Kind)
'(')) {
328 if (!expectToken(pc, Token::Kind::Identifier,
"Expected identifier in defined expression")) {
return 0; }
329 long e = isDefined(pc, pc->matchedToken.text);
330 if (!expectToken(pc, (Token::Kind)
')',
"Expected closing ')' in defined expression")) {
return 0; }
333 else if (!expectToken(pc, Token::Kind::Identifier,
"Expected identifier in defined expression")) {
return 0; }
335 return isDefined(pc, pc->matchedToken.text);
341 auto ref = Strings::add(pc->matchedToken.text);
342 if (
auto it = pc->pp->definitions.find(ref); it != pc->pp->definitions.end()) {
343 return numberFromString(Strings::get(it->second));
346 LOG_WARNING(logger,
"Using undefined identifier '%.*s' in expression",
static_cast<int>(pc->matchedToken.text.size()), pc->matchedToken.text.data());
354 else if (matchToken(pc, Token::Kind::Number)) {
355 return numberFromString(pc->matchedToken.text);
359 else if (matchToken(pc, (Token::Kind)
'(')) {
360 long r = parseExpression(pc);
361 expectToken(pc, (Token::Kind)
')',
"Expected closing ')'");
366 LOG_ERROR(logger,
"Syntax error: %.*s",
static_cast<int>(pc->currentToken.text.size()), pc->currentToken.text.data());
372 long parseUnaryExpression(ParseContext* pc)
374 if (matchToken(pc, (Token::Kind)
'+')) {
return parseUnaryExpression(pc); }
375 else if (matchToken(pc, (Token::Kind)
'-')) {
return -parseUnaryExpression(pc); }
376 else if (matchToken(pc, (Token::Kind)
'~')) {
return ~parseUnaryExpression(pc); }
377 else if (matchToken(pc, (Token::Kind)
'!')) {
return !parseUnaryExpression(pc); }
378 else return parsePrimaryExpression(pc);
381 long parseMultiplicativeExpression(ParseContext* pc)
383 long e = parseUnaryExpression(pc);
385 if (matchToken(pc, (Token::Kind)
'*')) { e = e * parseUnaryExpression(pc); }
386 else if (matchToken(pc, (Token::Kind)
'/')) { e = e / parseUnaryExpression(pc); }
387 else if (matchToken(pc, (Token::Kind)
'%')) { e = e % parseUnaryExpression(pc); }
393 long parseAdditiveExpression(ParseContext* pc)
395 long e = parseMultiplicativeExpression(pc);
397 if (matchToken(pc, (Token::Kind)
'+')) { e = e + parseMultiplicativeExpression(pc); }
398 else if (matchToken(pc, (Token::Kind)
'-')) { e = e - parseMultiplicativeExpression(pc); }
404 long parseShiftExpression(ParseContext* pc)
406 long e = parseAdditiveExpression(pc);
408 if (matchToken(pc, Token::Kind::ShiftLeft)) { e = e << parseAdditiveExpression(pc); }
409 else if (matchToken(pc, Token::Kind::ShiftRight)) { e = e >> parseAdditiveExpression(pc); }
415 long parseRelationalExpression(ParseContext* pc)
417 long e = parseShiftExpression(pc);
419 if (matchToken(pc, (Token::Kind)
'<')) { e = e < parseShiftExpression(pc); }
420 else if (matchToken(pc, (Token::Kind)
'>')) { e = e > parseShiftExpression(pc); }
421 else if (matchToken(pc, Token::Kind::LessThanEqual)) { e = e <= parseShiftExpression(pc); }
422 else if (matchToken(pc, Token::Kind::GreaterThanEqual)) { e = e >= parseShiftExpression(pc); }
428 long parseEqualityExpression(ParseContext* pc)
430 long e = parseRelationalExpression(pc);
432 if (matchToken(pc, Token::Kind::Equality)) { e = e == parseRelationalExpression(pc); }
433 else if (matchToken(pc, Token::Kind::Inequality)) { e = e != parseRelationalExpression(pc); }
439 long parseAndExpression(ParseContext* pc)
441 long e = parseEqualityExpression(pc);
442 while (matchToken(pc, (Token::Kind)
'&')) { e = e & parseEqualityExpression(pc); }
446 long parseExclusiveOrExpression(ParseContext* pc)
448 long e = parseAndExpression(pc);
449 while (matchToken(pc, (Token::Kind)
'^')) { e = e ^ parseAndExpression(pc); }
453 long parseInclusiveOrExpression(ParseContext* pc)
455 long e = parseExclusiveOrExpression(pc);
456 while (matchToken(pc, (Token::Kind)
'|')) { e = e | parseExclusiveOrExpression(pc); }
460 long parseLogicalAndExpression(ParseContext* pc)
462 long e = parseInclusiveOrExpression(pc);
463 while (matchToken(pc, Token::Kind::LogicalAnd)) {
464 long r = parseInclusiveOrExpression(pc);
470 long parseLogicalOrExpression(ParseContext* pc)
472 long e = parseLogicalAndExpression(pc);
473 while(matchToken(pc, Token::Kind::LogicalOr)) {
474 long r = parseLogicalAndExpression(pc);
480 long parseConditionalExpression(ParseContext* pc)
482 long e = parseLogicalOrExpression(pc);
483 if (isToken(pc, (Token::Kind)
'?')) {
485 long t = parseExpression(pc);
486 if (!expectToken(pc, (Token::Kind)
':',
"Expected ':' in ternary expression")) {
return 0; }
487 long f = parseConditionalExpression(pc);
493 long parseExpression(ParseContext* pc)
495 return parseConditionalExpression(pc);
498 void parseDefineDirectiveArgs(ParseContext* pc,
const Cogs::StringView& identifier)
500 LOG_DEBUG(logger,
"define %.*s has args, ignoring",
static_cast<int>(identifier.
length()), identifier.
data());
503 if (isToken(pc, Token::Kind::Eof)) {
504 LOG_ERROR(logger,
"EOF while scanning for args of define %.*s",
static_cast<int>(identifier.
length()), identifier.
data());
508 else if (isToken(pc, Token::Kind::NewLine)) {
509 LOG_ERROR(logger,
"Newline while scanning for args of define %.*s",
static_cast<int>(identifier.
length()), identifier.
data());
513 }
while (!isToken(pc, (Token::Kind)
')'));
517 void parseDefineDirective(ParseContext* pc)
519 if (!pc->stack.back().active) { skipRestOfLine(pc);
return; }
521 if (!expectToken(pc, Token::Kind::Identifier,
"Expected identifier in define directive")) {
return; }
523 auto identifier = pc->matchedToken.text;
526 if (isToken(pc, (Token::Kind)
'(') && (pc->currentToken.text.begin() == pc->matchedToken.text.end())) {
528 parseDefineDirectiveArgs(pc, identifier);
532 while (!isToken(pc, Token::Kind::Eof)) {
533 if (isToken(pc, Token::Kind::NewLine)) {
534 if (pc->matchedToken.kind == (Token::Kind)
'\\') {
535 LOG_DEBUG(logger,
"Line splicing.");
539 else if (isToken(pc, Token::Kind::Identifier)) {
540 if (!value.empty() && pc->matchedToken.kind == Token::Kind::Identifier) {
543 assert(pc->currentToken.text.empty() ==
false);
547 if (
auto it = pc->pp->definitions.find(Strings::add(t)); it != pc->pp->definitions.end()) {
548 t = Strings::get(it->second);
550 value.append(t.data(), t.data() + t.size());
553 value.append(pc->currentToken.text.data(), pc->currentToken.text.data() + pc->currentToken.text.size());
559 auto identifierRef = Strings::add(identifier);
560 auto valueRef = Strings::add(value);
562 pc->pp->definitions[identifierRef] = valueRef;
565 void parseUndefDirective(ParseContext* pc)
567 if (!pc->stack.back().active) { skipRestOfLine(pc);
return; }
569 if (!expectToken(pc, Token::Kind::Identifier,
"Expected identifier in undef directive")) {
return; }
571 if (
auto it = pc->pp->definitions.find(Strings::add(pc->matchedToken.text)); it != pc->pp->definitions.end()) {
572 pc->pp->definitions.erase(it);
575 LOG_WARNING(logger,
"Unable to undef unknown identifier \"%.*s\"",
static_cast<int>(pc->matchedToken.text.size()), pc->matchedToken.text.data());
579 void parseIncludeDirective(ParseContext * pc)
581 if (!pc->stack.back().active) { skipRestOfLine(pc);
return; }
583 if (!expectToken(pc, Token::Kind::String,
"Expected path in include directive")) {
return; }
585 assert(pc->matchedToken.text.length() >= 2);
586 assert(pc->matchedToken.text[0] ==
'"');
587 assert(pc->matchedToken.text[pc->matchedToken.text.length() - 1] ==
'"');
588 Cogs::StringView path(pc->matchedToken.text.data() + 1, pc->matchedToken.text.size() - 2);
590 auto maxDepth = (unsigned)std::max(0, pc->context->variables->get(
"preprocessor.maxDepth", 8));
591 if (maxDepth <= pc->depth + 1) {
592 LOG_ERROR(logger,
"Maximum recursion depth reached when trying to include \"%.*s\"",
static_cast<int>(path.length()), path.data());
597 auto contents = pc->context->resourceStore->getResourceContents(path);
598 if (!contents.buffer) {
599 LOG_ERROR(logger,
"Unable to retrieve resource \"%.*s\"",
static_cast<int>(path.length()), path.data());
606 pc->pp->processed.append(
"\n// \"");
607 pc->pp->processed.append(path.data(), path.size());
608 pc->pp->processed.append(
"\" {\n");
609 if (!parseStart(pc->context,
611 Cogs::StringView(
reinterpret_cast<const char*
>(contents.data()), contents.size()),
614 LOG_ERROR(logger,
"Error while parsing included file \"%.*s\"",
static_cast<int>(path.length()), path.data());
617 pc->pp->processed.append(
"// } of \"");
618 pc->pp->processed.append(path.data(), path.size());
619 pc->pp->processed.append(
"\"\n\n");
624 void parseIfDirective(ParseContext* pc)
626 assert(!pc->stack.empty());
627 if (pc->stack.back().active) {
628 pc->stack.push_back(parseExpression(pc) != 0 ? FlowState{
true } : FlowState{
false });
632 pc->stack.push_back(FlowState{
false });
636 void parseIfDefinedDirective(ParseContext* pc,
bool negate)
638 assert(!pc->stack.empty());
639 if (pc->stack.back().active) {
640 if (expectToken(pc, Token::Kind::Identifier,
"Expected identifier in if[n]def directive")) {
641 if (isDefined(pc, pc->matchedToken.text)) {
642 pc->stack.push_back(negate ? FlowState{
false } : FlowState{
true });
645 pc->stack.push_back(negate ? FlowState{
true } : FlowState{
false });
651 pc->stack.push_back(FlowState{
false });
655 void parseElIfDirective(ParseContext* pc)
657 if (pc->stack.size() < 2) {
658 LOG_ERROR(logger,
"#elif without matching preceding #if");
662 if (pc->stack[pc->stack.size() - 2].active) {
664 if (pc->stack.back().hasSeenElse) {
665 LOG_ERROR(logger,
"#elif after #else in chain.");
670 else if (pc->stack.back().active) {
671 pc->stack.back().active =
false;
672 pc->stack.back().hasBeenActive =
true;
677 else if (pc->stack.back().hasBeenActive ==
false) {
678 assert(pc->stack.back().active ==
false);
679 pc->stack.back().active = parseExpression(pc) != 0;
684 assert(pc->stack.back().active ==
false);
685 assert(pc->stack.back().hasBeenActive ==
true);
691 assert(pc->stack.back().active ==
false);
696 void parseElseDirective(ParseContext* pc)
698 if (pc->stack.size() < 2) {
699 LOG_ERROR(logger,
"#else without matching preceding #if");
703 if (pc->stack[pc->stack.size() - 2].active) {
705 if (pc->stack.back().hasSeenElse) {
706 LOG_ERROR(logger,
"Multiple #else's in chain.");
711 else if (pc->stack.back().active) {
712 pc->stack.back().active =
false;
713 pc->stack.back().hasBeenActive =
true;
714 pc->stack.back().hasSeenElse =
true;
718 else if (pc->stack.back().hasBeenActive ==
false) {
719 pc->stack.back().active =
true;
720 pc->stack.back().hasSeenElse =
true;
725 assert(pc->stack.back().active ==
false);
726 assert(pc->stack.back().hasBeenActive ==
true);
727 assert(pc->stack.back().hasSeenElse ==
false);
731 assert(pc->stack.back().active ==
false);
736 void parseEndifDirective(ParseContext* pc)
738 if (pc->stack.size() < 2) {
739 LOG_ERROR(logger,
"#endif without matching preceding #if");
743 pc->stack.pop_back();
749 pc.context = context;
753 pc.in = input.data();
754 pc.end = input.data() + input.length();
755 pc.stack.push_back(FlowState{
true });
759 while (pc.currentToken.kind != Token::Eof && !pc.error) {
760 const char* lineStart = pc.currentToken.text.data();
762 bool forward =
false;
765 if (matchToken(&pc, (Token::Kind)
'#')) {
766 if (!expectToken(&pc, Token::Kind::Identifier,
"Expected '#' to be followed by identifier")) {
break; }
767 switch (pc.matchedToken.text.hash()) {
769 case
Cogs::hash(
"extension"):
774 case Cogs::hash(
"include"): parseIncludeDirective(&pc);
break;
775 case Cogs::hash(
"define"): parseDefineDirective(&pc); forward =
true;
break;
776 case Cogs::hash(
"undef"): parseUndefDirective(&pc); forward =
true;
break;
777 case Cogs::hash(
"if"): parseIfDirective(&pc);
break;
778 case Cogs::hash(
"ifdef"): parseIfDefinedDirective(&pc, false);
break;
779 case Cogs::hash(
"ifndef"): parseIfDefinedDirective(&pc, true);
break;
780 case Cogs::hash(
"elif"): parseElIfDirective(&pc);
break;
781 case Cogs::hash(
"else"): parseElseDirective(&pc);
break;
782 case Cogs::hash(
"endif"): parseEndifDirective(&pc);
break;
785 LOG_WARNING(logger,
"Unhandled directive '%.*s'",
static_cast<int>(pc.matchedToken.text.length()), pc.matchedToken.text.data());
793 else if (pc.stack.back().active) {
795 while (!(pc.error || isToken(&pc, Token::Kind::NewLine) || isToken(&pc, Token::Kind::Eof))) {
796 if (isToken(&pc, Token::Kind::Identifier)) {
804 assert(!pc.stack.back().active);
808 if (pc.stack.back().active && forward) {
809 if (lineStart < pc.currentToken.text.data()) {
812 while (input.data() < lineStart && (lineStart[-1] ==
' ' || lineStart[-1] ==
'\t')) { lineStart--; }
813 pc.pp->processed.append(lineStart, pc.currentToken.text.data());
814 pc.pp->processed.append(
"\n");
818 assert(pc.error || isToken(&pc, Token::Kind::NewLine) || isToken(&pc, Token::Kind::Eof));
822 if (pc.stack.size() != 1) {
823 LOG_ERROR(logger,
"Unbalanced #if/#endif");
834 return parseStart(context,
this, input, 0);
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Log implementation class.
Provides a weakly referenced view over the contents of a string.
constexpr const char * data() const noexcept
Get the sequence of characters referenced by the string view.
constexpr size_t length() const noexcept
Get the length of the string.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Contains all Cogs related functionality.
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
bool process(Context *context, const StringView input)
Run a text block through the preprocessor.