mirror of
https://github.com/OpenMW/openmw.git
synced 2025-12-16 07:43:07 +00:00
Merge branch 'avoid_copy' into 'master'
Follow-up for YAML API See merge request OpenMW/openmw!3961
This commit is contained in:
commit
63276e0f1f
4 changed files with 277 additions and 270 deletions
|
|
@ -19,12 +19,11 @@ namespace MWLua
|
||||||
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
||||||
|
|
||||||
api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) {
|
api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) {
|
||||||
auto normalizedName = VFS::Path::normalizeFilename(fileName);
|
Files::IStreamPtr file = vfs->get(VFS::Path::Normalized(fileName));
|
||||||
auto file = vfs->getNormalized(normalizedName);
|
return LuaUtil::loadYaml(*file, lua->sol());
|
||||||
return LuaUtil::YamlLoader::load(*file, lua->sol());
|
|
||||||
};
|
};
|
||||||
api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) {
|
api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) {
|
||||||
return LuaUtil::YamlLoader::load(std::string(inputData), lua->sol());
|
return LuaUtil::loadYaml(std::string(inputData), lua->sol());
|
||||||
};
|
};
|
||||||
|
|
||||||
return LuaUtil::makeReadOnly(api);
|
return LuaUtil::makeReadOnly(api);
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
#include "gmock/gmock.h"
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <components/lua/yamlloader.hpp>
|
#include <sol/object.hpp>
|
||||||
|
#include <sol/state.hpp>
|
||||||
|
#include <sol/table.hpp>
|
||||||
|
|
||||||
#include "../testing_util.hpp"
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
|
#include <components/lua/yamlloader.hpp>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool checkNumber(sol::state_view& lua, const std::string& inputData, T requiredValue)
|
bool checkNumber(sol::state_view& lua, const std::string& inputData, T requiredValue)
|
||||||
{
|
{
|
||||||
sol::object result = LuaUtil::YamlLoader::load(inputData, lua);
|
sol::object result = LuaUtil::loadYaml(inputData, lua);
|
||||||
if (result.get_type() != sol::type::number)
|
if (result.get_type() != sol::type::number)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -19,7 +22,7 @@ namespace
|
||||||
|
|
||||||
bool checkBool(sol::state_view& lua, const std::string& inputData, bool requiredValue)
|
bool checkBool(sol::state_view& lua, const std::string& inputData, bool requiredValue)
|
||||||
{
|
{
|
||||||
sol::object result = LuaUtil::YamlLoader::load(inputData, lua);
|
sol::object result = LuaUtil::loadYaml(inputData, lua);
|
||||||
if (result.get_type() != sol::type::boolean)
|
if (result.get_type() != sol::type::boolean)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -28,13 +31,13 @@ namespace
|
||||||
|
|
||||||
bool checkNil(sol::state_view& lua, const std::string& inputData)
|
bool checkNil(sol::state_view& lua, const std::string& inputData)
|
||||||
{
|
{
|
||||||
sol::object result = LuaUtil::YamlLoader::load(inputData, lua);
|
sol::object result = LuaUtil::loadYaml(inputData, lua);
|
||||||
return result == sol::nil;
|
return result == sol::nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkNan(sol::state_view& lua, const std::string& inputData)
|
bool checkNan(sol::state_view& lua, const std::string& inputData)
|
||||||
{
|
{
|
||||||
sol::object result = LuaUtil::YamlLoader::load(inputData, lua);
|
sol::object result = LuaUtil::loadYaml(inputData, lua);
|
||||||
if (result.get_type() != sol::type::number)
|
if (result.get_type() != sol::type::number)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -43,7 +46,7 @@ namespace
|
||||||
|
|
||||||
bool checkString(sol::state_view& lua, const std::string& inputData, const std::string& requiredValue)
|
bool checkString(sol::state_view& lua, const std::string& inputData, const std::string& requiredValue)
|
||||||
{
|
{
|
||||||
sol::object result = LuaUtil::YamlLoader::load(inputData, lua);
|
sol::object result = LuaUtil::loadYaml(inputData, lua);
|
||||||
if (result.get_type() != sol::type::string)
|
if (result.get_type() != sol::type::string)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -52,7 +55,7 @@ namespace
|
||||||
|
|
||||||
bool checkString(sol::state_view& lua, const std::string& inputData)
|
bool checkString(sol::state_view& lua, const std::string& inputData)
|
||||||
{
|
{
|
||||||
sol::object result = LuaUtil::YamlLoader::load(inputData, lua);
|
sol::object result = LuaUtil::loadYaml(inputData, lua);
|
||||||
if (result.get_type() != sol::type::string)
|
if (result.get_type() != sol::type::string)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -164,7 +167,7 @@ namespace
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
YAML::Node root = YAML::Load(input);
|
YAML::Node root = YAML::Load(input);
|
||||||
sol::object result = LuaUtil::YamlLoader::load(input, lua);
|
sol::object result = LuaUtil::loadYaml(input, lua);
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error& e)
|
catch (const std::runtime_error& e)
|
||||||
{
|
{
|
||||||
|
|
@ -179,29 +182,29 @@ namespace
|
||||||
{
|
{
|
||||||
sol::state lua;
|
sol::state lua;
|
||||||
|
|
||||||
sol::object map = LuaUtil::YamlLoader::load("{ x: , y: 2, 4: 5 }", lua);
|
sol::object map = LuaUtil::loadYaml("{ x: , y: 2, 4: 5 }", lua);
|
||||||
ASSERT_EQ(map.as<sol::table>()["x"], sol::nil);
|
ASSERT_EQ(map.as<sol::table>()["x"], sol::nil);
|
||||||
ASSERT_EQ(map.as<sol::table>()["y"], 2);
|
ASSERT_EQ(map.as<sol::table>()["y"], 2);
|
||||||
ASSERT_EQ(map.as<sol::table>()[4], 5);
|
ASSERT_EQ(map.as<sol::table>()[4], 5);
|
||||||
|
|
||||||
sol::object array = LuaUtil::YamlLoader::load("[ 3, 4 ]", lua);
|
sol::object array = LuaUtil::loadYaml("[ 3, 4 ]", lua);
|
||||||
ASSERT_EQ(array.as<sol::table>()[1], 3);
|
ASSERT_EQ(array.as<sol::table>()[1], 3);
|
||||||
|
|
||||||
sol::object emptyTable = LuaUtil::YamlLoader::load("{}", lua);
|
sol::object emptyTable = LuaUtil::loadYaml("{}", lua);
|
||||||
ASSERT_TRUE(emptyTable.as<sol::table>().empty());
|
ASSERT_TRUE(emptyTable.as<sol::table>().empty());
|
||||||
|
|
||||||
sol::object emptyArray = LuaUtil::YamlLoader::load("[]", lua);
|
sol::object emptyArray = LuaUtil::loadYaml("[]", lua);
|
||||||
ASSERT_TRUE(emptyArray.as<sol::table>().empty());
|
ASSERT_TRUE(emptyArray.as<sol::table>().empty());
|
||||||
|
|
||||||
ASSERT_THROW(LuaUtil::YamlLoader::load("{ null: 1 }", lua), std::runtime_error);
|
ASSERT_THROW(LuaUtil::loadYaml("{ null: 1 }", lua), std::runtime_error);
|
||||||
ASSERT_THROW(LuaUtil::YamlLoader::load("{ .nan: 1 }", lua), std::runtime_error);
|
ASSERT_THROW(LuaUtil::loadYaml("{ .nan: 1 }", lua), std::runtime_error);
|
||||||
|
|
||||||
const std::string scalarArrayInput = R"(
|
const std::string scalarArrayInput = R"(
|
||||||
- First Scalar
|
- First Scalar
|
||||||
- 1
|
- 1
|
||||||
- true)";
|
- true)";
|
||||||
|
|
||||||
sol::object scalarArray = LuaUtil::YamlLoader::load(scalarArrayInput, lua);
|
sol::object scalarArray = LuaUtil::loadYaml(scalarArrayInput, lua);
|
||||||
ASSERT_EQ(scalarArray.as<sol::table>()[1], std::string("First Scalar"));
|
ASSERT_EQ(scalarArray.as<sol::table>()[1], std::string("First Scalar"));
|
||||||
ASSERT_EQ(scalarArray.as<sol::table>()[2], 1);
|
ASSERT_EQ(scalarArray.as<sol::table>()[2], 1);
|
||||||
ASSERT_EQ(scalarArray.as<sol::table>()[3], true);
|
ASSERT_EQ(scalarArray.as<sol::table>()[3], true);
|
||||||
|
|
@ -212,7 +215,7 @@ namespace
|
||||||
float: 0.278 # Float value
|
float: 0.278 # Float value
|
||||||
bool: false # Boolean value)";
|
bool: false # Boolean value)";
|
||||||
|
|
||||||
sol::object scalarMapWithComments = LuaUtil::YamlLoader::load(scalarMapWithCommentsInput, lua);
|
sol::object scalarMapWithComments = LuaUtil::loadYaml(scalarMapWithCommentsInput, lua);
|
||||||
ASSERT_EQ(scalarMapWithComments.as<sol::table>()["string"], std::string("str"));
|
ASSERT_EQ(scalarMapWithComments.as<sol::table>()["string"], std::string("str"));
|
||||||
ASSERT_EQ(scalarMapWithComments.as<sol::table>()["integer"], 65);
|
ASSERT_EQ(scalarMapWithComments.as<sol::table>()["integer"], 65);
|
||||||
ASSERT_EQ(scalarMapWithComments.as<sol::table>()["float"], 0.278);
|
ASSERT_EQ(scalarMapWithComments.as<sol::table>()["float"], 0.278);
|
||||||
|
|
@ -228,7 +231,7 @@ namespace
|
||||||
- false
|
- false
|
||||||
- 1)";
|
- 1)";
|
||||||
|
|
||||||
sol::object mapOfArrays = LuaUtil::YamlLoader::load(mapOfArraysInput, lua);
|
sol::object mapOfArrays = LuaUtil::loadYaml(mapOfArraysInput, lua);
|
||||||
ASSERT_EQ(mapOfArrays.as<sol::table>()["x"][3], true);
|
ASSERT_EQ(mapOfArrays.as<sol::table>()["x"][3], true);
|
||||||
ASSERT_EQ(mapOfArrays.as<sol::table>()["y"][1], std::string("aaa"));
|
ASSERT_EQ(mapOfArrays.as<sol::table>()["y"][1], std::string("aaa"));
|
||||||
|
|
||||||
|
|
@ -242,7 +245,7 @@ namespace
|
||||||
hr: 63
|
hr: 63
|
||||||
avg: 0.288)";
|
avg: 0.288)";
|
||||||
|
|
||||||
sol::object arrayOfMaps = LuaUtil::YamlLoader::load(arrayOfMapsInput, lua);
|
sol::object arrayOfMaps = LuaUtil::loadYaml(arrayOfMapsInput, lua);
|
||||||
ASSERT_EQ(arrayOfMaps.as<sol::table>()[1]["avg"], 0.278);
|
ASSERT_EQ(arrayOfMaps.as<sol::table>()[1]["avg"], 0.278);
|
||||||
ASSERT_EQ(arrayOfMaps.as<sol::table>()[2]["name"], std::string("Name2"));
|
ASSERT_EQ(arrayOfMaps.as<sol::table>()[2]["name"], std::string("Name2"));
|
||||||
|
|
||||||
|
|
@ -250,7 +253,7 @@ namespace
|
||||||
- [Name1, 65, 0.278]
|
- [Name1, 65, 0.278]
|
||||||
- [Name2 , 63, 0.288])";
|
- [Name2 , 63, 0.288])";
|
||||||
|
|
||||||
sol::object arrayOfArrays = LuaUtil::YamlLoader::load(arrayOfArraysInput, lua);
|
sol::object arrayOfArrays = LuaUtil::loadYaml(arrayOfArraysInput, lua);
|
||||||
ASSERT_EQ(arrayOfArrays.as<sol::table>()[1][2], 65);
|
ASSERT_EQ(arrayOfArrays.as<sol::table>()[1][2], 65);
|
||||||
ASSERT_EQ(arrayOfArrays.as<sol::table>()[2][1], std::string("Name2"));
|
ASSERT_EQ(arrayOfArrays.as<sol::table>()[2][1], std::string("Name2"));
|
||||||
|
|
||||||
|
|
@ -261,7 +264,7 @@ namespace
|
||||||
avg: 0.288,
|
avg: 0.288,
|
||||||
})";
|
})";
|
||||||
|
|
||||||
sol::object mapOfMaps = LuaUtil::YamlLoader::load(mapOfMapsInput, lua);
|
sol::object mapOfMaps = LuaUtil::loadYaml(mapOfMapsInput, lua);
|
||||||
ASSERT_EQ(mapOfMaps.as<sol::table>()["Name1"]["hr"], 65);
|
ASSERT_EQ(mapOfMaps.as<sol::table>()["Name1"]["hr"], 65);
|
||||||
ASSERT_EQ(mapOfMaps.as<sol::table>()["Name2"]["avg"], 0.288);
|
ASSERT_EQ(mapOfMaps.as<sol::table>()["Name2"]["avg"], 0.288);
|
||||||
}
|
}
|
||||||
|
|
@ -281,7 +284,7 @@ namespace
|
||||||
" - 3\n"
|
" - 3\n"
|
||||||
" - false";
|
" - false";
|
||||||
|
|
||||||
sol::object twoDocuments = LuaUtil::YamlLoader::load(twoDocumentsInput, lua);
|
sol::object twoDocuments = LuaUtil::loadYaml(twoDocumentsInput, lua);
|
||||||
ASSERT_EQ(twoDocuments.as<sol::table>()[1][1], std::string("First Scalar"));
|
ASSERT_EQ(twoDocuments.as<sol::table>()[1][1], std::string("First Scalar"));
|
||||||
ASSERT_EQ(twoDocuments.as<sol::table>()[2][3], false);
|
ASSERT_EQ(twoDocuments.as<sol::table>()[2][3], false);
|
||||||
|
|
||||||
|
|
@ -294,7 +297,7 @@ namespace
|
||||||
- *a # Subsequent occurrence
|
- *a # Subsequent occurrence
|
||||||
- Name2)";
|
- Name2)";
|
||||||
|
|
||||||
sol::object anchor = LuaUtil::YamlLoader::load(anchorInput, lua);
|
sol::object anchor = LuaUtil::loadYaml(anchorInput, lua);
|
||||||
ASSERT_EQ(anchor.as<sol::table>()["y"][1], std::string("Value1"));
|
ASSERT_EQ(anchor.as<sol::table>()["y"][1], std::string("Value1"));
|
||||||
|
|
||||||
const std::string compoundKeyInput = R"(
|
const std::string compoundKeyInput = R"(
|
||||||
|
|
@ -306,7 +309,7 @@ namespace
|
||||||
String4 ]
|
String4 ]
|
||||||
: [ 2, 3, 4 ])";
|
: [ 2, 3, 4 ])";
|
||||||
|
|
||||||
ASSERT_THROW(LuaUtil::YamlLoader::load(compoundKeyInput, lua), std::runtime_error);
|
ASSERT_THROW(LuaUtil::loadYaml(compoundKeyInput, lua), std::runtime_error);
|
||||||
|
|
||||||
const std::string compactNestedMappingInput = R"(
|
const std::string compactNestedMappingInput = R"(
|
||||||
- item : Item1
|
- item : Item1
|
||||||
|
|
@ -316,7 +319,7 @@ namespace
|
||||||
- item : Item3
|
- item : Item3
|
||||||
quantity: 11)";
|
quantity: 11)";
|
||||||
|
|
||||||
sol::object compactNestedMapping = LuaUtil::YamlLoader::load(compactNestedMappingInput, lua);
|
sol::object compactNestedMapping = LuaUtil::loadYaml(compactNestedMappingInput, lua);
|
||||||
ASSERT_EQ(compactNestedMapping.as<sol::table>()[2]["quantity"], 4);
|
ASSERT_EQ(compactNestedMapping.as<sol::table>()[2]["quantity"], 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,7 +349,7 @@ namespace
|
||||||
quoted: "So does this
|
quoted: "So does this
|
||||||
quoted scalar.\n")";
|
quoted scalar.\n")";
|
||||||
|
|
||||||
sol::object multiLinePlanarScalars = LuaUtil::YamlLoader::load(multiLinePlanarScalarsInput, lua);
|
sol::object multiLinePlanarScalars = LuaUtil::loadYaml(multiLinePlanarScalarsInput, lua);
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
multiLinePlanarScalars.as<sol::table>()["plain"] == std::string("This unquoted scalar spans many lines."));
|
multiLinePlanarScalars.as<sol::table>()["plain"] == std::string("This unquoted scalar spans many lines."));
|
||||||
ASSERT_TRUE(multiLinePlanarScalars.as<sol::table>()["quoted"] == std::string("So does this quoted scalar.\n"));
|
ASSERT_TRUE(multiLinePlanarScalars.as<sol::table>()["quoted"] == std::string("So does this quoted scalar.\n"));
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
#include "yamlloader.hpp"
|
#include "yamlloader.hpp"
|
||||||
|
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <system_error>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <sol/object.hpp>
|
||||||
|
#include <sol/state_view.hpp>
|
||||||
|
|
||||||
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
#include <components/misc/strings/format.hpp>
|
#include <components/misc/strings/format.hpp>
|
||||||
#include <components/misc/strings/lower.hpp>
|
#include <components/misc/strings/lower.hpp>
|
||||||
|
|
@ -11,231 +21,260 @@ namespace LuaUtil
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr uint64_t maxDepth = 250;
|
constexpr uint64_t maxDepth = 250;
|
||||||
|
|
||||||
|
enum class ScalarType
|
||||||
|
{
|
||||||
|
Boolean,
|
||||||
|
Decimal,
|
||||||
|
Float,
|
||||||
|
Hexadecimal,
|
||||||
|
Infinity,
|
||||||
|
NotNumber,
|
||||||
|
Null,
|
||||||
|
Octal,
|
||||||
|
String
|
||||||
|
};
|
||||||
|
|
||||||
|
sol::object loadAll(const std::vector<YAML::Node>& 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 YamlLoader::load(const std::string& input, const sol::state_view& lua)
|
sol::object loadYaml(const std::string& input, const sol::state_view& lua)
|
||||||
{
|
{
|
||||||
std::vector<YAML::Node> rootNodes = YAML::LoadAll(input);
|
std::vector<YAML::Node> rootNodes = YAML::LoadAll(input);
|
||||||
return LuaUtil::YamlLoader::load(rootNodes, lua);
|
return loadAll(rootNodes, lua);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object YamlLoader::load(std::istream& input, const sol::state_view& lua)
|
sol::object loadYaml(std::istream& input, const sol::state_view& lua)
|
||||||
{
|
{
|
||||||
std::vector<YAML::Node> rootNodes = YAML::LoadAll(input);
|
std::vector<YAML::Node> rootNodes = YAML::LoadAll(input);
|
||||||
return load(rootNodes, lua);
|
return loadAll(rootNodes, lua);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::object YamlLoader::load(const std::vector<YAML::Node> rootNodes, const sol::state_view& lua)
|
namespace
|
||||||
{
|
{
|
||||||
if (rootNodes.empty())
|
sol::object loadAll(const std::vector<YAML::Node>& rootNodes, const sol::state_view& lua)
|
||||||
return sol::nil;
|
|
||||||
|
|
||||||
if (rootNodes.size() == 1)
|
|
||||||
return getNode(rootNodes[0], lua, 0);
|
|
||||||
|
|
||||||
sol::table documentsTable(lua, sol::create);
|
|
||||||
for (const auto& root : rootNodes)
|
|
||||||
{
|
{
|
||||||
documentsTable.add(getNode(root, lua, 1));
|
if (rootNodes.empty())
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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<double>()))
|
|
||||||
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:
|
|
||||||
return sol::nil;
|
return sol::nil;
|
||||||
case ScalarType::String:
|
|
||||||
return sol::make_object<std::string>(lua, value);
|
if (rootNodes.size() == 1)
|
||||||
case ScalarType::NotNumber:
|
return getNode(rootNodes[0], lua, 0);
|
||||||
return sol::make_object<double>(lua, std::nan(""));
|
|
||||||
case ScalarType::Infinity:
|
sol::table documentsTable(lua, sol::create);
|
||||||
|
for (const auto& root : rootNodes)
|
||||||
{
|
{
|
||||||
if (!value.empty() && value[0] == '-')
|
documentsTable.add(getNode(root, lua, 1));
|
||||||
return sol::make_object<double>(lua, -std::numeric_limits<double>::infinity());
|
|
||||||
|
|
||||||
return sol::make_object<double>(lua, std::numeric_limits<double>::infinity());
|
|
||||||
}
|
}
|
||||||
case ScalarType::Boolean:
|
|
||||||
|
return documentsTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
if (pair.first.IsMap())
|
||||||
return sol::make_object<bool>(lua, true);
|
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")
|
auto key = getNode(pair.first, lua, depth);
|
||||||
return sol::make_object<bool>(lua, false);
|
if (key.get_type() == sol::type::number && std::isnan(key.as<double>()))
|
||||||
|
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;
|
childTable.add(getNode(child, lua, depth));
|
||||||
|
|
||||||
// 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<int>(lua, result);
|
|
||||||
|
|
||||||
nodeError(node, "Can not read a decimal value '" + value + "'");
|
|
||||||
}
|
}
|
||||||
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
|
case ScalarType::Null:
|
||||||
double result = 0.0;
|
return sol::nil;
|
||||||
bool success = YAML::convert<double>::decode(node, result);
|
case ScalarType::String:
|
||||||
if (success)
|
return sol::make_object<std::string>(lua, value);
|
||||||
return sol::make_object<double>(lua, result);
|
case ScalarType::NotNumber:
|
||||||
|
return sol::make_object<double>(lua, std::nan(""));
|
||||||
|
case ScalarType::Infinity:
|
||||||
|
{
|
||||||
|
if (!value.empty() && value[0] == '-')
|
||||||
|
return sol::make_object<double>(lua, -std::numeric_limits<double>::infinity());
|
||||||
|
|
||||||
nodeError(node, "Can not read a float value '" + value + "'");
|
return sol::make_object<double>(lua, std::numeric_limits<double>::infinity());
|
||||||
}
|
}
|
||||||
case ScalarType::Hexadecimal:
|
case ScalarType::Boolean:
|
||||||
{
|
{
|
||||||
int result = 0;
|
if (Misc::StringUtils::lowerCase(value) == "true")
|
||||||
const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16);
|
return sol::make_object<bool>(lua, true);
|
||||||
if (status.ec == std::errc())
|
|
||||||
return sol::make_object<int>(lua, result);
|
|
||||||
|
|
||||||
nodeError(node, "Can not read a hexadecimal value '" + value + "'");
|
if (Misc::StringUtils::lowerCase(value) == "false")
|
||||||
}
|
return sol::make_object<bool>(lua, false);
|
||||||
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<int>(lua, result);
|
|
||||||
|
|
||||||
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<int>(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<double>::decode(node, result);
|
||||||
|
if (success)
|
||||||
|
return sol::make_object<double>(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<int>(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<int>(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,16 @@
|
||||||
#ifndef COMPONENTS_LUA_YAMLLOADER_H
|
#ifndef COMPONENTS_LUA_YAMLLOADER_H
|
||||||
#define COMPONENTS_LUA_YAMLLOADER_H
|
#define COMPONENTS_LUA_YAMLLOADER_H
|
||||||
|
|
||||||
#include <map>
|
#include <iosfwd>
|
||||||
#include <sol/sol.hpp>
|
#include <string>
|
||||||
#include <stdexcept>
|
|
||||||
#include <yaml-cpp/yaml.h>
|
#include <sol/forward.hpp>
|
||||||
|
|
||||||
namespace LuaUtil
|
namespace LuaUtil
|
||||||
{
|
{
|
||||||
|
sol::object loadYaml(const std::string& input, const sol::state_view& lua);
|
||||||
|
|
||||||
class YamlLoader
|
sol::object loadYaml(std::istream& input, const sol::state_view& lua);
|
||||||
{
|
|
||||||
public:
|
|
||||||
static 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<YAML::Node> 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);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // COMPONENTS_LUA_YAMLLOADER_H
|
#endif // COMPONENTS_LUA_YAMLLOADER_H
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue