Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ports/javascript/describe.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
ASSERTION_PROPERTY_TYPE_STRICT_ANY, ASSERTION_PROPERTY_TYPE_STRICT_ANY_EVALUATE,
ASSERTION_ARRAY_PREFIX, ASSERTION_ARRAY_PREFIX_EVALUATE,
ASSERTION_OBJECT_PROPERTIES_SIMPLE,
ANNOTATION_EMIT, ANNOTATION_TO_PARENT, ANNOTATION_BASENAME_TO_PARENT,
ANNOTATION_EMIT, ANNOTATION_EMIT_COLLECTION, ANNOTATION_TO_PARENT, ANNOTATION_BASENAME_TO_PARENT,
EVALUATE,
LOGICAL_NOT, LOGICAL_NOT_EVALUATE,
LOGICAL_OR, LOGICAL_AND, LOGICAL_XOR, LOGICAL_CONDITION,
Expand Down Expand Up @@ -546,7 +546,7 @@ export function describe(valid, instruction, evaluatePath,
'in scope that declared a recursive anchor';
}

if (opcode === ANNOTATION_EMIT) {
if (opcode === ANNOTATION_EMIT || opcode === ANNOTATION_EMIT_COLLECTION) {
if (keyword === 'properties') {
return 'The object property ' + escapeString(annotation) +
' successfully validated against its property subschema';
Expand Down
2 changes: 1 addition & 1 deletion ports/javascript/index.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface StandardOutputAnnotationEntry {
keywordLocation: string;
absoluteKeywordLocation: string;
instanceLocation: string;
annotation: unknown[];
annotation: unknown;
}

export type StandardOutputFlagResult = { valid: boolean };
Expand Down
40 changes: 29 additions & 11 deletions ports/javascript/index.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ANNOTATION_EMIT, ANNOTATION_TO_PARENT, ANNOTATION_BASENAME_TO_PARENT,
ANNOTATION_EMIT, ANNOTATION_EMIT_COLLECTION, ANNOTATION_TO_PARENT, ANNOTATION_BASENAME_TO_PARENT,
isCollectionAnnotationOpcode,
ASSERTION_EQUALS_ANY,
CONTROL_GROUP as CONTROL_GROUP_START,
CONTROL_EVALUATE as CONTROL_EVALUATE_END,
Expand Down Expand Up @@ -529,8 +530,9 @@ class Blaze {
}

callbackPop(instruction, result) {
const isAnnotation = instruction[0] >= ANNOTATION_EMIT &&
instruction[0] <= ANNOTATION_BASENAME_TO_PARENT;
const isAnnotation = (instruction[0] >= ANNOTATION_EMIT &&
instruction[0] <= ANNOTATION_BASENAME_TO_PARENT) ||
instruction[0] === ANNOTATION_EMIT_COLLECTION;
this.callback("post", result, instruction,
buildJsonPointer(this.evaluatePathTokens, this.evaluatePathLength),
buildJsonPointer(this.instanceLocationTokens, this.instanceLocationLength),
Expand All @@ -555,7 +557,7 @@ class Blaze {
const evaluatePath = buildJsonPointer(this.evaluatePathTokens, this.evaluatePathLength);
const opcode = instruction[0];
let instanceLocation;
if (opcode === ANNOTATION_EMIT) {
if (opcode === ANNOTATION_EMIT || opcode === ANNOTATION_EMIT_COLLECTION) {
instanceLocation = buildJsonPointer(this.instanceLocationTokens, this.instanceLocationLength);
} else {
const parentLength = this.instanceLocationLength > 0 ? this.instanceLocationLength - 1 : 0;
Expand Down Expand Up @@ -1589,6 +1591,10 @@ function AnnotationEmit(instruction, instance, depth, template, evaluator) {
if (evaluator.callbackMode) evaluator.callbackAnnotation(instruction);
return true;
}
function AnnotationEmitCollection(instruction, instance, depth, template, evaluator) {
if (evaluator.callbackMode) evaluator.callbackAnnotation(instruction);
return true;
}
function AnnotationToParent(instruction, instance, depth, template, evaluator) {
if (evaluator.callbackMode) evaluator.callbackAnnotation(instruction);
return true;
Expand Down Expand Up @@ -2753,7 +2759,8 @@ const handlers = [
ControlGroupWhenType, // 96
ControlEvaluate, // 97
ControlDynamicAnchorJump, // 98
ControlJump // 99
ControlJump, // 99
AnnotationEmitCollection // 100
];

function AssertionTypeArrayBounded_fast(instruction, instance, depth, template, evaluator) {
Expand Down Expand Up @@ -3517,6 +3524,7 @@ function AssertionObjectPropertiesSimple_fast(instruction, instance, depth, temp
}

function AnnotationEmit_fast() { return true; }
function AnnotationEmitCollection_fast() { return true; }
function AnnotationToParent_fast() { return true; }
function AnnotationBasenameToParent_fast() { return true; }

Expand Down Expand Up @@ -4050,14 +4058,16 @@ fastHandlers[89] = LoopItemsPropertiesExactlyTypeStrictHash_fast;
fastHandlers[90] = LoopItemsIntegerBounded_fast;
fastHandlers[91] = LoopItemsIntegerBoundedSized_fast;
fastHandlers[98] = ControlDynamicAnchorJump_fast;
fastHandlers[100] = AnnotationEmitCollection_fast;

import { describe } from './describe.mjs';

const STANDARD_MASK_KEYWORDS =
new Set([ 'anyOf', 'oneOf', 'not', 'if', 'contains' ]);

function isAnnotationOpcode(opcode) {
return opcode >= ANNOTATION_EMIT && opcode <= ANNOTATION_BASENAME_TO_PARENT;
return (opcode >= ANNOTATION_EMIT && opcode <= ANNOTATION_BASENAME_TO_PARENT) ||
opcode === ANNOTATION_EMIT_COLLECTION;
}

function lastEvaluatePathToken(evaluatePath) {
Expand Down Expand Up @@ -4118,17 +4128,18 @@ class SimpleOutput {
keywordLocation: evaluatePath,
absoluteKeywordLocation: instruction[3],
instanceLocation,
annotation: [ annotation ]
values: [ annotation ],
isCollection: isCollectionAnnotationOpcode(instruction[0])
};
this.annotations.set(annotationKey, bucket);
} else {
const last = bucket.annotation[bucket.annotation.length - 1];
const last = bucket.values[bucket.values.length - 1];
let isSame = last === annotation;
if (!isSame && Array.isArray(last) && Array.isArray(annotation) &&
last.length === annotation.length) {
isSame = last.every((value, index) => value === annotation[index]);
}
if (!isSame) bucket.annotation.push(annotation);
if (!isSame) bucket.values.push(annotation);
}
return;
}
Expand Down Expand Up @@ -4166,7 +4177,7 @@ class SimpleOutput {
for (const [ key, value ] of this.annotations) {
if (value.instanceLocation === instanceLocation &&
(parentPath === '' ||
isPathPrefix(value.keywordLocation, parentPath))) {
isPathPrefix(value.keywordLocation, parentPath))) {
this.annotations.delete(key);
}
}
Expand Down Expand Up @@ -4201,7 +4212,14 @@ class SimpleOutput {
if (valid) {
const result = { valid: true };
if (this.annotations.size > 0) {
result.annotations = [ ...this.annotations.values() ];
result.annotations = [ ...this.annotations.values() ].map(entry => ({
keywordLocation: entry.keywordLocation,
absoluteKeywordLocation: entry.absoluteKeywordLocation,
instanceLocation: entry.instanceLocation,
annotation: entry.isCollection
? entry.values
: entry.values[entry.values.length - 1]
}));
}
return result;
}
Expand Down
8 changes: 7 additions & 1 deletion ports/javascript/opcodes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export const CONTROL_GROUP_WHEN_TYPE = 96;
export const CONTROL_EVALUATE = 97;
export const CONTROL_DYNAMIC_ANCHOR_JUMP = 98;
export const CONTROL_JUMP = 99;
export const ANNOTATION_EMIT_COLLECTION = 100;

export const INSTRUCTION_NAMES = {
"AssertionFail": ASSERTION_FAIL,
Expand Down Expand Up @@ -151,6 +152,7 @@ export const INSTRUCTION_NAMES = {
"AssertionArrayPrefixEvaluate": ASSERTION_ARRAY_PREFIX_EVALUATE,
"AssertionObjectPropertiesSimple": ASSERTION_OBJECT_PROPERTIES_SIMPLE,
"AnnotationEmit": ANNOTATION_EMIT,
"AnnotationEmitCollection": ANNOTATION_EMIT_COLLECTION,
Comment thread
HarshPopat23 marked this conversation as resolved.
"AnnotationToParent": ANNOTATION_TO_PARENT,
"AnnotationBasenameToParent": ANNOTATION_BASENAME_TO_PARENT,
"Evaluate": EVALUATE,
Expand Down Expand Up @@ -204,5 +206,9 @@ export const INSTRUCTION_NAMES = {
};

export const ANNOTATION_OPCODES = new Set([
ANNOTATION_EMIT, ANNOTATION_TO_PARENT, ANNOTATION_BASENAME_TO_PARENT
ANNOTATION_EMIT, ANNOTATION_EMIT_COLLECTION, ANNOTATION_TO_PARENT, ANNOTATION_BASENAME_TO_PARENT
]);

export function isCollectionAnnotationOpcode(opcode) {
return opcode === ANNOTATION_EMIT_COLLECTION || opcode === ANNOTATION_BASENAME_TO_PARENT;
}
8 changes: 4 additions & 4 deletions src/compiler/default_compiler_draft3.h
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,8 @@ auto compiler_draft3_applicator_properties_with_options(

if (emit_annotation) {
substeps.push_back(
make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context,
schema_context, relative_dynamic_context(),
make(sourcemeta::blaze::InstructionIndex::AnnotationEmitCollection,
context, schema_context, relative_dynamic_context(),
sourcemeta::core::JSON{name}));
}

Expand Down Expand Up @@ -736,8 +736,8 @@ auto compiler_draft3_applicator_properties_with_options(
for (auto &&[name, substeps] : properties) {
if (emit_annotation) {
substeps.push_back(
make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context,
schema_context, effective_dynamic_context,
make(sourcemeta::blaze::InstructionIndex::AnnotationEmitCollection,
context, schema_context, effective_dynamic_context,
sourcemeta::core::JSON{name}));
}

Expand Down
4 changes: 3 additions & 1 deletion src/evaluator/evaluator_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,9 @@ auto describe(const bool valid, const Instruction &step,
return message.str();
}

if (step.type == sourcemeta::blaze::InstructionIndex::AnnotationEmit) {
if (step.type == sourcemeta::blaze::InstructionIndex::AnnotationEmit ||
step.type ==
sourcemeta::blaze::InstructionIndex::AnnotationEmitCollection) {
if (keyword == "properties") {
assert(annotation.is_string());
std::ostringstream message;
Expand Down
11 changes: 9 additions & 2 deletions src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,12 @@ INSTRUCTION_HANDLER(AnnotationEmit) {
value);
}

INSTRUCTION_HANDLER(AnnotationEmitCollection) {
const auto &value{assume_value<ValueJSON>(instruction.value)};
EVALUATE_ANNOTATION(AnnotationEmitCollection,
context.evaluator->instance_location, value);
}

INSTRUCTION_HANDLER(AnnotationToParent) {
const auto &value{assume_value<ValueJSON>(instruction.value)};
EVALUATE_ANNOTATION(
Expand Down Expand Up @@ -2816,7 +2822,7 @@ using DispatchHandler = bool (*)(
template <bool Track, bool Dynamic, bool HasCallback>
// Must have same order as InstructionIndex
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
static constexpr DispatchHandler<Track, Dynamic, HasCallback> handlers[100] = {
static constexpr DispatchHandler<Track, Dynamic, HasCallback> handlers[101] = {
AssertionFail,
AssertionDefines,
AssertionDefinesStrict,
Expand Down Expand Up @@ -2916,7 +2922,8 @@ static constexpr DispatchHandler<Track, Dynamic, HasCallback> handlers[100] = {
ControlGroupWhenType,
ControlEvaluate,
ControlDynamicAnchorJump,
ControlJump};
ControlJump,
AnnotationEmitCollection};
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

template <bool Track, bool Dynamic, bool HasCallback>
inline auto
Expand Down
21 changes: 19 additions & 2 deletions src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ enum class InstructionIndex : std::uint8_t {
ControlGroupWhenType,
ControlEvaluate,
ControlDynamicAnchorJump,
ControlJump
ControlJump,
AnnotationEmitCollection
};

/// @ingroup evaluator
Expand Down Expand Up @@ -224,7 +225,8 @@ constexpr std::string_view InstructionNames[] = {
"ControlGroupWhenType",
"ControlEvaluate",
"ControlDynamicAnchorJump",
"ControlJump"};
"ControlJump",
"AnnotationEmitCollection"};

/// @ingroup evaluator
/// Check if a given instruction type corresponds to an annotation
Expand All @@ -237,6 +239,21 @@ inline auto is_annotation(const InstructionIndex type) noexcept -> bool {
return true;
case InstructionIndex::AnnotationEmit:
return true;
case InstructionIndex::AnnotationEmitCollection:
return true;
default:
return false;
}
}

/// @ingroup evaluator
/// Check if a given instruction type corresponds to a collection annotation
inline auto is_collection_annotation(const InstructionIndex type) noexcept
-> bool {
switch (type) {
case InstructionIndex::AnnotationBasenameToParent:
case InstructionIndex::AnnotationEmitCollection:
return true;
default:
return false;
}
Expand Down
1 change: 1 addition & 0 deletions src/output/include/sourcemeta/blaze/output_simple.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class SOURCEMETA_BLAZE_OUTPUT_EXPORT SimpleOutput {
sourcemeta::core::WeakPointer evaluate_path;
std::reference_wrapper<const std::string> schema_location;
sourcemeta::core::JSON value;
bool is_collection;
};

auto operator()(const EvaluationType type, const bool result,
Expand Down
3 changes: 0 additions & 3 deletions src/output/include/sourcemeta/blaze/output_standard.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ enum class StandardOutput : std::uint8_t {
// TODO: Implement the "detailed" and "verbose" output formats
};

// TODO: Integrate with
// https://github.com/json-schema-org/JSON-Schema-Test-Suite/tree/main/output-tests

/// @ingroup output
/// Perform JSON Schema evaluation using Standard Output formats. For example:
///
Expand Down
3 changes: 2 additions & 1 deletion src/output/output_simple.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ auto SimpleOutput::operator()(
{.instance_location = instance_location,
.evaluate_path = std::move(effective_evaluate_path),
.schema_location = step_metadata.keyword_location,
.value = annotation});
.value = annotation,
.is_collection = is_collection_annotation(step.type)});
Comment thread
HarshPopat23 marked this conversation as resolved.
}

return;
Expand Down
31 changes: 22 additions & 9 deletions src/output/output_standard.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,21 @@ struct AnnotationLocation {
std::reference_wrapper<const std::string> schema_location;
};

struct AnnotationGroup {
std::vector<sourcemeta::core::JSON> values;
bool is_collection{false};
};

auto group_annotations(const SimpleOutput &output)
-> std::map<AnnotationLocation, std::vector<sourcemeta::core::JSON>> {
std::map<AnnotationLocation, std::vector<sourcemeta::core::JSON>> result;
-> std::map<AnnotationLocation, AnnotationGroup> {
std::map<AnnotationLocation, AnnotationGroup> result;
for (const auto &entry : output.annotations()) {
auto &values{result[{.instance_location = entry.instance_location,
.evaluate_path = entry.evaluate_path,
.schema_location = entry.schema_location}]};
if (values.empty() || values.back() != entry.value) {
values.push_back(entry.value);
auto &group{result[{.instance_location = entry.instance_location,
.evaluate_path = entry.evaluate_path,
.schema_location = entry.schema_location}]};
group.is_collection = entry.is_collection;
if (group.values.empty() || group.values.back() != entry.value) {
group.values.push_back(entry.value);
}
}

Expand Down Expand Up @@ -87,8 +93,15 @@ auto handle_standard(Evaluator &evaluator, const Template &schema,
}
}

unit.assign_assume_new("annotation",
sourcemeta::core::to_json(annotation.second));
if (annotation.second.is_collection) {
unit.assign_assume_new("annotation", sourcemeta::core::to_json(
annotation.second.values));
} else {
assert(!annotation.second.values.empty());
unit.assign_assume_new(
"annotation",
sourcemeta::core::JSON{annotation.second.values.back()});
}
annotations.push_back(std::move(unit));
}

Expand Down
15 changes: 15 additions & 0 deletions test/evaluator/evaluator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -389,10 +389,25 @@ TEST(is_annotation) {
sourcemeta::blaze::InstructionIndex::AnnotationToParent));
EXPECT_TRUE(sourcemeta::blaze::is_annotation(
sourcemeta::blaze::InstructionIndex::AnnotationEmit));
EXPECT_TRUE(sourcemeta::blaze::is_annotation(
sourcemeta::blaze::InstructionIndex::AnnotationEmitCollection));
EXPECT_FALSE(sourcemeta::blaze::is_annotation(
sourcemeta::blaze::InstructionIndex::AssertionFail));
}

TEST(is_collection_annotation) {
EXPECT_TRUE(sourcemeta::blaze::is_collection_annotation(
sourcemeta::blaze::InstructionIndex::AnnotationBasenameToParent));
EXPECT_TRUE(sourcemeta::blaze::is_collection_annotation(
sourcemeta::blaze::InstructionIndex::AnnotationEmitCollection));
EXPECT_FALSE(sourcemeta::blaze::is_collection_annotation(
sourcemeta::blaze::InstructionIndex::AnnotationEmit));
EXPECT_FALSE(sourcemeta::blaze::is_collection_annotation(
sourcemeta::blaze::InstructionIndex::AnnotationToParent));
EXPECT_FALSE(sourcemeta::blaze::is_collection_annotation(
sourcemeta::blaze::InstructionIndex::AssertionFail));
}

TEST(instruction_copy_constructible) {
EXPECT_TRUE(std::is_copy_constructible_v<sourcemeta::blaze::Instruction>);
}
Expand Down
Loading
Loading