Porytiles
Loading...
Searching...
No Matches
parser.cpp
Go to the documentation of this file.
2
3#include <stack>
4
7
8namespace porytiles {
9
10namespace {
11
17[[nodiscard]] std::size_t skip_balanced_braces(const std::vector<Token> &tokens, std::size_t start)
18{
19 if (start >= tokens.size() || !tokens[start].is(TokenType::left_brace)) {
20 return start;
21 }
22
23 std::size_t pos = start + 1;
24 int depth = 1;
25
26 while (pos < tokens.size() && depth > 0) {
27 if (tokens[pos].is(TokenType::left_brace)) {
28 ++depth;
29 }
30 else if (tokens[pos].is(TokenType::right_brace)) {
31 --depth;
32 }
33 ++pos;
34 }
35
36 return pos;
37}
38
44[[nodiscard]] std::vector<Token> collect_brace_contents(const std::vector<Token> &tokens, std::size_t start)
45{
46 std::vector<Token> contents;
47
48 if (start >= tokens.size() || !tokens[start].is(TokenType::left_brace)) {
49 return contents;
50 }
51
52 std::size_t pos = start + 1;
53 int depth = 1;
54
55 while (pos < tokens.size() && depth > 0) {
56 if (tokens[pos].is(TokenType::left_brace)) {
57 ++depth;
58 }
59 else if (tokens[pos].is(TokenType::right_brace)) {
60 --depth;
61 if (depth == 0) {
62 break;
63 }
64 }
65 contents.push_back(tokens[pos]);
66 ++pos;
67 }
68
69 return contents;
70}
71
76[[nodiscard]] std::vector<std::string> extract_identifier_elements(const std::vector<Token> &brace_contents)
77{
78 std::vector<std::string> elements;
79
80 for (const auto &token : brace_contents) {
81 if (token.is(TokenType::identifier)) {
82 elements.push_back(token.text());
83 }
84 // Skip commas, newlines, and other tokens
85 }
86
87 return elements;
88}
89
95[[nodiscard]] std::size_t skip_balanced_parens(const std::vector<Token> &tokens, std::size_t start)
96{
97 if (start >= tokens.size() || !tokens[start].is(TokenType::left_paren)) {
98 return start;
99 }
100
101 std::size_t pos = start + 1;
102 int depth = 1;
103
104 while (pos < tokens.size() && depth > 0) {
105 if (tokens[pos].is(TokenType::left_paren)) {
106 ++depth;
107 }
108 else if (tokens[pos].is(TokenType::right_paren)) {
109 --depth;
110 }
111 ++pos;
112 }
113
114 return pos;
115}
116
126[[nodiscard]] bool is_gfx_inclusion_macro(const std::string &token)
127{
128 return token.starts_with("INCBIN_") || token.starts_with("INCGFX_");
129}
130
131} // namespace
132
133FormattableError Parser::make_error(SourcePosition pos, std::string message) const
134{
135 if (context_ != nullptr) {
136 return context_->make_error(pos, std::move(message));
137 }
138 return FormattableError{format_->format("{}:{}: {}", pos.line, pos.column, message)};
139}
140
141Parser::CondState Parser::effective_cond_state() const
142{
143 bool any_both = false;
144 for (const auto &frame : cond_stack_) {
145 if (frame.state == CondState::skipping) {
146 return CondState::skipping;
147 }
148 if (frame.state == CondState::both) {
149 any_both = true;
150 }
151 }
152 return any_both ? CondState::both : CondState::active;
153}
154
155std::pair<Parser::CondState, bool> Parser::classify_ifdef(bool negated)
156{
157 // Precondition: the parser is positioned just after '#ifdef'/'#ifndef'. Read the macro name and decide.
158 std::string name;
159 if (check(TokenType::identifier)) {
160 name = peek().text();
161 }
162 skip_to_next_line();
163
164 if (name.empty() || !defined_names_.contains(name)) {
165 // We cannot prove the macro is undefined (it may come from an unparsed include), so stay undecidable.
166 return {CondState::both, false};
167 }
168 const bool condition = negated ? false : true; // name is known-defined; #ifdef is true, #ifndef is false
169 return {condition ? CondState::active : CondState::skipping, true};
170}
171
172std::optional<std::vector<Token>> Parser::substitute_defined_operators(const std::vector<Token> &expr) const
173{
174 std::vector<Token> out;
175 std::size_t i = 0;
176 while (i < expr.size()) {
177 if (!expr[i].is(TokenType::kw_defined)) {
178 out.push_back(expr[i]);
179 ++i;
180 continue;
181 }
182
183 // Accept either `defined(NAME)` or `defined NAME`.
184 std::string name;
185 SourcePosition pos = expr[i].position();
186 std::size_t next = i + 1;
187 if (next < expr.size() && expr[next].is(TokenType::left_paren)) {
188 if (next + 1 < expr.size() && expr[next + 1].is(TokenType::identifier)) {
189 name = expr[next + 1].text();
190 }
191 std::size_t close = next + 1;
192 while (close < expr.size() && !expr[close].is(TokenType::right_paren)) {
193 ++close;
194 }
195 i = (close < expr.size()) ? close + 1 : expr.size();
196 }
197 else if (next < expr.size() && expr[next].is(TokenType::identifier)) {
198 name = expr[next].text();
199 i = next + 1;
200 }
201 else {
202 return std::nullopt; // malformed defined operator
203 }
204
205 if (name.empty() || !defined_names_.contains(name)) {
206 // The name is not known-defined, so the whole condition is undecidable.
207 return std::nullopt;
208 }
209 out.push_back(Token{"1", static_cast<std::int64_t>(1), pos});
210 }
211 return out;
212}
213
214std::pair<Parser::CondState, bool> Parser::classify_if_expression()
215{
216 // Precondition: the parser is positioned just after '#if'/'#elif'. Collect and evaluate the condition tokens.
217 std::vector<Token> expr = collect_expression_tokens();
218
219 auto substituted = substitute_defined_operators(expr);
220 if (!substituted.has_value() || substituted->empty()) {
221 return {CondState::both, false};
222 }
223
224 auto eval = evaluate_expression(substituted.value());
225 if (!eval.has_value()) {
226 // An unresolved identifier or other evaluation failure leaves the condition undecidable.
227 return {CondState::both, false};
228 }
229 return {eval.value() != 0 ? CondState::active : CondState::skipping, true};
230}
231
232bool Parser::try_handle_conditional()
233{
234 // Precondition: the current token is '#'.
235 const TokenType directive = peek_next().type();
236
237 switch (directive) {
240 const bool negated = directive == TokenType::kw_ifndef;
241 advance(); // '#'
242 advance(); // ifdef / ifndef
243 auto [state, decidable] = classify_ifdef(negated);
244 cond_stack_.push_back(ConditionalFrame{state, decidable, state == CondState::active});
245 return true;
246 }
247 case TokenType::kw_if: {
248 advance(); // '#'
249 advance(); // if
250 auto [state, decidable] = classify_if_expression();
251 cond_stack_.push_back(ConditionalFrame{state, decidable, state == CondState::active});
252 return true;
253 }
254 case TokenType::kw_elif: {
255 advance(); // '#'
256 advance(); // elif
257 auto [state, decidable] = classify_if_expression();
258 if (!cond_stack_.empty()) {
259 ConditionalFrame &frame = cond_stack_.back();
260 if (!frame.decidable || !decidable) {
261 // Any undecidable link pins the whole chain to both.
262 frame.state = CondState::both;
263 frame.decidable = false;
264 }
265 else if (frame.branch_taken) {
266 frame.state = CondState::skipping;
267 }
268 else {
269 frame.state = state;
270 frame.branch_taken = state == CondState::active;
271 }
272 }
273 return true;
274 }
275 case TokenType::kw_else: {
276 advance(); // '#'
277 advance(); // else
278 skip_to_next_line();
279 if (!cond_stack_.empty()) {
280 ConditionalFrame &frame = cond_stack_.back();
281 if (frame.decidable) {
282 frame.state = frame.branch_taken ? CondState::skipping : CondState::active;
283 frame.branch_taken = true;
284 }
285 // Undecidable chains stay both.
286 }
287 return true;
288 }
289 case TokenType::kw_endif: {
290 advance(); // '#'
291 advance(); // endif
292 skip_to_next_line();
293 if (!cond_stack_.empty()) {
294 cond_stack_.pop_back();
295 }
296 return true;
297 }
298 default:
299 return false; // not a conditional directive
300 }
301}
302
304{
305 std::vector<DefineStatement> defines;
306 cond_stack_.clear();
307
308 while (!is_at_end()) {
309 // Look for # token
310 if (check(TokenType::hash)) {
311 if (try_handle_conditional()) {
312 continue;
313 }
314 advance(); // consume #
315
316 // Check if this is a #define
317 if (check(TokenType::kw_define)) {
318 auto result = parse_define();
319 if (!result.has_value()) {
321 }
322 record_define(std::move(result).value(), defines);
323 }
324 else {
325 // Skip other preprocessor directives
326 skip_to_next_line();
327 }
328 }
329 else {
330 // Skip non-preprocessor tokens
331 advance();
332 }
333 }
334
335 return defines;
336}
337
338void Parser::record_define(DefineStatement statement, std::vector<DefineStatement> &out)
339{
340 const CondState eff = effective_cond_state();
341 if (eff == CondState::skipping) {
342 // The define lives in a region we can prove is inactive, so it neither defines a name nor contributes a value.
343 return;
344 }
345
346 if (statement.has_int_value()) {
347 auto it = defined_values_.find(statement.name());
348 if (eff == CondState::both && it != defined_values_.end() && it->second != statement.int_value()) {
349 ambiguous_defines_.insert(statement.name());
350 scan_warnings_.push_back(format_->format(
351 "conflicting redefinition of '{}' inside an undecidable conditional; using the last value",
352 FormatParam{statement.name(), Style::bold}));
353 }
354 defined_values_[statement.name()] = statement.int_value();
355 }
356 defined_names_.insert(statement.name());
357 out.push_back(std::move(statement));
358}
359
360Parser::DefineParseOutcome Parser::parse_define_tolerant()
361{
362 SourcePosition define_pos = peek().position();
363 advance(); // consume 'define'
364
365 if (!check(TokenType::identifier)) {
366 skip_to_next_line();
367 return {std::nullopt, SkippedConstruct{"", define_pos, "expected identifier after #define"}};
368 }
369
370 std::string name = peek().text();
371 SourcePosition name_pos = peek().position();
372 std::size_t name_end_column = name_pos.column + name.size();
373 advance(); // consume identifier
374
375 // Parametric (function-like) macro: '(' immediately follows the name.
376 if (check(TokenType::left_paren)) {
377 SourcePosition paren_pos = peek().position();
378 if (paren_pos.line == name_pos.line && paren_pos.column == name_end_column) {
379 skip_to_next_line();
380 return {DefineStatement{std::move(name), define_pos}, std::nullopt};
381 }
382 }
383
384 // Flag-like define with no value.
385 if (is_at_line_end()) {
386 if (check(TokenType::newline)) {
387 advance();
388 }
389 return {DefineStatement{std::move(name), define_pos}, std::nullopt};
390 }
391
392 // String value.
393 if (check(TokenType::string_literal)) {
394 std::string value = peek().text();
395 advance();
396 skip_to_next_line();
397 return {DefineStatement{std::move(name), std::move(value), define_pos}, std::nullopt};
398 }
399
400 // Expression value.
401 std::vector<Token> expr_tokens = collect_expression_tokens();
402 if (expr_tokens.empty()) {
403 return {DefineStatement{std::move(name), define_pos}, std::nullopt};
404 }
405
406 auto result = evaluate_expression(expr_tokens);
407 if (!result.has_value()) {
408 return {std::nullopt, SkippedConstruct{std::move(name), define_pos, "unevaluable value expression"}};
409 }
410 return {DefineStatement{std::move(name), result.value(), define_pos}, std::nullopt};
411}
412
414{
416 current_ = 0;
417 cond_stack_.clear();
418
419 while (!is_at_end()) {
420 if (check(TokenType::hash)) {
421 if (try_handle_conditional()) {
422 continue;
423 }
424 advance(); // consume #
425
426 if (check(TokenType::kw_define)) {
427 auto outcome = parse_define_tolerant();
428 if (effective_cond_state() == CondState::skipping) {
429 // Provably inactive region: consume but record nothing.
430 continue;
431 }
432 if (outcome.statement.has_value()) {
433 record_define(std::move(outcome.statement).value(), scan.defines);
434 }
435 else if (outcome.skipped.has_value()) {
436 if (!outcome.skipped->name.empty()) {
437 defined_names_.insert(outcome.skipped->name);
438 }
439 scan.skipped.push_back(std::move(outcome.skipped).value());
440 }
441 }
442 else {
443 skip_to_next_line();
444 }
445 }
446 else {
447 advance();
448 }
449 }
450
451 return scan;
452}
453
454TolerantEnumMember Parser::parse_enum_member_tolerant(std::int64_t &counter, bool &counter_valid)
455{
456 std::string name = peek().text();
457 SourcePosition member_pos = peek().position();
458 advance(); // consume identifier
459
460 if (check(TokenType::equal)) {
461 advance(); // consume '='
462 std::vector<Token> expr_tokens = collect_enum_value_tokens();
463 if (!expr_tokens.empty()) {
464 auto eval = evaluate_expression(expr_tokens);
465 if (eval.has_value()) {
466 counter = eval.value();
467 counter_valid = true;
468 TolerantEnumMember member{std::move(name), counter, member_pos};
469 counter++;
470 defined_values_[member.name] = member.value.value();
471 return member;
472 }
473 }
474 // Unevaluable (or empty) explicit value poisons the counter.
475 counter_valid = false;
476 return TolerantEnumMember{std::move(name), std::nullopt, member_pos};
477 }
478
479 // Implicit member: usable only while the counter is trustworthy.
480 std::optional<std::int64_t> value = counter_valid ? std::optional<std::int64_t>{counter} : std::nullopt;
481 TolerantEnumMember member{std::move(name), value, member_pos};
482 counter++;
483 if (member.value.has_value()) {
484 defined_values_[member.name] = member.value.value();
485 }
486 return member;
487}
488
489std::optional<TolerantEnum> Parser::parse_enum_tolerant()
490{
491 SourcePosition enum_pos = peek().position();
492 advance(); // consume 'enum'
493
494 std::optional<std::string> enum_name;
495 if (check(TokenType::identifier)) {
496 enum_name = peek().text();
497 advance();
498 }
499
500 while (check(TokenType::newline)) {
501 advance();
502 }
503
504 if (!check(TokenType::left_brace)) {
505 // Not a definition we can parse (e.g. a forward use like `enum Foo bar;`); nothing to record.
506 return std::nullopt;
507 }
508 advance(); // consume '{'
509
510 std::vector<TolerantEnumMember> members;
511 std::int64_t counter = 0;
512 bool counter_valid = true;
513 bool directive_seen = false;
514
515 while (!is_at_end() && !check(TokenType::right_brace)) {
516 while (check(TokenType::newline)) {
517 advance();
518 }
519 if (check(TokenType::right_brace)) {
520 break;
521 }
522 if (check(TokenType::hash)) {
523 // A preprocessor directive inside the body. The scanner does not evaluate conditionals here, so every
524 // member value beyond this point depends on a branch it cannot decide. Skip the directive line (rather
525 // than lexing its tokens as phantom members) and record later members as valueless.
526 skip_to_next_line();
527 directive_seen = true;
528 counter_valid = false;
529 continue;
530 }
531 if (!check(TokenType::identifier)) {
532 // Unexpected token inside the enum body; skip it to stay resilient.
533 advance();
534 continue;
535 }
536
537 members.push_back(parse_enum_member_tolerant(counter, counter_valid));
538 if (directive_seen) {
539 // Even an explicit '= value' cannot be trusted once a directive appeared: it may sit in an untaken branch.
540 members.back().value = std::nullopt;
541 counter_valid = false;
542 }
543
544 if (check(TokenType::comma)) {
545 advance();
546 }
547 while (check(TokenType::newline)) {
548 advance();
549 }
550 }
551
552 if (check(TokenType::right_brace)) {
553 advance(); // consume '}'
554 }
555 if (check(TokenType::semicolon)) {
556 advance();
557 }
558
559 return TolerantEnum{std::move(enum_name), std::move(members), enum_pos};
560}
561
563{
564 TolerantEnumScan scan;
565 current_ = 0;
566 cond_stack_.clear();
567
568 while (!is_at_end()) {
569 if (check(TokenType::hash)) {
570 if (try_handle_conditional()) {
571 continue;
572 }
573 skip_to_next_line();
574 continue;
575 }
576 if (check(TokenType::kw_enum)) {
577 auto parsed = parse_enum_tolerant();
578 if (parsed.has_value() && effective_cond_state() != CondState::skipping) {
579 scan.enums.push_back(std::move(parsed).value());
580 }
581 }
582 else {
583 advance();
584 }
585 }
586
587 return scan;
588}
589
591{
592 std::vector<EnumDeclaration> enums;
593
594 // Reset position to beginning (allows calling both parse_defines and parse_enums)
595 current_ = 0;
596 cond_stack_.clear();
597
598 while (!is_at_end()) {
599 if (check(TokenType::hash)) {
600 if (try_handle_conditional()) {
601 continue;
602 }
603 // A non-conditional directive (e.g. #define, #include) cannot start an enum, so skip its line.
604 skip_to_next_line();
605 continue;
606 }
607 if (check(TokenType::kw_enum)) {
608 auto result = parse_enum();
609 if (!result.has_value()) {
611 }
612 if (effective_cond_state() != CondState::skipping) {
613 enums.push_back(std::move(result).value());
614 }
615 }
616 else {
617 advance();
618 }
619 }
620
621 return enums;
622}
623
624ChainableResult<EnumDeclaration> Parser::parse_enum()
625{
626 SourcePosition enum_pos = peek().position();
627 advance(); // consume 'enum'
628
629 // Check for optional enum name
630 std::optional<std::string> enum_name;
631 if (check(TokenType::identifier)) {
632 enum_name = peek().text();
633 advance();
634 }
635
636 // Skip newlines before opening brace (handles `enum\n{` style)
637 while (check(TokenType::newline)) {
638 advance();
639 }
640
641 // Expect opening brace
642 if (!check(TokenType::left_brace)) {
643 return make_error(peek().position(), "expected '{' after 'enum'");
644 }
645 advance(); // consume '{'
646
647 // Parse members
648 std::vector<EnumMember> members;
649 std::int64_t counter = 0;
650
651 while (!is_at_end() && !check(TokenType::right_brace)) {
652 // Skip newlines
653 while (check(TokenType::newline)) {
654 advance();
655 }
656
657 if (check(TokenType::right_brace)) {
658 break;
659 }
660
661 auto member_result = parse_enum_member(counter);
662 if (!member_result.has_value()) {
663 return ChainableResult<EnumDeclaration>{member_result};
664 }
665 members.push_back(std::move(member_result).value());
666
667 // Skip trailing comma (optional for last member)
668 if (check(TokenType::comma)) {
669 advance();
670 }
671
672 // Skip newlines after comma
673 while (check(TokenType::newline)) {
674 advance();
675 }
676 }
677
678 // Expect closing brace
679 if (!check(TokenType::right_brace)) {
680 return make_error(peek().position(), "expected '}' to close enum");
681 }
682 advance(); // consume '}'
683
684 // Skip optional semicolon
685 if (check(TokenType::semicolon)) {
686 advance();
687 }
688
689 if (enum_name.has_value()) {
690 return EnumDeclaration{std::move(enum_name).value(), std::move(members), enum_pos};
691 }
692 return EnumDeclaration{std::move(members), enum_pos};
693}
694
695ChainableResult<EnumMember> Parser::parse_enum_member(std::int64_t &counter)
696{
697 // Expect identifier
698 if (!check(TokenType::identifier)) {
699 return make_error(peek().position(), "expected identifier for enum member");
700 }
701
702 std::string name = peek().text();
703 SourcePosition member_pos = peek().position();
704 advance(); // consume identifier
705
706 // Check for explicit value assignment
707 bool has_explicit = false;
708 if (check(TokenType::equal)) {
709 advance(); // consume '='
710 has_explicit = true;
711
712 // Collect expression tokens until comma, right_brace, or newline
713 std::vector<Token> expr_tokens = collect_enum_value_tokens();
714
715 if (expr_tokens.empty()) {
716 return make_error(
717 member_pos,
718 format_->format("expected expression after '=' for enum member '{}'", FormatParam{name, Style::bold}));
719 }
720
721 auto eval_result = evaluate_expression(expr_tokens);
722 if (!eval_result.has_value()) {
723 return ChainableResult<EnumMember>{
724 make_error(
725 member_pos,
726 format_->format(
727 "failed to evaluate expression for enum member '{}'", FormatParam{name, Style::bold})),
728 eval_result};
729 }
730
731 counter = eval_result.value();
732 }
733
734 EnumMember member{std::move(name), counter, has_explicit, member_pos};
735 counter++; // Increment for next member
736 defined_values_[member.name()] = member.int_value();
737 return member;
738}
739
740std::vector<Token> Parser::collect_enum_value_tokens()
741{
742 std::vector<Token> expr_tokens;
743
744 while (!is_at_end() && !check(TokenType::comma) && !check(TokenType::right_brace) && !check(TokenType::newline)) {
745 expr_tokens.push_back(peek());
746 advance();
747 }
748
749 return expr_tokens;
750}
751
752const Token &Parser::peek() const
753{
754 if (is_at_end()) {
755 return tokens_.back(); // Should be end_of_file
756 }
757 return tokens_[current_];
758}
759
760const Token &Parser::peek_next() const
761{
762 if (current_ + 1 >= tokens_.size()) {
763 return tokens_.back();
764 }
765 return tokens_[current_ + 1];
766}
767
768const Token &Parser::advance()
769{
770 if (!is_at_end()) {
771 ++current_;
772 }
773 return tokens_[current_ - 1];
774}
775
776bool Parser::is_at_end() const
777{
778 return current_ >= tokens_.size() || tokens_[current_].is(TokenType::end_of_file);
779}
780
781bool Parser::check(TokenType type) const
782{
783 if (is_at_end()) {
784 return type == TokenType::end_of_file;
785 }
786 return peek().is(type);
787}
788
789bool Parser::match(TokenType type)
790{
791 if (check(type)) {
792 advance();
793 return true;
794 }
795 return false;
796}
797
798void Parser::skip_to_next_line()
799{
800 while (!is_at_end() && !check(TokenType::newline)) {
801 advance();
802 }
803 if (check(TokenType::newline)) {
804 advance(); // consume the newline
805 }
806}
807
808bool Parser::is_at_line_end() const
809{
810 return is_at_end() || check(TokenType::newline);
811}
812
813ChainableResult<DefineStatement> Parser::parse_define()
814{
815 SourcePosition define_pos = peek().position();
816 advance(); // consume 'define'
817
818 // Skip whitespace (already handled by lexer, but newlines are significant)
819
820 // Expect identifier for macro name
821 if (!check(TokenType::identifier)) {
822 return make_error(peek().position(), "expected identifier after '#define'");
823 }
824
825 std::string name = peek().text();
826 SourcePosition name_pos = peek().position();
827 std::size_t name_end_column = name_pos.column + name.size();
828 advance(); // consume identifier
829
830 // Check for parametric macro (function-like macro)
831 // These have ( immediately after the name with no space
832 // We detect this by checking if the ( starts at the column immediately after the identifier
833 if (check(TokenType::left_paren)) {
834 SourcePosition paren_pos = peek().position();
835 // If the ( is immediately after the identifier (same line, adjacent column), it's a parametric macro
836 if (paren_pos.line == name_pos.line && paren_pos.column == name_end_column) {
837 // This is a parametric macro - skip it
838 skip_to_next_line();
839 return DefineStatement{std::move(name), define_pos}; // Return as flag define
840 }
841 // Otherwise, the ( starts an expression - fall through to expression evaluation
842 }
843
844 // Check for end of line (flag-like define with no value)
845 if (is_at_line_end()) {
846 if (check(TokenType::newline)) {
847 advance();
848 }
849 return DefineStatement{std::move(name), define_pos};
850 }
851
852 // Check for string literal value
853 if (check(TokenType::string_literal)) {
854 std::string value = peek().text();
855 advance();
856 skip_to_next_line();
857 return DefineStatement{std::move(name), std::move(value), define_pos};
858 }
859
860 // Otherwise, collect and evaluate expression
861 std::vector<Token> expr_tokens = collect_expression_tokens();
862
863 if (expr_tokens.empty()) {
864 // No expression, treat as flag define
865 return DefineStatement{std::move(name), define_pos};
866 }
867
868 auto result = evaluate_expression(expr_tokens);
869 if (!result.has_value()) {
870 return ChainableResult<DefineStatement>{
871 make_error(
872 define_pos,
873 format_->format("failed to evaluate expression for #define '{}'", FormatParam{name, Style::bold})),
874 result};
875 }
876
877 std::int64_t value = result.value();
878
879 // The symbol table and defined-name set are updated by record_define once the enclosing conditional state is known.
880 return DefineStatement{std::move(name), value, define_pos};
881}
882
883std::vector<Token> Parser::collect_expression_tokens()
884{
885 std::vector<Token> expr_tokens;
886
887 while (!is_at_line_end()) {
888 expr_tokens.push_back(peek());
889 advance();
890 }
891
892 // Consume the newline if present
893 if (check(TokenType::newline)) {
894 advance();
895 }
896
897 return expr_tokens;
898}
899
900ChainableResult<std::int64_t> Parser::evaluate_expression(const std::vector<Token> &expr_tokens)
901{
902 if (expr_tokens.empty()) {
903 return make_error(SourcePosition{}, "empty expression");
904 }
905
906 // Reject any token the evaluator has no rule for (ternaries, casts, sizeof, etc). Dropping it and evaluating the
907 // remaining tokens would produce a confidently wrong value; failing here degrades to "value unknown" instead.
908 for (const Token &token : expr_tokens) {
909 const bool supported = token.is(TokenType::integer_literal) || token.is(TokenType::identifier) ||
910 token.is(TokenType::left_paren) || token.is(TokenType::right_paren) ||
911 is_operator(token.type());
912 if (!supported) {
913 return make_error(
914 token.position(),
915 format_->format("unsupported token '{}' in expression", FormatParam{token.text(), Style::bold}));
916 }
917 }
918
919 // Convert to postfix notation using Shunting Yard
920 std::vector<Token> postfix = to_postfix(expr_tokens);
921
922 // Evaluate the postfix expression
923 return evaluate_postfix(postfix);
924}
925
926std::vector<Token> Parser::to_postfix(const std::vector<Token> &expr_tokens)
927{
928 std::vector<Token> output;
929 std::stack<Token> operators;
930
931 bool expect_operand = true; // Track if we expect an operand (for unary operators)
932
933 for (std::size_t i = 0; i < expr_tokens.size(); ++i) {
934 const Token &token = expr_tokens[i];
935
936 if (token.is(TokenType::integer_literal) || token.is(TokenType::identifier)) {
937 output.push_back(token);
938 expect_operand = false;
939 }
940 else if (token.is(TokenType::left_paren)) {
941 operators.push(token);
942 expect_operand = true;
943 }
944 else if (token.is(TokenType::right_paren)) {
945 while (!operators.empty() && !operators.top().is(TokenType::left_paren)) {
946 output.push_back(operators.top());
947 operators.pop();
948 }
949 if (!operators.empty() && operators.top().is(TokenType::left_paren)) {
950 operators.pop(); // Discard the left paren
951 }
952 expect_operand = false;
953 }
954 else if (is_operator(token.type())) {
955 // Handle unary operators (-, ~, !)
956 if (expect_operand && is_unary_operator(token.type())) {
957 // Create a special unary token by prefixing with 'u'
958 // We'll handle this in evaluation
959 Token unary_token{token.type(), "u" + token.text(), token.position()};
960 operators.push(unary_token);
961 }
962 else {
963 // Binary operator
964 while (!operators.empty() && !operators.top().is(TokenType::left_paren) &&
965 is_operator(operators.top().type())) {
966 int top_prec = operator_precedence(operators.top().type());
967 int curr_prec = operator_precedence(token.type());
968
969 if (top_prec < curr_prec || (top_prec == curr_prec && is_left_associative(token.type()))) {
970 output.push_back(operators.top());
971 operators.pop();
972 }
973 else {
974 break;
975 }
976 }
977 operators.push(token);
978 expect_operand = true;
979 }
980 }
981 // No other token kinds can appear: evaluate_expression rejects unsupported tokens before conversion.
982 }
983
984 // Pop remaining operators
985 while (!operators.empty()) {
986 if (!operators.top().is(TokenType::left_paren)) {
987 output.push_back(operators.top());
988 }
989 operators.pop();
990 }
991
992 return output;
993}
994
995ChainableResult<std::int64_t> Parser::evaluate_postfix(const std::vector<Token> &postfix)
996{
997 std::stack<std::int64_t> values;
998
999 for (const Token &token : postfix) {
1000 if (token.is(TokenType::integer_literal)) {
1001 values.push(token.int_value());
1002 }
1003 else if (token.is(TokenType::identifier)) {
1004 // Look up in symbol table
1005 auto it = defined_values_.find(token.text());
1006 if (it != defined_values_.end()) {
1007 values.push(it->second);
1008 }
1009 else {
1010 // Unknown identifier - could be an error or treat as 0
1011 return make_error(
1012 token.position(),
1013 format_->format("unknown identifier '{}'", FormatParam{token.text(), Style::bold}));
1014 }
1015 }
1016 else if (is_operator(token.type())) {
1017 // Check if it's a unary operator (text starts with 'u')
1018 if (token.text().size() > 1 && token.text()[0] == 'u') {
1019 if (values.empty()) {
1020 return make_error(
1021 token.position(),
1022 format_->format(
1023 "unary operator '{}' missing operand", FormatParam{token.text().substr(1), Style::bold}));
1024 }
1025 std::int64_t operand = values.top();
1026 values.pop();
1027
1028 std::int64_t result = 0;
1029 switch (token.type()) {
1030 case TokenType::minus:
1031 result = -operand;
1032 break;
1033 case TokenType::tilde:
1034 result = ~operand;
1035 break;
1036 case TokenType::exclaim:
1037 result = operand == 0 ? 1 : 0;
1038 break;
1039 default:
1040 return make_error(
1041 token.position(),
1042 format_->format("unknown unary operator '{}'", FormatParam{token.text(), Style::bold}));
1043 }
1044 values.push(result);
1045 }
1046 else {
1047 // Binary operator
1048 if (values.size() < 2) {
1049 return make_error(
1050 token.position(),
1051 format_->format(
1052 "binary operator '{}' missing operands", FormatParam{token.text(), Style::bold}));
1053 }
1054 std::int64_t right = values.top();
1055 values.pop();
1056 std::int64_t left = values.top();
1057 values.pop();
1058
1059 std::int64_t result = 0;
1060 switch (token.type()) {
1061 case TokenType::plus:
1062 result = left + right;
1063 break;
1064 case TokenType::minus:
1065 result = left - right;
1066 break;
1067 case TokenType::star:
1068 result = left * right;
1069 break;
1070 case TokenType::slash:
1071 if (right == 0) {
1072 return make_error(token.position(), "division by zero");
1073 }
1074 result = left / right;
1075 break;
1076 case TokenType::percent:
1077 if (right == 0) {
1078 return make_error(token.position(), "modulo by zero");
1079 }
1080 result = left % right;
1081 break;
1083 result = left & right;
1084 break;
1085 case TokenType::pipe:
1086 result = left | right;
1087 break;
1088 case TokenType::caret:
1089 result = left ^ right;
1090 break;
1092 result = left << right;
1093 break;
1095 result = left >> right;
1096 break;
1097 case TokenType::less:
1098 result = left < right ? 1 : 0;
1099 break;
1100 case TokenType::greater:
1101 result = left > right ? 1 : 0;
1102 break;
1104 result = left <= right ? 1 : 0;
1105 break;
1107 result = left >= right ? 1 : 0;
1108 break;
1110 result = left == right ? 1 : 0;
1111 break;
1113 result = left != right ? 1 : 0;
1114 break;
1116 result = (left != 0 && right != 0) ? 1 : 0;
1117 break;
1119 result = (left != 0 || right != 0) ? 1 : 0;
1120 break;
1121 default:
1122 return make_error(
1123 token.position(),
1124 format_->format("unknown binary operator '{}'", FormatParam{token.text(), Style::bold}));
1125 }
1126 values.push(result);
1127 }
1128 }
1129 }
1130
1131 if (values.empty()) {
1132 return make_error(SourcePosition{}, "expression evaluated to no value");
1133 }
1134 if (values.size() != 1) {
1135 // Operands were left stranded, meaning the expression was not fully understood. Returning the top of the
1136 // stack here would be a confidently wrong answer.
1137 return make_error(SourcePosition{}, "expression did not reduce to a single value");
1138 }
1139
1140 return values.top();
1141}
1142
1143int Parser::operator_precedence(TokenType type) const
1144{
1145 // Lower number = higher precedence (evaluated first)
1146 // Based on C operator precedence
1147 switch (type) {
1148 case TokenType::tilde:
1149 case TokenType::exclaim:
1150 // Always unary; they must bind tighter than every binary operator so that ~5 & 3 means (~5) & 3. Unary minus
1151 // cannot be distinguished from binary minus here, but sharing precedence 4 evaluates it correctly anyway.
1152 return 2;
1153 case TokenType::star:
1154 case TokenType::slash:
1155 case TokenType::percent:
1156 return 3;
1157 case TokenType::plus:
1158 case TokenType::minus:
1159 return 4;
1162 return 5;
1163 case TokenType::less:
1164 case TokenType::greater:
1167 return 6;
1170 return 7;
1172 return 8;
1173 case TokenType::caret:
1174 return 9;
1175 case TokenType::pipe:
1176 return 10;
1178 return 11;
1180 return 12;
1181 default:
1182 return 99; // Lowest precedence for unknown
1183 }
1184}
1185
1186bool Parser::is_left_associative(TokenType type) const
1187{
1188 // All our binary operators are left-associative
1189 return true;
1190}
1191
1192bool Parser::is_operator(TokenType type) const
1193{
1194 switch (type) {
1195 case TokenType::plus:
1196 case TokenType::minus:
1197 case TokenType::star:
1198 case TokenType::slash:
1199 case TokenType::percent:
1201 case TokenType::pipe:
1202 case TokenType::caret:
1203 case TokenType::tilde:
1204 case TokenType::exclaim:
1205 case TokenType::less:
1206 case TokenType::greater:
1215 return true;
1216 default:
1217 return false;
1218 }
1219}
1220
1221bool Parser::is_unary_operator(TokenType type) const
1222{
1223 switch (type) {
1224 case TokenType::minus: // Unary negation
1225 case TokenType::tilde: // Bitwise NOT
1226 case TokenType::exclaim: // Logical NOT
1227 case TokenType::plus: // Unary plus (no-op but valid)
1228 return true;
1229 default:
1230 return false;
1231 }
1232}
1233
1235{
1236 std::vector<ArrayDeclaration> arrays;
1237
1238 // Reset position to beginning
1239 current_ = 0;
1240
1241 while (!is_at_end()) {
1242 // Skip newlines
1243 while (check(TokenType::newline)) {
1244 advance();
1245 }
1246
1247 if (is_at_end()) {
1248 break;
1249 }
1250
1251 // Look for pattern: [static] [const] TYPE * [const] IDENTIFIER [] = { ... }
1252 // We need to find an identifier followed by [] = {
1253 // Start by looking for an identifier that could be an array name
1254
1255 std::size_t scan_start = current_;
1256
1257 // Skip 'static' if present
1258 if (check(TokenType::identifier) && peek().text() == "static") {
1259 advance();
1260 }
1261
1262 // Skip 'const' if present
1263 if (check(TokenType::identifier) && peek().text() == "const") {
1264 advance();
1265 }
1266
1267 // Skip type identifier (e.g., u16, int, etc.)
1268 if (check(TokenType::identifier)) {
1269 advance();
1270 }
1271 else {
1272 // Not a declaration, skip this token
1273 if (current_ == scan_start) {
1274 advance();
1275 }
1276 continue;
1277 }
1278
1279 // Look for * (pointer)
1280 if (!check(TokenType::star)) {
1281 continue;
1282 }
1283 advance(); // consume *
1284
1285 // Skip 'const' if present after *
1286 if (check(TokenType::identifier) && peek().text() == "const") {
1287 advance();
1288 }
1289
1290 // Now we should have the array name identifier
1291 if (!check(TokenType::identifier)) {
1292 continue;
1293 }
1294
1295 std::string array_name = peek().text();
1296 SourcePosition name_pos = peek().position();
1297 advance(); // consume identifier
1298
1299 // Look for []
1300 if (!check(TokenType::left_bracket)) {
1301 continue;
1302 }
1303 advance(); // consume [
1304
1305 if (!check(TokenType::right_bracket)) {
1306 continue;
1307 }
1308 advance(); // consume ]
1309
1310 // Look for =
1311 if (!check(TokenType::equal)) {
1312 continue;
1313 }
1314 advance(); // consume =
1315
1316 // Skip any newlines before {
1317 while (check(TokenType::newline)) {
1318 advance();
1319 }
1320
1321 // Look for {
1322 if (!check(TokenType::left_brace)) {
1323 continue;
1324 }
1325
1326 // Found a pointer array declaration - extract elements
1327 std::vector<Token> brace_contents = collect_brace_contents(tokens_, current_);
1328 std::vector<std::string> elements = extract_identifier_elements(brace_contents);
1329
1330 // Skip past the closing brace
1331 current_ = skip_balanced_braces(tokens_, current_);
1332
1333 // Skip optional semicolon
1334 if (check(TokenType::semicolon)) {
1335 advance();
1336 }
1337
1338 arrays.emplace_back(std::move(array_name), std::move(elements), name_pos);
1339 }
1340
1341 return arrays;
1342}
1343
1345{
1346 std::vector<FunctionDefinition> functions;
1347
1348 // Reset position to beginning
1349 current_ = 0;
1350
1351 while (!is_at_end()) {
1352 // Skip newlines
1353 while (check(TokenType::newline)) {
1354 advance();
1355 }
1356
1357 if (is_at_end()) {
1358 break;
1359 }
1360
1361 // Look for pattern: [static] TYPE IDENTIFIER ( params ) { body }
1362 std::size_t scan_start = current_;
1363
1364 // Skip 'static' if present
1365 if (check(TokenType::identifier) && peek().text() == "static") {
1366 advance();
1367 }
1368
1369 // Skip return type identifier (e.g., void, int, etc.)
1370 if (check(TokenType::identifier)) {
1371 advance();
1372 }
1373 else {
1374 // Not a function, skip this token
1375 if (current_ == scan_start) {
1376 advance();
1377 }
1378 continue;
1379 }
1380
1381 // Now we should have the function name identifier
1382 if (!check(TokenType::identifier)) {
1383 continue;
1384 }
1385
1386 std::string func_name = peek().text();
1387 SourcePosition name_pos = peek().position();
1388 advance(); // consume identifier
1389
1390 // Look for (
1391 if (!check(TokenType::left_paren)) {
1392 continue;
1393 }
1394
1395 // Skip past the parameter list
1396 current_ = skip_balanced_parens(tokens_, current_);
1397
1398 // Skip any newlines before {
1399 while (check(TokenType::newline)) {
1400 advance();
1401 }
1402
1403 // Look for {
1404 if (!check(TokenType::left_brace)) {
1405 continue;
1406 }
1407
1408 // Found a function definition - extract body tokens
1409 std::vector<Token> body_tokens = collect_brace_contents(tokens_, current_);
1410
1411 // Skip past the closing brace
1412 current_ = skip_balanced_braces(tokens_, current_);
1413
1414 functions.emplace_back(std::move(func_name), std::move(body_tokens), name_pos);
1415 }
1416
1417 return functions;
1418}
1419
1421{
1422 std::vector<StructVariableDeclaration> structs;
1423
1424 // Reset position to beginning
1425 current_ = 0;
1426
1427 while (!is_at_end()) {
1428 // Skip newlines
1429 while (check(TokenType::newline)) {
1430 advance();
1431 }
1432
1433 if (is_at_end()) {
1434 break;
1435 }
1436
1437 // Look for pattern: [const] struct TYPE IDENTIFIER = { ... } [;]
1438 std::size_t scan_start = current_;
1439
1440 // Skip 'const' if present
1441 if (check(TokenType::identifier) && peek().text() == "const") {
1442 advance();
1443 }
1444
1445 // Look for 'struct' keyword (lexer treats it as an identifier)
1446 if (!check(TokenType::identifier) || peek().text() != "struct") {
1447 // Not a struct declaration, skip this token
1448 if (current_ == scan_start) {
1449 advance();
1450 }
1451 continue;
1452 }
1453 advance(); // consume 'struct'
1454
1455 // Get struct type name
1456 if (!check(TokenType::identifier)) {
1457 continue;
1458 }
1459 std::string struct_type = peek().text();
1460 advance(); // consume type name
1461
1462 // Get variable name
1463 if (!check(TokenType::identifier)) {
1464 continue;
1465 }
1466 std::string variable_name = peek().text();
1467 SourcePosition name_pos = peek().position();
1468 advance(); // consume variable name
1469
1470 // Look for '='
1471 if (!check(TokenType::equal)) {
1472 continue;
1473 }
1474 advance(); // consume '='
1475
1476 // Skip any newlines before '{'
1477 while (check(TokenType::newline)) {
1478 advance();
1479 }
1480
1481 // Look for '{'
1482 if (!check(TokenType::left_brace)) {
1483 continue;
1484 }
1485
1486 // Skip balanced braces (we don't need the body contents)
1487 current_ = skip_balanced_braces(tokens_, current_);
1488
1489 // Skip optional semicolon
1490 if (check(TokenType::semicolon)) {
1491 advance();
1492 }
1493
1494 structs.emplace_back(std::move(struct_type), std::move(variable_name), name_pos);
1495 }
1496
1497 return structs;
1498}
1499
1501{
1502 std::vector<StructInitializerDeclaration> structs;
1503
1504 // Reset position to beginning
1505 current_ = 0;
1506
1507 while (!is_at_end()) {
1508 // Skip newlines
1509 while (check(TokenType::newline)) {
1510 advance();
1511 }
1512
1513 if (is_at_end()) {
1514 break;
1515 }
1516
1517 // Look for pattern: [const] struct TYPE IDENTIFIER = { .field = value, ... } [;]
1518 std::size_t scan_start = current_;
1519
1520 // Skip 'const' if present
1521 if (check(TokenType::identifier) && peek().text() == "const") {
1522 advance();
1523 }
1524
1525 // Look for 'struct' keyword (lexer treats it as an identifier)
1526 if (!check(TokenType::identifier) || peek().text() != "struct") {
1527 // Not a struct declaration, skip this token
1528 if (current_ == scan_start) {
1529 advance();
1530 }
1531 continue;
1532 }
1533 advance(); // consume 'struct'
1534
1535 // Get struct type name
1536 if (!check(TokenType::identifier)) {
1537 continue;
1538 }
1539 std::string struct_type = peek().text();
1540 advance(); // consume type name
1541
1542 // Get variable name
1543 if (!check(TokenType::identifier)) {
1544 continue;
1545 }
1546 std::string variable_name = peek().text();
1547 SourcePosition name_pos = peek().position();
1548 advance(); // consume variable name
1549
1550 // Look for '='
1551 if (!check(TokenType::equal)) {
1552 continue;
1553 }
1554 advance(); // consume '='
1555
1556 // Skip any newlines before '{'
1557 while (check(TokenType::newline)) {
1558 advance();
1559 }
1560
1561 // Look for '{'
1562 if (!check(TokenType::left_brace)) {
1563 continue;
1564 }
1565
1566 // Parse the designated initializer fields
1567 std::vector<DesignatedInitializerField> fields;
1568 std::vector<Token> brace_contents = collect_brace_contents(tokens_, current_);
1569
1570 // Parse each .field = value pair from the brace contents
1571 std::size_t brace_pos = 0;
1572 while (brace_pos < brace_contents.size()) {
1573 // Skip newlines and commas
1574 while (brace_pos < brace_contents.size() && (brace_contents[brace_pos].is(TokenType::newline) ||
1575 brace_contents[brace_pos].is(TokenType::comma))) {
1576 ++brace_pos;
1577 }
1578
1579 if (brace_pos >= brace_contents.size()) {
1580 break;
1581 }
1582
1583 // Look for '.'
1584 if (!brace_contents[brace_pos].is(TokenType::period)) {
1585 // Skip until next comma or end
1586 while (brace_pos < brace_contents.size() && !brace_contents[brace_pos].is(TokenType::comma)) {
1587 ++brace_pos;
1588 }
1589 continue;
1590 }
1591 ++brace_pos; // consume '.'
1592
1593 // Get field name
1594 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::identifier)) {
1595 continue;
1596 }
1597 std::string field_name = brace_contents[brace_pos].text();
1598 SourcePosition field_pos = brace_contents[brace_pos].position();
1599 ++brace_pos; // consume field name
1600
1601 // Look for '='
1602 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::equal)) {
1603 continue;
1604 }
1605 ++brace_pos; // consume '='
1606
1607 // Skip newlines after '='
1608 while (brace_pos < brace_contents.size() && brace_contents[brace_pos].is(TokenType::newline)) {
1609 ++brace_pos;
1610 }
1611
1612 // Get the value (identifier, or skip complex expressions)
1613 if (brace_pos >= brace_contents.size()) {
1614 continue;
1615 }
1616
1617 std::string value;
1618 if (brace_contents[brace_pos].is(TokenType::identifier)) {
1619 value = brace_contents[brace_pos].text();
1620 ++brace_pos;
1621 }
1622 else if (brace_contents[brace_pos].is(TokenType::integer_literal)) {
1623 value = brace_contents[brace_pos].text();
1624 ++brace_pos;
1625 }
1626 else {
1627 // Skip complex expressions (nested braces, etc.) until comma or end
1628 while (brace_pos < brace_contents.size() && !brace_contents[brace_pos].is(TokenType::comma) &&
1629 !brace_contents[brace_pos].is(TokenType::newline)) {
1630 ++brace_pos;
1631 }
1632 continue;
1633 }
1634
1635 fields.emplace_back(std::move(field_name), std::move(value), field_pos);
1636 }
1637
1638 // Skip past the closing brace
1639 current_ = skip_balanced_braces(tokens_, current_);
1640
1641 // Skip optional semicolon
1642 if (check(TokenType::semicolon)) {
1643 advance();
1644 }
1645
1646 structs.emplace_back(std::move(struct_type), std::move(variable_name), std::move(fields), name_pos);
1647 }
1648
1649 return structs;
1650}
1651
1653{
1654 std::vector<StructDefinition> definitions;
1655
1656 // Reset position to beginning
1657 current_ = 0;
1658
1659 while (!is_at_end()) {
1660 // Skip newlines
1661 while (check(TokenType::newline)) {
1662 advance();
1663 }
1664
1665 if (is_at_end()) {
1666 break;
1667 }
1668
1669 // Look for pattern: struct TYPE { members... } [;]
1670 std::size_t scan_start = current_;
1671
1672 if (!check(TokenType::identifier) || peek().text() != "struct") {
1673 if (current_ == scan_start) {
1674 advance();
1675 }
1676 continue;
1677 }
1678 advance(); // consume 'struct'
1679
1680 // Get the struct tag name
1681 if (!check(TokenType::identifier)) {
1682 continue;
1683 }
1684 std::string struct_name = peek().text();
1685 SourcePosition name_pos = peek().position();
1686 advance(); // consume tag name
1687
1688 // Skip any newlines before '{'
1689 while (check(TokenType::newline)) {
1690 advance();
1691 }
1692
1693 // A definition has a member body; anything else (forward declaration, variable declaration, struct-typed
1694 // member of an enclosing scan) is not a definition and is left for the outer loop to skip past.
1695 if (!check(TokenType::left_brace)) {
1696 continue;
1697 }
1698
1699 std::vector<Token> body_tokens = collect_brace_contents(tokens_, current_);
1700 current_ = skip_balanced_braces(tokens_, current_);
1701 if (check(TokenType::semicolon)) {
1702 advance();
1703 }
1704
1705 // Split the body into member declarations at depth-0 semicolons, dropping newlines and preprocessor
1706 // directive lines so a conditional inside the body cannot poison the neighboring members.
1707 std::vector<std::vector<Token>> member_runs;
1708 std::vector<Token> run;
1709 int nesting_depth = 0;
1710 for (std::size_t i = 0; i < body_tokens.size(); ++i) {
1711 const Token &tok = body_tokens[i];
1712 if (tok.is(TokenType::hash) && run.empty()) {
1713 while (i < body_tokens.size() && !body_tokens[i].is(TokenType::newline)) {
1714 ++i;
1715 }
1716 continue;
1717 }
1718 if (tok.is(TokenType::newline)) {
1719 continue;
1720 }
1721 if (tok.is(TokenType::left_brace)) {
1722 ++nesting_depth;
1723 }
1724 else if (tok.is(TokenType::right_brace)) {
1725 --nesting_depth;
1726 }
1727 if (tok.is(TokenType::semicolon) && nesting_depth == 0) {
1728 if (!run.empty()) {
1729 member_runs.push_back(std::move(run));
1730 run.clear();
1731 }
1732 continue;
1733 }
1734 run.push_back(tok);
1735 }
1736
1737 // Pattern-match each member against the simple declarator shape; anything else is skipped tolerantly.
1738 std::vector<StructMemberDeclaration> members;
1739 for (const auto &member_tokens : member_runs) {
1740 std::size_t pos = 0;
1741 bool is_const = false;
1742
1743 if (pos < member_tokens.size() && member_tokens[pos].is(TokenType::identifier) &&
1744 member_tokens[pos].text() == "const") {
1745 is_const = true;
1746 ++pos;
1747 }
1748 // A `struct TYPE` member's type is the tag name, not the keyword.
1749 if (pos < member_tokens.size() && member_tokens[pos].is(TokenType::identifier) &&
1750 member_tokens[pos].text() == "struct") {
1751 ++pos;
1752 }
1753 if (pos >= member_tokens.size() || !member_tokens[pos].is(TokenType::identifier)) {
1754 continue;
1755 }
1756 std::string type_name = member_tokens[pos].text();
1757 ++pos;
1758 // East-const spelling (`u16 const *`) qualifies the same declaration.
1759 if (pos < member_tokens.size() && member_tokens[pos].is(TokenType::identifier) &&
1760 member_tokens[pos].text() == "const") {
1761 is_const = true;
1762 ++pos;
1763 }
1764 std::size_t pointer_depth = 0;
1765 while (pos < member_tokens.size() && member_tokens[pos].is(TokenType::star)) {
1766 ++pointer_depth;
1767 ++pos;
1768 }
1769 if (pos >= member_tokens.size() || !member_tokens[pos].is(TokenType::identifier)) {
1770 continue;
1771 }
1772 std::string member_name = member_tokens[pos].text();
1773 SourcePosition member_pos = member_tokens[pos].position();
1774 ++pos;
1775 // Consume a bitfield width (`:1` or `:MACRO`); the width itself is not recorded.
1776 if (pos + 1 < member_tokens.size() && member_tokens[pos].is(TokenType::colon) &&
1777 (member_tokens[pos + 1].is(TokenType::integer_literal) ||
1778 member_tokens[pos + 1].is(TokenType::identifier))) {
1779 pos += 2;
1780 }
1781 if (pos != member_tokens.size()) {
1782 continue; // leftover tokens: array suffix, second declarator, or another unsupported shape
1783 }
1784
1785 members.push_back(
1787 std::move(type_name), pointer_depth, std::move(member_name), is_const, member_pos});
1788 }
1789
1790 definitions.push_back(StructDefinition{std::move(struct_name), std::move(members), name_pos});
1791 }
1792
1793 return definitions;
1794}
1795
1797{
1798 std::vector<IncbinDeclaration> incbins;
1799
1800 // Reset position to beginning
1801 current_ = 0;
1802
1803 while (!is_at_end()) {
1804 // Skip newlines
1805 while (check(TokenType::newline)) {
1806 advance();
1807 }
1808
1809 if (is_at_end()) {
1810 break;
1811 }
1812
1813 // Look for pattern: [static] [const] TYPE IDENTIFIER [] = INCBIN_MACRO("path");
1814 // or: [static] [const] TYPE IDENTIFIER [][SIZE] = { INCBIN_MACRO("p1"), ... };
1815 std::size_t scan_start = current_;
1816
1817 // Skip 'static' if present
1818 if (check(TokenType::identifier) && peek().text() == "static") {
1819 advance();
1820 }
1821
1822 // Skip 'const' if present
1823 if (check(TokenType::identifier) && peek().text() == "const") {
1824 advance();
1825 }
1826
1827 // Get type (e.g., u32, u16)
1828 if (!check(TokenType::identifier)) {
1829 if (current_ == scan_start) {
1830 advance();
1831 }
1832 continue;
1833 }
1834 advance(); // consume type
1835
1836 // Skip ALIGNED(N) directive if present (e.g., "const u16 ALIGNED(4) gTilesetPalettes_General")
1837 if (check(TokenType::identifier) && peek().text() == "ALIGNED") {
1838 advance(); // consume ALIGNED
1839 if (check(TokenType::left_paren)) {
1840 advance(); // consume '('
1841 // Skip until ')'
1842 while (!is_at_end() && !check(TokenType::right_paren)) {
1843 advance();
1844 }
1845 if (check(TokenType::right_paren)) {
1846 advance(); // consume ')'
1847 }
1848 }
1849 }
1850
1851 // Get variable name
1852 if (!check(TokenType::identifier)) {
1853 continue;
1854 }
1855 std::string variable_name = peek().text();
1856 SourcePosition name_pos = peek().position();
1857 advance(); // consume variable name
1858
1859 // Look for '['
1860 if (!check(TokenType::left_bracket)) {
1861 continue;
1862 }
1863 advance(); // consume '['
1864
1865 // Look for ']'
1866 if (!check(TokenType::right_bracket)) {
1867 // Skip to end of line
1868 while (!is_at_end() && !check(TokenType::newline) && !check(TokenType::semicolon)) {
1869 advance();
1870 }
1871 continue;
1872 }
1873 advance(); // consume ']'
1874
1875 // Check for optional second dimension [][SIZE]
1876 bool is_multi_dimensional = false;
1877 if (check(TokenType::left_bracket)) {
1878 is_multi_dimensional = true;
1879 advance(); // consume '['
1880 // Skip until ']'
1881 while (!is_at_end() && !check(TokenType::right_bracket)) {
1882 advance();
1883 }
1884 if (check(TokenType::right_bracket)) {
1885 advance(); // consume ']'
1886 }
1887 }
1888
1889 // Look for '='
1890 if (!check(TokenType::equal)) {
1891 continue;
1892 }
1893 advance(); // consume '='
1894
1895 // Skip any newlines after '='
1896 while (check(TokenType::newline)) {
1897 advance();
1898 }
1899
1900 if (is_multi_dimensional) {
1901 // Multi-path: expect { INCBIN_MACRO("p1"), INCBIN_MACRO("p2"), ... }
1902 if (!check(TokenType::left_brace)) {
1903 continue;
1904 }
1905
1906 std::vector<Token> brace_contents = collect_brace_contents(tokens_, current_);
1907 current_ = skip_balanced_braces(tokens_, current_);
1908
1909 std::vector<std::string> paths;
1910 std::string macro_name;
1911 std::size_t brace_pos = 0;
1912
1913 while (brace_pos < brace_contents.size()) {
1914 // Skip newlines and commas
1915 while (brace_pos < brace_contents.size() && (brace_contents[brace_pos].is(TokenType::newline) ||
1916 brace_contents[brace_pos].is(TokenType::comma))) {
1917 ++brace_pos;
1918 }
1919
1920 if (brace_pos >= brace_contents.size()) {
1921 break;
1922 }
1923
1924 // Look for INCBIN_* identifier
1925 if (!brace_contents[brace_pos].is(TokenType::identifier)) {
1926 ++brace_pos;
1927 continue;
1928 }
1929
1930 std::string token_text = brace_contents[brace_pos].text();
1931 if (!is_gfx_inclusion_macro(token_text)) {
1932 ++brace_pos;
1933 continue;
1934 }
1935
1936 if (macro_name.empty()) {
1937 macro_name = token_text;
1938 }
1939 ++brace_pos; // consume INCBIN_*
1940
1941 // Look for '('
1942 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::left_paren)) {
1943 continue;
1944 }
1945 ++brace_pos; // consume '('
1946
1947 // Look for string literal
1948 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::string_literal)) {
1949 continue;
1950 }
1951 paths.push_back(brace_contents[brace_pos].text());
1952 ++brace_pos; // consume string literal
1953
1954 // Skip until ')' (may have other stuff)
1955 while (brace_pos < brace_contents.size() && !brace_contents[brace_pos].is(TokenType::right_paren)) {
1956 ++brace_pos;
1957 }
1958 if (brace_pos < brace_contents.size()) {
1959 ++brace_pos; // consume ')'
1960 }
1961 }
1962
1963 if (!paths.empty()) {
1964 incbins.emplace_back(std::move(variable_name), std::move(macro_name), std::move(paths), name_pos);
1965 }
1966 }
1967 else {
1968 // Single path: expect INCBIN_MACRO("path")
1969 if (!check(TokenType::identifier)) {
1970 continue;
1971 }
1972
1973 std::string token_text = peek().text();
1974 if (!is_gfx_inclusion_macro(token_text)) {
1975 continue;
1976 }
1977 std::string macro_name = token_text;
1978 advance(); // consume INCBIN_*
1979
1980 // Look for '('
1981 if (!check(TokenType::left_paren)) {
1982 continue;
1983 }
1984 advance(); // consume '('
1985
1986 // Look for string literal
1987 if (!check(TokenType::string_literal)) {
1988 continue;
1989 }
1990 std::string path = peek().text();
1991 advance(); // consume string literal
1992
1993 // Skip until ')' and ';'
1994 while (!is_at_end() && !check(TokenType::semicolon) && !check(TokenType::newline)) {
1995 advance();
1996 }
1997 if (check(TokenType::semicolon)) {
1998 advance();
1999 }
2000
2001 incbins.emplace_back(std::move(variable_name), std::move(macro_name), std::move(path), name_pos);
2002 }
2003 }
2004
2005 return incbins;
2006}
2007
2008std::vector<IndexedArrayEntry> Parser::parse_indexed_entries(const std::vector<Token> &brace_contents)
2009{
2010 std::vector<IndexedArrayEntry> entries;
2011 std::size_t pos = 0;
2012
2013 while (pos < brace_contents.size()) {
2014 // Skip separators and blank lines between entries.
2015 while (pos < brace_contents.size() &&
2016 (brace_contents[pos].is(TokenType::newline) || brace_contents[pos].is(TokenType::comma))) {
2017 ++pos;
2018 }
2019 if (pos >= brace_contents.size()) {
2020 break;
2021 }
2022
2023 // Each entry must start with '['. Anything else is skipped to the next comma to stay resilient.
2024 if (!brace_contents[pos].is(TokenType::left_bracket)) {
2025 while (pos < brace_contents.size() && !brace_contents[pos].is(TokenType::comma)) {
2026 ++pos;
2027 }
2028 continue;
2029 }
2030 ++pos; // consume '['
2031
2032 // The designator: capture the first token's text (enum member name or numeric index).
2033 std::string index_name;
2034 SourcePosition entry_pos{};
2035 if (pos < brace_contents.size() &&
2036 (brace_contents[pos].is(TokenType::identifier) || brace_contents[pos].is(TokenType::integer_literal))) {
2037 index_name = brace_contents[pos].text();
2038 entry_pos = brace_contents[pos].position();
2039 }
2040 // Skip to the closing ']'.
2041 while (pos < brace_contents.size() && !brace_contents[pos].is(TokenType::right_bracket)) {
2042 ++pos;
2043 }
2044 if (pos < brace_contents.size()) {
2045 ++pos; // consume ']'
2046 }
2047
2048 // Skip newlines before '='.
2049 while (pos < brace_contents.size() && brace_contents[pos].is(TokenType::newline)) {
2050 ++pos;
2051 }
2052 if (pos >= brace_contents.size() || !brace_contents[pos].is(TokenType::equal)) {
2053 // Malformed entry (no '='); skip to the next comma.
2054 while (pos < brace_contents.size() && !brace_contents[pos].is(TokenType::comma)) {
2055 ++pos;
2056 }
2057 continue;
2058 }
2059 ++pos; // consume '='
2060
2061 // Collect value tokens up to the top-level comma that ends this entry.
2062 std::vector<Token> value_tokens;
2063 int depth = 0;
2064 while (pos < brace_contents.size()) {
2065 const Token &token = brace_contents[pos];
2066 if (depth == 0 && token.is(TokenType::comma)) {
2067 break;
2068 }
2070 ++depth;
2071 }
2073 --depth;
2074 }
2075 if (!token.is(TokenType::newline)) {
2076 value_tokens.push_back(token);
2077 }
2078 ++pos;
2079 }
2080
2081 std::optional<std::int64_t> value;
2082 if (!value_tokens.empty()) {
2083 auto eval = evaluate_expression(value_tokens);
2084 if (eval.has_value()) {
2085 value = eval.value();
2086 }
2087 }
2088
2089 if (!index_name.empty()) {
2090 entries.push_back(IndexedArrayEntry{std::move(index_name), value, std::move(value_tokens), entry_pos});
2091 }
2092 }
2093
2094 return entries;
2095}
2096
2098{
2099 std::vector<IndexedArrayDeclaration> arrays;
2100
2101 current_ = 0;
2102 cond_stack_.clear();
2103
2104 while (!is_at_end()) {
2105 while (check(TokenType::newline)) {
2106 advance();
2107 }
2108 if (is_at_end()) {
2109 break;
2110 }
2111
2112 if (check(TokenType::hash)) {
2113 if (try_handle_conditional()) {
2114 continue;
2115 }
2116 skip_to_next_line();
2117 continue;
2118 }
2119
2120 // Look for: [static] [const] TYPE IDENTIFIER [SIZE_EXPR] = { [index] = value, ... };
2121 std::size_t scan_start = current_;
2122
2123 if (check(TokenType::identifier) && peek().text() == "static") {
2124 advance();
2125 }
2126 if (check(TokenType::identifier) && peek().text() == "const") {
2127 advance();
2128 }
2129
2130 // Type identifier.
2131 if (!check(TokenType::identifier)) {
2132 if (current_ == scan_start) {
2133 advance();
2134 }
2135 continue;
2136 }
2137 advance(); // consume type
2138
2139 // Array name.
2140 if (!check(TokenType::identifier)) {
2141 continue;
2142 }
2143 std::string array_name = peek().text();
2144 SourcePosition name_pos = peek().position();
2145 advance(); // consume name
2146
2147 // Size expression in brackets.
2148 if (!check(TokenType::left_bracket)) {
2149 continue;
2150 }
2151 advance(); // consume '['
2152 int bracket_depth = 1;
2153 while (!is_at_end() && bracket_depth > 0) {
2154 if (check(TokenType::left_bracket)) {
2155 ++bracket_depth;
2156 }
2157 else if (check(TokenType::right_bracket)) {
2158 --bracket_depth;
2159 }
2160 advance();
2161 }
2162
2163 // Optional '=' then a brace initializer.
2164 while (check(TokenType::newline)) {
2165 advance();
2166 }
2167 if (!check(TokenType::equal)) {
2168 continue;
2169 }
2170 advance(); // consume '='
2171 while (check(TokenType::newline)) {
2172 advance();
2173 }
2174 if (!check(TokenType::left_brace)) {
2175 continue;
2176 }
2177
2178 std::vector<Token> brace_contents = collect_brace_contents(tokens_, current_);
2179 current_ = skip_balanced_braces(tokens_, current_);
2180 if (check(TokenType::semicolon)) {
2181 advance();
2182 }
2183
2184 std::vector<IndexedArrayEntry> entries = parse_indexed_entries(brace_contents);
2185 if (effective_cond_state() != CondState::skipping) {
2186 arrays.push_back(IndexedArrayDeclaration{std::move(array_name), std::move(entries), name_pos});
2187 }
2188 }
2189
2190 return arrays;
2191}
2192
2193} // namespace porytiles
FormattableError make_error(SourcePosition pos, const std::string &message) const
Creates a FormattableError with source context.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
Represents a parsed #define preprocessor statement.
const std::string & name() const
Returns the macro name.
bool has_int_value() const
Checks if this define has an integer value.
std::int64_t int_value() const
Returns the integer value.
ChainableResult< std::vector< ArrayDeclaration > > parse_pointer_arrays()
Parses all pointer array declarations from the token stream.
Definition parser.cpp:1234
ChainableResult< std::vector< EnumDeclaration > > parse_enums()
Parses all enum declarations from the token stream.
Definition parser.cpp:590
ChainableResult< std::vector< DefineStatement > > parse_defines()
Parses all #define statements from the token stream.
Definition parser.cpp:303
ChainableResult< std::vector< IncbinDeclaration > > parse_incbin_arrays()
Parses INCBIN array declarations from the token stream.
Definition parser.cpp:1796
ChainableResult< std::vector< StructVariableDeclaration > > parse_struct_variables()
Parses struct variable declarations from the token stream.
Definition parser.cpp:1420
ChainableResult< std::vector< StructInitializerDeclaration > > parse_struct_initializers()
Parses struct variable declarations with their designated initializer fields.
Definition parser.cpp:1500
TolerantEnumScan parse_enums_tolerant()
Parses all enum declarations, tolerating individual member evaluation failures.
Definition parser.cpp:562
ChainableResult< std::vector< StructDefinition > > parse_struct_definitions()
Parses struct type definitions from the token stream.
Definition parser.cpp:1652
ChainableResult< std::vector< IndexedArrayDeclaration > > parse_indexed_arrays()
Parses array declarations that use designated (indexed) initializers.
Definition parser.cpp:2097
TolerantDefineScan parse_defines_tolerant()
Parses all #define statements, tolerating individual evaluation failures.
Definition parser.cpp:413
ChainableResult< std::vector< FunctionDefinition > > parse_functions()
Parses function definitions from the token stream.
Definition parser.cpp:1344
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
Represents a lexical token from C source code.
Definition token.hpp:100
bool is(TokenType type) const
Checks if this token is of the specified type.
Definition token.hpp:160
const SourcePosition & position() const
Returns the source position where the token starts.
Definition token.hpp:141
TokenType type() const
Returns the token type.
Definition token.hpp:125
const std::string & text() const
Returns the raw text of the token.
Definition token.hpp:133
std::size_t start
std::string name
TokenType
Enumeration of token types recognized by the C parser lexer.
Definition token.hpp:17
A parsed C array declaration that uses designated initializers.
Represents a position within source content.
std::size_t column
1-based column number
A parsed C struct type definition.
One member declaration inside a C struct definition.
The result of a tolerant #define scan.
std::vector< DefineStatement > defines
std::vector< SkippedConstruct > skipped
One enum member from a tolerant enum scan.
The result of a tolerant enum scan.
std::vector< TolerantEnum > enums