yeptris 0.6.59.1-aarch64-linux → 0.6.60.1-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/ext/yeptris_native/version.h +2 -2
  3. data/lib/yeptris/3.3/native.so +0 -0
  4. data/lib/yeptris/3.4/native.so +0 -0
  5. data/lib/yeptris/4.0/native.so +0 -0
  6. data/lib/yeptris/ffi.rb +19 -0
  7. data/lib/yeptris/yaml.rb +61 -0
  8. data/lib/yeptris.rb +1 -1
  9. data/libyeptris.so +0 -0
  10. data/vendor/libyeptris/CMakeLists.txt +1 -1
  11. data/vendor/libyeptris/src/CMakeLists.txt +7 -0
  12. data/vendor/libyeptris/src/include/yeptris/emit.h +30 -2
  13. data/vendor/libyeptris/src/include/yeptris/parse.h +52 -0
  14. data/vendor/libyeptris/src/include/yeptris/resolve.h +8 -0
  15. data/vendor/libyeptris/src/include/yeptris/validate.h +222 -21
  16. data/vendor/libyeptris/src/include/yeptris/values.h +8 -2
  17. data/vendor/libyeptris/src/yeptris/cbor/cbor.h +10 -0
  18. data/vendor/libyeptris/src/yeptris/cbor/decode.c +5 -7
  19. data/vendor/libyeptris/src/yeptris/cbor/encode.c +9 -9
  20. data/vendor/libyeptris/src/yeptris/cbor/from_marshal.c +1 -1
  21. data/vendor/libyeptris/src/yeptris/common/error.h +2 -0
  22. data/vendor/libyeptris/src/yeptris/doc.h +20 -0
  23. data/vendor/libyeptris/src/yeptris/emit/emit.c +85 -13
  24. data/vendor/libyeptris/src/yeptris/emit/writer.c +158 -642
  25. data/vendor/libyeptris/src/yeptris/emit/writer.h +15 -1
  26. data/vendor/libyeptris/src/yeptris/events/push.c +28 -3
  27. data/vendor/libyeptris/src/yeptris/events/values.c +24 -0
  28. data/vendor/libyeptris/src/yeptris/marshal/to_json.c +650 -0
  29. data/vendor/libyeptris/src/yeptris/parse/engine.c +121 -40
  30. data/vendor/libyeptris/src/yeptris/parse/stream_iter.c +326 -0
  31. data/vendor/libyeptris/src/yeptris/parse.c +173 -43
  32. data/vendor/libyeptris/src/yeptris/resolve/compat11.c +1 -1
  33. data/vendor/libyeptris/src/yeptris/resolve/core12.c +11 -3
  34. data/vendor/libyeptris/src/yeptris/resolve/full12.c +201 -0
  35. data/vendor/libyeptris/src/yeptris/resolve/json12.c +12 -6
  36. data/vendor/libyeptris/src/yeptris/resolve/resolver.h +20 -0
  37. data/vendor/libyeptris/src/yeptris/validate/compile.c +39 -3
  38. data/vendor/libyeptris/src/yeptris/validate/internal.h +223 -7
  39. data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +645 -115
  40. data/vendor/libyeptris/src/yeptris/validate/output.c +246 -0
  41. data/vendor/libyeptris/src/yeptris/validate/regexp.c +623 -157
  42. data/vendor/libyeptris/src/yeptris/validate/registry.c +118 -0
  43. data/vendor/libyeptris/src/yeptris/validate/resources.c +601 -0
  44. data/vendor/libyeptris/src/yeptris/validate/stream.c +1155 -0
  45. data/vendor/libyeptris/src/yeptris/validate/validate.c +288 -145
  46. metadata +8 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9463f702df154adbe3e51d077e32a469047c525900e969a5ee0b73c2f7508f1
4
- data.tar.gz: 2bc87ee70675681914603b2017f8dca8f7a0b7aee2beae468d268e7c5d731238
3
+ metadata.gz: ee77fd51891c7d933f22d71cfebbdfb83a038422ef407625efc71ccbbaf4480a
4
+ data.tar.gz: e420af3ccf3b7cfa54da29d310c09c004e4d11dcf45f8814e2d286fb9b728318
5
5
  SHA512:
6
- metadata.gz: e520864ea28ebe73dc1abc5d0ce58f9c5026d3b378f47f1ea1824acc652b2f5dc44a2d9c5e1acf235d6dec5b5966b958635f82f32a3ffc72c76a15322de02841
7
- data.tar.gz: d1373ff525f01732eb6664a3875c944e49ad4ced6d542aa2b8ae01b3db6056a9cd7d504eb3ddf32186dc0927f86900eba86414f3b2960c0a0f53a0ebf6a0cda3
6
+ metadata.gz: ba893a5b1d87c8f40fb9a6331c3fe887fe41023a7f14ea2131638e3d9eaa6fffdb4e32c17631b68b5e205566b2f9942fd78d22701dafc464ef4fde5f9aa8b64d
7
+ data.tar.gz: d45bc687e00f72a52d7f27169d6ffb36416601b364c2f76a6de0e736910667b5ab55809fc16e75ffce38c26eada4bedea07dd52c334abd33c4bf67ade792eea9
@@ -11,8 +11,8 @@
11
11
 
12
12
  #define YEPTRIS_VERSION_MAJOR 0
