Blaze 0.0.1
The ultra high-performance JSON Schema evaluator
Loading...
Searching...
No Matches
evaluator_dispatch.h
1#ifndef SOURCEMETA_BLAZE_EVALUATOR_DISPATCH_H_
2#define SOURCEMETA_BLAZE_EVALUATOR_DISPATCH_H_
3
4// NOLINTNEXTLINE(misc-header-include-cycle)
5#include <sourcemeta/blaze/evaluator.h>
6
7#include <sourcemeta/core/crypto.h>
8#include <sourcemeta/core/css.h>
9#include <sourcemeta/core/dns.h>
10#include <sourcemeta/core/email.h>
11#include <sourcemeta/core/ip.h>
12#include <sourcemeta/core/regex.h>
13#include <sourcemeta/core/time.h>
14#include <sourcemeta/core/uritemplate.h>
15
16#include <cassert> // assert
17
18// TODO(C++23): Replace SOURCEMETA_ASSUME with [[assume]] when available
19// across all compilers (Clang 19, GCC 13)
20#if defined(__clang__)
21#define SOURCEMETA_ASSUME(x) \
22 assert(x); \
23 __builtin_assume(x)
24#else
25#define SOURCEMETA_ASSUME(x) assert(x)
26#endif
27
28#define SOURCEMETA_STRINGIFY(x) #x
29
30#define EVALUATE_PUSH() \
31 if constexpr (Track) { \
32 context.evaluator->evaluate_path.push_back( \
33 context.schema->extra[instruction.extra_index] \
34 .relative_schema_location); \
35 } \
36 if constexpr (HasCallback) { \
37 context.evaluator->instance_location.push_back( \
38 instruction.relative_instance_location); \
39 } \
40 if constexpr (Dynamic) { \
41 context.evaluator->resources.push_back( \
42 context.schema->extra[instruction.extra_index].schema_resource); \
43 } \
44 if constexpr (HasCallback) { \
45 (*context.callback)(EvaluationType::Pre, true, instruction, \
46 context.schema->extra[instruction.extra_index], \
47 context.evaluator->evaluate_path, \
48 context.evaluator->instance_location, \
49 Evaluator::NULL_VALUE); \
50 }
51
52#define EVALUATE_POP() \
53 if constexpr (HasCallback) { \
54 (*context.callback)(EvaluationType::Post, result, instruction, \
55 context.schema->extra[instruction.extra_index], \
56 context.evaluator->evaluate_path, \
57 context.evaluator->instance_location, \
58 Evaluator::NULL_VALUE); \
59 } \
60 if constexpr (Track) { \
61 context.evaluator->evaluate_path.pop_back( \
62 context.schema->extra[instruction.extra_index] \
63 .relative_schema_location.size()); \
64 } \
65 if constexpr (HasCallback) { \
66 context.evaluator->instance_location.pop_back( \
67 instruction.relative_instance_location.size()); \
68 } \
69 if constexpr (Dynamic) { \
70 context.evaluator->resources.pop_back(); \
71 }
72
73#define EVALUATE_BEGIN(instruction_type, precondition) \
74 assert(instruction.type == InstructionIndex::instruction_type); \
75 const auto &target{resolve_target( \
76 context.property_target, \
77 resolve_instance(instance, instruction.relative_instance_location))}; \
78 if (!(precondition)) [[unlikely]] { \
79 return true; \
80 } \
81 [[maybe_unused]] constexpr bool track{Track || HasCallback}; \
82 EVALUATE_PUSH() \
83 bool result{false};
84
85#define EVALUATE_BEGIN_NON_STRING(instruction_type, precondition) \
86 assert(instruction.type == InstructionIndex::instruction_type); \
87 const auto &target{ \
88 resolve_instance(instance, instruction.relative_instance_location)}; \
89 if (!(precondition)) [[unlikely]] { \
90 return true; \
91 } \
92 [[maybe_unused]] constexpr bool track{Track || HasCallback}; \
93 EVALUATE_PUSH() \
94 bool result{false};
95
96#define EVALUATE_BEGIN_IF_STRING(instruction_type) \
97 assert(instruction.type == InstructionIndex::instruction_type); \
98 const auto *maybe_target{ \
99 resolve_string_target(context.property_target, instance, \
100 instruction.relative_instance_location)}; \
101 if (!maybe_target) [[unlikely]] { \
102 return true; \
103 } \
104 EVALUATE_PUSH() \
105 const auto &target{*maybe_target}; \
106 bool result{false};
107
108#define EVALUATE_BEGIN_TRY_TARGET(instruction_type) \
109 assert(instruction.type == InstructionIndex::instruction_type); \
110 const auto &target{instance}; \
111 if (!target.is_object()) [[unlikely]] { \
112 return true; \
113 } \
114 assert(!instruction.relative_instance_location.empty()); \
115 const auto *target_check{ \
116 instruction.relative_instance_location.size() == 1 \
117 ? target.try_at( \
118 instruction.relative_instance_location.at(0).to_property(), \
119 instruction.relative_instance_location.at(0).property_hash()) \
120 : try_get(target, instruction.relative_instance_location)}; \
121 if (!target_check) [[unlikely]] { \
122 return true; \
123 } \
124 EVALUATE_PUSH() \
125 bool result{false};
126
127#define EVALUATE_BEGIN_NO_PRECONDITION(instruction_type) \
128 assert(instruction.type == InstructionIndex::instruction_type); \
129 [[maybe_unused]] constexpr bool track{Track || HasCallback}; \
130 EVALUATE_PUSH() \
131 bool result{false};
132
133#define EVALUATE_BEGIN_NO_PRECONDITION_AND_NO_PUSH(instruction_type) \
134 assert(instruction.type == InstructionIndex::instruction_type); \
135 if constexpr (HasCallback) { \
136 (*context.callback)(EvaluationType::Pre, true, instruction, \
137 context.schema->extra[instruction.extra_index], \
138 context.evaluator->evaluate_path, \
139 context.evaluator->instance_location, \
140 Evaluator::NULL_VALUE); \
141 } \
142 bool result{true};
143
144#define EVALUATE_BEGIN_PASS_THROUGH(instruction_type) \
145 assert(instruction.type == InstructionIndex::instruction_type); \
146 bool result{true};
147
148#define EVALUATE_END(instruction_type) \
149 EVALUATE_POP() \
150 return result;
151
152#define EVALUATE_END_NO_POP(instruction_type) \
153 if constexpr (HasCallback) { \
154 (*context.callback)(EvaluationType::Post, result, instruction, \
155 context.schema->extra[instruction.extra_index], \
156 context.evaluator->evaluate_path, \
157 context.evaluator->instance_location, \
158 Evaluator::NULL_VALUE); \
159 } \
160 return result;
161
162#define EVALUATE_END_PASS_THROUGH(instruction_type) return result;
163
164#define EVALUATE_ANNOTATION(instruction_type, destination, annotation_value) \
165 if constexpr (HasCallback) { \
166 context.evaluator->evaluate_path.push_back( \
167 context.schema->extra[instruction.extra_index] \
168 .relative_schema_location); \
169 context.evaluator->instance_location.push_back( \
170 instruction.relative_instance_location); \
171 const auto &reported_location{destination}; \
172 (*context.callback)(EvaluationType::Pre, true, instruction, \
173 context.schema->extra[instruction.extra_index], \
174 context.evaluator->evaluate_path, reported_location, \
175 Evaluator::NULL_VALUE); \
176 (*context.callback)(EvaluationType::Post, true, instruction, \
177 context.schema->extra[instruction.extra_index], \
178 context.evaluator->evaluate_path, reported_location, \
179 annotation_value); \
180 context.evaluator->evaluate_path.pop_back( \
181 context.schema->extra[instruction.extra_index] \
182 .relative_schema_location.size()); \
183 context.evaluator->instance_location.pop_back( \
184 instruction.relative_instance_location.size()); \
185 } \
186 return true;
187
188#define EVALUATE_RECURSE(child, target) \
189 evaluate_instruction(child, target, depth + 1, context)
190#define EVALUATE_RECURSE_ON_PROPERTY_NAME(child, target, name) \
191 evaluate_instruction_with_property(child, target, depth + 1, context, name)
192
193namespace sourcemeta::blaze::dispatch {
194using namespace sourcemeta::core;
195
196// The recursion depth past which evaluation is assumed to be non-terminating
197constexpr auto DEPTH_LIMIT{300};
198
199inline auto resolve_target(const JSON::String *property_target,
200 const JSON &instance) noexcept -> const JSON & {
201 if (property_target != nullptr) [[unlikely]] {
202 return Evaluator::EMPTY_STRING;
203 }
204
205 // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter)
206 return instance;
207}
208
209inline auto resolve_instance(const JSON &instance,
210 const Pointer &relative_instance_location)
211 -> const JSON & {
212 if (relative_instance_location.empty()) {
213 // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter)
214 return instance;
215 }
216
217 // A single property is what almost every instruction that navigates at all
218 // carries, and resolving it here keeps the lookup inline, rather than
219 // calling out to the pointer walk that the general case needs
220 if (relative_instance_location.size() == 1 &&
221 relative_instance_location.at(0).is_property()) {
222 return instance.at(relative_instance_location.at(0).to_property(),
223 relative_instance_location.at(0).property_hash());
224 }
225
226 return get(instance, relative_instance_location);
227}
228
229inline auto
230resolve_string_target(const JSON::String *property_target, const JSON &instance,
231 const Pointer &relative_instance_location) noexcept
232 -> const JSON::String * {
233 if (property_target != nullptr) [[unlikely]] {
234 return property_target;
235 }
236
237 const auto &target{resolve_instance(instance, relative_instance_location)};
238 if (!target.is_string()) [[unlikely]] {
239 return nullptr;
240 } else {
241 return &target.to_string();
242 }
243}
244
245inline auto named_index(const ValueNamedIndexes &indexes,
246 const JSON::String &name,
247 const JSON::Object::hash_type &name_hash) noexcept
248 -> const ValueUnsignedInteger * {
249 // Few enough names come without a table of contents, as reaching one costs
250 // more than scanning them, so the whole range is the candidate range
251 std::size_t first{1};
252 std::size_t last{indexes.first.size()};
253 if (!indexes.second.empty()) {
254 // A name whose size no entry has cannot match, which settles most of the
255 // misses without comparing against any entry at all
256 if (name.size() >= indexes.second.size()) {
257 return nullptr;
258 }
259
260 const auto &bounds{indexes.second[name.size()]};
261 if (bounds.second == 0) {
262 return nullptr;
263 }
264
265 first = bounds.first;
266 last = bounds.second;
267 }
268
269 // A perfect hash records the name's size, so matching one is proof the names
270 // are equal and only a longer name needs comparing. Whether the hash is
271 // perfect depends on the name alone, so it is decided once rather than per
272 // entry, as `JSONObject` does for the same reason
273 constexpr sourcemeta::core::PropertyHashJSON<JSON::String> HASHER{};
274 if (HASHER.is_perfect(name_hash)) {
275 for (auto position = first; position <= last; position++) {
276 const auto &entry{indexes.first[position - 1]};
277 if (std::get<0>(entry) == name_hash) {
278 return &std::get<1>(entry);
279 }
280 }
281 } else {
282 for (auto position = first; position <= last; position++) {
283 const auto &entry{indexes.first[position - 1]};
284 if (std::get<0>(entry) == name_hash && std::get<2>(entry) == name) {
285 return &std::get<1>(entry);
286 }
287 }
288 }
289
290 return nullptr;
291}
292
293// `std::bitset::test` bounds-checks its index and throws when it is out of
294// range, which every type check would pay on every value it looks at. A
295// `JSON::Type` is always a valid index into a set that holds one bit per type,
296// so the check can never fire and the subscript is used instead
297inline auto type_set_test(const ValueTypes &types,
298 const JSON::Type type) noexcept -> bool {
299 assert(std::to_underlying(type) < types.size());
300 return types[std::to_underlying(type)];
301}
302
303inline auto effective_type_strict_real(const JSON &instance) noexcept
304 -> JSON::Type {
305 const auto real_type{instance.type()};
306 switch (real_type) {
307 case JSON::Type::Decimal:
308 return instance.to_decimal().is_integer() ? JSON::Type::Integer
309 : JSON::Type::Real;
310 default:
311 return real_type;
312 }
313}
314
315// An integral number that no 64-bit integer can hold cannot be narrowed to one
316// to be compared, and `as_integer` throws rather than saturating, so the
317// comparison happens in a domain wide enough to hold any of them instead.
318// A real converts through `exact_from` because the constructor spells a double
319// with 17 significant digits, which is too few to tell apart two integers that
320// a bound of this width can sit between
321// Normalising a decimal to the integer or real it represents cannot change
322// whether a type set holds it when the set treats all three number kinds
323// alike, which is what `"type": "number"` compiles to. Deciding this once per
324// instruction keeps the normalisation out of the loop, rather than trading its
325// call for a branch on every element
326inline auto type_set_spans_numbers(const ValueTypes &types) noexcept -> bool {
327 const auto integer{types[std::to_underlying(JSON::Type::Integer)]};
328 return types[std::to_underlying(JSON::Type::Real)] == integer &&
329 types[std::to_underlying(JSON::Type::Decimal)] == integer;
330}
331
332template <bool Normalise>
333inline auto type_in_set(const JSON &instance, const ValueTypes &types) noexcept
334 -> bool {
335 if constexpr (Normalise) {
336 return type_set_test(types, effective_type_strict_real(instance));
337 } else {
338 return type_set_test(types, instance.type());
339 }
340}
341
342template <bool Normalise>
343inline auto array_items_in_type_set(const JSON &target,
344 const ValueTypes &types) noexcept -> bool {
345 for (const auto &entry : target.as_array()) {
346 if (!type_in_set<Normalise>(entry, types)) [[unlikely]] {
347 return false;
348 }
349 }
350
351 return true;
352}
353
354template <bool Normalise>
355inline auto object_values_in_type_set(const JSON &target,
356 const ValueTypes &types) noexcept
357 -> bool {
358 for (const auto &entry : target.as_object()) {
359 if (!type_in_set<Normalise>(entry.second, types)) [[unlikely]] {
360 return false;
361 }
362 }
363
364 return true;
365}
366
367template <bool Normalise>
368inline auto items_are_bounded_arrays_in_type_set(
369 const JSON &target, const ValueTypes &types, const std::size_t minimum,
370 const std::optional<std::size_t> &maximum) noexcept -> bool {
371 for (const auto &item : target.as_array()) {
372 if (item.type() != JSON::Type::Array || item.array_size() < minimum ||
373 (maximum.has_value() && item.array_size() > maximum.value()))
374 [[unlikely]] {
375 return false;
376 }
377
378 if (!array_items_in_type_set<Normalise>(item, types)) [[unlikely]] {
379 return false;
380 }
381 }
382
383 return true;
384}
385
386inline auto as_decimal(const JSON &instance) -> Decimal {
387 assert(instance.is_integral());
388 return instance.is_decimal() ? instance.to_decimal()
389 : Decimal::exact_from(instance.to_real());
390}
391
392inline auto integer_within_bounds(const JSON &target,
393 const ValueIntegerBounds &value) -> bool {
394 if (target.is_integer()) {
395 const auto integer{target.to_integer()};
396 return integer >= value.first && integer <= value.second;
397 }
398
399 if (target.is_integral()) {
400 const auto decimal{as_decimal(target)};
401 return decimal >= Decimal{value.first} && decimal <= Decimal{value.second};
402 }
403
404 return false;
405}
406
407template <typename T>
408inline auto assume_value(const Value &variant) noexcept -> const T & {
409 const auto *pointer{std::get_if<T>(&variant)};
410 SOURCEMETA_ASSUME(pointer != nullptr);
411 return *pointer;
412}
413
414template <typename T>
415inline auto assume_value_copy(const Value &variant) noexcept -> T {
416 const auto *pointer{std::get_if<T>(&variant)};
417 SOURCEMETA_ASSUME(pointer != nullptr);
418 return *pointer;
419}
420
421template <bool Track, bool Dynamic, bool HasCallback> struct DispatchContext {
422 const sourcemeta::blaze::Template *schema;
423 const sourcemeta::blaze::Callback *callback;
425 const sourcemeta::core::JSON::String *property_target;
426};
427
428template <bool Track, bool Dynamic, bool HasCallback>
429inline auto
430evaluate_instruction(const sourcemeta::blaze::Instruction &instruction,
431 const sourcemeta::core::JSON &instance,
432 const std::uint64_t depth,
434 -> bool;
435
436template <bool Track, bool Dynamic, bool HasCallback>
437inline auto evaluate_instruction_with_property(
438 const sourcemeta::blaze::Instruction &instruction,
439 const sourcemeta::core::JSON &instance, const std::uint64_t depth,
441 const sourcemeta::core::JSON::String &name) -> bool;
442
443#define INSTRUCTION_HANDLER(name) \
444 template <bool Track, bool Dynamic, bool HasCallback> \
445 static inline auto name( \
446 [[maybe_unused]] const sourcemeta::blaze::Instruction &instruction, \
447 [[maybe_unused]] const sourcemeta::core::JSON &instance, \
448 [[maybe_unused]] const std::uint64_t depth, \
449 [[maybe_unused]] DispatchContext<Track, Dynamic, HasCallback> &context) \
450 -> bool
451
452#define INSTRUCTION_DIRECT(name, value_type) \
453 SOURCEMETA_FORCEINLINE inline auto DIRECT_##name( \
454 const sourcemeta::core::JSON &target, const value_type &value) \
455 ->bool
456
457#define INSTRUCTION_DIRECT_COPY(name, value_type) \
458 SOURCEMETA_FORCEINLINE inline auto DIRECT_##name( \
459 const sourcemeta::core::JSON &target, const value_type value) \
460 ->bool
461
462#define DIRECT(name, target, value) DIRECT_##name(target, value)
463
464// Forward declarations
465INSTRUCTION_DIRECT_COPY(LoopItemsIntegerBounded, ValueIntegerBounds);
466
467// These handlers take their names from the instructions they implement, which
468// `InstructionIndex` spells the same way and trace output emits verbatim
469// NOLINTBEGIN(readability-identifier-naming)
470INSTRUCTION_HANDLER(AssertionFail) {
471 EVALUATE_BEGIN_NO_PRECONDITION(AssertionFail);
472 EVALUATE_END(AssertionFail);
473}
474
475INSTRUCTION_HANDLER(AssertionDefines) {
476 EVALUATE_BEGIN_NON_STRING(AssertionDefines, target.is_object());
477 const auto &value{assume_value<ValueProperty>(instruction.value)};
478 result = target.defines(value.first, value.second);
479 EVALUATE_END(AssertionDefines);
480}
481
482INSTRUCTION_HANDLER(AssertionDefinesStrict) {
483 EVALUATE_BEGIN_NO_PRECONDITION(AssertionDefinesStrict);
484 const auto &target{
485 resolve_instance(instance, instruction.relative_instance_location)};
486 const auto &value{assume_value<ValueProperty>(instruction.value)};
487 result = target.is_object() && target.defines(value.first, value.second);
488 EVALUATE_END(AssertionDefinesStrict);
489}
490
491INSTRUCTION_HANDLER(AssertionDefinesAll) {
492 EVALUATE_BEGIN_NON_STRING(AssertionDefinesAll, target.is_object());
493 const auto &value{assume_value<ValueStringSet>(instruction.value)};
494 // Otherwise we are we even emitting this instruction?
495 assert(value.size() > 1);
496
497 // Otherwise there is no way the instance can satisfy it anyway
498 if (value.size() <= target.object_size()) [[likely]] {
499 result = true;
500 const auto &object{target.as_object()};
501 for (const auto &property : value) {
502 if (!object.defines(property.first, property.second)) [[unlikely]] {
503 result = false;
504 break;
505 }
506 }
507 }
508
509 EVALUATE_END(AssertionDefinesAll);
510}
511
512INSTRUCTION_HANDLER(AssertionDefinesAllStrict) {
513 EVALUATE_BEGIN_NO_PRECONDITION(AssertionDefinesAllStrict);
514 const auto &target{
515 resolve_instance(instance, instruction.relative_instance_location)};
516 const auto &value{assume_value<ValueStringSet>(instruction.value)};
517 // Otherwise we are we even emitting this instruction?
518 assert(value.size() > 1);
519
520 // Otherwise there is no way the instance can satisfy it anyway
521 if (target.is_object() && value.size() <= target.object_size()) [[likely]] {
522 result = true;
523 const auto &object{target.as_object()};
524 for (const auto &property : value) {
525 if (!object.defines(property.first, property.second)) [[unlikely]] {
526 result = false;
527 break;
528 }
529 }
530 }
531
532 EVALUATE_END(AssertionDefinesAllStrict);
533}
534
535INSTRUCTION_HANDLER(AssertionDefinesExactly) {
536 EVALUATE_BEGIN_NON_STRING(AssertionDefinesExactly, target.is_object());
537 const auto &value{assume_value<ValueStringSet>(instruction.value)};
538 // Otherwise we are we even emitting this instruction?
539 assert(value.size() > 1);
540 const auto &object{target.as_object()};
541
542 if (value.size() == object.size()) [[likely]] {
543 result = true;
544 for (const auto &property : value) {
545 if (!object.defines(property.first, property.second)) [[unlikely]] {
546 result = false;
547 break;
548 }
549 }
550 }
551
552 EVALUATE_END(AssertionDefinesExactly);
553}
554
555INSTRUCTION_HANDLER(AssertionDefinesExactlyStrict) {
556 EVALUATE_BEGIN_NO_PRECONDITION(AssertionDefinesExactlyStrict);
557 const auto &target{
558 resolve_instance(instance, instruction.relative_instance_location)};
559 if (target.is_object()) [[likely]] {
560 const auto &value{assume_value<ValueStringSet>(instruction.value)};
561 // Otherwise we are we even emitting this instruction?
562 assert(value.size() > 1);
563 const auto &object{target.as_object()};
564
565 if (value.size() == object.size()) [[likely]] {
566 result = true;
567 for (const auto &property : value) {
568 if (!object.defines(property.first, property.second)) [[unlikely]] {
569 result = false;
570 break;
571 }
572 }
573 }
574 }
575
576 EVALUATE_END(AssertionDefinesExactlyStrict);
577}
578
579INSTRUCTION_HANDLER(AssertionDefinesExactlyStrictHash3) {
580 EVALUATE_BEGIN_NO_PRECONDITION(AssertionDefinesExactlyStrictHash3);
581 const auto &target{
582 resolve_instance(instance, instruction.relative_instance_location)};
583 if (target.is_object()) [[likely]] {
584 // TODO: Take advantage of the table of contents structure to speed up
585 // checks
586 const auto &value{assume_value<ValueStringHashes>(instruction.value)};
587 assert(value.first.size() == 3);
588 const auto &object{target.as_object()};
589
590 // A perfect hash records the key's size, which the compiler requires of
591 // every key here, so matching one proves the keys equal and the unrolled
592 // permutation check compares nothing but hashes
593 result =
594 object.size() == 3 && ((value.first.at(0).first == object.at(0).hash &&
595 value.first.at(1).first == object.at(1).hash &&
596 value.first.at(2).first == object.at(2).hash) ||
597 (value.first.at(0).first == object.at(0).hash &&
598 value.first.at(1).first == object.at(2).hash &&
599 value.first.at(2).first == object.at(1).hash) ||
600 (value.first.at(0).first == object.at(1).hash &&
601 value.first.at(1).first == object.at(0).hash &&
602 value.first.at(2).first == object.at(2).hash) ||
603 (value.first.at(0).first == object.at(1).hash &&
604 value.first.at(1).first == object.at(2).hash &&
605 value.first.at(2).first == object.at(0).hash) ||
606 (value.first.at(0).first == object.at(2).hash &&
607 value.first.at(1).first == object.at(0).hash &&
608 value.first.at(2).first == object.at(1).hash) ||
609 (value.first.at(0).first == object.at(2).hash &&
610 value.first.at(1).first == object.at(1).hash &&
611 value.first.at(2).first == object.at(0).hash));
612 }
613
614 EVALUATE_END(AssertionDefinesExactlyStrictHash3);
615}
616
617INSTRUCTION_HANDLER(AssertionPropertyDependencies) {
618 EVALUATE_BEGIN_NON_STRING(AssertionPropertyDependencies, target.is_object());
619 const auto &value{assume_value<ValueStringMap>(instruction.value)};
620 // Otherwise we are we even emitting this instruction?
621 assert(!value.empty());
622 result = true;
623 const auto &object{target.as_object()};
624 const sourcemeta::core::PropertyHashJSON<ValueString> hasher;
625 for (const auto &entry : value) {
626 if (!object.defines(entry.first, entry.hash)) {
627 continue;
628 }
629
630 assert(!entry.second.empty());
631 for (const auto &dependency : entry.second) {
632 if (!object.defines(dependency, hasher(dependency))) [[unlikely]] {
633 result = false;
634 EVALUATE_END(AssertionPropertyDependencies);
635 }
636 }
637 }
638
639 EVALUATE_END(AssertionPropertyDependencies);
640}
641
642INSTRUCTION_DIRECT_COPY(AssertionType, ValueType) {
643 // In non-strict mode, we consider a real number that represents an
644 // integer to be an integer
645 return target.type() == value ||
646 (value == JSON::Type::Integer && target.is_integral());
647}
648
649INSTRUCTION_HANDLER(AssertionType) {
650 EVALUATE_BEGIN_NO_PRECONDITION(AssertionType);
651 const auto &target{
652 resolve_instance(instance, instruction.relative_instance_location)};
653 const auto value{assume_value_copy<ValueType>(instruction.value)};
654 // TODO: Maybe make this instruction about integers, as it is the
655 // only where where it is actually useful?
656 assert(value == JSON::Type::Integer);
657 SOURCEMETA_ASSUME(value == JSON::Type::Integer);
658 result = DIRECT(AssertionType, target, value);
659 EVALUATE_END(AssertionType);
660}
661
662INSTRUCTION_DIRECT(AssertionTypeAny, ValueTypes) {
663 // In non-strict mode, we consider a real number that represents an
664 // integer to be an integer
665 return type_set_test(value, target.type()) ||
666 (type_set_test(value, JSON::Type::Integer) && target.is_integral());
667}
668
669INSTRUCTION_HANDLER(AssertionTypeAny) {
670 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeAny);
671 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
672 assert(value.any());
673 const auto &target{
674 resolve_instance(instance, instruction.relative_instance_location)};
675 result = DIRECT(AssertionTypeAny, target, value);
676 EVALUATE_END(AssertionTypeAny);
677}
678
679INSTRUCTION_DIRECT_COPY(AssertionTypeStrict, ValueType) {
680 return effective_type_strict_real(target) == value;
681}
682
683INSTRUCTION_HANDLER(AssertionTypeStrict) {
684 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeStrict);
685 const auto &target{
686 resolve_instance(instance, instruction.relative_instance_location)};
687 const auto value{assume_value_copy<ValueType>(instruction.value)};
688 result = DIRECT(AssertionTypeStrict, target, value);
689 EVALUATE_END(AssertionTypeStrict);
690}
691
692INSTRUCTION_DIRECT(AssertionTypeStrictAny, ValueTypes) {
693 return type_set_test(value, effective_type_strict_real(target));
694}
695
696INSTRUCTION_HANDLER(AssertionTypeStrictAny) {
697 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeStrictAny);
698 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
699 assert(value.any());
700 const auto &target{
701 resolve_instance(instance, instruction.relative_instance_location)};
702 result = DIRECT(AssertionTypeStrictAny, target, value);
703 EVALUATE_END(AssertionTypeStrictAny);
704}
705
706INSTRUCTION_DIRECT(AssertionNotTypeStrictAny, ValueTypes) {
707 return !type_set_test(value, effective_type_strict_real(target));
708}
709
710INSTRUCTION_HANDLER(AssertionNotTypeStrictAny) {
711 EVALUATE_BEGIN_NO_PRECONDITION(AssertionNotTypeStrictAny);
712 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
713 assert(value.any());
714 const auto &target{
715 resolve_instance(instance, instruction.relative_instance_location)};
716 result = DIRECT(AssertionNotTypeStrictAny, target, value);
717 EVALUATE_END(AssertionNotTypeStrictAny);
718}
719
720INSTRUCTION_DIRECT(AssertionTypeStringBounded, ValueRange) {
721 const auto &[minimum, maximum, exhaustive] = value;
722 return target.type() == JSON::Type::String &&
723 target.string_size() >= minimum &&
724 (!maximum.has_value() || target.string_size() <= maximum.value());
725}
726
727INSTRUCTION_HANDLER(AssertionTypeStringBounded) {
728 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeStringBounded);
729 const auto &target{
730 resolve_instance(instance, instruction.relative_instance_location)};
731 const auto &value{assume_value<ValueRange>(instruction.value)};
732 assert(!std::get<1>(value).has_value() ||
733 std::get<1>(value).value() >= std::get<0>(value));
734 assert(!std::get<2>(value));
735 result = DIRECT(AssertionTypeStringBounded, target, value);
736 EVALUATE_END(AssertionTypeStringBounded);
737}
738
739INSTRUCTION_HANDLER(AssertionTypeStringUpper) {
740 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeStringUpper);
741 const auto &target{
742 resolve_instance(instance, instruction.relative_instance_location)};
743 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
744 result = target.is_string() && target.string_size() <= value;
745 EVALUATE_END(AssertionTypeStringUpper);
746}
747
748INSTRUCTION_DIRECT(AssertionTypeArrayBounded, ValueRange) {
749 const auto &[minimum, maximum, exhaustive] = value;
750 // Require early breaking
751 assert(!exhaustive);
752 SOURCEMETA_ASSUME(!exhaustive);
753 return target.type() == JSON::Type::Array && target.array_size() >= minimum &&
754 (!maximum.has_value() || target.array_size() <= maximum.value());
755}
756
757INSTRUCTION_HANDLER(AssertionTypeArrayBounded) {
758 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeArrayBounded);
759 const auto &target{
760 resolve_instance(instance, instruction.relative_instance_location)};
761 const auto &value{assume_value<ValueRange>(instruction.value)};
762 assert(!std::get<1>(value).has_value() ||
763 std::get<1>(value).value() >= std::get<0>(value));
764 result = DIRECT(AssertionTypeArrayBounded, target, value);
765 EVALUATE_END(AssertionTypeArrayBounded);
766}
767
768INSTRUCTION_HANDLER(AssertionTypeArrayUpper) {
769 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeArrayUpper);
770 const auto &target{
771 resolve_instance(instance, instruction.relative_instance_location)};
772 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
773 result = target.is_array() && target.array_size() <= value;
774 EVALUATE_END(AssertionTypeArrayUpper);
775}
776
777INSTRUCTION_HANDLER(AssertionTypeObjectBounded) {
778 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeObjectBounded);
779 const auto &target{
780 resolve_instance(instance, instruction.relative_instance_location)};
781 const auto &value{assume_value<ValueRange>(instruction.value)};
782 const auto &[minimum, maximum, exhaustive] = value;
783 assert(!maximum.has_value() || maximum.value() >= minimum);
784 // Require early breaking
785 assert(!exhaustive);
786 SOURCEMETA_ASSUME(!exhaustive);
787 result = target.type() == JSON::Type::Object &&
788 target.object_size() >= minimum &&
789 (!maximum.has_value() || target.object_size() <= maximum.value());
790 EVALUATE_END(AssertionTypeObjectBounded);
791}
792
793INSTRUCTION_HANDLER(AssertionTypeObjectUpper) {
794 EVALUATE_BEGIN_NO_PRECONDITION(AssertionTypeObjectUpper);
795 const auto &target{
796 resolve_instance(instance, instruction.relative_instance_location)};
797 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
798 result = target.is_object() && target.object_size() <= value;
799 EVALUATE_END(AssertionTypeObjectUpper);
800}
801
802INSTRUCTION_HANDLER(AssertionRegex) {
803 EVALUATE_BEGIN_IF_STRING(AssertionRegex);
804 const auto &value{assume_value<ValueRegex>(instruction.value)};
805 result = matches(value.first, target);
806 EVALUATE_END(AssertionRegex);
807}
808
809INSTRUCTION_HANDLER(AssertionStringSizeLess) {
810 EVALUATE_BEGIN_IF_STRING(AssertionStringSizeLess);
811 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
812 result = (JSON::size(target) < value);
813 EVALUATE_END(AssertionStringSizeLess);
814}
815
816INSTRUCTION_HANDLER(AssertionStringSizeGreater) {
817 EVALUATE_BEGIN_IF_STRING(AssertionStringSizeGreater);
818 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
819 result = (JSON::size(target) > value);
820 EVALUATE_END(AssertionStringSizeGreater);
821}
822
823INSTRUCTION_HANDLER(AssertionArraySizeLess) {
824 EVALUATE_BEGIN_NON_STRING(AssertionArraySizeLess, target.is_array());
825 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
826 result = (target.array_size() < value);
827 EVALUATE_END(AssertionArraySizeLess);
828}
829
830INSTRUCTION_HANDLER(AssertionArraySizeGreater) {
831 EVALUATE_BEGIN_NON_STRING(AssertionArraySizeGreater, target.is_array());
832 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
833 result = (target.array_size() > value);
834 EVALUATE_END(AssertionArraySizeGreater);
835}
836
837INSTRUCTION_HANDLER(AssertionObjectSizeLess) {
838 EVALUATE_BEGIN_NON_STRING(AssertionObjectSizeLess, target.is_object());
839 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
840 result = (target.object_size() < value);
841 EVALUATE_END(AssertionObjectSizeLess);
842}
843
844INSTRUCTION_HANDLER(AssertionObjectSizeGreater) {
845 EVALUATE_BEGIN_NON_STRING(AssertionObjectSizeGreater, target.is_object());
846 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
847 result = (target.object_size() > value);
848 EVALUATE_END(AssertionObjectSizeGreater);
849}
850
851INSTRUCTION_DIRECT(AssertionEqual, ValueJSON) { return target == value; }
852
853INSTRUCTION_HANDLER(AssertionEqual) {
854 EVALUATE_BEGIN_NO_PRECONDITION(AssertionEqual);
855 const auto &value{assume_value<ValueJSON>(instruction.value)};
856
857 if (context.property_target) [[unlikely]] {
858 result = value.is_string() && value.to_string() == *context.property_target;
859 } else {
860 const auto &target{
861 resolve_instance(instance, instruction.relative_instance_location)};
862 result = DIRECT(AssertionEqual, target, value);
863 }
864
865 EVALUATE_END(AssertionEqual);
866}
867
868INSTRUCTION_DIRECT(AssertionEqualsAny, ValueSet) {
869 return value.contains(target);
870}
871
872INSTRUCTION_HANDLER(AssertionEqualsAny) {
873 EVALUATE_BEGIN_NO_PRECONDITION(AssertionEqualsAny);
874 const auto &value{assume_value<ValueSet>(instruction.value)};
875
876 if (context.property_target) [[unlikely]] {
877 // TODO: This involves a string copy
878 result = value.contains(JSON{*context.property_target});
879 } else {
880 const auto &target{
881 resolve_instance(instance, instruction.relative_instance_location)};
882 result = DIRECT(AssertionEqualsAny, target, value);
883 }
884
885 EVALUATE_END(AssertionEqualsAny);
886}
887
888INSTRUCTION_DIRECT(AssertionEqualsAnyStringHash, ValueStringHashes) {
889 if (!target.is_string()) [[unlikely]] {
890 return false;
891 }
892 const auto &target_string{target.to_string()};
893 const auto string_size{target_string.size()};
894 const sourcemeta::core::PropertyHashJSON<ValueString> hasher;
895 const auto value_hash{hasher(target_string)};
896 if (string_size < value.second.size()) [[likely]] {
897 const auto &hint{value.second[string_size]};
898 if (hint.second != 0) [[likely]] {
899 for (std::size_t index = hint.first - 1; index < hint.second; index++) {
900 if (value.first[index].first == value_hash) {
901 return true;
902 }
903 }
904 }
905 }
906 return false;
907}
908
909INSTRUCTION_HANDLER(AssertionEqualsAnyStringHash) {
910 EVALUATE_BEGIN_NO_PRECONDITION(AssertionEqualsAnyStringHash);
911 const auto &value{assume_value<ValueStringHashes>(instruction.value)};
912
913 if (context.property_target) [[unlikely]] {
914 const sourcemeta::core::PropertyHashJSON<ValueString> hasher;
915 const auto value_hash{hasher(*context.property_target)};
916 const auto string_size{context.property_target->size()};
917 if (string_size < value.second.size()) [[likely]] {
918 const auto &hint{value.second[string_size]};
919 assert(hint.first <= hint.second);
920 SOURCEMETA_ASSUME(hint.first <= hint.second);
921 if (hint.second != 0) [[likely]] {
922 for (std::size_t index = hint.first - 1; index < hint.second; index++) {
923 assert(hasher.is_perfect(value.first[index].first));
924 if (value.first[index].first == value_hash) {
925 result = true;
926 break;
927 }
928 }
929 }
930 }
931 } else {
932 const auto &target{
933 resolve_instance(instance, instruction.relative_instance_location)};
934 result = DIRECT(AssertionEqualsAnyStringHash, target, value);
935 }
936
937 EVALUATE_END(AssertionEqualsAnyStringHash);
938}
939
940INSTRUCTION_HANDLER(AssertionGreaterEqual) {
941 EVALUATE_BEGIN_NON_STRING(AssertionGreaterEqual, target.is_number());
942 const auto &value{assume_value<ValueJSON>(instruction.value)};
943 result = target >= value;
944 EVALUATE_END(AssertionGreaterEqual);
945}
946
947INSTRUCTION_HANDLER(AssertionLessEqual) {
948 EVALUATE_BEGIN_NON_STRING(AssertionLessEqual, target.is_number());
949 const auto &value{assume_value<ValueJSON>(instruction.value)};
950 result = target <= value;
951 EVALUATE_END(AssertionLessEqual);
952}
953
954INSTRUCTION_HANDLER(AssertionGreater) {
955 EVALUATE_BEGIN_NON_STRING(AssertionGreater, target.is_number());
956 const auto &value{assume_value<ValueJSON>(instruction.value)};
957 result = target > value;
958 EVALUATE_END(AssertionGreater);
959}
960
961INSTRUCTION_HANDLER(AssertionLess) {
962 EVALUATE_BEGIN_NON_STRING(AssertionLess, target.is_number());
963 const auto &value{assume_value<ValueJSON>(instruction.value)};
964 result = target < value;
965 EVALUATE_END(AssertionLess);
966}
967
968INSTRUCTION_HANDLER(AssertionUnique) {
969 EVALUATE_BEGIN_NON_STRING(AssertionUnique, target.is_array());
970 result = target.unique();
971 EVALUATE_END(AssertionUnique);
972}
973
974INSTRUCTION_HANDLER(AssertionDivisible) {
975 EVALUATE_BEGIN_NON_STRING(AssertionDivisible, target.is_number());
976 const auto &value{assume_value<ValueJSON>(instruction.value)};
977 assert(value.is_number());
978 result = target.divisible_by(value);
979 EVALUATE_END(AssertionDivisible);
980}
981
982INSTRUCTION_DIRECT(AssertionTypeIntegerBounded, ValueIntegerBounds) {
983 return integer_within_bounds(target, value);
984}
985
986INSTRUCTION_HANDLER(AssertionTypeIntegerBounded) {
987 EVALUATE_BEGIN_NON_STRING(AssertionTypeIntegerBounded, true);
988 result = DIRECT(AssertionTypeIntegerBounded, target,
989 assume_value_copy<ValueIntegerBounds>(instruction.value));
990 EVALUATE_END(AssertionTypeIntegerBounded);
991}
992
993INSTRUCTION_DIRECT(AssertionTypeIntegerBoundedStrict, ValueIntegerBounds) {
994 if (target.is_integer()) {
995 const auto integer{target.to_integer()};
996 return integer >= value.first && integer <= value.second;
997 }
998 return false;
999}
1000
1001INSTRUCTION_HANDLER(AssertionTypeIntegerBoundedStrict) {
1002 EVALUATE_BEGIN_NON_STRING(AssertionTypeIntegerBoundedStrict, true);
1003 result = DIRECT(AssertionTypeIntegerBoundedStrict, target,
1004 assume_value_copy<ValueIntegerBounds>(instruction.value));
1005 EVALUATE_END(AssertionTypeIntegerBoundedStrict);
1006}
1007
1008INSTRUCTION_DIRECT(AssertionTypeIntegerLowerBound, ValueIntegerBounds) {
1009 if (target.is_integer()) {
1010 return target.to_integer() >= value.first;
1011 }
1012 if (target.is_integral()) {
1013 return as_decimal(target) >= Decimal{value.first};
1014 }
1015 return false;
1016}
1017
1018INSTRUCTION_HANDLER(AssertionTypeIntegerLowerBound) {
1019 EVALUATE_BEGIN_NON_STRING(AssertionTypeIntegerLowerBound, true);
1020 result = DIRECT(AssertionTypeIntegerLowerBound, target,
1021 assume_value_copy<ValueIntegerBounds>(instruction.value));
1022 EVALUATE_END(AssertionTypeIntegerLowerBound);
1023}
1024
1025INSTRUCTION_DIRECT(AssertionTypeIntegerLowerBoundStrict, ValueIntegerBounds) {
1026 if (target.is_integer()) {
1027 return target.to_integer() >= value.first;
1028 }
1029 // An integer too large for 64 bits is held as a decimal, which the strict
1030 // type check still reads as an integer when it was written as digits alone.
1031 // The bounded counterpart needs no such branch, as a value out of that range
1032 // cannot sit within bounds of that width either way
1033 if (effective_type_strict_real(target) == JSON::Type::Integer) {
1034 return target.to_decimal() >= Decimal{value.first};
1035 }
1036 return false;
1037}
1038
1039INSTRUCTION_HANDLER(AssertionTypeIntegerLowerBoundStrict) {
1040 EVALUATE_BEGIN_NON_STRING(AssertionTypeIntegerLowerBoundStrict, true);
1041 result = DIRECT(AssertionTypeIntegerLowerBoundStrict, target,
1042 assume_value_copy<ValueIntegerBounds>(instruction.value));
1043 EVALUATE_END(AssertionTypeIntegerLowerBoundStrict);
1044}
1045
1046INSTRUCTION_DIRECT(AssertionNumberIntegerBounded, ValueIntegerBounds) {
1047 return integer_within_bounds(target, value);
1048}
1049
1050INSTRUCTION_HANDLER(AssertionNumberIntegerBounded) {
1051 EVALUATE_BEGIN_NON_STRING(AssertionNumberIntegerBounded, target.is_number());
1052 result = DIRECT(AssertionNumberIntegerBounded, target,
1053 assume_value_copy<ValueIntegerBounds>(instruction.value));
1054 EVALUATE_END(AssertionNumberIntegerBounded);
1055}
1056
1057INSTRUCTION_HANDLER(AssertionNumberType) {
1058 EVALUATE_BEGIN_NON_STRING(AssertionNumberType, target.is_number());
1059 switch (assume_value_copy<ValueNumberType>(instruction.value)) {
1060 case ValueNumberType::Float:
1061 result = target.is_float();
1062 break;
1063 case ValueNumberType::Double:
1064 result = target.is_double();
1065 break;
1066 }
1067 EVALUATE_END(AssertionNumberType);
1068}
1069
1070INSTRUCTION_HANDLER(AssertionStringType) {
1071 EVALUATE_BEGIN_IF_STRING(AssertionStringType);
1072 const auto value{assume_value_copy<ValueStringType>(instruction.value)};
1073 switch (value) {
1074 case ValueStringType::URI:
1075 result = URI::is_uri(target);
1076 break;
1077 case ValueStringType::URIReference:
1078 result = URI::is_uri_reference(target);
1079 break;
1080 case ValueStringType::URITemplate:
1081 result = URITemplate::is_uritemplate(target);
1082 break;
1083 case ValueStringType::IRI:
1084 result = URI::is_iri(target);
1085 break;
1086 case ValueStringType::IRIReference:
1087 result = URI::is_iri_reference(target);
1088 break;
1089 case ValueStringType::Email:
1090 result = is_email(target);
1091 break;
1092 case ValueStringType::IDNEmail:
1093 result = is_idn_email_uts46(target);
1094 break;
1095 case ValueStringType::IPv4:
1096 result = is_ipv4(target);
1097 break;
1098 case ValueStringType::IPv6:
1099 result = is_ipv6(target);
1100 break;
1101 case ValueStringType::Hostname:
1102 result = is_hostname(target);
1103 break;
1104 case ValueStringType::IDNHostname:
1105 result = is_idn_hostname(target);
1106 break;
1107 case ValueStringType::DateTime:
1108 result = is_rfc3339_datetime(target);
1109 break;
1110 case ValueStringType::Date:
1111 result = is_rfc3339_fulldate(target);
1112 break;
1113 case ValueStringType::Time:
1114 result = is_rfc3339_fulltime(target);
1115 break;
1116 case ValueStringType::PartialTime:
1117 result = is_rfc3339_partialtime_no_secfrac(target);
1118 break;
1119 case ValueStringType::Duration:
1120 result = is_rfc3339_duration(target);
1121 break;
1122 case ValueStringType::JSONPointer:
1123 result = is_pointer(target);
1124 break;
1125 case ValueStringType::RelativeJSONPointer:
1126 result = is_relative_pointer(target);
1127 break;
1128 case ValueStringType::UUID:
1129 result = is_uuid_like(target);
1130 break;
1131 case ValueStringType::Regex:
1132 result = is_regex_ecma(target);
1133 break;
1134 case ValueStringType::Color:
1135 result = is_css2_color(target);
1136 break;
1137 case ValueStringType::Base64:
1138 result = sourcemeta::core::is_base64(target);
1139 break;
1140 default:
1141 std::unreachable();
1142 }
1143
1144 EVALUATE_END(AssertionStringType);
1145}
1146
1147INSTRUCTION_HANDLER(AssertionPropertyType) {
1148 EVALUATE_BEGIN_TRY_TARGET(AssertionPropertyType);
1149 // Now here we refer to the actual property
1150 const auto value{assume_value_copy<ValueType>(instruction.value)};
1151 // In non-strict mode, we consider a real number that represents an
1152 // integer to be an integer
1153 result = target_check->type() == value ||
1154 (value == JSON::Type::Integer && target_check->is_integral());
1155 EVALUATE_END(AssertionPropertyType);
1156}
1157
1158INSTRUCTION_HANDLER(AssertionPropertyTypeEvaluate) {
1159 EVALUATE_BEGIN_TRY_TARGET(AssertionPropertyTypeEvaluate);
1160 // Now here we refer to the actual property
1161 const auto value{assume_value_copy<ValueType>(instruction.value)};
1162 // In non-strict mode, we consider a real number that represents an
1163 // integer to be an integer
1164 result = target_check->type() == value ||
1165 (value == JSON::Type::Integer && target_check->is_integral());
1166
1167 if (result) {
1168 context.evaluator->evaluate(target_check);
1169 }
1170
1171 EVALUATE_END(AssertionPropertyTypeEvaluate);
1172}
1173
1174INSTRUCTION_HANDLER(AssertionPropertyTypeStrict) {
1175 EVALUATE_BEGIN_TRY_TARGET(AssertionPropertyTypeStrict);
1176 // Now here we refer to the actual property
1177 const auto value{assume_value_copy<ValueType>(instruction.value)};
1178 result = effective_type_strict_real(*target_check) == value;
1179 EVALUATE_END(AssertionPropertyTypeStrict);
1180}
1181
1182INSTRUCTION_HANDLER(AssertionPropertyTypeStrictEvaluate) {
1183 EVALUATE_BEGIN_TRY_TARGET(AssertionPropertyTypeStrictEvaluate);
1184 // Now here we refer to the actual property
1185 const auto value{assume_value_copy<ValueType>(instruction.value)};
1186 result = effective_type_strict_real(*target_check) == value;
1187
1188 if (result) {
1189 context.evaluator->evaluate(target_check);
1190 }
1191
1192 EVALUATE_END(AssertionPropertyTypeStrictEvaluate);
1193}
1194
1195INSTRUCTION_HANDLER(AssertionPropertyEqual) {
1196 EVALUATE_BEGIN_TRY_TARGET(AssertionPropertyEqual);
1197 const auto &value{assume_value<ValueJSON>(instruction.value)};
1198 // Now here we refer to the actual property
1199 result = DIRECT(AssertionEqual, *target_check, value);
1200 EVALUATE_END(AssertionPropertyEqual);
1201}
1202
1203INSTRUCTION_HANDLER(AssertionPropertyTypeStrictAny) {
1204 EVALUATE_BEGIN_TRY_TARGET(AssertionPropertyTypeStrictAny);
1205 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
1206 assert(value.any());
1207 // Now here we refer to the actual property
1208 result = type_set_test(value, effective_type_strict_real(*target_check));
1209 EVALUATE_END(AssertionPropertyTypeStrictAny);
1210}
1211
1212INSTRUCTION_HANDLER(AssertionPropertyTypeStrictAnyEvaluate) {
1213 EVALUATE_BEGIN_TRY_TARGET(AssertionPropertyTypeStrictAnyEvaluate);
1214 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
1215 assert(value.any());
1216 // Now here we refer to the actual property
1217 result = type_set_test(value, effective_type_strict_real(*target_check));
1218
1219 if (result) {
1220 context.evaluator->evaluate(target_check);
1221 }
1222
1223 EVALUATE_END(AssertionPropertyTypeStrictAnyEvaluate);
1224}
1225
1226INSTRUCTION_HANDLER(AssertionObjectPropertiesSimple) {
1227 EVALUATE_BEGIN_NON_STRING(AssertionObjectPropertiesSimple, true);
1228 if (!target.is_object()) {
1229 EVALUATE_END(AssertionObjectPropertiesSimple);
1230 }
1231
1232 const auto &value{assume_value<ValueObjectProperties>(instruction.value)};
1233 assert(value.size() >= instruction.children.size());
1234 assert(value.size() <= 32);
1235 static constexpr sourcemeta::core::PropertyHashJSON<ValueString>
1236 PROPERTY_HASHER;
1237 const auto &object{target.as_object()};
1238 const auto schema_size{value.size()};
1239 std::uint32_t seen{0};
1240 result = true;
1241
1242 for (const auto &instance_entry : object) {
1243 const auto &instance_hash{instance_entry.hash};
1244 for (std::size_t schema_index = 0; schema_index < schema_size;
1245 schema_index++) {
1246 const auto &schema_hash{std::get<1>(value[schema_index])};
1247 // A perfect hash records the key's size, so matching one proves the keys
1248 // equal and only a longer key needs comparing
1249 if (schema_hash == instance_hash &&
1250 (PROPERTY_HASHER.is_perfect(instance_hash) ||
1251 instance_entry.first == std::get<0>(value[schema_index]))) {
1252 seen |= (static_cast<std::uint32_t>(1) << schema_index);
1253 if (schema_index < instruction.children.size()) {
1254 const auto &child{instruction.children[schema_index]};
1255 const auto &property_value{instance_entry.second};
1256 bool child_ok{false};
1257
1258// NOLINTBEGIN(bugprone-macro-parentheses)
1259#define CHILD_DIRECT(name, accessor, value_type) \
1260 case name: \
1261 child_ok = \
1262 DIRECT(name, property_value, accessor<value_type>(child.value)); \
1263 break
1264
1265 switch (child.type) {
1266 using enum InstructionIndex;
1267 CHILD_DIRECT(AssertionTypeStrict, assume_value_copy, ValueType);
1268 CHILD_DIRECT(AssertionType, assume_value_copy, ValueType);
1269 CHILD_DIRECT(AssertionTypeStrictAny, assume_value, ValueTypes);
1270 CHILD_DIRECT(AssertionTypeAny, assume_value, ValueTypes);
1271 CHILD_DIRECT(AssertionTypeStringBounded, assume_value, ValueRange);
1272 CHILD_DIRECT(AssertionEqual, assume_value, ValueJSON);
1273 CHILD_DIRECT(AssertionEqualsAny, assume_value, ValueSet);
1274 CHILD_DIRECT(AssertionEqualsAnyStringHash, assume_value,
1276 CHILD_DIRECT(AssertionTypeIntegerBounded, assume_value_copy,
1278 CHILD_DIRECT(AssertionTypeIntegerBoundedStrict, assume_value_copy,
1280 CHILD_DIRECT(AssertionTypeIntegerLowerBound, assume_value_copy,
1282 CHILD_DIRECT(AssertionTypeIntegerLowerBoundStrict,
1283 assume_value_copy, ValueIntegerBounds);
1284 CHILD_DIRECT(LoopItemsIntegerBounded, assume_value_copy,
1286 default:
1287 child_ok = evaluate_instruction_without_callback(
1288 child, property_value, depth + 1, context);
1289 break;
1290 }
1291
1292// NOLINTEND(bugprone-macro-parentheses)
1293#undef CHILD_DIRECT
1294
1295 if (!child_ok) [[unlikely]] {
1296 result = false;
1297 EVALUATE_END(AssertionObjectPropertiesSimple);
1298 }
1299 }
1300
1301 break;
1302 }
1303 }
1304 }
1305
1306 // Check required properties that were not seen
1307 const auto all_seen{schema_size == 32
1308 ? ~std::uint32_t{0}
1309 : (std::uint32_t{1} << schema_size) - 1};
1310 if (seen != all_seen) [[unlikely]] {
1311 for (std::size_t schema_index = 0; schema_index < schema_size;
1312 schema_index++) {
1313 if (std::get<2>(value[schema_index]) &&
1314 !(seen & (static_cast<std::uint32_t>(1) << schema_index))) {
1315 result = false;
1316 EVALUATE_END(AssertionObjectPropertiesSimple);
1317 }
1318 }
1319 }
1320
1321 EVALUATE_END(AssertionObjectPropertiesSimple);
1322}
1323
1324INSTRUCTION_HANDLER(AssertionArrayPrefix) {
1325 EVALUATE_BEGIN_NON_STRING(AssertionArrayPrefix, target.is_array());
1326 // Otherwise there is no point in emitting this instruction
1327 assert(!instruction.children.empty());
1328 result = target.empty();
1329 const auto prefixes{instruction.children.size() - 1};
1330 const auto array_size{target.array_size()};
1331 if (!result) [[likely]] {
1332 const auto pointer{
1333 array_size == prefixes ? prefixes : std::min(array_size, prefixes) - 1};
1334 const auto &entry{instruction.children[pointer]};
1335 result = true;
1336 assert(entry.type == sourcemeta::blaze::InstructionIndex::ControlGroup);
1337 SOURCEMETA_ASSUME(entry.type ==
1338 sourcemeta::blaze::InstructionIndex::ControlGroup);
1339 for (const auto &child : entry.children) {
1340 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1341 result = false;
1342 break;
1343 }
1344 }
1345 }
1346
1347 EVALUATE_END(AssertionArrayPrefix);
1348}
1349
1350INSTRUCTION_HANDLER(AssertionArrayPrefixEvaluate) {
1351 EVALUATE_BEGIN_NON_STRING(AssertionArrayPrefixEvaluate, target.is_array());
1352 // Otherwise there is no point in emitting this instruction
1353 assert(!instruction.children.empty());
1354 result = target.empty();
1355 const auto prefixes{instruction.children.size() - 1};
1356 const auto array_size{target.array_size()};
1357 if (!result) [[likely]] {
1358 const auto pointer{
1359 array_size == prefixes ? prefixes : std::min(array_size, prefixes) - 1};
1360 const auto &entry{instruction.children[pointer]};
1361 result = true;
1362 assert(entry.type == sourcemeta::blaze::InstructionIndex::ControlGroup);
1363 SOURCEMETA_ASSUME(entry.type ==
1364 sourcemeta::blaze::InstructionIndex::ControlGroup);
1365 for (const auto &child : entry.children) {
1366 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1367 result = false;
1368 EVALUATE_END(AssertionArrayPrefixEvaluate);
1369 }
1370 }
1371
1372 assert(result);
1373 SOURCEMETA_ASSUME(result);
1374 if (array_size == prefixes) {
1375 context.evaluator->evaluate(&target);
1376 } else {
1377 for (std::size_t cursor = 0; cursor <= pointer; cursor++) {
1378 context.evaluator->evaluate(&target.at(cursor));
1379 }
1380 }
1381 }
1382
1383 EVALUATE_END(AssertionArrayPrefixEvaluate);
1384}
1385
1386INSTRUCTION_HANDLER(LogicalOr) {
1387 EVALUATE_BEGIN_NO_PRECONDITION(LogicalOr);
1388 result = instruction.children.empty();
1389 const auto &target{
1390 resolve_instance(instance, instruction.relative_instance_location)};
1391 const auto value{assume_value_copy<ValueBoolean>(instruction.value)};
1392
1393 // This boolean value controls whether we should be exhaustive
1394 if (value) {
1395 for (const auto &child : instruction.children) {
1396 // A branch that does not hold contributes nothing, including any
1397 // evaluation it marked before failing. Only a schema that tracks
1398 // evaluation can ever observe those marks
1399 [[maybe_unused]] std::size_t checkpoint{0};
1400 if constexpr (Track) {
1401 checkpoint = context.evaluator->checkpoint();
1402 }
1403
1404 if (EVALUATE_RECURSE(child, target)) {
1405 result = true;
1406 } else {
1407 if constexpr (Track) {
1408 context.evaluator->rewind(checkpoint);
1409 }
1410 }
1411 }
1412 } else {
1413 for (const auto &child : instruction.children) {
1414 [[maybe_unused]] std::size_t checkpoint{0};
1415 if constexpr (Track) {
1416 checkpoint = context.evaluator->checkpoint();
1417 }
1418
1419 if (EVALUATE_RECURSE(child, target)) {
1420 result = true;
1421 break;
1422 }
1423
1424 if constexpr (Track) {
1425 context.evaluator->rewind(checkpoint);
1426 }
1427 }
1428 }
1429
1430 EVALUATE_END(LogicalOr);
1431}
1432
1433INSTRUCTION_HANDLER(LogicalAnd) {
1434 EVALUATE_BEGIN_NO_PRECONDITION(LogicalAnd);
1435 result = true;
1436 const auto &target{
1437 resolve_instance(instance, instruction.relative_instance_location)};
1438 for (const auto &child : instruction.children) {
1439 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1440 result = false;
1441 break;
1442 }
1443 }
1444
1445 EVALUATE_END(LogicalAnd);
1446}
1447
1448INSTRUCTION_HANDLER(LogicalWhenType) {
1449 const auto value{assume_value_copy<ValueType>(instruction.value)};
1450
1451 // Not having to worry about numbers in this instruction
1452 // makes things a lot simpler
1453 assert(value != JSON::Type::Integer);
1454 SOURCEMETA_ASSUME(value != JSON::Type::Integer);
1455 assert(value != JSON::Type::Real);
1456 SOURCEMETA_ASSUME(value != JSON::Type::Real);
1457 assert(value != JSON::Type::Decimal);
1458 SOURCEMETA_ASSUME(value != JSON::Type::Decimal);
1459
1460 EVALUATE_BEGIN(LogicalWhenType, target.type() == value);
1461 result = true;
1462 for (const auto &child : instruction.children) {
1463 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1464 result = false;
1465 break;
1466 }
1467 }
1468
1469 EVALUATE_END(LogicalWhenType);
1470}
1471
1472INSTRUCTION_HANDLER(LogicalWhenDefines) {
1473 const auto &value{assume_value<ValueProperty>(instruction.value)};
1474 EVALUATE_BEGIN_NON_STRING(LogicalWhenDefines,
1475 target.is_object() &&
1476 target.defines(value.first, value.second));
1477 result = true;
1478 for (const auto &child : instruction.children) {
1479 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1480 result = false;
1481 break;
1482 }
1483 }
1484
1485 EVALUATE_END(LogicalWhenDefines);
1486}
1487
1488INSTRUCTION_HANDLER(LogicalWhenArraySizeGreater) {
1489 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
1490 EVALUATE_BEGIN_NON_STRING(LogicalWhenArraySizeGreater,
1491 target.is_array() && target.array_size() > value);
1492 result = true;
1493 for (const auto &child : instruction.children) {
1494 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1495 result = false;
1496 break;
1497 }
1498 }
1499
1500 EVALUATE_END(LogicalWhenArraySizeGreater);
1501}
1502
1503INSTRUCTION_HANDLER(LogicalXor) {
1504 EVALUATE_BEGIN_NO_PRECONDITION(LogicalXor);
1505 result = true;
1506 bool has_matched{false};
1507 const auto &target{
1508 resolve_instance(instance, instruction.relative_instance_location)};
1509 const auto value{assume_value_copy<ValueBoolean>(instruction.value)};
1510 for (const auto &child : instruction.children) {
1511 // A branch that does not hold contributes nothing, including any
1512 // evaluation it marked before failing
1513 [[maybe_unused]] std::size_t checkpoint{0};
1514 if constexpr (Track) {
1515 checkpoint = context.evaluator->checkpoint();
1516 }
1517
1518 if (EVALUATE_RECURSE(child, target)) {
1519 if (has_matched) [[unlikely]] {
1520 result = false;
1521 // This boolean value controls whether we should be exhaustive
1522 if (!value) {
1523 break;
1524 }
1525 } else {
1526 has_matched = true;
1527 }
1528 } else {
1529 if constexpr (Track) {
1530 context.evaluator->rewind(checkpoint);
1531 }
1532 }
1533 }
1534
1535 result = result && has_matched;
1536 EVALUATE_END(LogicalXor);
1537}
1538
1539INSTRUCTION_HANDLER(LogicalCondition) {
1540 EVALUATE_BEGIN_NO_PRECONDITION(LogicalCondition);
1541 result = true;
1542 const auto value{assume_value_copy<ValueIndexPair>(instruction.value)};
1543 const auto children_size{instruction.children.size()};
1544 assert(children_size >= value.first);
1545 SOURCEMETA_ASSUME(children_size >= value.first);
1546 assert(children_size >= value.second);
1547 SOURCEMETA_ASSUME(children_size >= value.second);
1548
1549 // The condition is the leading segment [0, then start). It may be empty,
1550 // for example when a `$ref` condition inlines and its jump is dropped, in
1551 // which case it trivially passes and the then branch applies
1552 const auto &target{
1553 resolve_instance(instance, instruction.relative_instance_location)};
1554
1555 // A condition that holds does contribute the evaluation it marked, but one
1556 // that does not hold contributes nothing at all
1557 [[maybe_unused]] std::size_t checkpoint{0};
1558 if constexpr (Track) {
1559 checkpoint = context.evaluator->checkpoint();
1560 }
1561
1562 for (std::size_t cursor = 0; cursor < value.first; cursor++) {
1563 if (!EVALUATE_RECURSE(instruction.children[cursor], target)) {
1564 result = false;
1565 break;
1566 }
1567 }
1568
1569 if (!result) {
1570 if constexpr (Track) {
1571 context.evaluator->rewind(checkpoint);
1572 }
1573 }
1574
1575 // On a passing condition the then branch runs, otherwise the else branch,
1576 // which is absent when it starts at zero
1577 std::size_t consequence_start;
1578 std::size_t consequence_end;
1579 if (result) {
1580 consequence_start = value.first;
1581 consequence_end = value.second > 0 ? value.second : children_size;
1582 } else {
1583 consequence_start = value.second;
1584 consequence_end = value.second > 0 ? children_size : value.second;
1585 }
1586
1587 result = true;
1588 if (consequence_start < consequence_end) {
1589 if constexpr (Track || HasCallback) {
1590 if (track) {
1591 context.evaluator->evaluate_path.pop_back(
1592 context.schema->extra[instruction.extra_index]
1593 .relative_schema_location.size());
1594 }
1595 }
1596
1597 for (auto cursor = consequence_start; cursor < consequence_end; cursor++) {
1598 if (!EVALUATE_RECURSE(instruction.children[cursor], target)) {
1599 result = false;
1600 break;
1601 }
1602 }
1603
1604 if constexpr (Track || HasCallback) {
1605 if (track) {
1606 context.evaluator->evaluate_path.push_back(
1607 context.schema->extra[instruction.extra_index]
1608 .relative_schema_location);
1609 }
1610 }
1611 }
1612
1613 EVALUATE_END(LogicalCondition);
1614}
1615
1616INSTRUCTION_HANDLER(ControlGroup) {
1617 EVALUATE_BEGIN_PASS_THROUGH(ControlGroup);
1618 for (const auto &child : instruction.children) {
1619 if (!EVALUATE_RECURSE(child, instance)) [[unlikely]] {
1620 result = false;
1621 break;
1622 }
1623 }
1624
1625 EVALUATE_END_PASS_THROUGH(ControlGroup);
1626}
1627
1628INSTRUCTION_HANDLER(ControlGroupWhenDefines) {
1629 EVALUATE_BEGIN_PASS_THROUGH(ControlGroupWhenDefines);
1630 assert(!instruction.children.empty());
1631 // Otherwise why are we emitting this property?
1632 assert(!instruction.relative_instance_location.empty());
1633 const auto &target{
1634 resolve_instance(instance, instruction.relative_instance_location)};
1635 const auto &value{assume_value<ValueProperty>(instruction.value)};
1636 if (target.is_object() && target.defines(value.first, value.second)) {
1637 for (const auto &child : instruction.children) {
1638 // Note that in this control instruction, we purposely
1639 // don't navigate into the target
1640 if (!EVALUATE_RECURSE(child, instance)) [[unlikely]] {
1641 result = false;
1642 break;
1643 }
1644 }
1645 }
1646
1647 EVALUATE_END_PASS_THROUGH(ControlGroupWhenDefines);
1648}
1649
1650INSTRUCTION_HANDLER(ControlGroupWhenDefinesDirect) {
1651 EVALUATE_BEGIN_PASS_THROUGH(ControlGroupWhenDefinesDirect);
1652 assert(!instruction.children.empty());
1653 assert(instruction.relative_instance_location.empty());
1654 const auto &value{assume_value<ValueProperty>(instruction.value)};
1655
1656 if (instance.is_object() && instance.defines(value.first, value.second)) {
1657 for (const auto &child : instruction.children) {
1658 if (!EVALUATE_RECURSE(child, instance)) [[unlikely]] {
1659 result = false;
1660 break;
1661 }
1662 }
1663 }
1664
1665 EVALUATE_END_PASS_THROUGH(ControlGroupWhenDefinesDirect);
1666}
1667
1668INSTRUCTION_HANDLER(ControlGroupWhenType) {
1669 EVALUATE_BEGIN_PASS_THROUGH(ControlGroupWhenType);
1670 assert(!instruction.children.empty());
1671 assert(instruction.relative_instance_location.empty());
1672 const auto value{assume_value_copy<ValueType>(instruction.value)};
1673
1674 // Not having to worry about numbers in this instruction
1675 // makes things a lot simpler
1676 assert(value != JSON::Type::Integer);
1677 SOURCEMETA_ASSUME(value != JSON::Type::Integer);
1678 assert(value != JSON::Type::Real);
1679 SOURCEMETA_ASSUME(value != JSON::Type::Real);
1680 assert(value != JSON::Type::Decimal);
1681 SOURCEMETA_ASSUME(value != JSON::Type::Decimal);
1682
1683 if (instance.type() == value) [[likely]] {
1684 for (const auto &child : instruction.children) {
1685 if (!EVALUATE_RECURSE(child, instance)) [[unlikely]] {
1686 result = false;
1687 break;
1688 }
1689 }
1690 }
1691
1692 EVALUATE_END_PASS_THROUGH(ControlGroupWhenType);
1693}
1694
1695INSTRUCTION_HANDLER(ControlEvaluate) {
1696 EVALUATE_BEGIN_PASS_THROUGH(ControlEvaluate);
1697 const auto &value{assume_value<ValuePointer>(instruction.value)};
1698 context.evaluator->evaluate(&get(instance, value));
1699 EVALUATE_END_PASS_THROUGH(ControlEvaluate);
1700}
1701
1702INSTRUCTION_HANDLER(ControlDynamicAnchorJump) {
1703 EVALUATE_BEGIN_NO_PRECONDITION(ControlDynamicAnchorJump);
1704 result = false;
1705 const auto &target{
1706 resolve_instance(instance, instruction.relative_instance_location)};
1707 const auto &value{assume_value<ValueString>(instruction.value)};
1708 for (const auto &resource : context.evaluator->resources) {
1709 const auto label{Evaluator::hash(resource, value)};
1710 const auto match{std::ranges::find_if(
1711 context.schema->labels,
1712 [&label](const auto &entry) -> bool { return entry.first == label; })};
1713 if (match != context.schema->labels.cend()) [[likely]] {
1714 result = true;
1715 assert(match->second < context.schema->targets.size());
1716 for (const auto &child : context.schema->targets[match->second]) {
1717 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1718 result = false;
1719 EVALUATE_END(ControlDynamicAnchorJump);
1720 }
1721 }
1722
1723 break;
1724 }
1725 }
1726
1727 EVALUATE_END(ControlDynamicAnchorJump);
1728}
1729
1730INSTRUCTION_HANDLER(ControlJump) {
1731 EVALUATE_BEGIN_NO_PRECONDITION(ControlJump);
1732 result = true;
1733 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
1734 assert(context.schema->targets.size() > value);
1735 const auto &target{resolve_target(
1736 context.property_target,
1737 resolve_instance(instance, instruction.relative_instance_location))};
1738 for (const auto &child : context.schema->targets[value]) {
1739 if (!EVALUATE_RECURSE(child, target)) [[unlikely]] {
1740 result = false;
1741 break;
1742 }
1743 }
1744
1745 EVALUATE_END(ControlJump);
1746}
1747
1748INSTRUCTION_HANDLER(AnnotationEmit) {
1749 const auto &value{assume_value<ValueJSON>(instruction.value)};
1750 EVALUATE_ANNOTATION(AnnotationEmit, context.evaluator->instance_location,
1751 value);
1752}
1753
1754INSTRUCTION_HANDLER(AnnotationEmitWrapped) {
1755 const auto &value{assume_value<ValueJSON>(instruction.value)};
1756 EVALUATE_ANNOTATION(AnnotationEmitWrapped,
1757 context.evaluator->instance_location, value);
1758}
1759
1760INSTRUCTION_HANDLER(AnnotationToParent) {
1761 const auto &value{assume_value<ValueJSON>(instruction.value)};
1762 EVALUATE_ANNOTATION(
1763 AnnotationToParent,
1764 // TODO: Can we avoid a copy of the instance location here?
1765 context.evaluator->instance_location.initial(), value);
1766}
1767
1768INSTRUCTION_HANDLER(AnnotationBasenameToParent) {
1769 EVALUATE_ANNOTATION(
1770 AnnotationBasenameToParent,
1771 // TODO: Can we avoid a copy of the instance location here?
1772 context.evaluator->instance_location.initial(),
1773 context.evaluator->instance_location.back().to_json());
1774}
1775
1776INSTRUCTION_HANDLER(Evaluate) {
1777 EVALUATE_BEGIN_NO_PRECONDITION(Evaluate);
1778 const auto &target{
1779 resolve_instance(instance, instruction.relative_instance_location)};
1780 context.evaluator->evaluate(&target);
1781 result = true;
1782 EVALUATE_END(Evaluate);
1783}
1784
1785INSTRUCTION_HANDLER(LogicalNot) {
1786 EVALUATE_BEGIN_NO_PRECONDITION(LogicalNot);
1787
1788 const auto &target{
1789 resolve_instance(instance, instruction.relative_instance_location)};
1790 for (const auto &child : instruction.children) {
1791 if (!EVALUATE_RECURSE(child, target)) [[likely]] {
1792 result = true;
1793 break;
1794 }
1795 }
1796
1797 EVALUATE_END(LogicalNot);
1798}
1799
1800INSTRUCTION_HANDLER(LogicalNotEvaluate) {
1801 EVALUATE_BEGIN_NO_PRECONDITION(LogicalNotEvaluate);
1802
1803 const auto &target{
1804 resolve_instance(instance, instruction.relative_instance_location)};
1805 for (const auto &child : instruction.children) {
1806 if (!EVALUATE_RECURSE(child, target)) [[likely]] {
1807 result = true;
1808 break;
1809 }
1810 }
1811
1812 context.evaluator->unevaluate();
1813
1814 EVALUATE_END(LogicalNotEvaluate);
1815}
1816
1817INSTRUCTION_HANDLER(LoopPropertiesUnevaluated) {
1818 EVALUATE_BEGIN_NON_STRING(LoopPropertiesUnevaluated, target.is_object());
1819 assert(!instruction.children.empty());
1820 result = true;
1821
1822 if (!context.evaluator->is_evaluated(&target)) {
1823 for (const auto &entry : target.as_object()) {
1824 if (context.evaluator->is_evaluated(&entry.second)) {
1825 continue;
1826 }
1827
1828 if constexpr (HasCallback) {
1829 context.evaluator->instance_location.push_back(entry.first);
1830 }
1831 for (const auto &child : instruction.children) {
1832 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
1833 result = false;
1834 if constexpr (HasCallback) {
1835 context.evaluator->instance_location.pop_back();
1836 }
1837 EVALUATE_END(LoopPropertiesUnevaluated);
1838 }
1839 }
1840
1841 if constexpr (HasCallback) {
1842 context.evaluator->instance_location.pop_back();
1843 }
1844 }
1845
1846 context.evaluator->evaluate(&target);
1847 }
1848
1849 EVALUATE_END(LoopPropertiesUnevaluated);
1850}
1851
1852INSTRUCTION_HANDLER(LoopPropertiesUnevaluatedExcept) {
1853 EVALUATE_BEGIN_NON_STRING(LoopPropertiesUnevaluatedExcept,
1854 target.is_object());
1855 assert(!instruction.children.empty());
1856 const auto &value{assume_value<ValuePropertyFilter>(instruction.value)};
1857 const auto &[filter_strings, filter_prefixes, filter_regexes] = value;
1858 result = true;
1859 // Otherwise why emit this instruction?
1860 assert(!filter_strings.empty() || !filter_prefixes.empty() ||
1861 !filter_regexes.empty());
1862
1863 if (!context.evaluator->is_evaluated(&target)) {
1864 for (const auto &entry : target.as_object()) {
1865 if (filter_strings.contains(entry.first, entry.hash)) {
1866 continue;
1867 }
1868
1869 if (std::ranges::any_of(filter_prefixes,
1870 [&entry](const auto &prefix) -> bool {
1871 return entry.first.starts_with(prefix);
1872 })) {
1873 continue;
1874 }
1875
1876 if (std::ranges::any_of(filter_regexes,
1877 [&entry](const auto &pattern) -> bool {
1878 return matches(pattern.first, entry.first);
1879 })) {
1880 continue;
1881 }
1882
1883 if (context.evaluator->is_evaluated(&entry.second)) {
1884 continue;
1885 }
1886
1887 if constexpr (HasCallback) {
1888 context.evaluator->instance_location.push_back(entry.first);
1889 }
1890 for (const auto &child : instruction.children) {
1891 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
1892 result = false;
1893 if constexpr (HasCallback) {
1894 context.evaluator->instance_location.pop_back();
1895 }
1896 EVALUATE_END(LoopPropertiesUnevaluatedExcept);
1897 }
1898 }
1899
1900 if constexpr (HasCallback) {
1901 context.evaluator->instance_location.pop_back();
1902 }
1903 }
1904
1905 context.evaluator->evaluate(&target);
1906 }
1907
1908 EVALUATE_END(LoopPropertiesUnevaluatedExcept);
1909}
1910
1911INSTRUCTION_HANDLER(LoopPropertiesMatch) {
1912 EVALUATE_BEGIN_NON_STRING(LoopPropertiesMatch, target.is_object());
1913 const auto &value{assume_value<ValueNamedIndexes>(instruction.value)};
1914 assert(!value.first.empty());
1915 result = true;
1916 // The loop already holds each property's value, so a child that only looks at
1917 // that value is decided here, rather than dispatched to and left to find the
1918 // property in the object a second time. The evaluation-marking variants of
1919 // those assertions are deliberately absent, as skipping them would skip the
1920 // marker. Dynamic pushes and pops a resource per instruction that the direct
1921 // path would skip, and children are evaluated at `depth + 1`, which the
1922 // direct path does not route through the depth check. Every condition holds
1923 // for every child, so decide once rather than per property
1924 const auto direct_children{!Track && !HasCallback && !Dynamic &&
1925 depth < DEPTH_LIMIT};
1926 for (const auto &entry : target.as_object()) {
1927 const auto *index{named_index(value, entry.first, entry.hash)};
1928 if (!index) {
1929 continue;
1930 }
1931
1932 const auto &subinstruction{instruction.children[*index]};
1933 assert(subinstruction.type ==
1934 sourcemeta::blaze::InstructionIndex::ControlGroup);
1935 for (const auto &child : subinstruction.children) {
1936 bool child_ok;
1937 // A child whose instance location is not just the matched property is not
1938 // about the value this loop holds, which inlining a reference into the
1939 // group can produce, so it resolves its own target as usual
1940 if (direct_children && child.relative_instance_location.size() == 1)
1941 [[likely]] {
1942 switch (child.type) {
1943 case InstructionIndex::AssertionPropertyTypeStrict:
1944 child_ok = DIRECT(AssertionTypeStrict, entry.second,
1945 assume_value_copy<ValueType>(child.value));
1946 break;
1947 case InstructionIndex::AssertionPropertyType:
1948 child_ok = DIRECT(AssertionType, entry.second,
1949 assume_value_copy<ValueType>(child.value));
1950 break;
1951 case InstructionIndex::AssertionPropertyTypeStrictAny:
1952 child_ok = DIRECT(AssertionTypeStrictAny, entry.second,
1953 assume_value<ValueTypes>(child.value));
1954 break;
1955 default:
1956 child_ok = EVALUATE_RECURSE(child, target);
1957 break;
1958 }
1959 } else {
1960 child_ok = EVALUATE_RECURSE(child, target);
1961 }
1962
1963 if (!child_ok) [[unlikely]] {
1964 result = false;
1965 EVALUATE_END(LoopPropertiesMatch);
1966 }
1967 }
1968 }
1969
1970 EVALUATE_END(LoopPropertiesMatch);
1971}
1972
1973INSTRUCTION_HANDLER(LoopPropertiesMatchClosed) {
1974 EVALUATE_BEGIN_NON_STRING(LoopPropertiesMatchClosed, target.is_object());
1975 const auto &value{assume_value<ValueNamedIndexes>(instruction.value)};
1976 assert(!value.first.empty());
1977 result = true;
1978 // The loop already holds each property's value, so a child that only looks at
1979 // that value is decided here, rather than dispatched to and left to find the
1980 // property in the object a second time. The evaluation-marking variants of
1981 // those assertions are deliberately absent, as skipping them would skip the
1982 // marker. Dynamic pushes and pops a resource per instruction that the direct
1983 // path would skip, and children are evaluated at `depth + 1`, which the
1984 // direct path does not route through the depth check. Every condition holds
1985 // for every child, so decide once rather than per property
1986 const auto direct_children{!Track && !HasCallback && !Dynamic &&
1987 depth < DEPTH_LIMIT};
1988 for (const auto &entry : target.as_object()) {
1989 const auto *index{named_index(value, entry.first, entry.hash)};
1990 if (!index) [[unlikely]] {
1991 result = false;
1992 break;
1993 }
1994
1995 const auto &subinstruction{instruction.children[*index]};
1996 assert(subinstruction.type ==
1997 sourcemeta::blaze::InstructionIndex::ControlGroup);
1998 for (const auto &child : subinstruction.children) {
1999 bool child_ok;
2000 // A child whose instance location is not just the matched property is not
2001 // about the value this loop holds, which inlining a reference into the
2002 // group can produce, so it resolves its own target as usual
2003 if (direct_children && child.relative_instance_location.size() == 1)
2004 [[likely]] {
2005 switch (child.type) {
2006 case InstructionIndex::AssertionPropertyTypeStrict:
2007 child_ok = DIRECT(AssertionTypeStrict, entry.second,
2008 assume_value_copy<ValueType>(child.value));
2009 break;
2010 case InstructionIndex::AssertionPropertyType:
2011 child_ok = DIRECT(AssertionType, entry.second,
2012 assume_value_copy<ValueType>(child.value));
2013 break;
2014 case InstructionIndex::AssertionPropertyTypeStrictAny:
2015 child_ok = DIRECT(AssertionTypeStrictAny, entry.second,
2016 assume_value<ValueTypes>(child.value));
2017 break;
2018 default:
2019 child_ok = EVALUATE_RECURSE(child, target);
2020 break;
2021 }
2022 } else {
2023 child_ok = EVALUATE_RECURSE(child, target);
2024 }
2025
2026 if (!child_ok) [[unlikely]] {
2027 result = false;
2028 EVALUATE_END(LoopPropertiesMatchClosed);
2029 }
2030 }
2031 }
2032
2033 EVALUATE_END(LoopPropertiesMatchClosed);
2034}
2035
2036INSTRUCTION_HANDLER(LoopProperties) {
2037 EVALUATE_BEGIN_NON_STRING(LoopProperties, target.is_object());
2038 assert(!instruction.children.empty());
2039 result = true;
2040 for (const auto &entry : target.as_object()) {
2041 if constexpr (HasCallback) {
2042 if (track) {
2043 context.evaluator->instance_location.push_back(entry.first);
2044 }
2045 }
2046
2047 for (const auto &child : instruction.children) {
2048 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
2049 result = false;
2050
2051 if constexpr (HasCallback) {
2052 if (track) {
2053 context.evaluator->instance_location.pop_back();
2054 }
2055 }
2056
2057 EVALUATE_END(LoopProperties);
2058 }
2059 }
2060
2061 if constexpr (HasCallback) {
2062 if (track) {
2063 context.evaluator->instance_location.pop_back();
2064 }
2065 }
2066 }
2067
2068 EVALUATE_END(LoopProperties);
2069}
2070
2071INSTRUCTION_HANDLER(LoopPropertiesEvaluate) {
2072 EVALUATE_BEGIN_NON_STRING(LoopPropertiesEvaluate, target.is_object());
2073 // The subschema may compile to no children, in which case every property
2074 // trivially matches and only the evaluation marking below takes effect
2075 result = true;
2076 for (const auto &entry : target.as_object()) {
2077 if constexpr (HasCallback) {
2078 if (track) {
2079 context.evaluator->instance_location.push_back(entry.first);
2080 }
2081 }
2082
2083 for (const auto &child : instruction.children) {
2084 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
2085 result = false;
2086
2087 if constexpr (HasCallback) {
2088 if (track) {
2089 context.evaluator->instance_location.pop_back();
2090 }
2091 }
2092
2093 EVALUATE_END(LoopPropertiesEvaluate);
2094 }
2095 }
2096
2097 if constexpr (HasCallback) {
2098 if (track) {
2099 context.evaluator->instance_location.pop_back();
2100 }
2101 }
2102 }
2103
2104 context.evaluator->evaluate(&target);
2105 EVALUATE_END(LoopPropertiesEvaluate);
2106}
2107
2108INSTRUCTION_HANDLER(LoopPropertiesRegex) {
2109 EVALUATE_BEGIN_NON_STRING(LoopPropertiesRegex, target.is_object());
2110 assert(!instruction.children.empty());
2111 const auto &value{assume_value<ValueRegex>(instruction.value)};
2112 result = true;
2113 for (const auto &entry : target.as_object()) {
2114 if (!matches(value.first, entry.first)) {
2115 continue;
2116 }
2117
2118 if constexpr (HasCallback) {
2119 if (track) {
2120 context.evaluator->instance_location.push_back(entry.first);
2121 }
2122 }
2123
2124 for (const auto &child : instruction.children) {
2125 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
2126 result = false;
2127
2128 if constexpr (HasCallback) {
2129 if (track) {
2130 context.evaluator->instance_location.pop_back();
2131 }
2132 }
2133
2134 EVALUATE_END(LoopPropertiesRegex);
2135 }
2136 }
2137
2138 if constexpr (HasCallback) {
2139 if (track) {
2140 context.evaluator->instance_location.pop_back();
2141 }
2142 }
2143 }
2144
2145 EVALUATE_END(LoopPropertiesRegex);
2146}
2147
2148INSTRUCTION_HANDLER(LoopPropertiesRegexClosed) {
2149 EVALUATE_BEGIN_NON_STRING(LoopPropertiesRegexClosed, target.is_object());
2150 result = true;
2151 const auto &value{assume_value<ValueRegex>(instruction.value)};
2152 for (const auto &entry : target.as_object()) {
2153 if (!matches(value.first, entry.first)) [[unlikely]] {
2154 result = false;
2155 break;
2156 }
2157
2158 if (instruction.children.empty()) {
2159 continue;
2160 }
2161
2162 if constexpr (HasCallback) {
2163 if (track) {
2164 context.evaluator->instance_location.push_back(entry.first);
2165 }
2166 }
2167
2168 for (const auto &child : instruction.children) {
2169 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
2170 result = false;
2171
2172 if constexpr (HasCallback) {
2173 if (track) {
2174 context.evaluator->instance_location.pop_back();
2175 }
2176 }
2177
2178 EVALUATE_END(LoopPropertiesRegexClosed);
2179 }
2180 }
2181
2182 if constexpr (HasCallback) {
2183 if (track) {
2184 context.evaluator->instance_location.pop_back();
2185 }
2186 }
2187 }
2188
2189 EVALUATE_END(LoopPropertiesRegexClosed);
2190}
2191
2192INSTRUCTION_HANDLER(LoopPropertiesStartsWith) {
2193 EVALUATE_BEGIN_NON_STRING(LoopPropertiesStartsWith, target.is_object());
2194 assert(!instruction.children.empty());
2195 result = true;
2196 const auto &value{assume_value<ValueString>(instruction.value)};
2197 for (const auto &entry : target.as_object()) {
2198 if (!entry.first.starts_with(value)) {
2199 continue;
2200 }
2201
2202 if constexpr (HasCallback) {
2203 if (track) {
2204 context.evaluator->instance_location.push_back(entry.first);
2205 }
2206 }
2207
2208 for (const auto &child : instruction.children) {
2209 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
2210 result = false;
2211
2212 if constexpr (HasCallback) {
2213 if (track) {
2214 context.evaluator->instance_location.pop_back();
2215 }
2216 }
2217
2218 EVALUATE_END(LoopPropertiesStartsWith);
2219 }
2220 }
2221
2222 if constexpr (HasCallback) {
2223 if (track) {
2224 context.evaluator->instance_location.pop_back();
2225 }
2226 }
2227 }
2228
2229 EVALUATE_END(LoopPropertiesStartsWith);
2230}
2231
2232INSTRUCTION_HANDLER(LoopPropertiesExcept) {
2233 EVALUATE_BEGIN_NON_STRING(LoopPropertiesExcept, target.is_object());
2234 assert(!instruction.children.empty());
2235 result = true;
2236 const auto &value{assume_value<ValuePropertyFilter>(instruction.value)};
2237 const auto &[filter_strings, filter_prefixes, filter_regexes] = value;
2238 // Otherwise why emit this instruction?
2239 assert(!filter_strings.empty() || !filter_prefixes.empty() ||
2240 !filter_regexes.empty());
2241
2242 for (const auto &entry : target.as_object()) {
2243 if (filter_strings.contains(entry.first, entry.hash)) {
2244 continue;
2245 }
2246
2247 if (std::ranges::any_of(filter_prefixes,
2248 [&entry](const auto &prefix) -> bool {
2249 return entry.first.starts_with(prefix);
2250 })) {
2251 continue;
2252 }
2253
2254 if (std::ranges::any_of(filter_regexes,
2255 [&entry](const auto &pattern) -> bool {
2256 return matches(pattern.first, entry.first);
2257 })) {
2258 continue;
2259 }
2260
2261 if constexpr (HasCallback) {
2262 if (track) {
2263 context.evaluator->instance_location.push_back(entry.first);
2264 }
2265 }
2266
2267 for (const auto &child : instruction.children) {
2268 if (!EVALUATE_RECURSE(child, entry.second)) [[unlikely]] {
2269 result = false;
2270
2271 if constexpr (HasCallback) {
2272 if (track) {
2273 context.evaluator->instance_location.pop_back();
2274 }
2275 }
2276
2277 EVALUATE_END(LoopPropertiesExcept);
2278 }
2279 }
2280
2281 if constexpr (HasCallback) {
2282 if (track) {
2283 context.evaluator->instance_location.pop_back();
2284 }
2285 }
2286 }
2287
2288 EVALUATE_END(LoopPropertiesExcept);
2289}
2290
2291INSTRUCTION_HANDLER(LoopPropertiesType) {
2292 EVALUATE_BEGIN_NON_STRING(LoopPropertiesType, target.is_object());
2293 result = true;
2294 const auto value{assume_value_copy<ValueType>(instruction.value)};
2295 for (const auto &entry : target.as_object()) {
2296 if (entry.second.type() != value &&
2297 // In non-strict mode, we consider a real number that represents an
2298 // integer to be an integer
2299 (value != JSON::Type::Integer || !entry.second.is_integral()))
2300 [[unlikely]] {
2301 result = false;
2302 break;
2303 }
2304 }
2305
2306 EVALUATE_END(LoopPropertiesType);
2307}
2308
2309INSTRUCTION_HANDLER(LoopPropertiesTypeEvaluate) {
2310 EVALUATE_BEGIN_NON_STRING(LoopPropertiesTypeEvaluate, target.is_object());
2311 result = true;
2312 const auto value{assume_value_copy<ValueType>(instruction.value)};
2313 for (const auto &entry : target.as_object()) {
2314 if (entry.second.type() != value &&
2315 // In non-strict mode, we consider a real number that represents an
2316 // integer to be an integer
2317 (value != JSON::Type::Integer || !entry.second.is_integral()))
2318 [[unlikely]] {
2319 result = false;
2320 EVALUATE_END(LoopPropertiesTypeEvaluate);
2321 }
2322 }
2323
2324 context.evaluator->evaluate(&target);
2325 EVALUATE_END(LoopPropertiesTypeEvaluate);
2326}
2327
2328INSTRUCTION_HANDLER(LoopPropertiesExactlyTypeStrict) {
2329 EVALUATE_BEGIN_NO_PRECONDITION(LoopPropertiesExactlyTypeStrict);
2330 const auto &target{
2331 resolve_instance(instance, instruction.relative_instance_location)};
2332 const auto &value{assume_value<ValueTypedProperties>(instruction.value)};
2333 if (!target.is_object()) [[unlikely]] {
2334 EVALUATE_END(LoopPropertiesExactlyTypeStrict);
2335 }
2336
2337 const auto &object{target.as_object()};
2338 if (object.size() == value.second.size()) [[likely]] {
2339 // Otherwise why emit this instruction?
2340 assert(!value.second.empty());
2341 result = true;
2342 for (const auto &entry : object) {
2343 // The property count already matches the required set, so confirming
2344 // that every property is one of the required names is what makes the
2345 // object exactly the required set, and the value must be of the type
2346 if (!value.second.contains(entry.first, entry.hash) ||
2347 effective_type_strict_real(entry.second) != value.first)
2348 [[unlikely]] {
2349 result = false;
2350 break;
2351 }
2352 }
2353 }
2354
2355 EVALUATE_END(LoopPropertiesExactlyTypeStrict);
2356}
2357
2358INSTRUCTION_HANDLER(LoopPropertiesExactlyTypeStrictHash) {
2359 EVALUATE_BEGIN_NO_PRECONDITION(LoopPropertiesExactlyTypeStrictHash);
2360 const auto &target{
2361 resolve_instance(instance, instruction.relative_instance_location)};
2362 // TODO: Take advantage of the table of contents structure to speed up checks
2363 const auto &value{assume_value<ValueTypedHashes>(instruction.value)};
2364
2365 if (!target.is_object()) [[unlikely]] {
2366 EVALUATE_END(LoopPropertiesExactlyTypeStrictHash);
2367 }
2368
2369 const auto &object{target.as_object()};
2370 const auto size{object.size()};
2371 if (size == value.second.first.size()) [[likely]] {
2372 // Otherwise why emit this instruction?
2373 assert(!value.second.first.empty());
2374
2375 // The idea is to first assume the object property ordering and the
2376 // hashes collection aligns. If they don't we do a full comparison
2377 // from where we left of.
2378
2379 std::size_t index{0};
2380 for (const auto &entry : object) {
2381 if (effective_type_strict_real(entry.second) != value.first) {
2382 EVALUATE_END(LoopPropertiesExactlyTypeStrictHash);
2383 }
2384
2385 // A hash match alone does not prove the property is required, as two
2386 // distinct names can share a hash, so the name itself is confirmed
2387 if (entry.hash != value.second.first[index].first ||
2388 entry.first != value.second.first[index].second) {
2389 break;
2390 }
2391
2392 index += 1;
2393 }
2394
2395 result = true;
2396 if (index < size) {
2397 auto iterator = object.cbegin();
2398 // Continue where we left
2399 std::advance(iterator, index);
2400 for (; iterator != object.cend(); ++iterator) {
2401 if (effective_type_strict_real(iterator->second) != value.first) {
2402 result = false;
2403 break;
2404 }
2405
2406 // NOLINTNEXTLINE(modernize-use-ranges)
2407 if (std::ranges::none_of(value.second.first,
2408 [&iterator](const auto &entry) -> bool {
2409 return entry.first == iterator->hash &&
2410 entry.second == iterator->first;
2411 })) {
2412 result = false;
2413 break;
2414 }
2415 }
2416 }
2417 }
2418
2419 EVALUATE_END(LoopPropertiesExactlyTypeStrictHash);
2420}
2421
2422INSTRUCTION_HANDLER(LoopPropertiesTypeStrict) {
2423 EVALUATE_BEGIN_NON_STRING(LoopPropertiesTypeStrict, target.is_object());
2424 result = true;
2425 const auto value{assume_value_copy<ValueType>(instruction.value)};
2426 for (const auto &entry : target.as_object()) {
2427 if (effective_type_strict_real(entry.second) != value) [[unlikely]] {
2428 result = false;
2429 break;
2430 }
2431 }
2432
2433 EVALUATE_END(LoopPropertiesTypeStrict);
2434}
2435
2436INSTRUCTION_HANDLER(LoopPropertiesTypeStrictEvaluate) {
2437 EVALUATE_BEGIN_NON_STRING(LoopPropertiesTypeStrictEvaluate,
2438 target.is_object());
2439 result = true;
2440 const auto value{assume_value_copy<ValueType>(instruction.value)};
2441 for (const auto &entry : target.as_object()) {
2442 if (effective_type_strict_real(entry.second) != value) [[unlikely]] {
2443 result = false;
2444 EVALUATE_END(LoopPropertiesTypeStrictEvaluate);
2445 }
2446 }
2447
2448 context.evaluator->evaluate(&target);
2449 EVALUATE_END(LoopPropertiesTypeStrictEvaluate);
2450}
2451
2452INSTRUCTION_HANDLER(LoopPropertiesTypeStrictAny) {
2453 EVALUATE_BEGIN_NON_STRING(LoopPropertiesTypeStrictAny, target.is_object());
2454 result = true;
2455 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
2456 assert(value.any());
2457 result = type_set_spans_numbers(value)
2458 ? object_values_in_type_set<false>(target, value)
2459 : object_values_in_type_set<true>(target, value);
2460
2461 EVALUATE_END(LoopPropertiesTypeStrictAny);
2462}
2463
2464INSTRUCTION_HANDLER(LoopPropertiesTypeStrictAnyEvaluate) {
2465 EVALUATE_BEGIN_NON_STRING(LoopPropertiesTypeStrictAnyEvaluate,
2466 target.is_object());
2467 result = true;
2468 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
2469 assert(value.any());
2470 result = type_set_spans_numbers(value)
2471 ? object_values_in_type_set<false>(target, value)
2472 : object_values_in_type_set<true>(target, value);
2473 if (!result) [[unlikely]] {
2474 EVALUATE_END(LoopPropertiesTypeStrictAnyEvaluate);
2475 }
2476
2477 context.evaluator->evaluate(&target);
2478 EVALUATE_END(LoopPropertiesTypeStrictAnyEvaluate);
2479}
2480
2481INSTRUCTION_HANDLER(LoopKeys) {
2482 EVALUATE_BEGIN_NON_STRING(LoopKeys, target.is_object());
2483 assert(!instruction.children.empty());
2484 result = true;
2485 for (const auto &entry : target.as_object()) {
2486 if constexpr (HasCallback) {
2487 if (track) {
2488 context.evaluator->instance_location.push_back(entry.first);
2489 }
2490 }
2491
2492 for (const auto &child : instruction.children) {
2493 if (!EVALUATE_RECURSE_ON_PROPERTY_NAME(child, Evaluator::NULL_VALUE,
2494 entry.first)) [[unlikely]] {
2495 result = false;
2496
2497 if constexpr (HasCallback) {
2498 if (track) {
2499 context.evaluator->instance_location.pop_back();
2500 }
2501 }
2502
2503 EVALUATE_END(LoopKeys);
2504 }
2505 }
2506
2507 if constexpr (HasCallback) {
2508 if (track) {
2509 context.evaluator->instance_location.pop_back();
2510 }
2511 }
2512 }
2513
2514 EVALUATE_END(LoopKeys);
2515}
2516
2517INSTRUCTION_HANDLER(LoopItems) {
2518 EVALUATE_BEGIN_NON_STRING(LoopItems, target.is_array());
2519 assert(!instruction.children.empty());
2520 result = true;
2521
2522 // To avoid index lookups and unnecessary conditionals
2523 if constexpr (!Track && !HasCallback) {
2524 // Dynamic pushes and pops a resource per instruction that the direct path
2525 // would skip, and children are evaluated at `depth + 1`, which the direct
2526 // path does not route through the depth check. Both conditions hold for
2527 // every child, so decide once rather than per element
2528 const auto direct_children{!Dynamic && depth < DEPTH_LIMIT};
2529 for (const auto &new_instance : target.as_array()) {
2530 for (const auto &child : instruction.children) {
2531 bool child_ok;
2532 if (direct_children) [[likely]] {
2533 switch (child.type) {
2534 case InstructionIndex::AssertionTypeArrayBounded:
2535 child_ok =
2536 DIRECT(AssertionTypeArrayBounded,
2537 resolve_instance(new_instance,
2538 child.relative_instance_location),
2539 assume_value<ValueRange>(child.value));
2540 break;
2541 default:
2542 child_ok = EVALUATE_RECURSE(child, new_instance);
2543 break;
2544 }
2545 } else {
2546 child_ok = EVALUATE_RECURSE(child, new_instance);
2547 }
2548
2549 if (!child_ok) [[unlikely]] {
2550 result = false;
2551 EVALUATE_END(LoopItems);
2552 }
2553 }
2554 }
2555 } else {
2556 for (std::size_t index = 0; index < target.array_size(); index++) {
2557 if constexpr (HasCallback) {
2558 if (track) {
2559 context.evaluator->instance_location.push_back(index);
2560 }
2561 }
2562
2563 const auto &new_instance{target.at(index)};
2564 for (const auto &child : instruction.children) {
2565 if (!EVALUATE_RECURSE(child, new_instance)) [[unlikely]] {
2566 result = false;
2567
2568 if constexpr (HasCallback) {
2569 if (track) {
2570 context.evaluator->instance_location.pop_back();
2571 }
2572 }
2573
2574 EVALUATE_END(LoopItems);
2575 }
2576 }
2577
2578 if constexpr (HasCallback) {
2579 if (track) {
2580 context.evaluator->instance_location.pop_back();
2581 }
2582 }
2583 }
2584 }
2585
2586 EVALUATE_END(LoopItems);
2587}
2588
2589INSTRUCTION_HANDLER(LoopItemsFrom) {
2590 const auto value{assume_value_copy<ValueUnsignedInteger>(instruction.value)};
2591 EVALUATE_BEGIN_NON_STRING(LoopItemsFrom,
2592 target.is_array() && value < target.array_size());
2593 assert(!instruction.children.empty());
2594 result = true;
2595 for (std::size_t index = value; index < target.array_size(); index++) {
2596 if constexpr (HasCallback) {
2597 if (track) {
2598 context.evaluator->instance_location.push_back(index);
2599 }
2600 }
2601
2602 const auto &new_instance{target.at(index)};
2603 for (const auto &child : instruction.children) {
2604 if (!EVALUATE_RECURSE(child, new_instance)) [[unlikely]] {
2605 result = false;
2606
2607 if constexpr (HasCallback) {
2608 if (track) {
2609 context.evaluator->instance_location.pop_back();
2610 }
2611 }
2612
2613 EVALUATE_END(LoopItemsFrom);
2614 }
2615 }
2616
2617 if constexpr (HasCallback) {
2618 if (track) {
2619 context.evaluator->instance_location.pop_back();
2620 }
2621 }
2622 }
2623
2624 EVALUATE_END(LoopItemsFrom);
2625}
2626
2627INSTRUCTION_HANDLER(LoopItemsUnevaluated) {
2628 EVALUATE_BEGIN_NON_STRING(LoopItemsUnevaluated, target.is_array());
2629 // The subschema may compile to no children, in which case every item
2630 // trivially matches and only the evaluation marking below takes effect
2631 result = true;
2632
2633 if (!context.evaluator->is_evaluated(&target)) {
2634 for (std::size_t index = 0; index < target.array_size(); index++) {
2635 const auto &new_instance{target.at(index)};
2636 if (context.evaluator->is_evaluated(&new_instance)) {
2637 continue;
2638 }
2639
2640 if constexpr (HasCallback) {
2641 context.evaluator->instance_location.push_back(index);
2642 }
2643 for (const auto &child : instruction.children) {
2644 if (!EVALUATE_RECURSE(child, new_instance)) [[unlikely]] {
2645 result = false;
2646 if constexpr (HasCallback) {
2647 context.evaluator->instance_location.pop_back();
2648 }
2649 EVALUATE_END(LoopItemsUnevaluated);
2650 }
2651 }
2652
2653 if constexpr (HasCallback) {
2654 context.evaluator->instance_location.pop_back();
2655 }
2656 }
2657
2658 context.evaluator->evaluate(&target);
2659 }
2660
2661 EVALUATE_END(LoopItemsUnevaluated);
2662}
2663
2664INSTRUCTION_HANDLER(LoopItemsType) {
2665 EVALUATE_BEGIN_NON_STRING(LoopItemsType, target.is_array());
2666 result = true;
2667 const auto value{assume_value_copy<ValueType>(instruction.value)};
2668 for (const auto &entry : target.as_array()) {
2669 if (entry.type() != value &&
2670 // In non-strict mode, we consider a real number that represents an
2671 // integer to be an integer
2672 (value != JSON::Type::Integer || !entry.is_integral())) [[unlikely]] {
2673 result = false;
2674 break;
2675 }
2676 }
2677
2678 EVALUATE_END(LoopItemsType);
2679}
2680
2681INSTRUCTION_HANDLER(LoopItemsTypeStrict) {
2682 EVALUATE_BEGIN_NON_STRING(LoopItemsTypeStrict, target.is_array());
2683 result = true;
2684 const auto value{assume_value_copy<ValueType>(instruction.value)};
2685 for (const auto &entry : target.as_array()) {
2686 if (effective_type_strict_real(entry) != value) [[unlikely]] {
2687 result = false;
2688 break;
2689 }
2690 }
2691
2692 EVALUATE_END(LoopItemsTypeStrict);
2693}
2694
2695INSTRUCTION_HANDLER(LoopItemsTypeStrictAny) {
2696 EVALUATE_BEGIN_NON_STRING(LoopItemsTypeStrictAny, target.is_array());
2697 const auto value{assume_value_copy<ValueTypes>(instruction.value)};
2698 assert(value.any());
2699
2700 result = type_set_spans_numbers(value)
2701 ? array_items_in_type_set<false>(target, value)
2702 : array_items_in_type_set<true>(target, value);
2703
2704 EVALUATE_END(LoopItemsTypeStrictAny);
2705}
2706
2707INSTRUCTION_HANDLER(LoopItemsPropertiesExactlyTypeStrictHash) {
2708 EVALUATE_BEGIN_NO_PRECONDITION(LoopItemsPropertiesExactlyTypeStrictHash);
2709 const auto &target{
2710 resolve_instance(instance, instruction.relative_instance_location)};
2711 // TODO: Take advantage of the table of contents structure to speed up checks
2712 const auto &value{assume_value<ValueTypedHashes>(instruction.value)};
2713 // Otherwise why emit this instruction?
2714 assert(!value.second.first.empty());
2715
2716 result = true;
2717
2718 if (!target.is_array()) [[unlikely]] {
2719 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2720 }
2721
2722 const auto hashes_size{value.second.first.size()};
2723 for (const auto &item : target.as_array()) {
2724 if (!item.is_object()) [[unlikely]] {
2725 result = false;
2726 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2727 }
2728
2729 const auto &object{item.as_object()};
2730 const auto size{object.size()};
2731 if (size != hashes_size) [[unlikely]] {
2732 result = false;
2733 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2734 }
2735
2736 // Unroll, for performance reasons, for small collections. A perfect hash
2737 // records the key's size, which the compiler requires of every key here,
2738 // so matching one proves the keys equal
2739 if (hashes_size == 3) {
2740 for (const auto &entry : object) {
2741 if (effective_type_strict_real(entry.second) != value.first)
2742 // NOLINTNEXTLINE(bugprone-branch-clone)
2743 [[unlikely]] {
2744 result = false;
2745 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2746 } else if (!(entry.hash == value.second.first[0].first ||
2747 entry.hash == value.second.first[1].first ||
2748 entry.hash == value.second.first[2].first)) {
2749 result = false;
2750 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2751 }
2752 }
2753 } else if (hashes_size == 2) {
2754 for (const auto &entry : object) {
2755 if (effective_type_strict_real(entry.second) != value.first)
2756 // NOLINTNEXTLINE(bugprone-branch-clone)
2757 [[unlikely]] {
2758 result = false;
2759 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2760 } else if (!(entry.hash == value.second.first[0].first ||
2761 entry.hash == value.second.first[1].first)) {
2762 result = false;
2763 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2764 }
2765 }
2766 } else if (hashes_size == 1) {
2767 const auto &entry{*object.cbegin()};
2768 if (effective_type_strict_real(entry.second) != value.first ||
2769 entry.hash != value.second.first[0].first) [[unlikely]] {
2770 result = false;
2771 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2772 }
2773 } else {
2774 std::size_t index{0};
2775 for (const auto &entry : object) {
2776 if (effective_type_strict_real(entry.second) != value.first)
2777 // NOLINTNEXTLINE(bugprone-branch-clone)
2778 [[unlikely]] {
2779 result = false;
2780 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2781 } else if (entry.hash == value.second.first[index].first) {
2782 index += 1;
2783 continue;
2784 } else if (!std::ranges::any_of(
2785 value.second.first,
2786 [&entry](const auto &hash_entry) -> bool {
2787 return hash_entry.first == entry.hash;
2788 })) {
2789 result = false;
2790 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2791 } else {
2792 index += 1;
2793 }
2794 }
2795 }
2796 }
2797
2798 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash);
2799}
2800
2801INSTRUCTION_HANDLER(LoopItemsPropertiesExactlyTypeStrictHash3) {
2802 EVALUATE_BEGIN_NO_PRECONDITION(LoopItemsPropertiesExactlyTypeStrictHash3);
2803 const auto &target{
2804 resolve_instance(instance, instruction.relative_instance_location)};
2805 // TODO: Take advantage of the table of contents structure to speed up checks
2806 const auto &value{assume_value<ValueTypedHashes>(instruction.value)};
2807 assert(value.second.first.size() == 3);
2808 // Otherwise why emit this instruction?
2809 assert(!value.second.first.empty());
2810
2811 if (!target.is_array()) [[unlikely]] {
2812 result = true;
2813 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash3);
2814 }
2815
2816 for (const auto &item : target.as_array()) {
2817 if (!item.is_object()) [[unlikely]] {
2818 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash3);
2819 }
2820
2821 const auto &object{item.as_object()};
2822 if (object.size() != 3) [[unlikely]] {
2823 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash3);
2824 }
2825
2826 const auto &value_1{object.at(0)};
2827 const auto &value_2{object.at(1)};
2828 const auto &value_3{object.at(2)};
2829
2830 if (effective_type_strict_real(value_1.second) != value.first ||
2831 effective_type_strict_real(value_2.second) != value.first ||
2832 effective_type_strict_real(value_3.second) != value.first)
2833 [[unlikely]] {
2834 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash3);
2835 }
2836
2837 // A perfect hash records the key's size, which the compiler requires of
2838 // every key here, so matching one proves the keys equal and the unrolled
2839 // permutation check compares nothing but hashes
2840 if ((value_1.hash == value.second.first[0].first &&
2841 value_2.hash == value.second.first[1].first &&
2842 value_3.hash == value.second.first[2].first) ||
2843 (value_1.hash == value.second.first[0].first &&
2844 value_2.hash == value.second.first[2].first &&
2845 value_3.hash == value.second.first[1].first) ||
2846 (value_1.hash == value.second.first[1].first &&
2847 value_2.hash == value.second.first[0].first &&
2848 value_3.hash == value.second.first[2].first) ||
2849 (value_1.hash == value.second.first[1].first &&
2850 value_2.hash == value.second.first[2].first &&
2851 value_3.hash == value.second.first[0].first) ||
2852 (value_1.hash == value.second.first[2].first &&
2853 value_2.hash == value.second.first[0].first &&
2854 value_3.hash == value.second.first[1].first) ||
2855 (value_1.hash == value.second.first[2].first &&
2856 value_2.hash == value.second.first[1].first &&
2857 value_3.hash == value.second.first[0].first)) {
2858 continue;
2859 }
2860 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash3);
2861 }
2862
2863 result = true;
2864 EVALUATE_END(LoopItemsPropertiesExactlyTypeStrictHash3);
2865}
2866
2867INSTRUCTION_DIRECT_COPY(LoopItemsIntegerBounded, ValueIntegerBounds) {
2868 if (!target.is_array() || target.empty()) {
2869 return true;
2870 }
2871 for (const auto &element : target.as_array()) {
2872 if (!element.is_number()) [[unlikely]] {
2873 return false;
2874 }
2875
2876 if (element.is_integer()) {
2877 const auto integer{element.to_integer()};
2878 if (integer < value.first || integer > value.second) [[unlikely]] {
2879 return false;
2880 }
2881 } else if (element.is_real()) {
2882 const auto real{element.to_real()};
2883 if (real < static_cast<double>(value.first) ||
2884 real > static_cast<double>(value.second)) [[unlikely]] {
2885 return false;
2886 }
2887 } else {
2888 const auto real{element.to_decimal().to_double()};
2889 if (real < static_cast<double>(value.first) ||
2890 real > static_cast<double>(value.second)) [[unlikely]] {
2891 return false;
2892 }
2893 }
2894 }
2895 return true;
2896}
2897
2898INSTRUCTION_HANDLER(LoopItemsIntegerBounded) {
2899 EVALUATE_BEGIN_NON_STRING(LoopItemsIntegerBounded,
2900 target.is_array() && !target.empty());
2901 result = DIRECT(LoopItemsIntegerBounded, target,
2902 assume_value_copy<ValueIntegerBounds>(instruction.value));
2903 EVALUATE_END(LoopItemsIntegerBounded);
2904}
2905
2906INSTRUCTION_HANDLER(LoopItemsIntegerBoundedSized) {
2907 const auto &value{
2908 assume_value<ValueIntegerBoundsWithSize>(instruction.value)};
2909 const auto &integer_bounds{value.first};
2910 const auto minimum_size{std::get<0>(value.second)};
2911 const auto &maximum_size{std::get<1>(value.second)};
2912 EVALUATE_BEGIN_NON_STRING(LoopItemsIntegerBoundedSized, true);
2913 if (!target.is_array() || target.array_size() < minimum_size ||
2914 (maximum_size.has_value() &&
2915 target.array_size() > maximum_size.value())) {
2916 EVALUATE_END(LoopItemsIntegerBoundedSized);
2917 }
2918
2919 result = true;
2920 for (const auto &element : target.as_array()) {
2921 if (!element.is_number()) [[unlikely]] {
2922 result = false;
2923 break;
2924 }
2925
2926 if (element.is_integer()) {
2927 const auto integer{element.to_integer()};
2928 if (integer < integer_bounds.first || integer > integer_bounds.second)
2929 [[unlikely]] {
2930 result = false;
2931 break;
2932 }
2933 } else if (element.is_real()) {
2934 const auto real{element.to_real()};
2935 if (real < static_cast<double>(integer_bounds.first) ||
2936 real > static_cast<double>(integer_bounds.second)) [[unlikely]] {
2937 result = false;
2938 break;
2939 }
2940 } else {
2941 const auto real{element.to_decimal().to_double()};
2942 if (real < static_cast<double>(integer_bounds.first) ||
2943 real > static_cast<double>(integer_bounds.second)) [[unlikely]] {
2944 result = false;
2945 break;
2946 }
2947 }
2948 }
2949
2950 EVALUATE_END(LoopItemsIntegerBoundedSized);
2951}
2952
2953// `items` whose subschema is itself an `items` with a size bound compiles to a
2954// loop whose body is two instructions that both only look at the item, which
2955// means a dispatch per item of the outer array. Nesting the two loops here
2956// decides the whole shape in one instruction instead
2957INSTRUCTION_HANDLER(LoopItemsArrayBoundedTypeStrictAny) {
2958 EVALUATE_BEGIN_NON_STRING(LoopItemsArrayBoundedTypeStrictAny,
2959 target.is_array());
2960 const auto &value{assume_value<ValueTypesWithSize>(instruction.value)};
2961 const auto types{value.first};
2962 const auto minimum{std::get<0>(value.second)};
2963 const auto &maximum{std::get<1>(value.second)};
2964 assert(types.any());
2965 // Require early breaking
2966 assert(!std::get<2>(value.second));
2967 result = type_set_spans_numbers(types)
2968 ? items_are_bounded_arrays_in_type_set<false>(target, types,
2969 minimum, maximum)
2970 : items_are_bounded_arrays_in_type_set<true>(target, types,
2971 minimum, maximum);
2972
2973 EVALUATE_END(LoopItemsArrayBoundedTypeStrictAny);
2974}
2975
2976INSTRUCTION_HANDLER(LoopContains) {
2977 EVALUATE_BEGIN_NON_STRING(LoopContains, target.is_array());
2978 // The subschema may compile to no children, in which case every array
2979 // element trivially matches and the range check alone decides the result
2980 const auto &value{assume_value<ValueRange>(instruction.value)};
2981 const auto &[minimum, maximum, is_exhaustive] = value;
2982 // Note that the range may be unsatisfiable, as `minContains` and
2983 // `maxContains` are free to invert it, in which case no array matches
2984 result = minimum == 0;
2985 auto match_count{
2986 std::numeric_limits<std::remove_cvref_t<decltype(minimum)>>::min()};
2987
2988 for (std::size_t index = 0; index < target.array_size(); index++) {
2989 if constexpr (HasCallback) {
2990 if (track) {
2991 context.evaluator->instance_location.push_back(index);
2992 }
2993 }
2994
2995 const auto &new_instance{target.at(index)};
2996 bool subresult{true};
2997 for (const auto &child : instruction.children) {
2998 if (!EVALUATE_RECURSE(child, new_instance)) {
2999 subresult = false;
3000 break;
3001 }
3002 }
3003
3004 if constexpr (HasCallback) {
3005 if (track) {
3006 context.evaluator->instance_location.pop_back();
3007 }
3008 }
3009
3010 if (subresult) {
3011 match_count += 1;
3012
3013 // Exceeding the upper bound is definitely a failure
3014 if (maximum.has_value() && match_count > maximum.value()) {
3015 result = false;
3016
3017 // Note that here we don't want to consider whether to run
3018 // exhaustively or not. At this point, its already a failure,
3019 // and anything that comes after would not run at all anyway
3020 break;
3021 }
3022
3023 if (match_count >= minimum) {
3024 result = true;
3025
3026 // Exceeding the lower bound when there is no upper bound
3027 // is definitely a success
3028 if (!maximum.has_value() && !is_exhaustive) {
3029 break;
3030 }
3031 }
3032 }
3033 }
3034
3035 EVALUATE_END(LoopContains);
3036}
3037// NOLINTEND(readability-identifier-naming)
3038
3039#undef INSTRUCTION_HANDLER
3040
3041template <bool Track, bool Dynamic, bool HasCallback>
3042using DispatchHandler = bool (*)(
3043 const sourcemeta::blaze::Instruction &, const sourcemeta::core::JSON &,
3045
3046template <bool Track, bool Dynamic, bool HasCallback>
3047// Must have same order as InstructionIndex
3048// NOLINTNEXTLINE(modernize-avoid-c-arrays)
3049static constexpr DispatchHandler<Track, Dynamic, HasCallback> HANDLERS[105] = {
3050 AssertionFail,
3051 AssertionDefines,
3052 AssertionDefinesStrict,
3053 AssertionDefinesAll,
3054 AssertionDefinesAllStrict,
3055 AssertionDefinesExactly,
3056 AssertionDefinesExactlyStrict,
3057 AssertionDefinesExactlyStrictHash3,
3058 AssertionPropertyDependencies,
3059 AssertionType,
3060 AssertionTypeAny,
3061 AssertionTypeStrict,
3062 AssertionTypeStrictAny,
3063 AssertionNotTypeStrictAny,
3064 AssertionTypeStringBounded,
3065 AssertionTypeStringUpper,
3066 AssertionTypeArrayBounded,
3067 AssertionTypeArrayUpper,
3068 AssertionTypeObjectBounded,
3069 AssertionTypeObjectUpper,
3070 AssertionRegex,
3071 AssertionStringSizeLess,
3072 AssertionStringSizeGreater,
3073 AssertionArraySizeLess,
3074 AssertionArraySizeGreater,
3075 AssertionObjectSizeLess,
3076 AssertionObjectSizeGreater,
3077 AssertionEqual,
3078 AssertionEqualsAny,
3079 AssertionEqualsAnyStringHash,
3080 AssertionGreaterEqual,
3081 AssertionLessEqual,
3082 AssertionGreater,
3083 AssertionLess,
3084 AssertionUnique,
3085 AssertionDivisible,
3086 AssertionTypeIntegerBounded,
3087 AssertionTypeIntegerBoundedStrict,
3088 AssertionTypeIntegerLowerBound,
3089 AssertionTypeIntegerLowerBoundStrict,
3090 AssertionNumberIntegerBounded,
3091 AssertionNumberType,
3092 AssertionStringType,
3093 AssertionPropertyType,
3094 AssertionPropertyTypeEvaluate,
3095 AssertionPropertyTypeStrict,
3096 AssertionPropertyTypeStrictEvaluate,
3097 AssertionPropertyTypeStrictAny,
3098 AssertionPropertyTypeStrictAnyEvaluate,
3099 AssertionPropertyEqual,
3100 AssertionArrayPrefix,
3101 AssertionArrayPrefixEvaluate,
3102 AssertionObjectPropertiesSimple,
3103 AnnotationEmit,
3104 AnnotationEmitWrapped,
3105 AnnotationToParent,
3106 AnnotationBasenameToParent,
3107 Evaluate,
3108 LogicalNot,
3109 LogicalNotEvaluate,
3110 LogicalOr,
3111 LogicalAnd,
3112 LogicalXor,
3113 LogicalCondition,
3114 LogicalWhenType,
3115 LogicalWhenDefines,
3116 LogicalWhenArraySizeGreater,
3117 LoopPropertiesUnevaluated,
3118 LoopPropertiesUnevaluatedExcept,
3119 LoopPropertiesMatch,
3120 LoopPropertiesMatchClosed,
3121 LoopProperties,
3122 LoopPropertiesEvaluate,
3123 LoopPropertiesRegex,
3124 LoopPropertiesRegexClosed,
3125 LoopPropertiesStartsWith,
3126 LoopPropertiesExcept,
3127 LoopPropertiesType,
3128 LoopPropertiesTypeEvaluate,
3129 LoopPropertiesExactlyTypeStrict,
3130 LoopPropertiesExactlyTypeStrictHash,
3131 LoopPropertiesTypeStrict,
3132 LoopPropertiesTypeStrictEvaluate,
3133 LoopPropertiesTypeStrictAny,
3134 LoopPropertiesTypeStrictAnyEvaluate,
3135 LoopKeys,
3136 LoopItems,
3137 LoopItemsFrom,
3138 LoopItemsUnevaluated,
3139 LoopItemsType,
3140 LoopItemsTypeStrict,
3141 LoopItemsTypeStrictAny,
3142 LoopItemsPropertiesExactlyTypeStrictHash,
3143 LoopItemsPropertiesExactlyTypeStrictHash3,
3144 LoopItemsIntegerBounded,
3145 LoopItemsIntegerBoundedSized,
3146 LoopItemsArrayBoundedTypeStrictAny,
3147 LoopContains,
3148 ControlGroup,
3149 ControlGroupWhenDefines,
3150 ControlGroupWhenDefinesDirect,
3151 ControlGroupWhenType,
3152 ControlEvaluate,
3153 ControlDynamicAnchorJump,
3154 ControlJump};
3155
3156template <bool Track, bool Dynamic, bool HasCallback>
3157inline auto
3158evaluate_instruction(const sourcemeta::blaze::Instruction &instruction,
3159 const sourcemeta::core::JSON &instance,
3160 const std::uint64_t depth,
3162 -> bool {
3163 if (depth > DEPTH_LIMIT) [[unlikely]] {
3164 throw EvaluationError("The evaluation path depth limit was reached "
3165 "likely due to infinite recursion");
3166 }
3167
3168 return HANDLERS<Track, Dynamic, HasCallback>[std::to_underlying(
3169 instruction.type)](instruction, instance, depth, context);
3170}
3171
3172template <bool Track, bool Dynamic, bool HasCallback>
3173inline auto evaluate_instruction_without_callback(
3174 const sourcemeta::blaze::Instruction &instruction,
3175 const sourcemeta::core::JSON &instance, const std::uint64_t depth,
3177 if (depth > DEPTH_LIMIT) [[unlikely]] {
3178 throw EvaluationError("The evaluation path depth limit was reached "
3179 "likely due to infinite recursion");
3180 }
3181
3183 context.schema, context.callback, context.evaluator,
3184 context.property_target};
3185 return HANDLERS<false, Dynamic, false>[std::to_underlying(instruction.type)](
3186 instruction, instance, depth, plain_context);
3187}
3188
3189template <bool Track, bool Dynamic, bool HasCallback>
3190inline auto evaluate_instruction_with_property(
3191 const sourcemeta::blaze::Instruction &instruction,
3192 const sourcemeta::core::JSON &instance, const std::uint64_t depth,
3194 const sourcemeta::core::JSON::String &name) -> bool {
3195 const auto *previous = context.property_target;
3196 context.property_target = &name;
3197 const auto result =
3198 evaluate_instruction(instruction, instance, depth, context);
3199 context.property_target = previous;
3200 return result;
3201}
3202
3203} // namespace sourcemeta::blaze::dispatch
3204
3205#undef EVALUATE_PUSH
3206#undef EVALUATE_POP
3207#undef EVALUATE_BEGIN
3208#undef EVALUATE_BEGIN_NON_STRING
3209#undef EVALUATE_BEGIN_IF_STRING
3210#undef EVALUATE_BEGIN_TRY_TARGET
3211#undef EVALUATE_BEGIN_NO_PRECONDITION
3212#undef EVALUATE_BEGIN_NO_PRECONDITION_AND_NO_PUSH
3213#undef EVALUATE_BEGIN_PASS_THROUGH
3214#undef EVALUATE_END
3215#undef EVALUATE_END_NO_POP
3216#undef EVALUATE_END_PASS_THROUGH
3217#undef EVALUATE_ANNOTATION
3218#undef EVALUATE_RECURSE
3219#undef EVALUATE_RECURSE_ON_PROPERTY_NAME
3220#undef SOURCEMETA_ASSUME
3221#undef SOURCEMETA_STRINGIFY
3222
3223#endif
Definition evaluator.h:83
std::pair< std::vector< std::pair< ValueStringSet::hash_type, ValueString > >, std::vector< ValueIndexPair > > ValueStringHashes
Definition evaluator_value.h:180
sourcemeta::core::JSON::Type ValueType
Definition evaluator_value.h:64
std::function< void( const EvaluationType, bool, const Instruction &, const InstructionExtra &, const sourcemeta::core::WeakPointer &, const sourcemeta::core::WeakPointer &, const sourcemeta::core::JSON &)> Callback
Definition evaluator.h:77
sourcemeta::core::JSON ValueJSON
Definition evaluator_value.h:33
sourcemeta::core::JSON::TypeSet ValueTypes
Definition evaluator_value.h:60
std::pair< std::vector< ValueNamedIndex >, std::vector< ValueIndexPair > > ValueNamedIndexes
Definition evaluator_value.h:171
std::size_t ValueUnsignedInteger
Definition evaluator_value.h:101
std::tuple< std::size_t, std::optional< std::size_t >, bool > ValueRange
Definition evaluator_value.h:107
std::pair< std::int64_t, std::int64_t > ValueIntegerBounds
Definition evaluator_value.h:190
std::unordered_set< sourcemeta::core::JSON, sourcemeta::core::HashJSON< sourcemeta::core::JSON > > ValueSet
Definition evaluator_value.h:37
Definition evaluator_instruction.h:303
Definition evaluator.h:38
Definition evaluator_dispatch.h:421