Skip to content

Commit

Permalink
Implement a new LogicalWhenAdjacentAnnotations compiler step (#1078)
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 0c43415 commit adf7305
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 82 deletions.
39 changes: 20 additions & 19 deletions src/jsonschema/compile_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,6 @@ struct DescribeVisitor {
return message.str();
}

if (this->keyword == "then") {
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();
}

if (this->keyword == "properties") {
assert(!step.children.empty());
assert(this->target.is_object());
Expand Down Expand Up @@ -1586,6 +1571,26 @@ struct DescribeVisitor {
return unknown();
}

auto operator()(const SchemaCompilerLogicalWhenAdjacentAnnotations &step)
const -> std::string {
if (this->keyword == "then") {
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 @@ -1601,10 +1606,6 @@ struct DescribeVisitor {
return unknown();
}
auto
operator()(const SchemaCompilerAssertionAnnotation &) const -> std::string {
return unknown();
}
auto
operator()(const SchemaCompilerLoopPropertiesRegex &) const -> std::string {
return unknown();
}
Expand Down
57 changes: 32 additions & 25 deletions src/jsonschema/compile_evaluate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,7 @@ class EvaluationContext {
using namespace sourcemeta::jsontoolkit;

// An optimization for efficiently accessing annotations
if constexpr (std::is_same_v<Annotations, T>) {
const auto schema_location{
this->evaluate_path().initial().concat(target.second)};
assert(target.first != SchemaCompilerTargetType::ParentAnnotations);
if (target.first == SchemaCompilerTargetType::ParentAdjacentAnnotations) {
// TODO: This involves expensive pointer copies, allocations, and
// destructions
return this->annotations(this->instance_location().initial(),
schema_location);
} else {
return this->annotations(this->instance_location(), schema_location);
}
} else if constexpr (std::is_same_v<InstanceAnnotations, T>) {
if constexpr (std::is_same_v<InstanceAnnotations, T>) {
if (target.first == SchemaCompilerTargetType::ParentAnnotations) {
// TODO: This involves expensive pointer copies, allocations, and
// destructions
Expand Down Expand Up @@ -618,18 +606,6 @@ auto evaluate_step(
}

CALLBACK_POST("SchemaCompilerAssertionStringType", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionAnnotation>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionAnnotation");
const auto &assertion{std::get<SchemaCompilerAssertionAnnotation>(step)};
context.push(assertion);
EVALUATE_CONDITION_GUARD("SchemaCompilerAssertionAnnotation", 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("SchemaCompilerAssertionAnnotation", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionNoAnnotation>(
step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionNoAnnotation");
Expand Down Expand Up @@ -776,6 +752,37 @@ auto evaluate_step(
}

CALLBACK_POST("SchemaCompilerLogicalWhenNoAdjacentAnnotations", logical);
} else if (std::holds_alternative<
SchemaCompilerLogicalWhenAdjacentAnnotations>(step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerLogicalWhenAdjacentAnnotations");
const auto &logical{
std::get<SchemaCompilerLogicalWhenAdjacentAnnotations>(step)};
context.push(logical);
EVALUATE_CONDITION_GUARD("SchemaCompilerLogicalWhenAdjacentAnnotations",
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(
"SchemaCompilerLogicalWhenAdjacentAnnotations", 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("SchemaCompilerLogicalWhenAdjacentAnnotations", logical);
} else if (std::holds_alternative<SchemaCompilerLogicalXor>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerLogicalXor");
const auto &logical{std::get<SchemaCompilerLogicalXor>(step)};
Expand Down
9 changes: 2 additions & 7 deletions src/jsonschema/compile_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ auto target_to_json(const sourcemeta::jsontoolkit::SchemaCompilerTarget &target)
case SchemaCompilerTargetType::InstanceBasename:
result.assign("type", JSON{"instance-basename"});
return result;
case SchemaCompilerTargetType::AdjacentAnnotations:
result.assign("type", JSON{"adjacent-annotations"});
return result;
case SchemaCompilerTargetType::ParentAdjacentAnnotations:
result.assign("type", JSON{"parent-adjacent-annotations"});
return result;
case SchemaCompilerTargetType::ParentAnnotations:
result.assign("type", JSON{"parent-annotations"});
return result;
Expand Down Expand Up @@ -252,7 +246,6 @@ struct StepVisitor {
HANDLE_STEP("assertion", "string-type", SchemaCompilerAssertionStringType)
HANDLE_STEP("assertion", "equals-any", SchemaCompilerAssertionEqualsAny)
HANDLE_STEP("assertion", "size-equal", SchemaCompilerAssertionSizeEqual)
HANDLE_STEP("assertion", "annotation", SchemaCompilerAssertionAnnotation)
HANDLE_STEP("assertion", "no-annotation", SchemaCompilerAssertionNoAnnotation)
HANDLE_STEP("annotation", "emit", SchemaCompilerAnnotationEmit)
HANDLE_STEP("annotation", "to-parent", SchemaCompilerAnnotationToParent)
Expand All @@ -267,6 +260,8 @@ struct StepVisitor {
HANDLE_STEP("logical", "when-defines", SchemaCompilerLogicalWhenDefines)
HANDLE_STEP("logical", "when-no-adjacent-annotations",
SchemaCompilerLogicalWhenNoAdjacentAnnotations)
HANDLE_STEP("logical", "when-adjacent-annotations",
SchemaCompilerLogicalWhenAdjacentAnnotations)
HANDLE_STEP("loop", "properties-match", SchemaCompilerLoopPropertiesMatch)
HANDLE_STEP("loop", "properties", SchemaCompilerLoopProperties)
HANDLE_STEP("loop", "properties-regex", SchemaCompilerLoopPropertiesRegex)
Expand Down
18 changes: 9 additions & 9 deletions src/jsonschema/default_compiler_draft7.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ auto compiler_draft7_applicator_if(
children.push_back(make<SchemaCompilerAnnotationEmit>(
true, context, schema_context, relative_dynamic_context, JSON{true}, {},
SchemaCompilerTargetType::Instance));
// TODO: Make this a "try-and-mark" step that internally emits the annotation
// Maybe then we make the other rules about "marked", "unmarked", without
// pointing out the fact that annotations are produced? It would also
// avoid emitting this unnecessary annotation
return {make<SchemaCompilerLogicalTry>(
true, context, schema_context, dynamic_context, SchemaCompilerValueNone{},
std::move(children), SchemaCompilerTemplate{})};
Expand All @@ -37,15 +41,11 @@ auto compiler_draft7_applicator_then(
return {};
}

SchemaCompilerTemplate children{compile(context, schema_context,
relative_dynamic_context,
empty_pointer, empty_pointer)};
SchemaCompilerTemplate condition{make<SchemaCompilerAssertionAnnotation>(
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<SchemaCompilerLogicalWhenAdjacentAnnotations>(
true, context, schema_context, dynamic_context, "if",
compile(context, schema_context, relative_dynamic_context, empty_pointer,
empty_pointer),
SchemaCompilerTemplate{})};
}

auto compiler_draft7_applicator_else(
Expand Down
32 changes: 12 additions & 20 deletions src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ enum class SchemaCompilerTargetType {
/// The last path (i.e. property or index) of the instance location
InstanceBasename,

/// The annotations produced at the same base evaluation path for the
/// current instance location
AdjacentAnnotations,

/// The annotations produced at the same base evaluation path for the parent
/// of the current instance location
ParentAdjacentAnnotations,

/// The annotations produced for the parent of the current instance location
ParentAnnotations,

Expand Down Expand Up @@ -218,11 +210,6 @@ struct SchemaCompilerAssertionStringType;
/// respectively
struct SchemaCompilerAssertionSizeEqual;

/// @ingroup jsonschema
/// Represents a compiler assertion step that checks a certain
/// annotation was produced
struct SchemaCompilerAssertionAnnotation;

/// @ingroup jsonschema
/// Represents a compiler assertion step that checks a certain
/// annotation was not produced independently of the schema location
Expand Down Expand Up @@ -274,9 +261,14 @@ struct SchemaCompilerLogicalWhenDefines;

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

/// @ingroup jsonschema
/// Represents a compiler logical step that represents a conjunction
/// when the instance received any adjacent annotation
struct SchemaCompilerLogicalWhenAdjacentAnnotations;

/// @ingroup jsonschema
/// Represents a compiler step that matches steps to object properties
struct SchemaCompilerLoopPropertiesMatch;
Expand Down Expand Up @@ -345,13 +337,13 @@ using SchemaCompilerTemplate = std::vector<std::variant<
SchemaCompilerAssertionGreater, SchemaCompilerAssertionLess,
SchemaCompilerAssertionUnique, SchemaCompilerAssertionDivisible,
SchemaCompilerAssertionStringType, SchemaCompilerAssertionSizeEqual,
SchemaCompilerAssertionAnnotation, SchemaCompilerAssertionNoAnnotation,
SchemaCompilerAnnotationEmit, SchemaCompilerAnnotationToParent,
SchemaCompilerAnnotationBasenameToParent, SchemaCompilerLogicalOr,
SchemaCompilerLogicalAnd, SchemaCompilerLogicalXor,
SchemaCompilerAssertionNoAnnotation, SchemaCompilerAnnotationEmit,
SchemaCompilerAnnotationToParent, SchemaCompilerAnnotationBasenameToParent,
SchemaCompilerLogicalOr, SchemaCompilerLogicalAnd, SchemaCompilerLogicalXor,
SchemaCompilerLogicalTry, SchemaCompilerLogicalNot,
SchemaCompilerLogicalWhenType, SchemaCompilerLogicalWhenDefines,
SchemaCompilerLogicalWhenNoAdjacentAnnotations,
SchemaCompilerLogicalWhenAdjacentAnnotations,
SchemaCompilerLoopPropertiesMatch, SchemaCompilerLoopProperties,
SchemaCompilerLoopPropertiesRegex,
SchemaCompilerLoopPropertiesNoAdjacentAnnotation, SchemaCompilerLoopKeys,
Expand Down Expand Up @@ -435,8 +427,6 @@ DEFINE_STEP_WITH_VALUE(Assertion, Unique, SchemaCompilerValueNone)
DEFINE_STEP_WITH_VALUE(Assertion, Divisible, SchemaCompilerValueJSON)
DEFINE_STEP_WITH_VALUE(Assertion, StringType, SchemaCompilerValueStringType)
DEFINE_STEP_WITH_VALUE(Assertion, SizeEqual, SchemaCompilerValueUnsignedInteger)
DEFINE_STEP_WITH_VALUE(Assertion, Annotation, SchemaCompilerValueJSON)
DEFINE_STEP_WITH_VALUE(Assertion, NoAdjacentAnnotation, SchemaCompilerValueJSON)
DEFINE_STEP_WITH_VALUE_AND_DATA(Assertion, NoAnnotation,
SchemaCompilerValueJSON,
SchemaCompilerValueStrings)
Expand All @@ -452,6 +442,8 @@ DEFINE_STEP_APPLICATOR(Logical, WhenType, SchemaCompilerValueType)
DEFINE_STEP_APPLICATOR(Logical, WhenDefines, SchemaCompilerValueString)
DEFINE_STEP_APPLICATOR(Logical, WhenNoAdjacentAnnotations,
SchemaCompilerValueString)
DEFINE_STEP_APPLICATOR(Logical, WhenAdjacentAnnotations,
SchemaCompilerValueString)
DEFINE_STEP_APPLICATOR(Loop, PropertiesMatch, SchemaCompilerValueNamedIndexes)
DEFINE_STEP_APPLICATOR(Loop, Properties, SchemaCompilerValueNone)
DEFINE_STEP_APPLICATOR(Loop, PropertiesRegex, SchemaCompilerValueRegex)
Expand Down
5 changes: 3 additions & 2 deletions test/jsonschema/jsonschema_compile_draft7_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ TEST(JSONSchema_compile_draft7, then_2) {
EVALUATE_TRACE_PRE(0, LogicalTry, "/if", "#/if", "");
EVALUATE_TRACE_PRE(1, AssertionEqual, "/if/const", "#/if/const", "");
EVALUATE_TRACE_PRE_ANNOTATION(2, "/if", "#/if", "");
EVALUATE_TRACE_PRE(3, LogicalAnd, "/then", "#/then", "");
EVALUATE_TRACE_PRE(3, LogicalWhenAdjacentAnnotations, "/then", "#/then", "");
EVALUATE_TRACE_PRE(4, AssertionDivisible, "/then/multipleOf",
"#/then/multipleOf", "");

Expand All @@ -133,7 +133,8 @@ TEST(JSONSchema_compile_draft7, then_2) {
EVALUATE_TRACE_POST_SUCCESS(2, LogicalTry, "/if", "#/if", "");
EVALUATE_TRACE_POST_SUCCESS(3, AssertionDivisible, "/then/multipleOf",
"#/then/multipleOf", "");
EVALUATE_TRACE_POST_SUCCESS(4, LogicalAnd, "/then", "#/then", "");
EVALUATE_TRACE_POST_SUCCESS(4, LogicalWhenAdjacentAnnotations, "/then",
"#/then", "");

EVALUATE_TRACE_POST_DESCRIBE(
instance, 0,
Expand Down

4 comments on commit adf7305

@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: adf7305 Previous: 0c43415 Ratio
JSONSchema_Compile_Basic 197746.89795918885 ns/iter 195940.84611085083 ns/iter 1.01
JSONSchema_Validate_Draft4_Meta_1_No_Callback 6014.0444764863305 ns/iter 6037.750789703864 ns/iter 1.00
JSONSchema_Validate_Draft4_Required_Properties 3416.530299187251 ns/iter 3446.89567948083 ns/iter 0.99
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 1198.6364680905672 ns/iter 1278.2144624672735 ns/iter 0.94
JSONSchema_Validate_Draft4_Items_Schema 9002.564715278142 ns/iter 9479.108532344015 ns/iter 0.95

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: adf7305 Previous: 0c43415 Ratio
JSONSchema_Compile_Basic 393242.6071428439 ns/iter 395421.90715885034 ns/iter 0.99
JSONSchema_Validate_Draft4_Meta_1_No_Callback 5985.251449430998 ns/iter 6088.930098526194 ns/iter 0.98
JSONSchema_Validate_Draft4_Required_Properties 3332.339888755645 ns/iter 3262.718518310988 ns/iter 1.02
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 797.3393270655973 ns/iter 804.2761404600207 ns/iter 0.99
JSONSchema_Validate_Draft4_Items_Schema 10351.026556681616 ns/iter 10869.298311473403 ns/iter 0.95

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: adf7305 Previous: 0c43415 Ratio
JSONSchema_Validate_Draft4_Meta_1_No_Callback 6249.846650315542 ns/iter 6264.41489605974 ns/iter 1.00
JSONSchema_Validate_Draft4_Required_Properties 3513.4596351121663 ns/iter 3497.9399046706426 ns/iter 1.00
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 824.4736643282225 ns/iter 807.3775417655877 ns/iter 1.02
JSONSchema_Validate_Draft4_Items_Schema 12026.330718667257 ns/iter 12006.24987120015 ns/iter 1.00
JSONSchema_Compile_Basic 390307.01387345174 ns/iter 389587.60222221323 ns/iter 1.00

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: adf7305 Previous: 0c43415 Ratio
JSONSchema_Compile_Basic 793118.8616071085 ns/iter 782294.1964286758 ns/iter 1.01
JSONSchema_Validate_Draft4_Meta_1_No_Callback 12955.476785711946 ns/iter 13045.937964560395 ns/iter 0.99
JSONSchema_Validate_Draft4_Required_Properties 5359.2214285710525 ns/iter 5368.115999999645 ns/iter 1.00
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 2325.4324999996356 ns/iter 2295.0265624999133 ns/iter 1.01
JSONSchema_Validate_Draft4_Items_Schema 25842.693431492753 ns/iter 25981.262095396818 ns/iter 0.99

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

Please sign in to comment.