Porytiles
Loading...
Searching...
No Matches
anim_code_parser.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <format>
5#include <map>
6#include <optional>
7#include <set>
8#include <string>
9
17
18namespace {
19
20using namespace porytiles;
21
22[[nodiscard]] std::vector<std::string> make_highlighted_details(
23 const SourcePosition &position,
24 const TextFormatter &format,
25 const std::filesystem::path &file_path,
26 const std::string &message)
27{
28 const FileHighlightPrinter printer{&format};
29
30 // Build error details
31 std::vector<std::string> details;
32 details.push_back(std::format("{}:{}:{}: {}", file_path.string(), position.line, position.column, message));
33 details.emplace_back();
34
35 // Add source context if position is valid
36 if (position.line > 0) {
37 auto context = printer.print(file_path, position.line - 1, position.column - 1);
38 details.insert(details.end(), context.begin(), context.end());
39 }
40
41 return details;
42}
43
45constexpr const char *anim_code_parse_tag = "anim-code-parse";
46
54struct AnimArrayPrefixCandidates {
55 std::vector<std::string> strict;
56 std::vector<std::string> fallback;
57};
58
72[[nodiscard]] AnimArrayPrefixCandidates
73build_anim_array_prefix_candidates(const DynamicCasedName &tileset_cased_name, bool porytiles_managed)
74{
75 AnimArrayPrefixCandidates candidates;
76
77 if (porytiles_managed) {
78 candidates.strict.push_back(
80 return candidates;
81 }
82
83 const std::string c_identifier = tileset_cased_name.to_c_identifier();
84 candidates.strict.push_back(anim::g_tileset_anims_prefix + c_identifier + "_");
85 candidates.strict.push_back(anim::s_tileset_anims_prefix + c_identifier + "_");
86
87 const std::string pascal = tileset_cased_name.to_pascal_case();
88 if (pascal != c_identifier) {
89 candidates.strict.push_back(anim::g_tileset_anims_prefix + pascal + "_");
90 candidates.strict.push_back(anim::s_tileset_anims_prefix + pascal + "_");
91 }
92
93 std::string shortened = c_identifier;
94 for (auto underscore_pos = shortened.rfind('_'); underscore_pos != std::string::npos;
95 underscore_pos = shortened.rfind('_')) {
96 shortened = shortened.substr(0, underscore_pos);
97 candidates.fallback.push_back(anim::g_tileset_anims_prefix + shortened + "_");
98 candidates.fallback.push_back(anim::s_tileset_anims_prefix + shortened + "_");
99 }
100
101 return candidates;
102}
103
105struct ResolvedAnimArrayRef {
106 DynamicCasedName anim_name;
107 std::string matched_prefix;
108 bool used_shorthand_fallback{};
109};
110
124[[nodiscard]] ChainableResult<ResolvedAnimArrayRef> resolve_anim_name_from_array_ref(
125 const std::string &identifier,
126 const DynamicCasedName &tileset_cased_name,
127 bool porytiles_managed,
128 const TextFormatter &format)
129{
130 const auto candidates = build_anim_array_prefix_candidates(tileset_cased_name, porytiles_managed);
131
132 auto try_prefixes = [&](const std::vector<std::string> &prefixes,
133 bool is_fallback) -> std::optional<ResolvedAnimArrayRef> {
134 for (const auto &prefix : prefixes) {
135 if (!identifier.starts_with(prefix)) {
136 continue;
137 }
138
139 std::string remainder = identifier.substr(prefix.size());
140
141 // Trim the _Frame suffix (individual frame arrays) or the _VDests suffix
142 if (auto frame_pos = remainder.find("_Frame"); frame_pos != std::string::npos) {
143 remainder = remainder.substr(0, frame_pos);
144 }
145 else if (auto vdests_pos = remainder.find("_VDests"); vdests_pos != std::string::npos) {
146 remainder = remainder.substr(0, vdests_pos);
147 }
148
149 if (remainder.empty()) {
150 continue;
151 }
152
153 return ResolvedAnimArrayRef{DynamicCasedName::from_c_identifier(remainder), prefix, is_fallback};
154 }
155 return std::nullopt;
156 };
157
158 if (auto resolved = try_prefixes(candidates.strict, false); resolved.has_value()) {
159 return std::move(resolved).value();
160 }
161 if (auto resolved = try_prefixes(candidates.fallback, true); resolved.has_value()) {
162 return std::move(resolved).value();
163 }
164
165 auto quote_join = [&format](const std::vector<std::string> &prefixes) {
166 std::string joined;
167 for (const auto &prefix : prefixes) {
168 if (!joined.empty()) {
169 joined += ", ";
170 }
171 joined += format.format("'{}'", FormatParam{prefix, Style::bold});
172 }
173 return joined;
174 };
175
176 std::vector<std::string> lines;
177 lines.push_back(format.format("Could not extract animation name from '{}'.", FormatParam{identifier, Style::bold}));
178 lines.push_back(
179 format.format("Expected the array name to start with one of: {}.", FormatParam{quote_join(candidates.strict)}));
180 if (!candidates.fallback.empty()) {
181 lines.push_back(format.format(
182 "Also tried shortened tileset shorthand prefixes: {}.", FormatParam{quote_join(candidates.fallback)}));
183 }
184 lines.push_back(format.format(
185 "This usually means the animation arrays are named with a tileset shorthand that does not match '{}'.",
186 FormatParam{tileset_cased_name.to_c_identifier(), Style::bold}));
187 return FormattableError{lines};
188}
189
199[[nodiscard]] std::vector<DynamicCasedName> extract_frame_names(const std::vector<std::string> &elements)
200{
201 std::vector<DynamicCasedName> frames;
202 frames.reserve(elements.size());
203
204 for (const auto &elem : elements) {
205 // Find "_Frame" suffix and extract the frame name
206 auto frame_pos = elem.find("_Frame");
207 if (frame_pos != std::string::npos) {
208 std::string frame_str = elem.substr(frame_pos + 6); // Skip "_Frame"
209 frames.push_back(DynamicCasedName::from_pascal_case(frame_str));
210 }
211 }
212
213 return frames;
214}
215
226extract_tile_offset(const std::vector<Token> &tokens, const TextFormatter &format)
227{
228 for (std::size_t i = 0; i + 3 < tokens.size(); ++i) {
229 // Primary pattern: TILE_OFFSET_4BPP(<integer>)
230 if (tokens[i].is(TokenType::identifier) && tokens[i].text() == "TILE_OFFSET_4BPP" &&
231 tokens[i + 1].is(TokenType::left_paren) && tokens[i + 2].is(TokenType::integer_literal) &&
232 tokens[i + 3].is(TokenType::right_paren)) {
233 return tokens[i + 2].int_value();
234 }
235
236 // Secondary pattern: TILE_OFFSET_4BPP(NUM_TILES_IN_PRIMARY + <integer>)
237 if (i + 5 < tokens.size() && tokens[i].is(TokenType::identifier) && tokens[i].text() == "TILE_OFFSET_4BPP" &&
238 tokens[i + 1].is(TokenType::left_paren) && tokens[i + 2].is(TokenType::identifier) &&
239 tokens[i + 2].text() == "NUM_TILES_IN_PRIMARY" && tokens[i + 3].is(TokenType::plus) &&
240 tokens[i + 4].is(TokenType::integer_literal) && tokens[i + 5].is(TokenType::right_paren)) {
241 return tokens[i + 4].int_value();
242 }
243 }
244
245 std::string actual;
246 for (std::size_t i = 0; i < tokens.size(); ++i) {
247 if (i > 0) {
248 actual += " ";
249 }
250 actual += tokens[i].text();
251 }
252
253 return FormattableError{std::vector<std::string>{
254 format.format(
255 "Expected token pattern containing '{}' or '{}'.",
256 FormatParam{"TILE_OFFSET_4BPP(<integer>)", Style::bold},
257 FormatParam{"TILE_OFFSET_4BPP(NUM_TILES_IN_PRIMARY + <integer>)", Style::bold}),
258 format.format("Actual tokens: '{}'.", FormatParam{actual, Style::bold}),
259 }};
260}
261
272extract_tile_count(const std::vector<Token> &tokens, const TextFormatter &format)
273{
274 for (std::size_t i = 0; i + 2 < tokens.size(); ++i) {
275 if (tokens[i].is(TokenType::integer_literal) && tokens[i + 1].is(TokenType::star) &&
276 tokens[i + 2].is(TokenType::identifier) && tokens[i + 2].text() == "TILE_SIZE_4BPP") {
277 return tokens[i].int_value();
278 }
279 }
280
281 std::string actual;
282 for (std::size_t i = 0; i < tokens.size(); ++i) {
283 if (i > 0) {
284 actual += " ";
285 }
286 actual += tokens[i].text();
287 }
288
289 return FormattableError{std::vector<std::string>{
290 format.format(
291 "Expected token pattern containing '{}'.",
292 FormatParam{"<tile_count_integer> * TILE_SIZE_4BPP", Style::bold}),
293 format.format("Actual tokens: '{}'.", FormatParam{actual, Style::bold}),
294 }};
295}
296
305[[nodiscard]] ChainableResult<std::string> find_driver_function_from_callback(const std::vector<Token> &body_tokens)
306{
307 for (std::size_t i = 0; i + 2 < body_tokens.size(); ++i) {
308 if (body_tokens[i].is(TokenType::identifier) &&
309 (body_tokens[i].text() == "sPrimaryTilesetAnimCallback" ||
310 body_tokens[i].text() == "sSecondaryTilesetAnimCallback") &&
311 body_tokens[i + 1].is(TokenType::equal)) {
312 // Find the identifier after the equals sign
313 for (std::size_t j = i + 2; j < body_tokens.size(); ++j) {
314 if (body_tokens[j].is(TokenType::identifier)) {
315 return body_tokens[j].text();
316 }
317 // Stop if we hit a semicolon
318 if (body_tokens[j].is(TokenType::semicolon)) {
319 break;
320 }
321 }
322 }
323 }
324 return FormattableError{"Could not find tileset anim callback assignment in function body."};
325}
326
328struct TimerCondition {
329 std::size_t frame_factor; // The X in timer % X
330 std::size_t frame_offset; // The Y in timer % X == Y
331 std::string called_func; // The function called inside the condition block
332};
333
342[[nodiscard]] std::vector<TimerCondition> extract_timer_conditions(const std::vector<Token> &body_tokens)
343{
344 std::vector<TimerCondition> result;
345
346 // Look for: timer % X == Y patterns followed by function calls
347 for (std::size_t i = 0; i + 6 < body_tokens.size(); ++i) {
348 // Check for: timer % X == Y
349 if (body_tokens[i].is(TokenType::identifier) && body_tokens[i].text() == "timer" &&
350 body_tokens[i + 1].is(TokenType::percent) && body_tokens[i + 2].is(TokenType::integer_literal) &&
351 body_tokens[i + 3].is(TokenType::equal_equal) && body_tokens[i + 4].is(TokenType::integer_literal)) {
352
353 std::size_t frame_factor = body_tokens[i + 2].int_value();
354 std::size_t frame_offset = body_tokens[i + 4].int_value();
355
356 // Search ahead for function call (identifier followed by lparen)
357 for (std::size_t j = i + 5; j < body_tokens.size() && j < i + 50; ++j) {
358 if (body_tokens[j].is(TokenType::identifier) && j + 1 < body_tokens.size() &&
359 body_tokens[j + 1].is(TokenType::left_paren)) {
360 // Found a function call
361 result.push_back({frame_factor, frame_offset, body_tokens[j].text()});
362 break;
363 }
364
365 // Stop if we hit another 'if' - we've gone past the relevant block
366 if (body_tokens[j].is(TokenType::kw_if)) {
367 break;
368 }
369 }
370 }
371 }
372
373 return result;
374}
375
377struct DiscoveredAnimData {
378 std::string array_identifier; // Frame pointer array identifier referenced by the queue function
379 std::size_t tile_offset{};
380 std::size_t tile_count{};
381 std::size_t frame_factor{};
382 std::size_t frame_offset{};
383};
384
393[[nodiscard]] ChainableResult<std::string> extract_array_name_from_first_arg(const std::vector<Token> &arg_tokens)
394{
395 // Look for an identifier followed by left_bracket
396 for (std::size_t i = 0; i + 1 < arg_tokens.size(); ++i) {
397 if (arg_tokens[i].is(TokenType::identifier) && arg_tokens[i + 1].is(TokenType::left_bracket)) {
398 return arg_tokens[i].text();
399 }
400 }
401
402 // If no bracket found, just return the first identifier
403 for (const auto &tok : arg_tokens) {
404 if (tok.is(TokenType::identifier)) {
405 return tok.text();
406 }
407 }
408
409 return FormattableError{"No identifier found in first argument of AppendTilesetAnimToBuffer call."};
410}
411
421struct ParsedFunctions {
422 std::vector<FunctionDefinition> definitions;
423 std::map<std::string, const FunctionDefinition *> by_name;
424};
425
438[[nodiscard]] ChainableResult<std::string> step_1_find_driver_function(
439 CParserFacade &c_parser,
440 const std::string &callback_func_name,
441 const std::filesystem::path &c_file_path,
442 const TextFormatter *format)
443{
444 auto callback_funcs_result = c_parser.parse_functions(callback_func_name);
445 if (!callback_funcs_result.has_value()) {
447 FormattableError{format->format(
448 "'{}': Failed to parse callback function.", FormatParam{c_file_path.string(), Style::bold})},
449 callback_funcs_result};
450 }
451
452 auto &callback_funcs = callback_funcs_result.value();
453 // Narrow from prefix match to exact name match (parse_functions uses starts_with)
454 std::erase_if(callback_funcs, [&](const FunctionDefinition &func) { return func.name() != callback_func_name; });
455 if (callback_funcs.empty()) {
456 return std::string{};
457 }
458
459 if (callback_funcs.size() > 1) {
460 return FormattableError{
461 "Found multiple callback functions matching '{}'.", FormatParam{callback_func_name, Style::bold}};
462 }
463
464 const auto &callback_func = callback_funcs.front();
465 auto driver_func_name_result = find_driver_function_from_callback(callback_func.body_tokens());
466 if (!driver_func_name_result.has_value()) {
469 "Could not find driver function assignment in '{}'.", FormatParam{callback_func_name, Style::bold}},
470 driver_func_name_result};
471 }
472
473 return std::move(driver_func_name_result).value();
474}
475
487[[nodiscard]] ChainableResult<std::vector<TimerCondition>> step_2_extract_timer_conditions(
488 CParserFacade &c_parser,
489 const std::string &driver_func_name,
490 const std::filesystem::path &c_file_path,
491 const TextFormatter *format)
492{
493 auto driver_funcs_result = c_parser.parse_functions(driver_func_name);
494 if (!driver_funcs_result.has_value()) {
496 FormattableError{format->format(
497 "'{}': Failed to parse driver function '{}'.",
498 FormatParam{c_file_path.string(), Style::bold},
499 FormatParam{driver_func_name, Style::bold})},
500 driver_funcs_result};
501 }
502
503 auto &driver_funcs = driver_funcs_result.value();
504 // Narrow from prefix match to exact name match (parse_functions uses starts_with)
505 std::erase_if(driver_funcs, [&](const FunctionDefinition &func) { return func.name() != driver_func_name; });
506 if (driver_funcs.empty()) {
507 return FormattableError{"Driver function '{}' not found in file.", FormatParam{driver_func_name, Style::bold}};
508 }
509
510 const auto &driver_func = driver_funcs.front();
511 std::vector<TimerCondition> timer_conditions = extract_timer_conditions(driver_func.body_tokens());
512
513 if (timer_conditions.empty()) {
514 return FormattableError{
515 "No timer conditions found in driver function '{}'.", FormatParam{driver_func_name, Style::bold}};
516 }
517
518 return timer_conditions;
519}
520
531[[nodiscard]] ChainableResult<ParsedFunctions> step_3_build_function_map(
532 CParserFacade &c_parser, const std::filesystem::path &c_file_path, const TextFormatter *format)
533{
534 auto all_funcs_result = c_parser.parse_functions();
535 if (!all_funcs_result.has_value()) {
538 format->format("'{}': Failed to parse functions.", FormatParam{c_file_path.string(), Style::bold})},
539 all_funcs_result};
540 }
541
542 ParsedFunctions parsed;
543 parsed.definitions = std::move(all_funcs_result).value();
544 for (const auto &func : parsed.definitions) {
545 parsed.by_name[func.name()] = &func;
546 }
547
548 return parsed;
549}
550
564[[nodiscard]] ChainableResult<std::map<DynamicCasedName, DiscoveredAnimData>> step_4_extract_animation_data(
565 const std::vector<TimerCondition> &timer_conditions,
566 const std::map<std::string, const FunctionDefinition *> &func_map,
567 const DynamicCasedName &tileset_cased_name,
568 bool porytiles_managed,
569 const TextFormatter *format,
570 const UserDiagnostics *diag)
571{
572 std::map<DynamicCasedName, DiscoveredAnimData> discovered_anims;
573
574 for (const auto &condition : timer_conditions) {
575 auto it = func_map.find(condition.called_func);
576 if (it == func_map.end()) {
577 return FormattableError{
578 "Queue function '{}' not found in file.", FormatParam{condition.called_func, Style::bold}};
579 }
580
581 const FunctionDefinition *queue_func = it->second;
582
583 // Find AppendTilesetAnimToBuffer calls in the queue function
584 auto append_calls = find_function_calls(queue_func->body_tokens(), "AppendTilesetAnimToBuffer");
585
586 if (append_calls.empty()) {
587 return FormattableError{
588 "No AppendTilesetAnimToBuffer calls in queue function '{}'.",
589 FormatParam{condition.called_func, Style::bold}};
590 }
591
592 // Process the first AppendTilesetAnimToBuffer call
593 // Note: For VDests patterns, there may be multiple calls - we defer full handling per design decision
594 const auto &call = append_calls.front();
595
596 if (call.argument_count() < 3) {
597 return FormattableError{
598 "AppendTilesetAnimToBuffer call in '{}' has fewer than 3 arguments.",
599 FormatParam{condition.called_func, Style::bold}};
600 }
601
602 // Extract animation name from first argument (e.g., gTilesetAnims_General_Flower[i])
603 auto array_name_result = extract_array_name_from_first_arg(call.argument_at(0));
604 if (!array_name_result.has_value()) {
607 "Failed to parse animation data from queue function '{}'.",
608 FormatParam{condition.called_func, Style::bold}},
609 array_name_result};
610 }
611
612 auto resolved_result =
613 resolve_anim_name_from_array_ref(array_name_result.value(), tileset_cased_name, porytiles_managed, *format);
614 if (!resolved_result.has_value()) {
617 "Failed to parse animation data from queue function '{}'.",
618 FormatParam{condition.called_func, Style::bold}},
619 resolved_result};
620 }
621 auto resolved = std::move(resolved_result).value();
622
623 if (resolved.used_shorthand_fallback) {
624 diag->remark(
625 anim_code_parse_tag,
626 std::vector<std::string>{
627 format->format(
628 "Animation array '{}' is not named with the tileset shorthand '{}'.",
629 FormatParam{array_name_result.value(), Style::bold},
630 FormatParam{tileset_cased_name.to_c_identifier(), Style::bold}),
631 format->format(
632 "Resolved animation '{}' using the shortened shorthand prefix '{}'.",
633 FormatParam{resolved.anim_name.to_snake_case(), Style::bold},
634 FormatParam{resolved.matched_prefix, Style::bold}),
635 });
636 }
637
638 // Extract tile_offset from second argument
639 auto tile_offset = extract_tile_offset(call.argument_at(1), *format);
640 if (!tile_offset.has_value()) {
642 FormattableError{std::vector{
643 format->format(
644 "Failed to extract '{}' from second argument of '{}' call in '{}'.",
645 FormatParam{"TILE_OFFSET_4BPP", Style::bold},
646 FormatParam{"AppendTilesetAnimToBuffer", Style::bold},
647 FormatParam{condition.called_func, Style::bold}),
648 format->format("Full call: '{}'.", FormatParam{call.reconstruct_call_text(), Style::bold}),
649 }},
650 tile_offset};
651 }
652
653 // Extract tile_count from third argument
654 auto tile_count = extract_tile_count(call.argument_at(2), *format);
655 if (!tile_count.has_value()) {
657 FormattableError{std::vector{
658 format->format(
659 "Failed to extract '{}' from third argument of '{}' call in '{}'.",
660 FormatParam{"TILE_SIZE_4BPP", Style::bold},
661 FormatParam{"AppendTilesetAnimToBuffer", Style::bold},
662 FormatParam{condition.called_func, Style::bold}),
663 format->format("Full call: '{}'.", FormatParam{call.reconstruct_call_text(), Style::bold}),
664 }},
665 tile_count};
666 }
667
668 if (append_calls.size() > 1) {
669 return FormattableError{
670 "Queue function '{}' has multiple AppendTilesetAnimToBuffer calls (VDests pattern not yet supported).",
671 FormatParam{condition.called_func, Style::bold}};
672 }
673
674 // Store discovered animation data
675 discovered_anims[resolved.anim_name] = {
676 array_name_result.value(),
677 tile_offset.value(),
678 tile_count.value(),
679 condition.frame_factor,
680 condition.frame_offset};
681 }
682
683 return discovered_anims;
684}
685
696[[nodiscard]] ChainableResult<std::vector<ArrayDeclaration>> step_5_parse_frame_arrays(
697 CParserFacade &c_parser, const std::filesystem::path &c_file_path, const TextFormatter *format)
698{
699 auto anim_frame_arrays_result = c_parser.parse_pointer_arrays();
700 if (!anim_frame_arrays_result.has_value()) {
702 FormattableError{format->format(
703 "{}: Failed to parse animation frame arrays.", FormatParam{c_file_path.string(), Style::bold})},
704 anim_frame_arrays_result};
705 }
706
707 return anim_frame_arrays_result;
708}
709
721[[nodiscard]] ChainableResult<std::map<DynamicCasedName, AnimParams>> step_6_build_animation_params(
722 const std::map<DynamicCasedName, DiscoveredAnimData> &discovered_anims,
723 const std::vector<ArrayDeclaration> &frame_arrays,
724 const TextFormatter *format)
725{
726 std::map<DynamicCasedName, AnimParams> result;
727
728 for (const auto &[cased_name, anim_data] : discovered_anims) {
729 AnimParams params;
730 params.tile_offset(anim_data.tile_offset);
731 params.tile_count(anim_data.tile_count);
732 params.frame_factor(anim_data.frame_factor);
733 params.frame_offset(anim_data.frame_offset);
734
735 // The queue function referenced this array by name, so an exact identifier match is the correct lookup
736 const auto array_it = std::ranges::find_if(
737 frame_arrays, [&](const ArrayDeclaration &arr) { return arr.name() == anim_data.array_identifier; });
738 if (array_it == frame_arrays.end()) {
739 return FormattableError{std::vector<std::string>{
740 format->format(
741 "Could not find frame array '{}' for animation '{}'.",
742 FormatParam{anim_data.array_identifier, Style::bold},
743 FormatParam{cased_name.to_snake_case(), Style::bold}),
744 "A queue function references this array, but the file contains no pointer array declaration with "
745 "that name.",
746 }};
747 }
748
749 auto frame_order = extract_frame_names(array_it->elements());
750 if (frame_order.empty()) {
751 return FormattableError{
752 "Frame array '{}' for animation '{}' has no elements with a '{}' suffix.",
753 FormatParam{anim_data.array_identifier, Style::bold},
754 FormatParam{cased_name.to_snake_case(), Style::bold},
755 FormatParam{"_Frame", Style::bold}};
756 }
757
758 // Derive unique frame_names from frame_order (preserving first occurrence order)
759 std::vector<DynamicCasedName> frame_names;
760 std::set<DynamicCasedName> seen;
761 for (const auto &frame : frame_order) {
762 if (!seen.contains(frame)) {
763 seen.insert(frame);
764 frame_names.push_back(frame);
765 }
766 }
767 params.frame_names(std::move(frame_names));
768 params.frame_order(std::move(frame_order));
769 params.cased_name(cased_name);
770 params.frame_array_identifier(anim_data.array_identifier);
771
772 result[cased_name] = std::move(params);
773 }
774
775 return result;
776}
777
778} // namespace
779
780namespace porytiles {
781
783 const std::filesystem::path &c_file_path,
784 const std::string &callback_func_name,
785 const DynamicCasedName &tileset_cased_name,
786 bool porytiles_managed) const
787{
788 CParserFacade c_parser{c_file_path, format_};
789
790 using ResultType = std::map<DynamicCasedName, AnimParams>;
791
792 // Step 1: Parse callback function -> find driver function name
794 driver_func_name, step_1_find_driver_function(c_parser, callback_func_name, c_file_path, format_), ResultType);
795 if (driver_func_name.empty()) {
796 return ResultType{};
797 }
798
799 // Step 2: Parse driver function -> extract timer conditions
801 timer_conditions,
802 step_2_extract_timer_conditions(c_parser, driver_func_name, c_file_path, format_),
803 ResultType);
804
805 // Step 3: Parse all functions -> build lookup map
806 PT_TRY_ASSIGN_PASS_ERR(parsed_funcs, step_3_build_function_map(c_parser, c_file_path, format_), ResultType);
807
808 // Step 4: Extract animation data from queue functions
810 discovered_anims,
811 step_4_extract_animation_data(
812 timer_conditions, parsed_funcs.by_name, tileset_cased_name, porytiles_managed, format_, diag_),
813 ResultType);
814
815 // Step 5: Parse frame pointer arrays
816 PT_TRY_ASSIGN_PASS_ERR(frame_arrays, step_5_parse_frame_arrays(c_parser, c_file_path, format_), ResultType);
817
818 // Step 6: Build final AnimParams map
819 return step_6_build_animation_params(discovered_anims, frame_arrays, format_);
820}
821
822} // namespace porytiles
#define PT_TRY_ASSIGN_PASS_ERR(var, expr, return_type)
Unwraps a ChainableResult, passing through the error chain with an empty FormattableError when types ...
ChainableResult< std::map< DynamicCasedName, AnimParams > > parse_from_callback(const std::filesystem::path &c_file_path, const std::string &callback_func_name, const DynamicCasedName &tileset_cased_name, bool porytiles_managed) const
Parses animation parameters by following the callback chain.
Configuration parameters for a single tileset animation.
const std::string & frame_array_identifier() const
Returns the C identifier of the frame pointer array backing this animation.
const DynamicCasedName & cased_name() const
Returns the structured name for this animation, preserving case format information.
const std::vector< DynamicCasedName > & frame_order() const
Returns the playback sequence.
std::size_t frame_offset() const
Returns the frame offset (remainder value for timer modulo check).
std::size_t tile_offset() const
Returns the VRAM tile offset for this animation.
const std::vector< DynamicCasedName > & frame_names() const
Returns the unique frame definitions.
std::size_t frame_factor() const
Returns the frame factor (modulus divisor for timer).
std::size_t tile_count() const
Returns the number of tiles per animation frame.
Represents a parsed C pointer array declaration.
const std::string & name() const
Returns the array variable name.
High-level facade for parsing C/C++ source files.
ChainableResult< std::vector< FunctionDefinition > > parse_functions(const std::optional< std::string > &name_prefix=std::nullopt)
Parses function definitions from the file.
ChainableResult< std::vector< ArrayDeclaration > > parse_pointer_arrays(const std::optional< std::string > &name_prefix=std::nullopt)
Parses all pointer array declarations from the file.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A smart string wrapper that preserves word structure for lossless case format conversion.
static DynamicCasedName from_c_identifier(const std::string &input)
Constructs from a C identifier format string (PascalCase segments joined by underscores).
std::string to_pascal_case() const
Outputs all words flattened and joined in PascalCase (each word capitalized, no separators).
std::string to_c_identifier() const
Outputs PascalCase within each segment, with segments joined by underscores.
static DynamicCasedName from_pascal_case(const std::string &input)
Constructs from a PascalCase input string.
A service for printing file lines with highlighted lines and line numbers.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:57
Represents a parsed C function definition.
const std::string & name() const
Returns the function name.
const std::vector< Token > & body_tokens() const
Returns the function body tokens.
static const Style bold
Bold text formatting.
Abstract base class for applying text styling with context-aware formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
Abstract class for structured error reporting and diagnostic output.
virtual void remark(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged remark message.
constexpr std::string porytiles_managed_prefix
Definition animation.hpp:24
constexpr std::string g_tileset_anims_prefix
Definition animation.hpp:22
constexpr std::string s_tileset_anims_prefix
Definition animation.hpp:23
std::vector< FunctionCallInfo > find_function_calls(const std::vector< Token > &tokens, const std::string &target_function_name)
Finds all calls to a specific function within a token stream.
Represents a position within source content.
std::size_t line
1-based line number
std::size_t column
1-based column number