diff --git a/src/compiler/compile_json.cc b/src/compiler/compile_json.cc index 3356238f1..635acc1dc 100644 --- a/src/compiler/compile_json.cc +++ b/src/compiler/compile_json.cc @@ -1,42 +1,69 @@ #include -#include // assert -#include // std::visit +#include // assert +#include // std::string_view +#include // std::visit namespace { -auto to_json(const sourcemeta::blaze::Instruction &instruction) +auto to_json(const sourcemeta::blaze::Instruction &instruction, + std::vector &resources) -> sourcemeta::core::JSON { - auto result{sourcemeta::core::JSON::make_object()}; + // Note that we purposely avoid objects to help consumers avoid potentially + // expensive hash-map or flat-map lookups when parsing back + auto result{sourcemeta::core::JSON::make_array()}; + // We use single characters to save space, as this serialised format // is not meant to be human-readable anyway - result.assign("t", sourcemeta::core::to_json(instruction.type)); - result.assign( - "s", sourcemeta::core::to_json(instruction.relative_schema_location)); - result.assign( - "i", sourcemeta::core::to_json(instruction.relative_instance_location)); - result.assign("k", sourcemeta::core::to_json(instruction.keyword_location)); - result.assign("r", sourcemeta::core::to_json(instruction.schema_resource)); - - auto value{sourcemeta::core::JSON::make_object()}; - value.assign("t", sourcemeta::core::to_json(instruction.value.index())); - value.assign("v", std::visit( - [](const auto &variant) { - return sourcemeta::core::to_json(variant); - }, - instruction.value)); - result.assign("v", std::move(value)); - - assert(result.at("v").is_object()); - assert(result.at("v").size() == 2); - assert(result.at("v").defines("t")); - assert(result.at("v").defines("v")); - assert(result.at("v").at("t").is_integer()); - - auto children_json{sourcemeta::core::JSON::make_array()}; - result.assign("c", sourcemeta::core::to_json(instruction.children, - [](const auto &subinstruction) { - return to_json(subinstruction); - })); + result.push_back(sourcemeta::core::to_json(instruction.type)); + + result.push_back( + sourcemeta::core::to_json(instruction.relative_schema_location)); + result.push_back( + sourcemeta::core::to_json(instruction.relative_instance_location)); + + const auto match{instruction.keyword_location.find('#')}; + if (instruction.schema_resource > 0 && match != std::string::npos) { + if (resources.size() < instruction.schema_resource) { + resources.resize(instruction.schema_resource); + } + + if (resources[instruction.schema_resource - 1].empty()) { + resources[instruction.schema_resource - 1] = + instruction.keyword_location.substr(0, match); + } + + result.push_back( + sourcemeta::core::JSON{instruction.keyword_location.substr(match)}); + } else { + result.push_back(sourcemeta::core::to_json(instruction.keyword_location)); + } + + result.push_back(sourcemeta::core::to_json(instruction.schema_resource)); + + // Note that we purposely avoid objects to help consumers avoid potentially + // expensive hash-map or flat-map lookups when parsing back + auto value{sourcemeta::core::JSON::make_array()}; + const auto value_index{instruction.value.index()}; + value.push_back(sourcemeta::core::to_json(value_index)); + // Don't encode empty values, which tend to happen a lot + if (value_index != 0) { + value.push_back(std::visit( + [](const auto &variant) { return sourcemeta::core::to_json(variant); }, + instruction.value)); + } + assert(value.is_array()); + assert(!value.empty()); + assert(value.at(0).is_integer()); + result.push_back(std::move(value)); + + if (!instruction.children.empty()) { + auto children_json{sourcemeta::core::JSON::make_array()}; + result.push_back(sourcemeta::core::to_json( + instruction.children, [&resources](const auto &subinstruction) { + return to_json(subinstruction, resources); + })); + } + return result; } } // namespace @@ -44,14 +71,18 @@ auto to_json(const sourcemeta::blaze::Instruction &instruction) namespace sourcemeta::blaze { auto to_json(const Template &schema_template) -> sourcemeta::core::JSON { - auto result{sourcemeta::core::JSON::make_object()}; - result.assign("dynamic", sourcemeta::core::JSON{schema_template.dynamic}); - result.assign("track", sourcemeta::core::JSON{schema_template.track}); - result.assign("instructions", - sourcemeta::core::to_json(schema_template.instructions, - [](const auto &instruction) { - return ::to_json(instruction); - })); + // Note that we purposely avoid objects to help consumers avoid potentially + // expensive hash-map or flat-map lookups when parsing back + auto result{sourcemeta::core::JSON::make_array()}; + result.push_back(sourcemeta::core::JSON{schema_template.dynamic}); + result.push_back(sourcemeta::core::JSON{schema_template.track}); + std::vector resources; + auto instructions{sourcemeta::core::to_json( + schema_template.instructions, [&resources](const auto &instruction) { + return ::to_json(instruction, resources); + })}; + result.push_back(sourcemeta::core::to_json(resources)); + result.push_back(std::move(instructions)); return result; } diff --git a/src/evaluator/evaluator_json.cc b/src/evaluator/evaluator_json.cc index a41cd0bea..6c887823d 100644 --- a/src/evaluator/evaluator_json.cc +++ b/src/evaluator/evaluator_json.cc @@ -5,41 +5,40 @@ namespace { auto value_from_json(const sourcemeta::core::JSON &wrapper) -> std::optional { - if (!wrapper.is_object()) { + if (!wrapper.is_array() || wrapper.array_size() == 0 || + !wrapper.at(0).is_integer()) { return std::nullopt; + } else if (wrapper.array_size() == 1) { + return sourcemeta::blaze::ValueNone{}; } - const auto type{wrapper.try_at("t")}; - const auto value{wrapper.try_at("v")}; - if (!type || !value) { - return std::nullopt; - } + const auto &value{wrapper.at(1)}; using namespace sourcemeta::blaze; - switch (type->to_integer()) { + switch (wrapper.at(0).to_integer()) { // clang-format off - case 0: return sourcemeta::core::from_json(*value); - case 1: return sourcemeta::core::from_json(*value); - case 2: return sourcemeta::core::from_json(*value); - case 3: return sourcemeta::core::from_json(*value); - case 4: return sourcemeta::core::from_json(*value); - case 5: return sourcemeta::core::from_json(*value); - case 6: return sourcemeta::core::from_json(*value); - case 7: return sourcemeta::core::from_json(*value); - case 8: return sourcemeta::core::from_json(*value); - case 9: return sourcemeta::core::from_json(*value); - case 10: return sourcemeta::core::from_json(*value); - case 11: return sourcemeta::core::from_json(*value); - case 12: return sourcemeta::core::from_json(*value); - case 13: return sourcemeta::core::from_json(*value); - case 14: return sourcemeta::core::from_json(*value); - case 15: return sourcemeta::core::from_json(*value); - case 16: return sourcemeta::core::from_json(*value); - case 17: return sourcemeta::core::from_json(*value); - case 18: return sourcemeta::core::from_json(*value); - case 19: return sourcemeta::core::from_json(*value); - case 20: return sourcemeta::core::from_json(*value); - case 21: return sourcemeta::core::from_json(*value); + case 0: return ValueNone{}; + case 1: return sourcemeta::core::from_json(value); + case 2: return sourcemeta::core::from_json(value); + case 3: return sourcemeta::core::from_json(value); + case 4: return sourcemeta::core::from_json(value); + case 5: return sourcemeta::core::from_json(value); + case 6: return sourcemeta::core::from_json(value); + case 7: return sourcemeta::core::from_json(value); + case 8: return sourcemeta::core::from_json(value); + case 9: return sourcemeta::core::from_json(value); + case 10: return sourcemeta::core::from_json(value); + case 11: return sourcemeta::core::from_json(value); + case 12: return sourcemeta::core::from_json(value); + case 13: return sourcemeta::core::from_json(value); + case 14: return sourcemeta::core::from_json(value); + case 15: return sourcemeta::core::from_json(value); + case 16: return sourcemeta::core::from_json(value); + case 17: return sourcemeta::core::from_json(value); + case 18: return sourcemeta::core::from_json(value); + case 19: return sourcemeta::core::from_json(value); + case 20: return sourcemeta::core::from_json(value); + case 21: return sourcemeta::core::from_json(value); // clang-format on default: assert(false); @@ -47,7 +46,8 @@ auto value_from_json(const sourcemeta::core::JSON &wrapper) } } -auto instructions_from_json(const sourcemeta::core::JSON &instructions) +auto instructions_from_json(const sourcemeta::core::JSON &instructions, + const sourcemeta::core::JSON &resources) -> std::optional { if (!instructions.is_array()) { return std::nullopt; @@ -56,42 +56,36 @@ auto instructions_from_json(const sourcemeta::core::JSON &instructions) sourcemeta::blaze::Instructions result; result.reserve(instructions.size()); for (const auto &instruction : instructions.as_array()) { - if (!instruction.is_object()) { + if (!instruction.is_array() || instruction.array_size() < 6) { return std::nullopt; } - const auto type{instruction.try_at("t")}; - const auto relative_schema_location{instruction.try_at("s")}; - const auto relative_instance_location{instruction.try_at("i")}; - const auto keyword_location{instruction.try_at("k")}; - const auto schema_resource{instruction.try_at("r")}; - const auto value{instruction.try_at("v")}; - const auto children{instruction.try_at("c")}; - - if (!type || !relative_schema_location || !relative_instance_location || - !keyword_location || !schema_resource || !value || !children || - !type->is_positive() || !relative_schema_location->is_string() || - !relative_instance_location->is_string() || - !keyword_location->is_string() || !schema_resource->is_positive() || - !value->is_object() || !children->is_array()) { - return std::nullopt; - } + const auto &type{instruction.at(0)}; + const auto &relative_schema_location{instruction.at(1)}; + const auto &relative_instance_location{instruction.at(2)}; + const auto &keyword_location{instruction.at(3)}; + const auto &schema_resource{instruction.at(4)}; + const auto &value{instruction.at(5)}; auto type_result{ - sourcemeta::core::from_json( - *type)}; + sourcemeta::core::from_json(type)}; auto relative_schema_location_result{ sourcemeta::core::from_json( - *relative_schema_location)}; + relative_schema_location)}; auto relative_instance_location_result{ sourcemeta::core::from_json( - *relative_instance_location)}; + relative_instance_location)}; auto keyword_location_result{ - sourcemeta::core::from_json(*keyword_location)}; + sourcemeta::core::from_json(keyword_location)}; auto schema_resource_result{ - sourcemeta::core::from_json(*schema_resource)}; - auto value_result{value_from_json(*value)}; - auto children_result{instructions_from_json(*children)}; + sourcemeta::core::from_json(schema_resource)}; + auto value_result{value_from_json(value)}; + + // Parse children if there + std::optional children_result{ + instruction.array_size() == 7 + ? instructions_from_json(instruction.at(6), resources) + : sourcemeta::blaze::Instructions{}}; if (!type_result.has_value() || !relative_schema_location_result.has_value() || @@ -102,14 +96,28 @@ auto instructions_from_json(const sourcemeta::core::JSON &instructions) return std::nullopt; } - // TODO: Maybe we should emplace here? - result.push_back({std::move(type_result).value(), - std::move(relative_schema_location_result).value(), - std::move(relative_instance_location_result).value(), - std::move(keyword_location_result).value(), - std::move(schema_resource_result).value(), - std::move(value_result).value(), - std::move(children_result).value()}); + if (schema_resource_result.value() > 0 && + resources.array_size() >= schema_resource_result.value() && + keyword_location_result.value().starts_with('#')) { + // TODO: Maybe we should emplace here? + result.push_back( + {std::move(type_result).value(), + std::move(relative_schema_location_result).value(), + std::move(relative_instance_location_result).value(), + resources.at(schema_resource_result.value() - 1).to_string() + + std::move(keyword_location_result).value(), + schema_resource_result.value(), std::move(value_result).value(), + std::move(children_result).value()}); + } else { + // TODO: Maybe we should emplace here? + result.push_back({std::move(type_result).value(), + std::move(relative_schema_location_result).value(), + std::move(relative_instance_location_result).value(), + std::move(keyword_location_result).value(), + std::move(schema_resource_result).value(), + std::move(value_result).value(), + std::move(children_result).value()}); + } } return result; @@ -120,26 +128,27 @@ auto instructions_from_json(const sourcemeta::core::JSON &instructions) namespace sourcemeta::blaze { auto from_json(const sourcemeta::core::JSON &json) -> std::optional