Skip to content
Draft
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
114 changes: 89 additions & 25 deletions ports/javascript/index.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const JSON_VERSION = 4;
const JSON_VERSION = 5;
const DEPTH_LIMIT = 300;
const ANNOTATION_EMIT = 49;
const ANNOTATION_TO_PARENT = 50;
const ANNOTATION_BASENAME_TO_PARENT = 51;
const CONTROL_GROUP_START = 92;
const CONTROL_EVALUATE_END = 96;
const CONTROL_GROUP_START = 93;
const CONTROL_EVALUATE_END = 97;
const URI_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/;

function buildJsonPointer(tokens, length) {
Expand Down Expand Up @@ -152,7 +152,7 @@ function prepareInstruction(instruction) {
function resolveJumpTargets(instructions, targets) {
for (let index = 0; index < instructions.length; index++) {
const instruction = instructions[index];
if (instruction[0] === 98) {
if (instruction[0] === 99) {
const targetIndex = instruction[5];
if (targetIndex < targets.length) {
instruction[5] = targets[targetIndex];
Expand Down Expand Up @@ -187,7 +187,7 @@ function collectAnchorNames(targets, result) {
function collectAnchorNamesFromInstructions(instructions, result) {
for (let index = 0; index < instructions.length; index++) {
const instruction = instructions[index];
if (instruction[0] === 97 && typeof instruction[5] === 'string') {
if (instruction[0] === 98 && typeof instruction[5] === 'string') {
result.add(instruction[5]);
}
if (instruction[6]) {
Expand Down Expand Up @@ -326,14 +326,14 @@ function compileInstructionToCode(instruction, captures, visited, budget) {
case 84: { var r=R('t'); return r?r+'if(!Array.isArray(t))return true;for(var j=0;j<t.length;j++){var a=_jt(t[j]);if(a!=='+value+'&&!('+value+'===2&&_ii(t[j])))return false;}return true;':null; }
case 85: { var r=R('t'); return r?r+'if(!Array.isArray(t))return true;for(var j=0;j<t.length;j++){if(_es(t[j])!=='+value+')return false;}return true;':null; }
case 86: { var r=R('t'); return r?r+'if(!Array.isArray(t))return true;for(var j=0;j<t.length;j++){if(('+value+'&(1<<_es(t[j])))===0)return false;}return true;':null; }
case 87: return fb(87); case 88: return fb(88); case 89: return fb(89); case 90: return fb(90); case 91: return fb(91);
case 92: { if(!children||children.length===0)return 'return true;'; var c=''; for(var j=0;j<children.length;j++){var r2=compileInstructionToCode(children[j],captures,visited,budget); if(r2===null){var ci=captures.length;captures.push(children[j]);c+='if(!_e(_c['+ci+'],i,d+1,_t,_v))return false;';}else{budget[0]-=r2.length;c+='if(!(function(i,d,_t,_v){'+r2+'})(i,d+1,_t,_v))return false;';}} return c+'return true;'; }
case 93: { var r=R('t'); if(!r)return null; var c=r+TO+'return true;if(!Object.hasOwn(t,'+JSON.stringify(value)+'))return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 94: { var c=IO+'if(!Object.hasOwn(i,'+JSON.stringify(value)+'))return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 95: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 96: return 'return true;';
case 97: return fb(97);
case 98: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(98); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(98); var c=r; for(var j=0;j<value.length;j++){var r2=compileInstructionToCode(value[j],captures,visited,budget); if(r2===null){var ci=captures.length;captures.push(value[j]);c+='if(!_e(_c['+ci+'],t,d+1,_t,_v))return false;';}else{budget[0]-=r2.length;c+='if(!(function(i,d,_t,_v){'+r2+'})(t,d+1,_t,_v))return false;';}} return c+'return true;'; }
case 87: return fb(87); case 88: return fb(88); case 89: return fb(89); case 90: return fb(90); case 91: return fb(91); case 92: return fb(92);
case 93: { if(!children||children.length===0)return 'return true;'; var c=''; for(var j=0;j<children.length;j++){var r2=compileInstructionToCode(children[j],captures,visited,budget); if(r2===null){var ci=captures.length;captures.push(children[j]);c+='if(!_e(_c['+ci+'],i,d+1,_t,_v))return false;';}else{budget[0]-=r2.length;c+='if(!(function(i,d,_t,_v){'+r2+'})(i,d+1,_t,_v))return false;';}} return c+'return true;'; }
case 94: { var r=R('t'); if(!r)return null; var c=r+TO+'return true;if(!Object.hasOwn(t,'+JSON.stringify(value)+'))return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 95: { var c=IO+'if(!Object.hasOwn(i,'+JSON.stringify(value)+'))return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 96: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 97: return 'return true;';
case 98: return fb(98);
case 99: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(99); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(99); var c=r; for(var j=0;j<value.length;j++){var r2=compileInstructionToCode(value[j],captures,visited,budget); if(r2===null){var ci=captures.length;captures.push(value[j]);c+='if(!_e(_c['+ci+'],t,d+1,_t,_v))return false;';}else{budget[0]-=r2.length;c+='if(!(function(i,d,_t,_v){'+r2+'})(t,d+1,_t,_v))return false;';}} return c+'return true;'; }
default: return null;
}
}
Expand Down Expand Up @@ -656,7 +656,7 @@ function evaluateInstructionTracked(instruction, instance, depth, template, eval
if (!handler) return true;

const type = instruction[0];
if (type < 92 || type > 96) {
if (type < 93 || type > 97) {
if (evaluator.trackMode) {
evaluator.pushPath(instruction[1]);
}
Expand Down Expand Up @@ -1543,6 +1543,44 @@ function AssertionObjectPropertiesSimple(instruction, instance, depth, template,
return true;
};

function LoopItemsObjectProperties(instruction, instance, depth, template, evaluator) {
const target = resolveInstance(instance, instruction[2]);
if (evaluator.callbackMode) evaluator.callbackPush(instruction);
if (!Array.isArray(target)) {
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
return false;
}
const value = instruction[5];
const children = instruction[6];
for (let elementIndex = 0; elementIndex < target.length; elementIndex++) {
const element = target[elementIndex];
if (!isObject(element)) {
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
return false;
}
for (let index = 0; index < value.length; index++) {
const entry = value[index];
const name = entry[0];
const required = entry[2];
if (!Object.hasOwn(element, name)) {
if (required) {
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
return false;
}
continue;
}
if (index < children.length) {
if (!evaluateInstructionFast(children[index], element[name], depth + 1, template, evaluator)) {
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
return false;
}
}
}
}
if (evaluator.callbackMode) evaluator.callbackPop(instruction, true);
return true;
}

function AnnotationEmit(instruction, instance, depth, template, evaluator) {
if (evaluator.callbackMode) evaluator.callbackAnnotation(instruction);
return true;
Expand Down Expand Up @@ -2688,14 +2726,15 @@ const handlers = [
LoopItemsPropertiesExactlyTypeStrictHash, // 88
LoopItemsIntegerBounded, // 89
LoopItemsIntegerBoundedSized, // 90
LoopContains, // 91
ControlGroup, // 92
ControlGroupWhenDefines, // 93
ControlGroupWhenDefinesDirect, // 94
ControlGroupWhenType, // 95
ControlEvaluate, // 96
ControlDynamicAnchorJump, // 97
ControlJump // 98
LoopItemsObjectProperties, // 91
LoopContains, // 92
ControlGroup, // 93
ControlGroupWhenDefines, // 94
ControlGroupWhenDefinesDirect, // 95
ControlGroupWhenType, // 96
ControlEvaluate, // 97
ControlDynamicAnchorJump, // 98
ControlJump // 99
];

function AssertionTypeArrayBounded_fast(instruction, instance, depth, template, evaluator) {
Expand Down Expand Up @@ -3436,6 +3475,30 @@ function AssertionObjectPropertiesSimple_fast(instruction, instance, depth, temp
return true;
}

function LoopItemsObjectProperties_fast(instruction, instance, depth, template, evaluator) {
const target = resolveInstance(instance, instruction[2]);
if (!Array.isArray(target)) return false;
const value = instruction[5];
const children = instruction[6];
for (let elementIndex = 0; elementIndex < target.length; elementIndex++) {
const element = target[elementIndex];
if (!isObject(element)) return false;
for (let index = 0; index < value.length; index++) {
const entry = value[index];
const name = entry[0];
const required = entry[2];
if (!Object.hasOwn(element, name)) {
if (required) return false;
continue;
}
if (index < children.length) {
if (!evaluateInstructionFast(children[index], element[name], depth + 1, template, evaluator)) return false;
}
}
}
return true;
}

function AnnotationEmit_fast() { return true; }
function AnnotationToParent_fast() { return true; }
function AnnotationBasenameToParent_fast() { return true; }
Expand Down Expand Up @@ -3880,7 +3943,7 @@ fastHandlers[4] = AssertionDefinesAllStrict_fast;
fastHandlers[26] = AssertionEqual_fast;
fastHandlers[64] = LoopPropertiesMatch_fast;
fastHandlers[55] = LogicalOr_fast;
fastHandlers[98] = ControlJump_fast;
fastHandlers[99] = ControlJump_fast;
fastHandlers[28] = AssertionEqualsAnyStringHash_fast;
fastHandlers[57] = LogicalXor_fast;
fastHandlers[2] = AssertionDefinesStrict_fast;
Expand All @@ -3898,7 +3961,7 @@ fastHandlers[1] = AssertionDefines_fast;
fastHandlers[59] = LogicalWhenType_fast;
fastHandlers[60] = LogicalWhenDefines_fast;
fastHandlers[0] = AssertionFail_fast;
fastHandlers[91] = LoopContains_fast;
fastHandlers[92] = LoopContains_fast;
fastHandlers[53] = LogicalNot_fast;
fastHandlers[84] = LoopItemsType_fast;
fastHandlers[85] = LoopItemsTypeStrict_fast;
Expand Down Expand Up @@ -3965,6 +4028,7 @@ fastHandlers[87] = LoopItemsPropertiesExactlyTypeStrictHash_fast;
fastHandlers[88] = LoopItemsPropertiesExactlyTypeStrictHash_fast;
fastHandlers[89] = LoopItemsIntegerBounded_fast;
fastHandlers[90] = LoopItemsIntegerBoundedSized_fast;
fastHandlers[97] = ControlDynamicAnchorJump_fast;
fastHandlers[91] = LoopItemsObjectProperties_fast;
fastHandlers[98] = ControlDynamicAnchorJump_fast;

export { Blaze };
6 changes: 3 additions & 3 deletions ports/javascript/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ describe('reviver', () => {

describe('version', () => {
it('rejects a template with an unsupported version', () => {
const template = [5, false, false, [[]], []];
const template = [6, false, false, [[]], []];
assert.throws(() => new Blaze(template), {
message: 'Only version 4 of the compiled template is supported by this version of the evaluator'
message: 'Only version 5 of the compiled template is supported by this version of the evaluator'
});
});

it('rejects a template that is not an array', () => {
assert.throws(() => new Blaze({}), {
message: 'Only version 4 of the compiled template is supported by this version of the evaluator'
message: 'Only version 5 of the compiled template is supported by this version of the evaluator'
});
});
});
17 changes: 9 additions & 8 deletions ports/javascript/trace.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,15 @@ const INSTRUCTION_NAMES = {
"LoopItemsPropertiesExactlyTypeStrictHash3": 88,
"LoopItemsIntegerBounded": 89,
"LoopItemsIntegerBoundedSized": 90,
"LoopContains": 91,
"ControlGroup": 92,
"ControlGroupWhenDefines": 93,
"ControlGroupWhenDefinesDirect": 94,
"ControlGroupWhenType": 95,
"ControlEvaluate": 96,
"ControlDynamicAnchorJump": 97,
"ControlJump": 98,
"LoopItemsObjectProperties": 91,
"LoopContains": 92,
"ControlGroup": 93,
"ControlGroupWhenDefines": 94,
"ControlGroupWhenDefinesDirect": 95,
"ControlGroupWhenType": 96,
"ControlEvaluate": 97,
"ControlDynamicAnchorJump": 98,
"ControlJump": 99,
"Annotation": -1
};

Expand Down
59 changes: 59 additions & 0 deletions src/compiler/postprocess.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ inline auto is_noop_without_children(const InstructionIndex type) noexcept
case InstructionIndex::LoopItems:
case InstructionIndex::LoopItemsFrom:
case InstructionIndex::LoopItemsUnevaluated:
case InstructionIndex::LoopItemsObjectProperties:
case InstructionIndex::LoopContains:
case InstructionIndex::ControlGroupWhenDefines:
case InstructionIndex::ControlGroupWhenDefinesDirect:
Expand Down Expand Up @@ -414,7 +415,65 @@ inline auto postprocess(std::vector<Instructions> &targets,
}
}

std::size_t loop_items_object_candidate{SIZE_MAX};
std::size_t type_array_candidate{SIZE_MAX};
for (std::size_t scan = 0; scan < current->size(); scan++) {
const auto &scan_instruction{(*current)[scan]};
if ((scan_instruction.type == InstructionIndex::LoopItems ||
(scan_instruction.type == InstructionIndex::LoopItemsFrom &&
std::get<ValueUnsignedInteger>(scan_instruction.value) == 0)) &&
scan_instruction.children.size() == 1 &&
scan_instruction.children.front().type ==
InstructionIndex::AssertionObjectPropertiesSimple) {
loop_items_object_candidate = scan;
}

if ((scan_instruction.type == InstructionIndex::AssertionTypeStrict ||
scan_instruction.type == InstructionIndex::AssertionType ||
scan_instruction.type ==
InstructionIndex::AssertionPropertyTypeStrict ||
scan_instruction.type ==
InstructionIndex::AssertionPropertyType) &&
std::get<ValueType>(scan_instruction.value) ==
sourcemeta::core::JSON::Type::Array) {
type_array_candidate = scan;
}
}

const bool fuse_loop_items_object{loop_items_object_candidate !=
SIZE_MAX &&
type_array_candidate != SIZE_MAX};

for (auto &instruction : *current) {
if (fuse_loop_items_object) {
if (&instruction == &(*current)[type_array_candidate]) {
changed = true;
continue;
}

if (&instruction == &(*current)[loop_items_object_candidate]) {
auto &child{instruction.children.front()};
const auto new_extra_index{extra.size()};
auto &parent_meta{extra[instruction.extra_index]};
auto &child_meta{extra[child.extra_index]};
extra.push_back(
{.relative_schema_location =
parent_meta.relative_schema_location.concat(
child_meta.relative_schema_location),
.keyword_location = std::move(child_meta.keyword_location),
.schema_resource = child_meta.schema_resource});
result.push_back(Instruction{
.type = InstructionIndex::LoopItemsObjectProperties,
.relative_instance_location =
std::move(instruction.relative_instance_location),
.value = std::move(child.value),
.children = std::move(child.children),
.extra_index = new_extra_index});
changed = true;
continue;
}
}

if (!fusion_covered_properties.empty()) {
switch (instruction.type) {
case InstructionIndex::AssertionDefinesAllStrict:
Expand Down
6 changes: 6 additions & 0 deletions src/evaluator/evaluator_describe.cc
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,12 @@ auto describe(const bool valid, const Instruction &step,
"property subschemas";
}

if (step.type ==
sourcemeta::blaze::InstructionIndex::LoopItemsObjectProperties) {
return "Every item in the array value was expected to be an object "
"validating against the defined property subschemas";
}

if (step.type == sourcemeta::blaze::InstructionIndex::LoopPropertiesType) {
std::ostringstream message;
message << "The object properties were expected to be of type "
Expand Down
2 changes: 1 addition & 1 deletion src/evaluator/include/sourcemeta/blaze/evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct Template {
};

/// @ingroup evaluator
constexpr std::size_t JSON_VERSION{4};
constexpr std::size_t JSON_VERSION{5};

/// @ingroup evaluator
/// Parse a template from JSON
Expand Down
47 changes: 46 additions & 1 deletion src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h
Original file line number Diff line number Diff line change
Expand Up @@ -2371,6 +2371,50 @@ INSTRUCTION_HANDLER(LoopItemsIntegerBoundedSized) {
EVALUATE_END(LoopItemsIntegerBoundedSized);
}

INSTRUCTION_HANDLER(LoopItemsObjectProperties) {
EVALUATE_BEGIN_NON_STRING(LoopItemsObjectProperties, true);
if (!target.is_array()) {
EVALUATE_END(LoopItemsObjectProperties);
}

const auto &value{assume_value<ValueObjectProperties>(instruction.value)};
assert(value.size() >= instruction.children.size());
result = true;

for (const auto &element : target.as_array()) {
if (!element.is_object()) [[unlikely]] {
result = false;
EVALUATE_END(LoopItemsObjectProperties);
}

for (std::size_t index = 0; index < value.size(); index++) {
const auto &entry{value[index]};
const auto &name{std::get<0>(entry)};
const auto hash{std::get<1>(entry)};
const auto is_required{std::get<2>(entry)};
const auto *property_value{element.try_at(name, hash)};
if (!property_value) {
if (is_required) [[unlikely]] {
result = false;
EVALUATE_END(LoopItemsObjectProperties);
}

continue;
}

if (index < instruction.children.size() &&
!evaluate_instruction_without_callback(
instruction.children[index], *property_value, depth + 1, context))
[[unlikely]] {
result = false;
EVALUATE_END(LoopItemsObjectProperties);
}
}
}

EVALUATE_END(LoopItemsObjectProperties);
}

INSTRUCTION_HANDLER(LoopContains) {
EVALUATE_BEGIN_NON_STRING(LoopContains, target.is_array());
assert(!instruction.children.empty());
Expand Down Expand Up @@ -2441,7 +2485,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[99] = {
static constexpr DispatchHandler<Track, Dynamic, HasCallback> handlers[100] = {
AssertionFail,
AssertionDefines,
AssertionDefinesStrict,
Expand Down Expand Up @@ -2533,6 +2577,7 @@ static constexpr DispatchHandler<Track, Dynamic, HasCallback> handlers[99] = {
LoopItemsPropertiesExactlyTypeStrictHash3,
LoopItemsIntegerBounded,
LoopItemsIntegerBoundedSized,
LoopItemsObjectProperties,
LoopContains,
ControlGroup,
ControlGroupWhenDefines,
Expand Down
Loading
Loading