diff --git a/src/compiler/compile_describe.cc b/src/compiler/compile_describe.cc index ba052bbb..df8b779b 100644 --- a/src/compiler/compile_describe.cc +++ b/src/compiler/compile_describe.cc @@ -753,6 +753,12 @@ struct DescribeVisitor { return message.str(); } + auto operator()(const LoopItems &) const -> std::string { + assert(this->target.is_array()); + return "Every item in the array value was expected to validate against the " + "given subschema"; + } + auto operator()(const LoopItemsFrom &step) const -> std::string { assert(this->target.is_array()); const auto &value{step_value(step)}; diff --git a/src/compiler/compile_json.cc b/src/compiler/compile_json.cc index b0570160..b922ae8e 100644 --- a/src/compiler/compile_json.cc +++ b/src/compiler/compile_json.cc @@ -294,6 +294,7 @@ struct StepVisitor { HANDLE_STEP("loop", "properties-type-strict-evaluate-any", LoopPropertiesTypeStrictAnyEvaluate) HANDLE_STEP("loop", "keys", LoopKeys) + HANDLE_STEP("loop", "items", LoopItems) HANDLE_STEP("loop", "items-from", LoopItemsFrom) HANDLE_STEP("loop", "items-unevaluated", LoopItemsUnevaluated) HANDLE_STEP("loop", "items-type", LoopItemsType) diff --git a/src/compiler/default_compiler_draft4.h b/src/compiler/default_compiler_draft4.h index a31b48be..18f3d333 100644 --- a/src/compiler/default_compiler_draft4.h +++ b/src/compiler/default_compiler_draft4.h @@ -1229,9 +1229,9 @@ auto compiler_draft4_applicator_items_with_options( Instructions children; if (!subchildren.empty()) { - children.push_back(make( - context, schema_context, dynamic_context, ValueUnsignedInteger{0}, - std::move(subchildren))); + children.push_back(make(context, schema_context, + dynamic_context, ValueNone{}, + std::move(subchildren))); } if (!annotate && !track_evaluation) { @@ -1288,8 +1288,8 @@ auto compiler_draft4_applicator_items_with_options( } } - return {make(context, schema_context, dynamic_context, - ValueUnsignedInteger{0}, std::move(children))}; + return {make(context, schema_context, dynamic_context, + ValueNone{}, std::move(children))}; } return compiler_draft4_applicator_items_array( diff --git a/src/evaluator/dispatch.inc.h b/src/evaluator/dispatch.inc.h index 511bd32a..4aae504f 100644 --- a/src/evaluator/dispatch.inc.h +++ b/src/evaluator/dispatch.inc.h @@ -1221,6 +1221,46 @@ switch (static_cast(instruction.index())) { EVALUATE_END(loop, LoopKeys); } + case IS_INSTRUCTION(LoopItems): { + EVALUATE_BEGIN(loop, LoopItems, target.is_array()); + assert(!loop.children.empty()); + result = true; + + // To avoid index lookups and unnecessary conditionals +#ifdef SOURCEMETA_EVALUATOR_FAST + for (const auto &new_instance : target.as_array()) { + for (const auto &child : loop.children) { + if (!EVALUATE_RECURSE(child, new_instance)) { + result = false; + EVALUATE_END(loop, LoopItemsFrom); + } + } + } +#else + for (std::size_t index = 0; index < target.size(); index++) { + if (track) { + context.instance_location.push_back(index); + } + const auto &new_instance{target.at(index)}; + for (const auto &child : loop.children) { + if (!EVALUATE_RECURSE(child, new_instance)) { + result = false; + if (track) { + context.instance_location.pop_back(); + } + EVALUATE_END(loop, LoopItemsFrom); + } + } + + if (track) { + context.instance_location.pop_back(); + } + } +#endif + + EVALUATE_END(loop, LoopItems); + } + case IS_INSTRUCTION(LoopItemsFrom): { EVALUATE_BEGIN(loop, LoopItemsFrom, target.is_array() && loop.value < target.size()); diff --git a/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h b/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h index 291a2562..12d3fbd6 100644 --- a/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h +++ b/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h @@ -79,6 +79,7 @@ struct LoopPropertiesTypeStrictEvaluate; struct LoopPropertiesTypeStrictAny; struct LoopPropertiesTypeStrictAnyEvaluate; struct LoopKeys; +struct LoopItems; struct LoopItemsFrom; struct LoopItemsUnevaluated; struct LoopItemsType; @@ -119,7 +120,7 @@ using Instruction = std::variant< LoopPropertiesStartsWith, LoopPropertiesExcept, LoopPropertiesWhitelist, LoopPropertiesType, LoopPropertiesTypeEvaluate, LoopPropertiesTypeStrict, LoopPropertiesTypeStrictEvaluate, LoopPropertiesTypeStrictAny, - LoopPropertiesTypeStrictAnyEvaluate, LoopKeys, LoopItemsFrom, + LoopPropertiesTypeStrictAnyEvaluate, LoopKeys, LoopItems, LoopItemsFrom, LoopItemsUnevaluated, LoopItemsType, LoopItemsTypeStrict, LoopItemsTypeStrictAny, LoopContains, ControlGroup, ControlGroupWhenDefines, ControlLabel, ControlMark, ControlEvaluate, ControlJump, @@ -194,6 +195,7 @@ enum class InstructionIndex : std::uint8_t { LoopPropertiesTypeStrictAny, LoopPropertiesTypeStrictAnyEvaluate, LoopKeys, + LoopItems, LoopItemsFrom, LoopItemsUnevaluated, LoopItemsType, @@ -558,6 +560,10 @@ DEFINE_STEP_WITH_VALUE(Loop, PropertiesTypeStrictAnyEvaluate, ValueTypes) /// @brief Represents a compiler step that loops over object property keys DEFINE_STEP_APPLICATOR(Loop, Keys, ValueNone) +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over array items +DEFINE_STEP_APPLICATOR(Loop, Items, ValueNone) + /// @ingroup evaluator_instructions /// @brief Represents a compiler step that loops over array items starting from /// a given index diff --git a/test/evaluator/evaluator_2019_09_test.cc b/test/evaluator/evaluator_2019_09_test.cc index 1426ca40..828d682a 100644 --- a/test/evaluator/evaluator_2019_09_test.cc +++ b/test/evaluator/evaluator_2019_09_test.cc @@ -1854,7 +1854,7 @@ TEST(Evaluator_2019_09, items_2_exhaustive) { sourcemeta::jsontoolkit::parse("[ \"foo\", \"bar\", \"baz\" ]")}; EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 6); - EVALUATE_TRACE_PRE(0, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_PRE(0, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/items/type", "#/items/type", "/0"); EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/items/type", "#/items/type", @@ -1870,7 +1870,7 @@ TEST(Evaluator_2019_09, items_2_exhaustive) { "#/items/type", "/1"); EVALUATE_TRACE_POST_SUCCESS(2, AssertionTypeStrict, "/items/type", "#/items/type", "/2"); - EVALUATE_TRACE_POST_SUCCESS(3, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_POST_SUCCESS(3, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_POST_ANNOTATION(4, "/items", "#/items", "", true); EVALUATE_TRACE_POST_SUCCESS(5, LogicalWhenType, "/items", "#/items", ""); @@ -1921,7 +1921,7 @@ TEST(Evaluator_2019_09, items_3_exhaustive) { sourcemeta::jsontoolkit::parse("[ \"foo\", 5, \"baz\" ]")}; EVALUATE_WITH_TRACE_EXHAUSTIVE_FAILURE(schema, instance, 3); - EVALUATE_TRACE_PRE(0, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_PRE(0, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/items/type", "#/items/type", "/0"); EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/items/type", "#/items/type", @@ -1931,7 +1931,7 @@ TEST(Evaluator_2019_09, items_3_exhaustive) { "#/items/type", "/0"); EVALUATE_TRACE_POST_FAILURE(1, AssertionTypeStrict, "/items/type", "#/items/type", "/1"); - EVALUATE_TRACE_POST_FAILURE(2, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_POST_FAILURE(2, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_POST_DESCRIBE(instance, 0, "The value was expected to be of type string"); @@ -2273,7 +2273,7 @@ TEST(Evaluator_2019_09, additionalItems_2_exhaustive) { sourcemeta::jsontoolkit::parse("[ \"foo\", \"bar\", \"baz\" ]")}; EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 6); - EVALUATE_TRACE_PRE(0, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_PRE(0, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/items/type", "#/items/type", "/0"); EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/items/type", "#/items/type", @@ -2289,7 +2289,7 @@ TEST(Evaluator_2019_09, additionalItems_2_exhaustive) { "#/items/type", "/1"); EVALUATE_TRACE_POST_SUCCESS(2, AssertionTypeStrict, "/items/type", "#/items/type", "/2"); - EVALUATE_TRACE_POST_SUCCESS(3, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_POST_SUCCESS(3, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_POST_ANNOTATION(4, "/items", "#/items", "", true); EVALUATE_TRACE_POST_SUCCESS(5, LogicalWhenType, "/items", "#/items", ""); @@ -3861,7 +3861,7 @@ TEST(Evaluator_2019_09, unevaluatedItems_3_exhaustive) { EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 4); - EVALUATE_TRACE_PRE(0, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_PRE(0, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/items/type", "#/items/type", "/0"); EVALUATE_TRACE_PRE(2, LogicalWhenType, "/items", "#/items", ""); @@ -3869,7 +3869,7 @@ TEST(Evaluator_2019_09, unevaluatedItems_3_exhaustive) { EVALUATE_TRACE_POST_SUCCESS(0, AssertionTypeStrict, "/items/type", "#/items/type", "/0"); - EVALUATE_TRACE_POST_SUCCESS(1, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_POST_SUCCESS(1, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_POST_ANNOTATION(2, "/items", "#/items", "", true); EVALUATE_TRACE_POST_SUCCESS(3, LogicalWhenType, "/items", "#/items", ""); @@ -4595,7 +4595,7 @@ TEST(Evaluator_2019_09, unevaluatedItems_11) { EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 6); EVALUATE_TRACE_PRE(0, LogicalOr, "/anyOf", "#/anyOf", ""); - EVALUATE_TRACE_PRE(1, LoopItemsFrom, "/anyOf/0/items", "#/anyOf/0/items", ""); + EVALUATE_TRACE_PRE(1, LoopItems, "/anyOf/0/items", "#/anyOf/0/items", ""); EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/anyOf/0/items/type", "#/anyOf/0/items/type", "/0"); EVALUATE_TRACE_PRE(3, AssertionTypeStrict, "/anyOf/0/items/type", @@ -4609,8 +4609,8 @@ TEST(Evaluator_2019_09, unevaluatedItems_11) { "#/anyOf/0/items/type", "/0"); EVALUATE_TRACE_POST_FAILURE(1, AssertionTypeStrict, "/anyOf/0/items/type", "#/anyOf/0/items/type", "/1"); - EVALUATE_TRACE_POST_FAILURE(2, LoopItemsFrom, "/anyOf/0/items", - "#/anyOf/0/items", ""); + EVALUATE_TRACE_POST_FAILURE(2, LoopItems, "/anyOf/0/items", "#/anyOf/0/items", + ""); EVALUATE_TRACE_POST_SUCCESS(3, LogicalOr, "/anyOf", "#/anyOf", ""); EVALUATE_TRACE_POST_FAILURE(4, AssertionFail, "/unevaluatedItems", "#/unevaluatedItems", "/0"); @@ -4655,7 +4655,7 @@ TEST(Evaluator_2019_09, unevaluatedItems_11_exhaustive) { EVALUATE_WITH_TRACE_EXHAUSTIVE_FAILURE(schema, instance, 6); EVALUATE_TRACE_PRE(0, LogicalOr, "/anyOf", "#/anyOf", ""); - EVALUATE_TRACE_PRE(1, LoopItemsFrom, "/anyOf/0/items", "#/anyOf/0/items", ""); + EVALUATE_TRACE_PRE(1, LoopItems, "/anyOf/0/items", "#/anyOf/0/items", ""); EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/anyOf/0/items/type", "#/anyOf/0/items/type", "/0"); EVALUATE_TRACE_PRE(3, AssertionTypeStrict, "/anyOf/0/items/type", @@ -4669,8 +4669,8 @@ TEST(Evaluator_2019_09, unevaluatedItems_11_exhaustive) { "#/anyOf/0/items/type", "/0"); EVALUATE_TRACE_POST_FAILURE(1, AssertionTypeStrict, "/anyOf/0/items/type", "#/anyOf/0/items/type", "/1"); - EVALUATE_TRACE_POST_FAILURE(2, LoopItemsFrom, "/anyOf/0/items", - "#/anyOf/0/items", ""); + EVALUATE_TRACE_POST_FAILURE(2, LoopItems, "/anyOf/0/items", "#/anyOf/0/items", + ""); EVALUATE_TRACE_POST_SUCCESS(3, LogicalOr, "/anyOf", "#/anyOf", ""); EVALUATE_TRACE_POST_FAILURE(4, AssertionFail, "/unevaluatedItems", "#/unevaluatedItems", "/0"); @@ -5205,7 +5205,7 @@ TEST(Evaluator_2019_09, recursiveRef_5) { EVALUATE_WITH_TRACE_FAST_SUCCESS(schema, instance, 4); EVALUATE_TRACE_PRE(0, ControlMark, "", "https://example.com/nested", ""); - EVALUATE_TRACE_PRE(1, LoopItemsFrom, "/items", + EVALUATE_TRACE_PRE(1, LoopItems, "/items", "https://example.com/schema#/items", ""); EVALUATE_TRACE_PRE(2, AssertionArrayPrefix, "/items/items", "https://example.com/nested#/items", "/0"); @@ -5220,7 +5220,7 @@ TEST(Evaluator_2019_09, recursiveRef_5) { "https://example.com/nested#/items/0/$recursiveRef", "/0/0"); EVALUATE_TRACE_POST_SUCCESS(2, AssertionArrayPrefix, "/items/items", "https://example.com/nested#/items", "/0"); - EVALUATE_TRACE_POST_SUCCESS(3, LoopItemsFrom, "/items", + EVALUATE_TRACE_POST_SUCCESS(3, LoopItems, "/items", "https://example.com/schema#/items", ""); EVALUATE_TRACE_POST_DESCRIBE(instance, 0, @@ -5259,7 +5259,7 @@ TEST(Evaluator_2019_09, recursiveRef_5_exhaustive) { EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 8); EVALUATE_TRACE_PRE(0, ControlMark, "", "https://example.com/nested", ""); - EVALUATE_TRACE_PRE(1, LoopItemsFrom, "/items", + EVALUATE_TRACE_PRE(1, LoopItems, "/items", "https://example.com/schema#/items", ""); EVALUATE_TRACE_PRE(2, AssertionArrayPrefix, "/items/items", "https://example.com/nested#/items", "/0"); @@ -5286,7 +5286,7 @@ TEST(Evaluator_2019_09, recursiveRef_5_exhaustive) { 3, "/items/items", "https://example.com/nested#/items", "/0", true); EVALUATE_TRACE_POST_SUCCESS(4, AssertionArrayPrefix, "/items/items", "https://example.com/nested#/items", "/0"); - EVALUATE_TRACE_POST_SUCCESS(5, LoopItemsFrom, "/items", + EVALUATE_TRACE_POST_SUCCESS(5, LoopItems, "/items", "https://example.com/schema#/items", ""); EVALUATE_TRACE_POST_ANNOTATION(6, "/items", "https://example.com/schema#/items", "", true); diff --git a/test/evaluator/evaluator_draft4_test.cc b/test/evaluator/evaluator_draft4_test.cc index c42b8682..60d58096 100644 --- a/test/evaluator/evaluator_draft4_test.cc +++ b/test/evaluator/evaluator_draft4_test.cc @@ -1253,7 +1253,7 @@ TEST(Evaluator_draft4, properties_11) { EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/properties/bar/type", "#/properties/bar/type", "/bar"); - EVALUATE_TRACE_PRE(2, LoopItemsFrom, "/properties/foo/items", + EVALUATE_TRACE_PRE(2, LoopItems, "/properties/foo/items", "#/properties/foo/items", "/foo"); EVALUATE_TRACE_PRE(3, AssertionLessEqual, "/properties/foo/items/maximum", "#/properties/foo/items/maximum", "/foo/0"); @@ -1275,7 +1275,7 @@ TEST(Evaluator_draft4, properties_11) { EVALUATE_TRACE_POST_SUCCESS(4, AssertionTypeStrictAny, "/properties/foo/items/type", "#/properties/foo/items/type", "/foo/0"); - EVALUATE_TRACE_POST_SUCCESS(5, LoopItemsFrom, "/properties/foo/items", + EVALUATE_TRACE_POST_SUCCESS(5, LoopItems, "/properties/foo/items", "#/properties/foo/items", "/foo"); EVALUATE_TRACE_POST_DESCRIBE(instance, 0, @@ -2971,7 +2971,7 @@ TEST(Evaluator_draft4, items_2_exhaustive) { sourcemeta::jsontoolkit::parse("[ \"foo\", \"bar\", \"baz\" ]")}; EVALUATE_WITH_TRACE_EXHAUSTIVE_SUCCESS(schema, instance, 4); - EVALUATE_TRACE_PRE(0, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_PRE(0, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/items/type", "#/items/type", "/0"); EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/items/type", "#/items/type", @@ -2985,7 +2985,7 @@ TEST(Evaluator_draft4, items_2_exhaustive) { "#/items/type", "/1"); EVALUATE_TRACE_POST_SUCCESS(2, AssertionTypeStrict, "/items/type", "#/items/type", "/2"); - EVALUATE_TRACE_POST_SUCCESS(3, LoopItemsFrom, "/items", "#/items", ""); + EVALUATE_TRACE_POST_SUCCESS(3, LoopItems, "/items", "#/items", ""); EVALUATE_TRACE_POST_DESCRIBE(instance, 0, "The value was expected to be of type string"); @@ -3199,7 +3199,7 @@ TEST(Evaluator_draft4, items_10) { EVALUATE_WITH_TRACE_FAST_FAILURE(schema, instance, 2); - EVALUATE_TRACE_PRE(0, LoopItemsFrom, "/properties/features/items", + EVALUATE_TRACE_PRE(0, LoopItems, "/properties/features/items", "#/properties/features/items", "/features"); EVALUATE_TRACE_PRE(1, AssertionTypeStrict, "/properties/features/items/properties/geometry/type", @@ -3211,7 +3211,7 @@ TEST(Evaluator_draft4, items_10) { "/properties/features/items/properties/geometry/type", "#/properties/features/items/properties/geometry/type", "/features/0/geometry"); - EVALUATE_TRACE_POST_FAILURE(1, LoopItemsFrom, "/properties/features/items", + EVALUATE_TRACE_POST_FAILURE(1, LoopItems, "/properties/features/items", "#/properties/features/items", "/features"); EVALUATE_TRACE_POST_DESCRIBE(