yeptris 0.6.18.2-arm-linux-eabihf

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/README.adoc +141 -0
  3. data/ext/build_windows_native.rb +41 -0
  4. data/ext/libyeptris/extconf.rb +57 -0
  5. data/ext/yeptris_native/cbor_ruby.c +193 -0
  6. data/ext/yeptris_native/extconf.rb +47 -0
  7. data/ext/yeptris_native/json_ruby.c +354 -0
  8. data/ext/yeptris_native/yeptris_native.c +497 -0
  9. data/lib/yeptris/cbor.rb +130 -0
  10. data/lib/yeptris/document.rb +255 -0
  11. data/lib/yeptris/ffi.rb +426 -0
  12. data/lib/yeptris/json/descriptor.rb +266 -0
  13. data/lib/yeptris/json.rb +394 -0
  14. data/lib/yeptris/materializer.rb +354 -0
  15. data/lib/yeptris/native-3.4.so +0 -0
  16. data/lib/yeptris/node.rb +453 -0
  17. data/lib/yeptris/psych/class_loader.rb +24 -0
  18. data/lib/yeptris/psych/coder_shim.rb +55 -0
  19. data/lib/yeptris/psych/drop_in.rb +63 -0
  20. data/lib/yeptris/psych/encodable.rb +14 -0
  21. data/lib/yeptris/psych/handler.rb +90 -0
  22. data/lib/yeptris/psych/parser.rb +105 -0
  23. data/lib/yeptris/psych/scalar_scanner.rb +149 -0
  24. data/lib/yeptris/psych/visitors.rb +545 -0
  25. data/lib/yeptris/psych.rb +622 -0
  26. data/lib/yeptris/schema.rb +253 -0
  27. data/lib/yeptris/valueml.rb +455 -0
  28. data/lib/yeptris/yaml/descriptor.rb +56 -0
  29. data/lib/yeptris/yaml.rb +422 -0
  30. data/lib/yeptris.rb +113 -0
  31. data/libyeptris.so +0 -0
  32. data/vendor/libyeptris/CMakeLists.txt +254 -0
  33. data/vendor/libyeptris/cmake/yeptris-config.cmake.in +5 -0
  34. data/vendor/libyeptris/cmake/yeptris.pc.in +11 -0
  35. data/vendor/libyeptris/src/CMakeLists.txt +167 -0
  36. data/vendor/libyeptris/src/include/yeptris/api.h +32 -0
  37. data/vendor/libyeptris/src/include/yeptris/cbor.h +101 -0
  38. data/vendor/libyeptris/src/include/yeptris/dom.h +190 -0
  39. data/vendor/libyeptris/src/include/yeptris/emit.h +81 -0
  40. data/vendor/libyeptris/src/include/yeptris/error.h +41 -0
  41. data/vendor/libyeptris/src/include/yeptris/events.h +157 -0
  42. data/vendor/libyeptris/src/include/yeptris/json.h +45 -0
  43. data/vendor/libyeptris/src/include/yeptris/json.hpp +258 -0
  44. data/vendor/libyeptris/src/include/yeptris/jsonc_compat.h +115 -0
  45. data/vendor/libyeptris/src/include/yeptris/marshal.h +52 -0
  46. data/vendor/libyeptris/src/include/yeptris/parse.h +48 -0
  47. data/vendor/libyeptris/src/include/yeptris/plan.h +100 -0
  48. data/vendor/libyeptris/src/include/yeptris/resolve.h +71 -0
  49. data/vendor/libyeptris/src/include/yeptris/schema.h +121 -0
  50. data/vendor/libyeptris/src/include/yeptris/tape.h +123 -0
  51. data/vendor/libyeptris/src/include/yeptris/types.h +38 -0
  52. data/vendor/libyeptris/src/include/yeptris/values.h +102 -0
  53. data/vendor/libyeptris/src/include/yeptris/version.h.in +29 -0
  54. data/vendor/libyeptris/src/include/yeptris/visit.h +77 -0
  55. data/vendor/libyeptris/src/include/yeptris/yajl_compat.h +135 -0
  56. data/vendor/libyeptris/src/include/yeptris.h +23 -0
  57. data/vendor/libyeptris/src/yeptris/build.c +376 -0
  58. data/vendor/libyeptris/src/yeptris/cbor/cbor.h +25 -0
  59. data/vendor/libyeptris/src/yeptris/cbor/decode.c +981 -0
  60. data/vendor/libyeptris/src/yeptris/cbor/encode.c +835 -0
  61. data/vendor/libyeptris/src/yeptris/cbor/sink.h +58 -0
  62. data/vendor/libyeptris/src/yeptris/common/chartype.c +41 -0
  63. data/vendor/libyeptris/src/yeptris/common/chartype.h +77 -0
  64. data/vendor/libyeptris/src/yeptris/common/cpu.c +81 -0
  65. data/vendor/libyeptris/src/yeptris/common/cpu.h +40 -0
  66. data/vendor/libyeptris/src/yeptris/common/error.c +65 -0
  67. data/vendor/libyeptris/src/yeptris/common/error.h +79 -0
  68. data/vendor/libyeptris/src/yeptris/common/mutex.h +45 -0
  69. data/vendor/libyeptris/src/yeptris/common/nametab.c +275 -0
  70. data/vendor/libyeptris/src/yeptris/common/nametab.h +68 -0
  71. data/vendor/libyeptris/src/yeptris/common/port.h +74 -0
  72. data/vendor/libyeptris/src/yeptris/common/simd_text.c +49 -0
  73. data/vendor/libyeptris/src/yeptris/common/simd_text.h +295 -0
  74. data/vendor/libyeptris/src/yeptris/common/simd_text_avx2.c +534 -0
  75. data/vendor/libyeptris/src/yeptris/common/simd_text_neon.c +662 -0
  76. data/vendor/libyeptris/src/yeptris/common/simd_text_scalar.c +298 -0
  77. data/vendor/libyeptris/src/yeptris/common/string_view.c +34 -0
  78. data/vendor/libyeptris/src/yeptris/common/string_view.h +93 -0
  79. data/vendor/libyeptris/src/yeptris/doc.h +56 -0
  80. data/vendor/libyeptris/src/yeptris/dom/dom.c +1192 -0
  81. data/vendor/libyeptris/src/yeptris/dom/dom.h +312 -0
  82. data/vendor/libyeptris/src/yeptris/dom/hpool.c +107 -0
  83. data/vendor/libyeptris/src/yeptris/dom/mapindex.c +173 -0
  84. data/vendor/libyeptris/src/yeptris/dom/mapindex.h +61 -0
  85. data/vendor/libyeptris/src/yeptris/dom/mutate.c +431 -0
  86. data/vendor/libyeptris/src/yeptris/emit/emit.c +434 -0
  87. data/vendor/libyeptris/src/yeptris/emit/float/api.h +38 -0
  88. data/vendor/libyeptris/src/yeptris/emit/float/dragon.c +405 -0
  89. data/vendor/libyeptris/src/yeptris/emit/float/floatint.h +231 -0
  90. data/vendor/libyeptris/src/yeptris/emit/float/print.c +396 -0
  91. data/vendor/libyeptris/src/yeptris/emit/style.c +81 -0
  92. data/vendor/libyeptris/src/yeptris/emit/style.h +24 -0
  93. data/vendor/libyeptris/src/yeptris/emit/writer.c +816 -0
  94. data/vendor/libyeptris/src/yeptris/emit/writer.h +76 -0
  95. data/vendor/libyeptris/src/yeptris/encoding/bom.c +31 -0
  96. data/vendor/libyeptris/src/yeptris/encoding/encoding.h +63 -0
  97. data/vendor/libyeptris/src/yeptris/encoding/transcode.c +148 -0
  98. data/vendor/libyeptris/src/yeptris/encoding/utf8_validate.c +178 -0
  99. data/vendor/libyeptris/src/yeptris/events/capture.c +150 -0
  100. data/vendor/libyeptris/src/yeptris/events/capture.h +39 -0
  101. data/vendor/libyeptris/src/yeptris/events/iterparse.c +208 -0
  102. data/vendor/libyeptris/src/yeptris/events/pull.c +97 -0
  103. data/vendor/libyeptris/src/yeptris/events/push.c +104 -0
  104. data/vendor/libyeptris/src/yeptris/events/recorder.c +107 -0
  105. data/vendor/libyeptris/src/yeptris/events/values.c +527 -0
  106. data/vendor/libyeptris/src/yeptris/events/values_priv.h +51 -0
  107. data/vendor/libyeptris/src/yeptris/events/yaml_compat.c +86 -0
  108. data/vendor/libyeptris/src/yeptris/events/yaml_compat.h +90 -0
  109. data/vendor/libyeptris/src/yeptris/jsonapi/jsonc_compat.c +687 -0
  110. data/vendor/libyeptris/src/yeptris/jsonapi/yajl_compat.c +728 -0
  111. data/vendor/libyeptris/src/yeptris/marshal.c +782 -0
  112. data/vendor/libyeptris/src/yeptris/memory/allocator.c +20 -0
  113. data/vendor/libyeptris/src/yeptris/memory/allocator.h +43 -0
  114. data/vendor/libyeptris/src/yeptris/memory/arena.c +134 -0
  115. data/vendor/libyeptris/src/yeptris/memory/arena.h +52 -0
  116. data/vendor/libyeptris/src/yeptris/memory/pool.c +122 -0
  117. data/vendor/libyeptris/src/yeptris/memory/pool.h +36 -0
  118. data/vendor/libyeptris/src/yeptris/parse/engine.c +3698 -0
  119. data/vendor/libyeptris/src/yeptris/parse/engine.h +65 -0
  120. data/vendor/libyeptris/src/yeptris/parse/events.h +124 -0
  121. data/vendor/libyeptris/src/yeptris/parse/numbers.c +349 -0
  122. data/vendor/libyeptris/src/yeptris/parse/numbers.h +33 -0
  123. data/vendor/libyeptris/src/yeptris/parse/scalars.c +416 -0
  124. data/vendor/libyeptris/src/yeptris/parse/scalars.h +75 -0
  125. data/vendor/libyeptris/src/yeptris/parse.c +737 -0
  126. data/vendor/libyeptris/src/yeptris/plan.c +824 -0
  127. data/vendor/libyeptris/src/yeptris/resolve/compat11.c +334 -0
  128. data/vendor/libyeptris/src/yeptris/resolve/core12.c +143 -0
  129. data/vendor/libyeptris/src/yeptris/resolve/resolver.h +56 -0
  130. data/vendor/libyeptris/src/yeptris/resolve/tags.c +58 -0
  131. data/vendor/libyeptris/src/yeptris/scan/json.c +792 -0
  132. data/vendor/libyeptris/src/yeptris/scan/json.h +109 -0
  133. data/vendor/libyeptris/src/yeptris/scan/scan.c +463 -0
  134. data/vendor/libyeptris/src/yeptris/scan/scan.h +150 -0
  135. data/vendor/libyeptris/src/yeptris/schema.c +308 -0
  136. data/vendor/libyeptris/src/yeptris/tape.c +1308 -0
  137. data/vendor/libyeptris/src/yeptris/tape_in.h +19 -0
  138. data/vendor/libyeptris/src/yeptris/version.c +7 -0
  139. data/vendor/libyeptris/src/yeptris/visit/dom_visit.c +115 -0
  140. data/vendor/libyeptris/src/yeptris/visit/json_visit.c +293 -0
  141. data/vendor/libyeptris/src/yeptris/visit/yaml_visit.c +127 -0
  142. metadata +199 -0
