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,1192 @@
1
+ /* dom.c — event-built DOM (builder sink + storage). */
2
+
3
+ #include <stdio.h>
4
+ #include <string.h>
5
+
6
+ #include "common/simd_text.h"
7
+
8
+ #include "common/mutex.h"
9
+
10
+ #include "dom.h"
11
+
12
+ #include <yeptris/resolve.h> /* the tag-id SSOT (the strict literal arm) */
13
+
14
+ /* The bounded-parse caps: each node consumes at least one input byte
15
+ * (the grammar guarantees it), so len + 1024 is airtight for valid
16
+ * documents; docs and anchors are sparser still. The arena copies
17
+ * content the input already paid for (folds, tags, anchors) — generous
18
+ * 128x covers pathological tag expansion while still bounding any
19
+ * loop bug linearly in the input. */
20
+ static int cap_nodes(const yep_dom* d) {
21
+ return d->input_len == 0 || (size_t)d->ncount <= d->input_len + 1024;
22
+ }
23
+ static int cap_arena(const yep_dom* d, uint32_t need) {
24
+ return d->input_len == 0 || (size_t)(d->str_len + need) <= d->input_len * 128 + (1u << 20);
25
+ }
26
+
27
+ int dom_grow_nodes(yep_dom* d, uint32_t need) {
28
+ if (d->ncount + need <= d->ncap) {
29
+ return 1;
30
+ }
31
+ if (!cap_nodes(d)) {
32
+ return 0;
33
+ }
34
+ uint32_t ncap = d->ncap ? d->ncap * 2 : 64;
35
+ while (ncap < d->ncount + need) {
36
+ ncap *= 2;
37
+ }
38
+ yep_dnode* nd = yep_pool_alloc(d->pool, (size_t)ncap * sizeof(yep_dnode), 16);
39
+ if (nd == NULL) {
40
+ return 0;
41
+ }
42
+ if (d->ncount > 0) {
43
+ memcpy(nd, d->nodes, (size_t)d->ncount * sizeof(yep_dnode));
44
+ }
45
+ d->nodes = nd;
46
+ d->ncap = ncap;
47
+ return 1;
48
+ }
49
+
50
+ int dom_grow_docs(yep_dom* d, uint32_t need) {
51
+ if (d->dcount + need <= d->dcap) {
52
+ return 1;
53
+ }
54
+ if (d->input_len != 0 && (size_t)d->dcount > d->input_len / 2 + 16) {
55
+ return 0;
56
+ }
57
+ uint32_t ncap = d->dcap ? d->dcap * 2 : 16;
58
+ while (ncap < d->dcount + need) {
59
+ ncap *= 2;
60
+ }
61
+ uint32_t* nd = yep_pool_alloc(d->pool, (size_t)ncap * sizeof(uint32_t), 16);
62
+ if (nd == NULL) {
63
+ return 0;
64
+ }
65
+ if (d->dcount > 0) {
66
+ memcpy(nd, d->docs, (size_t)d->dcount * sizeof(uint32_t));
67
+ }
68
+ d->docs = nd;
69
+ d->dcap = ncap;
70
+ return 1;
71
+ }
72
+
73
+ /* Doubles the arena until cap bytes fit; 0 on OOM. Offsets stored in
74
+ * nodes stay valid across the move. */
75
+ static int str_grow(yep_dom* d, uint32_t need) {
76
+ if (d->str_len + need <= d->str_cap) {
77
+ return 1;
78
+ }
79
+ if (!cap_arena(d, need)) {
80
+ return 0;
81
+ }
82
+ uint32_t cap = d->str_cap ? d->str_cap : 256;
83
+ while (cap < d->str_len + need) {
84
+ cap *= 2;
85
+ }
86
+ char* ns = yep_alloc(d->sys, cap);
87
+ if (ns == NULL) {
88
+ return 0;
89
+ }
90
+ if (d->str_len > 0) {
91
+ memcpy(ns, d->str, d->str_len);
92
+ }
93
+ yep_free(d->sys, d->str);
94
+ d->str = ns;
95
+ d->str_cap = cap;
96
+ return 1;
97
+ }
98
+
99
+ yep_sview yep_dom_str_put(yep_dom* d, const char* p, uint32_t len) {
100
+ yep_sview sv = {YEP_SV_INPUT, 0}; /* empty: arena offset 0 */
101
+ if (len == 0 || !str_grow(d, len)) {
102
+ return sv; /* len 0 or OOM: empty view */
103
+ }
104
+ memcpy(d->str + d->str_len, p, len);
105
+ sv.off = YEP_SV_INPUT | d->str_len;
106
+ sv.len = len;
107
+ d->str_len += len;
108
+ return sv;
109
+ }
110
+
111
+ char* yep_dom_str_tail(yep_dom* d, uint32_t cap) {
112
+ if (!str_grow(d, cap)) {
113
+ return NULL;
114
+ }
115
+ return d->str + d->str_len;
116
+ }
117
+
118
+ yep_sview yep_dom_str_commit(yep_dom* d, uint32_t written) {
119
+ yep_sview sv = {YEP_SV_INPUT | d->str_len, written};
120
+ d->str_len += written;
121
+ return sv;
122
+ }
123
+
124
+ /* Node string: borrowed views stay INPUT offsets (zero copy);
125
+ * everything the engine produced (folded, escaped, resolved tags,
126
+ * anchor names) is copied into the arena — the finish pool is then
127
+ * free to die with the engine. */
128
+ static yep_sview dom_str_in(yep_dom* d, const yep_view* v, int borrowed) {
129
+ if (v == NULL || v->len == 0) {
130
+ yep_sview sv = {0, 0};
131
+ return sv;
132
+ }
133
+ if (borrowed && v->p != NULL && d->input_base != NULL && (const char*)v->p >= d->input_base) {
134
+ return yep_sv_input(d, *v);
135
+ }
136
+ return yep_dom_str_put(d, (const char*)v->p, v->len);
137
+ }
138
+
139
+ /* The node-init law (every builder's SSOT): creates one node with the
140
+ * given metadata; tag/anchor views encode through dom_str_in (tags and
141
+ * event-path anchors copy to the arena; input-backed views borrow).
142
+ * tag_id stays 0 — the caller resolves (scalars) or leaves 0. */
143
+ uint32_t dom_open_node(yep_dom* d, uint8_t kind, const yep_view* tag, const yep_view* anchor,
144
+ int anchor_borrowed, uint8_t style, uint8_t implicit, uint8_t flow) {
145
+ if (!dom_grow_nodes(d, 1)) {
146
+ return UINT32_MAX;
147
+ }
148
+ yep_dnode* n = &d->nodes[d->ncount];
149
+ /* template copy: one 48 B move replaces memset + four sentinel
150
+ * patches (TODO.restructure/73) — the hot per-node init */
151
+ static const yep_dnode k_init = {
152
+ .first_child = UINT32_MAX,
153
+ .last_child = UINT32_MAX,
154
+ .next_sibling = UINT32_MAX,
155
+ .target = UINT32_MAX,
156
+ };
157
+ *n = k_init;
158
+ n->kind = kind;
159
+ n->tag = dom_str_in(d, tag, 0);
160
+ n->anchor = dom_str_in(d, anchor, anchor_borrowed);
161
+ n->style = style;
162
+ n->implicit = implicit;
163
+ n->flow = flow;
164
+ return d->ncount++;
165
+ }
166
+
167
+ uint32_t dom_new_node(yep_dom* d, const yep_event* ev, uint8_t kind) {
168
+ if (ev == NULL) {
169
+ return dom_open_node(d, kind, NULL, NULL, 0, 0, 0, 0);
170
+ }
171
+ /* event-path anchors ride the VALUE's borrowedness (historical
172
+ * law, kept byte-identical) */
173
+ uint32_t id = dom_open_node(d, kind, &ev->tag, &ev->anchor, ev->borrowed, ev->style,
174
+ ev->implicit, ev->flow);
175
+ if (id == UINT32_MAX) {
176
+ return UINT32_MAX;
177
+ }
178
+ d->nodes[id].tag_id = ev->tag_id;
179
+ return id;
180
+ }
181
+
182
+ static int dom_anchor_set(yep_dom* d, uint32_t ordinal, uint32_t node) {
183
+ if (ordinal == 0) {
184
+ return -1;
185
+ }
186
+ if (d->input_len != 0 && ordinal > d->input_len + 16) {
187
+ return -1; /* more anchors than input bytes: a bug, not a doc */
188
+ }
189
+ if (ordinal > d->anchor_nodes_cap) {
190
+ /* First allocation sizes from the input (TODO.restructure/71):
191
+ * growth from a bare 64 re-copied the table ~18 times on
192
+ * anchor-heavy (one entry per '&x…' line). len/32 overshoots
193
+ * only pathological one-anchor-per-2-bytes documents, which
194
+ * then double as before. */
195
+ uint32_t cap = d->anchor_nodes_cap ? d->anchor_nodes_cap * 2 : 64;
196
+ if (d->anchor_nodes_cap == 0 && d->input_len > 2048) {
197
+ uint32_t sized = (uint32_t)(d->input_len / 32) + 64;
198
+ if (sized > cap) {
199
+ cap = sized;
200
+ }
201
+ }
202
+ while (cap < ordinal) {
203
+ cap *= 2;
204
+ }
205
+ uint32_t* na = yep_alloc(d->sys, cap * sizeof(*na));
206
+ if (na == NULL) {
207
+ return -1;
208
+ }
209
+ if (d->anchor_nodes != NULL) {
210
+ memcpy(na, d->anchor_nodes, d->anchor_nodes_cap * sizeof(*na));
211
+ yep_free(d->sys, d->anchor_nodes);
212
+ yep_free(d->sys, d->mut_att);
213
+ yep_free(d->sys, d->mut_depth);
214
+ }
215
+ d->anchor_nodes = na;
216
+ d->anchor_nodes_cap = cap;
217
+ }
218
+ d->anchor_nodes[ordinal - 1] = node;
219
+ if (ordinal > d->anchor_max) {
220
+ d->anchor_max = ordinal;
221
+ }
222
+ return 0;
223
+ }
224
+
225
+ static uint32_t dom_anchor_get(const yep_dom* d, uint32_t ordinal) {
226
+ if (ordinal == 0 || ordinal > d->anchor_max) {
227
+ return UINT32_MAX;
228
+ }
229
+ return d->anchor_nodes[ordinal - 1];
230
+ }
231
+
232
+ /* The one place links form (builder and mutation both): records
233
+ * attachment and depth so mutation can reject double-parents and cap
234
+ * nesting without parent pointers. */
235
+ void dom_link(yep_dom* d, uint32_t parent, uint32_t child) {
236
+ yep_dnode* p = &d->nodes[parent];
237
+ if (p->count == 0) {
238
+ p->first_child = child;
239
+ } else {
240
+ d->nodes[p->last_child].next_sibling = child;
241
+ }
242
+ p->last_child = child;
243
+ p->count++;
244
+ /* every link changes SOME container's child list (#377's cache) */
245
+ d->child_cache_id = UINT32_MAX;
246
+ d->child_cache_len = 0;
247
+ }
248
+
249
+ /* ---- mutation side tables (64-2a) ---- */
250
+
251
+ static int dom_mut_grow(yep_dom* d, uint32_t need) {
252
+ if (need <= d->mut_tbl_cap) {
253
+ return 1;
254
+ }
255
+ uint32_t cap = d->mut_tbl_cap ? d->mut_tbl_cap : 64;
256
+ while (cap < need) {
257
+ cap *= 2;
258
+ }
259
+ uint8_t* na = yep_alloc(d->sys, (size_t)cap / 8 + 1);
260
+ uint16_t* nd = yep_alloc(d->sys, (size_t)cap * sizeof(uint16_t));
261
+ if (na == NULL || nd == NULL) {
262
+ yep_free(d->sys, na);
263
+ yep_free(d->sys, nd);
264
+ return 0;
265
+ }
266
+ size_t old_bytes = ((size_t)d->mut_tbl_cap + 7) / 8;
267
+ if (d->mut_att != NULL) {
268
+ memcpy(na, d->mut_att, old_bytes);
269
+ memcpy(nd, d->mut_depth, (size_t)d->mut_tbl_cap * sizeof(uint16_t));
270
+ }
271
+ /* the grown region is FRESH facts: unattached, depth 0 (a CI
272
+ * failure traced to garbage bits here — local mallocs were zero) */
273
+ memset(na + old_bytes, 0, (size_t)cap / 8 + 1 - old_bytes);
274
+ memset(nd + d->mut_tbl_cap, 0, (size_t)(cap - d->mut_tbl_cap) * sizeof(uint16_t));
275
+ yep_free(d->sys, d->mut_att);
276
+ yep_free(d->sys, d->mut_depth);
277
+ d->mut_att = na;
278
+ d->mut_depth = nd;
279
+ d->mut_tbl_cap = cap;
280
+ return 1;
281
+ }
282
+
283
+ int dom_mut_att(const yep_dom* d, uint32_t id) {
284
+ yep_dom* m = (yep_dom*)d; /* lazy growth is benign bookkeeping */
285
+ if (id >= m->mut_tbl_cap && !dom_mut_grow(m, id + 1)) {
286
+ return 0;
287
+ }
288
+ return (m->mut_att[id >> 3] >> (id & 7)) & 1;
289
+ }
290
+
291
+ void dom_mut_set_att(yep_dom* d, uint32_t id, int v) {
292
+ if (id >= d->mut_tbl_cap && !dom_mut_grow(d, id + 1)) {
293
+ return;
294
+ }
295
+ uint8_t bit = (uint8_t)(1u << (id & 7));
296
+ if (v) {
297
+ d->mut_att[id >> 3] |= bit;
298
+ } else {
299
+ d->mut_att[id >> 3] &= (uint8_t)~bit;
300
+ }
301
+ }
302
+
303
+ uint16_t dom_mut_depth(const yep_dom* d, uint32_t id) {
304
+ yep_dom* m = (yep_dom*)d;
305
+ if (id >= m->mut_tbl_cap && !dom_mut_grow(m, id + 1)) {
306
+ return 0;
307
+ }
308
+ return m->mut_depth[id];
309
+ }
310
+
311
+ void dom_mut_set_depth(yep_dom* d, uint32_t id, uint16_t depth) {
312
+ if (id >= d->mut_tbl_cap && !dom_mut_grow(d, id + 1)) {
313
+ return;
314
+ }
315
+ d->mut_depth[id] = depth;
316
+ }
317
+
318
+ /* Places a completed node: value for a pending key, child of the top
319
+ * collection, or document root. */
320
+ int dom_place(yep_dom* d, uint32_t id) {
321
+ if (d->depth == 0) {
322
+ if (!dom_grow_docs(d, 1)) {
323
+ return -1;
324
+ }
325
+ d->docs[d->dcount++] = id;
326
+ return 0;
327
+ }
328
+ uint32_t top = d->stack[d->depth - 1];
329
+ yep_dnode* p = &d->nodes[top];
330
+ if (p->kind == YEP_DOM_MAPPING) {
331
+ /* per-frame pairing: a map nested as a KEY consumes this frame's
332
+ * slot without disturbing the parent's (global state corrupted
333
+ * complex keys) */
334
+ if (!d->map_pending_key[d->depth - 1]) {
335
+ d->pending_key_id[d->depth - 1] = id;
336
+ d->map_pending_key[d->depth - 1] = 1;
337
+ return 0;
338
+ }
339
+ d->map_pending_key[d->depth - 1] = 0;
340
+ dom_link(d, top, d->pending_key_id[d->depth - 1]); /* key */
341
+ dom_link(d, top, id); /* value */
342
+ return 0;
343
+ }
344
+ dom_link(d, top, id);
345
+ return 0;
346
+ }
347
+
348
+ int yep_dom_on_event(void* ctx, const yep_event* ev) {
349
+ yep_dom* d = (yep_dom*)ctx;
350
+ switch (ev->type) {
351
+ case YEP_EV_STREAM_START:
352
+ case YEP_EV_DOCUMENT_START:
353
+ case YEP_EV_DOCUMENT_END:
354
+ /* bindings are document-scoped, like the engine's names */
355
+ if (d->anchor_max != 0) {
356
+ memset(d->anchor_nodes, 0, d->anchor_max * sizeof(*d->anchor_nodes));
357
+ d->anchor_max = 0;
358
+ }
359
+ return 0;
360
+ case YEP_EV_STREAM_END:
361
+ /* no depth walk: the parse links TOP-DOWN (every child is
362
+ * linked the moment it starts, under a parent whose depth is
363
+ * final), so dom_link's O(1) assignment already globalizes
364
+ * them. The walk remains the MUTATION builder's job — it
365
+ * attaches pre-built subtrees whose descendants keep local
366
+ * depths (yep_mut_set_depths after attach). */
367
+ return 0;
368
+
369
+ case YEP_EV_SEQ_START:
370
+ case YEP_EV_MAP_START: {
371
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {
372
+ return -1;
373
+ }
374
+ uint8_t kind = (ev->type == YEP_EV_SEQ_START) ? YEP_DOM_SEQUENCE : YEP_DOM_MAPPING;
375
+ uint32_t id = dom_new_node(d, ev, kind);
376
+ if (id == UINT32_MAX) {
377
+ return -1;
378
+ }
379
+ if (ev->anchor_id != 0 && dom_anchor_set(d, ev->anchor_id, id) != 0) {
380
+ return -1;
381
+ }
382
+ if (dom_place(d, id) != 0) {
383
+ return -1;
384
+ }
385
+ d->map_pending_key[d->depth] = 0;
386
+ d->stack[d->depth++] = id;
387
+ return 0;
388
+ }
389
+
390
+ case YEP_EV_SEQ_END:
391
+ case YEP_EV_MAP_END:
392
+ if (d->depth == 0) {
393
+ return -1;
394
+ }
395
+ d->depth--;
396
+ return 0;
397
+
398
+ case YEP_EV_SCALAR: {
399
+ uint32_t id = dom_new_node(d, ev, YEP_DOM_SCALAR);
400
+ if (id == UINT32_MAX) {
401
+ return -1;
402
+ }
403
+ d->nodes[id].value = dom_str_in(d, &ev->value, ev->borrowed);
404
+ if (ev->anchor_id != 0 && dom_anchor_set(d, ev->anchor_id, id) != 0) {
405
+ return -1;
406
+ }
407
+ return dom_place(d, id);
408
+ }
409
+
410
+ case YEP_EV_ALIAS: {
411
+ uint32_t target = dom_anchor_get(d, ev->anchor_id);
412
+ if (target == UINT32_MAX) {
413
+ return -1;
414
+ }
415
+ uint32_t id = dom_new_node(d, ev, YEP_DOM_ALIAS);
416
+ if (id == UINT32_MAX) {
417
+ return -1;
418
+ }
419
+ d->nodes[id].value = dom_str_in(d, &ev->value, ev->borrowed);
420
+ d->nodes[id].target = target;
421
+ return dom_place(d, id);
422
+ }
423
+
424
+ default:
425
+ return -1;
426
+ }
427
+ }
428
+
429
+ void yep_dom_reserve(yep_dom* d, uint32_t node_hint, uint32_t str_hint) {
430
+ if (d == NULL) {
431
+ return;
432
+ }
433
+ if (node_hint > d->ncount) {
434
+ /* capacity for node_hint TOTAL nodes; growth remains on OOM */
435
+ (void)dom_grow_nodes(d, node_hint - d->ncount);
436
+ }
437
+ if (str_hint > d->str_cap) {
438
+ (void)str_grow(d, str_hint);
439
+ }
440
+ }
441
+
442
+ /* Content-derived sizing (TODO.impl/06): three SIMD count passes,
443
+ * then reserves. Every structural byte bounds a node boundary; the
444
+ * arena reserve only pays when the content copies (quoted scalars
445
+ * escape, block scalars fold) — borrowed-only documents must not
446
+ * allocate an arena at all. Hints are advisory. */
447
+ void yep_dom_prepare(yep_dom* d, const yep_text_stats* st) {
448
+ if (d == NULL || st == NULL) {
449
+ return;
450
+ }
451
+ /* a block line carries at least TWO nodes (a key and its value,
452
+ * or a dash and its entry): nl-colons cancels to ~zero on plain
453
+ * scalar maps, so the old hint undershot by 200k nodes on
454
+ * anchor-heavy (the growth memmove profiled at 5% of parse) */
455
+ size_t structural = st->comma + st->dash + st->bracket + st->brace + 8;
456
+ size_t by_line = st->nl * 2 + 8;
457
+ uint32_t node_hint = (uint32_t)(structural > by_line ? structural : by_line);
458
+ uint32_t str_hint = (st->dq + st->sq + st->pipe) > 0
459
+ ? (uint32_t)(st->dq * 16 + st->sq * 8 + st->pipe * 128 + 64)
460
+ : 0;
461
+ yep_dom_reserve(d, node_hint, str_hint);
462
+ }
463
+
464
+ void yep_dom_prepare_len(yep_dom* d, size_t len) {
465
+ if (d == NULL) {
466
+ return;
467
+ }
468
+ yep_dom_reserve(d, (uint32_t)(len / 2 + 64), (uint32_t)(len / 16 + 256));
469
+ }
470
+
471
+ yep_dom* yep_dom_create(const yep_allocator* sys) {
472
+ if (sys == NULL) {
473
+ return NULL;
474
+ }
475
+ yep_pool* pool = yep_pool_create(sys, 8192);
476
+ if (pool == NULL) {
477
+ return NULL;
478
+ }
479
+ yep_dom* d = yep_alloc(sys, sizeof(yep_dom));
480
+ if (d == NULL) {
481
+ yep_pool_destroy(pool);
482
+ return NULL;
483
+ }
484
+ memset(d, 0, sizeof(*d));
485
+ d->sys = sys;
486
+ d->pool = pool;
487
+ /* #157: the handle pool is lazy — two allocations and a mutex per
488
+ * document that decode-only consumers (CBOR load, serialize, free)
489
+ * never touch; yep_dom_handles() materializes it on first use */
490
+ d->handles = NULL;
491
+ d->child_cache = NULL;
492
+ d->child_cache_id = UINT32_MAX;
493
+ d->child_cache_len = 0;
494
+ if (yep_mutex_init(&d->midx.mu) != 0) {
495
+ yep_pool_destroy(pool);
496
+ yep_free(sys, d);
497
+ return NULL;
498
+ }
499
+ d->midx.mu_ready = 1;
500
+ return d;
501
+ }
502
+
503
+ /* Lazy handle pool, mirroring the map-index's discipline (mapindex.c):
504
+ * EVERY access rides the dom's lazy-init mutex — read-only sharing
505
+ * means multiple threads may race the FIRST handle creation on a
506
+ * shared document (Threads.ReadOnlySharing; the TSAN catch on the
507
+ * first cut). The uncontended lock costs less than the hpool's own
508
+ * per-allocation lock that follows. */
509
+ struct yep_hpool* yep_dom_handles(yep_dom* d) {
510
+ if (d == NULL || !d->midx.mu_ready) {
511
+ return NULL;
512
+ }
513
+ yep_mutex_lock(&d->midx.mu);
514
+ if (d->handles == NULL) {
515
+ d->handles = yep_hpool_create(d->sys);
516
+ }
517
+ struct yep_hpool* h = d->handles;
518
+ yep_mutex_unlock(&d->midx.mu);
519
+ return h;
520
+ }
521
+
522
+ /* #377 (ruby #168): seq_at/map_at were O(i) sibling walks — the
523
+ * bindings' per-element loops made an 80k-row sequence quadratic
524
+ * (the relaton index: 238-328s where stdlib takes ~3s). The bulk
525
+ * drain (yeptris_node_children) fixed the bindings; THIS fixes the
526
+ * accessor: the last indexed container's child ids cache under the
527
+ * lazy-init mutex, O(1) after the first call. Invalidated by every
528
+ * child-list mutation (dom_invalidate_child_cache). */
529
+ void dom_invalidate_child_cache(yep_dom* d) {
530
+ if (d == NULL) {
531
+ return;
532
+ }
533
+ d->child_cache_id = UINT32_MAX;
534
+ d->child_cache_len = 0;
535
+ }
536
+
537
+ /* Returns child id at index (UINT32_MAX when out of range); the
538
+ * container's child count rides *count_out. */
539
+ uint32_t dom_indexed_child(yep_dom* d, uint32_t cid, size_t index, uint32_t* count_out) {
540
+ const yep_dnode* n = yep_dom_node(d, cid);
541
+ if (n == NULL) {
542
+ *count_out = 0;
543
+ return UINT32_MAX;
544
+ }
545
+ *count_out = n->count;
546
+ if (d->child_cache_id == cid) {
547
+ if (index < d->child_cache_len) {
548
+ return d->child_cache[index];
549
+ }
550
+ return UINT32_MAX;
551
+ }
552
+ /* miss: build once under the lazy-init mutex (Threads.
553
+ * ReadOnlySharing — concurrent first calls race here exactly like
554
+ * the handle pool's) */
555
+ yep_mutex_lock(&d->midx.mu);
556
+ if (d->child_cache_id != cid) {
557
+ yep_free(d->sys, d->child_cache);
558
+ d->child_cache = NULL;
559
+ d->child_cache_len = 0;
560
+ if (n->count > 0) {
561
+ uint32_t* arr = yep_alloc(d->sys, n->count * sizeof(uint32_t));
562
+ if (arr != NULL) {
563
+ uint32_t k = 0;
564
+ for (uint32_t id = n->first_child; id != UINT32_MAX && k < n->count;) {
565
+ const yep_dnode* cn = yep_dom_node(d, id);
566
+ if (cn == NULL) {
567
+ break;
568
+ }
569
+ arr[k++] = id;
570
+ id = cn->next_sibling;
571
+ }
572
+ d->child_cache = arr;
573
+ d->child_cache_len = k;
574
+ }
575
+ }
576
+ d->child_cache_id = cid;
577
+ }
578
+ yep_mutex_unlock(&d->midx.mu);
579
+ if (index < d->child_cache_len) {
580
+ return d->child_cache[index];
581
+ }
582
+ return UINT32_MAX;
583
+ }
584
+
585
+ void yep_dom_destroy(yep_dom* d) {
586
+ if (d == NULL) {
587
+ return;
588
+ }
589
+ yep_free(d->sys, d->str);
590
+ yep_midx_destroy(d);
591
+ yep_free(d->sys, d->anchor_nodes);
592
+ yep_free(d->sys, d->mut_att);
593
+ yep_free(d->sys, d->mut_depth);
594
+ yep_hpool_destroy(d->handles);
595
+ yep_free(d->sys, d->child_cache);
596
+ yep_pool_destroy(d->pool);
597
+ yep_free(d->sys, d);
598
+ }
599
+
600
+ /* ------------------------------------------------- direct builders */
601
+ /* Two direct builders, one set of laws: yep_dom_build_json walks a
602
+ * whole strictly-validated JSON buffer (TODO.impl/27, the parse_json
603
+ * seam); dom_on_flow_json walks an engine-validated span mid-document
604
+ * (TODO.restructure/50, the sink fast path). Both create nodes through
605
+ * dom_open_node and place them through dom_place on the DOM's own
606
+ * stack — the event sink's pairing/link/anchor laws, shared, so the
607
+ * trees cannot diverge (the flow-direct-diff ctest pins it). */
608
+
609
+ #include "parse/scalars.h"
610
+ #include "resolve/resolver.h"
611
+ #include "scan/json.h"
612
+ #include "scan/scan.h"
613
+
614
+ static const yep_resolver* dom_resolver(const yep_dom* d) {
615
+ return d->resolver != NULL ? d->resolver : yep_resolver_core12();
616
+ }
617
+
618
+ typedef struct {
619
+ yep_dom* d;
620
+ const char* p;
621
+ size_t len;
622
+ size_t i;
623
+ } jbuilder;
624
+
625
+ /* Whole-buffer JSON: value-first recursion (the grammar is validated,
626
+ * the walk only advances). */
627
+ static int jb_value(jbuilder* b);
628
+
629
+ static int jb_string_node(jbuilder* b) {
630
+ uint32_t id = dom_open_node(b->d, YEP_DOM_SCALAR, NULL, NULL, 0, 0, 0, 0);
631
+ if (id == UINT32_MAX) {
632
+ return -1;
633
+ }
634
+ yep_dnode* n = &b->d->nodes[id];
635
+ size_t close;
636
+ int has_esc = 0;
637
+ size_t start = b->i;
638
+ if (!yep_json_string(b->p, b->len, &b->i, &close, &has_esc)) {
639
+ return -2;
640
+ }
641
+ if (has_esc) {
642
+ uint32_t span = (uint32_t)(close - start - 1);
643
+ char* dst = yep_dom_str_tail(b->d, span);
644
+ if (dst == NULL) {
645
+ return -1;
646
+ }
647
+ n->value = yep_dom_str_commit(
648
+ b->d, yep_finish_double_into(b->p, (uint32_t)(start + 1), (uint32_t)close, dst, span));
649
+ } else {
650
+ yep_view v = {b->p + start + 1, (uint32_t)(close - start - 1)};
651
+ n->value = dom_str_in(b->d, &v, 1);
652
+ }
653
+ n->style = YEP_STYLE_DOUBLE_QUOTED;
654
+ n->tag_id = 0; /* str */
655
+ return dom_place(b->d, id) == 0 ? 0 : -1;
656
+ }
657
+
658
+ static int jb_scalar_node(jbuilder* b) {
659
+ uint32_t id = dom_open_node(b->d, YEP_DOM_SCALAR, NULL, NULL, 0, 0, 0, 0);
660
+ if (id == UINT32_MAX) {
661
+ return -1;
662
+ }
663
+ yep_dnode* n = &b->d->nodes[id];
664
+ size_t start = b->i;
665
+ size_t i = b->i;
666
+ const yep_resolver* r = dom_resolver(b->d);
667
+ if (b->p[i] == 't') {
668
+ if (!yep_json_literal(b->p, b->len, &i, "true")) {
669
+ return -2;
670
+ }
671
+ } else if (b->p[i] == 'f') {
672
+ if (!yep_json_literal(b->p, b->len, &i, "false")) {
673
+ return -2;
674
+ }
675
+ } else if (b->p[i] == 'n') {
676
+ if (!yep_json_literal(b->p, b->len, &i, "null")) {
677
+ return -2;
678
+ }
679
+ } else {
680
+ if (!yep_json_number(b->p, b->len, &i)) {
681
+ return -2;
682
+ }
683
+ }
684
+ b->i = i;
685
+ yep_view v = {b->p + start, (uint32_t)(i - start)};
686
+ n->value = dom_str_in(b->d, &v, 1);
687
+ n->style = YEP_STYLE_PLAIN;
688
+ n->implicit = 1;
689
+ n->tag_id = r->resolve(NULL, v.p, v.len);
690
+ return dom_place(b->d, id) == 0 ? 0 : -1;
691
+ }
692
+
693
+ static void jb_ws(jbuilder* b) {
694
+ while (b->i < b->len) {
695
+ char c = b->p[b->i];
696
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
697
+ b->i++;
698
+ } else {
699
+ break;
700
+ }
701
+ }
702
+ }
703
+
704
+ static int jb_value(jbuilder* b) {
705
+ yep_dom* d = b->d;
706
+ jb_ws(b);
707
+ if (b->i >= b->len) {
708
+ return -2;
709
+ }
710
+ char c = b->p[b->i];
711
+ if (c == '{' || c == '[') {
712
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {
713
+ return -2;
714
+ }
715
+ int map = (c == '{');
716
+ b->i++;
717
+ uint32_t id =
718
+ dom_open_node(d, map ? YEP_DOM_MAPPING : YEP_DOM_SEQUENCE, NULL, NULL, 0, 0, 0, 1);
719
+ if (id == UINT32_MAX) {
720
+ return -1;
721
+ }
722
+ if (dom_place(d, id) != 0) {
723
+ return -1;
724
+ }
725
+ d->map_pending_key[d->depth] = 0;
726
+ d->stack[d->depth++] = id;
727
+ jb_ws(b);
728
+ if (b->i < b->len && b->p[b->i] == (map ? '}' : ']')) {
729
+ b->i++;
730
+ d->depth--;
731
+ return 0;
732
+ }
733
+ for (;;) {
734
+ int rc = jb_value(b);
735
+ if (rc != 0) {
736
+ return rc;
737
+ }
738
+ jb_ws(b);
739
+ if (map) {
740
+ /* a value must be followed by , or } — the map's key
741
+ * slot is pending until the ':' value completes */
742
+ if (b->i < b->len && b->p[b->i] == ':' && d->map_pending_key[d->depth - 1] != 0) {
743
+ b->i++;
744
+ rc = jb_value(b);
745
+ if (rc != 0) {
746
+ return rc;
747
+ }
748
+ jb_ws(b);
749
+ }
750
+ }
751
+ if (b->i >= b->len) {
752
+ return -2;
753
+ }
754
+ if (b->p[b->i] == ',') {
755
+ b->i++;
756
+ continue;
757
+ }
758
+ if (b->p[b->i] == (map ? '}' : ']')) {
759
+ b->i++;
760
+ d->depth--;
761
+ return 0;
762
+ }
763
+ return -2;
764
+ }
765
+ }
766
+ if (c == '"') {
767
+ return jb_string_node(b);
768
+ }
769
+ return jb_scalar_node(b);
770
+ }
771
+
772
+ int yep_dom_build_json(yep_dom* d, const char* buf, size_t len) {
773
+ jbuilder b = {d, buf, len, 0};
774
+ int rc = jb_value(&b);
775
+ if (rc != 0) {
776
+ return rc;
777
+ }
778
+ jb_ws(&b);
779
+ if (b.i != len) {
780
+ return -2;
781
+ }
782
+ return 0;
783
+ }
784
+
785
+ /* --- the flow fast path (TODO.restructure/50) --- */
786
+
787
+ /* --- the fused flow build (TODO.restructure/53) --- */
788
+
789
+ /* One walk validates AND builds: the shared grammar walker drives,
790
+ * nodes are created through the DOM's own laws (dom_open_node,
791
+ * dom_place on the live stack above the entry depth). Staging: the
792
+ * root's placement and anchor binding defer to commit; scratch
793
+ * nodes live past the caller's ncount until then — ANY deviation
794
+ * restores both counters with zero live-state mutation, so the
795
+ * engine's fallback paths are exactly as before. */
796
+ static void dom_flow_stage_reset(yep_dom* d) {
797
+ d->ncount = d->flow_stage_base;
798
+ d->str_len = d->flow_stage_str;
799
+ d->flow_staged = 0;
800
+ }
801
+
802
+ int dom_on_flow_build(void* ctx, const char* p, size_t open, size_t len, uint32_t line,
803
+ size_t line_start, yep_view anchor, yep_view tag, uint32_t anchor_id,
804
+ int max_depth, size_t* close) {
805
+ yep_dom* d = (yep_dom*)ctx;
806
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {
807
+ return 0; /* the event path owns the depth-limit error shape */
808
+ }
809
+ int entry_depth = d->depth;
810
+ d->flow_stage_base = d->ncount;
811
+ d->flow_stage_str = d->str_len;
812
+
813
+ uint32_t root = dom_open_node(d, p[open] == '[' ? YEP_DOM_SEQUENCE : YEP_DOM_MAPPING, &tag,
814
+ &anchor, 0, 0, 0, 1);
815
+ if (root == UINT32_MAX) {
816
+ goto fail;
817
+ }
818
+ d->map_pending_key[d->depth] = 0;
819
+ d->stack[d->depth++] = root;
820
+
821
+ const yep_resolver* r = dom_resolver(d);
822
+ yep_json_walk w;
823
+ yep_json_tok t;
824
+ yep_json_walk_init(&w, p, len, open, max_depth);
825
+ w.strict = d->flow_strict; /* the strict-JSON route rides the same
826
+ fused walk (TODO.restructure/81) */
827
+ for (;;) {
828
+ yep_jw_status st = yep_json_walk_next(&w, &t);
829
+ if (st == YEP_JW_REJECT) {
830
+ d->depth = entry_depth;
831
+ dom_flow_stage_reset(d);
832
+ return 0; /* not JSON-class: the general kernel */
833
+ }
834
+ if (st == YEP_JW_DONE) {
835
+ break;
836
+ }
837
+ switch (t.cls) {
838
+ case '[':
839
+ case '{': {
840
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {
841
+ goto fail;
842
+ }
843
+ uint32_t cid = dom_open_node(d, t.cls == '[' ? YEP_DOM_SEQUENCE : YEP_DOM_MAPPING, NULL,
844
+ NULL, 0, 0, 0, 1);
845
+ if (cid == UINT32_MAX) {
846
+ goto fail;
847
+ }
848
+ if (dom_place(d, cid) != 0) {
849
+ goto fail;
850
+ }
851
+ d->map_pending_key[d->depth] = 0;
852
+ d->stack[d->depth++] = cid;
853
+ break;
854
+ }
855
+ case ']':
856
+ case '}':
857
+ d->depth--;
858
+ break;
859
+ case '"': {
860
+ uint32_t sid =
861
+ dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, YEP_STYLE_DOUBLE_QUOTED, 0, 0);
862
+ if (sid == UINT32_MAX) {
863
+ goto fail;
864
+ }
865
+ if (t.has_escape) {
866
+ uint32_t span = (uint32_t)(t.end - t.at - 2);
867
+ char* dst = yep_dom_str_tail(d, span);
868
+ if (dst == NULL) {
869
+ goto fail;
870
+ }
871
+ d->nodes[sid].value =
872
+ yep_dom_str_commit(d, yep_finish_double_into(p, (uint32_t)(t.at + 1),
873
+ (uint32_t)(t.end - 1), dst, span));
874
+ } else {
875
+ yep_view v = {p + t.at + 1, (uint32_t)(t.end - t.at - 2)};
876
+ d->nodes[sid].value = dom_str_in(d, &v, 1);
877
+ }
878
+ if (dom_place(d, sid) != 0) {
879
+ goto fail;
880
+ }
881
+ break;
882
+ }
883
+ default: { /* number or literal: the walker validated the span */
884
+ uint32_t sid = dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, YEP_STYLE_PLAIN, 1, 0);
885
+ if (sid == UINT32_MAX) {
886
+ goto fail;
887
+ }
888
+ yep_view v = {p + t.at, (uint32_t)(t.end - t.at)};
889
+ d->nodes[sid].value = dom_str_in(d, &v, 1);
890
+ if (t.cls == '#') {
891
+ d->nodes[sid].tag_id = yep_resolve_number(r, t.is_float);
892
+ } else if (d->flow_strict) {
893
+ /* strict JSON: the walker validated the span is exactly
894
+ * one of the three RFC 8259 words — no resolver dispatch */
895
+ d->nodes[sid].tag_id = (v.p[0] == 'n') ? YEPTRIS_TAG_NULL : YEPTRIS_TAG_BOOL;
896
+ } else {
897
+ d->nodes[sid].tag_id = r->resolve(NULL, v.p, v.len);
898
+ }
899
+ if (dom_place(d, sid) != 0) {
900
+ goto fail;
901
+ }
902
+ break;
903
+ }
904
+ }
905
+ }
906
+ d->depth = entry_depth;
907
+ if (w.long_key) {
908
+ dom_flow_stage_reset(d);
909
+ return 2; /* grammar-valid; pass 2 owns the simple-key error */
910
+ }
911
+ d->flow_staged = 1;
912
+ d->flow_stage_root = root;
913
+ d->flow_stage_anchor = anchor_id;
914
+ *close = w.close;
915
+ return 1;
916
+ fail:
917
+ d->depth = entry_depth;
918
+ dom_flow_stage_reset(d);
919
+ return -1;
920
+ }
921
+
922
+ int dom_on_flow_commit(void* ctx) {
923
+ yep_dom* d = (yep_dom*)ctx;
924
+ if (!d->flow_staged) {
925
+ return 0;
926
+ }
927
+ d->flow_staged = 0;
928
+ if (d->flow_stage_anchor != 0 &&
929
+ dom_anchor_set(d, d->flow_stage_anchor, d->flow_stage_root) != 0) {
930
+ return -1;
931
+ }
932
+ if (dom_place(d, d->flow_stage_root) != 0) {
933
+ return -1;
934
+ }
935
+ return 1;
936
+ }
937
+
938
+ int dom_from_tape(yep_dom* d, const yeptris_json_tape* t) {
939
+ if (d == NULL || t == NULL || t->_src == NULL) {
940
+ return -1;
941
+ }
942
+ /* the columns are the one representation valid on every route
943
+ * (strict: native; lenient fused: lazily materialized from the
944
+ * records; scalar-root lenient: native column writes) */
945
+ if (yeptris_tape_columns((yeptris_json_tape*)t) != 0) {
946
+ return -1;
947
+ }
948
+ if (t->kinds == NULL) {
949
+ return -1;
950
+ }
951
+ d->input_base = (const char*)t->_src;
952
+ d->input_len = t->_srclen;
953
+ const char* src = (const char*)t->_src;
954
+ d->depth = 0;
955
+ for (uint32_t i = 0; i < t->count; i++) {
956
+ const uint8_t kind = t->kinds[i];
957
+ const uint32_t len = t->lens[i];
958
+ const uint32_t off = t->offs[i];
959
+ switch (kind) {
960
+ case YEP_T_DOC:
961
+ break; /* the stream boundary record */
962
+ case YEP_T_SEQ_OPEN:
963
+ case YEP_T_MAP_OPEN: {
964
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {
965
+ return -1;
966
+ }
967
+ uint32_t id =
968
+ dom_open_node(d, kind == YEP_T_SEQ_OPEN ? YEP_DOM_SEQUENCE : YEP_DOM_MAPPING, NULL,
969
+ NULL, 0, 0, 0, 1);
970
+ if (id == UINT32_MAX || dom_place(d, id) != 0) {
971
+ return -1;
972
+ }
973
+ d->map_pending_key[d->depth] = 0;
974
+ d->stack[d->depth++] = id;
975
+ break;
976
+ }
977
+ case YEP_T_CLOSE:
978
+ if (d->depth == 0) {
979
+ return -1;
980
+ }
981
+ d->depth--;
982
+ break;
983
+ case YEP_T_NULL:
984
+ case YEP_T_TRUE:
985
+ case YEP_T_FALSE:
986
+ case YEP_T_INT:
987
+ case YEP_T_FLOAT:
988
+ case YEP_T_NUM:
989
+ case YEP_T_STR: {
990
+ yep_tag_id tag = YEPTRIS_TAG_STR;
991
+ uint8_t style = YEP_STYLE_PLAIN;
992
+ int implicit = 0;
993
+ switch (kind) {
994
+ case YEP_T_NULL:
995
+ tag = YEPTRIS_TAG_NULL;
996
+ implicit = 1;
997
+ break;
998
+ case YEP_T_TRUE:
999
+ case YEP_T_FALSE:
1000
+ tag = YEPTRIS_TAG_BOOL;
1001
+ implicit = 1;
1002
+ break;
1003
+ case YEP_T_INT:
1004
+ case YEP_T_FLOAT:
1005
+ case YEP_T_NUM:
1006
+ tag = YEPTRIS_TAG_INT;
1007
+ implicit = 1;
1008
+ if (kind == YEP_T_NUM) {
1009
+ /* the lenient route's deferred contract: the span
1010
+ * carried NO number grammar at parse — classify
1011
+ * now (the tape convert's own authority) */
1012
+ size_t adv = 0;
1013
+ int flt = 0;
1014
+ if (yep_json_number_shape(src + off, len, &adv, &flt) == 0 || adv != len) {
1015
+ return -1; /* malformed: the strict route rejected at parse */
1016
+ }
1017
+ tag = flt ? YEPTRIS_TAG_FLOAT : YEPTRIS_TAG_INT;
1018
+ } else if (kind == YEP_T_FLOAT) {
1019
+ tag = YEPTRIS_TAG_FLOAT;
1020
+ }
1021
+ break;
1022
+ default:
1023
+ style = YEP_STYLE_DOUBLE_QUOTED;
1024
+ break;
1025
+ }
1026
+ uint32_t id =
1027
+ dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, style, (uint8_t)implicit, 0);
1028
+ if (id == UINT32_MAX) {
1029
+ return -1;
1030
+ }
1031
+ if (kind == YEP_T_STR) {
1032
+ /* the record's span is the INNER bytes (quotes off) */
1033
+ if (memchr(src + off, '\\', len) != NULL) {
1034
+ /* escaped: unescape into the arena (the fused
1035
+ * builder's arm, byte for byte) */
1036
+ char* dst = yep_dom_str_tail(d, len);
1037
+ if (dst == NULL) {
1038
+ return -1;
1039
+ }
1040
+ d->nodes[id].value = yep_dom_str_commit(
1041
+ d, yep_finish_double_into(src, off, off + len, dst, len));
1042
+ } else {
1043
+ yep_view v = {src + off, len};
1044
+ d->nodes[id].value = dom_str_in(d, &v, 1);
1045
+ }
1046
+ } else {
1047
+ yep_view v = {src + off, len};
1048
+ d->nodes[id].value = dom_str_in(d, &v, 1);
1049
+ }
1050
+ d->nodes[id].tag_id = tag;
1051
+ if (dom_place(d, id) != 0) {
1052
+ return -1;
1053
+ }
1054
+ break;
1055
+ }
1056
+ default:
1057
+ return -1; /* a CONT run or an unknown kind: neither reaches a
1058
+ strict-legal tape (CONT only past 16 MiB tokens) */
1059
+ }
1060
+ }
1061
+ return d->depth == 0 ? 0 : -1;
1062
+ }
1063
+
1064
+ void dom_on_flow_rollback(void* ctx) {
1065
+ yep_dom* d = (yep_dom*)ctx;
1066
+ if (d->flow_staged) {
1067
+ dom_flow_stage_reset(d);
1068
+ }
1069
+ }
1070
+
1071
+ /* The sink's block-open fast path (TODO.restructure/57): a `key:` line
1072
+ * whose value follows becomes the map node plus its pending key in one
1073
+ * call — the DOM stack mirrors what the MAP_START event would have
1074
+ * pushed. 0 only for the depth cap (the engine then takes the events,
1075
+ * which own the error). */
1076
+ int dom_on_block_open(void* ctx, const yep_view* key, uint32_t line, uint16_t key_col) {
1077
+ yep_dom* d = (yep_dom*)ctx;
1078
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {
1079
+ return 0;
1080
+ }
1081
+ const yep_resolver* r = dom_resolver(d);
1082
+ uint32_t mid = dom_open_node(d, YEP_DOM_MAPPING, NULL, NULL, 0, 0, 0, 0);
1083
+ if (mid == UINT32_MAX) {
1084
+ return -1;
1085
+ }
1086
+ if (dom_place(d, mid) != 0) {
1087
+ return -1;
1088
+ }
1089
+ d->map_pending_key[d->depth] = 0;
1090
+ d->stack[d->depth++] = mid;
1091
+ uint32_t kid = dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, YEP_STYLE_PLAIN, 1, 0);
1092
+ if (kid == UINT32_MAX) {
1093
+ d->depth--;
1094
+ return -1;
1095
+ }
1096
+ d->nodes[kid].value = dom_str_in(d, key, 1);
1097
+ d->nodes[kid].tag_id = r->resolve(NULL, key->p, key->len);
1098
+ return dom_place(d, kid) == 0 ? 1 : -1;
1099
+ }
1100
+
1101
+ /* The sink's dash-item fast path (TODO.restructure/78): one node
1102
+ * through the node-init law, placed into the sequence the engine
1103
+ * opened before offering — node-for-node the event path's item. */
1104
+ int dom_on_block_item(void* ctx, const yep_block_value* v) {
1105
+ yep_dom* d = (yep_dom*)ctx;
1106
+ const yep_resolver* r = dom_resolver(d);
1107
+ uint32_t vid = dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, YEP_STYLE_PLAIN, 1, 0);
1108
+ if (vid == UINT32_MAX) {
1109
+ return -1;
1110
+ }
1111
+ d->nodes[vid].value = dom_str_in(d, &v->value, v->borrowed);
1112
+ d->nodes[vid].tag_id = r->resolve(NULL, v->value.p, v->value.len);
1113
+ return dom_place(d, vid) == 0 ? 1 : -1;
1114
+ }
1115
+
1116
+ /* The sink's block fast path (TODO.restructure/54): one classified
1117
+ * `key: value` line becomes two nodes — the key scalar resolved like
1118
+ * every implicit key event, the value through its class — placed with
1119
+ * the shared pairing law. The engine offers the line BEFORE emitting;
1120
+ * returning 0 here (only for an unbound alias target, which cannot
1121
+ * happen when the engine resolved the name) falls back to the two
1122
+ * events with nothing built. */
1123
+ int dom_on_block_pair(void* ctx, const yep_view* key, const yep_block_value* v, uint32_t line,
1124
+ uint16_t key_col, uint16_t val_col) {
1125
+ yep_dom* d = (yep_dom*)ctx;
1126
+ uint32_t target = 0;
1127
+ if (v->cls == YEP_LVAL_ALIAS) {
1128
+ target = dom_anchor_get(d, v->anchor_id);
1129
+ if (target == UINT32_MAX) {
1130
+ return 0;
1131
+ }
1132
+ }
1133
+ const yep_resolver* r = dom_resolver(d);
1134
+ uint32_t kid = dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, YEP_STYLE_PLAIN, 1, 0);
1135
+ if (kid == UINT32_MAX) {
1136
+ return -1;
1137
+ }
1138
+ d->nodes[kid].value = dom_str_in(d, key, 1);
1139
+ d->nodes[kid].tag_id = r->resolve(NULL, key->p, key->len);
1140
+ if (dom_place(d, kid) != 0) {
1141
+ return -1;
1142
+ }
1143
+ if (v->cls == YEP_LVAL_ALIAS) {
1144
+ uint32_t vid = dom_open_node(d, YEP_DOM_ALIAS, NULL, NULL, 0, 0, 0, 0);
1145
+ if (vid == UINT32_MAX) {
1146
+ return -1;
1147
+ }
1148
+ d->nodes[vid].value = dom_str_in(d, &v->value, 1); /* borrowed — the
1149
+ event path stamps it too now (73); the gates compare decoded
1150
+ views, not arena placement */
1151
+ d->nodes[vid].target = target;
1152
+ return dom_place(d, vid) == 0 ? 1 : -1;
1153
+ }
1154
+ int anchored = (v->cls == YEP_LVAL_ANCHOR_PLAIN);
1155
+ uint32_t vid = dom_open_node(d, YEP_DOM_SCALAR, NULL, anchored ? &v->anchor : NULL, v->borrowed,
1156
+ YEP_STYLE_PLAIN, 1, 0);
1157
+ if (vid == UINT32_MAX) {
1158
+ return -1;
1159
+ }
1160
+ d->nodes[vid].value = dom_str_in(d, &v->value, v->borrowed);
1161
+ d->nodes[vid].tag_id = r->resolve(NULL, v->value.p, v->value.len);
1162
+ if (anchored && v->anchor_id != 0 && dom_anchor_set(d, v->anchor_id, vid) != 0) {
1163
+ return -1;
1164
+ }
1165
+ return dom_place(d, vid) == 0 ? 1 : -1;
1166
+ }
1167
+
1168
+ /* The direct scalar path (TODO.restructure/79): the YEP_EV_SCALAR
1169
+ * case's node build, minus the event seam. The engine resolved
1170
+ * tag_id (the typing SSOT); everything else is the case body. */
1171
+ int dom_on_scalar(void* ctx, const yep_view* value, const yep_view* tag, const yep_view* anchor,
1172
+ uint32_t anchor_id, uint8_t tag_id, uint8_t style, uint8_t implicit, uint8_t flow,
1173
+ int borrowed) {
1174
+ yep_dom* d = (yep_dom*)ctx;
1175
+ uint32_t id = dom_open_node(d, YEP_DOM_SCALAR, tag, anchor, borrowed, style, implicit, flow);
1176
+ if (id == UINT32_MAX) {
1177
+ return -1;
1178
+ }
1179
+ d->nodes[id].value = dom_str_in(d, value, borrowed);
1180
+ d->nodes[id].tag_id = tag_id;
1181
+ if (anchor_id != 0 && dom_anchor_set(d, anchor_id, id) != 0) {
1182
+ return -1;
1183
+ }
1184
+ return dom_place(d, id);
1185
+ }
1186
+
1187
+ const yep_dnode* yep_dom_node(const yep_dom* d, uint32_t id) {
1188
+ if (d == NULL || id >= d->ncount) {
1189
+ return NULL;
1190
+ }
1191
+ return &d->nodes[id];
1192
+ }