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
19[[nodiscard]] std::size_t skip_balanced_braces(const std::vector<Token> &tokens, std::size_t start)
20{
21 if (start >= tokens.size() || !tokens[start].is(TokenType::left_brace)) {
22 return start;
23 }
24
25 std::size_t pos = start + 1;
26 int depth = 1;
27
28 while (pos < tokens.size() && depth > 0) {
29 if (tokens[pos].is(TokenType::left_brace)) {
30 ++depth;
31 }
32 else if (tokens[pos].is(TokenType::right_brace)) {
33 --depth;
34 }
35 ++pos;
36 }
37
38 return pos;
39}
40
48[[nodiscard]] std::vector<Token> collect_brace_contents(const std::vector<Token> &tokens, std::size_t start)
49{
50 std::vector<Token> contents;
51
52 if (start >= tokens.size() || !tokens[start].is(TokenType::left_brace)) {
53 return contents;
54 }
55
56 std::size_t pos = start + 1;
57 int depth = 1;
58
59 while (pos < tokens.size() && depth > 0) {
60 if (tokens[pos].is(TokenType::left_brace)) {
61 ++depth;
62 }
63 else if (tokens[pos].is(TokenType::right_brace)) {
64 --depth;
65 if (depth == 0) {
66 break;
67 }
68 }
69 contents.push_back(tokens[pos]);
70 ++pos;
71 }
72
73 return contents;
74}
75
82[[nodiscard]] std::vector<std::string> extract_identifier_elements(const std::vector<Token> &brace_contents)
83{
84 std::vector<std::string> elements;
85
86 for (const auto &token : brace_contents) {
87 if (token.is(TokenType::identifier)) {
88 elements.push_back(token.text());
89 }
90 // Skip commas, newlines, and other tokens
91 }
92
93 return elements;
94}
95
103[[nodiscard]] std::size_t skip_balanced_parens(const std::vector<Token> &tokens, std::size_t start)
104{
105 if (start >= tokens.size() || !tokens[start].is(TokenType::left_paren)) {
106 return start;
107 }
108
109 std::size_t pos = start + 1;
110 int depth = 1;
111
112 while (pos < tokens.size() && depth > 0) {
113 if (tokens[pos].is(TokenType::left_paren)) {
114 ++depth;
115 }
116 else if (tokens[pos].is(TokenType::right_paren)) {
117 --depth;
118 }
119 ++pos;
120 }
121
122 return pos;
123}
124
125} // namespace
126
127FormattableError Parser::make_error(SourcePosition pos, std::string message) const
128{
129 if (context_ != nullptr) {
130 return context_->make_error(pos, std::move(message));
131 }
132 return FormattableError{format_->format("{}:{}: {}", pos.line, pos.column, message)};
133}
134
136{
137 std::vector<DefineStatement> defines;
138
139 while (!is_at_end()) {
140 // Look for # token
141 if (check(TokenType::hash)) {
142 advance(); // consume #
143
144 // Check if this is a #define
145 if (check(TokenType::kw_define)) {
146 auto result = parse_define();
147 if (!result.has_value()) {
149 }
150 defines.push_back(std::move(result).value());
151 }
152 else {
153 // Skip other preprocessor directives
154 skip_to_next_line();
155 }
156 }
157 else {
158 // Skip non-preprocessor tokens
159 advance();
160 }
161 }
162
163 return defines;
164}
165
167{
168 std::vector<EnumDeclaration> enums;
169
170 // Reset position to beginning (allows calling both parse_defines and parse_enums)
171 current_ = 0;
172
173 while (!is_at_end()) {
174 if (check(TokenType::kw_enum)) {
175 auto result = parse_enum();
176 if (!result.has_value()) {
178 }
179 enums.push_back(std::move(result).value());
180 }
181 else {
182 advance();
183 }
184 }
185
186 return enums;
187}
188
189ChainableResult<EnumDeclaration> Parser::parse_enum()
190{
191 SourcePosition enum_pos = peek().position();
192 advance(); // consume 'enum'
193
194 // Check for optional enum name
195 std::optional<std::string> enum_name;
196 if (check(TokenType::identifier)) {
197 enum_name = peek().text();
198 advance();
199 }
200
201 // Skip newlines before opening brace (handles `enum\n{` style)
202 while (check(TokenType::newline)) {
203 advance();
204 }
205
206 // Expect opening brace
207 if (!check(TokenType::left_brace)) {
208 return make_error(peek().position(), "expected '{' after 'enum'");
209 }
210 advance(); // consume '{'
211
212 // Parse members
213 std::vector<EnumMember> members;
214 std::int64_t counter = 0;
215
216 while (!is_at_end() && !check(TokenType::right_brace)) {
217 // Skip newlines
218 while (check(TokenType::newline)) {
219 advance();
220 }
221
222 if (check(TokenType::right_brace)) {
223 break;
224 }
225
226 auto member_result = parse_enum_member(counter);
227 if (!member_result.has_value()) {
228 return ChainableResult<EnumDeclaration>{member_result};
229 }
230 members.push_back(std::move(member_result).value());
231
232 // Skip trailing comma (optional for last member)
233 if (check(TokenType::comma)) {
234 advance();
235 }
236
237 // Skip newlines after comma
238 while (check(TokenType::newline)) {
239 advance();
240 }
241 }
242
243 // Expect closing brace
244 if (!check(TokenType::right_brace)) {
245 return make_error(peek().position(), "expected '}' to close enum");
246 }
247 advance(); // consume '}'
248
249 // Skip optional semicolon
250 if (check(TokenType::semicolon)) {
251 advance();
252 }
253
254 if (enum_name.has_value()) {
255 return EnumDeclaration{std::move(enum_name).value(), std::move(members), enum_pos};
256 }
257 return EnumDeclaration{std::move(members), enum_pos};
258}
259
260ChainableResult<EnumMember> Parser::parse_enum_member(std::int64_t &counter)
261{
262 // Expect identifier
263 if (!check(TokenType::identifier)) {
264 return make_error(peek().position(), "expected identifier for enum member");
265 }
266
267 std::string name = peek().text();
268 SourcePosition member_pos = peek().position();
269 advance(); // consume identifier
270
271 // Check for explicit value assignment
272 bool has_explicit = false;
273 if (check(TokenType::equal)) {
274 advance(); // consume '='
275 has_explicit = true;
276
277 // Collect expression tokens until comma, right_brace, or newline
278 std::vector<Token> expr_tokens = collect_enum_value_tokens();
279
280 if (expr_tokens.empty()) {
281 return make_error(
282 member_pos,
283 format_->format("expected expression after '=' for enum member '{}'", FormatParam{name, Style::bold}));
284 }
285
286 auto eval_result = evaluate_expression(expr_tokens);
287 if (!eval_result.has_value()) {
288 return ChainableResult<EnumMember>{
289 make_error(
290 member_pos,
291 format_->format(
292 "failed to evaluate expression for enum member '{}'", FormatParam{name, Style::bold})),
293 eval_result};
294 }
295
296 counter = eval_result.value();
297 }
298
299 EnumMember member{std::move(name), counter, has_explicit, member_pos};
300 counter++; // Increment for next member
301 return member;
302}
303
304std::vector<Token> Parser::collect_enum_value_tokens()
305{
306 std::vector<Token> expr_tokens;
307
308 while (!is_at_end() && !check(TokenType::comma) && !check(TokenType::right_brace) && !check(TokenType::newline)) {
309 expr_tokens.push_back(peek());
310 advance();
311 }
312
313 return expr_tokens;
314}
315
316const Token &Parser::peek() const
317{
318 if (is_at_end()) {
319 return tokens_.back(); // Should be end_of_file
320 }
321 return tokens_[current_];
322}
323
324const Token &Parser::peek_next() const
325{
326 if (current_ + 1 >= tokens_.size()) {
327 return tokens_.back();
328 }
329 return tokens_[current_ + 1];
330}
331
332const Token &Parser::advance()
333{
334 if (!is_at_end()) {
335 ++current_;
336 }
337 return tokens_[current_ - 1];
338}
339
340bool Parser::is_at_end() const
341{
342 return current_ >= tokens_.size() || tokens_[current_].is(TokenType::end_of_file);
343}
344
345bool Parser::check(TokenType type) const
346{
347 if (is_at_end()) {
348 return type == TokenType::end_of_file;
349 }
350 return peek().is(type);
351}
352
353bool Parser::match(TokenType type)
354{
355 if (check(type)) {
356 advance();
357 return true;
358 }
359 return false;
360}
361
362void Parser::skip_to_next_line()
363{
364 while (!is_at_end() && !check(TokenType::newline)) {
365 advance();
366 }
367 if (check(TokenType::newline)) {
368 advance(); // consume the newline
369 }
370}
371
372bool Parser::is_at_line_end() const
373{
374 return is_at_end() || check(TokenType::newline);
375}
376
377ChainableResult<DefineStatement> Parser::parse_define()
378{
379 SourcePosition define_pos = peek().position();
380 advance(); // consume 'define'
381
382 // Skip whitespace (already handled by lexer, but newlines are significant)
383
384 // Expect identifier for macro name
385 if (!check(TokenType::identifier)) {
386 return make_error(peek().position(), "expected identifier after '#define'");
387 }
388
389 std::string name = peek().text();
390 SourcePosition name_pos = peek().position();
391 std::size_t name_end_column = name_pos.column + name.size();
392 advance(); // consume identifier
393
394 // Check for parametric macro (function-like macro)
395 // These have ( immediately after the name with no space
396 // We detect this by checking if the ( starts at the column immediately after the identifier
397 if (check(TokenType::left_paren)) {
398 SourcePosition paren_pos = peek().position();
399 // If the ( is immediately after the identifier (same line, adjacent column), it's a parametric macro
400 if (paren_pos.line == name_pos.line && paren_pos.column == name_end_column) {
401 // This is a parametric macro - skip it
402 skip_to_next_line();
403 return DefineStatement{std::move(name), define_pos}; // Return as flag define
404 }
405 // Otherwise, the ( starts an expression - fall through to expression evaluation
406 }
407
408 // Check for end of line (flag-like define with no value)
409 if (is_at_line_end()) {
410 if (check(TokenType::newline)) {
411 advance();
412 }
413 return DefineStatement{std::move(name), define_pos};
414 }
415
416 // Check for string literal value
417 if (check(TokenType::string_literal)) {
418 std::string value = peek().text();
419 advance();
420 skip_to_next_line();
421 return DefineStatement{std::move(name), std::move(value), define_pos};
422 }
423
424 // Otherwise, collect and evaluate expression
425 std::vector<Token> expr_tokens = collect_expression_tokens();
426
427 if (expr_tokens.empty()) {
428 // No expression, treat as flag define
429 return DefineStatement{std::move(name), define_pos};
430 }
431
432 auto result = evaluate_expression(expr_tokens);
433 if (!result.has_value()) {
434 return ChainableResult<DefineStatement>{
435 make_error(
436 define_pos,
437 format_->format("failed to evaluate expression for #define '{}'", FormatParam{name, Style::bold})),
438 result};
439 }
440
441 std::int64_t value = result.value();
442
443 // Store in symbol table for later references
444 defined_values_[name] = value;
445
446 return DefineStatement{std::move(name), value, define_pos};
447}
448
449std::vector<Token> Parser::collect_expression_tokens()
450{
451 std::vector<Token> expr_tokens;
452
453 while (!is_at_line_end()) {
454 expr_tokens.push_back(peek());
455 advance();
456 }
457
458 // Consume the newline if present
459 if (check(TokenType::newline)) {
460 advance();
461 }
462
463 return expr_tokens;
464}
465
466ChainableResult<std::int64_t> Parser::evaluate_expression(const std::vector<Token> &expr_tokens)
467{
468 if (expr_tokens.empty()) {
469 return make_error(SourcePosition{}, "empty expression");
470 }
471
472 // Convert to postfix notation using Shunting Yard
473 std::vector<Token> postfix = to_postfix(expr_tokens);
474
475 // Evaluate the postfix expression
476 return evaluate_postfix(postfix);
477}
478
479std::vector<Token> Parser::to_postfix(const std::vector<Token> &expr_tokens)
480{
481 std::vector<Token> output;
482 std::stack<Token> operators;
483
484 bool expect_operand = true; // Track if we expect an operand (for unary operators)
485
486 for (std::size_t i = 0; i < expr_tokens.size(); ++i) {
487 const Token &token = expr_tokens[i];
488
489 if (token.is(TokenType::integer_literal) || token.is(TokenType::identifier)) {
490 output.push_back(token);
491 expect_operand = false;
492 }
493 else if (token.is(TokenType::left_paren)) {
494 operators.push(token);
495 expect_operand = true;
496 }
497 else if (token.is(TokenType::right_paren)) {
498 while (!operators.empty() && !operators.top().is(TokenType::left_paren)) {
499 output.push_back(operators.top());
500 operators.pop();
501 }
502 if (!operators.empty() && operators.top().is(TokenType::left_paren)) {
503 operators.pop(); // Discard the left paren
504 }
505 expect_operand = false;
506 }
507 else if (is_operator(token.type())) {
508 // Handle unary operators (-, ~, !)
509 if (expect_operand && is_unary_operator(token.type())) {
510 // Create a special unary token by prefixing with 'u'
511 // We'll handle this in evaluation
512 Token unary_token{token.type(), "u" + token.text(), token.position()};
513 operators.push(unary_token);
514 }
515 else {
516 // Binary operator
517 while (!operators.empty() && !operators.top().is(TokenType::left_paren) &&
518 is_operator(operators.top().type())) {
519 int top_prec = operator_precedence(operators.top().type());
520 int curr_prec = operator_precedence(token.type());
521
522 if (top_prec < curr_prec || (top_prec == curr_prec && is_left_associative(token.type()))) {
523 output.push_back(operators.top());
524 operators.pop();
525 }
526 else {
527 break;
528 }
529 }
530 operators.push(token);
531 expect_operand = true;
532 }
533 }
534 // Skip unknown tokens
535 }
536
537 // Pop remaining operators
538 while (!operators.empty()) {
539 if (!operators.top().is(TokenType::left_paren)) {
540 output.push_back(operators.top());
541 }
542 operators.pop();
543 }
544
545 return output;
546}
547
548ChainableResult<std::int64_t> Parser::evaluate_postfix(const std::vector<Token> &postfix)
549{
550 std::stack<std::int64_t> values;
551
552 for (const Token &token : postfix) {
553 if (token.is(TokenType::integer_literal)) {
554 values.push(token.int_value());
555 }
556 else if (token.is(TokenType::identifier)) {
557 // Look up in symbol table
558 auto it = defined_values_.find(token.text());
559 if (it != defined_values_.end()) {
560 values.push(it->second);
561 }
562 else {
563 // Unknown identifier - could be an error or treat as 0
564 return make_error(
565 token.position(),
566 format_->format("unknown identifier '{}'", FormatParam{token.text(), Style::bold}));
567 }
568 }
569 else if (is_operator(token.type())) {
570 // Check if it's a unary operator (text starts with 'u')
571 if (token.text().size() > 1 && token.text()[0] == 'u') {
572 if (values.empty()) {
573 return make_error(
574 token.position(),
575 format_->format(
576 "unary operator '{}' missing operand", FormatParam{token.text().substr(1), Style::bold}));
577 }
578 std::int64_t operand = values.top();
579 values.pop();
580
581 std::int64_t result = 0;
582 switch (token.type()) {
583 case TokenType::minus:
584 result = -operand;
585 break;
586 case TokenType::tilde:
587 result = ~operand;
588 break;
590 result = operand == 0 ? 1 : 0;
591 break;
592 default:
593 return make_error(
594 token.position(),
595 format_->format("unknown unary operator '{}'", FormatParam{token.text(), Style::bold}));
596 }
597 values.push(result);
598 }
599 else {
600 // Binary operator
601 if (values.size() < 2) {
602 return make_error(
603 token.position(),
604 format_->format(
605 "binary operator '{}' missing operands", FormatParam{token.text(), Style::bold}));
606 }
607 std::int64_t right = values.top();
608 values.pop();
609 std::int64_t left = values.top();
610 values.pop();
611
612 std::int64_t result = 0;
613 switch (token.type()) {
614 case TokenType::plus:
615 result = left + right;
616 break;
617 case TokenType::minus:
618 result = left - right;
619 break;
620 case TokenType::star:
621 result = left * right;
622 break;
623 case TokenType::slash:
624 if (right == 0) {
625 return make_error(token.position(), "division by zero");
626 }
627 result = left / right;
628 break;
630 if (right == 0) {
631 return make_error(token.position(), "modulo by zero");
632 }
633 result = left % right;
634 break;
636 result = left & right;
637 break;
638 case TokenType::pipe:
639 result = left | right;
640 break;
641 case TokenType::caret:
642 result = left ^ right;
643 break;
645 result = left << right;
646 break;
648 result = left >> right;
649 break;
650 default:
651 return make_error(
652 token.position(),
653 format_->format("unknown binary operator '{}'", FormatParam{token.text(), Style::bold}));
654 }
655 values.push(result);
656 }
657 }
658 }
659
660 if (values.empty()) {
661 return make_error(SourcePosition{}, "expression evaluated to no value");
662 }
663
664 return values.top();
665}
666
667int Parser::operator_precedence(TokenType type) const
668{
669 // Lower number = higher precedence (evaluated first)
670 // Based on C operator precedence
671 switch (type) {
672 case TokenType::star:
673 case TokenType::slash:
675 return 3;
676 case TokenType::plus:
677 case TokenType::minus:
678 return 4;
681 return 5;
682 case TokenType::less:
686 return 6;
689 return 7;
691 return 8;
692 case TokenType::caret:
693 return 9;
694 case TokenType::pipe:
695 return 10;
697 return 11;
699 return 12;
700 default:
701 return 99; // Lowest precedence for unknown
702 }
703}
704
705bool Parser::is_left_associative(TokenType type) const
706{
707 // All our binary operators are left-associative
708 return true;
709}
710
711bool Parser::is_operator(TokenType type) const
712{
713 switch (type) {
714 case TokenType::plus:
715 case TokenType::minus:
716 case TokenType::star:
717 case TokenType::slash:
720 case TokenType::pipe:
721 case TokenType::caret:
722 case TokenType::tilde:
724 case TokenType::less:
734 return true;
735 default:
736 return false;
737 }
738}
739
740bool Parser::is_unary_operator(TokenType type) const
741{
742 switch (type) {
743 case TokenType::minus: // Unary negation
744 case TokenType::tilde: // Bitwise NOT
745 case TokenType::exclaim: // Logical NOT
746 case TokenType::plus: // Unary plus (no-op but valid)
747 return true;
748 default:
749 return false;
750 }
751}
752
754{
755 std::vector<ArrayDeclaration> arrays;
756
757 // Reset position to beginning
758 current_ = 0;
759
760 while (!is_at_end()) {
761 // Skip newlines
762 while (check(TokenType::newline)) {
763 advance();
764 }
765
766 if (is_at_end()) {
767 break;
768 }
769
770 // Look for pattern: [static] [const] TYPE * [const] IDENTIFIER [] = { ... }
771 // We need to find an identifier followed by [] = {
772 // Start by looking for an identifier that could be an array name
773
774 std::size_t scan_start = current_;
775
776 // Skip 'static' if present
777 if (check(TokenType::identifier) && peek().text() == "static") {
778 advance();
779 }
780
781 // Skip 'const' if present
782 if (check(TokenType::identifier) && peek().text() == "const") {
783 advance();
784 }
785
786 // Skip type identifier (e.g., u16, int, etc.)
787 if (check(TokenType::identifier)) {
788 advance();
789 }
790 else {
791 // Not a declaration, skip this token
792 if (current_ == scan_start) {
793 advance();
794 }
795 continue;
796 }
797
798 // Look for * (pointer)
799 if (!check(TokenType::star)) {
800 continue;
801 }
802 advance(); // consume *
803
804 // Skip 'const' if present after *
805 if (check(TokenType::identifier) && peek().text() == "const") {
806 advance();
807 }
808
809 // Now we should have the array name identifier
810 if (!check(TokenType::identifier)) {
811 continue;
812 }
813
814 std::string array_name = peek().text();
815 SourcePosition name_pos = peek().position();
816 advance(); // consume identifier
817
818 // Look for []
819 if (!check(TokenType::left_bracket)) {
820 continue;
821 }
822 advance(); // consume [
823
824 if (!check(TokenType::right_bracket)) {
825 continue;
826 }
827 advance(); // consume ]
828
829 // Look for =
830 if (!check(TokenType::equal)) {
831 continue;
832 }
833 advance(); // consume =
834
835 // Skip any newlines before {
836 while (check(TokenType::newline)) {
837 advance();
838 }
839
840 // Look for {
841 if (!check(TokenType::left_brace)) {
842 continue;
843 }
844
845 // Found a pointer array declaration - extract elements
846 std::vector<Token> brace_contents = collect_brace_contents(tokens_, current_);
847 std::vector<std::string> elements = extract_identifier_elements(brace_contents);
848
849 // Skip past the closing brace
850 current_ = skip_balanced_braces(tokens_, current_);
851
852 // Skip optional semicolon
853 if (check(TokenType::semicolon)) {
854 advance();
855 }
856
857 arrays.emplace_back(std::move(array_name), std::move(elements), name_pos);
858 }
859
860 return arrays;
861}
862
864{
865 std::vector<FunctionDefinition> functions;
866
867 // Reset position to beginning
868 current_ = 0;
869
870 while (!is_at_end()) {
871 // Skip newlines
872 while (check(TokenType::newline)) {
873 advance();
874 }
875
876 if (is_at_end()) {
877 break;
878 }
879
880 // Look for pattern: [static] TYPE IDENTIFIER ( params ) { body }
881 std::size_t scan_start = current_;
882
883 // Skip 'static' if present
884 if (check(TokenType::identifier) && peek().text() == "static") {
885 advance();
886 }
887
888 // Skip return type identifier (e.g., void, int, etc.)
889 if (check(TokenType::identifier)) {
890 advance();
891 }
892 else {
893 // Not a function, skip this token
894 if (current_ == scan_start) {
895 advance();
896 }
897 continue;
898 }
899
900 // Now we should have the function name identifier
901 if (!check(TokenType::identifier)) {
902 continue;
903 }
904
905 std::string func_name = peek().text();
906 SourcePosition name_pos = peek().position();
907 advance(); // consume identifier
908
909 // Look for (
910 if (!check(TokenType::left_paren)) {
911 continue;
912 }
913
914 // Skip past the parameter list
915 current_ = skip_balanced_parens(tokens_, current_);
916
917 // Skip any newlines before {
918 while (check(TokenType::newline)) {
919 advance();
920 }
921
922 // Look for {
923 if (!check(TokenType::left_brace)) {
924 continue;
925 }
926
927 // Found a function definition - extract body tokens
928 std::vector<Token> body_tokens = collect_brace_contents(tokens_, current_);
929
930 // Skip past the closing brace
931 current_ = skip_balanced_braces(tokens_, current_);
932
933 functions.emplace_back(std::move(func_name), std::move(body_tokens), name_pos);
934 }
935
936 return functions;
937}
938
940{
941 std::vector<StructVariableDeclaration> structs;
942
943 // Reset position to beginning
944 current_ = 0;
945
946 while (!is_at_end()) {
947 // Skip newlines
948 while (check(TokenType::newline)) {
949 advance();
950 }
951
952 if (is_at_end()) {
953 break;
954 }
955
956 // Look for pattern: [const] struct TYPE IDENTIFIER = { ... } [;]
957 std::size_t scan_start = current_;
958
959 // Skip 'const' if present
960 if (check(TokenType::identifier) && peek().text() == "const") {
961 advance();
962 }
963
964 // Look for 'struct' keyword (lexer treats it as an identifier)
965 if (!check(TokenType::identifier) || peek().text() != "struct") {
966 // Not a struct declaration, skip this token
967 if (current_ == scan_start) {
968 advance();
969 }
970 continue;
971 }
972 advance(); // consume 'struct'
973
974 // Get struct type name
975 if (!check(TokenType::identifier)) {
976 continue;
977 }
978 std::string struct_type = peek().text();
979 advance(); // consume type name
980
981 // Get variable name
982 if (!check(TokenType::identifier)) {
983 continue;
984 }
985 std::string variable_name = peek().text();
986 SourcePosition name_pos = peek().position();
987 advance(); // consume variable name
988
989 // Look for '='
990 if (!check(TokenType::equal)) {
991 continue;
992 }
993 advance(); // consume '='
994
995 // Skip any newlines before '{'
996 while (check(TokenType::newline)) {
997 advance();
998 }
999
1000 // Look for '{'
1001 if (!check(TokenType::left_brace)) {
1002 continue;
1003 }
1004
1005 // Skip balanced braces (we don't need the body contents)
1006 current_ = skip_balanced_braces(tokens_, current_);
1007
1008 // Skip optional semicolon
1009 if (check(TokenType::semicolon)) {
1010 advance();
1011 }
1012
1013 structs.emplace_back(std::move(struct_type), std::move(variable_name), name_pos);
1014 }
1015
1016 return structs;
1017}
1018
1020{
1021 std::vector<StructInitializerDeclaration> structs;
1022
1023 // Reset position to beginning
1024 current_ = 0;
1025
1026 while (!is_at_end()) {
1027 // Skip newlines
1028 while (check(TokenType::newline)) {
1029 advance();
1030 }
1031
1032 if (is_at_end()) {
1033 break;
1034 }
1035
1036 // Look for pattern: [const] struct TYPE IDENTIFIER = { .field = value, ... } [;]
1037 std::size_t scan_start = current_;
1038
1039 // Skip 'const' if present
1040 if (check(TokenType::identifier) && peek().text() == "const") {
1041 advance();
1042 }
1043
1044 // Look for 'struct' keyword (lexer treats it as an identifier)
1045 if (!check(TokenType::identifier) || peek().text() != "struct") {
1046 // Not a struct declaration, skip this token
1047 if (current_ == scan_start) {
1048 advance();
1049 }
1050 continue;
1051 }
1052 advance(); // consume 'struct'
1053
1054 // Get struct type name
1055 if (!check(TokenType::identifier)) {
1056 continue;
1057 }
1058 std::string struct_type = peek().text();
1059 advance(); // consume type name
1060
1061 // Get variable name
1062 if (!check(TokenType::identifier)) {
1063 continue;
1064 }
1065 std::string variable_name = peek().text();
1066 SourcePosition name_pos = peek().position();
1067 advance(); // consume variable name
1068
1069 // Look for '='
1070 if (!check(TokenType::equal)) {
1071 continue;
1072 }
1073 advance(); // consume '='
1074
1075 // Skip any newlines before '{'
1076 while (check(TokenType::newline)) {
1077 advance();
1078 }
1079
1080 // Look for '{'
1081 if (!check(TokenType::left_brace)) {
1082 continue;
1083 }
1084
1085 // Parse the designated initializer fields
1086 std::vector<DesignatedInitializerField> fields;
1087 std::vector<Token> brace_contents = collect_brace_contents(tokens_, current_);
1088
1089 // Parse each .field = value pair from the brace contents
1090 std::size_t brace_pos = 0;
1091 while (brace_pos < brace_contents.size()) {
1092 // Skip newlines and commas
1093 while (brace_pos < brace_contents.size() && (brace_contents[brace_pos].is(TokenType::newline) ||
1094 brace_contents[brace_pos].is(TokenType::comma))) {
1095 ++brace_pos;
1096 }
1097
1098 if (brace_pos >= brace_contents.size()) {
1099 break;
1100 }
1101
1102 // Look for '.'
1103 if (!brace_contents[brace_pos].is(TokenType::period)) {
1104 // Skip until next comma or end
1105 while (brace_pos < brace_contents.size() && !brace_contents[brace_pos].is(TokenType::comma)) {
1106 ++brace_pos;
1107 }
1108 continue;
1109 }
1110 ++brace_pos; // consume '.'
1111
1112 // Get field name
1113 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::identifier)) {
1114 continue;
1115 }
1116 std::string field_name = brace_contents[brace_pos].text();
1117 SourcePosition field_pos = brace_contents[brace_pos].position();
1118 ++brace_pos; // consume field name
1119
1120 // Look for '='
1121 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::equal)) {
1122 continue;
1123 }
1124 ++brace_pos; // consume '='
1125
1126 // Skip newlines after '='
1127 while (brace_pos < brace_contents.size() && brace_contents[brace_pos].is(TokenType::newline)) {
1128 ++brace_pos;
1129 }
1130
1131 // Get the value (identifier, or skip complex expressions)
1132 if (brace_pos >= brace_contents.size()) {
1133 continue;
1134 }
1135
1136 std::string value;
1137 if (brace_contents[brace_pos].is(TokenType::identifier)) {
1138 value = brace_contents[brace_pos].text();
1139 ++brace_pos;
1140 }
1141 else if (brace_contents[brace_pos].is(TokenType::integer_literal)) {
1142 value = brace_contents[brace_pos].text();
1143 ++brace_pos;
1144 }
1145 else {
1146 // Skip complex expressions (nested braces, etc.) until comma or end
1147 while (brace_pos < brace_contents.size() && !brace_contents[brace_pos].is(TokenType::comma) &&
1148 !brace_contents[brace_pos].is(TokenType::newline)) {
1149 ++brace_pos;
1150 }
1151 continue;
1152 }
1153
1154 fields.emplace_back(std::move(field_name), std::move(value), field_pos);
1155 }
1156
1157 // Skip past the closing brace
1158 current_ = skip_balanced_braces(tokens_, current_);
1159
1160 // Skip optional semicolon
1161 if (check(TokenType::semicolon)) {
1162 advance();
1163 }
1164
1165 structs.emplace_back(std::move(struct_type), std::move(variable_name), std::move(fields), name_pos);
1166 }
1167
1168 return structs;
1169}
1170
1172{
1173 std::vector<IncbinDeclaration> incbins;
1174
1175 // Reset position to beginning
1176 current_ = 0;
1177
1178 while (!is_at_end()) {
1179 // Skip newlines
1180 while (check(TokenType::newline)) {
1181 advance();
1182 }
1183
1184 if (is_at_end()) {
1185 break;
1186 }
1187
1188 // Look for pattern: [static] [const] TYPE IDENTIFIER [] = INCBIN_MACRO("path");
1189 // or: [static] [const] TYPE IDENTIFIER [][SIZE] = { INCBIN_MACRO("p1"), ... };
1190 std::size_t scan_start = current_;
1191
1192 // Skip 'static' if present
1193 if (check(TokenType::identifier) && peek().text() == "static") {
1194 advance();
1195 }
1196
1197 // Skip 'const' if present
1198 if (check(TokenType::identifier) && peek().text() == "const") {
1199 advance();
1200 }
1201
1202 // Get type (e.g., u32, u16)
1203 if (!check(TokenType::identifier)) {
1204 if (current_ == scan_start) {
1205 advance();
1206 }
1207 continue;
1208 }
1209 advance(); // consume type
1210
1211 // Skip ALIGNED(N) directive if present (e.g., "const u16 ALIGNED(4) gTilesetPalettes_General")
1212 if (check(TokenType::identifier) && peek().text() == "ALIGNED") {
1213 advance(); // consume ALIGNED
1214 if (check(TokenType::left_paren)) {
1215 advance(); // consume '('
1216 // Skip until ')'
1217 while (!is_at_end() && !check(TokenType::right_paren)) {
1218 advance();
1219 }
1220 if (check(TokenType::right_paren)) {
1221 advance(); // consume ')'
1222 }
1223 }
1224 }
1225
1226 // Get variable name
1227 if (!check(TokenType::identifier)) {
1228 continue;
1229 }
1230 std::string variable_name = peek().text();
1231 SourcePosition name_pos = peek().position();
1232 advance(); // consume variable name
1233
1234 // Look for '['
1235 if (!check(TokenType::left_bracket)) {
1236 continue;
1237 }
1238 advance(); // consume '['
1239
1240 // Look for ']'
1241 if (!check(TokenType::right_bracket)) {
1242 // Skip to end of line
1243 while (!is_at_end() && !check(TokenType::newline) && !check(TokenType::semicolon)) {
1244 advance();
1245 }
1246 continue;
1247 }
1248 advance(); // consume ']'
1249
1250 // Check for optional second dimension [][SIZE]
1251 bool is_multi_dimensional = false;
1252 if (check(TokenType::left_bracket)) {
1253 is_multi_dimensional = true;
1254 advance(); // consume '['
1255 // Skip until ']'
1256 while (!is_at_end() && !check(TokenType::right_bracket)) {
1257 advance();
1258 }
1259 if (check(TokenType::right_bracket)) {
1260 advance(); // consume ']'
1261 }
1262 }
1263
1264 // Look for '='
1265 if (!check(TokenType::equal)) {
1266 continue;
1267 }
1268 advance(); // consume '='
1269
1270 // Skip any newlines after '='
1271 while (check(TokenType::newline)) {
1272 advance();
1273 }
1274
1275 if (is_multi_dimensional) {
1276 // Multi-path: expect { INCBIN_MACRO("p1"), INCBIN_MACRO("p2"), ... }
1277 if (!check(TokenType::left_brace)) {
1278 continue;
1279 }
1280
1281 std::vector<Token> brace_contents = collect_brace_contents(tokens_, current_);
1282 current_ = skip_balanced_braces(tokens_, current_);
1283
1284 std::vector<std::string> paths;
1285 std::string macro_name;
1286 std::size_t brace_pos = 0;
1287
1288 while (brace_pos < brace_contents.size()) {
1289 // Skip newlines and commas
1290 while (brace_pos < brace_contents.size() && (brace_contents[brace_pos].is(TokenType::newline) ||
1291 brace_contents[brace_pos].is(TokenType::comma))) {
1292 ++brace_pos;
1293 }
1294
1295 if (brace_pos >= brace_contents.size()) {
1296 break;
1297 }
1298
1299 // Look for INCBIN_* identifier
1300 if (!brace_contents[brace_pos].is(TokenType::identifier)) {
1301 ++brace_pos;
1302 continue;
1303 }
1304
1305 std::string token_text = brace_contents[brace_pos].text();
1306 if (token_text.find("INCBIN_") != 0) {
1307 ++brace_pos;
1308 continue;
1309 }
1310
1311 if (macro_name.empty()) {
1312 macro_name = token_text;
1313 }
1314 ++brace_pos; // consume INCBIN_*
1315
1316 // Look for '('
1317 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::left_paren)) {
1318 continue;
1319 }
1320 ++brace_pos; // consume '('
1321
1322 // Look for string literal
1323 if (brace_pos >= brace_contents.size() || !brace_contents[brace_pos].is(TokenType::string_literal)) {
1324 continue;
1325 }
1326 paths.push_back(brace_contents[brace_pos].text());
1327 ++brace_pos; // consume string literal
1328
1329 // Skip until ')' (may have other stuff)
1330 while (brace_pos < brace_contents.size() && !brace_contents[brace_pos].is(TokenType::right_paren)) {
1331 ++brace_pos;
1332 }
1333 if (brace_pos < brace_contents.size()) {
1334 ++brace_pos; // consume ')'
1335 }
1336 }
1337
1338 if (!paths.empty()) {
1339 incbins.emplace_back(std::move(variable_name), std::move(macro_name), std::move(paths), name_pos);
1340 }
1341 }
1342 else {
1343 // Single path: expect INCBIN_MACRO("path")
1344 if (!check(TokenType::identifier)) {
1345 continue;
1346 }
1347
1348 std::string token_text = peek().text();
1349 if (token_text.find("INCBIN_") != 0) {
1350 continue;
1351 }
1352 std::string macro_name = token_text;
1353 advance(); // consume INCBIN_*
1354
1355 // Look for '('
1356 if (!check(TokenType::left_paren)) {
1357 continue;
1358 }
1359 advance(); // consume '('
1360
1361 // Look for string literal
1362 if (!check(TokenType::string_literal)) {
1363 continue;
1364 }
1365 std::string path = peek().text();
1366 advance(); // consume string literal
1367
1368 // Skip until ')' and ';'
1369 while (!is_at_end() && !check(TokenType::semicolon) && !check(TokenType::newline)) {
1370 advance();
1371 }
1372 if (check(TokenType::semicolon)) {
1373 advance();
1374 }
1375
1376 incbins.emplace_back(std::move(variable_name), std::move(macro_name), std::move(path), name_pos);
1377 }
1378 }
1379
1380 return incbins;
1381}
1382
1383} // 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.
ChainableResult< std::vector< ArrayDeclaration > > parse_pointer_arrays()
Parses all pointer array declarations from the token stream.
Definition parser.cpp:753
ChainableResult< std::vector< EnumDeclaration > > parse_enums()
Parses all enum declarations from the token stream.
Definition parser.cpp:166
ChainableResult< std::vector< DefineStatement > > parse_defines()
Parses all #define statements from the token stream.
Definition parser.cpp:135
ChainableResult< std::vector< IncbinDeclaration > > parse_incbin_arrays()
Parses INCBIN array declarations from the token stream.
Definition parser.cpp:1171
ChainableResult< std::vector< StructVariableDeclaration > > parse_struct_variables()
Parses struct variable declarations from the token stream.
Definition parser.cpp:939
ChainableResult< std::vector< StructInitializerDeclaration > > parse_struct_initializers()
Parses struct variable declarations with their designated initializer fields.
Definition parser.cpp:1019
ChainableResult< std::vector< FunctionDefinition > > parse_functions()
Parses function definitions from the token stream.
Definition parser.cpp:863
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
bool is(TokenType type) const
Checks if this token is of the specified type.
Definition token.hpp:180
const SourcePosition & position() const
Returns the source position where the token starts.
Definition token.hpp:157
const std::string & text() const
Returns the raw text of the token.
Definition token.hpp:147
std::size_t start
TokenType
Enumeration of token types recognized by the C parser lexer.
Definition token.hpp:19
Represents a position within source content.
std::size_t column
1-based column number