diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp index 198cf1d0b5..759834c5e1 100644 --- a/apps/openmw_test_suite/lua/test_yaml.cpp +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include "../testing_util.hpp" diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 41b8fb346f..7e2736aa8e 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -3,239 +3,267 @@ #include #include +#include + #include #include namespace LuaUtil { - namespace + namespace YamlLoader { constexpr uint64_t maxDepth = 250; - } - sol::object YamlLoader::load(const std::string& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return LuaUtil::YamlLoader::load(rootNodes, lua); - } - - sol::object YamlLoader::load(std::istream& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return load(rootNodes, lua); - } - - sol::object YamlLoader::load(const std::vector& rootNodes, const sol::state_view& lua) - { - if (rootNodes.empty()) - return sol::nil; - - if (rootNodes.size() == 1) - return getNode(rootNodes[0], lua, 0); - - sol::table documentsTable(lua, sol::create); - for (const auto& root : rootNodes) + enum class ScalarType { - documentsTable.add(getNode(root, lua, 1)); + Boolean, + Decimal, + Float, + Hexadecimal, + Infinity, + NotNumber, + Null, + Octal, + String + }; + + sol::object load(const std::vector& rootNodes, const sol::state_view& lua); + + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + ScalarType getScalarType(const YAML::Node& node); + + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); + + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message); + + sol::object load(const std::string& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); } - return documentsTable; - } - - sol::object YamlLoader::getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - if (depth >= maxDepth) - throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); - - ++depth; - - if (node.IsMap()) - return getMap(node, lua, depth); - else if (node.IsSequence()) - return getArray(node, lua, depth); - else if (node.IsScalar()) - return getScalar(node, lua); - else if (node.IsNull()) - return sol::nil; - - nodeError(node, "An unknown YAML node encountered"); - } - - sol::table YamlLoader::getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - sol::table childTable(lua, sol::create); - - for (const auto& pair : node) + sol::object load(const std::vector& rootNodes, const sol::state_view& lua) { - if (pair.first.IsMap()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); - if (pair.first.IsSequence()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); - if (pair.first.IsNull()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); - - auto key = getNode(pair.first, lua, depth); - if (key.get_type() == sol::type::number && std::isnan(key.as())) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); - - childTable[key] = getNode(pair.second, lua, depth); - } - - return childTable; - } - - sol::table YamlLoader::getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - sol::table childTable(lua, sol::create); - - for (const auto& child : node) - { - childTable.add(getNode(child, lua, depth)); - } - - return childTable; - } - - YamlLoader::ScalarType YamlLoader::getScalarType(const YAML::Node& node) - { - const auto& tag = node.Tag(); - const auto& value = node.Scalar(); - if (tag == "!") - return ScalarType::String; - - // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no - // sense in Lua: - // 1. Both integers and floats use the "number" type prior to Lua 5.3 - // 2. Strings can be quoted, which is more readable than "!!str" - // 3. Most of possible conversions are invalid or their result is unclear - // So ignore this feature for now. - if (tag != "?") - nodeError(node, "An invalid tag'" + tag + "' encountered"); - - if (value.empty()) - return ScalarType::Null; - - // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) - static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), boolRegex)) - return ScalarType::Boolean; - - static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), decimalRegex)) - return ScalarType::Decimal; - - static const std::regex floatRegex( - "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), floatRegex)) - return ScalarType::Float; - - static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), octalRegex)) - return ScalarType::Octal; - - static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), hexdecimalRegex)) - return ScalarType::Hexadecimal; - - static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), infinityRegex)) - return ScalarType::Infinity; - - static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), nanRegex)) - return ScalarType::NotNumber; - - static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), nullRegex)) - return ScalarType::Null; - - return ScalarType::String; - } - - sol::object YamlLoader::getScalar(const YAML::Node& node, const sol::state_view& lua) - { - auto type = getScalarType(node); - const auto& value = node.Scalar(); - - switch (type) - { - case ScalarType::Null: + if (rootNodes.empty()) return sol::nil; - case ScalarType::String: - return sol::make_object(lua, value); - case ScalarType::NotNumber: - return sol::make_object(lua, std::nan("")); - case ScalarType::Infinity: + + if (rootNodes.size() == 1) + return getNode(rootNodes[0], lua, 0); + + sol::table documentsTable(lua, sol::create); + for (const auto& root : rootNodes) { - if (!value.empty() && value[0] == '-') - return sol::make_object(lua, -std::numeric_limits::infinity()); - - return sol::make_object(lua, std::numeric_limits::infinity()); + documentsTable.add(getNode(root, lua, 1)); } - case ScalarType::Boolean: + + return documentsTable; + } + + sol::object load(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); + } + + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + if (depth >= maxDepth) + throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); + + ++depth; + + if (node.IsMap()) + return getMap(node, lua, depth); + else if (node.IsSequence()) + return getArray(node, lua, depth); + else if (node.IsScalar()) + return getScalar(node, lua); + else if (node.IsNull()) + return sol::nil; + + nodeError(node, "An unknown YAML node encountered"); + } + + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& pair : node) { - if (Misc::StringUtils::lowerCase(value) == "true") - return sol::make_object(lua, true); + if (pair.first.IsMap()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); + if (pair.first.IsSequence()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); + if (pair.first.IsNull()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); - if (Misc::StringUtils::lowerCase(value) == "false") - return sol::make_object(lua, false); + auto key = getNode(pair.first, lua, depth); + if (key.get_type() == sol::type::number && std::isnan(key.as())) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); - nodeError(node, "Can not read a boolean value '" + value + "'"); + childTable[key] = getNode(pair.second, lua, depth); } - case ScalarType::Decimal: + + return childTable; + } + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& child : node) { - int offset = 0; - - // std::from_chars does not support "+" sign - if (!value.empty() && value[0] == '+') - ++offset; - - int result = 0; - const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); - if (status.ec == std::errc()) - return sol::make_object(lua, result); - - nodeError(node, "Can not read a decimal value '" + value + "'"); + childTable.add(getNode(child, lua, depth)); } - case ScalarType::Float: + + return childTable; + } + + ScalarType getScalarType(const YAML::Node& node) + { + const auto& tag = node.Tag(); + const auto& value = node.Scalar(); + if (tag == "!") + return ScalarType::String; + + // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no + // sense in Lua: + // 1. Both integers and floats use the "number" type prior to Lua 5.3 + // 2. Strings can be quoted, which is more readable than "!!str" + // 3. Most of possible conversions are invalid or their result is unclear + // So ignore this feature for now. + if (tag != "?") + nodeError(node, "An invalid tag'" + tag + "' encountered"); + + if (value.empty()) + return ScalarType::Null; + + // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) + static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), boolRegex)) + return ScalarType::Boolean; + + static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), decimalRegex)) + return ScalarType::Decimal; + + static const std::regex floatRegex( + "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), floatRegex)) + return ScalarType::Float; + + static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), octalRegex)) + return ScalarType::Octal; + + static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), hexdecimalRegex)) + return ScalarType::Hexadecimal; + + static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), infinityRegex)) + return ScalarType::Infinity; + + static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nanRegex)) + return ScalarType::NotNumber; + + static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nullRegex)) + return ScalarType::Null; + + return ScalarType::String; + } + + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua) + { + auto type = getScalarType(node); + const auto& value = node.Scalar(); + + switch (type) { - // Not all compilers support std::from_chars for floats - double result = 0.0; - bool success = YAML::convert::decode(node, result); - if (success) - return sol::make_object(lua, result); + case ScalarType::Null: + return sol::nil; + case ScalarType::String: + return sol::make_object(lua, value); + case ScalarType::NotNumber: + return sol::make_object(lua, std::nan("")); + case ScalarType::Infinity: + { + if (!value.empty() && value[0] == '-') + return sol::make_object(lua, -std::numeric_limits::infinity()); - nodeError(node, "Can not read a float value '" + value + "'"); - } - case ScalarType::Hexadecimal: - { - int result = 0; - const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); - if (status.ec == std::errc()) - return sol::make_object(lua, result); + return sol::make_object(lua, std::numeric_limits::infinity()); + } + case ScalarType::Boolean: + { + if (Misc::StringUtils::lowerCase(value) == "true") + return sol::make_object(lua, true); - nodeError(node, "Can not read a hexadecimal value '" + value + "'"); - } - case ScalarType::Octal: - { - int result = 0; - const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); - if (status.ec == std::errc()) - return sol::make_object(lua, result); + if (Misc::StringUtils::lowerCase(value) == "false") + return sol::make_object(lua, false); - nodeError(node, "Can not read an octal value '" + value + "'"); + nodeError(node, "Can not read a boolean value '" + value + "'"); + } + case ScalarType::Decimal: + { + int offset = 0; + + // std::from_chars does not support "+" sign + if (!value.empty() && value[0] == '+') + ++offset; + + int result = 0; + const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a decimal value '" + value + "'"); + } + case ScalarType::Float: + { + // Not all compilers support std::from_chars for floats + double result = 0.0; + bool success = YAML::convert::decode(node, result); + if (success) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a float value '" + value + "'"); + } + case ScalarType::Hexadecimal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a hexadecimal value '" + value + "'"); + } + case ScalarType::Octal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read an octal value '" + value + "'"); + } + default: + nodeError(node, "An unknown scalar '" + value + "' encountered"); } - default: - nodeError(node, "An unknown scalar '" + value + "' encountered"); + } + + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message) + { + const auto& mark = node.Mark(); + std::string error = Misc::StringUtils::format( + " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); + throw std::runtime_error(message + error); } } - - [[noreturn]] void YamlLoader::nodeError(const YAML::Node& node, const std::string& message) - { - const auto& mark = node.Mark(); - std::string error = Misc::StringUtils::format( - " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); - throw std::runtime_error(message + error); - } - } diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index 0ba2e5a1f1..c3339d7eb3 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -2,46 +2,16 @@ #define COMPONENTS_LUA_YAMLLOADER_H #include -#include namespace LuaUtil { - class YamlLoader + namespace YamlLoader { - public: - static sol::object load(const std::string& input, const sol::state_view& lua); + sol::object load(const std::string& input, const sol::state_view& lua); - static sol::object load(std::istream& input, const sol::state_view& lua); - - private: - enum class ScalarType - { - Boolean, - Decimal, - Float, - Hexadecimal, - Infinity, - NotNumber, - Null, - Octal, - String - }; - - static sol::object load(const std::vector& rootNodes, const sol::state_view& lua); - - static sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static ScalarType getScalarType(const YAML::Node& node); - - static sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); - - [[noreturn]] static void nodeError(const YAML::Node& node, const std::string& message); - }; + sol::object load(std::istream& input, const sol::state_view& lua); + } }