Skip to content

Commit

Permalink
Add a new LogicalWhenNoAdjacentAnnotations compiler step (#1077)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Aug 31, 2024
1 parent 480064e commit 0c43415
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 46 deletions.
26 changes: 21 additions & 5 deletions src/jsonschema/compile_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ struct DescribeVisitor {
return message.str();
}

if (this->keyword == "then" || this->keyword == "else") {
if (this->keyword == "then") {
assert(!step.children.empty());
std::ostringstream message;
message << "Because of the conditional outcome, the "
Expand Down Expand Up @@ -1566,6 +1566,26 @@ struct DescribeVisitor {
return unknown();
}

auto operator()(const SchemaCompilerLogicalWhenNoAdjacentAnnotations &step)
const -> std::string {
if (this->keyword == "else") {
assert(!step.children.empty());
std::ostringstream message;
message << "Because of the conditional outcome, the "
<< to_string(this->target.type())
<< " value was expected to validate against the ";
if (step.children.size() > 1) {
message << step.children.size() << " given subschemas";
} else {
message << "given subschema";
}

return message.str();
}

return unknown();
}

// These steps are never described, at least not right now

auto
Expand All @@ -1584,10 +1604,6 @@ struct DescribeVisitor {
operator()(const SchemaCompilerAssertionAnnotation &) const -> std::string {
return unknown();
}
auto operator()(const SchemaCompilerAssertionNoAdjacentAnnotation &) const
-> std::string {
return unknown();
}
auto
operator()(const SchemaCompilerLoopPropertiesRegex &) const -> std::string {
return unknown();
Expand Down
47 changes: 32 additions & 15 deletions src/jsonschema/compile_evaluate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -630,21 +630,6 @@ auto evaluate_step(
context.resolve_target<std::set<JSON>>(assertion.target, instance)};
result = target.contains(value);
CALLBACK_POST("SchemaCompilerAssertionAnnotation", assertion);
} else if (std::holds_alternative<
SchemaCompilerAssertionNoAdjacentAnnotation>(step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerAssertionNoAdjacentAnnotation");
const auto &assertion{
std::get<SchemaCompilerAssertionNoAdjacentAnnotation>(step)};
context.push(assertion);
EVALUATE_CONDITION_GUARD("SchemaCompilerAssertionNoAdjacentAnnotation",
assertion, instance);
CALLBACK_PRE(assertion, context.instance_location());
const auto &value{context.resolve_value(assertion.value, instance)};
const auto &target{
context.resolve_target<std::set<JSON>>(assertion.target, instance)};
result = !target.contains(value);
CALLBACK_POST("SchemaCompilerAssertionNoAdjacentAnnotation", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionNoAnnotation>(
step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionNoAnnotation");
Expand Down Expand Up @@ -760,6 +745,37 @@ auto evaluate_step(
}

CALLBACK_POST("SchemaCompilerLogicalWhenDefines", logical);
} else if (std::holds_alternative<
SchemaCompilerLogicalWhenNoAdjacentAnnotations>(step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerLogicalWhenNoAdjacentAnnotations");
const auto &logical{
std::get<SchemaCompilerLogicalWhenNoAdjacentAnnotations>(step)};
context.push(logical);
EVALUATE_CONDITION_GUARD("SchemaCompilerLogicalWhenNoAdjacentAnnotations",
logical, instance);
const auto &value{context.resolve_value(logical.value, instance)};

// TODO: How can we avoid this expensive pointer manipulation?
auto expected_evaluate_path{context.evaluate_path()};
expected_evaluate_path.pop_back();
expected_evaluate_path.push_back({value});
const auto &current_annotations{context.annotations(
context.instance_location(), expected_evaluate_path)};
EVALUATE_IMPLICIT_PRECONDITION(
"SchemaCompilerLogicalWhenNoAdjacentAnnotations", logical,
current_annotations.empty());

CALLBACK_PRE(logical, context.instance_location());
result = true;
for (const auto &child : logical.children) {
if (!evaluate_step(child, instance, mode, callback, context)) {
result = false;
break;
}
}

CALLBACK_POST("SchemaCompilerLogicalWhenNoAdjacentAnnotations", logical);
} else if (std::holds_alternative<SchemaCompilerLogicalXor>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerLogicalXor");
const auto &logical{std::get<SchemaCompilerLogicalXor>(step)};
Expand Down Expand Up @@ -1071,6 +1087,7 @@ auto evaluate_step(
for (const auto &entry : target.as_object()) {
bool apply_children{true};
for (const auto &annotations : current_annotations) {
// TODO: Can we avoid this JSON conversion?
if (annotations.get().contains(JSON{entry.first})) {
apply_children = false;
break;
Expand Down
4 changes: 2 additions & 2 deletions src/jsonschema/compile_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,6 @@ struct StepVisitor {
HANDLE_STEP("assertion", "equals-any", SchemaCompilerAssertionEqualsAny)
HANDLE_STEP("assertion", "size-equal", SchemaCompilerAssertionSizeEqual)
HANDLE_STEP("assertion", "annotation", SchemaCompilerAssertionAnnotation)
HANDLE_STEP("assertion", "no-adjacent-annotation",
SchemaCompilerAssertionNoAdjacentAnnotation)
HANDLE_STEP("assertion", "no-annotation", SchemaCompilerAssertionNoAnnotation)
HANDLE_STEP("annotation", "emit", SchemaCompilerAnnotationEmit)
HANDLE_STEP("annotation", "to-parent", SchemaCompilerAnnotationToParent)
Expand All @@ -267,6 +265,8 @@ struct StepVisitor {
HANDLE_STEP("logical", "not", SchemaCompilerLogicalNot)
HANDLE_STEP("logical", "when-type", SchemaCompilerLogicalWhenType)
HANDLE_STEP("logical", "when-defines", SchemaCompilerLogicalWhenDefines)
HANDLE_STEP("logical", "when-no-adjacent-annotations",
SchemaCompilerLogicalWhenNoAdjacentAnnotations)
HANDLE_STEP("loop", "properties-match", SchemaCompilerLoopPropertiesMatch)
HANDLE_STEP("loop", "properties", SchemaCompilerLoopProperties)
HANDLE_STEP("loop", "properties-regex", SchemaCompilerLoopPropertiesRegex)
Expand Down
15 changes: 5 additions & 10 deletions src/jsonschema/default_compiler_draft7.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,11 @@ auto compiler_draft7_applicator_else(
return {};
}

SchemaCompilerTemplate children{compile(context, schema_context,
relative_dynamic_context,
empty_pointer, empty_pointer)};
SchemaCompilerTemplate condition{
make<SchemaCompilerAssertionNoAdjacentAnnotation>(
false, context, schema_context, relative_dynamic_context, JSON{true},
{}, SchemaCompilerTargetType::AdjacentAnnotations, Pointer{"if"})};
return {make<SchemaCompilerLogicalAnd>(
true, context, schema_context, dynamic_context, SchemaCompilerValueNone{},
std::move(children), std::move(condition))};
return {make<SchemaCompilerLogicalWhenNoAdjacentAnnotations>(
true, context, schema_context, dynamic_context, "if",
compile(context, schema_context, relative_dynamic_context, empty_pointer,
empty_pointer),
SchemaCompilerTemplate{})};
}

} // namespace internal
Expand Down
22 changes: 12 additions & 10 deletions src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,6 @@ struct SchemaCompilerAssertionSizeEqual;
/// annotation was produced
struct SchemaCompilerAssertionAnnotation;

/// @ingroup jsonschema
/// Represents a compiler assertion step that checks a certain
/// annotation was not produced at an adjacent location
struct SchemaCompilerAssertionNoAdjacentAnnotation;

/// @ingroup jsonschema
/// Represents a compiler assertion step that checks a certain
/// annotation was not produced independently of the schema location
Expand Down Expand Up @@ -277,6 +272,11 @@ struct SchemaCompilerLogicalWhenType;
/// when the instance is an object and defines a given property
struct SchemaCompilerLogicalWhenDefines;

/// @ingroup jsonschema
/// Represents a compiler logical step that represents a conjunction
/// when the instance did not receive any certain adjacent annotation
struct SchemaCompilerLogicalWhenNoAdjacentAnnotations;

/// @ingroup jsonschema
/// Represents a compiler step that matches steps to object properties
struct SchemaCompilerLoopPropertiesMatch;
Expand Down Expand Up @@ -345,13 +345,13 @@ using SchemaCompilerTemplate = std::vector<std::variant<
SchemaCompilerAssertionGreater, SchemaCompilerAssertionLess,
SchemaCompilerAssertionUnique, SchemaCompilerAssertionDivisible,
SchemaCompilerAssertionStringType, SchemaCompilerAssertionSizeEqual,
SchemaCompilerAssertionAnnotation,
SchemaCompilerAssertionNoAdjacentAnnotation,
SchemaCompilerAssertionNoAnnotation, SchemaCompilerAnnotationEmit,
SchemaCompilerAnnotationToParent, SchemaCompilerAnnotationBasenameToParent,
SchemaCompilerLogicalOr, SchemaCompilerLogicalAnd, SchemaCompilerLogicalXor,
SchemaCompilerAssertionAnnotation, SchemaCompilerAssertionNoAnnotation,
SchemaCompilerAnnotationEmit, SchemaCompilerAnnotationToParent,
SchemaCompilerAnnotationBasenameToParent, SchemaCompilerLogicalOr,
SchemaCompilerLogicalAnd, SchemaCompilerLogicalXor,
SchemaCompilerLogicalTry, SchemaCompilerLogicalNot,
SchemaCompilerLogicalWhenType, SchemaCompilerLogicalWhenDefines,
SchemaCompilerLogicalWhenNoAdjacentAnnotations,
SchemaCompilerLoopPropertiesMatch, SchemaCompilerLoopProperties,
SchemaCompilerLoopPropertiesRegex,
SchemaCompilerLoopPropertiesNoAdjacentAnnotation, SchemaCompilerLoopKeys,
Expand Down Expand Up @@ -450,6 +450,8 @@ DEFINE_STEP_APPLICATOR(Logical, Try, SchemaCompilerValueNone)
DEFINE_STEP_APPLICATOR(Logical, Not, SchemaCompilerValueNone)
DEFINE_STEP_APPLICATOR(Logical, WhenType, SchemaCompilerValueType)
DEFINE_STEP_APPLICATOR(Logical, WhenDefines, SchemaCompilerValueString)
DEFINE_STEP_APPLICATOR(Logical, WhenNoAdjacentAnnotations,
SchemaCompilerValueString)
DEFINE_STEP_APPLICATOR(Loop, PropertiesMatch, SchemaCompilerValueNamedIndexes)
DEFINE_STEP_APPLICATOR(Loop, Properties, SchemaCompilerValueNone)
DEFINE_STEP_APPLICATOR(Loop, PropertiesRegex, SchemaCompilerValueRegex)
Expand Down
12 changes: 8 additions & 4 deletions test/jsonschema/jsonschema_compile_draft7_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,15 +256,17 @@ TEST(JSONSchema_compile_draft7, else_3) {

EVALUATE_TRACE_PRE(0, LogicalTry, "/if", "#/if", "");
EVALUATE_TRACE_PRE(1, AssertionEqual, "/if/const", "#/if/const", "");
EVALUATE_TRACE_PRE(2, LogicalAnd, "/else", "#/else", "");
EVALUATE_TRACE_PRE(2, LogicalWhenNoAdjacentAnnotations, "/else", "#/else",
"");
EVALUATE_TRACE_PRE(3, AssertionDivisible, "/else/multipleOf",
"#/else/multipleOf", "");

EVALUATE_TRACE_POST_FAILURE(0, AssertionEqual, "/if/const", "#/if/const", "");
EVALUATE_TRACE_POST_SUCCESS(1, LogicalTry, "/if", "#/if", "");
EVALUATE_TRACE_POST_SUCCESS(2, AssertionDivisible, "/else/multipleOf",
"#/else/multipleOf", "");
EVALUATE_TRACE_POST_SUCCESS(3, LogicalAnd, "/else", "#/else", "");
EVALUATE_TRACE_POST_SUCCESS(3, LogicalWhenNoAdjacentAnnotations, "/else",
"#/else", "");

EVALUATE_TRACE_POST_DESCRIBE(
instance, 0,
Expand Down Expand Up @@ -300,15 +302,17 @@ TEST(JSONSchema_compile_draft7, else_4) {

EVALUATE_TRACE_PRE(0, LogicalTry, "/if", "#/if", "");
EVALUATE_TRACE_PRE(1, AssertionEqual, "/if/const", "#/if/const", "");
EVALUATE_TRACE_PRE(2, LogicalAnd, "/else", "#/else", "");
EVALUATE_TRACE_PRE(2, LogicalWhenNoAdjacentAnnotations, "/else", "#/else",
"");
EVALUATE_TRACE_PRE(3, AssertionDivisible, "/else/multipleOf",
"#/else/multipleOf", "");

EVALUATE_TRACE_POST_FAILURE(0, AssertionEqual, "/if/const", "#/if/const", "");
EVALUATE_TRACE_POST_SUCCESS(1, LogicalTry, "/if", "#/if", "");
EVALUATE_TRACE_POST_FAILURE(2, AssertionDivisible, "/else/multipleOf",
"#/else/multipleOf", "");
EVALUATE_TRACE_POST_FAILURE(3, LogicalAnd, "/else", "#/else", "");
EVALUATE_TRACE_POST_FAILURE(3, LogicalWhenNoAdjacentAnnotations, "/else",
"#/else", "");

EVALUATE_TRACE_POST_DESCRIBE(
instance, 0,
Expand Down

4 comments on commit 0c43415

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (macos)

Benchmark suite Current: 0c43415 Previous: 480064e Ratio
JSONSchema_Compile_Basic 195940.84611085083 ns/iter 227165.89116716702 ns/iter 0.86
JSONSchema_Validate_Draft4_Meta_1_No_Callback 6037.750789703864 ns/iter 6592.511059077348 ns/iter 0.92
JSONSchema_Validate_Draft4_Required_Properties 3446.89567948083 ns/iter 3754.8350718569322 ns/iter 0.92
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 1278.2144624672735 ns/iter 1356.523137438307 ns/iter 0.94
JSONSchema_Validate_Draft4_Items_Schema 9479.108532344015 ns/iter 9108.107636411585 ns/iter 1.04

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (linux/llvm)

Benchmark suite Current: 0c43415 Previous: 480064e Ratio
JSONSchema_Compile_Basic 395421.90715885034 ns/iter 386266.53455964563 ns/iter 1.02
JSONSchema_Validate_Draft4_Meta_1_No_Callback 6088.930098526194 ns/iter 5989.054565142286 ns/iter 1.02
JSONSchema_Validate_Draft4_Required_Properties 3262.718518310988 ns/iter 3277.5309923937107 ns/iter 1.00
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 804.2761404600207 ns/iter 786.4345209741658 ns/iter 1.02
JSONSchema_Validate_Draft4_Items_Schema 10869.298311473403 ns/iter 11228.439590213111 ns/iter 0.97

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (linux/gcc)

Benchmark suite Current: 0c43415 Previous: 480064e Ratio
JSONSchema_Validate_Draft4_Meta_1_No_Callback 6264.41489605974 ns/iter 6257.373380736289 ns/iter 1.00
JSONSchema_Validate_Draft4_Required_Properties 3497.9399046706426 ns/iter 3477.9908922972154 ns/iter 1.01
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 807.3775417655877 ns/iter 822.1333332551211 ns/iter 0.98
JSONSchema_Validate_Draft4_Items_Schema 12006.24987120015 ns/iter 12053.86300426822 ns/iter 1.00
JSONSchema_Compile_Basic 389587.60222221323 ns/iter 385088.9200220885 ns/iter 1.01

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (windows)

Benchmark suite Current: 0c43415 Previous: 480064e Ratio
JSONSchema_Compile_Basic 782294.1964286758 ns/iter 789299.2187498647 ns/iter 0.99
JSONSchema_Validate_Draft4_Meta_1_No_Callback 13045.937964560395 ns/iter 13027.503571428108 ns/iter 1.00
JSONSchema_Validate_Draft4_Required_Properties 5368.115999999645 ns/iter 5295.269642857647 ns/iter 1.01
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 2295.0265624999133 ns/iter 2302.3993276791516 ns/iter 1.00
JSONSchema_Validate_Draft4_Items_Schema 25981.262095396818 ns/iter 26561.051872647815 ns/iter 0.98

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.