13
13
  #define YEPTRIS_VERSION_MINOR 6
14
- #define YEPTRIS_VERSION_PATCH 59
15
- #define YEPTRIS_VERSION_STRING "0.6.59"
14
+ #define YEPTRIS_VERSION_PATCH 60
15
+ #define YEPTRIS_VERSION_STRING "0.6.60"
16
16
 
17
17
  #ifdef __cplusplus
18
18
  extern "C" {
Binary file
Binary file
Binary file
data/lib/yeptris/ffi.rb CHANGED
@@ -56,6 +56,7 @@ module Yeptris
56
56
 
57
57
 
58
58
  typedef :pointer, :yeptris_document
59
+ typedef :pointer, :yeptris_stream_iter
59
60
  typedef :pointer, :yeptris_node
60
61
  typedef :pointer, :yeptris_status_out
61
62
  typedef :int, :yeptris_status
@@ -87,6 +88,24 @@ module Yeptris
87
88
  %i[pointer size_t pointer yeptris_status_out], :yeptris_document
88
89
  attach_function :yeptris_parse_json, %i[pointer size_t yeptris_status_out], :yeptris_document
89
90
 
91
+ # The tolerant per-document stream iterator (engine #921): frames
92
+ # documents scanner-accurately, reports failures per document (the
93
+ # failed run's line/col ARE that document's own), resyncs to the
94
+ # next line-start marker. On an engine older than the iterator the
95
+ # attach stubs above raise the stale-engine message at USE
96
+ # (each_document's caller sees it directly — no separate gate
97
+ # constant, the @missing law covers it).
98
+ attach_function :yeptris_stream_iter_new, %i[pointer size_t pointer], :yeptris_stream_iter
99
+ attach_function :yeptris_stream_iter_next,
100
+ %i[yeptris_stream_iter pointer pointer], :yeptris_status
101
+ attach_function :yeptris_stream_iter_status,
102
+ %i[yeptris_stream_iter pointer pointer], :yeptris_status
103
+ attach_function :yeptris_stream_iter_free, [:yeptris_stream_iter], :void
104
+ # the capability probe (evaluated AFTER the attaches above — the
105
+ # CBOR.available? pattern): specs and hosts gate on it; at USE a
106
+ # stale engine still raises the stub's message
107
+ STREAM_ITER = !(@missing ||= []).include?(:yeptris_stream_iter_new)
108
+
90
109
  # The JSON tape (TODO.restructure/85; the JSON surface's load
91
110
  # engine — issue #81): ONE call, three bulk columns, spans borrow
92
111
  # the caller's string (no arena copy, no second validating parse).
data/lib/yeptris/yaml.rb CHANGED
@@ -197,6 +197,67 @@ module Yeptris
197
197
  results
198
198
  end
199
199
 
200
+ # The engine capability behind each_document (the CBOR.available?
201
+ # pattern): false when the loaded libyeptris predates the stream
202
+ # iterator — the walk itself raises the stale-engine message at use.
203
+ def each_document?
204
+ FFI::STREAM_ITER
205
+ end
206
+
207
+ # The tolerant per-document walk (engine #921): every document of
208
+ # the stream reaches the block IN STREAM ORDER — a valid document
209
+ # yields (document, nil); a failed document yields (nil, error)
210
+ # and the walk CONTINUES at the next line-start marker, so the
211
+ # valid documents after a bad one are kept (load_stream with
212
+ # errors: :raise would drop the whole stream; :collect recovers by
213
+ # a regex slice split, which quoted scalars spanning marker-looking
214
+ # lines can defeat — the engine iterator frames scanner-accurately,
215
+ # so those documents fail as ONE unit instead of splitting).
216
+ # Errors carry the failed document's own line/column and its
217
+ # stream index. Without a block, an Enumerator over the pairs.
218
+ def each_document(yaml, schema: :compat_11, max_depth: 0, &block)
219
+ return enum_for(:each_document, yaml, schema: schema, max_depth: max_depth) unless block
220
+
221
+ yaml = Yeptris.read_input(yaml)
222
+ yaml = yaml.to_s
223
+ opts = FFI::ParseOptions.new
224
+ opts[:schema] = schema == :compat_11 ? FFI::SCHEMA_11_COMPAT : FFI::SCHEMA_12_CORE
225
+ opts[:max_depth] = max_depth
226
+ it = FFI.yeptris_stream_iter_new(yaml, yaml.bytesize, opts)
227
+ doc_p = ::FFI::MemoryPointer.new(:pointer)
228
+ idx_p = ::FFI::MemoryPointer.new(:size_t)
229
+ line_p = ::FFI::MemoryPointer.new(:uint32)
230
+ col_p = ::FFI::MemoryPointer.new(:uint32)
231
+ begin
232
+ loop do
233
+ st = FFI.yeptris_stream_iter_next(it, doc_p, idx_p)
234
+ doc_ptr = doc_p.read_pointer
235
+ break if st == FFI::OK && doc_ptr.null? # the stream is over
236
+
237
+ index = FFI.read_c_size_t(idx_p)
238
+ if st == FFI::OK
239
+ doc = Document.without_finalizer(doc_ptr)
240
+ begin
241
+ yield doc.to_ruby, nil
242
+ ensure
243
+ doc.free
244
+ end
245
+ else
246
+ FFI.yeptris_stream_iter_status(it, line_p, col_p)
247
+ reason = st == FFI::ERROR_DEPTH ? "depth limit exceeded" : "parse error"
248
+ yield nil,
249
+ ParseError.new(
250
+ "stream document #{index}: #{reason} at line #{line_p.read_uint32}, " \
251
+ "column #{col_p.read_uint32}"
252
+ )
253
+ break if st == FFI::ERROR_MEMORY || st == FFI::ERROR_ENCODING
254
+ end
255
+ end
256
+ ensure
257
+ FFI.yeptris_stream_iter_free(it)
258
+ end
259
+ end
260
+
200
261
  # The Marshal fast path when the loaded libyeptris has it (>= 0.1.11
201
262
  # era builds), falling back to the columnar drain and finally the
202
263
  # record drain — one code path, the fastest the library offers.
data/lib/yeptris.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Yeptris
4
4
  # The gem's version lives in the parent namespace's file — the last
5
5
  # internal require (yeptris/version) retired with it.
6
- VERSION = "0.6.59.1".freeze
6
+ VERSION = "0.6.60.1".freeze
7
7
 
8
8
  # Informational notices are OPT-IN (yeptris-ruby#217): consumers
9
9
  # embed yeptris inside CLIs that assert clean stderr, and a
data/libyeptris.so CHANGED
Binary file
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.59
6
+ VERSION 0.6.60
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -29,6 +29,7 @@ set(YEPTRIS_SOURCES
29
29
  yeptris/dom/mapindex.c
30
30
  yeptris/dom/mutate.c
31
31
  yeptris/parse.c
32
+ yeptris/parse/stream_iter.c
32
33
  yeptris/build.c
33
34
  yeptris/events/capture.c
34
35
  yeptris/events/push.c
@@ -40,17 +41,23 @@ set(YEPTRIS_SOURCES
40
41
  yeptris/resolve/core12.c
41
42
  yeptris/resolve/compat11.c
42
43
  yeptris/resolve/json12.c
44
+ yeptris/resolve/full12.c
43
45
  yeptris/parse/numbers.c
44
46
  yeptris/events/values.c
45
47
  yeptris/schema.c
46
48
  yeptris/validate/regexp.c
47
49
  yeptris/validate/validate.c
48
50
  yeptris/validate/compile.c
51
+ yeptris/validate/resources.c
49
52
  yeptris/validate/jsonschema.c
53
+ yeptris/validate/registry.c
54
+ yeptris/validate/output.c
55
+ yeptris/validate/stream.c
50
56
  yeptris/tape.c
51
57
  yeptris/plan.c
52
58
 
53
59
  yeptris/marshal.c
60
+ yeptris/marshal/to_json.c
54
61
  yeptris/visit/json_visit.c
55
62
  yeptris/visit/yaml_visit.c
56
63
  yeptris/visit/dom_visit.c
@@ -24,7 +24,14 @@ extern "C" {
24
24
  #endif
25
25
 
26
26
  /* Emission options (13B). Versioned by size: initialize with
27
- * {sizeof(yeptris_emit_options)} so future fields stay optional. */
27
+ * {sizeof(yeptris_emit_options)} so future fields stay optional.
28
+ *
29
+ * Deliberately omitted: a flowLevel knob (libyaml's best_width +
30
+ * default_flow_style interplay). The engine's style chooser is
31
+ * recorded-style driven — flow is whatever the document recorded
32
+ * (plus the fixed canonical/JSON forms) — so there is no "switch to
33
+ * flow past level N" decision to expose; revisit only if a binding
34
+ * face actually needs PyYAML's default_flow_style mapping. */
28
35
  typedef struct yeptris_emit_options {
29
36
  uint32_t size; /* sizeof(yeptris_emit_options) */
30
37
  int canonical; /* fixed form: flow collections, quoted strings,
@@ -32,13 +39,34 @@ typedef struct yeptris_emit_options {
32
39
  * output re-serializes byte-identically */
33
40
  int best_width; /* 0 = library default (80): flow collections
34
41
  * wrap after a value when the current line
35
- * exceeds the width (13B) */
42
+ * exceeds the width (13B); with block_fold
43
+ * it is also the fold column for block
44
+ * scalars (#903) */
36
45
  int explicit_doc_start; /* nonzero: every document carries the
37
46
  * leading --- marker (Psych/libyaml's
38
47
  * implicit=false parity). Default 0: a
39
48
  * single document is emitted bare. Only
40
49
  * read when the caller's struct is large
41
50
  * enough (size-versioned). */
51
+ int best_indent; /* (#903) block indentation per level: the
52
+ * content column of a nested block mapping
53
+ * and the "- " item level. Out of range
54
+ * (< 2 or > 9, libyaml's clamp) or 0 = the
55
+ * library default 2. Only read when the
56
+ * caller's struct is large enough. */
57
+ int block_fold; /* (#903) nonzero: plain and single-quoted
58
+ * scalars in block context fold at
59
+ * best_width the way libyaml/Psych/PyYAML
60
+ * do — at an interior single space past the
61
+ * width the break replaces the space and
62
+ * the continuation re-indents to
63
+ * parent_col + best_indent. Default 0:
64
+ * block output is width-invariant (byte-
65
+ * identical with releases before the
66
+ * option). Keys and flow members never
67
+ * fold; canonical/JSON keep their fixed
68
+ * forms. Only read when the caller's
69
+ * struct is large enough. */
42
70
  } yeptris_emit_options;
43
71
 
44
72
  /* Options-aware entries; opts == NULL selects the fidelity mode
@@ -41,6 +41,58 @@ YEPTRIS_API YeptrisDocument yeptris_parse_ex(const char* buf, size_t len,
41
41
  const YeptrisParseOptions* opts,
42
42
  YeptrisStatus* status);
43
43
 
44
+ /* ---- tolerant per-document stream iteration (#921) ----------------------
45
+ *
46
+ * load_stream parses the whole stream and aborts on the first bad
47
+ * document; this iterator frames documents engine-side (the abort-at-
48
+ * DOCUMENT_END run — scanner-accurate: a quoted scalar spanning a
49
+ * marker-looking line never splits) and reports failures PER DOCUMENT:
50
+ * a parse-class error surfaces as the step's status with NULL *out, the
51
+ * error channel carrying the failed document's own line/col (each run
52
+ * starts at the frame), and the iterator resynced to the next line-
53
+ * start marker — the host keeps calling and keeps the valid documents.
54
+ *
55
+ * yeptris_stream_iter* it = yeptris_stream_iter_new(buf, len, &opts);
56
+ * YeptrisDocument doc;
57
+ * size_t index;
58
+ * YeptrisStatus st;
59
+ * while ((st = yeptris_stream_iter_next(it, &doc, &index)) == YEPTRIS_OK && doc != NULL) {
60
+ * // doc is document `index` of the stream; free it with
61
+ * // yeptris_document_free
62
+ * }
63
+ * // st == OK && doc == NULL: the stream is over; st != OK: document
64
+ * // `index` failed (per-document line/col via
65
+ * // yeptris_stream_iter_status), keep iterating.
66
+ *
67
+ * Memory: the caller owns each *out (yeptris_document_free). The
68
+ * iterator borrows `buf` until free. Each document parses twice (the
69
+ * boundary run + the slice parse) — the tolerance contract is the
70
+ * point; a single-pass upgrade rides the same seam when needed.
71
+ */
72
+ typedef struct yeptris_stream_iter yeptris_stream_iter;
73
+
74
+ /* The iterator borrows `buf` until free; `opts` is borrowed (may be
75
+ * NULL: the 12_CORE defaults). NULL on arg/alloc failure. */
76
+ YEPTRIS_API yeptris_stream_iter* yeptris_stream_iter_new(const char* buf, size_t len,
77
+ const YeptrisParseOptions* opts);
78
+
79
+ /* The next document: OK + *out = the document (the caller frees it);
80
+ * a parse/DEPTH-class DOCUMENT failure: that status + *out = NULL +
81
+ * the error channel set for THAT document — the iterator has already
82
+ * resynced, keep calling; OK + *out = NULL: the stream is over;
83
+ * MEMORY/ENCODING: stream-fatal, the iterator is done. *index (may be
84
+ * NULL) is the 0-based stream document index this step consumed
85
+ * (failures consume an index too — the yield order is the stream's). */
86
+ YEPTRIS_API YeptrisStatus yeptris_stream_iter_next(yeptris_stream_iter* it, YeptrisDocument* out,
87
+ size_t* index);
88
+
89
+ /* The LAST step's per-document verdict and its 1-based line/col
90
+ * (0/0 when the last step was a success or the stream end). */
91
+ YEPTRIS_API YeptrisStatus yeptris_stream_iter_status(const yeptris_stream_iter* it, uint32_t* line,
92
+ uint32_t* col);
93
+
94
+ YEPTRIS_API void yeptris_stream_iter_free(yeptris_stream_iter* it);
95
+
44
96
  #ifdef __cplusplus
45
97
  }
46
98
  #endif
@@ -46,6 +46,14 @@ typedef enum {
46
46
  YEPTRIS_SCHEMA_11_COMPAT, /* libyaml/Psych implicit typing */
47
47
  YEPTRIS_SCHEMA_12_JSON, /* spec 10.2: JSON productions; plain
48
48
  * scalars matching none are errors (#882) */
49
+ YEPTRIS_SCHEMA_12_FULL, /* the full 1.2 family (#906 slice 3):
50
+ * core typing + RFC3339-era timestamps
51
+ * (spec 2.22's four forms; the yaml.org
52
+ * type repository carried forward) and
53
+ * the merge/value words; !!binary rides
54
+ * as passthrough. A validation family:
55
+ * explicit !!timestamp content must
56
+ * re-resolve through the classifier. */
49
57
  } YeptrisSchema;
50
58
 
51
59
  /* Canonical tag URI for a core id ("tag:yaml.org,2002:str"); NULL for
@@ -7,21 +7,52 @@
7
7
  * consistency (spec 10.3 resolution) and mapping-key uniqueness
8
8
  * (spec 3.2.1.3: keys compare by canonical form — 0o13 and 0xB
9
9
  * are the same key). Under YEPTRIS_SCHEMA_12_JSON the spec 10.2
10
- * family applies: every PLAIN scalar must match a JSON production
11
- * (the table's `*` -> Error row), mapping keys must be strings,
12
- * non-JSON tags are rejected; core checks still run.
10
+ * family applies: implicit resolution uses ONLY the JSON
11
+ * productions (the core extensions — 0o/0x radix, "+12.3",
12
+ * "True"/"~" casings — stay strings, per example 10.8's Invalid
13
+ * list), mapping keys must be strings (a key resolving to a
14
+ * non-string production is KEY_NOT_STRING; a no-production plain
15
+ * key IS a string, per example 10.8), explicit non-JSON tags are
16
+ * rejected; core checks still run. Under YEPTRIS_SCHEMA_12_FULL
17
+ * the spec 10.4 "other schema" family applies — the core schema
18
+ * "extended with additional tag resolution rules": untagged plains
19
+ * also resolve RFC3339-era timestamps (spec example 2.22's
20
+ * canonical/iso8601/spaced/date forms, classified by PyYAML's
21
+ * timestamp regex as the practice oracle) and the merge/value
22
+ * words, while explicit !!timestamp content must re-resolve
23
+ * through the same classifier (TAG_MISMATCH otherwise). !!binary
24
+ * rides as passthrough (base64 shaping is a construction concern);
25
+ * core checks still run. The 1.1 numeric quirks (underscores,
26
+ * sexagesimal, leading-0 octal, y/n booleans) stay with
27
+ * 11_COMPAT — full is spec-refereed, not psych-refereed.
13
28
  *
14
29
  * 2. JSON SCHEMA validation (`yeptris_json_schema_validate`): the
15
30
  * schema arrives as bytes (parsed with the same engine), the
16
31
  * target against it — draft 2020-12 keyword subset (see the entry
17
32
  * comment). Unknown keywords are ignored, per 2020-12.
18
33
  *
34
+ * The COMPILED-schema economy (`yeptris_schema_*`) retains one parsed
35
+ * schema for many instance streams; the TAG-DRIVEN registry
36
+ * (`yeptris_schema_registry_*`, #906 slice 4) binds compiled schemas
37
+ * to user tags over the family walk (`yeptris_validate_ex`). The
38
+ * report converts to the published JSON-Schema output shape
39
+ * (`yeptris_validate_report_write`, #906 slice 5).
40
+ *
19
41
  * Every diagnostic carries a JSON-pointer instance path ("/a/b/0" —
20
42
  * ~0/~1 escaped), a YEPTRIS_DIAG_* code, a bounded expected/got
21
43
  * detail, and the offending node's 1-based line/column (0 = unknown:
22
44
  * scalars whose bytes the DOM owns in its arena have no input
23
45
  * position; the path still identifies them exactly).
24
46
  *
47
+ * STREAMS (#885 item 4): a validated stream is addressed as an array
48
+ * of documents. When the input holds MORE THAN ONE document, every
49
+ * diagnostic path is prefixed with its document's index segment —
50
+ * "/1/a/b" is a/b of document 1 (0-based), so diagnostics name their
51
+ * document even when the miss is unpositioned (an empty document's
52
+ * Error-row scalar has no line/col). Line/col/offset stay
53
+ * input-absolute. Streams with a single document keep the bare
54
+ * instance pointers — existing consumers' paths are unchanged.
55
+ *
25
56
  * Results are WHOLE REPORTS: one call, no per-diagnostic callback —
26
57
  * the caller drains the flat array and frees the report once.
27
58
  */
@@ -84,6 +115,9 @@ typedef enum {
84
115
  YEPTRIS_DIAG_MIN_CONTAINS = 31,
85
116
  YEPTRIS_DIAG_MAX_CONTAINS = 32,
86
117
  YEPTRIS_DIAG_DEPENDENT_REQUIRED = 33,
118
+ /* the 2020-12 unevaluated* annotation model (the sota sweep) */
119
+ YEPTRIS_DIAG_UNEVALUATED_PROPERTY = 34, /* no sibling/applicator evaluated it */
120
+ YEPTRIS_DIAG_UNEVALUATED_ITEM = 35,
87
121
  } YeptrisDiagCode;
88
122
 
89
123
  /* Bounded diagnostic text; always NUL-terminated (over-long paths and
@@ -118,7 +152,14 @@ YEPTRIS_API void yeptris_validate_report_free(yeptris_validate_report* report);
118
152
 
119
153
  /* Parses `buf` (the same front door as yeptris_parse_ex: encodings,
120
154
  * BOM, NEL normalization) and validates every document of the stream
121
- * against `schema`. Paths are root-relative per document.
155
+ * against `schema` — the strict lanes: explicit-tag consistency,
156
+ * canonical-form key uniqueness, (12_JSON) string keys over the JSON
157
+ * productions, and (12_FULL) timestamp content against the full
158
+ * family's classifier. Paths are root-relative per document; a
159
+ * multi-document stream prefixes them with the document index (see
160
+ * STREAMS above), so diagnostics name their document. The parse's own
161
+ * guards compose: a document past the engine's depth cap fails the
162
+ * parse (YEPTRIS_ERROR_DEPTH) before any validation runs.
122
163
  *
123
164
  * Status: YEPTRIS_OK (valid; *out is a report — possibly with zero
124
165
  * diagnostics for an empty stream); YEPTRIS_ERROR_SCHEMA (invalid:
@@ -135,22 +176,67 @@ YEPTRIS_API YeptrisStatus yeptris_validate(const char* buf, size_t len, YeptrisS
135
176
  YEPTRIS_API YeptrisStatus yeptris_validate_document(YeptrisDocument doc, YeptrisSchema schema,
136
177
  yeptris_validate_report** out);
137
178
 
138
- /* JSON Schema draft 2020-12 subset (#882 slice 2; #885 item 2). `schema`
139
- * is parsed with the same engine (JSON is YAML flow); document 0 of the
140
- * stream is the schema. Keyword subset: type, enum, const, pattern,
141
- * minimum/maximum/exclusiveMinimum/exclusiveMaximum (numeric, 2020-12
142
- * form), multipleOf (libm-free quotient test), minLength/maxLength
143
- * (code points), minItems/maxItems, uniqueItems, contains (+ the
144
- * minContains/maxContains qualifiers), items (+ prefixItems; the
145
- * draft-07 array form rides as prefixItems), properties, required,
146
- * patternProperties, propertyNames, minProperties/maxProperties,
147
- * additionalProperties (bool or schema; excludes patternProperties
148
- * matches), dependentRequired, dependentSchemas, if/then/else,
149
- * allOf/anyOf/oneOf/not, $ref (LOCAL "#/..." pointers only — $defs and
150
- * definitions both resolve). Boolean schemas (`true`/`false`) apply.
151
- * Unknown keywords and known keywords with unreadable values are
152
- * ignored (2020-12). Instance types ride the core-schema resolution
153
- * (the YAML->JSON data model): "y" is boolean, 1.0 is integer.
179
+ /* The EVENT-ROUTE twin of yeptris_validate (#902): the same parse
180
+ * (front-end, depth/encoding guards, statuses — the DOM route's
181
+ * sink-entry seam) feeding a streaming validator state machine. The
182
+ * family lanes ride the event walk itself; NO node tree is ever
183
+ * built. Everything observable is the DOM route's, by construction
184
+ * and by the differential gate:
185
+ *
186
+ * - diagnostics: the same code rail (0-35 after the sweep), the same report shape and
187
+ * cap, the same JSON-pointer paths and the stream face's
188
+ * per-document attribution — emitted in the DOM walk's order (a
189
+ * mapping's own tag check, its duplicate-key misses, then its
190
+ * pairs' content);
191
+ * - positions: the DOM route's region law — a diagnostic sits at the
192
+ * node's input-borrowed value bytes (0/0 when the engine pooled
193
+ * them), so line/col/offset match bit for bit;
194
+ * - statuses: OK / SCHEMA / PARSE / DEPTH / ENCODING / MEMORY / ARG,
195
+ * byte-identical argument gates (NULL with length, 11_COMPAT).
196
+ * All three families (12_CORE / 12_JSON / 12_FULL) ride both
197
+ * routes with identical verdicts.
198
+ *
199
+ * The compiled JSON Schema lane (yeptris_schema_validate) keeps the
200
+ * DOM route: its evaluator needs random access (uniqueItems, contains,
201
+ * required); a streaming twin waits for #901's annotation-model seam.
202
+ *
203
+ * Memory: the caller owns *out; free it with
204
+ * yeptris_validate_report_free. */
205
+ YEPTRIS_API YeptrisStatus yeptris_validate_stream(const char* buf, size_t len, YeptrisSchema schema,
206
+ yeptris_validate_report** out);
207
+
208
+ /* JSON Schema draft 2020-12 subset (#882 slice 2; #885 item 2; the
209
+ * 2020-12 sota sweep). `schema` is parsed with the same engine (JSON
210
+ * is YAML flow); document 0 of the stream is the schema. Keyword
211
+ * subset: type, enum, const, pattern, minimum, maximum,
212
+ * exclusiveMinimum, exclusiveMaximum (numeric, 2020-12 form),
213
+ * multipleOf (libm-free quotient test),
214
+ * minLength/maxLength (code points), minItems/maxItems, uniqueItems,
215
+ * contains (+ the minContains/maxContains qualifiers), items
216
+ * (+ prefixItems; the draft-07 array form rides as prefixItems),
217
+ * properties, required, patternProperties, propertyNames,
218
+ * minProperties/maxProperties, additionalProperties (bool or schema;
219
+ * excludes patternProperties matches), dependentRequired,
220
+ * dependentSchemas, if/then/else, allOf/anyOf/oneOf/not,
221
+ * unevaluatedProperties/unevaluatedItems (the 2020-12 annotation
222
+ * model: evaluations collect upward through the in-place applicators,
223
+ * a failed branch annotates nothing, and nothing crosses an
224
+ * instance-location boundary), $ref, $dynamicRef, boolean schemas.
225
+ * REFERENCES: $id base-URI composition (RFC 3986 path merge),
226
+ * $anchor/$dynamicAnchor plain-name fragments scoped to their nearest
227
+ * $id resource, JSON Pointer fragments with the percent layer
228
+ * (%25 -> '%', then ~0/~1), and $dynamicRef's runtime dynamic scope
229
+ * (the OUTERMOST schema resource in scope defining the anchor). No
230
+ * network: a reference resolves only against the schema's own
231
+ * identifier space — an external target (the metaschema, an HTTP
232
+ * fixture) is a DIAG_REF, never a fetch. Boolean schemas
233
+ * (`true`/`false`) apply. Unknown keywords and known keywords with
234
+ * unreadable values are ignored (2020-12). Instance types ride the
235
+ * core-schema resolution (the YAML->JSON data model): "y" is boolean,
236
+ * 1.0 is integer. The IETF WG's next-edition draft
237
+ * (draft-ietf-jsonschema-json-schema-00) carries these same keyword
238
+ * semantics — no evaluator delta; the diff record is
239
+ * docs/schema/ietf-wg-draft-delta.md.
154
240
  *
155
241
  * Status: as yeptris_validate, plus YEPTRIS_ERROR_ARG when the schema
156
242
  * bytes hold no document, and YEPTRIS_ERROR_PARSE when they do not
@@ -175,7 +261,9 @@ YEPTRIS_API YeptrisStatus yeptris_json_schema_validate(const char* schema, size_
175
261
  *
176
262
  * yeptris_schema_validate keeps yeptris_json_schema_validate's exact
177
263
  * status/report contract; diagnostics enumerate every miss across
178
- * every document of the instance stream. yeptris_schema_free releases
264
+ * every document of the instance stream — the stream face's document
265
+ * attribution applies (see STREAMS above): a multi-document stream's
266
+ * paths carry the document index. yeptris_schema_free releases
179
267
  * the handle and everything it owns in ONE call (no refcounting) —
180
268
  * afterwards the handle is DEAD: passing it anywhere is undefined,
181
269
  * exactly like a freed document. NULL is accepted (a no-op).
@@ -193,6 +281,119 @@ YEPTRIS_API YeptrisStatus yeptris_schema_validate(yeptris_schema* s, const char*
193
281
 
194
282
  YEPTRIS_API void yeptris_schema_free(yeptris_schema* s);
195
283
 
284
+ /* ---- the TAG-DRIVEN SCHEMA registry (#906 slice 4) -----------------------
285
+ *
286
+ * The dialect table's reserved slot: a compiled schema binds to a user
287
+ * tag, so a document node carrying `!shape` (its EXPLICIT tag, exactly
288
+ * the resolved bytes the DOM exposes — `!shape`, `!<tag:...>`, or a
289
+ * `%TAG`-resolved URI; `!!x` arrives as `tag:yaml.org,2002:x`) has its
290
+ * SUBTREE validated against the bound schema. Registration is the only
291
+ * extension point (OCP): the family walk reads the registry, it never
292
+ * grows schema branches.
293
+ *
294
+ * Matching is byte-exact on the tag's resolved form and fires only on
295
+ * an explicit tag — implicit (plain) typing never triggers a binding.
296
+ * An unregistered tag passes untouched: no diagnostic, no lane. The
297
+ * hook rides the DOM family walk (yeptris_validate_ex /
298
+ * yeptris_validate_document_ex): roots, sequence items and mapping
299
+ * VALUES (a tagged mapping KEY is not registry-validated — its
300
+ * instance path is undefined in the walk's model). Diagnostics from a
301
+ * bound subtree land at the tag site: the tag node's own path + the
302
+ * schema walk's instance pointers, with the offending node's
303
+ * line/col/offset; the report cap and codes are the shared rail.
304
+ * Bindings COMPOSE: a node inside a bound subtree that carries its own
305
+ * registered tag is validated by both schemas.
306
+ *
307
+ * THE REGISTRY LAW (the document-ownership law, no refcounting): the
308
+ * registry BORROWS the compiled schemas it binds — it owns nothing
309
+ * they own. A bound schema must OUTLIVE the registry: free the
310
+ * registry first (yeptris_schema_registry_free), then the schemas.
311
+ * Freeing a bound schema while its registry lives leaves the registry
312
+ * dead (a later lookup is undefined), exactly like a freed document.
313
+ * NULL is accepted everywhere a handle is (a NULL registry is simply
314
+ * no bindings; yeptris_schema_registry_free(NULL) is a no-op).
315
+ *
316
+ * Thread law: one registry per thread, like documents and schemas —
317
+ * it is mutable under register and read-only during validation;
318
+ * read-only sharing across threads (no register in flight) is safe. */
319
+ typedef struct yeptris_schema_registry yeptris_schema_registry;
320
+
321
+ /* A fresh empty registry; NULL on allocation failure. */
322
+ YEPTRIS_API yeptris_schema_registry* yeptris_schema_registry_new(void);
323
+
324
+ /* Binds `tag` (copied — nothing borrows the caller's bytes beyond the
325
+ * call) to the compiled `schema` (borrowed — see THE REGISTRY LAW).
326
+ * Status: YEPTRIS_OK; YEPTRIS_ERROR_ARG (NULL handle, an empty tag,
327
+ * or a NULL schema); YEPTRIS_ERROR_MEMORY. Re-registering a tag
328
+ * REPLACES the binding (last registration wins). */
329
+ YEPTRIS_API YeptrisStatus yeptris_schema_registry_register(yeptris_schema_registry* reg,
330
+ const char* tag, size_t tag_len,
331
+ const yeptris_schema* schema);
332
+
333
+ /* The schema bound to `tag` (exact bytes), or NULL. Memory: borrowed
334
+ * from the registry — valid until a register replaces the tag or the
335
+ * registry is freed. */
336
+ YEPTRIS_API const yeptris_schema* yeptris_schema_registry_lookup(const yeptris_schema_registry* reg,
337
+ const char* tag, size_t tag_len);
338
+
339
+ YEPTRIS_API void yeptris_schema_registry_free(yeptris_schema_registry* reg);
340
+
341
+ /* Validation options (size-versioned like yeptris_emit_options:
342
+ * initialize with {sizeof(yeptris_validate_options)} so future fields
343
+ * stay optional; NULL selects the defaults). */
344
+ typedef struct yeptris_validate_options {
345
+ uint32_t size; /* sizeof(yeptris_validate_options) */
346
+ /* The tag-driven registry (#906 slice 4): NULL = none. Only read
347
+ * when the caller's struct is large enough (size-versioned). The
348
+ * registry is borrowed for the call; see THE REGISTRY LAW above. */
349
+ const yeptris_schema_registry* registry;
350
+ } yeptris_validate_options;
351
+
352
+ /* yeptris_validate / yeptris_validate_document with options: the
353
+ * family walk plus, when a registry is attached, the tag-driven
354
+ * schema applications described above. Everything else (statuses,
355
+ * report shape, paths, positions) is the flat face's, unchanged. */
356
+ YEPTRIS_API YeptrisStatus yeptris_validate_ex(const char* buf, size_t len, YeptrisSchema schema,
357
+ const yeptris_validate_options* opts,
358
+ yeptris_validate_report** out);
359
+
360
+ YEPTRIS_API YeptrisStatus yeptris_validate_document_ex(YeptrisDocument doc, YeptrisSchema schema,
361
+ const yeptris_validate_options* opts,
362
+ yeptris_validate_report** out);
363
+
364
+ /* ---- the structured OUTPUT face (#906 slice 5) ---------------------------
365
+ *
366
+ * The report as the json-schema.org RECOMMENDED OUTPUT shape (core
367
+ * spec section 12; the meta-schema at
368
+ * https://json-schema.org/draft/2020-12/output/schema): one root
369
+ * output unit — {"valid", "keywordLocation", "instanceLocation"} and,
370
+ * when the report carries diagnostics, "errors" — with one child unit
371
+ * per diagnostic {valid, keywordLocation, instanceLocation, error}.
372
+ * This is the published BASIC form: the report is flat by the
373
+ * enumerated-misses law, so the schema-structured DETAILED/VERBOSE
374
+ * hierarchies (which need per-miss schema-side keyword paths the flat
375
+ * report does not carry) are not derivable from it. keywordLocation
376
+ * is derived from the diagnostic code — the keyword's name segment
377
+ * ("/type", "/required", ...; the family codes and the bookkeeping
378
+ * codes sit at the root pointer ""); it is a JSON Pointer, but NOT
379
+ * necessarily resolvable in the schema (the flat report carries no
380
+ * schema path). absoluteKeywordLocation is never emitted, and $ref
381
+ * sites report the root pointer, so the output always satisfies the
382
+ * published meta-schema. Strings are JSON-escaped (RFC 8259); the
383
+ * bytes are UTF-8; the transform is pure and deterministic — the same
384
+ * report always produces the same bytes.
385
+ *
386
+ * buf == NULL: returns the exact byte count needed, writes nothing.
387
+ * Otherwise writes at most cap bytes (NUL-terminated when cap
388
+ * allows) and returns the count written; when cap is too small
389
+ * nothing is written and the needed size is returned. Returns 0 on a
390
+ * NULL report (use the flat face for those — the transform never
391
+ * invents a verdict).
392
+ *
393
+ * Memory: writes into the caller's buffer; borrows nothing. */
394
+ YEPTRIS_API size_t yeptris_validate_report_write(const yeptris_validate_report* report, char* buf,
395
+ size_t cap);
396
+
196
397
  #ifdef __cplusplus
197
398
  }
198
399
  #endif
@@ -52,7 +52,11 @@ typedef struct {
52
52
  uint8_t tag_id; /* the resolver's verdict for this scalar */
53
53
  uint8_t is_key; /* the entry lands in the inner map's key slot */
54
54
  uint8_t b; /* BOOL payload; implicit-plain flag otherwise */
55
- uint32_t off; /* STR/TIMESTAMP/ALIAS/anchor bytes */
55
+ uint32_t off; /* STR/TIMESTAMP/ALIAS/anchor bytes; SEQ/MAP OPEN:
56
+ * the collection's EXPLICIT tag text ("tag:yaml.
57
+ * org,2002:set") when it carries one — off/len stay
58
+ * 0 for untagged containers, so hosts re-tag
59
+ * containers without a second drain */
56
60
  uint32_t len;
57
61
  uint64_t p; /* INT or FLOAT payload, by kind */
58
62
  } YeptrisValue;
@@ -80,7 +84,9 @@ typedef struct {
80
84
  size_t count;
81
85
  size_t arena_len;
82
86
  int64_t* payloads; /* INT or FLOAT bits, by kind */
83
- uint32_t* offs; /* STR/TIMESTAMP/ALIAS/ANCHOR bytes */
87
+ uint32_t* offs; /* STR/TIMESTAMP/ALIAS/ANCHOR bytes; SEQ/MAP OPEN:
88
+ the explicit tag text when tagged (see
89
+ YeptrisValue.off) */
84
90
  uint32_t* lens;
85
91
  uint8_t* kinds; /* YeptrisValueKind */
86
92
  uint8_t* tags; /* resolver verdict per scalar */