10#include "yaml-cpp/yaml.h"
43std::map<std::filesystem::path, YAML::Node> yaml_cache;
44std::map<std::filesystem::path, std::vector<std::string>> file_lines_cache;
55std::string get_line_content(
const std::filesystem::path &path, std::size_t line_num)
57 const auto it = file_lines_cache.find(path);
58 if (it != file_lines_cache.end() && line_num < it->second.size()) {
59 return it->second[line_num];
75std::string make_source_string(
const TextFormatter *format,
const std::string &file_path,
const YAML::Mark &mark)
102std::vector<std::string>
103make_source_details(
const TextFormatter *format,
const std::string &file_path,
const YAML::Mark &mark)
105 const std::filesystem::path path{file_path};
106 const auto it = file_lines_cache.find(path);
107 if (it == file_lines_cache.end()) {
111 const auto &lines = it->second;
112 const std::size_t line_num = mark.line;
114 if (lines.empty() || line_num >= lines.size()) {
120 return printer.print(lines, std::vector{line_num});
132void collect_yaml_paths(
133 const YAML::Node &node,
const std::string &prefix, std::vector<std::pair<std::string, YAML::Mark>> &paths)
139 for (
const auto &kv : node) {
140 const auto key = kv.first.as<std::string>();
141 const auto full_path = prefix.empty() ? key : prefix +
"." + key;
142 paths.emplace_back(full_path, kv.first.Mark());
145 if (kv.second.IsMap()) {
146 collect_yaml_paths(kv.second, full_path, paths);
163[[nodiscard]]
bool validate_yaml_paths(
166 const std::filesystem::path &file_path,
167 const YAML::Node &node)
169 if (diagnostics ==
nullptr) {
173 bool found_unknown =
false;
174 std::vector<std::pair<std::string, YAML::Mark>> paths;
175 collect_yaml_paths(node,
"", paths);
177 for (
const auto &[path, mark] : paths) {
180 bool is_map_child =
false;
182 if (path.starts_with(prefix +
".")) {
191 const auto source = make_source_string(format, file_path.string(), mark);
192 auto details = make_source_details(format, file_path.string(), mark);
194 std::vector<std::string> error_lines;
195 error_lines.push_back(format->
format(
"Unknown configuration key '{}'.",
FormatParam{path, Style::bold}));
196 error_lines.emplace_back();
197 error_lines.push_back(format->
format(
"Source: {}",
FormatParam{source, Style::italic}));
198 error_lines.emplace_back();
199 for (
auto &detail : details) {
200 error_lines.push_back(std::move(detail));
203 diagnostics->
error(
"unknown-config-key", error_lines);
204 found_unknown =
true;
208 return found_unknown;
219parse_size_t(
const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
221 if (!node.IsDefined()) {
226 const auto value = node.as<std::size_t>();
227 const auto mark = node.Mark();
228 const auto source = make_source_string(format, file_path, mark);
229 const auto details = make_source_details(format, file_path, mark);
232 catch (
const YAML::Exception &e) {
233 const auto mark = node.Mark();
236 const auto source = make_source_string(format, file_path, mark);
237 const auto details = make_source_details(format, file_path, mark);
254 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
256 if (!node.IsDefined()) {
261 const auto value = node.as<std::size_t>();
262 const auto mark = node.Mark();
263 const auto source = make_source_string(format, file_path, mark);
264 const auto details = make_source_details(format, file_path, mark);
267 catch (
const YAML::Exception &e) {
268 const auto mark = node.Mark();
271 const auto source = make_source_string(format, file_path, mark);
272 const auto details = make_source_details(format, file_path, mark);
285parse_bool(
const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
287 if (!node.IsDefined()) {
292 const auto value = node.as<
bool>();
293 const auto mark = node.Mark();
294 const auto source = make_source_string(format, file_path, mark);
295 const auto details = make_source_details(format, file_path, mark);
298 catch (
const YAML::Exception &e) {
299 const auto mark = node.Mark();
302 const auto source = make_source_string(format, file_path, mark);
303 const auto details = make_source_details(format, file_path, mark);
316parse_string(
const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
318 if (!node.IsDefined()) {
323 const auto value = node.as<std::string>();
324 const auto mark = node.Mark();
325 const auto source = make_source_string(format, file_path, mark);
326 const auto details = make_source_details(format, file_path, mark);
329 catch (
const YAML::Exception &e) {
330 const auto mark = node.Mark();
333 const auto source = make_source_string(format, file_path, mark);
334 const auto details = make_source_details(format, file_path, mark);
351parse_rgba32(
const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
353 if (!node.IsDefined()) {
358 const auto mark = node.Mark();
359 const auto source = make_source_string(format, file_path, mark);
360 const auto details = make_source_details(format, file_path, mark);
362 if (!node.IsSequence()) {
368 if (node.size() < 3 || node.size() > 4) {
370 "'{}' must have 3 or 4 elements [r, g, b] or [r, g, b, a], got {}",
376 const auto r = node[0].as<std::uint8_t>();
377 const auto g = node[1].as<std::uint8_t>();
378 const auto b = node[2].as<std::uint8_t>();
381 const Rgba32 color{r, g, b, a};
384 catch (
const YAML::Exception &e) {
385 const auto mark = node.Mark();
387 const auto source = make_source_string(format, file_path, mark);
388 const auto details = make_source_details(format, file_path, mark);
394 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
396 if (!node.IsDefined()) {
401 const auto mark = node.Mark();
402 const auto details = make_source_details(format, file_path, mark);
404 if (!node.IsSequence()) {
407 const auto source = make_source_string(format, file_path, mark);
411 std::vector<PaletteHint> hints;
412 for (std::size_t i = 0; i < node.size(); ++i) {
413 const auto &hint_node = node[i];
415 if (!hint_node.IsMap()) {
416 const auto hint_mark = hint_node.Mark();
418 "'{}[{}]' must be a map with 'name' and 'colors' keys",
421 const auto source = make_source_string(format, file_path, hint_mark);
422 const auto hint_details = make_source_details(format, file_path, hint_mark);
427 const auto name_node = hint_node[
"name"];
428 if (!name_node.IsDefined()) {
429 const auto hint_mark = hint_node.Mark();
432 const auto source = make_source_string(format, file_path, hint_mark);
433 const auto hint_details = make_source_details(format, file_path, hint_mark);
436 const auto name = name_node.as<std::string>();
439 const auto colors_node = hint_node[
"colors"];
440 if (!colors_node.IsDefined()) {
441 const auto hint_mark = hint_node.Mark();
444 const auto source = make_source_string(format, file_path, hint_mark);
445 const auto hint_details = make_source_details(format, file_path, hint_mark);
449 if (!colors_node.IsSequence()) {
450 const auto colors_mark = colors_node.Mark();
453 const auto source = make_source_string(format, file_path, colors_mark);
454 const auto colors_details = make_source_details(format, file_path, colors_mark);
459 std::vector<Rgba32> colors;
460 for (std::size_t j = 0; j < colors_node.size(); ++j) {
461 const auto &color_node = colors_node[j];
463 if (!color_node.IsSequence() || color_node.size() != 3) {
464 const auto color_mark = color_node.Mark();
466 "'{}[{}].colors[{}]' must be [r, g, b]",
470 const auto source = make_source_string(format, file_path, color_mark);
471 const auto color_details = make_source_details(format, file_path, color_mark);
475 const auto r = color_node[0].as<std::uint8_t>();
476 const auto g = color_node[1].as<std::uint8_t>();
477 const auto b = color_node[2].as<std::uint8_t>();
478 const auto a = (color_node.size() == 4) ? color_node[3].as<std::uint8_t>() :
Rgba32::alpha_opaque;
480 colors.emplace_back(r, g, b, a);
483 hints.emplace_back(
name,
Palette{std::move(colors)});
486 const auto source = make_source_string(format, file_path, mark);
489 catch (
const YAML::Exception &e) {
490 const auto mark = node.Mark();
493 const auto source = make_source_string(format, file_path, mark);
494 const auto details = make_source_details(format, file_path, mark);
511 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
513 if (!node.IsDefined()) {
518 const auto mark = node.Mark();
519 const auto source = make_source_string(format, file_path, mark);
520 const auto details = make_source_details(format, file_path, mark);
522 if (!node.IsSequence()) {
527 std::vector<std::string> result;
528 for (std::size_t i = 0; i < node.size(); ++i) {
529 result.push_back(node[i].as<std::string>());
533 catch (
const YAML::Exception &e) {
534 const auto mark = node.Mark();
537 const auto source = make_source_string(format, file_path, mark);
538 const auto details = make_source_details(format, file_path, mark);
554 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
556 if (!node.IsDefined()) {
561 const auto mark = node.Mark();
562 const auto source = make_source_string(format, file_path, mark);
563 const auto details = make_source_details(format, file_path, mark);
564 const auto node_value = node.as<std::string>();
567 if (!mode_opt.has_value()) {
575 catch (
const YAML::Exception &e) {
576 const auto mark = node.Mark();
579 const auto source = make_source_string(format, file_path, mark);
580 const auto details = make_source_details(format, file_path, mark);
586 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
588 if (!node.IsDefined()) {
593 const auto mark = node.Mark();
594 const auto source = make_source_string(format, file_path, mark);
595 const auto details = make_source_details(format, file_path, mark);
596 const auto node_value = node.as<std::string>();
599 if (!mode_opt.has_value()) {
607 catch (
const YAML::Exception &e) {
608 const auto mark = node.Mark();
611 const auto source = make_source_string(format, file_path, mark);
612 const auto details = make_source_details(format, file_path, mark);
618 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
620 if (!node.IsDefined()) {
625 const auto mark = node.Mark();
626 const auto source = make_source_string(format, file_path, mark);
627 const auto details = make_source_details(format, file_path, mark);
628 const auto node_value = node.as<std::string>();
631 if (!mode_opt.has_value()) {
639 catch (
const YAML::Exception &e) {
640 const auto mark = node.Mark();
643 const auto source = make_source_string(format, file_path, mark);
644 const auto details = make_source_details(format, file_path, mark);
650 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
652 if (!node.IsDefined()) {
657 const auto mark = node.Mark();
658 const auto source = make_source_string(format, file_path, mark);
659 const auto details = make_source_details(format, file_path, mark);
668 for (
const auto &kv : node) {
669 const auto anim_name = kv.first.as<std::string>();
670 const auto &anim_node = kv.second;
675 if (!anim_node.IsMap()) {
676 const auto anim_mark = kv.first.Mark();
677 const auto anim_source = make_source_string(format, file_path, anim_mark);
678 const auto anim_details = make_source_details(format, file_path, anim_mark);
680 "'{}' animation '{}' must be a map.",
687 if (anim_node[
"frame_linking"].IsDefined()) {
688 const auto linking_str = anim_node[
"frame_linking"].as<std::string>();
690 if (!linking_opt.has_value()) {
691 const auto linking_mark = anim_node[
"frame_linking"].Mark();
692 const auto linking_source = make_source_string(format, file_path, linking_mark);
693 const auto linking_details = make_source_details(format, file_path, linking_mark);
695 "'{}' animation '{}' has invalid frame_linking value '{}'.",
701 const auto fl_mark = anim_node[
"frame_linking"].Mark();
704 key +
"." + anim_name +
".frame_linking",
705 "Animation Config (" + anim_name +
") frame_linking",
706 make_source_string(format, file_path, fl_mark),
707 make_source_details(format, file_path, fl_mark)};
711 if (anim_node[
"palette_resolution_strategy"].IsDefined()) {
712 const auto &strategy_node = anim_node[
"palette_resolution_strategy"];
713 const auto strategy_str = strategy_node.as<std::string>();
715 if (!strategy_opt.has_value()) {
716 const auto strategy_mark = strategy_node.Mark();
717 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
718 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
720 "'{}' animation '{}' palette_resolution_strategy has invalid value '{}'.",
726 const auto palette_mark = strategy_node.Mark();
728 strategy_opt.
value(),
729 key +
"." + anim_name +
".palette_resolution_strategy",
730 "Animation Config (" + anim_name +
") per-anim strategy",
731 make_source_string(format, file_path, palette_mark),
732 make_source_details(format, file_path, palette_mark)};
736 if (anim_node[
"key_frame_resolution_strategy"].IsDefined()) {
737 const auto &strategy_node = anim_node[
"key_frame_resolution_strategy"];
738 const auto strategy_str = strategy_node.as<std::string>();
740 if (!strategy_opt.has_value()) {
741 const auto strategy_mark = strategy_node.Mark();
742 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
743 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
745 "'{}' animation '{}' key_frame_resolution_strategy has invalid value '{}'.",
751 const auto kf_mark = strategy_node.Mark();
753 strategy_opt.
value(),
754 key +
"." + anim_name +
".key_frame_resolution_strategy",
755 "Animation Config (" + anim_name +
") key_frame_resolution_strategy",
756 make_source_string(format, file_path, kf_mark),
757 make_source_details(format, file_path, kf_mark)};
761 if (anim_node[
"multi_palette_subtile_resolution_strategy"].IsDefined()) {
762 const auto &strategy_node = anim_node[
"multi_palette_subtile_resolution_strategy"];
763 const auto strategy_str = strategy_node.as<std::string>();
765 if (!strategy_opt.has_value()) {
766 const auto strategy_mark = strategy_node.Mark();
767 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
768 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
770 "'{}' animation '{}' multi_palette_subtile_resolution_strategy has invalid value '{}'.",
776 const auto mps_mark = strategy_node.Mark();
778 strategy_opt.
value(),
779 key +
"." + anim_name +
".multi_palette_subtile_resolution_strategy",
780 "Animation Config (" + anim_name +
") multi_palette_subtile_resolution_strategy",
781 make_source_string(format, file_path, mps_mark),
782 make_source_details(format, file_path, mps_mark)};
786 if (anim_node[
"per_tile_palette_resolution_strategies"].IsDefined()) {
787 const auto &strategies_node = anim_node[
"per_tile_palette_resolution_strategies"];
788 if (!strategies_node.IsSequence()) {
789 const auto strategies_mark = strategies_node.Mark();
790 const auto strategies_source = make_source_string(format, file_path, strategies_mark);
791 const auto strategies_details = make_source_details(format, file_path, strategies_mark);
793 "'{}' animation '{}' per_tile_palette_resolution_strategies must be a sequence.",
799 for (std::size_t i = 0; i < strategies_node.size(); ++i) {
800 const auto strategy_str = strategies_node[i].as<std::string>();
801 if (strategy_str ==
"_") {
806 if (!strategy_opt.has_value()) {
807 const auto strategy_mark = strategies_node[i].Mark();
808 const auto strategy_source = make_source_string(format, file_path, strategy_mark);
809 const auto strategy_details = make_source_details(format, file_path, strategy_mark);
811 "'{}' animation '{}' per_tile_palette_resolution_strategies[{}] has invalid value "
819 const auto tile_mark = strategies_node[i].Mark();
822 strategy_opt.
value(),
823 key +
"." + anim_name +
".per_tile_palette_resolution_strategies[" + std::to_string(i) +
825 "Animation Config (" + anim_name +
") subtile " + std::to_string(i),
826 make_source_string(format, file_path, tile_mark),
827 make_source_details(format, file_path, tile_mark)});
832 configs[anim_name] = std::move(anim_config);
837 catch (
const YAML::Exception &e) {
838 const auto mark = node.Mark();
841 const auto source = make_source_string(format, file_path, mark);
842 const auto details = make_source_details(format, file_path, mark);
849[[nodiscard]] std::optional<std::uint32_t> parse_mask_scalar(
const std::string &text)
852 std::size_t consumed = 0;
853 const unsigned long parsed = std::stoul(text, &consumed, 0);
854 if (consumed != text.size() || parsed > 0xFFFFFFFFUL) {
857 return static_cast<std::uint32_t
>(parsed);
859 catch (
const std::exception &) {
865[[nodiscard]] std::optional<HeaderFormat> header_format_from_config_str(
const std::string &text)
867 if (text ==
"enums_only" || text ==
"enums-only") {
868 return HeaderFormat::enums_only;
870 if (text ==
"defines_only" || text ==
"defines-only") {
871 return HeaderFormat::defines_only;
873 if (text ==
"either") {
874 return HeaderFormat::either;
881[[nodiscard]] std::optional<FieldRole> field_role_from_config_str(
const std::string &text)
888[[nodiscard]] std::optional<std::string>
889first_unknown_key(
const YAML::Node &map_node,
const std::unordered_set<std::string> &allowed)
891 for (
const auto &kv : map_node) {
892 const auto member = kv.first.as<std::string>();
893 if (!allowed.contains(member)) {
901 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
903 if (!node.IsDefined()) {
908 const auto mark = node.Mark();
909 const auto source = make_source_string(format, file_path, mark);
910 const auto details = make_source_details(format, file_path, mark);
912 if (!node.IsSequence()) {
914 format->
format(
"'{}' must be a sequence of field definitions.",
FormatParam{key, Style::bold}),
919 const std::unordered_set<std::string> field_keys{
"name",
"mask",
"default",
"provider",
"role"};
920 const std::unordered_set<std::string> provider_keys{
"header",
"prefix",
"skipped",
"format"};
923 for (std::size_t i = 0; i < node.size(); ++i) {
924 const auto &field_node = node[i];
925 const auto field_mark = field_node.Mark();
926 const auto field_source = make_source_string(format, file_path, field_mark);
927 const auto field_details = make_source_details(format, file_path, field_mark);
929 if (!field_node.IsMap()) {
935 if (
auto unknown = first_unknown_key(field_node, field_keys);
unknown.has_value()) {
938 "'{}[{}]' has unknown key '{}'.",
947 const auto name_node = field_node[
"name"];
948 if (!name_node.IsDefined()) {
955 definition.
name = name_node.as<std::string>();
957 for (
const auto &[member, target] :
958 std::initializer_list<std::pair<const char *, std::optional<std::uint32_t> *>>{
960 if (field_node[member].IsDefined()) {
961 const auto text = field_node[member].as<std::string>();
962 const auto parsed = parse_mask_scalar(text);
963 if (!parsed.has_value()) {
966 "'{}[{}].{}' is not a valid 32-bit integer: '{}'.",
978 if (field_node[
"provider"].IsDefined()) {
979 const auto &provider_node = field_node[
"provider"];
980 if (!provider_node.IsMap()) {
987 if (
auto unknown = first_unknown_key(provider_node, provider_keys);
unknown.has_value()) {
990 "'{}[{}].provider' has unknown key '{}'.",
999 if (!provider_node[
"header"].IsDefined() || !provider_node[
"prefix"].IsDefined()) {
1002 "'{}[{}].provider' requires both 'header' and 'prefix'.",
1008 provider.header = provider_node[
"header"].as<std::string>();
1009 provider.prefix = provider_node[
"prefix"].as<std::string>();
1010 if (provider_node[
"skipped"].IsDefined()) {
1011 if (!provider_node[
"skipped"].IsSequence()) {
1014 "'{}[{}].provider.skipped' must be a sequence.",
1020 for (std::size_t j = 0; j < provider_node[
"skipped"].size(); ++j) {
1021 provider.skipped.insert(provider_node[
"skipped"][j].as<std::string>());
1024 if (provider_node[
"format"].IsDefined()) {
1025 const auto fmt_str = provider_node[
"format"].as<std::string>();
1026 const auto fmt = header_format_from_config_str(fmt_str);
1027 if (!fmt.has_value()) {
1030 "'{}[{}].provider.format' has invalid value '{}'.",
1042 if (field_node[
"role"].IsDefined() && !field_node[
"role"].IsNull()) {
1045 const auto role_str = field_node[
"role"].as<std::string>();
1046 const auto role = field_role_from_config_str(role_str);
1047 if (!role.has_value()) {
1050 "'{}[{}].role' has invalid value '{}'; the only role is 'layer_type'.",
1057 definition.
role = role;
1060 definitions.push_back(std::move(definition));
1065 catch (
const YAML::Exception &e) {
1066 const auto mark = node.Mark();
1069 const auto source = make_source_string(format, file_path, mark);
1070 const auto details = make_source_details(format, file_path, mark);
1076 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1078 if (!node.IsDefined()) {
1083 const auto mark = node.Mark();
1084 const auto source = make_source_string(format, file_path, mark);
1085 const auto details = make_source_details(format, file_path, mark);
1087 if (!node.IsMap()) {
1089 format->
format(
"'{}' must be a map of field names to overrides.",
FormatParam{key, Style::bold}),
1094 const std::unordered_set<std::string> override_keys{
"mask",
"default",
"provider",
"role"};
1095 const std::unordered_set<std::string> provider_keys{
"header",
"prefix",
"skipped",
"format"};
1098 for (
const auto &kv : node) {
1099 const auto field_name = kv.first.as<std::string>();
1100 const auto &override_node = kv.second;
1101 const auto field_mark = kv.first.Mark();
1102 const auto field_source = make_source_string(format, file_path, field_mark);
1103 const auto field_details = make_source_details(format, file_path, field_mark);
1105 if (!override_node.IsMap()) {
1108 "'{}' override for '{}' must be a map.",
1114 if (
auto unknown = first_unknown_key(override_node, override_keys);
unknown.has_value()) {
1117 "'{}' override for '{}' has unknown key '{}'.",
1126 for (
const auto &[member, target] :
1127 std::initializer_list<std::pair<const char *, std::optional<std::uint32_t> *>>{
1129 if (override_node[member].IsDefined()) {
1130 const auto text = override_node[member].as<std::string>();
1131 const auto parsed = parse_mask_scalar(text);
1132 if (!parsed.has_value()) {
1135 "'{}' override for '{}' has invalid '{}': '{}'.",
1147 if (override_node[
"provider"].IsDefined()) {
1148 const auto &provider_node = override_node[
"provider"];
1150 if (provider_node.IsNull()) {
1152 provider_override.
remove =
true;
1154 else if (provider_node.IsMap()) {
1155 if (
auto unknown = first_unknown_key(provider_node, provider_keys);
unknown.has_value()) {
1158 "'{}' override for '{}' has unknown provider key '{}'.",
1165 if (provider_node[
"header"].IsDefined()) {
1166 provider_override.
header = provider_node[
"header"].as<std::string>();
1168 if (provider_node[
"prefix"].IsDefined()) {
1169 provider_override.
prefix = provider_node[
"prefix"].as<std::string>();
1171 if (provider_node[
"skipped"].IsDefined()) {
1172 if (!provider_node[
"skipped"].IsSequence()) {
1175 "'{}' override for '{}' provider.skipped must be a sequence.",
1181 std::unordered_set<std::string> skipped;
1182 for (std::size_t j = 0; j < provider_node[
"skipped"].size(); ++j) {
1183 skipped.insert(provider_node[
"skipped"][j].as<std::string>());
1185 provider_override.
skipped = std::move(skipped);
1187 if (provider_node[
"format"].IsDefined()) {
1188 const auto fmt_str = provider_node[
"format"].as<std::string>();
1189 const auto fmt = header_format_from_config_str(fmt_str);
1190 if (!fmt.has_value()) {
1193 "'{}' override for '{}' provider.format has invalid value '{}'.",
1200 provider_override.
format = fmt.value();
1206 "'{}' override for '{}' provider must be a map or null.",
1212 override_value.
provider = std::move(provider_override);
1215 if (override_node[
"role"].IsDefined()) {
1216 const auto &role_node = override_node[
"role"];
1217 if (role_node.IsNull()) {
1219 override_value.
role = std::optional<FieldRole>{std::nullopt};
1222 const auto role_str = role_node.as<std::string>();
1223 const auto role = field_role_from_config_str(role_str);
1224 if (!role.has_value()) {
1227 "'{}' override for '{}' has invalid 'role' value '{}'; the only role is "
1235 override_value.
role = role;
1239 overrides[field_name] = std::move(override_value);
1244 catch (
const YAML::Exception &e) {
1245 const auto mark = node.Mark();
1247 "Failed to parse '{}' as metatile attribute field overrides: {}.",
FormatParam{key,
Style::bold}, e.what());
1248 const auto source = make_source_string(format, file_path, mark);
1249 const auto details = make_source_details(format, file_path, mark);
1255 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1257 if (!node.IsDefined()) {
1262 const auto mark = node.Mark();
1263 const auto source = make_source_string(format, file_path, mark);
1264 const auto details = make_source_details(format, file_path, mark);
1266 if (!node.IsSequence()) {
1268 format->
format(
"'{}' must be a sequence of role pin definitions.",
FormatParam{key, Style::bold}),
1273 const std::unordered_set<std::string> pin_keys{
"role"};
1278 std::unordered_set<std::string> seen_roles;
1279 for (std::size_t i = 0; i < node.size(); ++i) {
1280 const auto &pin_node = node[i];
1281 const auto pin_mark = pin_node.Mark();
1282 const auto pin_source = make_source_string(format, file_path, pin_mark);
1283 const auto pin_details = make_source_details(format, file_path, pin_mark);
1285 if (!pin_node.IsMap()) {
1291 if (
auto unknown = first_unknown_key(pin_node, pin_keys);
unknown.has_value()) {
1294 "'{}[{}]' has unknown key '{}'.",
1302 const auto role_node = pin_node[
"role"];
1303 if (!role_node.IsDefined()) {
1310 const auto role_str = role_node.as<std::string>();
1311 const auto role = field_role_from_config_str(role_str);
1312 if (!role.has_value()) {
1315 "'{}[{}].role' has invalid value '{}'; the only role is 'layer_type'.",
1322 if (!seen_roles.insert(role_str).second) {
1325 "'{}[{}]' repeats role '{}'; each role may be pinned at most once.",
1334 definition.
role = role.value();
1335 definitions.push_back(definition);
1340 catch (
const YAML::Exception &e) {
1341 const auto mark = node.Mark();
1344 const auto source = make_source_string(format, file_path, mark);
1345 const auto details = make_source_details(format, file_path, mark);
1351 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1353 if (!node.IsDefined()) {
1358 const auto mark = node.Mark();
1359 const auto source = make_source_string(format, file_path, mark);
1360 const auto details = make_source_details(format, file_path, mark);
1361 const auto node_value = node.as<std::string>();
1364 if (!mode_opt.has_value()) {
1372 catch (
const YAML::Exception &e) {
1373 const auto mark = node.Mark();
1376 const auto source = make_source_string(format, file_path, mark);
1377 const auto details = make_source_details(format, file_path, mark);
1383 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1385 if (!node.IsDefined()) {
1390 const auto mark = node.Mark();
1391 const auto source = make_source_string(format, file_path, mark);
1392 const auto details = make_source_details(format, file_path, mark);
1393 const auto node_value = node.as<std::string>();
1396 if (!mode_opt.has_value()) {
1404 catch (
const YAML::Exception &e) {
1405 const auto mark = node.Mark();
1407 "Failed to parse '{}' as AnimMultiPaletteSubtileResolutionStrategy: {}.",
1410 const auto source = make_source_string(format, file_path, mark);
1411 const auto details = make_source_details(format, file_path, mark);
1417 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1419 if (!node.IsDefined()) {
1424 const auto mark = node.Mark();
1425 const auto source = make_source_string(format, file_path, mark);
1426 const auto details = make_source_details(format, file_path, mark);
1427 const auto node_value = node.as<std::string>();
1430 if (!mode_opt.has_value()) {
1438 catch (
const YAML::Exception &e) {
1439 const auto mark = node.Mark();
1442 const auto source = make_source_string(format, file_path, mark);
1443 const auto details = make_source_details(format, file_path, mark);
1449 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1451 if (!node.IsDefined()) {
1456 const auto mark = node.Mark();
1457 const auto source = make_source_string(format, file_path, mark);
1458 const auto details = make_source_details(format, file_path, mark);
1459 const auto node_value = node.as<std::string>();
1462 if (!mode_opt.has_value()) {
1470 catch (
const YAML::Exception &e) {
1471 const auto mark = node.Mark();
1474 const auto source = make_source_string(format, file_path, mark);
1475 const auto details = make_source_details(format, file_path, mark);
1481 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1483 if (!node.IsDefined()) {
1488 const auto mark = node.Mark();
1489 const auto source = make_source_string(format, file_path, mark);
1490 const auto details = make_source_details(format, file_path, mark);
1491 const auto node_value = node.as<std::string>();
1494 if (!mode_opt.has_value()) {
1502 catch (
const YAML::Exception &e) {
1503 const auto mark = node.Mark();
1506 const auto source = make_source_string(format, file_path, mark);
1507 const auto details = make_source_details(format, file_path, mark);
1513 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1515 if (!node.IsDefined()) {
1520 const auto mark = node.Mark();
1521 const auto source = make_source_string(format, file_path, mark);
1522 const auto details = make_source_details(format, file_path, mark);
1523 const auto node_value = node.as<std::string>();
1526 if (!mode_opt.has_value()) {
1534 catch (
const YAML::Exception &e) {
1535 const auto mark = node.Mark();
1538 const auto source = make_source_string(format, file_path, mark);
1539 const auto details = make_source_details(format, file_path, mark);
1545 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1547 if (!node.IsDefined()) {
1552 const auto mark = node.Mark();
1553 const auto source = make_source_string(format, file_path, mark);
1554 const auto details = make_source_details(format, file_path, mark);
1555 const auto node_value = node.as<std::string>();
1558 if (!mode_opt.has_value()) {
1566 catch (
const YAML::Exception &e) {
1567 const auto mark = node.Mark();
1570 const auto source = make_source_string(format, file_path, mark);
1571 const auto details = make_source_details(format, file_path, mark);
1577 const TextFormatter *format,
const YAML::Node &node,
const std::string &key,
const std::string &file_path)
1579 if (!node.IsDefined()) {
1584 const auto mark = node.Mark();
1585 const auto source = make_source_string(format, file_path, mark);
1586 const auto details = make_source_details(format, file_path, mark);
1588 if (!node.IsMap()) {
1597 if (node[
"backtracking"].IsDefined()) {
1598 const auto &bt_node = node[
"backtracking"];
1599 if (!bt_node.IsMap()) {
1600 const auto bt_mark = bt_node.Mark();
1601 const auto bt_source = make_source_string(format, file_path, bt_mark);
1602 const auto bt_details = make_source_details(format, file_path, bt_mark);
1607 if (bt_node[
"search_algorithm"].IsDefined()) {
1608 const auto &sa_node = bt_node[
"search_algorithm"];
1609 const auto sa_str = sa_node.as<std::string>();
1611 if (!sa_opt.has_value()) {
1612 const auto sa_mark = sa_node.Mark();
1613 const auto sa_source = make_source_string(format, file_path, sa_mark);
1614 const auto sa_details = make_source_details(format, file_path, sa_mark);
1616 "'{}' backtracking search_algorithm has invalid value '{}'.",
1621 const auto sa_mark = sa_node.Mark();
1624 key +
".backtracking.search_algorithm",
1625 "Packing Strategy Params (backtracking) search_algorithm",
1626 make_source_string(format, file_path, sa_mark),
1627 make_source_details(format, file_path, sa_mark)};
1630 if (bt_node[
"node_cutoff"].IsDefined()) {
1631 const auto &nc_node = bt_node[
"node_cutoff"];
1632 const auto nc_val = nc_node.as<std::size_t>();
1633 const auto nc_mark = nc_node.Mark();
1636 key +
".backtracking.node_cutoff",
1637 "Packing Strategy Params (backtracking) node_cutoff",
1638 make_source_string(format, file_path, nc_mark),
1639 make_source_details(format, file_path, nc_mark)};
1642 if (bt_node[
"best_branches"].IsDefined()) {
1643 const auto &bb_node = bt_node[
"best_branches"];
1644 const auto bb_val = bb_node.as<std::size_t>();
1645 const auto bb_mark = bb_node.Mark();
1648 key +
".backtracking.best_branches",
1649 "Packing Strategy Params (backtracking) best_branches",
1650 make_source_string(format, file_path, bb_mark),
1651 make_source_details(format, file_path, bb_mark)};
1654 if (bt_node[
"smart_prune"].IsDefined()) {
1655 const auto &sp_node = bt_node[
"smart_prune"];
1656 const auto sp_val = sp_node.as<
bool>();
1657 const auto sp_mark = sp_node.Mark();
1660 key +
".backtracking.smart_prune",
1661 "Packing Strategy Params (backtracking) smart_prune",
1662 make_source_string(format, file_path, sp_mark),
1663 make_source_details(format, file_path, sp_mark)};
1668 if (node[
"overload_and_remove"].IsDefined()) {
1669 const auto &oar_node = node[
"overload_and_remove"];
1670 if (!oar_node.IsMap()) {
1671 const auto oar_mark = oar_node.Mark();
1672 const auto oar_source = make_source_string(format, file_path, oar_mark);
1673 const auto oar_details = make_source_details(format, file_path, oar_mark);
1679 if (oar_node[
"max_attempts"].IsDefined()) {
1680 const auto &ma_node = oar_node[
"max_attempts"];
1681 const auto ma_val = ma_node.as<std::size_t>();
1682 const auto ma_mark = ma_node.Mark();
1685 key +
".overload_and_remove.max_attempts",
1686 "Packing Strategy Params (overload_and_remove) max_attempts",
1687 make_source_string(format, file_path, ma_mark),
1688 make_source_details(format, file_path, ma_mark)};
1691 if (oar_node[
"seed"].IsDefined()) {
1692 const auto &seed_node = oar_node[
"seed"];
1693 const auto seed_val = seed_node.as<std::uint64_t>();
1694 const auto seed_mark = seed_node.Mark();
1697 key +
".overload_and_remove.seed",
1698 "Packing Strategy Params (overload_and_remove) seed",
1699 make_source_string(format, file_path, seed_mark),
1700 make_source_details(format, file_path, seed_mark)};
1703 if (oar_node[
"shuffle_strategy"].IsDefined()) {
1704 const auto &ss_node = oar_node[
"shuffle_strategy"];
1705 const auto ss_str = ss_node.as<std::string>();
1707 if (!ss_opt.has_value()) {
1708 const auto ss_mark = ss_node.Mark();
1709 const auto ss_source = make_source_string(format, file_path, ss_mark);
1710 const auto ss_details = make_source_details(format, file_path, ss_mark);
1712 "'{}' overload_and_remove shuffle_strategy has invalid value '{}'.",
1717 const auto ss_mark = ss_node.Mark();
1720 key +
".overload_and_remove.shuffle_strategy",
1721 "Packing Strategy Params (overload_and_remove) shuffle_strategy",
1722 make_source_string(format, file_path, ss_mark),
1723 make_source_details(format, file_path, ss_mark)};
1729 catch (
const YAML::Exception &e) {
1730 const auto mark = node.Mark();
1733 const auto source = make_source_string(format, file_path, mark);
1734 const auto details = make_source_details(format, file_path, mark);
1751std::optional<YAML::Node> load_yaml_file(
1752 const std::filesystem::path &path,
1755 bool *out_had_unknown_keys =
nullptr)
1758 const auto cache_it = yaml_cache.find(path);
1759 if (cache_it != yaml_cache.end()) {
1760 return cache_it->second;
1764 if (!std::filesystem::exists(path)) {
1765 return std::nullopt;
1770 auto node = YAML::LoadFile(path.string());
1771 yaml_cache[path] = node;
1774 std::ifstream file{path};
1775 std::vector<std::string> lines;
1777 while (std::getline(file, line)) {
1778 lines.push_back(line);
1780 file_lines_cache[path] = std::move(lines);
1783 if (format !=
nullptr && diagnostics !=
nullptr) {
1784 if (validate_yaml_paths(format, diagnostics, path, node) && out_had_unknown_keys !=
nullptr) {
1785 *out_had_unknown_keys =
true;
1791 catch (
const YAML::Exception &) {
1793 return std::nullopt;
1812std::vector<std::filesystem::path>
1813get_tileset_config_path_chain(
const std::filesystem::path &project_root,
const std::string &
tileset)
1815 std::vector<std::filesystem::path> paths;
1818 const auto porytiles_dir = project_root /
"porytiles";
1822 paths.push_back(porytiles_dir /
"tilesets" /
tileset /
"config.local.yaml");
1825 paths.push_back(porytiles_dir /
"tilesets" /
tileset /
"config.yaml");
1828 paths.push_back(porytiles_dir /
"config.local.yaml");
1831 paths.push_back(porytiles_dir /
"config.yaml");
1847get_config_path_chain(
const std::filesystem::path &project_root,
ConfigScopeType type,
const std::string &scope)
1850 case ConfigScopeType::tileset:
1851 return get_tileset_config_path_chain(project_root, scope);
1852 case ConfigScopeType::layout:
1853 panic(
"Layout config path chain resolution is not yet implemented.");
1856 panic(
"Invalid ConfigScopeType");
1872[[nodiscard]]
bool preload_and_validate_yaml_files(
1875 const std::filesystem::path &project_root,
1877 const std::string &scope)
1879 auto paths_result = get_config_path_chain(project_root, type, scope);
1880 if (!paths_result.has_value()) {
1884 bool had_unknown_keys =
false;
1885 for (
const auto &path : paths_result.value()) {
1886 load_yaml_file(path, format, diagnostics, &had_unknown_keys);
1889 return had_unknown_keys;
1915template <
typename T,
typename LoadFunc,
typename NodeExtractFunc,
typename ParseFunc>
1918 const std::vector<std::filesystem::path> &paths,
1920 NodeExtractFunc extract_node_func,
1921 ParseFunc parse_func,
1922 const std::string &key,
1923 const std::string &provider_name)
1925 for (
const auto &path : paths) {
1926 const auto yaml_doc = load_func(path);
1927 if (!yaml_doc.has_value()) {
1933 const auto node = extract_node_func(yaml_doc.value());
1934 auto result = parse_func(format, node, key, path.string());
1937 if (result.state == ValidationState::valid || result.state == ValidationState::invalid) {
1938 result.source_key = provider_name;
1944 catch (
const YAML::Exception &) {
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A service for printing file lines with highlighted lines and line numbers.
A generic palette container for colors that support transparency checking.
Represents a 32-bit RGBA color.
static constexpr std::uint8_t alpha_opaque
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 > ¶ms) const
Formats a string with styled parameters using fmtlib syntax.
Abstract class for structured error reporting and diagnostic output.
virtual void error(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged error message.
const std::unordered_set< std::string > valid_yaml_paths
Set of valid YAML configuration paths.
std::optional< ArtifactEditMode > artifact_edit_mode_from_str(const std::string &str)
Parses a string into a ArtifactEditMode with fuzzy matching.
std::map< std::string, MetatileAttributeFieldOverride > MetatileAttributeFieldOverrides
A map from field name to its override; applied at schema load time.
std::vector< RolePinDefinition > RolePinDefinitions
An ordered list of role pin definitions; order is display/declaration order (also CSV column order).
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
std::optional< PackingStrategyType > packing_strategy_type_from_str(const std::string &str)
Parses a string into a PackingStrategyType with fuzzy matching.
std::optional< FrameLinking > frame_linking_from_str(const std::string &str)
Parses a string into a FrameLinking with fuzzy matching.
std::optional< PrimaryPairingMode > primary_pairing_mode_from_str(const std::string &str)
Parses a string into a PrimaryPairingMode with fuzzy matching.
std::optional< AnimMultiPaletteSubtileResolutionStrategy > anim_multi_palette_subtile_resolution_strategy_from_str(const std::string &str)
Parses a string into a AnimMultiPaletteSubtileResolutionStrategy with fuzzy matching.
const std::unordered_set< std::string > valid_yaml_map_prefixes
Set of YAML path prefixes that represent map-type configuration values.
std::optional< ShuffleStrategy > shuffle_strategy_from_str(const std::string &str)
Parses a string into a ShuffleStrategy with fuzzy matching.
std::vector< MetatileAttributeFieldDefinition > MetatileAttributeFieldDefinitions
An ordered list of metatile attribute field definitions; order is display/declaration order.
std::unordered_map< std::string, PerAnimOverride > PerAnimOverrides
Per-animation configuration map.
@ not_provided
nothing attribute-related was found; other providers should be consulted
@ valid
one or more usable candidate sets were inferred
@ invalid
no layout could be determined from what the project declares (fatal at resolution time)
@ error
Emit a formatted error and fail decompilation.
std::optional< FieldRole > field_role_from_string(const std::string &text)
Parses a FieldRole from its string form, or nullopt when the name is not a known role.
std::optional< TileSharingPacking > tile_sharing_packing_from_str(const std::string &str)
Parses a string into a TileSharingPacking with fuzzy matching.
std::optional< TileSharingAlignment > tile_sharing_alignment_from_str(const std::string &str)
Parses a string into a TileSharingAlignment with fuzzy matching.
ConfigScopeType
Specifies the scope type for configuration value lookups.
@ tileset
Configuration scoped to a specific tileset.
std::optional< AnimPaletteResolutionStrategy > anim_palette_resolution_strategy_from_str(const std::string &str)
Parses a string into a AnimPaletteResolutionStrategy with fuzzy matching.
std::optional< TilesPaletteMode > tiles_palette_mode_from_str(const std::string &str)
Parses a string into a TilesPaletteMode with fuzzy matching.
std::optional< SearchAlgorithm > search_algorithm_from_str(const std::string &str)
Parses a string into a SearchAlgorithm with fuzzy matching.
std::optional< AnimKeyFrameResolutionStrategy > anim_key_frame_resolution_strategy_from_str(const std::string &str)
Parses a string into a AnimKeyFrameResolutionStrategy with fuzzy matching.
Utility functions for string manipulation and formatting.
ConfigPODField< std::size_t > best_branches
ConfigPODField< std::size_t > node_cutoff
ConfigPODField< bool > smart_prune
ConfigPODField< SearchAlgorithm > search_algorithm
A lightweight wrapper for per-field configuration values with source metadata.
A small container that holds an optional-wrapped value, validation state, and metadata about the valu...
static LayerValue valid(T val, std::string source_key, std::string source_info)
Creates a LayerValue representing a valid configuration value.
static LayerValue not_provided()
Creates a LayerValue representing that the provider does not supply this configuration.
static LayerValue invalid(std::string error, std::string source_info)
Creates a LayerValue representing an invalid configuration value.
ConfigPODField< ShuffleStrategy > shuffle_strategy
ConfigPODField< std::uint64_t > seed
ConfigPODField< std::size_t > max_attempts
Container for per-strategy packing parameters.
BacktrackingParams backtracking
OverloadAndRemoveParams overload_and_remove
Per-animation configuration override for animation decompilation.
std::vector< ConfigPODField< AnimPaletteResolutionStrategy > > per_tile_palette_resolution_strategies
ConfigPODField< AnimKeyFrameResolutionStrategy > key_frame_resolution_strategy
ConfigPODField< AnimMultiPaletteSubtileResolutionStrategy > multi_palette_subtile_resolution_strategy
ConfigPODField< FrameLinking > linking
ConfigPODField< AnimPaletteResolutionStrategy > palette_resolution_strategy
A partial override of a field's provider definition.
std::optional< std::unordered_set< std::string > > skipped
std::optional< std::filesystem::path > header
std::optional< HeaderFormat > format
std::optional< std::string > prefix
Describes where and how a field's named values are declared.
A user request to emit a trailing pin column for one schema role in attributes.csv.