Blaze 0.0.1
The ultra high-performance JSON Schema evaluator
Loading...
Searching...
No Matches
evaluator.h
1#ifndef SOURCEMETA_BLAZE_EVALUATOR_H_
2#define SOURCEMETA_BLAZE_EVALUATOR_H_
3
4#ifndef SOURCEMETA_BLAZE_EVALUATOR_EXPORT
5#include <sourcemeta/blaze/evaluator_export.h>
6#endif
7
8#include <sourcemeta/blaze/evaluator_error.h>
9#include <sourcemeta/blaze/evaluator_instruction.h>
10
11#include <sourcemeta/core/json.h>
12#include <sourcemeta/core/jsonpointer.h>
13#include <sourcemeta/core/regex.h>
14
15#include <algorithm> // std::min, std::any_of, std::find
16#include <cassert> // assert
17#include <cstdint> // std::uint8_t
18#include <functional> // std::function
19#include <limits> // std::numeric_limits
20#include <ranges> // std::ranges
21#include <string_view> // std::string_view
22#include <utility> // std::pair
23#include <vector> // std::vector
24
33
34namespace sourcemeta::blaze {
35
38struct Template {
39 bool dynamic;
40 bool track;
41 std::vector<Instructions> targets;
42 std::vector<std::pair<std::size_t, std::size_t>> labels;
43 std::vector<InstructionExtra> extra;
45 std::vector<std::string> vocabularies;
47 std::vector<std::string> messages;
48};
49
51constexpr std::size_t JSON_VERSION{15};
52
55auto SOURCEMETA_BLAZE_EVALUATOR_EXPORT
56from_json(const sourcemeta::core::JSON &json) -> std::optional<Template>;
57
60enum class EvaluationType : std::uint8_t { Pre, Post };
61
76// TODO(C++23): Use std::move_only_function when available in libc++
77using Callback = std::function<void(
78 const EvaluationType, bool, const Instruction &, const InstructionExtra &,
79 const sourcemeta::core::WeakPointer &,
80 const sourcemeta::core::WeakPointer &, const sourcemeta::core::JSON &)>;
81
83class SOURCEMETA_BLAZE_EVALUATOR_EXPORT Evaluator {
84public:
113 auto validate(const Template &schema, const sourcemeta::core::JSON &instance)
114 -> bool {
115 assert(this->evaluate_path.empty());
116 assert(this->instance_location.empty());
117 assert(this->resources.empty());
118
119 if (schema.track && schema.dynamic) [[unlikely]] {
120 this->evaluated.clear();
121 return this->evaluate_impl<true, true, false>(schema, instance, nullptr);
122 } else if (schema.track) [[unlikely]] {
123 this->evaluated.clear();
124 return this->evaluate_impl<true, false, false>(schema, instance, nullptr);
125 } else if (schema.dynamic) [[unlikely]] {
126 return this->evaluate_impl<false, true, false>(schema, instance, nullptr);
127 } else {
128 return this->evaluate_impl<false, false, false>(schema, instance,
129 nullptr);
130 }
131 }
132
193 auto validate(const Template &schema, const sourcemeta::core::JSON &instance,
194 const Callback &callback) -> bool {
195 assert(this->evaluate_path.empty());
196 assert(this->instance_location.empty());
197 assert(this->resources.empty());
198 this->evaluated.clear();
199 if (schema.dynamic) [[unlikely]] {
200 return this->evaluate_impl<true, true, true>(schema, instance, &callback);
201 }
202
203 return this->evaluate_impl<true, false, true>(schema, instance, &callback);
204 }
205
206#ifndef DOXYGEN
207 template <bool Track, bool Dynamic, bool HasCallback>
208 auto evaluate_impl(const Template &schema,
209 const sourcemeta::core::JSON &instance,
210 const Callback *callback) -> bool;
211
212 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables,cert-err58-cpp,bugprone-throwing-static-initialization)
213 static inline const sourcemeta::core::JSON NULL_VALUE{nullptr};
214 static inline const sourcemeta::core::JSON EMPTY_STRING{""};
215 // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables,cert-err58-cpp,bugprone-throwing-static-initialization)
216
217 // Compute a hash that fits within the IEEE 754 double-precision safe
218 // integer range (2^53 - 1), ensuring the serialized template labels
219 // are usable from JavaScript and other languages whose numbers are doubles
220 [[nodiscard]] static auto hash(const std::size_t resource,
221 const std::string_view fragment) noexcept
222 -> std::size_t {
223 constexpr std::size_t MASK{(1ULL << 53) - 1};
224 std::size_t result{14695981039346656037ULL & MASK};
225 for (const auto byte : fragment) {
226 result ^= static_cast<std::size_t>(static_cast<unsigned char>(byte));
227 result = (result * 1099511628211ULL) & MASK;
228 }
229
230 return (resource + result) & MASK;
231 }
232
233 auto evaluate(const sourcemeta::core::JSON *target) -> void {
234 Evaluation mark{.instance = target,
235 .evaluate_path = this->evaluate_path,
236 .skip = false};
237 this->evaluated.push_back(std::move(mark));
238 }
239
240 [[nodiscard]] auto is_evaluated(const sourcemeta::core::JSON *target) const
241 -> bool {
242 // NOLINTNEXTLINE(modernize-loop-convert)
243 for (auto iterator = this->evaluated.rbegin();
244 iterator != this->evaluated.rend(); ++iterator) {
245 if (target == iterator->instance && !iterator->skip &&
246 iterator->evaluate_path.starts_with_initial(this->evaluate_path)) {
247 return true;
248 }
249 }
250
251 return false;
252 }
253
254 auto unevaluate() -> void {
255 for (auto &entry : this->evaluated) {
256 if (!entry.skip && entry.evaluate_path.starts_with(this->evaluate_path)) {
257 entry.skip = true;
258 }
259 }
260 }
261
262 // A subschema that ends up failing must not contribute any of the marks it
263 // made along the way, so a disjunction records the length here before each
264 // branch and truncates back to it when the branch does not hold. Marks are
265 // only ever appended, never inserted earlier, so everything a branch adds
266 // sits past the recorded length and nothing from outside it can be lost
267 [[nodiscard]] auto checkpoint() const -> std::size_t {
268 return this->evaluated.size();
269 }
270
271 auto rewind(const std::size_t checkpoint) -> void {
272 assert(checkpoint <= this->evaluated.size());
273 this->evaluated.resize(checkpoint);
274 }
275
276#if defined(_MSC_VER)
277#pragma warning(disable : 4251 4275)
278#endif
279 sourcemeta::core::WeakPointer evaluate_path;
280 sourcemeta::core::WeakPointer instance_location;
281 std::vector<std::size_t> resources;
282
283 struct Evaluation {
284 const sourcemeta::core::JSON *instance;
285 sourcemeta::core::WeakPointer evaluate_path;
286 bool skip;
287 };
288
289 std::vector<Evaluation> evaluated;
290#if defined(_MSC_VER)
291#pragma warning(default : 4251 4275)
292#endif
293#endif
294};
295
296} // namespace sourcemeta::blaze
297
298#ifndef DOXYGEN
299
300#include <sourcemeta/blaze/evaluator_dispatch.h>
301
302template <bool Track, bool Dynamic, bool HasCallback>
303auto sourcemeta::blaze::Evaluator::evaluate_impl(
304 const Template &schema, const sourcemeta::core::JSON &instance,
305 const Callback *callback) -> bool {
306 assert(!schema.targets.empty());
307 dispatch::DispatchContext<Track, Dynamic, HasCallback> context{
308 .schema = &schema,
309 .callback = callback,
310 .evaluator = this,
311 .property_target = nullptr};
312 bool overall{true};
313 for (const auto &instruction : schema.targets[0]) {
314 if (!dispatch::evaluate_instruction(instruction, instance, 0, context))
315 [[unlikely]] {
316 overall = false;
317 break;
318 }
319 }
320
321 if constexpr (Track || HasCallback) {
322 assert(this->evaluate_path.empty());
323 }
324 if constexpr (HasCallback) {
325 assert(this->instance_location.empty());
326 }
327 if constexpr (Dynamic) {
328 assert(this->resources.empty());
329 }
330
331 return overall;
332}
333
334#endif // !DOXYGEN
335
336#endif
auto validate(const Template &schema, const sourcemeta::core::JSON &instance) -> bool
Definition evaluator.h:113
auto validate(const Template &schema, const sourcemeta::core::JSON &instance, const Callback &callback) -> bool
Definition evaluator.h:193
std::vector< std::string > messages
The message templates that instructions refer to, without duplicates.
Definition evaluator.h:47
std::vector< std::string > vocabularies
The vocabulary URIs that instructions refer to, without duplicates.
Definition evaluator.h:45
Definition evaluator.h:83
EvaluationType
Definition evaluator.h:60
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
auto from_json(const sourcemeta::core::JSON &json) -> std::optional< Template >
Definition evaluator_instruction.h:303
Definition evaluator_instruction.h:286
Definition evaluator.h:38