Skip to content

Commit 9cdd791

Browse files
authored
Prototype a new LoopItemsIntegerBounded instruction (#713)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent a718931 commit 9cdd791

16 files changed

Lines changed: 1376 additions & 47 deletions

ports/javascript/index.mjs

Lines changed: 61 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
const JSON_VERSION = 1;
1+
const JSON_VERSION = 2;
22
const DEPTH_LIMIT = 300;
33
const ANNOTATION_EMIT = 44;
44
const ANNOTATION_TO_PARENT = 45;
55
const ANNOTATION_BASENAME_TO_PARENT = 46;
6-
const CONTROL_GROUP_START = 85;
7-
const CONTROL_EVALUATE_END = 89;
6+
const CONTROL_GROUP_START = 86;
7+
const CONTROL_EVALUATE_END = 90;
88
const URI_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/;
99

1010
function buildJsonPointer(tokens, length) {
@@ -150,7 +150,7 @@ function prepareInstruction(instruction) {
150150
function resolveJumpTargets(instructions, targets) {
151151
for (let index = 0; index < instructions.length; index++) {
152152
const instruction = instructions[index];
153-
if (instruction[0] === 91) {
153+
if (instruction[0] === 92) {
154154
const targetIndex = instruction[5];
155155
if (targetIndex < targets.length) {
156156
instruction[5] = targets[targetIndex];
@@ -185,7 +185,7 @@ function collectAnchorNames(targets, result) {
185185
function collectAnchorNamesFromInstructions(instructions, result) {
186186
for (let index = 0; index < instructions.length; index++) {
187187
const instruction = instructions[index];
188-
if (instruction[0] === 90 && typeof instruction[5] === 'string') {
188+
if (instruction[0] === 91 && typeof instruction[5] === 'string') {
189189
result.add(instruction[5]);
190190
}
191191
if (instruction[6]) {
@@ -321,14 +321,14 @@ function compileInstructionToCode(instruction, captures, visited, budget) {
321321
case 79: { 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; }
322322
case 80: { 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; }
323323
case 81: { 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; }
324-
case 82: return fb(82); case 83: return fb(83); case 84: return fb(84);
325-
case 85: { 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;'; }
326-
case 86: { 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;'; }
327-
case 87: { 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;'; }
328-
case 88: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
329-
case 89: return 'return true;';
330-
case 90: return fb(90);
331-
case 91: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(91); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(91); 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;'; }
324+
case 82: return fb(82); case 83: return fb(83); case 84: return fb(84); case 85: return fb(85);
325+
case 86: { 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;'; }
326+
case 87: { 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;'; }
327+
case 88: { 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;'; }
328+
case 89: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; }
329+
case 90: return 'return true;';
330+
case 91: return fb(91);
331+
case 92: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(92); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(92); 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;'; }
332332
default: return null;
333333
}
334334
}
@@ -623,7 +623,7 @@ function evaluateInstructionTracked(instruction, instance, depth, template, eval
623623
if (!handler) return true;
624624

625625
const type = instruction[0];
626-
if (type < 85 || type > 89) {
626+
if (type < 86 || type > 90) {
627627
if (evaluator.trackMode) {
628628
evaluator.pushPath(instruction[1]);
629629
}
@@ -2288,6 +2288,28 @@ function LoopItemsPropertiesExactlyTypeStrictHash(instruction, instance, depth,
22882288
if (evaluator.callbackMode) evaluator.callbackPop(instruction, true);
22892289
return true;
22902290
};
2291+
2292+
function LoopItemsIntegerBounded(instruction, instance, depth, template, evaluator) {
2293+
const target = resolveInstance(instance, instruction[2]);
2294+
if (!Array.isArray(target) || target.length === 0) return true;
2295+
if (evaluator.callbackMode) evaluator.callbackPush(instruction);
2296+
const minimum = instruction[5][0];
2297+
const maximum = instruction[5][1];
2298+
for (let index = 0; index < target.length; index++) {
2299+
const element = target[index];
2300+
if (typeof element !== 'number') {
2301+
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
2302+
return false;
2303+
}
2304+
if (element < minimum || element > maximum) {
2305+
if (evaluator.callbackMode) evaluator.callbackPop(instruction, false);
2306+
return false;
2307+
}
2308+
}
2309+
if (evaluator.callbackMode) evaluator.callbackPop(instruction, true);
2310+
return true;
2311+
};
2312+
22912313
function LoopContains(instruction, instance, depth, template, evaluator) {
22922314
const target = resolveInstance(instance, instruction[2]);
22932315
if (!Array.isArray(target)) return true;
@@ -2516,14 +2538,15 @@ const handlers = [
25162538
LoopItemsTypeStrictAny, // 81
25172539
LoopItemsPropertiesExactlyTypeStrictHash, // 82
25182540
LoopItemsPropertiesExactlyTypeStrictHash, // 83
2519-
LoopContains, // 84
2520-
ControlGroup, // 85
2521-
ControlGroupWhenDefines, // 86
2522-
ControlGroupWhenDefinesDirect, // 87
2523-
ControlGroupWhenType, // 88
2524-
ControlEvaluate, // 89
2525-
ControlDynamicAnchorJump, // 90
2526-
ControlJump // 91
2541+
LoopItemsIntegerBounded, // 84
2542+
LoopContains, // 85
2543+
ControlGroup, // 86
2544+
ControlGroupWhenDefines, // 87
2545+
ControlGroupWhenDefinesDirect, // 88
2546+
ControlGroupWhenType, // 89
2547+
ControlEvaluate, // 90
2548+
ControlDynamicAnchorJump, // 91
2549+
ControlJump // 92
25272550
];
25282551

25292552
function AssertionTypeArrayBounded_fast(instruction, instance, depth, template, evaluator) {
@@ -3622,6 +3645,18 @@ function ControlDynamicAnchorJump_fast(instruction, instance, depth, template, e
36223645
return false;
36233646
}
36243647

3648+
function LoopItemsIntegerBounded_fast(instruction, instance, depth, template, evaluator) {
3649+
const target = resolveInstance(instance, instruction[2]);
3650+
if (!Array.isArray(target) || target.length === 0) return true;
3651+
const minimum = instruction[5][0];
3652+
const maximum = instruction[5][1];
3653+
for (let index = 0; index < target.length; index++) {
3654+
const element = target[index];
3655+
if (typeof element !== 'number' || element < minimum || element > maximum) return false;
3656+
}
3657+
return true;
3658+
}
3659+
36253660
const fastHandlers = handlers.slice();
36263661
fastHandlers[15] = AssertionTypeArrayBounded_fast;
36273662
fastHandlers[81] = LoopItemsTypeStrictAny_fast;
@@ -3631,7 +3666,7 @@ fastHandlers[4] = AssertionDefinesAllStrict_fast;
36313666
fastHandlers[26] = AssertionEqual_fast;
36323667
fastHandlers[59] = LoopPropertiesMatch_fast;
36333668
fastHandlers[50] = LogicalOr_fast;
3634-
fastHandlers[91] = ControlJump_fast;
3669+
fastHandlers[92] = ControlJump_fast;
36353670
fastHandlers[28] = AssertionEqualsAnyStringHash_fast;
36363671
fastHandlers[52] = LogicalXor_fast;
36373672
fastHandlers[2] = AssertionDefinesStrict_fast;
@@ -3649,7 +3684,7 @@ fastHandlers[1] = AssertionDefines_fast;
36493684
fastHandlers[54] = LogicalWhenType_fast;
36503685
fastHandlers[55] = LogicalWhenDefines_fast;
36513686
fastHandlers[0] = AssertionFail_fast;
3652-
fastHandlers[84] = LoopContains_fast;
3687+
fastHandlers[85] = LoopContains_fast;
36533688
fastHandlers[48] = LogicalNot_fast;
36543689
fastHandlers[79] = LoopItemsType_fast;
36553690
fastHandlers[80] = LoopItemsTypeStrict_fast;
@@ -3709,6 +3744,7 @@ fastHandlers[77] = LoopItemsFrom_fast;
37093744
fastHandlers[78] = LoopItemsUnevaluated_fast;
37103745
fastHandlers[82] = LoopItemsPropertiesExactlyTypeStrictHash_fast;
37113746
fastHandlers[83] = LoopItemsPropertiesExactlyTypeStrictHash_fast;
3712-
fastHandlers[90] = ControlDynamicAnchorJump_fast;
3747+
fastHandlers[84] = LoopItemsIntegerBounded_fast;
3748+
fastHandlers[91] = ControlDynamicAnchorJump_fast;
37133749

37143750
export { Blaze };

ports/javascript/test.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,15 @@ for (const [subdirectory, blacklist] of Object.entries(BLACKLISTS)) {
116116

117117
describe('version', () => {
118118
it('rejects a template with an unsupported version', () => {
119-
const template = [2, false, false, [[]], []];
119+
const template = [3, false, false, [[]], []];
120120
assert.throws(() => new Blaze(template), {
121-
message: 'Only version 1 of the compiled template is supported by this version of the evaluator'
121+
message: 'Only version 2 of the compiled template is supported by this version of the evaluator'
122122
});
123123
});
124124

125125
it('rejects a template that is not an array', () => {
126126
assert.throws(() => new Blaze({}), {
127-
message: 'Only version 1 of the compiled template is supported by this version of the evaluator'
127+
message: 'Only version 2 of the compiled template is supported by this version of the evaluator'
128128
});
129129
});
130130
});

ports/javascript/trace.mjs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,15 @@ const INSTRUCTION_NAMES = {
9595
"LoopItemsTypeStrictAny": 81,
9696
"LoopItemsPropertiesExactlyTypeStrictHash": 82,
9797
"LoopItemsPropertiesExactlyTypeStrictHash3": 83,
98-
"LoopContains": 84,
99-
"ControlGroup": 85,
100-
"ControlGroupWhenDefines": 86,
101-
"ControlGroupWhenDefinesDirect": 87,
102-
"ControlGroupWhenType": 88,
103-
"ControlEvaluate": 89,
104-
"ControlDynamicAnchorJump": 90,
105-
"ControlJump": 91,
98+
"LoopItemsIntegerBounded": 84,
99+
"LoopContains": 85,
100+
"ControlGroup": 86,
101+
"ControlGroupWhenDefines": 87,
102+
"ControlGroupWhenDefinesDirect": 88,
103+
"ControlGroupWhenType": 89,
104+
"ControlEvaluate": 90,
105+
"ControlDynamicAnchorJump": 91,
106+
"ControlJump": 92,
106107
"Annotation": -1
107108
};
108109

src/compiler/default_compiler_draft4.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,64 @@ auto compiler_draft4_applicator_items_array(
14741474
}
14751475
}
14761476

1477+
auto is_number_type_check(const Instruction &instruction) -> bool {
1478+
if (instruction.type != InstructionIndex::AssertionTypeStrictAny) {
1479+
return false;
1480+
}
1481+
1482+
const auto &value{std::get<ValueTypes>(instruction.value)};
1483+
const auto numeric_count{
1484+
static_cast<std::size_t>(value.test(
1485+
std::to_underlying(sourcemeta::core::JSON::Type::Integer))) +
1486+
static_cast<std::size_t>(
1487+
value.test(std::to_underlying(sourcemeta::core::JSON::Type::Real))) +
1488+
static_cast<std::size_t>(value.test(
1489+
std::to_underlying(sourcemeta::core::JSON::Type::Decimal)))};
1490+
return numeric_count >= 2 && value.count() == numeric_count;
1491+
}
1492+
1493+
auto is_integer_bounded_pattern(const Instructions &children) -> bool {
1494+
if (children.size() != 3) {
1495+
return false;
1496+
}
1497+
1498+
bool has_type{false};
1499+
bool has_min{false};
1500+
bool has_max{false};
1501+
for (const auto &child : children) {
1502+
if (is_number_type_check(child)) {
1503+
has_type = true;
1504+
} else if (child.type == InstructionIndex::AssertionGreaterEqual) {
1505+
if (!std::get<ValueJSON>(child.value).is_integer()) {
1506+
return false;
1507+
}
1508+
has_min = true;
1509+
} else if (child.type == InstructionIndex::AssertionLessEqual) {
1510+
if (!std::get<ValueJSON>(child.value).is_integer()) {
1511+
return false;
1512+
}
1513+
has_max = true;
1514+
}
1515+
}
1516+
1517+
return has_type && has_min && has_max;
1518+
}
1519+
1520+
auto extract_integer_bounds(const Instructions &children)
1521+
-> ValueIntegerBounds {
1522+
std::int64_t minimum{0};
1523+
std::int64_t maximum{0};
1524+
for (const auto &child : children) {
1525+
if (child.type == InstructionIndex::AssertionGreaterEqual) {
1526+
minimum = std::get<ValueJSON>(child.value).to_integer();
1527+
} else if (child.type == InstructionIndex::AssertionLessEqual) {
1528+
maximum = std::get<ValueJSON>(child.value).to_integer();
1529+
}
1530+
}
1531+
1532+
return {minimum, maximum};
1533+
}
1534+
14771535
auto compiler_draft4_applicator_items_with_options(
14781536
const Context &context, const SchemaContext &schema_context,
14791537
const DynamicContext &dynamic_context, const bool annotate,
@@ -1539,6 +1597,13 @@ auto compiler_draft4_applicator_items_with_options(
15391597
return {};
15401598
}
15411599

1600+
if (context.mode == Mode::FastValidation && children.size() == 3 &&
1601+
is_integer_bounded_pattern(children)) {
1602+
return {make(sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded,
1603+
context, schema_context, dynamic_context,
1604+
extract_integer_bounds(children))};
1605+
}
1606+
15421607
if (context.mode == Mode::FastValidation && children.size() == 1) {
15431608
if (children.front().type == InstructionIndex::AssertionTypeStrict) {
15441609
return {make(sourcemeta::blaze::InstructionIndex::LoopItemsTypeStrict,
@@ -1614,6 +1679,15 @@ auto compiler_draft4_applicator_additionalitems_from_cursor(
16141679
Instructions children;
16151680

16161681
if (!subchildren.empty()) {
1682+
if (context.mode == Mode::FastValidation && cursor == 0 && !annotate &&
1683+
!track_evaluation && is_integer_bounded_pattern(subchildren)) {
1684+
children.push_back(
1685+
make(sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded,
1686+
context, schema_context, dynamic_context,
1687+
extract_integer_bounds(subchildren)));
1688+
return children;
1689+
}
1690+
16171691
children.push_back(make(sourcemeta::blaze::InstructionIndex::LoopItemsFrom,
16181692
context, schema_context, dynamic_context,
16191693
ValueUnsignedInteger{cursor},

src/compiler/default_compiler_draft6.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ auto compiler_draft6_validation_type(const Context &context,
107107
LoopItemsPropertiesExactlyTypeStrictHash ||
108108
current.back().type ==
109109
sourcemeta::blaze::InstructionIndex::
110-
LoopItemsPropertiesExactlyTypeStrictHash3) &&
110+
LoopItemsPropertiesExactlyTypeStrictHash3 ||
111+
current.back().type ==
112+
sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded) &&
111113
current.back().relative_instance_location ==
112114
to_pointer(dynamic_context.base_instance_location)) {
113115
return {};

src/evaluator/evaluator_describe.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,12 @@ auto describe(const bool valid, const Instruction &step,
775775
return message.str();
776776
}
777777

778+
if (step.type ==
779+
sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded) {
780+
return "Every item in the array was expected to be a number within the "
781+
"given range";
782+
}
783+
778784
if (step.type == sourcemeta::blaze::InstructionIndex::LoopPropertiesType) {
779785
std::ostringstream message;
780786
message << "The object properties were expected to be of type "

src/evaluator/evaluator_json.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ auto value_from_json(const sourcemeta::core::JSON &wrapper)
4040
case 19: return sourcemeta::core::from_json<ValueTypedProperties>(value);
4141
case 20: return sourcemeta::core::from_json<ValueStringHashes>(value);
4242
case 21: return sourcemeta::core::from_json<ValueTypedHashes>(value);
43+
case 22:
44+
if (value.is_array() && value.array_size() == 2 &&
45+
value.at(0).is_integer() && value.at(1).is_integer()) {
46+
return ValueIntegerBounds{value.at(0).to_integer(),
47+
value.at(1).to_integer()};
48+
}
49+
return std::nullopt;
4350
// clang-format on
4451
default:
4552
std::unreachable();

src/evaluator/include/sourcemeta/blaze/evaluator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ struct Template {
4444
};
4545

4646
/// @ingroup evaluator
47-
constexpr std::size_t JSON_VERSION{1};
47+
constexpr std::size_t JSON_VERSION{2};
4848

4949
/// @ingroup evaluator
5050
/// Parse a template from JSON

0 commit comments

Comments
 (0)