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,497 @@
1
+ /* yeptris_native.c — Ruby C-API materializer over libyeptris visit
2
+ * (TODO.restructure/22). One Ruby→C call builds the whole object
3
+ * graph via rb_hash_new / rb_ary_push / rb_str_new_len — the same
4
+ * shape as JSON.parse, so the binding can beat it on the fused JSON
5
+ * path. The extension is optional: LoadError falls back to the FFI
6
+ * Marshal ladder. */
7
+
8
+ #include <ruby.h>
9
+ #include <ruby/encoding.h>
10
+
11
+ #include <stdlib.h>
12
+ #include <string.h>
13
+
14
+ #include <yeptris/api.h>
15
+ #include <yeptris/error.h>
16
+ #include <yeptris/resolve.h>
17
+ #include <yeptris/visit.h>
18
+
19
+ #define YEP_RB_MAX_DEPTH 1024
20
+
21
+ static rb_encoding* utf8_enc;
22
+
23
+ typedef struct {
24
+ VALUE stack[YEP_RB_MAX_DEPTH];
25
+ VALUE keys[YEP_RB_MAX_DEPTH]; /* pending map key at this depth */
26
+ int is_map[YEP_RB_MAX_DEPTH];
27
+ int sp;
28
+ VALUE root;
29
+ VALUE anchors; /* Hash name=>object for YAML identity */
30
+ VALUE pending_anchor; /* String name awaiting the next value */
31
+ int failed;
32
+ } rb_ctx;
33
+
34
+ static void rb_fail(rb_ctx* c) {
35
+ c->failed = 1;
36
+ }
37
+
38
+ static int rb_push_value(rb_ctx* c, VALUE v) {
39
+ if (c->failed) {
40
+ return -1;
41
+ }
42
+ /* bind pending anchor */
43
+ if (!NIL_P(c->pending_anchor) && !NIL_P(c->anchors)) {
44
+ rb_hash_aset(c->anchors, c->pending_anchor, v);
45
+ c->pending_anchor = Qnil;
46
+ }
47
+ if (c->sp == 0) {
48
+ c->root = v;
49
+ return 0;
50
+ }
51
+ VALUE parent = c->stack[c->sp - 1];
52
+ if (c->is_map[c->sp - 1]) {
53
+ VALUE key = c->keys[c->sp - 1];
54
+ if (NIL_P(key)) {
55
+ rb_fail(c);
56
+ return -1;
57
+ }
58
+ rb_hash_aset(parent, key, v);
59
+ c->keys[c->sp - 1] = Qnil;
60
+ } else {
61
+ rb_ary_push(parent, v);
62
+ }
63
+ return 0;
64
+ }
65
+
66
+ static int on_null(void* ctx) {
67
+ return rb_push_value((rb_ctx*)ctx, Qnil);
68
+ }
69
+
70
+ static int on_bool(void* ctx, int truthy) {
71
+ return rb_push_value((rb_ctx*)ctx, truthy ? Qtrue : Qfalse);
72
+ }
73
+
74
+ static int on_int(void* ctx, int64_t v) {
75
+ return rb_push_value((rb_ctx*)ctx, LL2NUM(v));
76
+ }
77
+
78
+ static int on_float(void* ctx, double v) {
79
+ return rb_push_value((rb_ctx*)ctx, DBL2NUM(v));
80
+ }
81
+
82
+ static rb_encoding* utf8_enc;
83
+
84
+ static VALUE rb_utf8_str(const char* p, size_t len) {
85
+ return rb_enc_str_new(p, (long)len, utf8_enc);
86
+ }
87
+
88
+ /* One-shot interned string (no intermediate alloc) — keys and the
89
+ * short repeated values ("a"/"b"/…) share one VALUE. */
90
+ static VALUE rb_utf8_interned(const char* p, size_t len) {
91
+ return rb_enc_interned_str(p, (long)len, utf8_enc);
92
+ }
93
+
94
+ static int on_string(void* ctx, const char* p, size_t len) {
95
+ rb_ctx* c = (rb_ctx*)ctx;
96
+ /* short strings are almost always repeated tokens in JSON corpora;
97
+ * intern them. Longer payloads stay unique. */
98
+ VALUE s = (len <= 16) ? rb_utf8_interned(p, len) : rb_utf8_str(p, len);
99
+ return rb_push_value(c, s);
100
+ }
101
+
102
+ static int on_key(void* ctx, const char* p, size_t len) {
103
+ rb_ctx* c = (rb_ctx*)ctx;
104
+ if (c->sp == 0 || !c->is_map[c->sp - 1]) {
105
+ rb_fail(c);
106
+ return -1;
107
+ }
108
+ c->keys[c->sp - 1] = rb_utf8_interned(p, len);
109
+ return 0;
110
+ }
111
+
112
+ static int on_seq_start(void* ctx) {
113
+ rb_ctx* c = (rb_ctx*)ctx;
114
+ if (c->failed || c->sp >= YEP_RB_MAX_DEPTH) {
115
+ rb_fail(c);
116
+ return -1;
117
+ }
118
+ VALUE a = rb_ary_new();
119
+ /* bind anchor to the container before placing it */
120
+ if (!NIL_P(c->pending_anchor) && !NIL_P(c->anchors)) {
121
+ rb_hash_aset(c->anchors, c->pending_anchor, a);
122
+ c->pending_anchor = Qnil;
123
+ }
124
+ if (c->sp == 0) {
125
+ c->root = a;
126
+ } else {
127
+ VALUE parent = c->stack[c->sp - 1];
128
+ if (c->is_map[c->sp - 1]) {
129
+ VALUE key = c->keys[c->sp - 1];
130
+ if (NIL_P(key)) {
131
+ rb_fail(c);
132
+ return -1;
133
+ }
134
+ rb_hash_aset(parent, key, a);
135
+ c->keys[c->sp - 1] = Qnil;
136
+ } else {
137
+ rb_ary_push(parent, a);
138
+ }
139
+ }
140
+ c->stack[c->sp] = a;
141
+ c->is_map[c->sp] = 0;
142
+ c->keys[c->sp] = Qnil;
143
+ c->sp++;
144
+ return 0;
145
+ }
146
+
147
+ static int on_seq_end(void* ctx) {
148
+ rb_ctx* c = (rb_ctx*)ctx;
149
+ if (c->sp <= 0) {
150
+ rb_fail(c);
151
+ return -1;
152
+ }
153
+ c->sp--;
154
+ return 0;
155
+ }
156
+
157
+ static int on_map_start(void* ctx) {
158
+ rb_ctx* c = (rb_ctx*)ctx;
159
+ if (c->failed || c->sp >= YEP_RB_MAX_DEPTH) {
160
+ rb_fail(c);
161
+ return -1;
162
+ }
163
+ VALUE h = rb_hash_new();
164
+ if (!NIL_P(c->pending_anchor) && !NIL_P(c->anchors)) {
165
+ rb_hash_aset(c->anchors, c->pending_anchor, h);
166
+ c->pending_anchor = Qnil;
167
+ }
168
+ if (c->sp == 0) {
169
+ c->root = h;
170
+ } else {
171
+ VALUE parent = c->stack[c->sp - 1];
172
+ if (c->is_map[c->sp - 1]) {
173
+ VALUE key = c->keys[c->sp - 1];
174
+ if (NIL_P(key)) {
175
+ rb_fail(c);
176
+ return -1;
177
+ }
178
+ rb_hash_aset(parent, key, h);
179
+ c->keys[c->sp - 1] = Qnil;
180
+ } else {
181
+ rb_ary_push(parent, h);
182
+ }
183
+ }
184
+ c->stack[c->sp] = h;
185
+ c->is_map[c->sp] = 1;
186
+ c->keys[c->sp] = Qnil;
187
+ c->sp++;
188
+ return 0;
189
+ }
190
+
191
+ static int on_map_end(void* ctx) {
192
+ rb_ctx* c = (rb_ctx*)ctx;
193
+ if (c->sp <= 0) {
194
+ rb_fail(c);
195
+ return -1;
196
+ }
197
+ c->sp--;
198
+ return 0;
199
+ }
200
+
201
+ static int on_anchor(void* ctx, const char* name, size_t len) {
202
+ rb_ctx* c = (rb_ctx*)ctx;
203
+ c->pending_anchor = rb_utf8_str(name, len);
204
+ return 0;
205
+ }
206
+
207
+ static int on_alias(void* ctx, const char* name, size_t len) {
208
+ rb_ctx* c = (rb_ctx*)ctx;
209
+ VALUE key = rb_utf8_str(name, len);
210
+ VALUE v = rb_hash_lookup2(c->anchors, key, Qundef);
211
+ if (v == Qundef) {
212
+ v = Qnil;
213
+ }
214
+ return rb_push_value(c, v);
215
+ }
216
+
217
+ static int on_doc(void* ctx) {
218
+ /* multi-doc: for load_all we'd collect; single-load takes first.
219
+ * reset root so subsequent docs replace — load_stream uses a
220
+ * different entry that accumulates. */
221
+ (void)ctx;
222
+ return 0;
223
+ }
224
+
225
+ static const YeptrisVisitVTable k_vt = {
226
+ on_null, on_bool, on_int, on_float, on_string,
227
+ on_seq_start, on_seq_end, on_map_start, on_map_end,
228
+ on_key, on_doc, on_anchor, on_alias,
229
+ };
230
+
231
+ /* fused JSON→Ruby (json_ruby.c) — no vtable, beats JSON.parse */
232
+ VALUE yep_rb_parse_json(const char* p, size_t len, int strict_dup);
233
+ VALUE yep_rb_cbor_load(const char* p, size_t len, int strict);
234
+
235
+ static VALUE ctx_result(rb_ctx* c, YeptrisStatus st) {
236
+ if (st != YEPTRIS_OK || c->failed) {
237
+ if (st == YEPTRIS_ERROR_PARSE) {
238
+ rb_raise(rb_path2class("Yeptris::ParseError"), "native parse failed");
239
+ }
240
+ if (st == YEPTRIS_ERROR_MEMORY) {
241
+ rb_raise(rb_eNoMemError, "yeptris native");
242
+ }
243
+ rb_raise(rb_path2class("Yeptris::Error"), "native materialize failed (%d)", (int)st);
244
+ }
245
+ return c->root;
246
+ }
247
+
248
+ static VALUE native_cbor_load(int argc, VALUE* argv, VALUE self) {
249
+ (void)self;
250
+ VALUE input, strict;
251
+ rb_scan_args(argc, argv, "11", &input, &strict);
252
+ StringValue(input);
253
+ VALUE v = yep_rb_cbor_load(RSTRING_PTR(input), (size_t)RSTRING_LEN(input), RTEST(strict));
254
+ if (v == Qundef) {
255
+ rb_raise(rb_path2class("Yeptris::ParseError"), "native cbor decode failed");
256
+ }
257
+ return v;
258
+ }
259
+
260
+ static VALUE native_load_json(int argc, VALUE* argv, VALUE self) {
261
+ (void)self;
262
+ VALUE input, strict;
263
+ rb_scan_args(argc, argv, "11", &input, &strict);
264
+ StringValue(input);
265
+ return yep_rb_parse_json(RSTRING_PTR(input), (size_t)RSTRING_LEN(input), RTEST(strict));
266
+ }
267
+
268
+ static VALUE native_load(VALUE self, VALUE input, VALUE schema) {
269
+ (void)self;
270
+ StringValue(input);
271
+ int sch = YEPTRIS_SCHEMA_11_COMPAT;
272
+ if (!NIL_P(schema)) {
273
+ Check_Type(schema, T_SYMBOL);
274
+ if (rb_sym2id(schema) == rb_intern("core_12")) {
275
+ sch = YEPTRIS_SCHEMA_12_CORE;
276
+ }
277
+ }
278
+ rb_ctx c;
279
+ memset(&c, 0, sizeof(c));
280
+ c.root = Qnil;
281
+ c.pending_anchor = Qnil;
282
+ c.anchors = rb_hash_new();
283
+ VALUE already = rb_gc_disable();
284
+ YeptrisStatus st = yeptris_visit(RSTRING_PTR(input), (size_t)RSTRING_LEN(input),
285
+ (YeptrisSchema)sch, &k_vt, &c);
286
+ if (already == Qfalse) {
287
+ rb_gc_enable();
288
+ }
289
+ return ctx_result(&c, st);
290
+ }
291
+
292
+ /* load_stream: accumulate documents into an Array. */
293
+ typedef struct {
294
+ rb_ctx inner;
295
+ VALUE docs;
296
+ int in_doc;
297
+ } rb_stream_ctx;
298
+
299
+ static int stream_on_doc(void* ctx) {
300
+ rb_stream_ctx* s = (rb_stream_ctx*)ctx;
301
+ if (s->in_doc && !NIL_P(s->inner.root)) {
302
+ rb_ary_push(s->docs, s->inner.root);
303
+ }
304
+ s->inner.root = Qnil;
305
+ s->inner.sp = 0;
306
+ s->in_doc = 1;
307
+ return 0;
308
+ }
309
+
310
+ static VALUE native_load_stream(VALUE self, VALUE input, VALUE schema) {
311
+ (void)self;
312
+ StringValue(input);
313
+ int sch = YEPTRIS_SCHEMA_11_COMPAT;
314
+ if (!NIL_P(schema) && rb_sym2id(schema) == rb_intern("core_12")) {
315
+ sch = YEPTRIS_SCHEMA_12_CORE;
316
+ }
317
+ rb_stream_ctx s;
318
+ memset(&s, 0, sizeof(s));
319
+ s.inner.root = Qnil;
320
+ s.inner.pending_anchor = Qnil;
321
+ s.inner.anchors = rb_hash_new();
322
+ s.docs = rb_ary_new();
323
+ YeptrisVisitVTable vt = k_vt;
324
+ vt.on_doc = stream_on_doc;
325
+ /* trick: the ctx for scalar callbacks is &s.inner, but on_doc needs
326
+ * &s. Use a unified ctx — rebind all callbacks to take stream ctx
327
+ * by making inner the first field (already is). on_doc uses outer;
328
+ * others use inner via same pointer since inner is first field. */
329
+ YeptrisStatus st = yeptris_visit(RSTRING_PTR(input), (size_t)RSTRING_LEN(input),
330
+ (YeptrisSchema)sch, &vt, &s);
331
+ if (st == YEPTRIS_OK && !s.inner.failed) {
332
+ if (!NIL_P(s.inner.root) || s.in_doc) {
333
+ rb_ary_push(s.docs, s.inner.root);
334
+ }
335
+ return s.docs;
336
+ }
337
+ return ctx_result(&s.inner, st == YEPTRIS_OK ? YEPTRIS_ERROR_INTERNAL : st);
338
+ }
339
+
340
+ /* GC-strategy surface (TODO.restructure/34): ENV at load sets the
341
+ * default; the setter re-picks at runtime so the CI referee can A/B
342
+ * in-process. Symbols: :disable, :none, :start. */
343
+ extern int yep_rb_gc_mode(void);
344
+ extern void yep_rb_set_gc_mode(int mode);
345
+ extern int yep_rb_ins_mode(void);
346
+ extern void yep_rb_set_ins_mode(int mode);
347
+ extern int yep_rb_cache_mode(void);
348
+ extern void yep_rb_set_cache_mode(int mode);
349
+ extern int yep_rb_shape_mode(void);
350
+ extern void yep_rb_set_shape_mode(int mode);
351
+ extern double yep_rb_scan_time(const char* p, size_t len, int n);
352
+
353
+ static VALUE native_gc_mode(VALUE self) {
354
+ (void)self;
355
+ switch (yep_rb_gc_mode()) {
356
+ case 1: return ID2SYM(rb_intern("none"));
357
+ case 2: return ID2SYM(rb_intern("start"));
358
+ default: return ID2SYM(rb_intern("disable"));
359
+ }
360
+ }
361
+
362
+ static VALUE native_ins_mode(VALUE self) {
363
+ (void)self;
364
+ return yep_rb_ins_mode() == 1 ? ID2SYM(rb_intern("aset")) : ID2SYM(rb_intern("bulk"));
365
+ }
366
+
367
+ static VALUE native_ins_mode_set(VALUE self, VALUE mode) {
368
+ (void)self;
369
+ Check_Type(mode, T_SYMBOL);
370
+ ID id = rb_sym2id(mode);
371
+ if (id == rb_intern("bulk")) yep_rb_set_ins_mode(0);
372
+ else if (id == rb_intern("aset")) yep_rb_set_ins_mode(1);
373
+ else rb_raise(rb_eArgError, "ins_mode must be :bulk or :aset");
374
+ return mode;
375
+ }
376
+
377
+ static VALUE native_cache_mode(VALUE self) {
378
+ (void)self;
379
+ return yep_rb_cache_mode() == 1 ? ID2SYM(rb_intern("off")) : ID2SYM(rb_intern("on"));
380
+ }
381
+
382
+ static VALUE native_cache_mode_set(VALUE self, VALUE mode) {
383
+ (void)self;
384
+ Check_Type(mode, T_SYMBOL);
385
+ ID id = rb_sym2id(mode);
386
+ if (id == rb_intern("on")) yep_rb_set_cache_mode(0);
387
+ else if (id == rb_intern("off")) yep_rb_set_cache_mode(1);
388
+ else rb_raise(rb_eArgError, "cache_mode must be :on or :off");
389
+ return mode;
390
+ }
391
+
392
+ static VALUE native_scan_time(VALUE self, VALUE input, VALUE count) {
393
+ (void)self;
394
+ StringValue(input);
395
+ int n = NUM2INT(count);
396
+ double secs = yep_rb_scan_time(RSTRING_PTR(input), (size_t)RSTRING_LEN(input), n);
397
+ return DBL2NUM(secs / (double)n);
398
+ }
399
+
400
+ static VALUE native_shape_mode(VALUE self) {
401
+ (void)self;
402
+ return yep_rb_shape_mode() == 1 ? ID2SYM(rb_intern("natural")) : ID2SYM(rb_intern("pre"));
403
+ }
404
+
405
+ static VALUE native_shape_mode_set(VALUE self, VALUE mode) {
406
+ (void)self;
407
+ Check_Type(mode, T_SYMBOL);
408
+ ID id = rb_sym2id(mode);
409
+ if (id == rb_intern("pre")) yep_rb_set_shape_mode(0);
410
+ else if (id == rb_intern("natural")) yep_rb_set_shape_mode(1);
411
+ else rb_raise(rb_eArgError, "shape_mode must be :pre or :natural");
412
+ return mode;
413
+ }
414
+
415
+ static VALUE native_gc_mode_set(VALUE self, VALUE mode) {
416
+ (void)self;
417
+ Check_Type(mode, T_SYMBOL);
418
+ ID id = rb_sym2id(mode);
419
+ if (id == rb_intern("disable")) yep_rb_set_gc_mode(0);
420
+ else if (id == rb_intern("none")) yep_rb_set_gc_mode(1);
421
+ else if (id == rb_intern("start")) yep_rb_set_gc_mode(2);
422
+ else rb_raise(rb_eArgError, "gc_mode must be :disable, :none, or :start");
423
+ return mode;
424
+ }
425
+
426
+ /* ABI-drift self-check (the #157-audit silent-nil class): the bundle
427
+ * is compiled against a specific yep_dom layout; engine drift makes
428
+ * the walkers read fields at stale offsets — cbor_load returned NIL
429
+ * on valid CBOR in the dev-checkout audit, with no error anywhere.
430
+ * Both walkers must round-trip a known document at load, or Init
431
+ * raises LoadError: lib/yeptris.rb's existing rescue falls back to
432
+ * the FFI ladder (loudly), and the stale bundle never answers with
433
+ * garbage. Runs inside rb_protect so a drifted walk raises instead of
434
+ * crashing the load. */
435
+ static VALUE native_self_check_body(VALUE unused) {
436
+ /* JSON: {"a"=>[1, 2.5, "x", true, nil]} */
437
+ const char* json = "{\"a\":[1,2.5,\"x\",true,null]}";
438
+ VALUE j = yep_rb_parse_json(json, strlen(json), 0);
439
+ if (j == Qundef || !RB_TYPE_P(j, T_HASH)) return Qfalse;
440
+ VALUE a = rb_hash_lookup(j, rb_str_new_cstr("a"));
441
+ if (!RB_TYPE_P(a, T_ARRAY) || RARRAY_LEN(a) != 5) return Qfalse;
442
+ if (!RB_INTEGER_TYPE_P(rb_ary_entry(a, 0))) return Qfalse;
443
+ if (!RB_FLOAT_TYPE_P(rb_ary_entry(a, 1))) return Qfalse;
444
+ if (!RB_TYPE_P(rb_ary_entry(a, 2), T_STRING)) return Qfalse;
445
+ if (rb_ary_entry(a, 3) != Qtrue || !NIL_P(rb_ary_entry(a, 4))) return Qfalse;
446
+
447
+ /* CBOR: A1 61 6B 01 = {"k" => 1} (canonical, hand-encoded) */
448
+ const char cbor[] = "\xA1\x61\x6B\x01";
449
+ VALUE c = yep_rb_cbor_load(cbor, sizeof(cbor) - 1, 0);
450
+ if (c == Qundef || !RB_TYPE_P(c, T_HASH)) return Qfalse;
451
+ VALUE one = rb_hash_lookup(c, rb_str_new_cstr("k"));
452
+ if (!RB_INTEGER_TYPE_P(one) || !rb_eql(one, INT2FIX(1))) return Qfalse;
453
+ return Qtrue;
454
+ }
455
+
456
+ static void native_self_check(void) {
457
+ int state = 0;
458
+ VALUE ok = rb_protect(native_self_check_body, Qnil, &state);
459
+ if (state != 0 || ok != Qtrue) {
460
+ if (state != 0) rb_set_errinfo(Qnil);
461
+ rb_raise(rb_eLoadError,
462
+ "yeptris: native materializer failed its ABI self-check "
463
+ "(stale build against a drifted engine?) — refusing to "
464
+ "register; the FFI ladder will carry the load");
465
+ }
466
+ }
467
+
468
+ RUBY_FUNC_EXPORTED void Init_native(void) {
469
+ utf8_enc = rb_utf8_encoding();
470
+ native_self_check();
471
+ VALUE mYep = rb_define_module("Yeptris");
472
+ VALUE mNat = rb_define_module_under(mYep, "Native");
473
+ rb_define_singleton_method(mNat, "load_json", native_load_json, -1);
474
+ rb_define_singleton_method(mNat, "cbor_load", native_cbor_load, -1);
475
+ rb_define_singleton_method(mNat, "load", native_load, 2);
476
+ rb_define_singleton_method(mNat, "load_stream", native_load_stream, 2);
477
+ rb_define_singleton_method(mNat, "gc_mode", native_gc_mode, 0);
478
+ rb_define_singleton_method(mNat, "gc_mode=", native_gc_mode_set, 1);
479
+ rb_define_singleton_method(mNat, "ins_mode", native_ins_mode, 0);
480
+ rb_define_singleton_method(mNat, "ins_mode=", native_ins_mode_set, 1);
481
+ rb_define_singleton_method(mNat, "cache_mode", native_cache_mode, 0);
482
+ rb_define_singleton_method(mNat, "cache_mode=", native_cache_mode_set, 1);
483
+ rb_define_singleton_method(mNat, "shape_mode", native_shape_mode, 0);
484
+ rb_define_singleton_method(mNat, "shape_mode=", native_shape_mode_set, 1);
485
+ rb_define_singleton_method(mNat, "scan_time", native_scan_time, 2);
486
+ rb_define_const(mNat, "AVAILABLE", Qtrue);
487
+ const char* env = getenv("YEPTRIS_NATIVE_GC");
488
+ if (env != NULL && strcmp(env, "none") == 0) yep_rb_set_gc_mode(1);
489
+ else if (env != NULL && strcmp(env, "start") == 0) yep_rb_set_gc_mode(2);
490
+ else if (env != NULL && strcmp(env, "disable") == 0) yep_rb_set_gc_mode(0);
491
+ const char* ins = getenv("YEPTRIS_NATIVE_INSERT");
492
+ if (ins != NULL && strcmp(ins, "aset") == 0) yep_rb_set_ins_mode(1);
493
+ const char* cache = getenv("YEPTRIS_NATIVE_CACHE");
494
+ if (cache != NULL && strcmp(cache, "off") == 0) yep_rb_set_cache_mode(1);
495
+ const char* shape = getenv("YEPTRIS_NATIVE_SHAPE");
496
+ if (shape != NULL && strcmp(shape, "natural") == 0) yep_rb_set_shape_mode(1);
497
+ }
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yeptris
4
+ # CBOR (RFC 8949) over the same FFI document the rest of the
5
+ # binding rides (TODO.cbor/04). Decode materializes through the
6
+ # Psych-compatible path; encode builds the tree with YAMLTree and
7
+ # hands the document to the C encoder — plain data only (aliases
8
+ # and non-decimal tag text are unencodable by the C contract).
9
+ #
10
+ # Representation notes inherited from the decoder: integers beyond
11
+ # int64 and bignums materialize as their shortest-double text, byte
12
+ # strings as tag-chained text — CBOR's JSON-data-model mapping.
13
+ module CBOR
14
+ STRICT = (1 << 0) # decode: reject non-minimal arguments
15
+ CANONICAL = (1 << 1) # encode: s4.2.1 core deterministic profile
16
+
17
+ class Error < Yeptris::Error; end
18
+ class UnencodableError < Error; end
19
+
20
+ module_function
21
+
22
+ def available?
23
+ defined?(::Yeptris::FFI::CBOR_EX) && ::Yeptris::FFI::CBOR_EX
24
+ end
25
+
26
+ def load(data, strict: false)
27
+ raise Error, "libyeptris has no CBOR support" unless available?
28
+
29
+ # #157: the native materializer (decode + direct VALUE
30
+ # construction in one C pass) when the extension is loaded;
31
+ # the FFI ladder otherwise
32
+ if defined?(::Yeptris::Native) && ::Yeptris::Native.respond_to?(:cbor_load)
33
+ return ::Yeptris::Native.cbor_load(data, strict)
34
+ end
35
+
36
+ doc_ptr = ::Yeptris::FFI.yeptris_cbor_decode(data, data.bytesize, strict ? STRICT : 0, nil)
37
+ raise ParseError, ::Yeptris::FFI.last_error_message if doc_ptr.null?
38
+
39
+ # the wrapper's finalizer also frees — its idempotent #free is
40
+ # the ONLY release path (a raw double free corrupts the heap)
41
+ doc = ::Yeptris::Document.new(doc_ptr)
42
+ doc.root.to_ruby
43
+ ensure
44
+ doc&.free
45
+ end
46
+
47
+ def load_sequence(data, strict: false)
48
+ raise Error, "libyeptris has no CBOR support" unless available?
49
+
50
+ items = []
51
+ pending_error = nil
52
+ receiver = ::FFI::Function.new(:int, %i[pointer pointer size_t]) do |_ctx, item, _index|
53
+ # the callback owns the item; materialize then free in place
54
+ # (through the wrapper — the finalizer frees too). A raise
55
+ # inside an FFI callback leaves the return value undefined —
56
+ # abort the C iteration cleanly (nonzero) and re-raise after
57
+ begin
58
+ doc = ::Yeptris::Document.new(item)
59
+ items << doc.root&.to_ruby
60
+ doc.free
61
+ 0
62
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
63
+ pending_error = e
64
+ 1
65
+ end
66
+ end
67
+ st = ::Yeptris::FFI.yeptris_cbor_decode_sequence(
68
+ data, data.bytesize, strict ? STRICT : 0, receiver, nil, nil
69
+ )
70
+ raise pending_error if pending_error
71
+ raise ParseError, ::Yeptris::FFI.last_error_message if st.zero? && !data.empty?
72
+
73
+ items
74
+ end
75
+
76
+ def dump(obj, canonical: true)
77
+ docs = build_documents([obj])
78
+ begin
79
+ encode_one(docs[0], canonical)
80
+ ensure
81
+ docs.each(&:free)
82
+ end
83
+ end
84
+
85
+ def dump_sequence(objects, canonical: true)
86
+ docs = build_documents(objects)
87
+ begin
88
+ opts = canonical ? CANONICAL : 0
89
+ arr = ::FFI::MemoryPointer.new(:pointer, docs.size)
90
+ arr.write_array_of_pointer(docs.map(&:c_ptr))
91
+ len_ptr = ::FFI::MemoryPointer.new(:size_t)
92
+ take_buffer(
93
+ ::Yeptris::FFI.yeptris_cbor_encode_sequence(arr, docs.size, opts, len_ptr),
94
+ len_ptr
95
+ )
96
+ ensure
97
+ docs.each(&:free)
98
+ end
99
+ end
100
+
101
+ # -- internals ----------------------------------------------------
102
+
103
+ def build_documents(objects)
104
+ # ONE document per object: a visitor's tree is a single
105
+ # document and push sets its root — reuse would overwrite
106
+ objects.map do |obj|
107
+ visitor = ::Yeptris::Psych::Visitors::YAMLTree.new
108
+ visitor.push(obj)
109
+ visitor.document
110
+ end
111
+ end
112
+
113
+ def encode_one(doc, canonical)
114
+ opts = canonical ? CANONICAL : 0
115
+ len_ptr = ::FFI::MemoryPointer.new(:size_t)
116
+ take_buffer(::Yeptris::FFI.yeptris_cbor_encode(doc.c_ptr, opts, len_ptr), len_ptr)
117
+ end
118
+
119
+ # The encode convenience contract: a malloc'd buffer plus its
120
+ # byte length — binary data, freed through the library's own free.
121
+ def take_buffer(ptr, len_ptr)
122
+ raise UnencodableError, ::Yeptris::FFI.last_error_message if ptr.null?
123
+
124
+ len = Yeptris::FFI.read_c_size_t(len_ptr)
125
+ ptr.read_bytes(len).force_encoding(Encoding::BINARY)
126
+ ensure
127
+ ::Yeptris::FFI.yeptris_free(ptr) if ptr && !ptr.null?
128
+ end
129
+ end
130
+ end