@@ -0,0 +1,737 @@
1
+ /* parse.c — public API glue: encoding front-end → engine → DOM. */
2
+
3
+ #include <string.h>
4
+
5
+ #include "common/simd_text.h"
6
+ #include "doc.h"
7
+ #include "dom/dom.h"
8
+ #include "encoding/encoding.h"
9
+ #include "memory/allocator.h"
10
+ #include "memory/pool.h"
11
+ #include "parse/engine.h"
12
+ #include "parse/numbers.h"
13
+ #include "resolve/resolver.h"
14
+ #include "scan/json.h"
15
+ #include "tape_in.h"
16
+
17
+ #include "parse/events.h"
18
+ #include <errno.h>
19
+ #include <math.h>
20
+ #include <stdlib.h>
21
+
22
+ #include <yeptris.h>
23
+
24
+ YEPTRIS_API const char* yeptris_last_error(uint32_t* line, uint32_t* col) {
25
+ const yep_error* tls = yep_error_tls();
26
+ if (line != NULL) {
27
+ *line = tls->line;
28
+ }
29
+ if (col != NULL) {
30
+ *col = tls->col;
31
+ }
32
+ return tls->msg;
33
+ }
34
+
35
+ static YeptrisDocument parse_impl(const char* buf, size_t len, const YeptrisParseOptions* opts,
36
+ int json_mode, YeptrisStatus* status);
37
+
38
+ YEPTRIS_API YeptrisDocument yeptris_parse(const char* buf, size_t len, YeptrisStatus* status) {
39
+ return yeptris_parse_ex(buf, len, NULL, status);
40
+ }
41
+
42
+ /* #342 slice 2: materialize the deferred tree on first access. The
43
+ * gate-clean JSON route carries the parsed TAPE (simdjson's design:
44
+ * its "DOM" is a tape) and pays dom_from_tape only when a consumer
45
+ * touches the tree — parse-only workloads never build nodes. */
46
+ yep_dom* yep_doc_dom(yeptris_document* doc) {
47
+ if (doc == NULL) {
48
+ return NULL;
49
+ }
50
+ if (doc->dom == NULL && doc->lazy_tape != NULL) {
51
+ yeptris_json_tape* t = (yeptris_json_tape*)doc->lazy_tape;
52
+ yep_dom* dom = yep_dom_create(doc->sys);
53
+ if (dom != NULL && dom_from_tape(dom, t) == 0) {
54
+ doc->dom = dom;
55
+ yeptris_tape_free(t);
56
+ yep_free(doc->sys, t);
57
+ doc->lazy_tape = NULL;
58
+ } else {
59
+ yep_dom_destroy(dom);
60
+ dom = NULL; /* the tape stays: a later access retries, or
61
+ free drops it (the materializer only fails on
62
+ malformed NUM spans, which the strict gate
63
+ already rejected at parse) */
64
+ }
65
+ return dom;
66
+ }
67
+ return doc->dom;
68
+ }
69
+
70
+ /* The strict-JSON document wrapper (both routes share it). */
71
+ /* The lazy route's acceptance debt: the fused lenient walk records
72
+ * number runs UNVALIDATED (its simdjson-style deferred contract).
73
+ * parse_json's contract is RFC 8259 at parse time, so the deferred
74
+ * spans settle HERE — full-span shape check per NUM record, decoding
75
+ * the packed records directly (no columns build at parse; the walk
76
+ * guarantees recs are primary on this route). */
77
+ static int lazy_nums_settled(const yeptris_json_tape* t) {
78
+ const char* p = (const char*)t->_src;
79
+ for (uint32_t i = 0; i < t->count; i++) {
80
+ uint64_t r = t->recs[i];
81
+ if ((uint8_t)(r & 0xFFu) != YEP_T_NUM) {
82
+ continue;
83
+ }
84
+ uint32_t len = (uint32_t)((r >> 8) & 0x7FFFFFu);
85
+ uint32_t off = (uint32_t)(r >> 32);
86
+ size_t adv = 0;
87
+ int flt = 0;
88
+ if (yep_json_number_shape(p + off, len, &adv, &flt) == 0 || adv != (size_t)len) {
89
+ return -1;
90
+ }
91
+ }
92
+ return 0;
93
+ }
94
+
95
+ /* The strict-JSON document wrapper (both routes share it). */
96
+ static YeptrisDocument yep_json_doc_wrap(yep_dom* dom, const char* buf, size_t len,
97
+ const yep_allocator* sys, YeptrisStatus* status) {
98
+ (void)len;
99
+ yeptris_document* doc = yep_alloc(sys, sizeof(yeptris_document));
100
+ if (doc == NULL) {
101
+ yep_dom_destroy(dom);
102
+ if (status != NULL) {
103
+ *status = YEPTRIS_ERROR_MEMORY;
104
+ }
105
+ return NULL;
106
+ }
107
+ doc->dom = dom;
108
+ doc->sys = sys;
109
+ doc->lazy_tape = NULL;
110
+ doc->schema = YEPTRIS_SCHEMA_12_CORE; /* strict JSON is core by construction */
111
+ doc->transcoded = NULL;
112
+ doc->transcoded_len = 0;
113
+ doc->input = buf;
114
+ doc->finish_pool = NULL;
115
+ if (status != NULL) {
116
+ *status = YEPTRIS_OK;
117
+ }
118
+ return (YeptrisDocument)doc;
119
+ }
120
+
121
+ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, YeptrisStatus* status) {
122
+ YeptrisStatus st = YEPTRIS_OK;
123
+ if (buf == NULL && len != 0) {
124
+ st = YEPTRIS_ERROR_ARG;
125
+ goto jfail;
126
+ }
127
+ /* Clean-gate fast route (TODO.restructure/81 stage 2): a gate-clean
128
+ * buffer is printable ASCII, and the FUSED builder's walk runs
129
+ * STRICT here (dom->flow_strict) — one pass validates RFC 8259 and
130
+ * builds. Rejects, non-opener roots, and surprises fall to the
131
+ * original sequence below, whose error precedence is byte-for-byte
132
+ * the pinned behavior (json-suite-strict gates this). */
133
+ int gated = len > 0 && yep_text_active()->gate_scan(buf, len);
134
+ if (!gated) {
135
+ size_t off = 0;
136
+ while (off < len &&
137
+ (buf[off] == ' ' || buf[off] == '\t' || buf[off] == '\n' || buf[off] == '\r')) {
138
+ off++;
139
+ }
140
+ /* tabs: the lenient walk's own pinned acceptance (its route
141
+ * takes them); the strict routes reject them (ErrorParity's
142
+ * pinned agreement). A tab-carrying buffer must NOT take the
143
+ * lazy walk — the validating sequence below reports exactly
144
+ * the pinned reject. */
145
+ if (off < len && (buf[off] == '[' || buf[off] == '{') && memchr(buf, '\t', len) == NULL) {
146
+ /* #342 slice 2: the fused LENIENT walk (one pass, records
147
+ * only — no node building) settles the deferred number
148
+ * grammar inline, then the tape rides the document and
149
+ * dom_from_tape builds nodes on the first tree access.
150
+ * A reject or malformed span falls to the original
151
+ * sequence below, whose error precedence is byte-for-byte
152
+ * the pinned behavior. */
153
+ const yep_allocator* sys = yep_system_allocator();
154
+ yeptris_json_tape* t = yep_alloc(sys, sizeof(*t));
155
+ if (t == NULL) {
156
+ st = YEPTRIS_ERROR_MEMORY;
157
+ goto jfail;
158
+ }
159
+ if (yep_tape_walk_lenient_fused(buf, len, off, t) == YEPTRIS_OK &&
160
+ lazy_nums_settled(t) == 0) {
161
+ YeptrisDocument h = yep_json_doc_wrap(NULL, buf, len, sys, status);
162
+ if (h != NULL) {
163
+ ((yeptris_document*)h)->lazy_tape = t;
164
+ return h;
165
+ }
166
+ yeptris_tape_free(t); /* wrap failed (memory): below */
167
+ } else {
168
+ yeptris_tape_free(t); /* reject/malformed: below */
169
+ }
170
+ yep_free(sys, t);
171
+ }
172
+ }
173
+ size_t verr = 0;
174
+ if (!yep_json_document(buf, len, &verr)) {
175
+ yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, verr,
176
+ "not strict JSON at byte %zu", verr);
177
+ st = YEPTRIS_ERROR_PARSE;
178
+ goto jfail;
179
+ }
180
+ size_t uerr = 0;
181
+ if (gated && !yep_utf8_validate((const unsigned char*)buf, len, &uerr)) {
182
+ yep_error_set(yep_error_tls(), YEP_ERR_ENCODING, 0, 0, uerr, "ill-formed UTF-8 at byte %zu",
183
+ uerr);
184
+ st = YEPTRIS_ERROR_ENCODING;
185
+ goto jfail;
186
+ }
187
+ /* Direct DOM construction (TODO.impl/27): the validated buffer
188
+ * needs no engine — one walk builds the tree, skipping the event
189
+ * pipeline. A builder/validator disagreement defers to the engine
190
+ * (never happens in-tree; belt and braces). */
191
+ {
192
+ const yep_allocator* sys = yep_system_allocator();
193
+ yep_dom* dom = yep_dom_create(sys);
194
+ if (dom == NULL) {
195
+ st = YEPTRIS_ERROR_MEMORY;
196
+ goto jfail;
197
+ }
198
+ dom->input_base = buf; /* strict JSON is UTF-8 by definition */
199
+ dom->input_len = len;
200
+ yep_dom_prepare_len(dom, len);
201
+ int brc = yep_dom_build_json(dom, buf, len);
202
+ if (brc == -1) {
203
+ yep_dom_destroy(dom);
204
+ st = YEPTRIS_ERROR_MEMORY;
205
+ goto jfail;
206
+ }
207
+ if (brc == -2) {
208
+ yep_dom_destroy(dom);
209
+ return parse_impl(buf, len, NULL, 1, status);
210
+ }
211
+ return yep_json_doc_wrap(dom, buf, len, sys, status);
212
+ }
213
+ jfail:
214
+ if (status != NULL) {
215
+ *status = st;
216
+ }
217
+ return NULL;
218
+ }
219
+
220
+ static YeptrisDocument parse_impl(const char* buf, size_t len, const YeptrisParseOptions* opts,
221
+ int json_mode, YeptrisStatus* status) {
222
+ YeptrisStatus st = YEPTRIS_OK;
223
+ if (buf == NULL && len != 0) {
224
+ st = YEPTRIS_ERROR_ARG;
225
+ goto fail;
226
+ }
227
+
228
+ const yep_allocator* sys = yep_system_allocator();
229
+
230
+ /* Encoding front-end: BOM sniff; borrow UTF-8, transcode the rest. */
231
+ const char* data = buf;
232
+ size_t data_len = len;
233
+ unsigned char* transcoded = NULL;
234
+ size_t transcoded_len = 0;
235
+ yep_encoding enc = YEP_ENC_UNKNOWN;
236
+
237
+ if (json_mode) {
238
+ /* JSON mode validated grammar and UTF-8 well-formedness at the
239
+ * entry: no BOM, no transcoding, and never the printable set —
240
+ * RFC 8259 allows DEL and noncharacters that YAML c-printable
241
+ * excludes. The engine takes the bytes directly. */
242
+ goto engine_enter;
243
+ }
244
+
245
+ size_t bom = yep_bom_sniff((const unsigned char*)buf, len, &enc);
246
+ data = buf + bom;
247
+ data_len = len - bom;
248
+
249
+ if (enc == YEP_ENC_UTF16LE || enc == YEP_ENC_UTF16BE || enc == YEP_ENC_UTF32LE ||
250
+ enc == YEP_ENC_UTF32BE) {
251
+ size_t terr = 0;
252
+ int rc = yep_transcode_to_utf8(sys, enc, (const unsigned char*)data, data_len, &transcoded,
253
+ &transcoded_len, &terr);
254
+ if (rc != 0) {
255
+ yep_error_set(yep_error_tls(), YEP_ERR_ENCODING, 0, 0, terr,
256
+ "ill-formed input encoding");
257
+ yep_free(sys, transcoded);
258
+ st = YEPTRIS_ERROR_ENCODING;
259
+ goto fail;
260
+ }
261
+ data = (const char*)transcoded;
262
+ data_len = transcoded_len;
263
+ size_t pverr = 0;
264
+ if (!yep_printable_validate((const unsigned char*)data, data_len, &pverr)) {
265
+ yep_error_set(yep_error_tls(), YEP_ERR_ENCODING, 0, 0, pverr,
266
+ "non-printable character at byte %zu", pverr);
267
+ yep_free(sys, transcoded);
268
+ st = YEPTRIS_ERROR_ENCODING;
269
+ goto fail;
270
+ }
271
+ } else {
272
+ /* The encoding gate rides the cheap gate_scan kernel; sizing
273
+ * rides length heuristics (TODO.restructure/66: the fused
274
+ * multi-class stats sweep cost ~12% on the ubuntu asymmetry
275
+ * shapes — ryml pays no pre-pass). Pure gate-safe input skips
276
+ * the SWAR validator entirely; anything else falls to it for
277
+ * the authoritative answer + error position. */
278
+ if (yep_text_active()->gate_scan(data, data_len)) {
279
+ size_t verr = 0;
280
+ if (!yep_printable_validate((const unsigned char*)data, data_len, &verr)) {
281
+ yep_error_set(yep_error_tls(), YEP_ERR_ENCODING, 0, 0, verr,
282
+ "ill-formed or non-printable UTF-8 at byte %zu", verr);
283
+ st = YEPTRIS_ERROR_ENCODING;
284
+ goto fail;
285
+ }
286
+ }
287
+ }
288
+
289
+ /* Engine → DOM. */
290
+ yep_engine* eng = NULL;
291
+ engine_enter:
292
+ eng = yep_engine_create(sys);
293
+ if (opts != NULL) {
294
+ yep_engine_set_resolver(eng, opts->schema == YEPTRIS_SCHEMA_11_COMPAT
295
+ ? yep_resolver_compat11()
296
+ : yep_resolver_core12());
297
+ if (opts->max_depth > 0) {
298
+ yep_engine_set_max_depth(eng, opts->max_depth);
299
+ }
300
+ }
301
+ if (eng == NULL) {
302
+ yep_free(sys, transcoded);
303
+ st = YEPTRIS_ERROR_MEMORY;
304
+ goto fail;
305
+ }
306
+ yep_dom* dom = yep_dom_create(sys);
307
+ if (dom == NULL) {
308
+ yep_engine_destroy(eng);
309
+ yep_free(sys, transcoded);
310
+ st = YEPTRIS_ERROR_MEMORY;
311
+ goto fail;
312
+ }
313
+ /* the direct builders resolve with the engine's schema (the typing
314
+ * SSOT — one resolver decision per scalar, whichever path builds) */
315
+ dom->resolver = (opts != NULL && opts->schema == YEPTRIS_SCHEMA_11_COMPAT)
316
+ ? yep_resolver_compat11()
317
+ : yep_resolver_core12();
318
+ /* BEFORE the run: input-slice strings borrow as views (zero copy)
319
+ * — the document keeps `transcoded` alive for exactly this. Set
320
+ * after, every scalar was arena-copied (found 2026-09-10). */
321
+ dom->input_len = data_len;
322
+ dom->input_base = transcoded ? (const char*)transcoded : buf;
323
+ yep_dom_prepare_len(dom, data_len);
324
+ {
325
+ /* the nametab reserve survives: a memchr chain for '&' is
326
+ * far cheaper than the class sweep it replaced */
327
+ yep_text_stats amp_only = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
328
+ const char* q = data;
329
+ const char* end = data + data_len;
330
+ if (q != NULL) {
331
+ while ((q = memchr(q, '&', (size_t)(end - q))) != NULL) {
332
+ amp_only.amp++;
333
+ q++;
334
+ }
335
+ }
336
+ yep_engine_prepare(eng, &amp_only);
337
+ }
338
+
339
+ yep_sink sink = {.on_event = yep_dom_on_event,
340
+ .ctx = dom,
341
+ .on_flow_build = dom_on_flow_build,
342
+ .on_flow_commit = dom_on_flow_commit,
343
+ .on_flow_rollback = dom_on_flow_rollback,
344
+ .on_block_pair = dom_on_block_pair,
345
+ .on_scalar = dom_on_scalar,
346
+ .on_block_open = dom_on_block_open,
347
+ .on_block_item = dom_on_block_item};
348
+ int rc = yep_engine_run(eng, data, data_len, &sink);
349
+ if (rc != 0) {
350
+ const yep_error* ee = yep_engine_error(eng);
351
+ yep_err_code code = ee ? ee->code : YEP_ERR_UNEXPECTED;
352
+ if (ee) {
353
+ yep_error* tls = yep_error_tls();
354
+ *tls = *ee;
355
+ }
356
+ yep_dom_destroy(dom);
357
+ yep_engine_destroy(eng);
358
+ yep_free(sys, transcoded);
359
+ st = (code == YEP_ERR_MEMORY) ? YEPTRIS_ERROR_MEMORY
360
+ : (code == YEP_ERR_DEPTH) ? YEPTRIS_ERROR_DEPTH
361
+ : YEPTRIS_ERROR_PARSE;
362
+ goto fail;
363
+ }
364
+ /* Resolved tags, folded and escaped scalars live in the engine's
365
+ * finish pool; the document must own it or every such string would
366
+ * dangle at engine teardown (found by ASAN). */
367
+ yep_pool* finish = yep_engine_detach_pool(eng);
368
+ yep_engine_destroy(eng);
369
+ /* DOM strings are input-offsets or arena copies; nothing in the
370
+ * tree references the finish pool anymore — release it now */
371
+
372
+ /* Empty stream (no documents): NULL document with YEPTRIS_OK. */
373
+ if (dom->dcount == 0) {
374
+ yep_dom_destroy(dom);
375
+ yep_pool_destroy(finish);
376
+ yep_free(sys, transcoded);
377
+ if (status != NULL) {
378
+ *status = YEPTRIS_OK;
379
+ }
380
+ return NULL;
381
+ }
382
+
383
+ yeptris_document* doc = yep_alloc(sys, sizeof(yeptris_document));
384
+ if (doc == NULL) {
385
+ yep_dom_destroy(dom);
386
+ yep_pool_destroy(finish);
387
+ yep_free(sys, transcoded);
388
+ st = YEPTRIS_ERROR_MEMORY;
389
+ goto fail;
390
+ }
391
+ doc->dom = dom;
392
+ doc->sys = sys;
393
+ doc->schema = (opts != NULL && opts->schema == YEPTRIS_SCHEMA_11_COMPAT)
394
+ ? YEPTRIS_SCHEMA_11_COMPAT
395
+ : YEPTRIS_SCHEMA_12_CORE;
396
+ doc->transcoded = transcoded;
397
+ doc->transcoded_len = transcoded_len;
398
+ doc->input = buf;
399
+ /* the finish pool dies here: every DOM string is an input offset
400
+ * or an arena copy (dom_ev_str) — nothing references it */
401
+ yep_pool_destroy(finish);
402
+ doc->finish_pool = NULL;
403
+ doc->lazy_tape = NULL; /* field-by-field ctor: leave no garbage
404
+ * (document_free frees a non-NULL tape) */
405
+ return (YeptrisDocument)doc;
406
+
407
+ fail:
408
+ if (status != NULL) {
409
+ *status = st;
410
+ }
411
+ return NULL;
412
+ }
413
+
414
+ YEPTRIS_API YeptrisDocument yeptris_parse_ex(const char* buf, size_t len,
415
+ const YeptrisParseOptions* opts,
416
+ YeptrisStatus* status) {
417
+ return parse_impl(buf, len, opts, 0, status);
418
+ }
419
+
420
+ YEPTRIS_API void yeptris_document_free(YeptrisDocument handle) {
421
+ yeptris_document* doc = (yeptris_document*)handle;
422
+ if (doc == NULL) {
423
+ return;
424
+ }
425
+ if (doc->lazy_tape != NULL) { /* never materialized: free stays free */
426
+ yeptris_tape_free((yeptris_json_tape*)doc->lazy_tape);
427
+ yep_free(doc->sys, doc->lazy_tape);
428
+ }
429
+ yep_dom_destroy(doc->dom);
430
+ yep_pool_destroy((yep_pool*)doc->finish_pool);
431
+ yep_free(doc->sys, doc->transcoded);
432
+ yep_free(doc->sys, doc);
433
+ }
434
+
435
+ YEPTRIS_API size_t yeptris_document_count(YeptrisDocument handle) {
436
+ yeptris_document* doc = (yeptris_document*)handle;
437
+ return doc ? yep_doc_dom(doc)->dcount : 0;
438
+ }
439
+
440
+ yeptris_node* yep_handle_new(yeptris_document* doc, uint32_t id) {
441
+ yeptris_node* n = yep_hpool_alloc(yep_dom_handles(doc->dom), sizeof(yeptris_node), 16);
442
+ if (n == NULL) {
443
+ return NULL;
444
+ }
445
+ n->doc = doc;
446
+ n->id = id;
447
+ return n;
448
+ }
449
+
450
+ static YeptrisNode node_new_handle(yeptris_document* doc, uint32_t id) {
451
+ return (YeptrisNode)yep_handle_new(doc, id);
452
+ }
453
+
454
+ YEPTRIS_API YeptrisNode yeptris_document_root(YeptrisDocument handle, size_t index) {
455
+ yeptris_document* doc = (yeptris_document*)handle;
456
+ if (doc == NULL) {
457
+ return NULL;
458
+ }
459
+ yep_dom* dom = yep_doc_dom(doc);
460
+ if (dom == NULL || index >= dom->dcount) {
461
+ return NULL;
462
+ }
463
+ return node_new_handle(doc, dom->docs[index]);
464
+ }
465
+
466
+ static const yep_dnode* node_of(YeptrisNode handle) {
467
+ yeptris_node* n = (yeptris_node*)handle;
468
+ if (n == NULL || n->doc == NULL) {
469
+ return NULL;
470
+ }
471
+ return yep_dom_node(n->doc->dom, n->id);
472
+ }
473
+
474
+ YEPTRIS_API uint32_t yeptris_node_id(YeptrisNode handle) {
475
+ yeptris_node* n = (yeptris_node*)handle;
476
+ return n == NULL ? UINT32_MAX : n->id;
477
+ }
478
+
479
+ YEPTRIS_API YeptrisNodeKind yeptris_node_kind(YeptrisNode handle) {
480
+ const yep_dnode* n = node_of(handle);
481
+ if (n == NULL) {
482
+ return YEPTRIS_NODE_SCALAR;
483
+ }
484
+ switch (n->kind) {
485
+ case YEP_DOM_SEQUENCE:
486
+ return YEPTRIS_NODE_SEQUENCE;
487
+ case YEP_DOM_MAPPING:
488
+ return YEPTRIS_NODE_MAPPING;
489
+ case YEP_DOM_ALIAS:
490
+ return YEPTRIS_NODE_ALIAS;
491
+ default:
492
+ return YEPTRIS_NODE_SCALAR;
493
+ }
494
+ }
495
+
496
+ /* Decodes a node's compact string through its document's regions. */
497
+ static yep_view node_view(const yeptris_node* h, yep_sview sv) {
498
+ return yep_dom_view(h->doc->dom, sv);
499
+ }
500
+
501
+ static const char* view_out(yep_view v, size_t* len) {
502
+ if (len != NULL) {
503
+ *len = v.len;
504
+ }
505
+ return v.len ? v.p : (v.p ? v.p : "");
506
+ }
507
+
508
+ YEPTRIS_API const char* yeptris_node_value(YeptrisNode handle, size_t* len) {
509
+ const yep_dnode* n = node_of(handle);
510
+ if (n == NULL || (n->kind != YEP_DOM_SCALAR && n->kind != YEP_DOM_ALIAS)) {
511
+ if (len != NULL) {
512
+ *len = 0;
513
+ }
514
+ return NULL;
515
+ }
516
+ return view_out(node_view((yeptris_node*)handle, n->value), len);
517
+ }
518
+
519
+ YEPTRIS_API YeptrisScalarStyle yeptris_node_style(YeptrisNode handle) {
520
+ const yep_dnode* n = node_of(handle);
521
+ return n ? (YeptrisScalarStyle)n->style : YEPTRIS_STYLE_ANY;
522
+ }
523
+
524
+ YEPTRIS_API const char* yeptris_tag_uri(YeptrisTagId id) {
525
+ return yep_tag_uri((yep_tag_id)id);
526
+ }
527
+
528
+ YEPTRIS_API YeptrisTagId yeptris_node_tag_id(YeptrisNode handle) {
529
+ const yep_dnode* n = node_of(handle);
530
+ if (n == NULL) {
531
+ return YEPTRIS_TAG_CUSTOM;
532
+ }
533
+ if (n->kind == YEPTRIS_NODE_SEQUENCE) {
534
+ return YEPTRIS_TAG_SEQ;
535
+ }
536
+ if (n->kind == YEPTRIS_NODE_MAPPING) {
537
+ return YEPTRIS_TAG_MAP;
538
+ }
539
+ return (YeptrisTagId)n->tag_id;
540
+ }
541
+
542
+ /* strips '_' and ',' into dst (nul-terminated); returns length.
543
+ * The COMMON case (no separators) returns the borrowed view — no
544
+ * copy, no nul-termination needed by the in-place parsers. */
545
+ YEPTRIS_API YeptrisStatus yeptris_node_int(YeptrisNode handle, int64_t* out) {
546
+ /* conversion is the number kernel's alone (MECE: one fold, one
547
+ * fast path, one Psych-sexagesimal weight table) */
548
+ const yep_dnode* n = node_of(handle);
549
+ if (n == NULL || out == NULL) {
550
+ return YEPTRIS_ERROR_ARG;
551
+ }
552
+ if (n->tag_id != YEPTRIS_TAG_INT) {
553
+ return YEPTRIS_ERROR_PARSE;
554
+ }
555
+ yep_view v = node_view((yeptris_node*)handle, n->value);
556
+ return yep_num_i64((const char*)v.p, v.len, out) == 0 ? YEPTRIS_OK : YEPTRIS_ERROR_PARSE;
557
+ }
558
+
559
+ YEPTRIS_API YeptrisStatus yeptris_node_float(YeptrisNode handle, double* out) {
560
+ const yep_dnode* n = node_of(handle);
561
+ if (n == NULL || out == NULL) {
562
+ return YEPTRIS_ERROR_ARG;
563
+ }
564
+ if (n->tag_id != YEPTRIS_TAG_FLOAT) {
565
+ return YEPTRIS_ERROR_PARSE;
566
+ }
567
+ yep_view v = node_view((yeptris_node*)handle, n->value);
568
+ return yep_num_f64((const char*)v.p, v.len, out) == 0 ? YEPTRIS_OK : YEPTRIS_ERROR_PARSE;
569
+ }
570
+ YEPTRIS_API YeptrisStatus yeptris_node_bool(YeptrisNode handle, int* out) {
571
+ const yep_dnode* n = node_of(handle);
572
+ if (n == NULL || out == NULL) {
573
+ return YEPTRIS_ERROR_ARG;
574
+ }
575
+ if (n->tag_id != YEPTRIS_TAG_BOOL) {
576
+ return YEPTRIS_ERROR_PARSE;
577
+ }
578
+ /* the resolver accepted it; true-set membership decides the value */
579
+ static const char* k_true[] = {"true", "True", "TRUE", "y", "Y", "yes",
580
+ "Yes", "YES", "on", "On", "ON"};
581
+ for (size_t i = 0; i < sizeof(k_true) / sizeof(k_true[0]); i++) {
582
+ size_t m = strlen(k_true[i]);
583
+ yep_view v = node_view((yeptris_node*)handle, n->value);
584
+ if (v.len == m && memcmp(v.p, k_true[i], m) == 0) {
585
+ *out = 1;
586
+ return YEPTRIS_OK;
587
+ }
588
+ }
589
+ *out = 0;
590
+ return YEPTRIS_OK;
591
+ }
592
+
593
+ YEPTRIS_API const char* yeptris_node_tag(YeptrisNode handle, size_t* len) {
594
+ const yep_dnode* n = node_of(handle);
595
+ if (n == NULL || n->tag.len == 0) {
596
+ if (len != NULL) {
597
+ *len = 0;
598
+ }
599
+ return n ? NULL : NULL;
600
+ }
601
+ return view_out(node_view((yeptris_node*)handle, n->tag), len);
602
+ }
603
+
604
+ YEPTRIS_API const char* yeptris_node_anchor(YeptrisNode handle, size_t* len) {
605
+ const yep_dnode* n = node_of(handle);
606
+ if (n == NULL || n->anchor.len == 0) {
607
+ if (len != NULL) {
608
+ *len = 0;
609
+ }
610
+ return NULL;
611
+ }
612
+ return view_out(node_view((yeptris_node*)handle, n->anchor), len);
613
+ }
614
+
615
+ static YeptrisNode wrap(yeptris_node* base, uint32_t id) {
616
+ if (base == NULL || base->doc == NULL) {
617
+ return NULL;
618
+ }
619
+ /* Handles live in the document's thread-safe arena: one
620
+ * document_free reclaims every handle ever handed out (zero-leak
621
+ * contract) while read-only sharing stays safe (TODO.impl/19) */
622
+ yeptris_node* n = yep_hpool_alloc(yep_dom_handles(base->doc->dom), sizeof(yeptris_node), 16);
623
+ if (n == NULL) {
624
+ return NULL;
625
+ }
626
+ n->doc = base->doc;
627
+ n->id = id;
628
+ return (YeptrisNode)n;
629
+ }
630
+
631
+ YEPTRIS_API YeptrisNode yeptris_node_alias_target(YeptrisNode handle) {
632
+ const yep_dnode* n = node_of(handle);
633
+ if (n == NULL || n->kind != YEP_DOM_ALIAS || n->target == UINT32_MAX) {
634
+ return NULL;
635
+ }
636
+ return wrap((yeptris_node*)handle, n->target);
637
+ }
638
+
639
+ YEPTRIS_API size_t yeptris_node_seq_count(YeptrisNode handle) {
640
+ const yep_dnode* n = node_of(handle);
641
+ return (n && n->kind == YEP_DOM_SEQUENCE) ? n->count : 0;
642
+ }
643
+
644
+ YEPTRIS_API YeptrisNode yeptris_node_seq_at(YeptrisNode handle, size_t index) {
645
+ const yep_dnode* n = node_of(handle);
646
+ if (n == NULL || n->kind != YEP_DOM_SEQUENCE || index >= n->count) {
647
+ return NULL;
648
+ }
649
+ yep_dom* dom = ((yeptris_node*)handle)->doc->dom;
650
+ uint32_t count = 0;
651
+ /* the node id IS its dnode index */
652
+ uint32_t id = dom_indexed_child(dom, ((yeptris_node*)handle)->id, index, &count); /* #377 */
653
+ if (id == UINT32_MAX) {
654
+ return NULL;
655
+ }
656
+ return wrap((yeptris_node*)handle, id);
657
+ }
658
+
659
+ YEPTRIS_API size_t yeptris_node_children(YeptrisNode handle, YeptrisNode* out, size_t cap) {
660
+ const yep_dnode* n = node_of(handle);
661
+ if (n == NULL || (n->kind != YEP_DOM_SEQUENCE && n->kind != YEP_DOM_MAPPING)) {
662
+ return 0;
663
+ }
664
+ size_t count = 0;
665
+ uint32_t id = n->first_child;
666
+ while (id != UINT32_MAX) {
667
+ if (out != NULL && count < cap) {
668
+ out[count] = wrap((yeptris_node*)handle, id);
669
+ }
670
+ count++;
671
+ const yep_dnode* cur = yep_dom_node(((yeptris_node*)handle)->doc->dom, id);
672
+ id = cur ? cur->next_sibling : UINT32_MAX;
673
+ }
674
+ return count;
675
+ }
676
+
677
+ YEPTRIS_API size_t yeptris_node_map_count(YeptrisNode handle) {
678
+ const yep_dnode* n = node_of(handle);
679
+ return (n && n->kind == YEP_DOM_MAPPING) ? n->count / 2 : 0;
680
+ }
681
+
682
+ YEPTRIS_API int yeptris_node_map_at(YeptrisNode handle, size_t index, YeptrisNode* key,
683
+ YeptrisNode* value) {
684
+ const yep_dnode* n = node_of(handle);
685
+ if (n == NULL || n->kind != YEPTRIS_NODE_MAPPING || index >= n->count / 2) {
686
+ return -1;
687
+ }
688
+ yeptris_node* h = (yeptris_node*)handle;
689
+ yep_dom* d = h->doc->dom;
690
+ uint32_t count = 0;
691
+ uint32_t child = dom_indexed_child(d, h->id, (size_t)index * 2, &count); /* #377 */
692
+ if (child == UINT32_MAX) {
693
+ return -1;
694
+ }
695
+ if (key != NULL) {
696
+ *key = wrap(h, child);
697
+ }
698
+ if (value != NULL) {
699
+ *value = wrap(h, d->nodes[child].next_sibling);
700
+ }
701
+ return 0;
702
+ }
703
+
704
+ YEPTRIS_API YeptrisNode yeptris_node_map_get(YeptrisNode handle, const char* key, size_t key_len) {
705
+ const yep_dnode* n = node_of(handle);
706
+ if (n == NULL || n->kind != YEP_DOM_MAPPING || key == NULL) {
707
+ return NULL;
708
+ }
709
+ yep_dom* dom = ((yeptris_node*)handle)->doc->dom;
710
+ /* O(1) through the lazy index (first use builds it); the scan
711
+ * below is the build-failure fallback */
712
+ uint32_t hit =
713
+ yep_midx_lookup(dom, ((yeptris_node*)handle)->id, (yep_view){key, (uint32_t)key_len});
714
+ if (hit != UINT32_MAX) {
715
+ const yep_dnode* kn = yep_dom_node(dom, hit);
716
+ if (kn != NULL) {
717
+ return wrap((yeptris_node*)handle, kn->next_sibling);
718
+ }
719
+ }
720
+ uint32_t id = n->first_child;
721
+ int want_key = 1;
722
+ while (id != UINT32_MAX) {
723
+ const yep_dnode* cur = yep_dom_node(dom, id);
724
+ if (cur == NULL) {
725
+ break;
726
+ }
727
+ if (want_key && cur->kind == YEP_DOM_SCALAR) {
728
+ yep_view kv = {key, (uint32_t)key_len};
729
+ if (yep_view_eq(yep_dom_view(dom, cur->value), kv)) {
730
+ return wrap((yeptris_node*)handle, cur->next_sibling);
731
+ }
732
+ }
733
+ want_key = !want_key;
734
+ id = cur->next_sibling;
735
+ }
736
+ return NULL;
737
+ }