Cogs.Core
Preprocessor.cpp
1#include "Foundation/Logging/Logger.h"
2#include "Preprocessor.h"
3#include "Services/Variables.h"
4#include "Resources/ResourceStore.h"
5#include "Context.h"
6#include <sstream>
7
8namespace {
9 using namespace Cogs::Core;
10 Cogs::Logging::Log logger = Cogs::Logging::getLogger("Preprocessor");
11
12 struct Token
13 {
14 enum Kind
15 {
16 Eof = 0,
17 LastChar = 127,
18 NewLine,
19 String,
20 Number,
21 LogicalOr,
22 LogicalAnd,
23 Equality,
24 Inequality,
25 LessThanEqual,
26 GreaterThanEqual,
27 ShiftLeft,
28 ShiftRight,
29 ConcatTokens,
30 Identifier
31 } kind = Kind::Eof;
32
34 };
35
36 struct FlowState
37 {
38 bool active = false;
39 bool hasBeenActive = false;
40 bool hasSeenElse = false;
41 };
42
43 struct ParseContext
44 {
45 Context* context = nullptr;
46 PartialPreprocessor* pp = nullptr;
47
48 std::vector<FlowState> stack;
49
50 const char* in = nullptr;
51 const char* end = nullptr;
52 unsigned depth = 0;
53
54 bool error = false;
55
56 Token currentToken;
57 Token matchedToken;
58 };
59
60 bool parseStart(Context* context, PartialPreprocessor* pp, const Cogs::StringView input, unsigned depth);
61
62 bool isSpace(const char c)
63 {
64 switch (c) {
65 case ' ': case '\t': case '\v': case '\f':
66 return true;
67 default:
68 return false;
69 }
70 }
71
72 bool isDigit(const char c)
73 {
74 return '0' <= c && c <= '9';
75 }
76
77 bool isIdentifierCharacter(const char c)
78 {
79 switch (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':
84 case 'y': case 'z':
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 '_':
89 return true;
90 default:
91 return false;
92 }
93 }
94
95 void nextToken(ParseContext* pc)
96 {
97 pc->matchedToken = pc->currentToken;
98 start:
99 if (pc->end <= pc->in) {
100 pc->currentToken.kind = Token::Kind::Eof;
101 return;
102 }
103
104 auto * token_start = pc->in;
105 switch (*pc->in) {
106 case '\r':
107 pc->in++;
108 goto start;
109
110 case '\n':
111 pc->currentToken.kind = Token::Kind::NewLine;
112 pc->in++;
113 break;
114
115 case ' ': case '\t': case '\v': case '\f':
116 do { pc->in++; } while (pc->in < pc->end && isSpace(*pc->in));
117 goto start;
118 break;
119
120 case '#':
121 pc->currentToken.kind = (Token::Kind)*pc->in;
122 pc->in++;
123 if (pc->in < pc->end && *pc->in == '#') {
124 pc->currentToken.kind = Token::Kind::ConcatTokens;
125 pc->in++;
126 }
127 break;
128
129 case '|':
130 pc->currentToken.kind = (Token::Kind)*pc->in;
131 pc->in++;
132 if (pc->in < pc->end && *pc->in == '|') {
133 pc->currentToken.kind = Token::Kind::LogicalOr;
134 pc->in++;
135 }
136 break;
137
138 case '&':
139 pc->currentToken.kind = (Token::Kind)*pc->in;;
140 pc->in++;
141 if (pc->in < pc->end && *pc->in == '&') {
142 pc->currentToken.kind = Token::Kind::LogicalAnd;
143 pc->in++;
144 }
145 break;
146
147 case '=':
148 pc->currentToken.kind = (Token::Kind)*pc->in;;
149 pc->in++;
150 if (pc->in < pc->end && *pc->in == '=') {
151 pc->currentToken.kind = Token::Kind::Equality;
152 pc->in++;
153 }
154 break;
155
156 case '<':
157 pc->currentToken.kind = (Token::Kind)*pc->in;;
158 pc->in++;
159 if (pc->in < pc->end && *pc->in == '=') {
160 pc->currentToken.kind = Token::Kind::LessThanEqual;
161 pc->in++;
162 }
163 else if (pc->in < pc->end && *pc->in == '<') {
164 pc->currentToken.kind = Token::Kind::ShiftLeft;
165 pc->in++;
166 }
167 break;
168
169 case '>':
170 pc->currentToken.kind = (Token::Kind)*pc->in;;
171 pc->in++;
172 if (pc->in < pc->end && *pc->in == '=') {
173 pc->currentToken.kind = Token::Kind::GreaterThanEqual;
174 pc->in++;
175 }
176 else if (pc->in < pc->end && *pc->in == '>') {
177 pc->currentToken.kind = Token::Kind::ShiftRight;
178 pc->in++;
179 }
180 break;
181
182 case '!':
183 pc->currentToken.kind = (Token::Kind)*pc->in;;
184 pc->in++;
185 if (pc->in < pc->end && *pc->in == '=') {
186 pc->currentToken.kind = Token::Kind::Inequality;
187 pc->in++;
188 }
189 break;
190
191 case '/':
192 pc->currentToken.kind = (Token::Kind)'/';
193 pc->in++;
194 if (pc->in < pc->end) {
195 if (*pc->in == '/') {
196 do { pc->in++; } while (pc->in < pc->end && *pc->in != '\n');
197 goto start;
198 }
199 else if (*pc->in == '*') {
200 pc->in++;
201 if (pc->in < pc->end) {
202 pc->in++;
203 while (pc->in < pc->end) {
204 if (pc->in[-1] == '*' && pc->in[0] == '/') break;
205 pc->in++;
206 }
207 }
208 if (pc->end <= pc->in) {
209 LOG_ERROR(logger, "EOF while scanning for end of /* */-comment.");
210 pc->error = true;
211 goto done;
212 }
213 pc->in++;
214 goto start;
215 }
216 }
217 break;
218
219 case '"':
220 pc->currentToken.kind = Token::Kind::String;
221 pc->in++;
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.");
225 pc->error = true;
226 goto done;
227 }
228 pc->in++;
229 break;
230
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;
233 pc->in++;
234 while (pc->in < pc->end && isDigit(*pc->in)) { pc->in++; }
235 break;
236
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':
240 case 'y': case 'z':
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));
247 break;
248
249 default:
250 pc->currentToken.kind = (Token::Kind)*pc->in;
251 pc->in++;
252 break;
253 }
254 done:
255 pc->currentToken.text = Cogs::StringView(token_start, pc->in - token_start);
256 }
257
258 bool isToken(ParseContext* pc, Token::Kind kind)
259 {
260 return pc->currentToken.kind == kind;
261 }
262
263 bool expectToken(ParseContext* pc, Token::Kind kind, const char* what)
264 {
265 if (isToken(pc, kind)) {
266 nextToken(pc);
267 return true;
268 }
269 LOG_ERROR(logger, "%s [got %.*s]", what, static_cast<int>(pc->currentToken.text.length()), pc->currentToken.text.data());
270 pc->error = true;
271 return false;
272 }
273
274 bool matchToken(ParseContext* pc, Token::Kind kind)
275 {
276 if (isToken(pc, kind)) {
277 nextToken(pc);
278 return true;
279 }
280 return false;
281 }
282
283 void skipRestOfLine(ParseContext* pc)
284 {
285 while (!(isToken(pc, Token::Kind::NewLine) || isToken(pc, Token::Kind::Eof))) nextToken(pc);
286 }
287
288 void seeIdentifier(ParseContext* pc)
289 {
290 pc->pp->identifiersSeen.insert(Strings::add(pc->currentToken.text));
291 }
292
293 long isDefined(ParseContext* pc, Cogs::StringView identifier)
294 {
295 auto ref = Strings::add(identifier);
296 return pc->pp->definitions.find(ref) != pc->pp->definitions.end() ? 1 : 0;
297 }
298
299 long numberFromString(Cogs::StringView string)
300 {
301 bool positive = true;
302 long rv = 0;
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++; }
308 bool any = false;
309 while (p < e && '0' <= *p && *p <= '9') { any = true; rv = 10 * rv + (*p++ - '0'); }
310 if (p < e) {
311 LOG_WARNING(logger, "Non-digit characters in '%.*s' when converting to number", static_cast<int>(string.length()), string.data());
312 }
313 else if (!any) {
314 LOG_WARNING(logger, "No digits in '%.*s' when converting to number", static_cast<int>(string.length()), string.data());
315 }
316 return positive ? rv : -rv;
317 }
318
319 long parseExpression(ParseContext* pc);
320
321 long parsePrimaryExpression(ParseContext* pc)
322 {
323 if (matchToken(pc, Token::Kind::Identifier)) {
324
325 // defined <identifier> or defined ( <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; }
331 return e;
332 }
333 else if (!expectToken(pc, Token::Kind::Identifier, "Expected identifier in defined expression")) { return 0; }
334
335 return isDefined(pc, pc->matchedToken.text);
336 }
337
338 // Look up identifier and interpret value
339 else {
340
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));
344 }
345 else {
346 LOG_WARNING(logger, "Using undefined identifier '%.*s' in expression", static_cast<int>(pc->matchedToken.text.size()), pc->matchedToken.text.data());
347 return 0;
348 }
349 }
350
351 }
352
353 // Literal number
354 else if (matchToken(pc, Token::Kind::Number)) {
355 return numberFromString(pc->matchedToken.text);
356 }
357
358 // Expression on parantheses
359 else if (matchToken(pc, (Token::Kind)'(')) {
360 long r = parseExpression(pc);
361 expectToken(pc, (Token::Kind)')', "Expected closing ')'");
362 return r;
363 }
364
365 else {
366 LOG_ERROR(logger, "Syntax error: %.*s", static_cast<int>(pc->currentToken.text.size()), pc->currentToken.text.data());
367 return 0;
368 }
369
370 }
371
372 long parseUnaryExpression(ParseContext* pc)
373 {
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);
379 }
380
381 long parseMultiplicativeExpression(ParseContext* pc)
382 {
383 long e = parseUnaryExpression(pc);
384 while (true) {
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); }
388 else { break; }
389 }
390 return e;
391 }
392
393 long parseAdditiveExpression(ParseContext* pc)
394 {
395 long e = parseMultiplicativeExpression(pc);
396 while (true) {
397 if (matchToken(pc, (Token::Kind)'+')) { e = e + parseMultiplicativeExpression(pc); }
398 else if (matchToken(pc, (Token::Kind)'-')) { e = e - parseMultiplicativeExpression(pc); }
399 else { break; }
400 }
401 return e;
402 }
403
404 long parseShiftExpression(ParseContext* pc)
405 {
406 long e = parseAdditiveExpression(pc);
407 while (true) {
408 if (matchToken(pc, Token::Kind::ShiftLeft)) { e = e << parseAdditiveExpression(pc); }
409 else if (matchToken(pc, Token::Kind::ShiftRight)) { e = e >> parseAdditiveExpression(pc); }
410 else { break; }
411 }
412 return e;
413 }
414
415 long parseRelationalExpression(ParseContext* pc)
416 {
417 long e = parseShiftExpression(pc);
418 while (true) {
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); }
423 else { break; }
424 }
425 return e;
426 }
427
428 long parseEqualityExpression(ParseContext* pc)
429 {
430 long e = parseRelationalExpression(pc);
431 while (true) {
432 if (matchToken(pc, Token::Kind::Equality)) { e = e == parseRelationalExpression(pc); }
433 else if (matchToken(pc, Token::Kind::Inequality)) { e = e != parseRelationalExpression(pc); }
434 else { break; }
435 }
436 return e;
437 }
438
439 long parseAndExpression(ParseContext* pc)
440 {
441 long e = parseEqualityExpression(pc);
442 while (matchToken(pc, (Token::Kind)'&')) { e = e & parseEqualityExpression(pc); }
443 return e;
444 }
445
446 long parseExclusiveOrExpression(ParseContext* pc)
447 {
448 long e = parseAndExpression(pc);
449 while (matchToken(pc, (Token::Kind)'^')) { e = e ^ parseAndExpression(pc); }
450 return e;
451 }
452
453 long parseInclusiveOrExpression(ParseContext* pc)
454 {
455 long e = parseExclusiveOrExpression(pc);
456 while (matchToken(pc, (Token::Kind)'|')) { e = e | parseExclusiveOrExpression(pc); }
457 return e;
458 }
459
460 long parseLogicalAndExpression(ParseContext* pc)
461 {
462 long e = parseInclusiveOrExpression(pc);
463 while (matchToken(pc, Token::Kind::LogicalAnd)) {
464 long r = parseInclusiveOrExpression(pc); // avoid short-circuit
465 e = e && r;
466 }
467 return e;
468 }
469
470 long parseLogicalOrExpression(ParseContext* pc)
471 {
472 long e = parseLogicalAndExpression(pc);
473 while(matchToken(pc, Token::Kind::LogicalOr)) {
474 long r = parseLogicalAndExpression(pc); // avoid short-circuit
475 e = e || r;
476 }
477 return e;
478 }
479
480 long parseConditionalExpression(ParseContext* pc)
481 {
482 long e = parseLogicalOrExpression(pc);
483 if (isToken(pc, (Token::Kind)'?')) {
484 nextToken(pc);
485 long t = parseExpression(pc);
486 if (!expectToken(pc, (Token::Kind)':', "Expected ':' in ternary expression")) { return 0; }
487 long f = parseConditionalExpression(pc);
488 e = e ? t : f;
489 }
490 return e;
491 }
492
493 long parseExpression(ParseContext* pc)
494 {
495 return parseConditionalExpression(pc);
496 }
497
498 void parseDefineDirectiveArgs(ParseContext* pc, const Cogs::StringView& identifier)
499 {
500 LOG_DEBUG(logger, "define %.*s has args, ignoring", static_cast<int>(identifier.length()), identifier.data());
501 do {
502 nextToken(pc);
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());
505 pc->error = true;
506 return;
507 }
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());
510 pc->error = true;
511 return;
512 }
513 } while (!isToken(pc, (Token::Kind)')'));
514 nextToken(pc);
515 }
516
517 void parseDefineDirective(ParseContext* pc)
518 {
519 if (!pc->stack.back().active) { skipRestOfLine(pc); return; }
520
521 if (!expectToken(pc, Token::Kind::Identifier, "Expected identifier in define directive")) { return; }
522
523 auto identifier = pc->matchedToken.text;
524
525 // Identifier immediatly followed by a '(' with no white-space inbetween
526 if (isToken(pc, (Token::Kind)'(') && (pc->currentToken.text.begin() == pc->matchedToken.text.end())) {
527 nextToken(pc);
528 parseDefineDirectiveArgs(pc, identifier);
529 }
530
531 std::string value;
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.");
536 }
537 else { break; }
538 }
539 else if (isToken(pc, Token::Kind::Identifier)) {
540 if (!value.empty() && pc->matchedToken.kind == Token::Kind::Identifier) {
541 value.append(" ");
542 }
543 assert(pc->currentToken.text.empty() == false);
544
545 // Do substitution in defines
546 Cogs::StringView t = pc->currentToken.text;
547 if (auto it = pc->pp->definitions.find(Strings::add(t)); it != pc->pp->definitions.end()) {
548 t = Strings::get(it->second);
549 }
550 value.append(t.data(), t.data() + t.size());
551 }
552 else {
553 value.append(pc->currentToken.text.data(), pc->currentToken.text.data() + pc->currentToken.text.size());
554 }
555 nextToken(pc);
556 }
557 //LOG_DEBUG(logger, "#define '%.*s' as '%s'", identifier.length(), identifier.data(), value.c_str());
558
559 auto identifierRef = Strings::add(identifier);
560 auto valueRef = Strings::add(value);
561
562 pc->pp->definitions[identifierRef] = valueRef;
563 }
564
565 void parseUndefDirective(ParseContext* pc)
566 {
567 if (!pc->stack.back().active) { skipRestOfLine(pc); return; }
568
569 if (!expectToken(pc, Token::Kind::Identifier, "Expected identifier in undef directive")) { return; }
570
571 if (auto it = pc->pp->definitions.find(Strings::add(pc->matchedToken.text)); it != pc->pp->definitions.end()) {
572 pc->pp->definitions.erase(it);
573 }
574 else {
575 LOG_WARNING(logger, "Unable to undef unknown identifier \"%.*s\"", static_cast<int>(pc->matchedToken.text.size()), pc->matchedToken.text.data());
576 }
577 }
578
579 void parseIncludeDirective(ParseContext * pc)
580 {
581 if (!pc->stack.back().active) { skipRestOfLine(pc); return; }
582
583 if (!expectToken(pc, Token::Kind::String, "Expected path in include directive")) { return; }
584
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);
589
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());
593 pc->error = true;
594 }
595 else {
596
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());
600 pc->error = true;
601 }
602 else {
603
604
605 //LOG_DEBUG(logger, "#include \"%.*s\"", 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,
610 pc->pp,
611 Cogs::StringView(reinterpret_cast<const char*>(contents.data()), contents.size()),
612 pc->depth + 1u))
613 {
614 LOG_ERROR(logger, "Error while parsing included file \"%.*s\"", static_cast<int>(path.length()), path.data());
615 pc->error = true;
616 }
617 pc->pp->processed.append("// } of \"");
618 pc->pp->processed.append(path.data(), path.size());
619 pc->pp->processed.append("\"\n\n");
620 }
621 }
622 }
623
624 void parseIfDirective(ParseContext* pc)
625 {
626 assert(!pc->stack.empty());
627 if (pc->stack.back().active) {
628 pc->stack.push_back(parseExpression(pc) != 0 ? FlowState{ true } : FlowState{ false });
629 }
630 else {
631 skipRestOfLine(pc);
632 pc->stack.push_back(FlowState{ false });
633 }
634 }
635
636 void parseIfDefinedDirective(ParseContext* pc, bool negate)
637 {
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 });
643 }
644 else {
645 pc->stack.push_back(negate ? FlowState{ true } : FlowState{ false });
646 }
647 }
648 }
649 else {
650 skipRestOfLine(pc);
651 pc->stack.push_back(FlowState{ false });
652 }
653 }
654
655 void parseElIfDirective(ParseContext* pc)
656 {
657 if (pc->stack.size() < 2) {
658 LOG_ERROR(logger, "#elif without matching preceding #if");
659 pc->error = true;
660 return;
661 }
662 if (pc->stack[pc->stack.size() - 2].active) {
663
664 if (pc->stack.back().hasSeenElse) {
665 LOG_ERROR(logger, "#elif after #else in chain.");
666 pc->error = false;
667 }
668
669 // Was recently active, must become inactive.
670 else if (pc->stack.back().active) {
671 pc->stack.back().active = false;
672 pc->stack.back().hasBeenActive = true;
673 skipRestOfLine(pc);
674 }
675
676 // Was recently inactive, and has never been active, so is active if condition is true.
677 else if (pc->stack.back().hasBeenActive == false) {
678 assert(pc->stack.back().active == false);
679 pc->stack.back().active = parseExpression(pc) != 0;
680 }
681
682 // Was recently inactive, but has been active previously, so must remain inactive.
683 else {
684 assert(pc->stack.back().active == false);
685 assert(pc->stack.back().hasBeenActive == true);
686 skipRestOfLine(pc);
687 }
688
689 }
690 else {
691 assert(pc->stack.back().active == false);
692 skipRestOfLine(pc);
693 }
694 }
695
696 void parseElseDirective(ParseContext* pc)
697 {
698 if (pc->stack.size() < 2) {
699 LOG_ERROR(logger, "#else without matching preceding #if");
700 pc->error = true;
701 return;
702 }
703 if (pc->stack[pc->stack.size() - 2].active) {
704
705 if (pc->stack.back().hasSeenElse) {
706 LOG_ERROR(logger, "Multiple #else's in chain.");
707 pc->error = true;
708 }
709
710 // Was recently active, must become inactive
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;
715 }
716
717 // Was recently inactive, and has never been active, must become active
718 else if (pc->stack.back().hasBeenActive == false) {
719 pc->stack.back().active = true;
720 pc->stack.back().hasSeenElse = true;
721 }
722
723 // Was recently inactive, but has been active, must remain inactive
724 else {
725 assert(pc->stack.back().active == false);
726 assert(pc->stack.back().hasBeenActive == true);
727 assert(pc->stack.back().hasSeenElse == false);
728 }
729 }
730 else {
731 assert(pc->stack.back().active == false);
732 skipRestOfLine(pc);
733 }
734 }
735
736 void parseEndifDirective(ParseContext* pc)
737 {
738 if (pc->stack.size() < 2) {
739 LOG_ERROR(logger, "#endif without matching preceding #if");
740 pc->error = true;
741 return;
742 }
743 pc->stack.pop_back();
744 }
745
746 bool parseStart(Context* context, PartialPreprocessor* pp, const Cogs::StringView input, unsigned depth)
747 {
748 ParseContext pc;
749 pc.context = context;
750 pc.depth = depth;
751 pc.error = false;
752 pc.pp = pp;
753 pc.in = input.data();
754 pc.end = input.data() + input.length();
755 pc.stack.push_back(FlowState{ true });
756
757 // Line-by-line loop
758 nextToken(&pc);
759 while (pc.currentToken.kind != Token::Eof && !pc.error) {
760 const char* lineStart = pc.currentToken.text.data();
761
762 bool forward = false;
763
764 // Line with directive
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()) {
768 case Cogs::hash("version"):
769 case Cogs::hash("extension"):
770 // Known directives we ignore.
771 skipRestOfLine(&pc);
772 forward = true;
773 break;
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;
783 default:
784 // Unknown directive we nag about and ignore.
785 LOG_WARNING(logger, "Unhandled directive '%.*s'", static_cast<int>(pc.matchedToken.text.length()), pc.matchedToken.text.data());
786 skipRestOfLine(&pc);
787 forward = true;
788 break;
789 }
790 }
791
792 // Non-directive line in active text
793 else if (pc.stack.back().active) {
794 forward = true;
795 while (!(pc.error || isToken(&pc, Token::Kind::NewLine) || isToken(&pc, Token::Kind::Eof))) {
796 if (isToken(&pc, Token::Kind::Identifier)) {
797 seeIdentifier(&pc);
798 }
799 nextToken(&pc);
800 }
801
802 }
803 else {
804 assert(!pc.stack.back().active);
805 skipRestOfLine(&pc);
806 }
807
808 if (pc.stack.back().active && forward) {
809 if (lineStart < pc.currentToken.text.data()) {
810
811 // include indentation if any
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");
815 }
816 }
817
818 assert(pc.error || isToken(&pc, Token::Kind::NewLine) || isToken(&pc, Token::Kind::Eof));
819 nextToken(&pc);
820 }
821
822 if (pc.stack.size() != 1) {
823 LOG_ERROR(logger, "Unbalanced #if/#endif");
824 pc.error = true;
825 }
826
827 return !pc.error;
828 }
829
830}
831
833{
834 return parseStart(context, this, input, 0);
835}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Log implementation class.
Definition: LogManager.h:139
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 size_t length() const noexcept
Get the length of the string.
Definition: StringView.h:185
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
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
Partial C preprocessor.
Definition: Preprocessor.h:42
bool process(Context *context, const StringView input)
Run a text block through the preprocessor.