Skip to content
Merged
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
84 changes: 65 additions & 19 deletions ports/javascript/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
CONTROL_JUMP, CONTROL_DYNAMIC_ANCHOR_JUMP
} from './opcodes.mjs';

const JSON_VERSION = 10;
const JSON_VERSION = 11;
const DEPTH_LIMIT = 300;
const URI_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/;

Expand Down Expand Up @@ -338,13 +338,14 @@ function compileInstructionToCode(instruction, captures, visited, budget) {
case 90: { 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 91: return fb(91); case 92: return fb(92); case 93: return fb(93); case 94: return fb(94);
case 95: return fb(95);
case 96: { 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 97: { 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 98: { 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 99: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 100: return 'return true;';
case 101: return fb(101);
case 102: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(102); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(102); 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 96: return fb(96);
case 97: { 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 98: { 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 99: { 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 100: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
case 101: return 'return true;';
case 102: return fb(102);
case 103: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(103); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(103); 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 @@ -2556,6 +2557,30 @@ function LoopItemsIntegerBoundedSized(instruction, instance, depth, template, ev
return true;
};

function LoopItemsArrayBoundedTypeStrictAny(instruction, instance, depth, template, evaluator) {
Comment thread
jviotti marked this conversation as resolved.
Resolved
const target = resolveInstance(instance, instruction[2]);
if (!Array.isArray(target)) return true;
if (evaluator.callbackMode) evaluator.callbackPush(instruction);
const bitmask = instruction[6][0];
const range = instruction[6][1];
for (let index = 0; index < target.length; index++) {
const item = target[index];
if (!Array.isArray(item) || item.length < range[0] ||
(range[1] !== null && range[1] !== undefined && item.length > range[1])) {
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
return false;
}
for (let nested = 0; nested < item.length; nested++) {
if (!typeSetTest(bitmask, effectiveTypeStrictReal(item[nested]))) {
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
return false;
}
}
}
if (evaluator.callbackMode) evaluator.callbackPop(instruction, true);
return true;
};

function LoopContains(instruction, instance, depth, template, evaluator) {
const target = resolveInstance(instance, instruction[2]);
if (!Array.isArray(target)) return true;
Expand Down Expand Up @@ -2794,14 +2819,15 @@ const handlers = [
LoopItemsPropertiesExactlyTypeStrictHash, // 92
LoopItemsIntegerBounded, // 93
LoopItemsIntegerBoundedSized, // 94
LoopContains, // 95
ControlGroup, // 96
ControlGroupWhenDefines, // 97
ControlGroupWhenDefinesDirect, // 98
ControlGroupWhenType, // 99
ControlEvaluate, // 100
ControlDynamicAnchorJump, // 101
ControlJump // 102
LoopItemsArrayBoundedTypeStrictAny, // 95
LoopContains, // 96
ControlGroup, // 97
ControlGroupWhenDefines, // 98
ControlGroupWhenDefinesDirect, // 99
ControlGroupWhenType, // 100
ControlEvaluate, // 101
ControlDynamicAnchorJump, // 102
ControlJump // 103
];

function AssertionTypeArrayBounded_fast(instruction, instance, depth, template, evaluator) {
Expand Down Expand Up @@ -3962,6 +3988,25 @@ function LoopItemsIntegerBounded_fast(instruction, instance, depth, template, ev
return true;
}

function LoopItemsArrayBoundedTypeStrictAny_fast(instruction, instance, depth, template, evaluator) {
const relInstance = instruction[2];
const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance);
if (!Array.isArray(target)) return true;
const bitmask = instruction[6][0];
const range = instruction[6][1];
const minimum = range[0];
const maximum = range[1];
for (let index = 0; index < target.length; index++) {
const item = target[index];
if (!Array.isArray(item) || item.length < minimum ||
(maximum !== null && maximum !== undefined && item.length > maximum)) return false;
for (let nested = 0; nested < item.length; nested++) {
if (!typeSetTest(bitmask, effectiveTypeStrictReal(item[nested]))) return false;
}
}
return true;
}

function LoopItemsIntegerBoundedSized_fast(instruction, instance, depth, template, evaluator) {
const value = instruction[6];
const minimum = value[0][0];
Expand Down Expand Up @@ -4025,7 +4070,7 @@ fastHandlers[4] = AssertionDefinesAllStrict_fast;
fastHandlers[27] = AssertionEqual_fast;
fastHandlers[68] = LoopPropertiesMatch_fast;
fastHandlers[59] = LogicalOr_fast;
fastHandlers[102] = ControlJump_fast;
fastHandlers[103] = ControlJump_fast;
fastHandlers[29] = AssertionEqualsAnyStringHash_fast;
fastHandlers[61] = LogicalXor_fast;
fastHandlers[2] = AssertionDefinesStrict_fast;
Expand All @@ -4043,7 +4088,8 @@ fastHandlers[1] = AssertionDefines_fast;
fastHandlers[63] = LogicalWhenType_fast;
fastHandlers[64] = LogicalWhenDefines_fast;
fastHandlers[0] = AssertionFail_fast;
fastHandlers[95] = LoopContains_fast;
fastHandlers[95] = LoopItemsArrayBoundedTypeStrictAny_fast;
fastHandlers[96] = LoopContains_fast;
fastHandlers[57] = LogicalNot_fast;
fastHandlers[88] = LoopItemsType_fast;
fastHandlers[89] = LoopItemsTypeStrict_fast;
Expand Down Expand Up @@ -4114,7 +4160,7 @@ fastHandlers[91] = LoopItemsPropertiesExactlyTypeStrictHash_fast;
fastHandlers[92] = LoopItemsPropertiesExactlyTypeStrictHash_fast;
fastHandlers[93] = LoopItemsIntegerBounded_fast;
fastHandlers[94] = LoopItemsIntegerBoundedSized_fast;
fastHandlers[101] = ControlDynamicAnchorJump_fast;
fastHandlers[102] = ControlDynamicAnchorJump_fast;

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

Expand Down
6 changes: 3 additions & 3 deletions ports/javascript/official.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 = [9, false, false, [[]], [], []];
const template = [10, false, false, [[]], [], []];
assert.throws(() => new Blaze(template), {
message: 'Only version 10 of the compiled template is supported by this version of the evaluator'
message: 'Only version 11 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 10 of the compiled template is supported by this version of the evaluator'
message: 'Only version 11 of the compiled template is supported by this version of the evaluator'
});
});
});
18 changes: 10 additions & 8 deletions ports/javascript/opcodes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,15 @@ export const LOOP_ITEMS_PROPERTIES_EXACTLY_TYPE_STRICT_HASH = 91;
export const LOOP_ITEMS_PROPERTIES_EXACTLY_TYPE_STRICT_HASH3 = 92;
export const LOOP_ITEMS_INTEGER_BOUNDED = 93;
export const LOOP_ITEMS_INTEGER_BOUNDED_SIZED = 94;
export const LOOP_CONTAINS = 95;
export const CONTROL_GROUP = 96;
export const CONTROL_GROUP_WHEN_DEFINES = 97;
export const CONTROL_GROUP_WHEN_DEFINES_DIRECT = 98;
export const CONTROL_GROUP_WHEN_TYPE = 99;
export const CONTROL_EVALUATE = 100;
export const CONTROL_DYNAMIC_ANCHOR_JUMP = 101;
export const CONTROL_JUMP = 102;
export const LOOP_ITEMS_ARRAY_BOUNDED_TYPE_STRICT_ANY = 95;
export const LOOP_CONTAINS = 96;
export const CONTROL_GROUP = 97;
export const CONTROL_GROUP_WHEN_DEFINES = 98;
export const CONTROL_GROUP_WHEN_DEFINES_DIRECT = 99;
export const CONTROL_GROUP_WHEN_TYPE = 100;
export const CONTROL_EVALUATE = 101;
export const CONTROL_DYNAMIC_ANCHOR_JUMP = 102;
export const CONTROL_JUMP = 103;

export const INSTRUCTION_NAMES = {
"AssertionFail": ASSERTION_FAIL,
Expand Down Expand Up @@ -198,6 +199,7 @@ export const INSTRUCTION_NAMES = {
"LoopItemsPropertiesExactlyTypeStrictHash3": LOOP_ITEMS_PROPERTIES_EXACTLY_TYPE_STRICT_HASH3,
"LoopItemsIntegerBounded": LOOP_ITEMS_INTEGER_BOUNDED,
"LoopItemsIntegerBoundedSized": LOOP_ITEMS_INTEGER_BOUNDED_SIZED,
"LoopItemsArrayBoundedTypeStrictAny": LOOP_ITEMS_ARRAY_BOUNDED_TYPE_STRICT_ANY,
"LoopContains": LOOP_CONTAINS,
"ControlGroup": CONTROL_GROUP,
"ControlGroupWhenDefines": CONTROL_GROUP_WHEN_DEFINES,
Expand Down
53 changes: 53 additions & 0 deletions src/compiler/default_compiler_draft3.h
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,51 @@ auto is_integer_bounded_pattern(const Instructions &children) -> bool {
return has_type && has_min && has_max;
}

auto is_bounded_array_items_pattern(const Instructions &children) -> bool {
if (children.size() != 2) {
return false;
}

bool has_types{false};
bool has_bounds{false};
for (const auto &child : children) {
// A child that looks anywhere other than the item itself is not part of
// the shape, whatever its type says
if (!child.relative_instance_location.empty()) {
return false;
}

if (child.type == InstructionIndex::LoopItemsTypeStrictAny) {
has_types = true;
} else if (child.type == InstructionIndex::AssertionTypeArrayBounded) {
// The fused instruction stops at the first item that does not match,
// which an exhaustive bound does not allow
if (std::get<2>(std::get<ValueRange>(child.value))) {
return false;
}

has_bounds = true;
}
}

return has_types && has_bounds;
}

auto extract_bounded_array_items(const Instructions &children)
-> ValueTypesWithSize {
ValueTypes types;
ValueRange bounds;
for (const auto &child : children) {
if (child.type == InstructionIndex::LoopItemsTypeStrictAny) {
types = std::get<ValueTypes>(child.value);
} else if (child.type == InstructionIndex::AssertionTypeArrayBounded) {
bounds = std::get<ValueRange>(child.value);
}
}

return {types, bounds};
}

auto compiler_draft3_applicator_items_with_options(
const Context &context, const SchemaContext &schema_context,
const DynamicContext &dynamic_context, const bool annotate,
Expand Down Expand Up @@ -2038,6 +2083,14 @@ auto compiler_draft3_applicator_items_with_options(
extract_integer_bounds(children))};
}

if (context.mode == Mode::FastValidation && children.size() == 2 &&
is_bounded_array_items_pattern(children)) {
return {make(sourcemeta::blaze::InstructionIndex::
LoopItemsArrayBoundedTypeStrictAny,
context, schema_context, dynamic_context,
extract_bounded_array_items(children))};
}

if (context.mode == Mode::FastValidation && children.size() == 1) {
if (children.front().type == InstructionIndex::AssertionTypeStrict) {
return {make(sourcemeta::blaze::InstructionIndex::LoopItemsTypeStrict,
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 @@ -1168,6 +1168,12 @@ auto describe(const bool valid, const Instruction &step,
"given range";
}

if (step.type ==
sourcemeta::blaze::InstructionIndex::LoopItemsArrayBoundedTypeStrictAny) {
return "Every item in the array was expected to be an array of the given "
"size whose items were of the given types";
}

if (step.type ==
sourcemeta::blaze::InstructionIndex::AssertionTypeIntegerBounded ||
step.type == sourcemeta::blaze::InstructionIndex::
Expand Down
1 change: 1 addition & 0 deletions src/evaluator/evaluator_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ auto value_from_json(const sourcemeta::core::JSON &wrapper)
case 23: return sourcemeta::core::from_json<ValueIntegerBoundsWithSize>(value);
case 24: return sourcemeta::core::from_json<ValueObjectProperties>(value);
case 25: return sourcemeta::core::from_json<ValueNumberType>(value);
case 26: return sourcemeta::core::from_json<ValueTypesWithSize>(value);
// clang-format on
default:
std::unreachable();
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 @@ -46,7 +46,7 @@ struct Template {
};

/// @ingroup evaluator
constexpr std::size_t JSON_VERSION{10};
constexpr std::size_t JSON_VERSION{11};

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

// `items` whose subschema is itself an `items` with a size bound compiles to a
// loop whose body is two instructions that both only look at the item, which
// means a dispatch per item of the outer array. Nesting the two loops here
// decides the whole shape in one instruction instead
INSTRUCTION_HANDLER(LoopItemsArrayBoundedTypeStrictAny) {
EVALUATE_BEGIN_NON_STRING(LoopItemsArrayBoundedTypeStrictAny,
target.is_array());
const auto &value{assume_value<ValueTypesWithSize>(instruction.value)};
const auto types{value.first};
const auto minimum{std::get<0>(value.second)};
const auto &maximum{std::get<1>(value.second)};
assert(types.any());
// Require early breaking
assert(!std::get<2>(value.second));
result = true;
for (const auto &item : target.as_array()) {
if (item.type() != JSON::Type::Array || item.array_size() < minimum ||
(maximum.has_value() && item.array_size() > maximum.value()))
[[unlikely]] {
result = false;
EVALUATE_END(LoopItemsArrayBoundedTypeStrictAny);
}

for (const auto &entry : item.as_array()) {
if (!types.test(std::to_underlying(effective_type_strict_real(entry))))
[[unlikely]] {
result = false;
EVALUATE_END(LoopItemsArrayBoundedTypeStrictAny);
}
}
}

EVALUATE_END(LoopItemsArrayBoundedTypeStrictAny);
}

INSTRUCTION_HANDLER(LoopContains) {
EVALUATE_BEGIN_NON_STRING(LoopContains, target.is_array());
// The subschema may compile to no children, in which case every array
Expand Down Expand Up @@ -2991,7 +3026,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[103] = {
static constexpr DispatchHandler<Track, Dynamic, HasCallback> HANDLERS[104] = {
AssertionFail,
AssertionDefines,
AssertionDefinesStrict,
Expand Down Expand Up @@ -3087,6 +3122,7 @@ static constexpr DispatchHandler<Track, Dynamic, HasCallback> HANDLERS[103] = {
LoopItemsPropertiesExactlyTypeStrictHash3,
LoopItemsIntegerBounded,
LoopItemsIntegerBoundedSized,
LoopItemsArrayBoundedTypeStrictAny,
LoopContains,
ControlGroup,
ControlGroupWhenDefines,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ enum class InstructionIndex : std::uint8_t {
LoopItemsPropertiesExactlyTypeStrictHash3,
LoopItemsIntegerBounded,
LoopItemsIntegerBoundedSized,
LoopItemsArrayBoundedTypeStrictAny,
LoopContains,
ControlGroup,
ControlGroupWhenDefines,
Expand Down Expand Up @@ -223,6 +224,7 @@ constexpr std::string_view INSTRUCTION_NAMES[] = {
"LoopItemsPropertiesExactlyTypeStrictHash3",
"LoopItemsIntegerBounded",
"LoopItemsIntegerBoundedSized",
"LoopItemsArrayBoundedTypeStrictAny",
"LoopContains",
"ControlGroup",
"ControlGroupWhenDefines",
Expand Down
6 changes: 5 additions & 1 deletion src/evaluator/include/sourcemeta/blaze/evaluator_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ using ValueIntegerBounds = std::pair<std::int64_t, std::int64_t>;
/// Represents integer bounds combined with array size range
using ValueIntegerBoundsWithSize = std::pair<ValueIntegerBounds, ValueRange>;

/// @ingroup evaluator
/// Represents a set of types combined with an array size range
using ValueTypesWithSize = std::pair<ValueTypes, ValueRange>;

/// @ingroup evaluator
/// Represents a list of object property entries: (name, hash, required)
using ValueObjectProperties = std::vector<
Expand All @@ -211,7 +215,7 @@ using Value =
ValuePropertyFilter, ValueIndexPair, ValuePointer,
ValueTypedProperties, ValueStringHashes, ValueTypedHashes,
ValueIntegerBounds, ValueIntegerBoundsWithSize,
ValueObjectProperties, ValueNumberType>;
ValueObjectProperties, ValueNumberType, ValueTypesWithSize>;

} // namespace sourcemeta::blaze

Expand Down
Loading
Loading