Skip to content

Commit

Permalink
Remove the idea of explicit conditions from the compiler (#1100)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Sep 3, 2024
1 parent 1abf1db commit 2917e0c
Show file tree
Hide file tree
Showing 16 changed files with 527 additions and 1,459 deletions.
5 changes: 2 additions & 3 deletions src/jsonschema/compile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ auto compile_subschema(
} else {
return {make<SchemaCompilerAssertionFail>(true, context, schema_context,
dynamic_context,
SchemaCompilerValueNone{}, {})};
SchemaCompilerValueNone{})};
}
}

Expand Down Expand Up @@ -154,8 +154,7 @@ auto compile(const JSON &schema, const SchemaWalker &walker,
true, context, nested_schema_context, dynamic_context,
SchemaCompilerValueUnsignedInteger{label},
compile(context, nested_schema_context, relative_dynamic_context,
empty_pointer, empty_pointer, entry.first.second),
{}));
empty_pointer, empty_pointer, entry.first.second)));
}
}

Expand Down
162 changes: 91 additions & 71 deletions src/jsonschema/compile_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -839,11 +839,11 @@ struct DescribeVisitor {
return message.str();
}

auto operator()(const SchemaCompilerAssertionSizeGreater &step) const
auto operator()(const SchemaCompilerAssertionStringSizeLess &step) const
-> std::string {
if (this->keyword == "minLength") {
if (this->keyword == "maxLength") {
std::ostringstream message;
const auto minimum{step_value(step) + 1};
const auto maximum{step_value(step) - 1};

if (is_within_keyword(this->evaluate_path, "propertyNames")) {
assert(this->instance_location.back().is_property());
Expand All @@ -854,8 +854,8 @@ struct DescribeVisitor {
stringify(this->target, message);
}

message << " was expected to consist of at least " << minimum
<< (minimum == 1 ? " character" : " characters");
message << " was expected to consist of at most " << maximum
<< (maximum == 1 ? " character" : " characters");

if (this->valid) {
message << " and";
Expand All @@ -878,45 +878,63 @@ struct DescribeVisitor {
return message.str();
}

if (this->keyword == "minItems") {
assert(this->target.is_array());
return unknown();
}

auto operator()(const SchemaCompilerAssertionStringSizeGreater &step) const
-> std::string {
if (this->keyword == "minLength") {
std::ostringstream message;
const auto minimum{step_value(step) + 1};
message << "The array value was expected to contain at least " << minimum;
assert(minimum > 0);
if (minimum == 1) {
message << " item";

if (is_within_keyword(this->evaluate_path, "propertyNames")) {
assert(this->instance_location.back().is_property());
message << "The object property name "
<< escape_string(this->instance_location.back().to_property());
} else {
message << " items";
message << "The string value ";
stringify(this->target, message);
}

message << " was expected to consist of at least " << minimum
<< (minimum == 1 ? " character" : " characters");

if (this->valid) {
message << " and";
} else {
message << " but";
}

message << " it contained " << this->target.size();
if (this->target.size() == 1) {
message << " item";
message << " it consisted of ";

if (is_within_keyword(this->evaluate_path, "propertyNames")) {
message << this->instance_location.back().to_property().size();
message << (this->instance_location.back().to_property().size() == 1
? " character"
: " characters");
} else {
message << " items";
message << this->target.size();
message << (this->target.size() == 1 ? " character" : " characters");
}

return message.str();
}

if (this->keyword == "minProperties") {
assert(this->target.is_object());
return unknown();
}

auto operator()(const SchemaCompilerAssertionArraySizeLess &step) const
-> std::string {
if (this->keyword == "maxItems") {
assert(this->target.is_array());
std::ostringstream message;
const auto minimum{step_value(step) + 1};
message << "The object value was expected to contain at least "
<< minimum;
assert(minimum > 0);
if (minimum == 1) {
message << " property";
const auto maximum{step_value(step) - 1};
message << "The array value was expected to contain at most " << maximum;
assert(maximum > 0);
if (maximum == 1) {
message << " item";
} else {
message << " properties";
message << " items";
}

if (this->valid) {
Expand All @@ -927,18 +945,9 @@ struct DescribeVisitor {

message << " it contained " << this->target.size();
if (this->target.size() == 1) {
message << " property: ";
message << escape_string(this->target.as_object().cbegin()->first);
message << " item";
} else {
message << " properties: ";
for (auto iterator = this->target.as_object().cbegin();
iterator != this->target.as_object().cend(); ++iterator) {
if (std::next(iterator) == this->target.as_object().cend()) {
message << "and " << escape_string(iterator->first);
} else {
message << escape_string(iterator->first) << ", ";
}
}
message << " items";
}

return message.str();
Expand All @@ -947,55 +956,51 @@ struct DescribeVisitor {
return unknown();
}

auto
operator()(const SchemaCompilerAssertionSizeLess &step) const -> std::string {
if (this->keyword == "maxLength") {
auto operator()(const SchemaCompilerAssertionArraySizeGreater &step) const
-> std::string {
if (this->keyword == "minItems") {
assert(this->target.is_array());
std::ostringstream message;
const auto maximum{step_value(step) - 1};

if (is_within_keyword(this->evaluate_path, "propertyNames")) {
assert(this->instance_location.back().is_property());
message << "The object property name "
<< escape_string(this->instance_location.back().to_property());
const auto minimum{step_value(step) + 1};
message << "The array value was expected to contain at least " << minimum;
assert(minimum > 0);
if (minimum == 1) {
message << " item";
} else {
message << "The string value ";
stringify(this->target, message);
message << " items";
}

message << " was expected to consist of at most " << maximum
<< (maximum == 1 ? " character" : " characters");

if (this->valid) {
message << " and";
} else {
message << " but";
}

message << " it consisted of ";

if (is_within_keyword(this->evaluate_path, "propertyNames")) {
message << this->instance_location.back().to_property().size();
message << (this->instance_location.back().to_property().size() == 1
? " character"
: " characters");
message << " it contained " << this->target.size();
if (this->target.size() == 1) {
message << " item";
} else {
message << this->target.size();
message << (this->target.size() == 1 ? " character" : " characters");
message << " items";
}

return message.str();
}

if (this->keyword == "maxItems") {
assert(this->target.is_array());
return unknown();
}

auto operator()(const SchemaCompilerAssertionObjectSizeLess &step) const
-> std::string {
if (this->keyword == "maxProperties") {
assert(this->target.is_object());
std::ostringstream message;
const auto maximum{step_value(step) - 1};
message << "The array value was expected to contain at most " << maximum;
message << "The object value was expected to contain at most " << maximum;
assert(maximum > 0);
if (maximum == 1) {
message << " item";
message << " property";
} else {
message << " items";
message << " properties";
}

if (this->valid) {
Expand All @@ -1006,21 +1011,36 @@ struct DescribeVisitor {

message << " it contained " << this->target.size();
if (this->target.size() == 1) {
message << " item";
message << " property: ";
message << escape_string(this->target.as_object().cbegin()->first);
} else {
message << " items";
message << " properties: ";
for (auto iterator = this->target.as_object().cbegin();
iterator != this->target.as_object().cend(); ++iterator) {
if (std::next(iterator) == this->target.as_object().cend()) {
message << "and " << escape_string(iterator->first);
} else {
message << escape_string(iterator->first) << ", ";
}
}
}

return message.str();
}

if (this->keyword == "maxProperties") {
return unknown();
}

auto operator()(const SchemaCompilerAssertionObjectSizeGreater &step) const
-> std::string {
if (this->keyword == "minProperties") {
assert(this->target.is_object());
std::ostringstream message;
const auto maximum{step_value(step) - 1};
message << "The object value was expected to contain at most " << maximum;
assert(maximum > 0);
if (maximum == 1) {
const auto minimum{step_value(step) + 1};
message << "The object value was expected to contain at least "
<< minimum;
assert(minimum > 0);
if (minimum == 1) {
message << " property";
} else {
message << " properties";
Expand Down
97 changes: 68 additions & 29 deletions src/jsonschema/compile_evaluate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -347,16 +347,6 @@ auto evaluate_step(
return true; \
}

#define EVALUATE_CONDITION_GUARD(title, step, instance) \
for (const auto &child : step.condition) { \
if (!evaluate_step(child, instance, SchemaCompilerEvaluationMode::Fast, \
std::nullopt, context)) { \
context.pop(step); \
SOURCEMETA_TRACE_END(trace_id, title); \
return true; \
} \
}

if (std::holds_alternative<SchemaCompilerAssertionFail>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionFail");
const auto &assertion{std::get<SchemaCompilerAssertionFail>(step)};
Expand Down Expand Up @@ -483,30 +473,80 @@ auto evaluate_step(
CALLBACK_PRE(assertion, context.instance_location());
result = std::regex_search(target.to_string(), assertion.value.first);
CALLBACK_POST("SchemaCompilerAssertionRegex", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionSizeGreater>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionSizeGreater");
const auto &assertion{std::get<SchemaCompilerAssertionSizeGreater>(step)};
} else if (std::holds_alternative<SchemaCompilerAssertionStringSizeLess>(
step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionStringSizeLess");
const auto &assertion{
std::get<SchemaCompilerAssertionStringSizeLess>(step)};
context.push(assertion);
const auto &target{context.resolve_target(instance)};
EVALUATE_IMPLICIT_PRECONDITION("SchemaCompilerAssertionStringSizeLess",
assertion, target.is_string());
CALLBACK_PRE(assertion, context.instance_location());
result = (target.size() < assertion.value);
CALLBACK_POST("SchemaCompilerAssertionStringSizeLess", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionStringSizeGreater>(
step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerAssertionStringSizeGreater");
const auto &assertion{
std::get<SchemaCompilerAssertionStringSizeGreater>(step)};
context.push(assertion);
const auto &target{context.resolve_target(instance)};
EVALUATE_IMPLICIT_PRECONDITION("SchemaCompilerAssertionStringSizeGreater",
assertion, target.is_string());
CALLBACK_PRE(assertion, context.instance_location());
result = (target.size() > assertion.value);
CALLBACK_POST("SchemaCompilerAssertionStringSizeGreater", assertion);

} else if (std::holds_alternative<SchemaCompilerAssertionArraySizeLess>(
step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionArraySizeLess");
const auto &assertion{std::get<SchemaCompilerAssertionArraySizeLess>(step)};
context.push(assertion);
// TODO: Get rid of this
EVALUATE_CONDITION_GUARD("SchemaCompilerAssertionSizeGreater", assertion,
instance);
const auto &target{context.resolve_target(instance)};
EVALUATE_IMPLICIT_PRECONDITION("SchemaCompilerAssertionArraySizeLess",
assertion, target.is_array());
CALLBACK_PRE(assertion, context.instance_location());
result = (target.size() < assertion.value);
CALLBACK_POST("SchemaCompilerAssertionArraySizeLess", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionArraySizeGreater>(
step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionArraySizeGreater");
const auto &assertion{
std::get<SchemaCompilerAssertionArraySizeGreater>(step)};
context.push(assertion);
const auto &target{context.resolve_target(instance)};
result = (target.is_array() || target.is_object() || target.is_string()) &&
(target.size() > assertion.value);
CALLBACK_POST("SchemaCompilerAssertionSizeGreater", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionSizeLess>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionSizeLess");
const auto &assertion{std::get<SchemaCompilerAssertionSizeLess>(step)};
EVALUATE_IMPLICIT_PRECONDITION("SchemaCompilerAssertionArraySizeGreater",
assertion, target.is_array());
CALLBACK_PRE(assertion, context.instance_location());
result = (target.size() > assertion.value);
CALLBACK_POST("SchemaCompilerAssertionArraySizeGreater", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionObjectSizeLess>(
step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionObjectSizeLess");
const auto &assertion{
std::get<SchemaCompilerAssertionObjectSizeLess>(step)};
context.push(assertion);
// TODO: Get rid of this
EVALUATE_CONDITION_GUARD("SchemaCompilerAssertionSizeLess", assertion,
instance);
const auto &target{context.resolve_target(instance)};
EVALUATE_IMPLICIT_PRECONDITION("SchemaCompilerAssertionObjectSizeLess",
assertion, target.is_object());
CALLBACK_PRE(assertion, context.instance_location());
result = (target.size() < assertion.value);
CALLBACK_POST("SchemaCompilerAssertionObjectSizeLess", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionObjectSizeGreater>(
step)) {
SOURCEMETA_TRACE_START(trace_id,
"SchemaCompilerAssertionObjectSizeGreater");
const auto &assertion{
std::get<SchemaCompilerAssertionObjectSizeGreater>(step)};
context.push(assertion);
const auto &target{context.resolve_target(instance)};
result = (target.is_array() || target.is_object() || target.is_string()) &&
(target.size() < assertion.value);
CALLBACK_POST("SchemaCompilerAssertionSizeLess", assertion);
EVALUATE_IMPLICIT_PRECONDITION("SchemaCompilerAssertionObjectSizeGreater",
assertion, target.is_object());
CALLBACK_PRE(assertion, context.instance_location());
result = (target.size() > assertion.value);
CALLBACK_POST("SchemaCompilerAssertionObjectSizeGreater", assertion);
} else if (std::holds_alternative<SchemaCompilerAssertionEqual>(step)) {
SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerAssertionEqual");
const auto &assertion{std::get<SchemaCompilerAssertionEqual>(step)};
Expand Down Expand Up @@ -1358,7 +1398,6 @@ auto evaluate_step(
#undef CALLBACK_POST
#undef CALLBACK_ANNOTATION
#undef EVALUATE_IMPLICIT_PRECONDITION
#undef EVALUATE_CONDITION_GUARD
// We should never get here
assert(false);
return result;
Expand Down
Loading

4 comments on commit 2917e0c

@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: 2917e0c Previous: 1abf1db Ratio
JSONSchema_Compile_Basic 219456.9733239552 ns/iter 196226.76803394748 ns/iter 1.12
JSONSchema_Validate_Draft4_Meta_1_No_Callback 1650.526482546473 ns/iter 1594.761266570678 ns/iter 1.03
JSONSchema_Validate_Draft4_Required_Properties 2481.170113577368 ns/iter 2446.842602561657 ns/iter 1.01
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 176.9487637737001 ns/iter 168.34540484314013 ns/iter 1.05
JSONSchema_Validate_Draft4_Items_Schema 9483.548418128574 ns/iter 9097.066254912957 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: 2917e0c Previous: 1abf1db Ratio
JSONSchema_Compile_Basic 386107.70388888544 ns/iter 390770.88024619897 ns/iter 0.99
JSONSchema_Validate_Draft4_Meta_1_No_Callback 18873.597333190737 ns/iter 16712.404949005475 ns/iter 1.13
JSONSchema_Validate_Draft4_Required_Properties 8083.825567421225 ns/iter 8153.480031776332 ns/iter 0.99
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 1701.0656826638995 ns/iter 1565.3725641636925 ns/iter 1.09
JSONSchema_Validate_Draft4_Items_Schema 114870.9017303271 ns/iter 106439.87255347312 ns/iter 1.08

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: 2917e0c Previous: 1abf1db Ratio
JSONSchema_Validate_Draft4_Meta_1_No_Callback 2230.3699313517304 ns/iter 2262.645817470263 ns/iter 0.99
JSONSchema_Validate_Draft4_Required_Properties 2803.165440954927 ns/iter 2790.6076460504382 ns/iter 1.00
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 202.44954390979092 ns/iter 221.70541130855193 ns/iter 0.91
JSONSchema_Validate_Draft4_Items_Schema 11678.648198799068 ns/iter 12088.773145912433 ns/iter 0.97
JSONSchema_Compile_Basic 382126.2940854064 ns/iter 381287.5500544322 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: 2917e0c Previous: 1abf1db Ratio
JSONSchema_Compile_Basic 797801.2048191973 ns/iter 839142.522321217 ns/iter 0.95
JSONSchema_Validate_Draft4_Meta_1_No_Callback 4705.916977493449 ns/iter 4838.967944124349 ns/iter 0.97
JSONSchema_Validate_Draft4_Required_Properties 3733.6338386808925 ns/iter 3802.796959291389 ns/iter 0.98
JSONSchema_Validate_Draft4_Optional_Properties_Minimal_Match 778.9469866071081 ns/iter 774.106361607098 ns/iter 1.01
JSONSchema_Validate_Draft4_Items_Schema 24722.55357142623 ns/iter 24780.01071428285 ns/iter 1.00

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

Please sign in to comment.