From 874bd9e1e48db3765b5a06d0266aeb53f17bde9c Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Fri, 4 Oct 2024 17:28:00 -0400 Subject: [PATCH] Upgrade JSON Toolkit to `b5c8f63fbc4b4b7a9cd4bdd71774d89db6ee2a99` Signed-off-by: Juan Cruz Viotti --- DEPENDENCIES | 2 +- vendor/jsontoolkit/CMakeLists.txt | 34 +- vendor/jsontoolkit/Config.uk | 9 - vendor/jsontoolkit/Makefile.uk | 71 - .../cmake/FindGoogleBenchmark.cmake | 5 + vendor/jsontoolkit/config.cmake.in | 6 + .../jsontoolkit/src/evaluator/CMakeLists.txt | 15 + vendor/jsontoolkit/src/evaluator/context.cc | 288 +++ vendor/jsontoolkit/src/evaluator/evaluator.cc | 1189 +++++++++++ .../sourcemeta/jsontoolkit/evaluator.h | 181 ++ .../jsontoolkit/evaluator_context.h | 138 ++ .../sourcemeta/jsontoolkit/evaluator_error.h | 39 + .../jsontoolkit/evaluator_template.h | 516 +++++ .../sourcemeta/jsontoolkit/evaluator_value.h | 115 ++ vendor/jsontoolkit/src/evaluator/trace.h | 30 + .../include/sourcemeta/jsontoolkit/json.h | 4 - .../sourcemeta/jsontoolkit/json_error.h | 4 - .../sourcemeta/jsontoolkit/json_object.h | 60 +- .../sourcemeta/jsontoolkit/json_value.h | 45 +- vendor/jsontoolkit/src/json/json_value.cc | 98 +- vendor/jsontoolkit/src/json/parser.h | 74 +- .../include/sourcemeta/jsontoolkit/jsonl.h | 4 - .../sourcemeta/jsontoolkit/jsonl_iterator.h | 8 +- vendor/jsontoolkit/src/jsonl/iterator.cc | 25 +- .../sourcemeta/jsontoolkit/jsonpointer.h | 206 +- .../jsontoolkit/jsonpointer_error.h | 4 - .../jsontoolkit/jsonpointer_pointer.h | 175 +- .../jsonpointer_subpointer_walker.h | 17 +- .../jsontoolkit/jsonpointer_token.h | 48 +- .../jsontoolkit/jsonpointer_walker.h | 17 +- .../src/jsonpointer/jsonpointer.cc | 133 +- vendor/jsontoolkit/src/jsonpointer/parser.h | 106 +- .../jsontoolkit/src/jsonpointer/stringify.h | 101 +- .../jsontoolkit/src/jsonschema/CMakeLists.txt | 31 +- vendor/jsontoolkit/src/jsonschema/bundle.cc | 90 +- vendor/jsontoolkit/src/jsonschema/compile.cc | 191 +- .../src/jsonschema/compile_describe.cc | 1784 ++++++++++++++++- .../src/jsonschema/compile_evaluate.cc | 936 --------- .../src/jsonschema/compile_helpers.h | 154 +- .../src/jsonschema/compile_json.cc | 280 +-- .../src/jsonschema/default_compiler.cc | 142 +- .../src/jsonschema/default_compiler_2019_09.h | 493 ++--- .../src/jsonschema/default_compiler_2020_12.h | 76 + .../src/jsonschema/default_compiler_draft4.h | 1221 +++++++---- .../src/jsonschema/default_compiler_draft6.h | 188 +- .../src/jsonschema/default_compiler_draft7.h | 105 +- .../src/jsonschema/default_walker.cc | 45 +- .../sourcemeta/jsontoolkit/jsonschema.h | 117 +- .../jsontoolkit/jsonschema_anchor.h | 7 +- .../jsontoolkit/jsonschema_bundle.h | 15 +- .../jsontoolkit/jsonschema_compile.h | 590 +----- .../sourcemeta/jsontoolkit/jsonschema_error.h | 32 +- .../jsontoolkit/jsonschema_reference.h | 9 +- .../jsontoolkit/jsonschema_resolver.h | 4 - .../jsontoolkit/jsonschema_transform_bundle.h | 162 -- .../jsontoolkit/jsonschema_transform_rule.h | 111 - .../jsontoolkit/jsonschema_transformer.h | 112 -- .../jsontoolkit/jsonschema_walker.h | 9 +- .../jsontoolkit/src/jsonschema/jsonschema.cc | 154 +- .../jsontoolkit/src/jsonschema/reference.cc | 171 +- vendor/jsontoolkit/src/jsonschema/resolver.cc | 7 +- .../rules/additional_properties_default.h | 32 - .../src/jsonschema/rules/const_with_type.h | 25 - .../content_media_type_without_encoding.h | 23 - .../jsonschema/rules/content_schema_default.h | 26 - .../rules/content_schema_without_media_type.h | 23 - .../src/jsonschema/rules/else_without_if.h | 23 - .../src/jsonschema/rules/enum_to_const.h | 26 - .../src/jsonschema/rules/enum_with_type.h | 29 - .../jsonschema/rules/items_array_default.h | 27 - .../jsonschema/rules/items_schema_default.h | 30 - .../rules/max_contains_without_contains.h | 23 - .../rules/min_contains_without_contains.h | 23 - .../src/jsonschema/rules/single_type_array.h | 30 - .../src/jsonschema/rules/then_without_if.h | 23 - .../rules/unevaluated_items_default.h | 26 - .../rules/unevaluated_properties_default.h | 26 - .../src/jsonschema/transform_bundle.cc | 151 -- .../src/jsonschema/transform_rule.cc | 93 - .../jsontoolkit/src/jsonschema/transformer.cc | 90 - vendor/jsontoolkit/src/jsonschema/walker.cc | 9 +- .../uri/include/sourcemeta/jsontoolkit/uri.h | 83 +- .../sourcemeta/jsontoolkit/uri_error.h | 4 - vendor/jsontoolkit/src/uri/uri.cc | 146 +- .../vendor/jsonschema-test-suite.mask | 7 - .../noa/cmake/noa/compiler/options.cmake | 16 +- .../vendor/noa/cmake/noa/library.cmake | 7 +- .../vendor/uriparser/src/UriMemory.c | 2 - 88 files changed, 7610 insertions(+), 4416 deletions(-) delete mode 100644 vendor/jsontoolkit/Config.uk delete mode 100644 vendor/jsontoolkit/Makefile.uk create mode 100644 vendor/jsontoolkit/cmake/FindGoogleBenchmark.cmake create mode 100644 vendor/jsontoolkit/src/evaluator/CMakeLists.txt create mode 100644 vendor/jsontoolkit/src/evaluator/context.cc create mode 100644 vendor/jsontoolkit/src/evaluator/evaluator.cc create mode 100644 vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator.h create mode 100644 vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h create mode 100644 vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_error.h create mode 100644 vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h create mode 100644 vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_value.h create mode 100644 vendor/jsontoolkit/src/evaluator/trace.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/compile_evaluate.cc create mode 100644 vendor/jsontoolkit/src/jsonschema/default_compiler_2020_12.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_transform_bundle.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_transform_rule.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_transformer.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/additional_properties_default.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/const_with_type.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/content_media_type_without_encoding.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/content_schema_default.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/content_schema_without_media_type.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/else_without_if.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/enum_to_const.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/enum_with_type.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/items_array_default.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/items_schema_default.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/max_contains_without_contains.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/min_contains_without_contains.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/single_type_array.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/then_without_if.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/unevaluated_items_default.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/rules/unevaluated_properties_default.h delete mode 100644 vendor/jsontoolkit/src/jsonschema/transform_bundle.cc delete mode 100644 vendor/jsontoolkit/src/jsonschema/transform_rule.cc delete mode 100644 vendor/jsontoolkit/src/jsonschema/transformer.cc diff --git a/DEPENDENCIES b/DEPENDENCIES index 339493e4..c5f24ec4 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,6 +1,6 @@ vendorpull https://github.com/sourcemeta/vendorpull 70342aaf458e6cb80baeb5b718901075fc42ede6 noa https://github.com/sourcemeta/noa 517e88aef5981b88ac6bb8caff15d17dffcb4320 -jsontoolkit https://github.com/sourcemeta/jsontoolkit a3765c8038ba4271e55318a677f6366bdaa7b805 +jsontoolkit https://github.com/sourcemeta/jsontoolkit b5c8f63fbc4b4b7a9cd4bdd71774d89db6ee2a99 bearssl https://www.bearssl.org/git/BearSSL 8ef7680081c61b486622f2d983c0d3d21e83caad zlib https://github.com/madler/zlib 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf uwebsockets https://github.com/uNetworking/uWebSockets v20.67.0 diff --git a/vendor/jsontoolkit/CMakeLists.txt b/vendor/jsontoolkit/CMakeLists.txt index eca28f8c..37f1c948 100644 --- a/vendor/jsontoolkit/CMakeLists.txt +++ b/vendor/jsontoolkit/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) project(jsontoolkit VERSION 2.0.0 LANGUAGES CXX - DESCRIPTION "The swiss-army knife for JSON programming in C++" + DESCRIPTION "The high-performance JSON Schema evaluator and related JSON utilities for modern C++" HOMEPAGE_URL "https://jsontoolkit.sourcemeta.com") list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") include(vendor/noa/cmake/noa.cmake) @@ -9,9 +9,11 @@ include(vendor/noa/cmake/noa.cmake) option(JSONTOOLKIT_URI "Build the JSON Toolkit URI library" ON) option(JSONTOOLKIT_JSON "Build the JSON Toolkit JSON library" ON) option(JSONTOOLKIT_JSONSCHEMA "Build the JSON Toolkit JSON Schema library" ON) +option(JSONTOOLKIT_EVALUATOR "Build the JSON Toolkit JSON Schema evaluator library" ON) option(JSONTOOLKIT_JSONPOINTER "Build the JSON Toolkit JSON Pointer library" ON) option(JSONTOOLKIT_JSONL "Build the JSON Toolkit JSONL library" ON) option(JSONTOOLKIT_TESTS "Build the JSON Toolkit tests" OFF) +option(JSONTOOLKIT_BENCHMARK "Build the JSON Toolkit benchmarks" OFF) option(JSONTOOLKIT_DOCS "Build the JSON Toolkit docs" OFF) option(JSONTOOLKIT_INSTALL "Install the JSON Toolkit library" ON) option(JSONTOOLKIT_ADDRESS_SANITIZER "Build JSON Toolkit with an address sanitizer" OFF) @@ -47,7 +49,14 @@ if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONPOINTER) add_subdirectory(src/jsonpointer) endif() -if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONSCHEMA) +if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR) + add_subdirectory(src/evaluator) +endif() + +if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR AND + JSONTOOLKIT_JSONSCHEMA) add_subdirectory(src/jsonschema) endif() @@ -69,6 +78,7 @@ endif() if(PROJECT_IS_TOP_LEVEL) noa_target_clang_format(SOURCES src/*.h src/*.cc + benchmark/*.h benchmark/*.cc test/*.h test/*.cc) noa_target_clang_tidy(SOURCES src/*.h src/*.cc) @@ -87,14 +97,21 @@ if(JSONTOOLKIT_TESTS) add_subdirectory(test/json) endif() - if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONSCHEMA) - add_subdirectory(test/jsonschema) - endif() - if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONPOINTER) add_subdirectory(test/jsonpointer) endif() + if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR) + add_subdirectory(test/evaluator) + endif() + + if(JSONTOOLKIT_URI AND JSONTOOLKIT_JSON AND + JSONTOOLKIT_JSONPOINTER AND JSONTOOLKIT_EVALUATOR AND + JSONTOOLKIT_JSONSCHEMA) + add_subdirectory(test/jsonschema) + endif() + if(JSONTOOLKIT_JSON AND JSONTOOLKIT_JSONL) add_subdirectory(test/jsonl) endif() @@ -106,4 +123,9 @@ if(JSONTOOLKIT_TESTS) add_subdirectory(test/packaging) endif() endif() + + if(JSONTOOLKIT_BENCHMARK) + find_package(GoogleBenchmark REQUIRED) + add_subdirectory(benchmark) + endif() endif() diff --git a/vendor/jsontoolkit/Config.uk b/vendor/jsontoolkit/Config.uk deleted file mode 100644 index 923ffdea..00000000 --- a/vendor/jsontoolkit/Config.uk +++ /dev/null @@ -1,9 +0,0 @@ -menuconfig LIBJSONTOOLKIT - bool "libjsontoolkit - a swiss-army knife library for expressive JSON programming in modern C++" - select LIBCXXABI - select LIBCXX - select CXX_THREADS - select LIBUNWIND - select LIBCOMPILER_RT - select LIBMUSL - default n diff --git a/vendor/jsontoolkit/Makefile.uk b/vendor/jsontoolkit/Makefile.uk deleted file mode 100644 index ca1d1062..00000000 --- a/vendor/jsontoolkit/Makefile.uk +++ /dev/null @@ -1,71 +0,0 @@ -$(eval $(call addlib_s,libjsontoolkit,$(CONFIG_LIBJSONTOOLKIT))) - -# Flags -LIBJSONTOOLKIT_SRC = $(LIBJSONTOOLKIT_BASE)/src -LIBJSONTOOLKIT_CXXFLAGS-y += --std=c++20 - -# JSON -CXXINCLUDES-$(CONFIG_LIBJSONTOOLKIT) += -I$(LIBJSONTOOLKIT_SRC)/json/include -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/json/json.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/json/json_value.cc - -# JSONL -CXXINCLUDES-$(CONFIG_LIBJSONTOOLKIT) += -I$(LIBJSONTOOLKIT_SRC)/jsonl/include -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonl/jsonl.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonl/iterator.cc - -# JSON Pointer -CXXINCLUDES-$(CONFIG_LIBJSONTOOLKIT) += -I$(LIBJSONTOOLKIT_SRC)/jsonpointer/include -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonpointer/jsonpointer.cc - -# uriparser -CINCLUDES-$(CONFIG_LIBJSONTOOLKIT) += -I$(LIBJSONTOOLKIT_BASE)/vendor/uriparser/include -CXXINCLUDES-$(CONFIG_LIBJSONTOOLKIT) += -I$(LIBJSONTOOLKIT_BASE)/vendor/uriparser/include -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriCommon.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriCompare.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriEscape.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriFile.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriIp4.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriIp4Base.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriMemory.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriNormalize.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriNormalizeBase.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriParse.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriParseBase.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriQuery.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriRecompose.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriResolve.c -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BASE)/vendor/uriparser/src/UriShorten.c - -# URI -CXXINCLUDES-$(CONFIG_LIBJSONTOOLKIT) += -I$(LIBJSONTOOLKIT_SRC)/uri/include -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/uri/uri.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/uri/escaping.cc - -# JSON Schema -CXXINCLUDES-$(CONFIG_LIBJSONTOOLKIT) += -I$(LIBJSONTOOLKIT_SRC)/jsonschema/include -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/anchor.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/bundle.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/default_walker.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/jsonschema.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/walker.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/reference.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/transform_rule.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/transform_bundle.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/transformer.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/resolver.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/compile.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/compile_evaluate.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/compile_json.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/compile_describe.cc -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_SRC)/jsonschema/default_compiler.cc - -# TODO: Can we do this with standard POSIX tools? -$(LIBJSONTOOLKIT_BUILD)/official_resolver.cc: \ - $(LIBJSONTOOLKIT_SRC)/jsonschema/official_resolver.cmake \ - $(LIBJSONTOOLKIT_SRC)/jsonschema/official_resolver.in.cc - cmake "-DPROJECT_SOURCE_DIR=$(LIBJSONTOOLKIT_BASE)" \ - "-DOFFICIAL_RESOLVER_INPUT=$(word 2,$^)" \ - "-DOFFICIAL_RESOLVER_OUTPUT=$@" \ - -P "$<" -LIBJSONTOOLKIT_SRCS-y += $(LIBJSONTOOLKIT_BUILD)/official_resolver.cc diff --git a/vendor/jsontoolkit/cmake/FindGoogleBenchmark.cmake b/vendor/jsontoolkit/cmake/FindGoogleBenchmark.cmake new file mode 100644 index 00000000..1ae1b8af --- /dev/null +++ b/vendor/jsontoolkit/cmake/FindGoogleBenchmark.cmake @@ -0,0 +1,5 @@ +if(NOT Benchnark_FOUND) + set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Enable testing of the benchmark library.") + add_subdirectory("${PROJECT_SOURCE_DIR}/vendor/googlebenchmark") + set(Benchnark_FOUND ON) +endif() diff --git a/vendor/jsontoolkit/config.cmake.in b/vendor/jsontoolkit/config.cmake.in index 541dc72b..6d611955 100644 --- a/vendor/jsontoolkit/config.cmake.in +++ b/vendor/jsontoolkit/config.cmake.in @@ -9,6 +9,7 @@ if(NOT JSONTOOLKIT_COMPONENTS) list(APPEND JSONTOOLKIT_COMPONENTS jsonl) list(APPEND JSONTOOLKIT_COMPONENTS jsonpointer) list(APPEND JSONTOOLKIT_COMPONENTS jsonschema) + list(APPEND JSONTOOLKIT_COMPONENTS evaluator) endif() foreach(component ${JSONTOOLKIT_COMPONENTS}) @@ -35,6 +36,7 @@ foreach(component ${JSONTOOLKIT_COMPONENTS}) include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_uri.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_json.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_jsonpointer.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_evaluator.cmake") # GCC does not allow the use of std::promise, std::future # without compiling with pthreads support. @@ -46,6 +48,10 @@ foreach(component ${JSONTOOLKIT_COMPONENTS}) endif() include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_jsonschema.cmake") + elseif(component STREQUAL "evaluator") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_uri.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_json.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_jsontoolkit_evaluator.cmake") else() message(FATAL_ERROR "Unknown JSON Toolkit component: ${component}") endif() diff --git a/vendor/jsontoolkit/src/evaluator/CMakeLists.txt b/vendor/jsontoolkit/src/evaluator/CMakeLists.txt new file mode 100644 index 00000000..075dadcd --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/CMakeLists.txt @@ -0,0 +1,15 @@ +noa_library(NAMESPACE sourcemeta PROJECT jsontoolkit NAME evaluator + FOLDER "JSON Toolkit/Evaluator" + PRIVATE_HEADERS error.h value.h template.h context.h + SOURCES evaluator.cc context.cc trace.h) + +if(JSONTOOLKIT_INSTALL) + noa_library_install(NAMESPACE sourcemeta PROJECT jsontoolkit NAME evaluator) +endif() + +target_link_libraries(sourcemeta_jsontoolkit_evaluator PUBLIC + sourcemeta::jsontoolkit::json) +target_link_libraries(sourcemeta_jsontoolkit_evaluator PUBLIC + sourcemeta::jsontoolkit::jsonpointer) +target_link_libraries(sourcemeta_jsontoolkit_evaluator PRIVATE + sourcemeta::jsontoolkit::uri) diff --git a/vendor/jsontoolkit/src/evaluator/context.cc b/vendor/jsontoolkit/src/evaluator/context.cc new file mode 100644 index 00000000..699f63b0 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/context.cc @@ -0,0 +1,288 @@ +#include +#include + +#include // assert + +namespace sourcemeta::jsontoolkit { + +auto EvaluationContext::prepare(const JSON &instance) -> void { + // Do a full reset for the next run + assert(this->evaluate_path_.empty()); + assert(this->instance_location_.empty()); + assert(this->frame_sizes.empty()); + assert(this->resources_.empty()); + this->instances_.clear(); + this->instances_.emplace_back(instance); + this->annotation_blacklist.clear(); + this->annotations_.clear(); + this->labels.clear(); + this->property_as_instance = false; +} + +auto EvaluationContext::push_without_traverse( + const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, const bool dynamic) -> void { + // Guard against infinite recursion in a cheap manner, as + // infinite recursion will manifest itself through huge + // ever-growing evaluate paths + constexpr auto EVALUATE_PATH_LIMIT{300}; + if (this->evaluate_path_.size() > EVALUATE_PATH_LIMIT) [[unlikely]] { + throw sourcemeta::jsontoolkit::SchemaEvaluationError( + "The evaluation path depth limit was reached " + "likely due to infinite recursion"); + } + + this->frame_sizes.emplace_back(relative_schema_location.size(), + relative_instance_location.size()); + this->evaluate_path_.push_back(relative_schema_location); + this->instance_location_.push_back(relative_instance_location); + + if (dynamic) { + // Note that we are potentially repeatedly pushing back the + // same schema resource over and over again. However, the + // logic for making sure this list is "pure" takes a lot of + // computation power. Being silly seems faster. + this->resources_.push_back(schema_resource); + } +} + +auto EvaluationContext::push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, + const bool dynamic) -> void { + this->push_without_traverse(relative_schema_location, + relative_instance_location, schema_resource, + dynamic); + if (!relative_instance_location.empty()) { + assert(!this->instances_.empty()); + this->instances_.emplace_back( + get(this->instances_.back().get(), relative_instance_location)); + } +} + +auto EvaluationContext::push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, + const bool dynamic, + std::reference_wrapper &&new_instance) + -> void { + this->push_without_traverse(relative_schema_location, + relative_instance_location, schema_resource, + dynamic); + assert(!relative_instance_location.empty()); + this->instances_.emplace_back(new_instance); +} + +auto EvaluationContext::pop(const bool dynamic) -> void { + assert(!this->frame_sizes.empty()); + const auto &sizes{this->frame_sizes.back()}; + this->evaluate_path_.pop_back(sizes.first); + this->instance_location_.pop_back(sizes.second); + if (sizes.second > 0) { + this->instances_.pop_back(); + } + + this->frame_sizes.pop_back(); + + // TODO: Do schema resource management using hashes to avoid + // expensive string comparisons + if (dynamic) { + assert(!this->resources_.empty()); + this->resources_.pop_back(); + } +} + +auto EvaluationContext::annotate(const WeakPointer ¤t_instance_location, + const JSON &value) + -> std::pair, bool> { + const auto result{this->annotations_.insert({current_instance_location, {}}) + .first->second.insert({this->evaluate_path(), {}}) + .first->second.insert(value)}; + return {*(result.first), result.second}; +} + +auto EvaluationContext::defines_annotation( + const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, + const std::vector &keywords, const JSON &value) const -> bool { + if (keywords.empty()) { + return false; + } + + const auto instance_location_result{ + this->annotations_.find(expected_instance_location)}; + if (instance_location_result == this->annotations_.end()) { + return false; + } + + for (const auto &[schema_location, schema_annotations] : + instance_location_result->second) { + assert(!schema_location.empty()); + const auto &keyword{schema_location.back()}; + + if (keyword.is_property() && + std::find(keywords.cbegin(), keywords.cend(), keyword.to_property()) != + keywords.cend() && + schema_annotations.contains(value) && + schema_location.initial().starts_with(base_evaluate_path)) { + bool blacklisted = false; + for (const auto &masked : this->annotation_blacklist) { + if (schema_location.starts_with(masked) && + !this->evaluate_path_.starts_with(masked)) { + blacklisted = true; + break; + } + } + + if (!blacklisted) { + return true; + } + } + } + + return false; +} + +auto EvaluationContext::largest_annotation_index( + const WeakPointer &expected_instance_location, + const std::vector &keywords, + const std::uint64_t default_value) const -> std::uint64_t { + // TODO: We should be taking masks into account + + std::uint64_t result{default_value}; + + const auto instance_location_result{ + this->annotations_.find(expected_instance_location)}; + if (instance_location_result == this->annotations_.end()) { + return result; + } + + for (const auto &[schema_location, schema_annotations] : + instance_location_result->second) { + assert(!schema_location.empty()); + const auto &keyword{schema_location.back()}; + if (!keyword.is_property()) { + continue; + } + + if (std::find(keywords.cbegin(), keywords.cend(), keyword.to_property()) == + keywords.cend()) { + continue; + } + + for (const auto &annotation : schema_annotations) { + if (annotation.is_integer() && annotation.is_positive()) { + result = std::max( + result, static_cast(annotation.to_integer()) + 1); + } + } + } + + return result; +} + +auto EvaluationContext::enter(const WeakPointer::Token::Property &property) + -> void { + this->instance_location_.push_back(property); + this->instances_.emplace_back(this->instances_.back().get().at(property)); +} + +auto EvaluationContext::enter(const WeakPointer::Token::Index &index) -> void { + this->instance_location_.push_back(index); + this->instances_.emplace_back(this->instances_.back().get().at(index)); +} + +auto EvaluationContext::leave() -> void { + this->instance_location_.pop_back(); + this->instances_.pop_back(); +} + +auto EvaluationContext::instances() const noexcept + -> const std::vector> & { + return this->instances_; +} + +auto EvaluationContext::resources() const noexcept + -> const std::vector & { + return this->resources_; +} + +auto EvaluationContext::evaluate_path() const noexcept -> const WeakPointer & { + return this->evaluate_path_; +} + +auto EvaluationContext::instance_location() const noexcept + -> const WeakPointer & { + return this->instance_location_; +} + +auto EvaluationContext::target_type(const TargetType type) noexcept -> void { + this->property_as_instance = (type == TargetType::Key); +} + +auto EvaluationContext::resolve_target() -> const JSON & { + if (this->property_as_instance) [[unlikely]] { + // In this case, we still need to return a string in order + // to cope with non-string keywords inside `propertyNames` + // that need to fail validation. But then, the actual string + // we return doesn't matter, so we can always return a dummy one. + static const JSON empty_string{""}; + return empty_string; + } + + return this->instances_.back().get(); +} + +auto EvaluationContext::resolve_string_target() + -> std::optional> { + if (this->property_as_instance) [[unlikely]] { + assert(!this->instance_location().empty()); + assert(this->instance_location().back().is_property()); + return this->instance_location().back().to_property(); + } else { + const auto &result{this->instances_.back().get()}; + if (!result.is_string()) { + return std::nullopt; + } + + return result.to_string(); + } +} + +auto EvaluationContext::mark(const std::size_t id, + const SchemaCompilerTemplate &children) -> void { + this->labels.try_emplace(id, children); +} + +// TODO: At least currently, we only need to mask if a schema +// makes use of `unevaluatedProperties` or `unevaluatedItems` +// Detect if a schema does need this so if not, we avoid +// an unnecessary copy +auto EvaluationContext::mask() -> void { + this->annotation_blacklist.push_back(this->evaluate_path_); +} + +auto EvaluationContext::jump(const std::size_t id) const noexcept + -> const SchemaCompilerTemplate & { + assert(this->labels.contains(id)); + return this->labels.at(id).get(); +} + +auto EvaluationContext::find_dynamic_anchor(const std::string &anchor) const + -> std::optional { + for (const auto &resource : this->resources()) { + std::ostringstream name; + name << resource; + name << '#'; + name << anchor; + const auto label{std::hash{}(name.str())}; + if (this->labels.contains(label)) { + return label; + } + } + + return std::nullopt; +} + +} // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/evaluator/evaluator.cc b/vendor/jsontoolkit/src/evaluator/evaluator.cc new file mode 100644 index 00000000..e9d46829 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/evaluator.cc @@ -0,0 +1,1189 @@ +#include +#include + +#include "trace.h" + +#include // std::min, std::any_of +#include // assert +#include // std::distance, std::advance +#include // std::numeric_limits +#include // std::optional + +namespace { + +auto evaluate_step( + const sourcemeta::jsontoolkit::SchemaCompilerTemplate::value_type &step, + const sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode mode, + const std::optional< + sourcemeta::jsontoolkit::SchemaCompilerEvaluationCallback> &callback, + sourcemeta::jsontoolkit::EvaluationContext &context) -> bool { + SOURCEMETA_TRACE_REGISTER_ID(trace_dispatch_id); + SOURCEMETA_TRACE_REGISTER_ID(trace_id); + SOURCEMETA_TRACE_START(trace_dispatch_id, "Dispatch"); + using namespace sourcemeta::jsontoolkit; + +#define STRINGIFY(x) #x + +#define EVALUATE_BEGIN(step_category, step_type, precondition) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + const auto &target{context.resolve_target()}; \ + if (!(precondition)) { \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + +#define EVALUATE_BEGIN_IF_STRING(step_category, step_type) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + const auto &maybe_target{context.resolve_string_target()}; \ + if (!maybe_target.has_value()) { \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + const auto &target{maybe_target.value().get()}; \ + bool result{false}; + +#define EVALUATE_BEGIN_NO_TARGET(step_category, step_type, precondition) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + if (!(precondition)) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + + // This is a slightly complicated dance to avoid traversing the relative + // instance location twice. We first need to traverse it to check if its + // valid in the document as part of the condition, but if it is, we can + // pass it to `.push()` so that it doesn't need to traverse it again. +#define EVALUATE_BEGIN_TRY_TARGET(step_category, step_type, precondition) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &target{context.resolve_target()}; \ + const auto &step_category{std::get(step)}; \ + if (!(precondition)) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + auto target_check{ \ + try_get(target, step_category.relative_instance_location)}; \ + if (!target_check.has_value()) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic, \ + std::move(target_check.value())); \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + +#define EVALUATE_BEGIN_NO_PRECONDITION(step_category, step_type) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + bool result{false}; + +#define EVALUATE_END(step_category, step_type) \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Post, result, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return result; + + // As a safety guard, only emit the annotation if it didn't exist already. + // Otherwise we risk confusing consumers + +#define EVALUATE_ANNOTATION(step_category, step_type, precondition, \ + destination, annotation_value) \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + assert(step_category.relative_instance_location.empty()); \ + const auto &target{context.resolve_target()}; \ + if (!(precondition)) { \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + const auto annotation_result{ \ + context.annotate(destination, annotation_value)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (annotation_result.second && step_category.report && \ + callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), destination, context.null); \ + callback.value()(SchemaCompilerEvaluationType::Post, true, step, \ + context.evaluate_path(), destination, \ + annotation_result.first); \ + } \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; + +#define EVALUATE_ANNOTATION_NO_PRECONDITION(step_category, step_type, \ + destination, annotation_value) \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + const auto annotation_result{ \ + context.annotate(destination, annotation_value)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + if (annotation_result.second && step_category.report && \ + callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), destination, context.null); \ + callback.value()(SchemaCompilerEvaluationType::Post, true, step, \ + context.evaluate_path(), destination, \ + annotation_result.first); \ + } \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; + +#define IS_STEP(step_type) SchemaCompilerTemplateIndex::step_type + switch (static_cast(step.index())) { + case IS_STEP(SchemaCompilerAssertionFail): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionFail); + EVALUATE_END(assertion, SchemaCompilerAssertionFail); + } + + case IS_STEP(SchemaCompilerAssertionDefines): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDefines, + target.is_object()); + result = target.defines(assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionDefines); + } + + case IS_STEP(SchemaCompilerAssertionDefinesAll): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDefinesAll, + target.is_object()); + + // Otherwise we are we even emitting this instruction? + assert(assertion.value.size() > 1); + result = true; + for (const auto &property : assertion.value) { + if (!target.defines(property)) { + result = false; + break; + } + } + + EVALUATE_END(assertion, SchemaCompilerAssertionDefinesAll); + } + + case IS_STEP(SchemaCompilerAssertionPropertyDependencies): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionPropertyDependencies, + target.is_object()); + // Otherwise we are we even emitting this instruction? + assert(!assertion.value.empty()); + result = true; + for (const auto &[property, dependencies] : assertion.value) { + if (!target.defines(property)) { + continue; + } + + assert(!dependencies.empty()); + for (const auto &dependency : dependencies) { + if (!target.defines(dependency)) { + result = false; + // For efficiently breaking from the outer loop too + goto evaluate_assertion_property_dependencies_end; + } + } + } + + evaluate_assertion_property_dependencies_end: + EVALUATE_END(assertion, SchemaCompilerAssertionPropertyDependencies); + } + + case IS_STEP(SchemaCompilerAssertionType): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionType); + const auto &target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + result = + target.type() == assertion.value || + (assertion.value == JSON::Type::Integer && target.is_integer_real()); + EVALUATE_END(assertion, SchemaCompilerAssertionType); + } + + case IS_STEP(SchemaCompilerAssertionTypeAny): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionTypeAny); + // Otherwise we are we even emitting this instruction? + assert(assertion.value.size() > 1); + const auto &target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + for (const auto type : assertion.value) { + if (type == JSON::Type::Integer && target.is_integer_real()) { + result = true; + break; + } else if (type == target.type()) { + result = true; + break; + } + } + + EVALUATE_END(assertion, SchemaCompilerAssertionTypeAny); + } + + case IS_STEP(SchemaCompilerAssertionTypeStrict): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeStrict); + result = context.resolve_target().type() == assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionTypeStrict); + } + + case IS_STEP(SchemaCompilerAssertionTypeStrictAny): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeStrictAny); + // Otherwise we are we even emitting this instruction? + assert(assertion.value.size() > 1); + result = (std::find(assertion.value.cbegin(), assertion.value.cend(), + context.resolve_target().type()) != + assertion.value.cend()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeStrictAny); + } + + case IS_STEP(SchemaCompilerAssertionTypeStringBounded): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeStringBounded); + const auto &target{context.resolve_target()}; + const auto minimum{std::get<0>(assertion.value)}; + const auto maximum{std::get<1>(assertion.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + // Require early breaking + assert(!std::get<2>(assertion.value)); + result = target.type() == JSON::Type::String && + target.size() >= minimum && + (!maximum.has_value() || target.size() <= maximum.value()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeStringBounded); + } + + case IS_STEP(SchemaCompilerAssertionTypeArrayBounded): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeArrayBounded); + const auto &target{context.resolve_target()}; + const auto minimum{std::get<0>(assertion.value)}; + const auto maximum{std::get<1>(assertion.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + // Require early breaking + assert(!std::get<2>(assertion.value)); + result = target.type() == JSON::Type::Array && target.size() >= minimum && + (!maximum.has_value() || target.size() <= maximum.value()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeArrayBounded); + } + + case IS_STEP(SchemaCompilerAssertionTypeObjectBounded): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionTypeObjectBounded); + const auto &target{context.resolve_target()}; + const auto minimum{std::get<0>(assertion.value)}; + const auto maximum{std::get<1>(assertion.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + // Require early breaking + assert(!std::get<2>(assertion.value)); + result = target.type() == JSON::Type::Object && + target.size() >= minimum && + (!maximum.has_value() || target.size() <= maximum.value()); + EVALUATE_END(assertion, SchemaCompilerAssertionTypeObjectBounded); + } + + case IS_STEP(SchemaCompilerAssertionRegex): { + EVALUATE_BEGIN_IF_STRING(assertion, SchemaCompilerAssertionRegex); + result = std::regex_search(target, assertion.value.first); + EVALUATE_END(assertion, SchemaCompilerAssertionRegex); + } + + case IS_STEP(SchemaCompilerAssertionStringSizeLess): { + EVALUATE_BEGIN_IF_STRING(assertion, + SchemaCompilerAssertionStringSizeLess); + result = (JSON::size(target) < assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeLess); + } + + case IS_STEP(SchemaCompilerAssertionStringSizeGreater): { + EVALUATE_BEGIN_IF_STRING(assertion, + SchemaCompilerAssertionStringSizeGreater); + result = (JSON::size(target) > assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeGreater); + } + + case IS_STEP(SchemaCompilerAssertionArraySizeLess): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionArraySizeLess, + target.is_array()); + result = (target.size() < assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionArraySizeLess); + } + + case IS_STEP(SchemaCompilerAssertionArraySizeGreater): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionArraySizeGreater, + target.is_array()); + result = (target.size() > assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionArraySizeGreater); + } + + case IS_STEP(SchemaCompilerAssertionObjectSizeLess): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionObjectSizeLess, + target.is_object()); + result = (target.size() < assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionObjectSizeLess); + } + + case IS_STEP(SchemaCompilerAssertionObjectSizeGreater): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionObjectSizeGreater, + target.is_object()); + result = (target.size() > assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionObjectSizeGreater); + } + + case IS_STEP(SchemaCompilerAssertionEqual): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, SchemaCompilerAssertionEqual); + result = (context.resolve_target() == assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionEqual); + } + + case IS_STEP(SchemaCompilerAssertionEqualsAny): { + EVALUATE_BEGIN_NO_PRECONDITION(assertion, + SchemaCompilerAssertionEqualsAny); + result = (std::find(assertion.value.cbegin(), assertion.value.cend(), + context.resolve_target()) != assertion.value.cend()); + EVALUATE_END(assertion, SchemaCompilerAssertionEqualsAny); + } + + case IS_STEP(SchemaCompilerAssertionGreaterEqual): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionGreaterEqual, + target.is_number()); + result = target >= assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionGreaterEqual); + } + + case IS_STEP(SchemaCompilerAssertionLessEqual): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionLessEqual, + target.is_number()); + result = target <= assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionLessEqual); + } + + case IS_STEP(SchemaCompilerAssertionGreater): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionGreater, + target.is_number()); + result = target > assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionGreater); + } + + case IS_STEP(SchemaCompilerAssertionLess): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionLess, + target.is_number()); + result = target < assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionLess); + } + + case IS_STEP(SchemaCompilerAssertionUnique): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionUnique, + target.is_array()); + result = target.unique(); + EVALUATE_END(assertion, SchemaCompilerAssertionUnique); + } + + case IS_STEP(SchemaCompilerAssertionDivisible): { + EVALUATE_BEGIN(assertion, SchemaCompilerAssertionDivisible, + target.is_number()); + assert(assertion.value.is_number()); + result = target.divisible_by(assertion.value); + EVALUATE_END(assertion, SchemaCompilerAssertionDivisible); + } + + case IS_STEP(SchemaCompilerAssertionStringType): { + EVALUATE_BEGIN_IF_STRING(assertion, SchemaCompilerAssertionStringType); + switch (assertion.value) { + case SchemaCompilerValueStringType::URI: + try { + // TODO: This implies a string copy + result = URI{target}.is_absolute(); + } catch (const URIParseError &) { + result = false; + } + + break; + default: + // We should never get here + assert(false); + } + + EVALUATE_END(assertion, SchemaCompilerAssertionStringType); + } + + case IS_STEP(SchemaCompilerAssertionPropertyType): { + EVALUATE_BEGIN_TRY_TARGET( + assertion, SchemaCompilerAssertionPropertyType, + // Note that here are are referring to the parent + // object that might hold the given property, + // before traversing into the actual property + target.is_object()); + // Now here we refer to the actual property + const auto &effective_target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + result = effective_target.type() == assertion.value || + (assertion.value == JSON::Type::Integer && + effective_target.is_integer_real()); + EVALUATE_END(assertion, SchemaCompilerAssertionPropertyType); + } + + case IS_STEP(SchemaCompilerAssertionPropertyTypeStrict): { + EVALUATE_BEGIN_TRY_TARGET( + assertion, SchemaCompilerAssertionPropertyTypeStrict, + // Note that here are are referring to the parent + // object that might hold the given property, + // before traversing into the actual property + target.is_object()); + // Now here we refer to the actual property + result = context.resolve_target().type() == assertion.value; + EVALUATE_END(assertion, SchemaCompilerAssertionPropertyTypeStrict); + } + + case IS_STEP(SchemaCompilerLogicalOr): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalOr); + result = logical.children.empty(); + for (const auto &child : logical.children) { + if (evaluate_step(child, mode, callback, context)) { + result = true; + // This boolean value controls whether we should still evaluate + // every disjunction even on fast mode + if (mode == SchemaCompilerEvaluationMode::Fast && !logical.value) { + break; + } + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalOr); + } + + case IS_STEP(SchemaCompilerLogicalAnd): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalAnd); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalAnd); + } + + case IS_STEP(SchemaCompilerLogicalWhenType): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenType, + target.type() == logical.value); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenType); + } + + case IS_STEP(SchemaCompilerLogicalWhenDefines): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenDefines, + target.is_object() && target.defines(logical.value)); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenDefines); + } + + case IS_STEP(SchemaCompilerLogicalWhenArraySizeGreater): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenArraySizeGreater, + target.is_array() && target.size() > logical.value); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenArraySizeGreater); + } + + case IS_STEP(SchemaCompilerLogicalWhenArraySizeEqual): { + EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenArraySizeEqual, + target.is_array() && target.size() == logical.value); + result = true; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalWhenArraySizeEqual); + } + + case IS_STEP(SchemaCompilerLogicalXor): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalXor); + result = true; + bool has_matched{false}; + for (const auto &child : logical.children) { + if (evaluate_step(child, mode, callback, context)) { + if (has_matched) { + result = false; + if (mode == SchemaCompilerEvaluationMode::Fast) { + break; + } + } else { + has_matched = true; + } + } + } + + result = result && has_matched; + EVALUATE_END(logical, SchemaCompilerLogicalXor); + } + + case IS_STEP(SchemaCompilerLogicalCondition): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalCondition); + result = true; + const auto children_size{logical.children.size()}; + assert(children_size >= logical.value.first); + assert(children_size >= logical.value.second); + + auto condition_end{children_size}; + if (logical.value.first > 0) { + condition_end = logical.value.first; + } else if (logical.value.second > 0) { + condition_end = logical.value.second; + } + + for (std::size_t cursor = 0; cursor < condition_end; cursor++) { + if (!evaluate_step(logical.children[cursor], mode, callback, context)) { + result = false; + break; + } + } + + const auto consequence_start{result ? logical.value.first + : logical.value.second}; + const auto consequence_end{(result && logical.value.second > 0) + ? logical.value.second + : children_size}; + result = true; + if (consequence_start > 0) { + for (auto cursor = consequence_start; cursor < consequence_end; + cursor++) { + if (!evaluate_step(logical.children[cursor], mode, callback, + context)) { + result = false; + break; + } + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalCondition); + } + + case IS_STEP(SchemaCompilerLogicalNot): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalNot); + // Ignore annotations produced inside "not" + context.mask(); + result = false; + for (const auto &child : logical.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = true; + if (mode == SchemaCompilerEvaluationMode::Fast) { + break; + } + } + } + + EVALUATE_END(logical, SchemaCompilerLogicalNot); + } + + case IS_STEP(SchemaCompilerControlLabel): { + EVALUATE_BEGIN_NO_PRECONDITION(control, SchemaCompilerControlLabel); + context.mark(control.value, control.children); + result = true; + for (const auto &child : control.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(control, SchemaCompilerControlLabel); + } + + case IS_STEP(SchemaCompilerControlMark): { + SOURCEMETA_TRACE_START(trace_id, "SchemaCompilerControlMark"); + const auto &control{std::get(step)}; + context.mark(control.value, control.children); + SOURCEMETA_TRACE_END(trace_id, "SchemaCompilerControlMark"); + return true; + } + + case IS_STEP(SchemaCompilerControlJump): { + EVALUATE_BEGIN_NO_PRECONDITION(control, SchemaCompilerControlJump); + result = true; + for (const auto &child : context.jump(control.value)) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + + EVALUATE_END(control, SchemaCompilerControlJump); + } + + case IS_STEP(SchemaCompilerControlDynamicAnchorJump): { + EVALUATE_BEGIN_NO_PRECONDITION(control, + SchemaCompilerControlDynamicAnchorJump); + const auto id{context.find_dynamic_anchor(control.value)}; + result = id.has_value(); + if (id.has_value()) { + for (const auto &child : context.jump(id.value())) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + break; + } + } + } + + EVALUATE_END(control, SchemaCompilerControlDynamicAnchorJump); + } + + case IS_STEP(SchemaCompilerAnnotationEmit): { + EVALUATE_ANNOTATION_NO_PRECONDITION( + annotation, SchemaCompilerAnnotationEmit, context.instance_location(), + annotation.value); + } + + case IS_STEP(SchemaCompilerAnnotationWhenArraySizeEqual): { + EVALUATE_ANNOTATION( + annotation, SchemaCompilerAnnotationWhenArraySizeEqual, + target.is_array() && target.size() == annotation.value.first, + context.instance_location(), annotation.value.second); + } + + case IS_STEP(SchemaCompilerAnnotationWhenArraySizeGreater): { + EVALUATE_ANNOTATION( + annotation, SchemaCompilerAnnotationWhenArraySizeGreater, + target.is_array() && target.size() > annotation.value.first, + context.instance_location(), annotation.value.second); + } + + case IS_STEP(SchemaCompilerAnnotationToParent): { + EVALUATE_ANNOTATION_NO_PRECONDITION( + annotation, SchemaCompilerAnnotationToParent, + // TODO: Can we avoid a copy of the instance location here? + context.instance_location().initial(), annotation.value); + } + + case IS_STEP(SchemaCompilerAnnotationBasenameToParent): { + EVALUATE_ANNOTATION_NO_PRECONDITION( + annotation, SchemaCompilerAnnotationBasenameToParent, + // TODO: Can we avoid a copy of the instance location here? + context.instance_location().initial(), + context.instance_location().back().to_json()); + } + + case IS_STEP(SchemaCompilerLoopPropertiesMatch): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesMatch, + target.is_object()); + assert(!loop.value.empty()); + result = true; + for (const auto &entry : target.as_object()) { + const auto index{loop.value.find(entry.first)}; + if (index == loop.value.cend()) { + continue; + } + + const auto &substep{loop.children[index->second]}; + assert(std::holds_alternative(substep)); + for (const auto &child : + std::get(substep).children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_match_end; + } + } + } + + evaluate_loop_properties_match_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesMatch); + } + + case IS_STEP(SchemaCompilerLoopProperties): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopProperties, target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_end: + EVALUATE_END(loop, SchemaCompilerLoopProperties); + } + + case IS_STEP(SchemaCompilerLoopPropertiesRegex): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesRegex, + target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + if (!std::regex_search(entry.first, loop.value.first)) { + continue; + } + + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_regex_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_regex_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesRegex); + } + + case IS_STEP(SchemaCompilerLoopPropertiesNoAnnotation): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesNoAnnotation, + target.is_object()); + result = true; + assert(!loop.value.empty()); + + for (const auto &entry : target.as_object()) { + // TODO: It might be more efficient to get all the annotations we + // potentially care about as a set first, and the make the loop + // check for O(1) containment in that set? + if (context.defines_annotation( + context.instance_location(), + // TODO: Can we avoid doing this expensive operation on a loop? + context.evaluate_path().initial(), loop.value, + // TODO: This conversion implies a string copy + JSON{entry.first})) { + continue; + } + + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_no_annotation_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_no_annotation_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesNoAnnotation); + } + + case IS_STEP(SchemaCompilerLoopPropertiesExcept): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesExcept, + target.is_object()); + result = true; + // Otherwise why emit this instruction? + assert(!loop.value.first.empty() || !loop.value.second.empty()); + + for (const auto &entry : target.as_object()) { + if (loop.value.first.contains(entry.first) || + std::any_of(loop.value.second.cbegin(), loop.value.second.cend(), + [&entry](const auto &pattern) { + return std::regex_search(entry.first, pattern.first); + })) { + continue; + } + + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + // For efficiently breaking from the outer loop too + goto evaluate_loop_properties_except_end; + } + } + + context.leave(); + } + + evaluate_loop_properties_except_end: + EVALUATE_END(loop, SchemaCompilerLoopPropertiesExcept); + } + + case IS_STEP(SchemaCompilerLoopPropertiesType): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesType, + target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + const auto &entry_target{context.resolve_target()}; + // In non-strict mode, we consider a real number that represents an + // integer to be an integer + if (entry_target.type() != loop.value && + (loop.value != JSON::Type::Integer || + entry_target.is_integer_real())) { + result = false; + context.leave(); + break; + } + + context.leave(); + } + + EVALUATE_END(loop, SchemaCompilerLoopPropertiesType); + } + + case IS_STEP(SchemaCompilerLoopPropertiesTypeStrict): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopPropertiesTypeStrict, + target.is_object()); + result = true; + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + if (context.resolve_target().type() != loop.value) { + result = false; + context.leave(); + break; + } + + context.leave(); + } + + EVALUATE_END(loop, SchemaCompilerLoopPropertiesTypeStrict); + } + + case IS_STEP(SchemaCompilerLoopKeys): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopKeys, target.is_object()); + result = true; + context.target_type( + sourcemeta::jsontoolkit::EvaluationContext::TargetType::Key); + for (const auto &entry : target.as_object()) { + context.enter(entry.first); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_loop_keys_end; + } + } + + context.leave(); + } + + evaluate_loop_keys_end: + context.target_type( + sourcemeta::jsontoolkit::EvaluationContext::TargetType::Value); + EVALUATE_END(loop, SchemaCompilerLoopKeys); + } + + case IS_STEP(SchemaCompilerLoopItems): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopItems, target.is_array()); + const auto &array{target.as_array()}; + result = true; + auto iterator{array.cbegin()}; + + // We need this check, as advancing an iterator past its bounds + // is considered undefined behavior + // See https://en.cppreference.com/w/cpp/iterator/advance + std::advance(iterator, + std::min(static_cast(loop.value), + static_cast(target.size()))); + + for (; iterator != array.cend(); ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + context.enter(static_cast(index)); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_compiler_loop_items_end; + } + } + + context.leave(); + } + + evaluate_compiler_loop_items_end: + EVALUATE_END(loop, SchemaCompilerLoopItems); + } + + case IS_STEP(SchemaCompilerLoopItemsUnmarked): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopItemsUnmarked, + target.is_array() && + !context.defines_annotation( + context.instance_location(), + context.evaluate_path(), loop.value, JSON{true})); + // Otherwise you shouldn't be using this step? + assert(!loop.value.empty()); + const auto &array{target.as_array()}; + result = true; + + for (auto iterator = array.cbegin(); iterator != array.cend(); + ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + context.enter(static_cast(index)); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_compiler_loop_items_unmarked_end; + } + } + + context.leave(); + } + + evaluate_compiler_loop_items_unmarked_end: + EVALUATE_END(loop, SchemaCompilerLoopItemsUnmarked); + } + + case IS_STEP(SchemaCompilerLoopItemsUnevaluated): { + // TODO: This precondition is very expensive due to pointer manipulation + EVALUATE_BEGIN(loop, SchemaCompilerLoopItemsUnevaluated, + target.is_array() && !context.defines_annotation( + context.instance_location(), + context.evaluate_path().initial(), + loop.value.mask, JSON{true})); + const auto &array{target.as_array()}; + result = true; + auto iterator{array.cbegin()}; + + // Determine the proper start based on integer annotations collected for + // the current instance location by the keyword requested by the user. + const std::uint64_t start{context.largest_annotation_index( + context.instance_location(), {loop.value.index}, 0)}; + + // We need this check, as advancing an iterator past its bounds + // is considered undefined behavior + // See https://en.cppreference.com/w/cpp/iterator/advance + std::advance(iterator, + std::min(static_cast(start), + static_cast(target.size()))); + + for (; iterator != array.cend(); ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + + if (context.defines_annotation( + context.instance_location(), + // TODO: Can we avoid doing this expensive operation on a loop? + context.evaluate_path().initial(), loop.value.filter, + JSON{static_cast(index)})) { + continue; + } + + context.enter(static_cast(index)); + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + result = false; + context.leave(); + goto evaluate_compiler_loop_items_unevaluated_end; + } + } + + context.leave(); + } + + evaluate_compiler_loop_items_unevaluated_end: + EVALUATE_END(loop, SchemaCompilerLoopItemsUnevaluated); + } + + case IS_STEP(SchemaCompilerLoopContains): { + EVALUATE_BEGIN(loop, SchemaCompilerLoopContains, target.is_array()); + const auto minimum{std::get<0>(loop.value)}; + const auto &maximum{std::get<1>(loop.value)}; + assert(!maximum.has_value() || maximum.value() >= minimum); + const auto is_exhaustive{std::get<2>(loop.value)}; + result = minimum == 0 && target.empty(); + const auto &array{target.as_array()}; + auto match_count{std::numeric_limits::min()}; + for (auto iterator = array.cbegin(); iterator != array.cend(); + ++iterator) { + const auto index{std::distance(array.cbegin(), iterator)}; + context.enter(static_cast(index)); + bool subresult{true}; + for (const auto &child : loop.children) { + if (!evaluate_step(child, mode, callback, context)) { + subresult = false; + break; + } + } + + context.leave(); + + if (subresult) { + match_count += 1; + + // Exceeding the upper bound is definitely a failure + if (maximum.has_value() && match_count > maximum.value()) { + result = false; + + // Note that here we don't want to consider whether to run + // exhaustively or not. At this point, its already a failure, + // and anything that comes after would not run at all anyway + break; + } + + if (match_count >= minimum) { + result = true; + + // Exceeding the lower bound when there is no upper bound + // is definitely a success + if (!maximum.has_value() && !is_exhaustive) { + break; + } + } + } + } + + EVALUATE_END(loop, SchemaCompilerLoopContains); + } + +#undef IS_STEP +#undef STRINGIFY +#undef EVALUATE_BEGIN +#undef EVALUATE_BEGIN_IF_STRING +#undef EVALUATE_BEGIN_NO_TARGET +#undef EVALUATE_BEGIN_TRY_TARGET +#undef EVALUATE_BEGIN_NO_PRECONDITION +#undef EVALUATE_END +#undef EVALUATE_ANNOTATION +#undef EVALUATE_ANNOTATION_NO_PRECONDITION + + default: + // We should never get here + assert(false); + return false; + } +} + +inline auto evaluate_internal( + sourcemeta::jsontoolkit::EvaluationContext &context, + const sourcemeta::jsontoolkit::SchemaCompilerTemplate &steps, + const sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode mode, + const std::optional< + sourcemeta::jsontoolkit::SchemaCompilerEvaluationCallback> &callback) + -> bool { + bool overall{true}; + for (const auto &step : steps) { + if (!evaluate_step(step, mode, callback, context)) { + overall = false; + break; + } + } + + // The evaluation path and instance location must be empty by the time + // we are done, otherwise there was a frame push/pop mismatch + assert(context.evaluate_path().empty()); + assert(context.instance_location().empty()); + assert(context.resources().empty()); + // We should end up at the root of the instance + assert(context.instances().size() == 1); + return overall; +} + +} // namespace + +namespace sourcemeta::jsontoolkit { + +auto evaluate(const SchemaCompilerTemplate &steps, const JSON &instance, + const SchemaCompilerEvaluationMode mode, + const SchemaCompilerEvaluationCallback &callback) -> bool { + EvaluationContext context; + context.prepare(instance); + return evaluate_internal(context, steps, mode, callback); +} + +auto evaluate(const SchemaCompilerTemplate &steps, const JSON &instance) + -> bool { + EvaluationContext context; + context.prepare(instance); + return evaluate_internal(context, steps, + // Otherwise what's the point of an exhaustive + // evaluation if you don't get the results? + SchemaCompilerEvaluationMode::Fast, std::nullopt); +} + +auto evaluate(const SchemaCompilerTemplate &steps, EvaluationContext &context) + -> bool { + return evaluate_internal(context, steps, + // Otherwise what's the point of an exhaustive + // evaluation if you don't get the results? + SchemaCompilerEvaluationMode::Fast, std::nullopt); +} + +} // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator.h new file mode 100644 index 00000000..b7d0f78a --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator.h @@ -0,0 +1,181 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_H_ +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_H_ + +#include "evaluator_export.h" + +#include +#include +#include + +#include +#include + +#include // std::uint8_t +#include // std::function + +/// @defgroup evaluator Evaluator +/// @brief A high-performance JSON Schema evaluator +/// +/// This functionality is included as follows: +/// +/// ```cpp +/// #include +/// ``` + +namespace sourcemeta::jsontoolkit { + +/// @ingroup evaluator +/// Represents the mode of evalution +enum class SchemaCompilerEvaluationMode : std::uint8_t { + /// Attempt to get to a boolean result as fast as possible + Fast, + /// Perform a full schema evaluation + Exhaustive +}; + +/// @ingroup evaluator +/// Represents the state of a step evaluation +enum class SchemaCompilerEvaluationType : std::uint8_t { Pre, Post }; + +/// @ingroup evaluator +/// A callback of this type is invoked after evaluating any keyword. The +/// arguments go as follows: +/// +/// - The stage at which the step in question is +/// - Whether the evaluation was successful or not (always true before +/// evaluation) +/// - The step that was just evaluated +/// - The evaluation path +/// - The instance location +/// - The annotation result, if any (otherwise null) +/// +/// You can use this callback mechanism to implement arbitrary output formats. +using SchemaCompilerEvaluationCallback = + std::function; + +/// @ingroup evaluator +/// +/// This function evaluates a schema compiler template in validation mode, +/// returning a boolean without error information. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// const sourcemeta::jsontoolkit::JSON schema = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "type": "string" +/// })JSON"); +/// +/// const auto schema_template{sourcemeta::jsontoolkit::compile( +/// schema, sourcemeta::jsontoolkit::default_schema_walker, +/// sourcemeta::jsontoolkit::official_resolver, +/// sourcemeta::jsontoolkit::default_schema_compiler)}; +/// +/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; +/// const auto result{sourcemeta::jsontoolkit::evaluate( +/// schema_template, instance)}; +/// assert(result); +/// ``` +auto SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT +evaluate(const SchemaCompilerTemplate &steps, const JSON &instance) -> bool; + +/// @ingroup evaluator +/// +/// This function evaluates a schema compiler template, executing the given +/// callback at every step of the way. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// const sourcemeta::jsontoolkit::JSON schema = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "type": "string" +/// })JSON"); +/// +/// const auto schema_template{sourcemeta::jsontoolkit::compile( +/// schema, sourcemeta::jsontoolkit::default_schema_walker, +/// sourcemeta::jsontoolkit::official_resolver, +/// sourcemeta::jsontoolkit::default_schema_compiler)}; +/// +/// static auto callback( +/// bool result, +/// const sourcemeta::jsontoolkit::SchemaCompilerTemplate::value_type &step, +/// const sourcemeta::jsontoolkit::Pointer &evaluate_path, +/// const sourcemeta::jsontoolkit::Pointer &instance_location, +/// const sourcemeta::jsontoolkit::JSON &document, +/// const sourcemeta::jsontoolkit::JSON &annotation) -> void { +/// std::cout << "TYPE: " << (result ? "Success" : "Failure") << "\n"; +/// std::cout << "STEP:\n"; +/// sourcemeta::jsontoolkit::prettify(sourcemeta::jsontoolkit::to_json({step}), +/// std::cout); +/// std::cout << "\nEVALUATE PATH:"; +/// sourcemeta::jsontoolkit::stringify(evaluate_path, std::cout); +/// std::cout << "\nINSTANCE LOCATION:"; +/// sourcemeta::jsontoolkit::stringify(instance_location, std::cout); +/// std::cout << "\nANNOTATION:\n"; +/// sourcemeta::jsontoolkit::prettify(annotation, std::cout); +/// std::cout << "\n"; +/// } +/// +/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; +/// const auto result{sourcemeta::jsontoolkit::evaluate( +/// schema_template, instance, +/// sourcemeta::jsontoolkit::SchemaCompilerEvaluationMode::Fast, +/// callback)}; +/// +/// assert(result); +/// ``` +auto SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT +evaluate(const SchemaCompilerTemplate &steps, const JSON &instance, + const SchemaCompilerEvaluationMode mode, + const SchemaCompilerEvaluationCallback &callback) -> bool; + +/// @ingroup evaluator +/// +/// This function evaluates a schema compiler template from an evaluation +/// context, returning a boolean without error information. The evaluation +/// context can be re-used among evaluations (as long as its always loaded with +/// the new instance first) for performance reasons. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// const sourcemeta::jsontoolkit::JSON schema = +/// sourcemeta::jsontoolkit::parse(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "type": "string" +/// })JSON"); +/// +/// const auto schema_template{sourcemeta::jsontoolkit::compile( +/// schema, sourcemeta::jsontoolkit::default_schema_walker, +/// sourcemeta::jsontoolkit::official_resolver, +/// sourcemeta::jsontoolkit::default_schema_compiler)}; +/// +/// const sourcemeta::jsontoolkit::JSON instance{"foo bar"}; +/// sourcemeta::jsontoolkit::EvaluationContext context; +/// context.prepare(instance); +/// +/// const auto result{sourcemeta::jsontoolkit::evaluate( +/// schema_template, context)}; +/// assert(result); +/// ``` +auto SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT evaluate( + const SchemaCompilerTemplate &steps, EvaluationContext &context) -> bool; + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h new file mode 100644 index 00000000..9a10278e --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h @@ -0,0 +1,138 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_CONTEXT_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_CONTEXT_H + +#include "evaluator_export.h" + +#include + +#include +#include + +#include // assert +#include // std::uint8_t +#include // std::reference_wrapper +#include // std::map +#include // std::optional +#include // std::set +#include // std::vector + +namespace sourcemeta::jsontoolkit { + +/// @ingroup evaluator +/// Represents a stateful schema evaluation context +class SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT EvaluationContext { +public: + /// Prepare the schema evaluation context with a given instance. + /// Performing evaluation on a context without preparing it with + /// an instance is undefined behavior. + auto prepare(const JSON &instance) -> void; + + // All of these methods are considered internal and no + // client must depend on them +#ifndef DOXYGEN + + /////////////////////////////////////////////// + // Evaluation stack + /////////////////////////////////////////////// + + auto evaluate_path() const noexcept -> const WeakPointer &; + auto instance_location() const noexcept -> const WeakPointer &; + auto push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, const bool dynamic) -> void; + // A performance shortcut for pushing without re-traversing the target + // if we already know that the destination target will be + auto push(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, const bool dynamic, + std::reference_wrapper &&new_instance) -> void; + auto pop(const bool dynamic) -> void; + auto enter(const WeakPointer::Token::Property &property) -> void; + auto enter(const WeakPointer::Token::Index &index) -> void; + auto leave() -> void; + +private: + auto push_without_traverse(const Pointer &relative_schema_location, + const Pointer &relative_instance_location, + const std::string &schema_resource, + const bool dynamic) -> void; + +public: + /////////////////////////////////////////////// + // Target resolution + /////////////////////////////////////////////// + + auto instances() const noexcept + -> const std::vector> &; + enum class TargetType : std::uint8_t { Key, Value }; + auto target_type(const TargetType type) noexcept -> void; + auto resolve_target() -> const JSON &; + auto resolve_string_target() + -> std::optional>; + + /////////////////////////////////////////////// + // References and anchors + /////////////////////////////////////////////// + + auto resources() const noexcept -> const std::vector &; + auto mark(const std::size_t id, const SchemaCompilerTemplate &children) + -> void; + // TODO: At least currently, we only need to mask if a schema + // makes use of `unevaluatedProperties` or `unevaluatedItems` + // Detect if a schema does need this so if not, we avoid + // an unnecessary copy + auto mask() -> void; + auto jump(const std::size_t id) const noexcept + -> const SchemaCompilerTemplate &; + auto find_dynamic_anchor(const std::string &anchor) const + -> std::optional; + + /////////////////////////////////////////////// + // Annotations + /////////////////////////////////////////////// + + auto annotate(const WeakPointer ¤t_instance_location, const JSON &value) + -> std::pair, bool>; + auto defines_annotation(const WeakPointer &expected_instance_location, + const WeakPointer &base_evaluate_path, + const std::vector &keywords, + const JSON &value) const -> bool; + auto largest_annotation_index(const WeakPointer &expected_instance_location, + const std::vector &keywords, + const std::uint64_t default_value) const + -> std::uint64_t; + +public: + // TODO: Remove this + const JSON null{nullptr}; + +private: +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + std::vector> instances_; + WeakPointer evaluate_path_; + WeakPointer instance_location_; + std::vector> frame_sizes; + // TODO: Keep hashes of schema resources URI instead for performance reasons + std::vector resources_; + std::vector annotation_blacklist; + // We don't use a pair for holding the two pointers for runtime + // efficiency when resolving keywords like `unevaluatedProperties` + std::map>> annotations_; + std::map> + labels; + bool property_as_instance{false}; +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif +#endif +}; + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_error.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_error.h new file mode 100644 index 00000000..46df2644 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_error.h @@ -0,0 +1,39 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_ERROR_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_ERROR_H + +#include "evaluator_export.h" + +#include // std::exception +#include // std::string +#include // std::move + +namespace sourcemeta::jsontoolkit { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup jsonschema +/// An error that represents a schema evaluation error event +class SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT SchemaEvaluationError + : public std::exception { +public: + SchemaEvaluationError(std::string message) : message_{std::move(message)} {} + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_.c_str(); + } + +private: + std::string message_; +}; + +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h new file mode 100644 index 00000000..9a71442a --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h @@ -0,0 +1,516 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_TEMPLATE_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_TEMPLATE_H + +#include + +#include + +#include // std::uint8_t +#include // std::string +#include // std::vector + +namespace sourcemeta::jsontoolkit { + +// Forward declarations for the sole purpose of being bale to define circular +// structures +#ifndef DOXYGEN +struct SchemaCompilerAssertionFail; +struct SchemaCompilerAssertionDefines; +struct SchemaCompilerAssertionDefinesAll; +struct SchemaCompilerAssertionPropertyDependencies; +struct SchemaCompilerAssertionType; +struct SchemaCompilerAssertionTypeAny; +struct SchemaCompilerAssertionTypeStrict; +struct SchemaCompilerAssertionTypeStrictAny; +struct SchemaCompilerAssertionTypeStringBounded; +struct SchemaCompilerAssertionTypeArrayBounded; +struct SchemaCompilerAssertionTypeObjectBounded; +struct SchemaCompilerAssertionRegex; +struct SchemaCompilerAssertionStringSizeLess; +struct SchemaCompilerAssertionStringSizeGreater; +struct SchemaCompilerAssertionArraySizeLess; +struct SchemaCompilerAssertionArraySizeGreater; +struct SchemaCompilerAssertionObjectSizeLess; +struct SchemaCompilerAssertionObjectSizeGreater; +struct SchemaCompilerAssertionEqual; +struct SchemaCompilerAssertionEqualsAny; +struct SchemaCompilerAssertionGreaterEqual; +struct SchemaCompilerAssertionLessEqual; +struct SchemaCompilerAssertionGreater; +struct SchemaCompilerAssertionLess; +struct SchemaCompilerAssertionUnique; +struct SchemaCompilerAssertionDivisible; +struct SchemaCompilerAssertionStringType; +struct SchemaCompilerAssertionPropertyType; +struct SchemaCompilerAssertionPropertyTypeStrict; +struct SchemaCompilerAnnotationEmit; +struct SchemaCompilerAnnotationWhenArraySizeEqual; +struct SchemaCompilerAnnotationWhenArraySizeGreater; +struct SchemaCompilerAnnotationToParent; +struct SchemaCompilerAnnotationBasenameToParent; +struct SchemaCompilerLogicalOr; +struct SchemaCompilerLogicalAnd; +struct SchemaCompilerLogicalXor; +struct SchemaCompilerLogicalCondition; +struct SchemaCompilerLogicalNot; +struct SchemaCompilerLogicalWhenType; +struct SchemaCompilerLogicalWhenDefines; +struct SchemaCompilerLogicalWhenArraySizeGreater; +struct SchemaCompilerLogicalWhenArraySizeEqual; +struct SchemaCompilerLoopPropertiesMatch; +struct SchemaCompilerLoopProperties; +struct SchemaCompilerLoopPropertiesRegex; +struct SchemaCompilerLoopPropertiesNoAnnotation; +struct SchemaCompilerLoopPropertiesExcept; +struct SchemaCompilerLoopPropertiesType; +struct SchemaCompilerLoopPropertiesTypeStrict; +struct SchemaCompilerLoopKeys; +struct SchemaCompilerLoopItems; +struct SchemaCompilerLoopItemsUnmarked; +struct SchemaCompilerLoopItemsUnevaluated; +struct SchemaCompilerLoopContains; +struct SchemaCompilerControlLabel; +struct SchemaCompilerControlMark; +struct SchemaCompilerControlJump; +struct SchemaCompilerControlDynamicAnchorJump; +#endif + +/// @ingroup evaluator +/// Represents a schema compilation step that can be evaluated +using SchemaCompilerTemplate = std::vector>; + +#if !defined(DOXYGEN) +// For fast internal instruction dispatching. It must stay +// in sync with the variant ordering above +enum class SchemaCompilerTemplateIndex : std::uint8_t { + SchemaCompilerAssertionFail = 0, + SchemaCompilerAssertionDefines, + SchemaCompilerAssertionDefinesAll, + SchemaCompilerAssertionPropertyDependencies, + SchemaCompilerAssertionType, + SchemaCompilerAssertionTypeAny, + SchemaCompilerAssertionTypeStrict, + SchemaCompilerAssertionTypeStrictAny, + SchemaCompilerAssertionTypeStringBounded, + SchemaCompilerAssertionTypeArrayBounded, + SchemaCompilerAssertionTypeObjectBounded, + SchemaCompilerAssertionRegex, + SchemaCompilerAssertionStringSizeLess, + SchemaCompilerAssertionStringSizeGreater, + SchemaCompilerAssertionArraySizeLess, + SchemaCompilerAssertionArraySizeGreater, + SchemaCompilerAssertionObjectSizeLess, + SchemaCompilerAssertionObjectSizeGreater, + SchemaCompilerAssertionEqual, + SchemaCompilerAssertionEqualsAny, + SchemaCompilerAssertionGreaterEqual, + SchemaCompilerAssertionLessEqual, + SchemaCompilerAssertionGreater, + SchemaCompilerAssertionLess, + SchemaCompilerAssertionUnique, + SchemaCompilerAssertionDivisible, + SchemaCompilerAssertionStringType, + SchemaCompilerAssertionPropertyType, + SchemaCompilerAssertionPropertyTypeStrict, + SchemaCompilerAnnotationEmit, + SchemaCompilerAnnotationWhenArraySizeEqual, + SchemaCompilerAnnotationWhenArraySizeGreater, + SchemaCompilerAnnotationToParent, + SchemaCompilerAnnotationBasenameToParent, + SchemaCompilerLogicalOr, + SchemaCompilerLogicalAnd, + SchemaCompilerLogicalXor, + SchemaCompilerLogicalCondition, + SchemaCompilerLogicalNot, + SchemaCompilerLogicalWhenType, + SchemaCompilerLogicalWhenDefines, + SchemaCompilerLogicalWhenArraySizeGreater, + SchemaCompilerLogicalWhenArraySizeEqual, + SchemaCompilerLoopPropertiesMatch, + SchemaCompilerLoopProperties, + SchemaCompilerLoopPropertiesRegex, + SchemaCompilerLoopPropertiesNoAnnotation, + SchemaCompilerLoopPropertiesExcept, + SchemaCompilerLoopPropertiesType, + SchemaCompilerLoopPropertiesTypeStrict, + SchemaCompilerLoopKeys, + SchemaCompilerLoopItems, + SchemaCompilerLoopItemsUnmarked, + SchemaCompilerLoopItemsUnevaluated, + SchemaCompilerLoopContains, + SchemaCompilerControlLabel, + SchemaCompilerControlMark, + SchemaCompilerControlJump, + SchemaCompilerControlDynamicAnchorJump +}; +#endif + +#define DEFINE_STEP_WITH_VALUE(category, name, type) \ + struct SchemaCompiler##category##name { \ + const Pointer relative_schema_location; \ + const Pointer relative_instance_location; \ + const std::string keyword_location; \ + const std::string schema_resource; \ + const bool dynamic; \ + const bool report; \ + const type value; \ + }; + +#define DEFINE_STEP_APPLICATOR(category, name, type) \ + struct SchemaCompiler##category##name { \ + const Pointer relative_schema_location; \ + const Pointer relative_instance_location; \ + const std::string keyword_location; \ + const std::string schema_resource; \ + const bool dynamic; \ + const bool report; \ + const type value; \ + const SchemaCompilerTemplate children; \ + }; + +/// @defgroup evaluator_instructions Instruction Set +/// @ingroup evaluator +/// @brief The set of instructions supported by the evaluator. +/// @details +/// +/// Every instruction operates at a specific instance location and with the +/// given value, whose type depends on the instruction. + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that always fails +DEFINE_STEP_WITH_VALUE(Assertion, Fail, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if an object defines +/// a given property +DEFINE_STEP_WITH_VALUE(Assertion, Defines, SchemaCompilerValueString) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if an object defines +/// a set of properties +DEFINE_STEP_WITH_VALUE(Assertion, DefinesAll, SchemaCompilerValueStrings) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if an object defines +/// a set of properties if it defines other set of properties +DEFINE_STEP_WITH_VALUE(Assertion, PropertyDependencies, + SchemaCompilerValueStringMap) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// the given type +DEFINE_STEP_WITH_VALUE(Assertion, Type, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// any of the given types +DEFINE_STEP_WITH_VALUE(Assertion, TypeAny, SchemaCompilerValueTypes) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// the given type (strict version) +DEFINE_STEP_WITH_VALUE(Assertion, TypeStrict, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// any of the given types (strict version) +DEFINE_STEP_WITH_VALUE(Assertion, TypeStrictAny, SchemaCompilerValueTypes) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// type string and adheres to the given bounds +DEFINE_STEP_WITH_VALUE(Assertion, TypeStringBounded, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// type array and adheres to the given bounds +DEFINE_STEP_WITH_VALUE(Assertion, TypeArrayBounded, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks if a document is of +/// type object and adheres to the given bounds +DEFINE_STEP_WITH_VALUE(Assertion, TypeObjectBounded, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a string against an +/// ECMA regular expression +DEFINE_STEP_WITH_VALUE(Assertion, Regex, SchemaCompilerValueRegex) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given string has +/// less than a certain number of characters +DEFINE_STEP_WITH_VALUE(Assertion, StringSizeLess, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given string has +/// greater than a certain number of characters +DEFINE_STEP_WITH_VALUE(Assertion, StringSizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given array has +/// less than a certain number of items +DEFINE_STEP_WITH_VALUE(Assertion, ArraySizeLess, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given array has +/// greater than a certain number of items +DEFINE_STEP_WITH_VALUE(Assertion, ArraySizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given object has +/// less than a certain number of properties +DEFINE_STEP_WITH_VALUE(Assertion, ObjectSizeLess, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given object has +/// greater than a certain number of properties +DEFINE_STEP_WITH_VALUE(Assertion, ObjectSizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks the instance equals +/// a given JSON document +DEFINE_STEP_WITH_VALUE(Assertion, Equal, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that a JSON document +/// is equal to at least one of the given elements +DEFINE_STEP_WITH_VALUE(Assertion, EqualsAny, SchemaCompilerValueArray) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// greater than or equal to another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, GreaterEqual, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// less than or equal to another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, LessEqual, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// greater than another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, Greater, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a JSON document is +/// less than another JSON document +DEFINE_STEP_WITH_VALUE(Assertion, Less, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a given JSON array +/// does not contain duplicate items +DEFINE_STEP_WITH_VALUE(Assertion, Unique, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks a number is +/// divisible by another number +DEFINE_STEP_WITH_VALUE(Assertion, Divisible, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that a string is of +/// a certain type +DEFINE_STEP_WITH_VALUE(Assertion, StringType, SchemaCompilerValueStringType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that an instance +/// property is of a given type if present +DEFINE_STEP_WITH_VALUE(Assertion, PropertyType, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler assertion step that checks that an instance +/// property is of a given type if present (strict mode) +DEFINE_STEP_WITH_VALUE(Assertion, PropertyTypeStrict, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation +DEFINE_STEP_WITH_VALUE(Annotation, Emit, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation when the size of +/// the array instance is equal to the given size +DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeEqual, + SchemaCompilerValueIndexedJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation when the size of +/// the array instance is greater than the given size +DEFINE_STEP_WITH_VALUE(Annotation, WhenArraySizeGreater, + SchemaCompilerValueIndexedJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits an annotation to the parent +DEFINE_STEP_WITH_VALUE(Annotation, ToParent, SchemaCompilerValueJSON) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that emits the current basename as an +/// annotation to the parent +DEFINE_STEP_WITH_VALUE(Annotation, BasenameToParent, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a disjunction +DEFINE_STEP_APPLICATOR(Logical, Or, SchemaCompilerValueBoolean) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction +DEFINE_STEP_APPLICATOR(Logical, And, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents an exclusive +/// disjunction +DEFINE_STEP_APPLICATOR(Logical, Xor, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents an imperative conditional compiler logical step +DEFINE_STEP_APPLICATOR(Logical, Condition, SchemaCompilerValueIndexPair) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a negation +DEFINE_STEP_APPLICATOR(Logical, Not, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the instance is of a given type +DEFINE_STEP_APPLICATOR(Logical, WhenType, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the instance is an object and defines a given property +DEFINE_STEP_APPLICATOR(Logical, WhenDefines, SchemaCompilerValueString) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the array instance size is greater than the given number +DEFINE_STEP_APPLICATOR(Logical, WhenArraySizeGreater, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler logical step that represents a conjunction when +/// the array instance size is equal to the given number +DEFINE_STEP_APPLICATOR(Logical, WhenArraySizeEqual, + SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that matches steps to object properties +DEFINE_STEP_APPLICATOR(Loop, PropertiesMatch, SchemaCompilerValueNamedIndexes) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties +DEFINE_STEP_APPLICATOR(Loop, Properties, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties that +/// match a given ECMA regular expression +DEFINE_STEP_APPLICATOR(Loop, PropertiesRegex, SchemaCompilerValueRegex) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties that +/// were not collected as annotations +DEFINE_STEP_APPLICATOR(Loop, PropertiesNoAnnotation, SchemaCompilerValueStrings) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object properties that +/// do not match the given property filters +DEFINE_STEP_APPLICATOR(Loop, PropertiesExcept, + SchemaCompilerValuePropertyFilter) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that checks every object property is of a +/// given type +DEFINE_STEP_WITH_VALUE(Loop, PropertiesType, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that checks every object property is of a +/// given type (strict mode) +DEFINE_STEP_WITH_VALUE(Loop, PropertiesTypeStrict, SchemaCompilerValueType) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over object property keys +DEFINE_STEP_APPLICATOR(Loop, Keys, SchemaCompilerValueNone) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over array items starting from +/// a given index +DEFINE_STEP_APPLICATOR(Loop, Items, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over array items when the array +/// is considered unmarked +DEFINE_STEP_APPLICATOR(Loop, ItemsUnmarked, SchemaCompilerValueStrings) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that loops over unevaluated array items +DEFINE_STEP_APPLICATOR(Loop, ItemsUnevaluated, + SchemaCompilerValueItemsAnnotationKeywords) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that checks array items match a given +/// criteria +DEFINE_STEP_APPLICATOR(Loop, Contains, SchemaCompilerValueRange) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of a mark to jump to while +/// executing children instructions +DEFINE_STEP_APPLICATOR(Control, Label, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of a mark to jump to, but +/// without executing children instructions +DEFINE_STEP_APPLICATOR(Control, Mark, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of jumping into a +/// pre-registered label +DEFINE_STEP_WITH_VALUE(Control, Jump, SchemaCompilerValueUnsignedInteger) + +/// @ingroup evaluator_instructions +/// @brief Represents a compiler step that consists of jump to a dynamic anchor +DEFINE_STEP_WITH_VALUE(Control, DynamicAnchorJump, SchemaCompilerValueString) + +#undef DEFINE_STEP_WITH_VALUE +#undef DEFINE_STEP_APPLICATOR + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_value.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_value.h new file mode 100644 index 00000000..2a2ba107 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_value.h @@ -0,0 +1,115 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_VALUE_H +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_VALUE_H + +#include +#include + +#include // std::uint8_t +#include // std::optional, std::nullopt +#include // std::regex +#include // std::set +#include // std::string +#include // std::tuple +#include // std::unordered_map +#include // std::pair +#include // std::vector + +namespace sourcemeta::jsontoolkit { + +/// @ingroup evaluator +/// @brief Represents a compiler step empty value +struct SchemaCompilerValueNone {}; + +/// @ingroup evaluator +/// Represents a compiler step JSON value +using SchemaCompilerValueJSON = JSON; + +// Note that for these steps, we prefer vectors over sets as the former performs +// better for small collections, where we can even guarantee uniqueness when +// generating the instructions + +/// @ingroup evaluator +/// Represents a set of JSON values +using SchemaCompilerValueArray = std::vector; + +/// @ingroup evaluator +/// Represents a compiler step string values +using SchemaCompilerValueStrings = std::vector; + +/// @ingroup evaluator +/// Represents a compiler step JSON types value +using SchemaCompilerValueTypes = std::vector; + +/// @ingroup evaluator +/// Represents a compiler step string value +using SchemaCompilerValueString = JSON::String; + +/// @ingroup evaluator +/// Represents a compiler step JSON type value +using SchemaCompilerValueType = JSON::Type; + +/// @ingroup evaluator +/// Represents a compiler step ECMA regular expression value. We store both the +/// original string and the regular expression as standard regular expressions +/// do not keep a copy of their original value (which we need for serialization +/// purposes) +using SchemaCompilerValueRegex = std::pair; + +/// @ingroup evaluator +/// Represents a compiler step JSON unsigned integer value +using SchemaCompilerValueUnsignedInteger = std::size_t; + +/// @ingroup evaluator +/// Represents a compiler step range value. The boolean option +/// modifies whether the range is considered exhaustively or +/// if the evaluator is allowed to break early +using SchemaCompilerValueRange = + std::tuple, bool>; + +/// @ingroup evaluator +/// Represents a compiler step boolean value +using SchemaCompilerValueBoolean = bool; + +/// @ingroup evaluator +/// Represents a compiler step string to index map +using SchemaCompilerValueNamedIndexes = + std::unordered_map; + +/// @ingroup evaluator +/// Represents a compiler step string logical type +enum class SchemaCompilerValueStringType : std::uint8_t { URI }; + +/// @ingroup evaluator +/// Represents an array loop compiler step annotation keywords +struct SchemaCompilerValueItemsAnnotationKeywords { + const SchemaCompilerValueString index; + const SchemaCompilerValueStrings filter; + const SchemaCompilerValueStrings mask; +}; + +/// @ingroup evaluator +/// Represents an compiler step that maps strings to strings +using SchemaCompilerValueStringMap = + std::unordered_map; + +/// @ingroup evaluator +/// Represents a compiler step JSON value accompanied with an index +using SchemaCompilerValueIndexedJSON = + std::pair; + +// Note that while we generally avoid sets, in this case, we want +// hash-based lookups on string collections that might get large. +/// @ingroup evaluator +/// Represents a compiler step value that consist of object property filters +using SchemaCompilerValuePropertyFilter = + std::pair, + std::vector>; + +/// @ingroup evaluator +/// Represents a compiler step value that consists of two indexes +using SchemaCompilerValueIndexPair = std::pair; + +} // namespace sourcemeta::jsontoolkit + +#endif diff --git a/vendor/jsontoolkit/src/evaluator/trace.h b/vendor/jsontoolkit/src/evaluator/trace.h new file mode 100644 index 00000000..770967f2 --- /dev/null +++ b/vendor/jsontoolkit/src/evaluator/trace.h @@ -0,0 +1,30 @@ +#ifndef SOURCEMETA_JSONTOOLKIT_EVALUATOR_TRACE_H_ +#define SOURCEMETA_JSONTOOLKIT_EVALUATOR_TRACE_H_ + +// We only perform tracing on debugging builds, at least for now +#if !defined(NDEBUG) && defined(__APPLE__) && defined(__clang__) + +#include +#include + +// See +// https://www.jviotti.com/2022/02/21/emitting-signposts-to-instruments-on-macos-using-cpp.html + +static os_log_t log_handle = os_log_create("com.sourcemeta.jsontoolkit", + OS_LOG_CATEGORY_POINTS_OF_INTEREST); + +#define SOURCEMETA_TRACE_REGISTER_ID(name) \ + const os_signpost_id_t name = os_signpost_id_generate(log_handle); \ + assert((name) != OS_SIGNPOST_ID_INVALID); +#define SOURCEMETA_TRACE_START(id, title) \ + os_signpost_interval_begin(log_handle, id, title); +#define SOURCEMETA_TRACE_END(id, title) \ + os_signpost_interval_end(log_handle, id, title); + +#else +#define SOURCEMETA_TRACE_REGISTER_ID(name) +#define SOURCEMETA_TRACE_START(id, title) +#define SOURCEMETA_TRACE_END(id, title) +#endif + +#endif diff --git a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json.h b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json.h index 4223f0a2..19fd1b97 100644 --- a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json.h +++ b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json.h @@ -1,11 +1,7 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSON_H_ #define SOURCEMETA_JSONTOOLKIT_JSON_H_ -#if defined(__EMSCRIPTEN__) || defined(__Unikraft__) -#define SOURCEMETA_JSONTOOLKIT_JSON_EXPORT -#else #include "json_export.h" -#endif #include #include diff --git a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_error.h b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_error.h index bc0f0f75..08b90cab 100644 --- a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_error.h +++ b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_error.h @@ -1,11 +1,7 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSON_ERROR_H_ #define SOURCEMETA_JSONTOOLKIT_JSON_ERROR_H_ -#if defined(__EMSCRIPTEN__) || defined(__Unikraft__) -#define SOURCEMETA_JSONTOOLKIT_JSON_EXPORT -#else #include "json_export.h" -#endif #include // std::uint64_t #include // std::exception diff --git a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h index c7bf278d..b9d2b774 100644 --- a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h +++ b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h @@ -1,9 +1,14 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSON_OBJECT_H_ #define SOURCEMETA_JSONTOOLKIT_JSON_OBJECT_H_ -#include // std::less +#include // std::equal_to, std::less #include // std::initializer_list -#include // std::map + +#if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 12) +#include // std::map +#else +#include // std::unordered_map +#endif namespace sourcemeta::jsontoolkit { @@ -11,10 +16,18 @@ namespace sourcemeta::jsontoolkit { template class JSONObject { public: // Constructors + + // Older versions of GCC don't allow `std::unordered_map` to incomplete + // types, and in this case, `Value` is an incomplete type. using Container = +#if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 12) std::map, +#else + std::unordered_map, std::equal_to, +#endif typename Value::template Allocator< std::pair>>; + JSONObject() : data{} {} JSONObject(std::initializer_list values) : data{values} {} @@ -22,9 +35,27 @@ template class JSONObject { // Operators // We cannot default given that this class references // a JSON "value" as an incomplete type + auto operator<(const JSONObject &other) const noexcept -> bool { - return this->data < other.data; + // The `std::unordered_map` container, by definition, does not provide + // ordering. However, we still want some level of ordering to allow + // arrays of objects to be sorted. + + // First try a size comparison + if (this->data.size() != other.data.size()) { + return this->data.size() < other.data.size(); + } + + // Otherwise do value comparison for common properties + for (const auto &[key, value] : this->data) { + if (other.data.contains(key) && value < other.data.at(key)) { + return true; + } + } + + return false; } + auto operator<=(const JSONObject &other) const noexcept -> bool { return this->data <= other.data; } @@ -47,7 +78,6 @@ template class JSONObject { using value_type = typename Container::value_type; using size_type = typename Container::size_type; using difference_type = typename Container::difference_type; - using key_compare = typename Container::key_compare; using allocator_type = typename Container::allocator_type; using reference = typename Container::reference; using const_reference = typename Container::const_reference; @@ -55,8 +85,6 @@ template class JSONObject { using const_pointer = typename Container::const_pointer; using iterator = typename Container::iterator; using const_iterator = typename Container::const_iterator; - using reverse_iterator = typename Container::reverse_iterator; - using const_reverse_iterator = typename Container::const_reverse_iterator; /// Get a mutable begin iterator on the object auto begin() noexcept -> iterator { return this->data.begin(); } @@ -70,26 +98,6 @@ template class JSONObject { auto cbegin() const noexcept -> const_iterator { return this->data.cbegin(); } /// Get a constant end iterator on the object auto cend() const noexcept -> const_iterator { return this->data.cend(); } - /// Get a mutable reverse begin iterator on the object - auto rbegin() noexcept -> reverse_iterator { return this->data.rbegin(); } - /// Get a mutable reverse end iterator on the object - auto rend() noexcept -> reverse_iterator { return this->data.rend(); } - /// Get a constant reverse begin iterator on the object - auto rbegin() const noexcept -> const_reverse_iterator { - return this->data.rbegin(); - } - /// Get a constant reverse end iterator on the object - auto rend() const noexcept -> const_reverse_iterator { - return this->data.rend(); - } - /// Get a constant reverse begin iterator on the object - auto crbegin() const noexcept -> const_reverse_iterator { - return this->data.crbegin(); - } - /// Get a constant reverse end iterator on the object - auto crend() const noexcept -> const_reverse_iterator { - return this->data.crend(); - } private: friend Value; diff --git a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h index 3cdbe5ad..e2142b02 100644 --- a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h +++ b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h @@ -1,11 +1,7 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSON_VALUE_H_ #define SOURCEMETA_JSONTOOLKIT_JSON_VALUE_H_ -#if defined(__EMSCRIPTEN__) || defined(__Unikraft__) -#define SOURCEMETA_JSONTOOLKIT_JSON_EXPORT -#else #include "json_export.h" -#endif #include #include @@ -16,6 +12,7 @@ #include // std::less, std::reference_wrapper #include // std::initializer_list #include // std::allocator +#include // std::optional #include // std::set #include // std::basic_istringstream #include // std::basic_string, std::char_traits @@ -221,6 +218,18 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// objects. static auto make_object() -> JSON; + /// This function calculates the logical size of a string according to the + /// JSON specification. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::jsontoolkit::JSON::String value{"foo"}; + /// assert(sourcemeta::jsontoolkit::JSON::size(value) == 3); + /// ``` + static auto size(const String &value) noexcept -> std::size_t; + /* * Operators */ @@ -654,8 +663,8 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// sourcemeta::jsontoolkit::parse("{ \"1\": "foo" }"); /// assert(my_array.at(1).to_string() == "foo"); /// ``` - [[nodiscard]] auto - at(const typename Array::size_type index) const -> const JSON &; + [[nodiscard]] auto at(const typename Array::size_type index) const + -> const JSON &; /// This method retrieves a element by its index. If the input JSON instance /// is an object, a property that corresponds to the stringified integer will @@ -851,6 +860,22 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// ``` [[nodiscard]] auto empty() const -> bool; + /// This method checks whether an input JSON object defines a specific key + /// and returns the value if it does. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::jsontoolkit::JSON document = + /// sourcemeta::jsontoolkit::parse("{ \"foo\": 1 }"); + /// EXPECT_TRUE(document.is_object()); + /// const auto result = document.try_at("foo"); + /// EXPECT_TRUE(result.has_value()); + /// EXPECT_EQ(result.value().get().to_integer(), 1); + [[nodiscard]] auto try_at(const String &key) const + -> std::optional>; + /// This method checks whether an input JSON object defines a specific key. /// For example: /// @@ -877,8 +902,8 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// assert(document.defines(0)); /// assert(!document.defines(1)); /// ``` - [[nodiscard]] auto - defines(const typename Array::size_type index) const -> bool; + [[nodiscard]] auto defines(const typename Array::size_type index) const + -> bool; /// This method checks whether an input JSON object defines at least one given /// key. @@ -914,8 +939,8 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// /// assert(document.defines_any({ "foo", "qux" })); /// ``` - [[nodiscard]] auto - defines_any(std::initializer_list keys) const -> bool; + [[nodiscard]] auto defines_any(std::initializer_list keys) const + -> bool; /// This method checks if an JSON array contains a given JSON instance. For /// example: diff --git a/vendor/jsontoolkit/src/json/json_value.cc b/vendor/jsontoolkit/src/json/json_value.cc index d5b3486b..19a0010d 100644 --- a/vendor/jsontoolkit/src/json/json_value.cc +++ b/vendor/jsontoolkit/src/json/json_value.cc @@ -1,6 +1,6 @@ #include -#include // std::find, std::sort, std::unique +#include // std::find #include // assert #include // std::isinf, std::isnan, std::modf, std::trunc #include // std::transform @@ -69,6 +69,24 @@ auto JSON::make_array() -> JSON { return JSON{Array{}}; } auto JSON::make_object() -> JSON { return JSON{Object{}}; } +auto JSON::size(const String &value) noexcept -> std::size_t { + std::size_t result{0}; + + // We want to count the number of logical characters, + // not the number of bytes + for (const auto character : value) { + // In UTF-8, continuation bytes (i.e. not the first) are + // encoded as `10xxxxxx`, so this means we are at the start + // of a code-point + // See https://en.wikipedia.org/wiki/UTF-8#Encoding + if ((character & 0b11000000) != 0b10000000) { + result += 1; + } + } + + return result; +} + auto JSON::operator<(const JSON &other) const noexcept -> bool { if ((this->type() == Type::Integer && other.type() == Type::Real) || (this->type() == Type::Real && other.type() == Type::Integer)) { @@ -284,27 +302,15 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { } } -[[nodiscard]] auto -JSON::at(const typename JSON::Array::size_type index) const -> const JSON & { - // In practice, this case only applies in some edge cases when - // using JSON Pointers - if (this->is_object()) [[unlikely]] { - return this->at(std::to_string(index)); - } - +[[nodiscard]] auto JSON::at(const typename JSON::Array::size_type index) const + -> const JSON & { assert(this->is_array()); assert(index < this->size()); return std::get(this->data).data.at(index); } -[[nodiscard]] auto -JSON::at(const typename JSON::Array::size_type index) -> JSON & { - // In practice, this case only applies in some edge cases when - // using JSON Pointers - if (this->is_object()) [[unlikely]] { - return this->at(std::to_string(index)); - } - +[[nodiscard]] auto JSON::at(const typename JSON::Array::size_type index) + -> JSON & { assert(this->is_array()); assert(index < this->size()); return std::get(this->data).data.at(index); @@ -313,13 +319,13 @@ JSON::at(const typename JSON::Array::size_type index) -> JSON & { [[nodiscard]] auto JSON::at(const JSON::String &key) const -> const JSON & { assert(this->is_object()); assert(this->defines(key)); - return std::get(this->data).data.at(key); + return std::get(this->data).data.find(key)->second; } [[nodiscard]] auto JSON::at(const JSON::String &key) -> JSON & { assert(this->is_object()); assert(this->defines(key)); - return std::get(this->data).data.at(key); + return std::get(this->data).data.find(key)->second; } [[nodiscard]] auto JSON::front() -> JSON & { @@ -353,21 +359,7 @@ JSON::at(const typename JSON::Array::size_type index) -> JSON & { return std::get(this->data).data.size(); } else { assert(this->is_string()); - std::size_t result{0}; - - // We want to count the number of logical characters, - // not the number of bytes - for (const auto character : std::get(this->data)) { - // In UTF-8, continuation bytes (i.e. not the first) are - // encoded as `10xxxxxx`, so this means we are at the start - // of a code-point - // See https://en.wikipedia.org/wiki/UTF-8#Encoding - if ((character & 0b11000000) != 0b10000000) { - result += 1; - } - } - - return result; + return JSON::size(std::get(this->data)); } } @@ -450,6 +442,19 @@ JSON::at(const typename JSON::Array::size_type index) -> JSON & { } } +[[nodiscard]] auto JSON::try_at(const JSON::String &key) const + -> std::optional> { + assert(this->is_object()); + + const auto &object{std::get(this->data)}; + const auto value{object.data.find(key)}; + + if (value == object.data.cend()) { + return std::nullopt; + } + return value->second; +} + [[nodiscard]] auto JSON::defines(const JSON::String &key) const -> bool { assert(this->is_object()); return std::get(this->data).data.contains(key); @@ -473,10 +478,23 @@ JSON::defines_any(std::initializer_list keys) const -> bool { [[nodiscard]] auto JSON::unique() const -> bool { assert(this->is_array()); - // TODO: Can we do efficiently do this without copying? - auto copy{std::get(this->data).data}; - std::sort(copy.begin(), copy.end()); - return std::unique(copy.begin(), copy.end()) == copy.end(); + const auto &items{std::get(this->data).data}; + // Arrays of 0 or 1 item are unique by definition + if (items.size() <= 1) { + return true; + } + + // Otherwise std::unique would require us to create a copy of the contents + for (auto iterator = items.cbegin(); iterator != items.cend(); ++iterator) { + for (auto subiterator = std::next(iterator); subiterator != items.cend(); + ++subiterator) { + if (*iterator == *subiterator) { + return false; + } + } + } + + return true; } auto JSON::push_back(const JSON &value) -> void { @@ -525,8 +543,8 @@ auto JSON::assign(const JSON::String &key, JSON &&value) -> void { std::get(this->data).data.insert_or_assign(key, std::move(value)); } -auto JSON::assign_if_missing(const JSON::String &key, - const JSON &value) -> void { +auto JSON::assign_if_missing(const JSON::String &key, const JSON &value) + -> void { assert(this->is_object()); if (!this->defines(key)) { this->assign(key, value); diff --git a/vendor/jsontoolkit/src/json/parser.h b/vendor/jsontoolkit/src/json/parser.h index 9720ac1d..88253c62 100644 --- a/vendor/jsontoolkit/src/json/parser.h +++ b/vendor/jsontoolkit/src/json/parser.h @@ -71,12 +71,10 @@ inline auto parse_boolean_false( return JSON{false}; } -auto parse_string_unicode( +auto parse_string_unicode_code_point( const std::uint64_t line, std::uint64_t &column, - std::basic_istream &stream, - std::basic_ostringstream> - &result) -> void { + std::basic_istream &stream) + -> unsigned long { std::basic_string> code_point; @@ -109,8 +107,68 @@ auto parse_string_unicode( // According to ECMA 404, \u can be followed by "any" // sequence of 4 hexadecimal digits. constexpr auto unicode_base{16}; - result.put(static_cast( - std::stoul(code_point, nullptr, unicode_base))); + const auto result{std::stoul(code_point, nullptr, unicode_base)}; + // The largest possible valid unicode code point + assert(result <= 0xFFFF); + return result; +} + +auto parse_string_unicode( + const std::uint64_t line, std::uint64_t &column, + std::basic_istream &stream, + std::basic_ostringstream> + &result) -> void { + auto code_point{parse_string_unicode_code_point(line, column, stream)}; + using CharT = typename JSON::Char; + + // This means we are at the beginning of a UTF-16 surrogate pair high code + // point See + // https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF + if (code_point >= 0xD800 && code_point <= 0xDBFF) { + // Next, we expect "\" + column += 1; + if (stream.get() != internal::token_string_escape) { + throw ParseError(line, column); + } + + // Next, we expect "u" + column += 1; + if (stream.get() != internal::token_string_escape_unicode) { + throw ParseError(line, column); + } + + // Finally, get the low code point of the surrogate and calculate + // the real final code point + const auto low_code_point{ + parse_string_unicode_code_point(line, column, stream)}; + + // See + // https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF + if (low_code_point >= 0xDC00 && low_code_point <= 0xDFFF) { + code_point = + 0x10000 + ((code_point - 0xD800) << 10) + (low_code_point - 0xDC00); + } else { + throw ParseError(line, column); + } + } + + // Convert a Unicode codepoint into UTF-8 + // See https://en.wikipedia.org/wiki/UTF-8#Description + + if (code_point <= 0x7F) { + // UTF-8 + result.put(static_cast(code_point)); + } else if (code_point <= 0x7FF) { + // UTF-16 + result.put(static_cast(0xC0 | ((code_point >> 6) & 0x1F))); + result.put(static_cast(0x80 | (code_point & 0x3F))); + } else { + // UTF-32 + result.put(static_cast(0xE0 | ((code_point >> 12) & 0x0F))); + result.put(static_cast(0x80 | ((code_point >> 6) & 0x3F))); + result.put(static_cast(0x80 | (code_point & 0x3F))); + } } auto parse_string_escape( @@ -596,7 +654,7 @@ auto internal_parse( std::uint64_t &line, std::uint64_t &column) -> JSON { // Globals using Result = JSON; - enum class Container { Array, Object }; + enum class Container : std::uint8_t { Array, Object }; std::stack levels; std::stack> frames; std::optional result; diff --git a/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl.h b/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl.h index b44953aa..0f76cad1 100644 --- a/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl.h +++ b/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl.h @@ -1,11 +1,7 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSONL_H_ #define SOURCEMETA_JSONTOOLKIT_JSONL_H_ -#if defined(__EMSCRIPTEN__) || defined(__Unikraft__) -#define SOURCEMETA_JSONTOOLKIT_JSONL_EXPORT -#else #include "jsonl_export.h" -#endif #include #include diff --git a/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h b/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h index 93ac4c36..654098ad 100644 --- a/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h +++ b/vendor/jsontoolkit/src/jsonl/include/sourcemeta/jsontoolkit/jsonl_iterator.h @@ -1,11 +1,7 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSONL_ITERATOR_H_ #define SOURCEMETA_JSONTOOLKIT_JSONL_ITERATOR_H_ -#if defined(__EMSCRIPTEN__) || defined(__Unikraft__) -#define SOURCEMETA_JSONTOOLKIT_JSONL_EXPORT -#else #include "jsonl_export.h" -#endif #include @@ -34,8 +30,8 @@ class SOURCEMETA_JSONTOOLKIT_JSONL_EXPORT ConstJSONLIterator { auto operator++() -> ConstJSONLIterator &; SOURCEMETA_JSONTOOLKIT_JSONL_EXPORT friend auto - operator==(const ConstJSONLIterator &left, - const ConstJSONLIterator &right) -> bool; + operator==(const ConstJSONLIterator &left, const ConstJSONLIterator &right) + -> bool; private: std::uint64_t line{1}; diff --git a/vendor/jsontoolkit/src/jsonl/iterator.cc b/vendor/jsontoolkit/src/jsonl/iterator.cc index 1efe4cec..1d26b804 100644 --- a/vendor/jsontoolkit/src/jsonl/iterator.cc +++ b/vendor/jsontoolkit/src/jsonl/iterator.cc @@ -15,7 +15,26 @@ struct ConstJSONLIterator::Internal { */ auto ConstJSONLIterator::parse_next() -> JSON { - if (this->data && !this->data->eof()) { + while (this->data && !this->data->eof()) { + switch (this->data->peek()) { + // Whitespace + case internal::token_jsonl_whitespace_space: + case internal::token_jsonl_whitespace_tabulation: + case internal::token_jsonl_whitespace_carriage_return: + this->column += 1; + this->data->ignore(1); + break; + case JSON::CharTraits::eof(): + this->data = nullptr; + break; + default: + goto parse_start; + } + } + +parse_start: + if (this->data) { + assert(!this->data->eof()); return parse(*this->data, this->line, this->column); } else { // Just as a cheap placeholder @@ -83,8 +102,8 @@ ConstJSONLIterator::ConstJSONLIterator( ConstJSONLIterator::~ConstJSONLIterator() {} -auto operator==(const ConstJSONLIterator &left, - const ConstJSONLIterator &right) -> bool { +auto operator==(const ConstJSONLIterator &left, const ConstJSONLIterator &right) + -> bool { return (!left.data && !right.data) || (left.data && right.data && left.internal->current == right.internal->current); diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h index 7cda7052..cedda358 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h @@ -1,11 +1,7 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSONPOINTER_H_ #define SOURCEMETA_JSONTOOLKIT_JSONPOINTER_H_ -#if defined(__EMSCRIPTEN__) || defined(__Unikraft__) -#define SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT -#else #include "jsonpointer_export.h" -#endif #include #include @@ -15,9 +11,11 @@ #include #include -#include // std::allocator -#include // std::basic_ostream -#include // std::basic_string +#include // std::reference_wrapper +#include // std::allocator +#include // std::optional +#include // std::basic_ostream +#include // std::basic_string /// @defgroup jsonpointer JSON Pointer /// @brief An growing implementation of RFC 6901 JSON Pointer. @@ -28,17 +26,22 @@ /// #include /// ``` -// TODO: Remove character and character traits template arguments from pointers - namespace sourcemeta::jsontoolkit { /// @ingroup jsonpointer -using Pointer = GenericPointer; +using Pointer = GenericPointer; + +/// @ingroup jsonpointer +using WeakPointer = GenericPointer>; /// @ingroup jsonpointer /// A global constant instance of the empty JSON Pointer. const Pointer empty_pointer; +/// @ingroup jsonpointer +/// A global constant instance of the empty JSON WeakPointer. +const WeakPointer empty_weak_pointer; + /// @ingroup jsonpointer /// Get a value from a JSON document using a JSON Pointer (`const` overload). /// @@ -61,6 +64,73 @@ const Pointer empty_pointer; SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT auto get(const JSON &document, const Pointer &pointer) -> const JSON &; +/// @ingroup jsonpointer +/// Get a value from a JSON document using a JSON WeakPointer (`const` +/// overload). +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"[ { \"foo\": 1 }, { \"bar\": 2 } ]"}; +/// const sourcemeta::jsontoolkit::JSON document = +/// sourcemeta::jsontoolkit::parse(stream); +/// +/// const std::string bar = "bar"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{1, std::cref(bar)}; +/// const sourcemeta::jsontoolkit::JSON &value{ +/// sourcemeta::jsontoolkit::get(document, pointer)}; +/// assert(value.is_integer()); +/// assert(value.to_integer() == 2); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto get(const JSON &document, const WeakPointer &pointer) -> const JSON &; + +/// @ingroup jsonpointer +/// Get a value from a JSON document using a Pointer, returning an optional that +/// is not set if the path does not exist in the document. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"[ { \"foo\": 1 }, { \"bar\": 2 } ]"}; +/// const auto document{sourcemeta::jsontoolkit::parse(stream)}; +/// const sourcemeta::jsontoolkit::Pointer pointer{1, "bar"}; +/// const auto result{sourcemeta::jsontoolkit::try_get(document, pointer)}; +/// assert(result.has_value()); +/// assert(result.value().get() == document.at(1).at("bar")); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto try_get(const JSON &document, const Pointer &pointer) + -> std::optional>; + +/// @ingroup jsonpointer +/// Get a value from a JSON document using a WeakPointer, returning an optional +/// that is not set if the path does not exist in the document. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"[ { \"foo\": 1 }, { \"bar\": 2 } ]"}; +/// const auto document{sourcemeta::jsontoolkit::parse(stream)}; +/// const std::string bar = "bar"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{1, std::cref(bar)}; +/// const auto result{sourcemeta::jsontoolkit::try_get(document, pointer)}; +/// assert(result.has_value()); +/// assert(result.value().get() == document.at(1).at("bar")); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto try_get(const JSON &document, const WeakPointer &pointer) + -> std::optional>; + /// @ingroup jsonpointer /// Get a value from a JSON document using a JSON Pointer (non-`const` /// overload). @@ -85,6 +155,75 @@ auto get(const JSON &document, const Pointer &pointer) -> const JSON &; SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT auto get(JSON &document, const Pointer &pointer) -> JSON &; +/// @ingroup jsonpointer +/// Get a value from a JSON document using a JSON Pointer token (`const` +/// overload). +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"{ \"foo\": 1 }"}; +/// const sourcemeta::jsontoolkit::JSON document = +/// sourcemeta::jsontoolkit::parse(stream); +/// +/// const sourcemeta::jsontoolkit::JSON &value{ +/// sourcemeta::jsontoolkit::get(document, +/// sourcemeta::jsontoolkit::Pointer{"foo"})}; +/// assert(value.is_integer()); +/// assert(value.to_integer() == 1); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto get(const JSON &document, const Pointer::Token &token) -> const JSON &; + +/// @ingroup jsonpointer +/// Get a value from a JSON document using a JSON WeakPointer token (`const` +/// overload). +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"{ \"foo\": 1 }"}; +/// const sourcemeta::jsontoolkit::JSON document = +/// sourcemeta::jsontoolkit::parse(stream); +/// +/// const std::string foo = "foo"; +/// const sourcemeta::jsontoolkit::JSON &value{ +/// sourcemeta::jsontoolkit::get(document, +/// sourcemeta::jsontoolkit::WeakPointer{std::cref(foo)})}; +/// assert(value.is_integer()); +/// assert(value.to_integer() == 1); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto get(const JSON &document, const WeakPointer::Token &token) -> const JSON &; + +/// @ingroup jsonpointer +/// Get a value from a JSON document using a JSON Pointer token (non-`const` +/// overload). +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"{ \"foo\": 1 }"}; +/// sourcemeta::jsontoolkit::JSON document = +/// sourcemeta::jsontoolkit::parse(stream); +/// +/// sourcemeta::jsontoolkit::JSON &value{ +/// sourcemeta::jsontoolkit::get(document, "bar")}; +/// assert(value.is_integer()); +/// assert(value.to_integer() == 2); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto get(JSON &document, const Pointer::Token &token) -> JSON &; + /// @ingroup jsonpointer /// Set a value in a JSON document using a JSON Pointer (`const` overload). /// @@ -191,6 +330,27 @@ auto stringify(const Pointer &pointer, std::basic_ostream &stream) -> void; +/// @ingroup jsonpointer +/// +/// Stringify the input JSON WeakPointer into a given C++ standard output +/// stream. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// const std::string foo = "foo"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{std::cref(foo)}; +/// std::ostringstream stream; +/// sourcemeta::jsontoolkit::stringify(pointer, stream); +/// std::cout << stream.str() << std::endl; +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto stringify(const WeakPointer &pointer, + std::basic_ostream &stream) + -> void; + /// @ingroup jsonpointer /// /// Stringify the input JSON Pointer into a C++ standard string. For example: @@ -209,6 +369,26 @@ auto to_string(const Pointer &pointer) -> std::basic_string>; +/// @ingroup jsonpointer +/// +/// Stringify the input JSON WeakPointer into a C++ standard string. For +/// example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// const std::string foo = "foo"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{foo}; +/// const std::string result{sourcemeta::jsontoolkit::to_string(pointer)}; +/// std::cout << result << std::endl; +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto to_string(const WeakPointer &pointer) + -> std::basic_string>; + /// @ingroup jsonpointer /// /// Stringify the input JSON Pointer into a properly escaped URI fragment. For @@ -274,8 +454,7 @@ auto to_uri(const Pointer &pointer, const URI &base) -> URI; /// assert(subpointers.at(2) == sourcemeta::jsontoolkit::Pointer{1}); /// assert(subpointers.at(3) == sourcemeta::jsontoolkit::Pointer{2}); /// ``` -using PointerWalker = - GenericPointerWalker; +using PointerWalker = GenericPointerWalker; /// @ingroup jsonpointer /// @@ -301,8 +480,7 @@ using PointerWalker = /// assert(subpointers.at(1) == sourcemeta::jsontoolkit::Pointer{"foo"}); /// assert(subpointers.at(2) == sourcemeta::jsontoolkit::Pointer{}); /// ``` -using SubPointerWalker = - GenericSubPointerWalker; +using SubPointerWalker = GenericSubPointerWalker; } // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_error.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_error.h index ce8592a4..6710219d 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_error.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_error.h @@ -1,11 +1,7 @@ #ifndef SOURCEMETA_JSONTOOLKIT_JSONPOINTER_ERROR_H_ #define SOURCEMETA_JSONTOOLKIT_JSONPOINTER_ERROR_H_ -#if defined(__EMSCRIPTEN__) || defined(__Unikraft__) -#define SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT -#else #include "jsonpointer_export.h" -#endif #include diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h index 703bcd92..0427dccc 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h @@ -5,21 +5,21 @@ #include // std::copy, std::equal #include // assert +#include // std::reference_wrapper #include // std::initializer_list #include // std::advance, std::back_inserter #include // std::basic_ostringstream #include // std::runtime_error +#include // std::enable_if_t, std::is_same_v #include // std::move #include // std::vector namespace sourcemeta::jsontoolkit { /// @ingroup jsonpointer -template typename Allocator> -class GenericPointer { +template class GenericPointer { public: - using Token = GenericToken; + using Token = GenericToken; using Value = typename Token::Value; using Container = std::vector; @@ -194,8 +194,14 @@ class GenericPointer { /// assert(pointer.at(1).to_property() == "bar"); /// assert(pointer.at(2).to_property() == "baz"); /// ``` - auto - push_back(const GenericPointer &other) -> void { + auto push_back(const GenericPointer &other) -> void { + if (other.empty()) { + return; + } else if (other.size() == 1) { + this->emplace_back(other.back()); + return; + } + this->data.reserve(this->data.size() + other.size()); std::copy(other.data.cbegin(), other.data.cend(), std::back_inserter(this->data)); @@ -221,12 +227,131 @@ class GenericPointer { /// assert(pointer.at(1).to_property() == "bar"); /// assert(pointer.at(2).to_property() == "baz"); /// ``` - auto push_back(GenericPointer &&other) -> void { + auto push_back(GenericPointer &&other) -> void { + if (other.empty()) { + return; + } else if (other.size() == 1) { + this->emplace_back(std::move(other.back())); + return; + } + this->data.reserve(this->data.size() + other.size()); std::move(other.data.begin(), other.data.end(), std::back_inserter(this->data)); } + /// Push a JSON Pointer into the back of a JSON WeakPointer. Make sure that + /// the pointer you are pushing remains alive for the duration of the + /// WeakPointer. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const std::string foo{"foo"}; + /// sourcemeta::jsontoolkit::WeakPointer pointer{std::cref(foo)}; + /// const sourcemeta::jsontoolkit::Pointer other{"bar", "baz"}; + /// pointer.push_back(other); + /// assert(pointer.size() == 3); + /// + /// assert(pointer.at(0).is_property()); + /// assert(pointer.at(1).is_property()); + /// assert(pointer.at(2).is_property()); + /// + /// assert(pointer.at(0).to_property() == "foo"); + /// assert(pointer.at(1).to_property() == "bar"); + /// assert(pointer.at(2).to_property() == "baz"); + /// ``` + template >>> + auto push_back(const GenericPointer &other) -> void { + if (other.empty()) { + return; + } else if (other.size() == 1) { + const auto &token{other.back()}; + if (token.is_property()) { + this->data.emplace_back(token.to_property()); + } else { + this->data.emplace_back(token.to_index()); + } + } else { + this->data.reserve(this->data.size() + other.size()); + for (const auto &token : other) { + if (token.is_property()) { + this->data.emplace_back(token.to_property()); + } else { + this->data.emplace_back(token.to_index()); + } + } + } + } + + /// Push a property token into the back of a JSON Pointer. + /// For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// sourcemeta::jsontoolkit::Pointer pointer{"foo"}; + /// const sourcemeta::jsontoolkit::Pointer other{"bar"}; + /// pointer.push_back(other.back().to_property()); + /// assert(pointer.size() == 2); + /// + /// assert(pointer.at(0).is_property()); + /// assert(pointer.at(1).is_property()); + /// + /// assert(pointer.at(0).to_property() == "foo"); + /// assert(pointer.at(1).to_property() == "bar"); + /// ``` + auto push_back(const typename Token::Property &property) -> void { + this->data.emplace_back(property); + } + + /// Move a property token into the back of a JSON Pointer. + /// For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// sourcemeta::jsontoolkit::Pointer pointer{"foo"}; + /// pointer.push_back("bar"); + /// assert(pointer.size() == 2); + /// + /// assert(pointer.at(0).is_property()); + /// assert(pointer.at(1).is_property()); + /// + /// assert(pointer.at(0).to_property() == "foo"); + /// assert(pointer.at(1).to_property() == "bar"); + /// ``` + auto push_back(typename Token::Property &&property) -> void { + this->data.emplace_back(std::move(property)); + } + + /// Push an index token into the back of a JSON Pointer. + /// For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// sourcemeta::jsontoolkit::Pointer pointer{"foo"}; + /// const sourcemeta::jsontoolkit::Pointer other{0}; + /// pointer.push_back(other.back().to_index()); + /// assert(pointer.size() == 2); + /// + /// assert(pointer.at(0).is_property()); + /// assert(pointer.at(1).is_index()); + /// + /// assert(pointer.at(0).to_property() == "foo"); + /// assert(pointer.at(1).to_index() == 0); + /// ``` + auto push_back(const typename Token::Index &index) -> void { + this->data.emplace_back(index); + } + /// Remove the last token of a JSON Pointer. For example: /// /// ```cpp @@ -283,10 +408,9 @@ class GenericPointer { /// assert(pointer.at(1).is_property()); /// assert(pointer.at(1).to_property() == "bar"); /// ``` - [[nodiscard]] auto - initial() const -> GenericPointer { + [[nodiscard]] auto initial() const -> GenericPointer { assert(!this->empty()); - GenericPointer result{*this}; + GenericPointer result{*this}; result.pop_back(); return result; } @@ -303,9 +427,9 @@ class GenericPointer { /// assert(left.concat(right) == /// sourcemeta::jsontoolkit::Pointer{"foo", "bar", "baz"}); /// ``` - auto concat(const GenericPointer &other) const - -> GenericPointer { - GenericPointer result{*this}; + auto concat(const GenericPointer &other) const + -> GenericPointer { + GenericPointer result{*this}; result.push_back(other); return result; } @@ -321,8 +445,7 @@ class GenericPointer { /// const sourcemeta::jsontoolkit::Pointer prefix{"foo", "bar"}; /// assert(pointer.starts_with(prefix)); /// ``` - auto starts_with(const GenericPointer &other) const - -> bool { + auto starts_with(const GenericPointer &other) const -> bool { return other.data.size() <= this->data.size() && std::equal(other.data.cbegin(), other.data.cend(), this->data.cbegin()); @@ -341,9 +464,9 @@ class GenericPointer { /// assert(pointer.rebase(prefix, replacement) == /// sourcemeta::jsontoolkit::Pointer{"qux", "baz"}); /// ``` - auto rebase(const GenericPointer &prefix, - const GenericPointer &replacement) const - -> GenericPointer { + auto rebase(const GenericPointer &prefix, + const GenericPointer &replacement) const + -> GenericPointer { typename Container::size_type index{0}; while (index < prefix.size()) { if (index >= this->size() || prefix.data[index] != this->data[index]) { @@ -357,7 +480,7 @@ class GenericPointer { assert(this->starts_with(prefix)); auto new_begin{this->data.cbegin()}; std::advance(new_begin, index); - GenericPointer result{replacement}; + GenericPointer result{replacement}; std::copy(new_begin, this->data.cend(), std::back_inserter(result.data)); return result; } @@ -376,8 +499,8 @@ class GenericPointer { /// /// If the JSON Pointer is not relative to the base, a copy of the original /// input pointer is returned. - auto resolve_from(const GenericPointer &base) const - -> GenericPointer { + auto resolve_from(const GenericPointer &base) const + -> GenericPointer { typename Container::size_type index{0}; while (index < base.size()) { if (index >= this->size() || base.data[index] != this->data[index]) { @@ -390,21 +513,21 @@ class GenericPointer { // Make a pointer from the remaining tokens auto new_begin{this->data.cbegin()}; std::advance(new_begin, index); - GenericPointer result; + GenericPointer result; std::copy(new_begin, this->data.cend(), std::back_inserter(result.data)); return result; } /// Compare JSON Pointer instances - auto operator==(const GenericPointer &other) - const noexcept -> bool { + auto operator==(const GenericPointer &other) const noexcept + -> bool { return this->data == other.data; } /// Overload to support ordering of JSON Pointers. Typically for sorting /// reasons. - auto operator<(const GenericPointer &other) - const noexcept -> bool { + auto operator<(const GenericPointer &other) const noexcept + -> bool { return this->data < other.data; } diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_subpointer_walker.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_subpointer_walker.h index 5a812aa3..8b953d9a 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_subpointer_walker.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_subpointer_walker.h @@ -10,13 +10,11 @@ namespace sourcemeta::jsontoolkit { /// @ingroup jsonpointer /// A forward iterator to traverse all subpointers of a given JSON Pointer. -template typename Allocator> -class GenericSubPointerIterator { +template class GenericSubPointerIterator { public: using iterator_category = std::forward_iterator_tag; using difference_type = std::ptrdiff_t; - using value_type = GenericPointer; + using value_type = PointerT; using pointer = value_type *; using reference = value_type &; @@ -53,13 +51,10 @@ class GenericSubPointerIterator { /// @ingroup jsonpointer /// A walker to traverse all subpointers of a given JSON Pointer. -template typename Allocator> -class GenericSubPointerWalker { +template class GenericSubPointerWalker { public: - using const_iterator = GenericSubPointerIterator; - GenericSubPointerWalker(const typename const_iterator::value_type &pointer) - : data{pointer} {} + using const_iterator = GenericSubPointerIterator; + GenericSubPointerWalker(const PointerT &pointer) : data{pointer} {} const_iterator begin() { return {&this->data}; } const_iterator end() { return {nullptr}; } @@ -67,7 +62,7 @@ class GenericSubPointerWalker { const_iterator cend() { return {nullptr}; } private: - typename const_iterator::value_type data; + PointerT data; }; } // namespace sourcemeta::jsontoolkit diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_token.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_token.h index 775feb98..b763eb0c 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_token.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_token.h @@ -10,12 +10,10 @@ namespace sourcemeta::jsontoolkit { /// @ingroup jsonpointer -template typename Allocator> -class GenericToken { +template class GenericToken { public: using Value = JSON; - using Property = typename Value::String; + using Property = PropertyT; using Index = typename Value::Array::size_type; /// This constructor creates an JSON Pointer token from a string. For @@ -39,7 +37,7 @@ class GenericToken { /// /// const sourcemeta::jsontoolkit::Pointer::Token token{"foo"}; /// ``` - GenericToken(const CharT *const property) + GenericToken(const JSON::Char *const property) : data{std::in_place_type, property} {} /// This constructor creates an JSON Pointer token from a character. For @@ -51,7 +49,7 @@ class GenericToken { /// /// const sourcemeta::jsontoolkit::Pointer::Token token{'a'}; /// ``` - GenericToken(const CharT character) + GenericToken(const JSON::Char character) : data{std::in_place_type, Property{character}} {} /// This constructor creates an JSON Pointer token from an item index. For @@ -146,9 +144,13 @@ class GenericToken { /// assert(token.is_property()); /// assert(token.to_property() == "foo"); /// ``` - [[nodiscard]] auto to_property() const noexcept -> const Property & { + [[nodiscard]] auto to_property() const noexcept -> const auto & { assert(this->is_property()); - return std::get(this->data); + if constexpr (requires { std::get(this->data).get(); }) { + return std::get(this->data).get(); + } else { + return std::get(this->data); + } } /// Get the underlying value of a JSON Pointer object property token @@ -162,9 +164,13 @@ class GenericToken { /// assert(token.is_property()); /// assert(token.to_property() == "foo"); /// ``` - auto to_property() noexcept -> Property & { + auto to_property() noexcept -> auto & { assert(this->is_property()); - return std::get(this->data); + if constexpr (requires { std::get(this->data).get(); }) { + return std::get(this->data).get(); + } else { + return std::get(this->data); + } } /// Get the underlying value of a JSON Pointer array index token @@ -208,16 +214,26 @@ class GenericToken { } /// Compare JSON Pointer tokens - auto operator==(const GenericToken &other) - const noexcept -> bool { - return this->data == other.data; + auto operator==(const GenericToken &other) const noexcept -> bool { + if (this->data.index() != other.data.index()) { + return false; + } + if (this->is_property()) { + return this->to_property() == other.to_property(); + } + return this->to_index() == other.to_index(); } /// Overload to support ordering of JSON Pointer token. Typically for sorting /// reasons. - auto operator<(const GenericToken &other) - const noexcept -> bool { - return this->data < other.data; + auto operator<(const GenericToken &other) const noexcept -> bool { + if (this->data.index() != other.data.index()) { + return this->data.index() < other.data.index(); + } + if (this->is_property()) { + return this->to_property() < other.to_property(); + } + return this->to_index() < other.to_index(); } private: diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_walker.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_walker.h index 17e141d6..08da9061 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_walker.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_walker.h @@ -9,13 +9,11 @@ namespace sourcemeta::jsontoolkit { /// @ingroup jsonpointer -/// A walker to get every JSON Pointer in a JSON document -template typename Allocator> -class GenericPointerWalker { +/// A walker to get every JSON Pointer in a JSON document. Note that no specific +/// ordering is guaranteed. If you expect any ordering, sort afterwards. +template class GenericPointerWalker { private: - using internal = - typename std::vector>; + using internal = typename std::vector; public: GenericPointerWalker(const JSON &document) { this->walk(document, {}); } @@ -27,18 +25,17 @@ class GenericPointerWalker { auto cend() const -> const_iterator { return this->pointers.cend(); }; private: - auto walk(const JSON &document, - const GenericPointer &pointer) -> void { + auto walk(const JSON &document, const PointerT &pointer) -> void { this->pointers.push_back(pointer); if (document.is_array()) { for (std::size_t index = 0; index < document.size(); index++) { - GenericPointer subpointer{pointer}; + PointerT subpointer{pointer}; subpointer.emplace_back(index); this->walk(document.at(index), subpointer); } } else if (document.is_object()) { for (const auto &pair : document.as_object()) { - GenericPointer subpointer{pointer}; + PointerT subpointer{pointer}; subpointer.emplace_back(pair.first); this->walk(pair.second, subpointer); } diff --git a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc index 3780004b..0d3ccbba 100644 --- a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc +++ b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc @@ -13,19 +13,14 @@ #include // std::move namespace { -template