YAML: Add support for resolving anchors, and use it for GameDB

This commit is contained in:
Silent
2026-07-01 16:24:37 +02:00
committed by Ty
parent 2444c6090b
commit 78d0cf4a4f
3 changed files with 92 additions and 28 deletions
+88 -26
View File
@@ -17,7 +17,7 @@ struct RapidYAMLContext
Error* error = nullptr;
};
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error)
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors)
{
RapidYAMLContext context;
context.error = error;
@@ -28,40 +28,93 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
callbacks.set_user_data(static_cast<void*>(&context));
callbacks.set_error_basic([](ryml::csubstr msg, const ryml::ErrorDataBasic& errdata, void* user_data) {
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_basic_format(std::move(callback), msg, errdata);
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_basic_format(std::move(callback), msg, errdata);
// We might have already returned, so don't try to recover.
pxFailRel(description.c_str());
std::abort();
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
if (context != nullptr)
{
std::longjmp(context->env, 1);
}
else
{
std::terminate();
}
});
callbacks.set_error_parse([](ryml::csubstr msg, const ryml::ErrorDataParse& errdata, void* user_data) {
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_parse_format(std::move(callback), msg, errdata);
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_parse_format(std::move(callback), msg, errdata);
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
Error::SetString(context->error, std::move(description));
std::longjmp(context->env, 1);
if (context != nullptr)
{
std::longjmp(context->env, 2);
}
else
{
std::terminate();
}
});
callbacks.set_error_visit([](ryml::csubstr msg, const ryml::ErrorDataVisit& errdata, void* user_data) {
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_visit_format(std::move(callback), msg, errdata);
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_visit_format(std::move(callback), msg, errdata);
// We've probably already returned, so don't try to recover.
pxFailRel(description.c_str());
std::abort();
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
if (context != nullptr)
{
std::longjmp(context->env, 3);
}
else
{
std::terminate();
}
});
#else
callbacks.m_user_data = static_cast<void*>(&context);
@@ -76,15 +129,24 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
ryml::EventHandlerTree event_handler(callbacks);
ryml::Parser parser(&event_handler);
ryml::Tree tree;
ryml::Tree tree(callbacks);
// The only options RapidYAML provides for recovering from errors are
// throwing an exception or using setjmp/longjmp. Since we have exceptions
// disabled we have to use the latter option.
if (setjmp(context.env))
if (setjmp(context.env) != 0)
return std::nullopt;
ryml::parse_in_arena(&parser, file_name, yaml, &tree);
if (resolve_anchors)
{
tree.resolve();
}
// Callbacks passed to ryml::Tree are used for value parsing errors later,
// so we need to clear the context before it goes out of scope.
callbacks.set_user_data(nullptr);
tree.callbacks(callbacks);
return tree;
}
+3 -1
View File
@@ -14,4 +14,6 @@
/// parsing errors (as is recommended by the documentation for cases where
/// exceptions are disabled). The file_name parameter is only used for error
/// messages, which are returned via the error parameter.
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error);
/// If resolve_anchors is set to true, YAML anchors and aliases will be resolved.
/// It's a potentially slow operation, so it's opt-in.
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors = false);
+1 -1
View File
@@ -1030,7 +1030,7 @@ void GameDatabase::initDatabase()
const ryml::csubstr yaml = ryml::to_csubstr(*buffer);
Error error;
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error);
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error, true);
if (!tree.has_value())
{
Console.ErrorFmt("GameDB: Failed to parse game database file {}:", path);