Porytiles
Loading...
Searching...
No Matches
header_behavior_map_provider.cpp
Go to the documentation of this file.
2
3#include <limits>
4#include <utility>
5
8
9namespace porytiles {
10
11namespace {
12
16[[nodiscard]] FormattableError make_duplicate_error(
17 const std::string &header_message,
18 SourcePosition duplicate_pos,
19 const std::string &note_message,
20 SourcePosition original_pos,
21 const std::vector<std::string> &file_lines,
22 const TextFormatter *format)
23{
24 std::vector<std::string> lines;
25 FileHighlightPrinter printer{format};
26
27 // Add header for duplicate location
28 lines.push_back(header_message);
29
30 // Add source context for duplicate location (convert 1-based to 0-based)
31 assert_or_panic(duplicate_pos.line > 0, "duplicate_pos.line must be positive (1-based)");
32 assert_or_panic(duplicate_pos.line <= file_lines.size(), "duplicate_pos.line exceeds file bounds");
33 assert_or_panic(duplicate_pos.column > 0, "duplicate_pos.column must be positive (1-based)");
34 auto dup_context = printer.print(file_lines, duplicate_pos.line - 1, duplicate_pos.column - 1);
35 for (auto &line : dup_context) {
36 lines.push_back(std::move(line));
37 }
38
39 // Add blank line separator
40 lines.emplace_back("");
41
42 // Add note about original location
43 lines.push_back(note_message);
44
45 // Add source context for original location (convert 1-based to 0-based)
46 assert_or_panic(original_pos.line > 0, "original_pos.line must be positive (1-based)");
47 assert_or_panic(original_pos.line <= file_lines.size(), "original_pos.line exceeds file bounds");
48 assert_or_panic(original_pos.column > 0, "original_pos.column must be positive (1-based)");
49 auto orig_context = printer.print(file_lines, original_pos.line - 1, original_pos.column - 1);
50 for (auto &line : orig_context) {
51 lines.push_back(std::move(line));
52 }
53
54 return FormattableError{std::move(lines)};
55}
56
57} // namespace
58
59template <typename Entry>
60ChainableResult<void> HeaderBehaviorMapProvider::try_add_behavior_entry(const Entry &entry) const
61{
62 const auto &name = entry.name();
63
64 // Filter: must start with MB_
65 if (!name.starts_with("MB_")) {
66 return {};
67 }
68
69 auto raw_value = entry.int_value();
70
71 // Filter: value must be in valid range
72 if (raw_value < 0 || raw_value > std::numeric_limits<std::uint16_t>::max()) {
73 return {};
74 }
75
76 auto value = static_cast<std::uint16_t>(raw_value);
77 const auto &new_pos = entry.position();
78
79 // Check for duplicate name
80 if (name_to_value_.contains(name)) {
81 load_failed_ = true;
82 const auto &orig_pos = name_to_position_.at(name);
83 return make_duplicate_error(
84 format_->format(
85 "{}:{}:{}: duplicate behavior name '{}'.",
86 FormatParam{header_path_, Style::bold},
87 new_pos.line,
88 new_pos.column,
89 FormatParam{name, Style::bold}),
90 new_pos,
91 format_->format(
92 "{} originally defined at line {}:", FormatParam{"note:", Style::cyan | Style::bold}, orig_pos.line),
93 orig_pos,
94 driver_->file_lines(),
95 format_);
96 }
97
98 // Check for duplicate value
99 if (value_to_name_.contains(value)) {
100 load_failed_ = true;
101 const auto &orig_name = value_to_name_.at(value);
102 const auto &orig_pos = value_to_position_.at(value);
103 return make_duplicate_error(
104 format_->format(
105 "{}:{}:{}: duplicate behavior value '{}': both '{}' and '{}' have this value.",
106 FormatParam{header_path_, Style::bold},
107 new_pos.line,
108 new_pos.column,
109 FormatParam{value, Style::bold},
110 FormatParam{orig_name, Style::bold},
111 FormatParam{name, Style::bold}),
112 new_pos,
113 format_->format(
114 "{} '{}' originally defined at line {}:",
115 FormatParam{"note:", Style::cyan | Style::bold},
116 FormatParam{orig_name, Style::bold},
117 orig_pos.line),
118 orig_pos,
119 driver_->file_lines(),
120 format_);
121 }
122
123 // Insert into all maps
124 name_to_value_[name] = value;
125 value_to_name_[value] = name;
126 name_to_position_[name] = new_pos;
127 value_to_position_[value] = new_pos;
128
129 return {};
130}
131
133{
134 if (!behavior_name.starts_with("MB_")) {
135 return FormattableError{
136 "Invalid behavior name '{}': expected prefix '{}'.",
137 FormatParam{behavior_name, Style::bold},
138 FormatParam{"MB_", Style::bold}};
139 }
140
141 auto load_result = ensure_loaded();
142 if (!load_result.has_value()) {
144 FormattableError{"Metatile behavior provider lookup failed."}, load_result};
145 }
146
147 const auto it = name_to_value_.find(behavior_name);
148 if (it == name_to_value_.end()) {
149 return FormattableError{
150 "Behavior '{}' not found in '{}'.",
151 FormatParam{behavior_name, Style::bold},
152 FormatParam{header_path_.string(), Style::bold}};
153 }
154 return it->second;
155}
156
158{
159 auto load_result = ensure_loaded();
160 if (!load_result.has_value()) {
161 return ChainableResult<std::string>{FormattableError{"Metatile behavior provider lookup failed."}, load_result};
162 }
163
164 const auto it = value_to_name_.find(behavior_value);
165 if (it == value_to_name_.end()) {
166 return FormattableError{
167 "Unknown behavior value '{}' not found in '{}'.",
168 FormatParam{behavior_value, Style::bold},
169 FormatParam{header_path_.string(), Style::bold}};
170 }
171 return it->second;
172}
173
174ChainableResult<void> HeaderBehaviorMapProvider::ensure_loaded() const
175{
176 if (loaded_) {
177 if (load_failed_) {
178 return FormattableError{"Behavior header file previously failed to load."};
179 }
180 return {};
181 }
182
183 loaded_ = true;
184
185 // Create and store CParserFacade for rich error formatting with source context
186 driver_ = std::make_unique<CParserFacade>(header_path_, format_);
187
188 // Parse #define statements
189 auto defines_result = driver_->parse_defines();
190 if (!defines_result.has_value()) {
191 load_failed_ = true;
192 return ChainableResult<void>{defines_result};
193 }
194 for (const auto &def : defines_result.value()) {
195 if (!def.has_int_value()) {
196 continue;
197 }
198 auto insert_result = try_add_behavior_entry(def);
199 if (!insert_result.has_value()) {
200 return insert_result;
201 }
202 }
203
204 // Parse enum declarations
205 auto enums_result = driver_->parse_enums();
206 if (!enums_result.has_value()) {
207 load_failed_ = true;
208 return ChainableResult<void>{enums_result};
209 }
210 for (const auto &enum_decl : enums_result.value()) {
211 for (const auto &member : enum_decl.members()) {
212 auto insert_result = try_add_behavior_entry(member);
213 if (!insert_result.has_value()) {
214 return insert_result;
215 }
216 }
217 }
218
219 if (name_to_value_.empty()) {
220 load_failed_ = true;
221 return FormattableError{
222 "{}: no behavior definitions exist in file.", FormatParam{header_path_.string(), Style::bold}};
223 }
224
225 return {};
226}
227
228} // namespace porytiles
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:63
ChainableResult< std::uint16_t > lookup(const std::string &behavior_name) const override
Looks up the numeric value for a behavior constant name.
static const Style bold
Bold text 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.
void assert_or_panic(bool condition, const StringViewSourceLoc &s)
Conditionally panics if the given condition is false.
Definition panic.cpp:53