yeptris 0.1.13.3-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.adoc +126 -0
- data/ext/yeptris_native/extconf.rb +47 -0
- data/ext/yeptris_native/json_ruby.c +330 -0
- data/ext/yeptris_native/yeptris_native.c +438 -0
- data/lib/yeptris/document.rb +209 -0
- data/lib/yeptris/ffi.rb +256 -0
- data/lib/yeptris/json.rb +145 -0
- data/lib/yeptris/materializer.rb +347 -0
- data/lib/yeptris/native.so +0 -0
- data/lib/yeptris/node.rb +354 -0
- data/lib/yeptris/psych/coder_shim.rb +55 -0
- data/lib/yeptris/psych/encodable.rb +14 -0
- data/lib/yeptris/psych/handler.rb +90 -0
- data/lib/yeptris/psych/parser.rb +105 -0
- data/lib/yeptris/psych/visitors.rb +383 -0
- data/lib/yeptris/psych.rb +342 -0
- data/lib/yeptris/valueml.rb +442 -0
- data/lib/yeptris/yaml.rb +278 -0
- data/lib/yeptris.rb +70 -0
- data/libyeptris.so +0 -0
- metadata +82 -0
|
@@ -0,0 +1,438 @@
|
|
|
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);
|
|
233
|
+
|
|
234
|
+
static VALUE ctx_result(rb_ctx* c, YeptrisStatus st) {
|
|
235
|
+
if (st != YEPTRIS_OK || c->failed) {
|
|
236
|
+
if (st == YEPTRIS_ERROR_PARSE) {
|
|
237
|
+
rb_raise(rb_path2class("Yeptris::ParseError"), "native parse failed");
|
|
238
|
+
}
|
|
239
|
+
if (st == YEPTRIS_ERROR_MEMORY) {
|
|
240
|
+
rb_raise(rb_eNoMemError, "yeptris native");
|
|
241
|
+
}
|
|
242
|
+
rb_raise(rb_path2class("Yeptris::Error"), "native materialize failed (%d)", (int)st);
|
|
243
|
+
}
|
|
244
|
+
return c->root;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static VALUE native_load_json(VALUE self, VALUE input) {
|
|
248
|
+
(void)self;
|
|
249
|
+
StringValue(input);
|
|
250
|
+
return yep_rb_parse_json(RSTRING_PTR(input), (size_t)RSTRING_LEN(input));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
static VALUE native_load(VALUE self, VALUE input, VALUE schema) {
|
|
254
|
+
(void)self;
|
|
255
|
+
StringValue(input);
|
|
256
|
+
int sch = YEPTRIS_SCHEMA_11_COMPAT;
|
|
257
|
+
if (!NIL_P(schema)) {
|
|
258
|
+
Check_Type(schema, T_SYMBOL);
|
|
259
|
+
if (rb_sym2id(schema) == rb_intern("core_12")) {
|
|
260
|
+
sch = YEPTRIS_SCHEMA_12_CORE;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
rb_ctx c;
|
|
264
|
+
memset(&c, 0, sizeof(c));
|
|
265
|
+
c.root = Qnil;
|
|
266
|
+
c.pending_anchor = Qnil;
|
|
267
|
+
c.anchors = rb_hash_new();
|
|
268
|
+
VALUE already = rb_gc_disable();
|
|
269
|
+
YeptrisStatus st = yeptris_visit(RSTRING_PTR(input), (size_t)RSTRING_LEN(input),
|
|
270
|
+
(YeptrisSchema)sch, &k_vt, &c);
|
|
271
|
+
if (already == Qfalse) {
|
|
272
|
+
rb_gc_enable();
|
|
273
|
+
}
|
|
274
|
+
return ctx_result(&c, st);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* load_stream: accumulate documents into an Array. */
|
|
278
|
+
typedef struct {
|
|
279
|
+
rb_ctx inner;
|
|
280
|
+
VALUE docs;
|
|
281
|
+
int in_doc;
|
|
282
|
+
} rb_stream_ctx;
|
|
283
|
+
|
|
284
|
+
static int stream_on_doc(void* ctx) {
|
|
285
|
+
rb_stream_ctx* s = (rb_stream_ctx*)ctx;
|
|
286
|
+
if (s->in_doc && !NIL_P(s->inner.root)) {
|
|
287
|
+
rb_ary_push(s->docs, s->inner.root);
|
|
288
|
+
}
|
|
289
|
+
s->inner.root = Qnil;
|
|
290
|
+
s->inner.sp = 0;
|
|
291
|
+
s->in_doc = 1;
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
static VALUE native_load_stream(VALUE self, VALUE input, VALUE schema) {
|
|
296
|
+
(void)self;
|
|
297
|
+
StringValue(input);
|
|
298
|
+
int sch = YEPTRIS_SCHEMA_11_COMPAT;
|
|
299
|
+
if (!NIL_P(schema) && rb_sym2id(schema) == rb_intern("core_12")) {
|
|
300
|
+
sch = YEPTRIS_SCHEMA_12_CORE;
|
|
301
|
+
}
|
|
302
|
+
rb_stream_ctx s;
|
|
303
|
+
memset(&s, 0, sizeof(s));
|
|
304
|
+
s.inner.root = Qnil;
|
|
305
|
+
s.inner.pending_anchor = Qnil;
|
|
306
|
+
s.inner.anchors = rb_hash_new();
|
|
307
|
+
s.docs = rb_ary_new();
|
|
308
|
+
YeptrisVisitVTable vt = k_vt;
|
|
309
|
+
vt.on_doc = stream_on_doc;
|
|
310
|
+
/* trick: the ctx for scalar callbacks is &s.inner, but on_doc needs
|
|
311
|
+
* &s. Use a unified ctx — rebind all callbacks to take stream ctx
|
|
312
|
+
* by making inner the first field (already is). on_doc uses outer;
|
|
313
|
+
* others use inner via same pointer since inner is first field. */
|
|
314
|
+
YeptrisStatus st = yeptris_visit(RSTRING_PTR(input), (size_t)RSTRING_LEN(input),
|
|
315
|
+
(YeptrisSchema)sch, &vt, &s);
|
|
316
|
+
if (st == YEPTRIS_OK && !s.inner.failed) {
|
|
317
|
+
if (!NIL_P(s.inner.root) || s.in_doc) {
|
|
318
|
+
rb_ary_push(s.docs, s.inner.root);
|
|
319
|
+
}
|
|
320
|
+
return s.docs;
|
|
321
|
+
}
|
|
322
|
+
return ctx_result(&s.inner, st == YEPTRIS_OK ? YEPTRIS_ERROR_INTERNAL : st);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* GC-strategy surface (TODO.restructure/34): ENV at load sets the
|
|
326
|
+
* default; the setter re-picks at runtime so the CI referee can A/B
|
|
327
|
+
* in-process. Symbols: :disable, :none, :start. */
|
|
328
|
+
extern int yep_rb_gc_mode(void);
|
|
329
|
+
extern void yep_rb_set_gc_mode(int mode);
|
|
330
|
+
extern int yep_rb_ins_mode(void);
|
|
331
|
+
extern void yep_rb_set_ins_mode(int mode);
|
|
332
|
+
extern int yep_rb_cache_mode(void);
|
|
333
|
+
extern void yep_rb_set_cache_mode(int mode);
|
|
334
|
+
extern int yep_rb_shape_mode(void);
|
|
335
|
+
extern void yep_rb_set_shape_mode(int mode);
|
|
336
|
+
extern double yep_rb_scan_time(const char* p, size_t len, int n);
|
|
337
|
+
|
|
338
|
+
static VALUE native_gc_mode(VALUE self) {
|
|
339
|
+
(void)self;
|
|
340
|
+
switch (yep_rb_gc_mode()) {
|
|
341
|
+
case 1: return ID2SYM(rb_intern("none"));
|
|
342
|
+
case 2: return ID2SYM(rb_intern("start"));
|
|
343
|
+
default: return ID2SYM(rb_intern("disable"));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
static VALUE native_ins_mode(VALUE self) {
|
|
348
|
+
(void)self;
|
|
349
|
+
return yep_rb_ins_mode() == 1 ? ID2SYM(rb_intern("aset")) : ID2SYM(rb_intern("bulk"));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
static VALUE native_ins_mode_set(VALUE self, VALUE mode) {
|
|
353
|
+
(void)self;
|
|
354
|
+
Check_Type(mode, T_SYMBOL);
|
|
355
|
+
ID id = rb_sym2id(mode);
|
|
356
|
+
if (id == rb_intern("bulk")) yep_rb_set_ins_mode(0);
|
|
357
|
+
else if (id == rb_intern("aset")) yep_rb_set_ins_mode(1);
|
|
358
|
+
else rb_raise(rb_eArgError, "ins_mode must be :bulk or :aset");
|
|
359
|
+
return mode;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
static VALUE native_cache_mode(VALUE self) {
|
|
363
|
+
(void)self;
|
|
364
|
+
return yep_rb_cache_mode() == 1 ? ID2SYM(rb_intern("off")) : ID2SYM(rb_intern("on"));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
static VALUE native_cache_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("on")) yep_rb_set_cache_mode(0);
|
|
372
|
+
else if (id == rb_intern("off")) yep_rb_set_cache_mode(1);
|
|
373
|
+
else rb_raise(rb_eArgError, "cache_mode must be :on or :off");
|
|
374
|
+
return mode;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
static VALUE native_scan_time(VALUE self, VALUE input, VALUE count) {
|
|
378
|
+
(void)self;
|
|
379
|
+
StringValue(input);
|
|
380
|
+
int n = NUM2INT(count);
|
|
381
|
+
double secs = yep_rb_scan_time(RSTRING_PTR(input), (size_t)RSTRING_LEN(input), n);
|
|
382
|
+
return DBL2NUM(secs / (double)n);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
static VALUE native_shape_mode(VALUE self) {
|
|
386
|
+
(void)self;
|
|
387
|
+
return yep_rb_shape_mode() == 1 ? ID2SYM(rb_intern("natural")) : ID2SYM(rb_intern("pre"));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
static VALUE native_shape_mode_set(VALUE self, VALUE mode) {
|
|
391
|
+
(void)self;
|
|
392
|
+
Check_Type(mode, T_SYMBOL);
|
|
393
|
+
ID id = rb_sym2id(mode);
|
|
394
|
+
if (id == rb_intern("pre")) yep_rb_set_shape_mode(0);
|
|
395
|
+
else if (id == rb_intern("natural")) yep_rb_set_shape_mode(1);
|
|
396
|
+
else rb_raise(rb_eArgError, "shape_mode must be :pre or :natural");
|
|
397
|
+
return mode;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
static VALUE native_gc_mode_set(VALUE self, VALUE mode) {
|
|
401
|
+
(void)self;
|
|
402
|
+
Check_Type(mode, T_SYMBOL);
|
|
403
|
+
ID id = rb_sym2id(mode);
|
|
404
|
+
if (id == rb_intern("disable")) yep_rb_set_gc_mode(0);
|
|
405
|
+
else if (id == rb_intern("none")) yep_rb_set_gc_mode(1);
|
|
406
|
+
else if (id == rb_intern("start")) yep_rb_set_gc_mode(2);
|
|
407
|
+
else rb_raise(rb_eArgError, "gc_mode must be :disable, :none, or :start");
|
|
408
|
+
return mode;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
RUBY_FUNC_EXPORTED void Init_native(void) {
|
|
412
|
+
utf8_enc = rb_utf8_encoding();
|
|
413
|
+
VALUE mYep = rb_define_module("Yeptris");
|
|
414
|
+
VALUE mNat = rb_define_module_under(mYep, "Native");
|
|
415
|
+
rb_define_singleton_method(mNat, "load_json", native_load_json, 1);
|
|
416
|
+
rb_define_singleton_method(mNat, "load", native_load, 2);
|
|
417
|
+
rb_define_singleton_method(mNat, "load_stream", native_load_stream, 2);
|
|
418
|
+
rb_define_singleton_method(mNat, "gc_mode", native_gc_mode, 0);
|
|
419
|
+
rb_define_singleton_method(mNat, "gc_mode=", native_gc_mode_set, 1);
|
|
420
|
+
rb_define_singleton_method(mNat, "ins_mode", native_ins_mode, 0);
|
|
421
|
+
rb_define_singleton_method(mNat, "ins_mode=", native_ins_mode_set, 1);
|
|
422
|
+
rb_define_singleton_method(mNat, "cache_mode", native_cache_mode, 0);
|
|
423
|
+
rb_define_singleton_method(mNat, "cache_mode=", native_cache_mode_set, 1);
|
|
424
|
+
rb_define_singleton_method(mNat, "shape_mode", native_shape_mode, 0);
|
|
425
|
+
rb_define_singleton_method(mNat, "shape_mode=", native_shape_mode_set, 1);
|
|
426
|
+
rb_define_singleton_method(mNat, "scan_time", native_scan_time, 2);
|
|
427
|
+
rb_define_const(mNat, "AVAILABLE", Qtrue);
|
|
428
|
+
const char* env = getenv("YEPTRIS_NATIVE_GC");
|
|
429
|
+
if (env != NULL && strcmp(env, "none") == 0) yep_rb_set_gc_mode(1);
|
|
430
|
+
else if (env != NULL && strcmp(env, "start") == 0) yep_rb_set_gc_mode(2);
|
|
431
|
+
else if (env != NULL && strcmp(env, "disable") == 0) yep_rb_set_gc_mode(0);
|
|
432
|
+
const char* ins = getenv("YEPTRIS_NATIVE_INSERT");
|
|
433
|
+
if (ins != NULL && strcmp(ins, "aset") == 0) yep_rb_set_ins_mode(1);
|
|
434
|
+
const char* cache = getenv("YEPTRIS_NATIVE_CACHE");
|
|
435
|
+
if (cache != NULL && strcmp(cache, "off") == 0) yep_rb_set_cache_mode(1);
|
|
436
|
+
const char* shape = getenv("YEPTRIS_NATIVE_SHAPE");
|
|
437
|
+
if (shape != NULL && strcmp(shape, "natural") == 0) yep_rb_set_shape_mode(1);
|
|
438
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Yeptris::Document
|
|
4
|
+
attr_reader :c_ptr
|
|
5
|
+
|
|
6
|
+
# Shared between the instance and its GC finalizer (Procs close over
|
|
7
|
+
# variables by reference) — the leptris-ruby double-free fix: the
|
|
8
|
+
# explicit free path and the finalizer both flip the same flag.
|
|
9
|
+
Freed = Struct.new(:state) # :alive | :freed
|
|
10
|
+
|
|
11
|
+
def initialize(c_ptr = nil, freed = Freed.new(:alive))
|
|
12
|
+
@c_ptr = c_ptr
|
|
13
|
+
@freed = freed
|
|
14
|
+
@readonly = false
|
|
15
|
+
# Strong wrapper cache keyed on the C node address: the same C node
|
|
16
|
+
# always yields the same Ruby object (identity for aliases/eql?),
|
|
17
|
+
# cleared at free — no stale entries, no GC-race weak maps.
|
|
18
|
+
@wrapper_cache = {}
|
|
19
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(c_ptr, freed)) unless c_ptr.null?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.parse(yaml, schema: :core_12, max_depth: 0)
|
|
23
|
+
yaml = Yeptris.read_input(yaml)
|
|
24
|
+
yaml = yaml.to_s
|
|
25
|
+
doc =
|
|
26
|
+
if schema == :core_12 && max_depth.zero?
|
|
27
|
+
Yeptris::FFI.yeptris_parse(yaml, yaml.bytesize, nil)
|
|
28
|
+
else
|
|
29
|
+
opts = Yeptris::FFI::ParseOptions.new
|
|
30
|
+
opts[:schema] = schema == :compat_11 ? Yeptris::FFI::SCHEMA_11_COMPAT : Yeptris::FFI::SCHEMA_12_CORE
|
|
31
|
+
opts[:max_depth] = max_depth
|
|
32
|
+
Yeptris::FFI.yeptris_parse_ex(yaml, yaml.bytesize, opts, nil)
|
|
33
|
+
end
|
|
34
|
+
if doc.null?
|
|
35
|
+
# the status out-param is skipped: the thread-local error
|
|
36
|
+
# channel carries the failure detail (measurable on small docs)
|
|
37
|
+
raise Yeptris::ParseError,
|
|
38
|
+
"parse failed: #{Yeptris::FFI.last_error_message}"
|
|
39
|
+
end
|
|
40
|
+
wrap(doc)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.parse_json(json)
|
|
44
|
+
json = Yeptris.read_input(json)
|
|
45
|
+
json = json.to_s
|
|
46
|
+
doc = Yeptris::FFI.yeptris_parse_json(json, json.bytesize, nil)
|
|
47
|
+
raise Yeptris::ParseError,
|
|
48
|
+
"json parse failed: #{Yeptris::FFI.last_error_message}" if doc.null?
|
|
49
|
+
|
|
50
|
+
wrap(doc)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# An empty document for from-scratch construction (TODO.impl/11 p3).
|
|
54
|
+
def self.create
|
|
55
|
+
doc = Yeptris::FFI.yeptris_document_new
|
|
56
|
+
raise Yeptris::Error, "yeptris_document_new failed" if doc.null?
|
|
57
|
+
|
|
58
|
+
wrap(doc)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @api private
|
|
62
|
+
def self.wrap(c_ptr)
|
|
63
|
+
new(c_ptr)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def ensure_alive!
|
|
67
|
+
raise Yeptris::FreedError, "document is freed" if @freed.state == :freed
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def free
|
|
71
|
+
return if @freed.state == :freed
|
|
72
|
+
|
|
73
|
+
@freed.state = :freed
|
|
74
|
+
@wrapper_cache.clear
|
|
75
|
+
Yeptris::FFI.yeptris_document_free(@c_ptr)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def freed?
|
|
79
|
+
@freed.state == :freed
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def readonly!
|
|
83
|
+
@readonly = true
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def readonly?
|
|
88
|
+
@readonly
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @api private — memo table for readonly materialization: node ids
|
|
92
|
+
# that already produced their Ruby object keep it (leptris pattern:
|
|
93
|
+
# readonly documents never change, so the memo is forever valid).
|
|
94
|
+
def readonly_memo
|
|
95
|
+
@readonly_memo ||= {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @api private — the single Node construction path. Query handles
|
|
99
|
+
# are transient C allocations; the wrapper cache is keyed on the
|
|
100
|
+
# STABLE node id (yeptris_node_id), so the same node always yields
|
|
101
|
+
# the same Ruby object no matter which query produced the handle.
|
|
102
|
+
def wrap_node(c_ptr)
|
|
103
|
+
ensure_alive!
|
|
104
|
+
return nil if c_ptr.null?
|
|
105
|
+
|
|
106
|
+
id = Yeptris::FFI.yeptris_node_id(c_ptr)
|
|
107
|
+
@wrapper_cache[id] ||= Yeptris::Node.new(c_ptr, self)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def document_count
|
|
111
|
+
ensure_alive!
|
|
112
|
+
Yeptris::FFI.yeptris_document_count(@c_ptr)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Root node of stream document i (0-based).
|
|
116
|
+
def root(index = 0)
|
|
117
|
+
ensure_alive!
|
|
118
|
+
wrap_node(Yeptris::FFI.yeptris_document_root(@c_ptr, index))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Bulk build (TODO.impl/15 phase D): one call raises the whole
|
|
122
|
+
# tree from a flat entry array + blob (see YAML::BulkBuilder).
|
|
123
|
+
def build_entries(entries, count, blob, blob_len)
|
|
124
|
+
ensure_alive!
|
|
125
|
+
Yeptris::FFI.yeptris_document_build(@c_ptr, entries, count, blob, blob_len)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def serialize(canonical: false, best_width: 0)
|
|
129
|
+
ensure_alive!
|
|
130
|
+
len = ::FFI::MemoryPointer.new(:uint64)
|
|
131
|
+
ptr =
|
|
132
|
+
if canonical || best_width.positive?
|
|
133
|
+
opts = Yeptris::FFI::EmitOptions.new
|
|
134
|
+
opts[:size] = Yeptris::FFI::EmitOptions.size
|
|
135
|
+
opts[:canonical] = canonical ? 1 : 0
|
|
136
|
+
opts[:best_width] = best_width
|
|
137
|
+
Yeptris::FFI.yeptris_serialize_ex(@c_ptr, opts, len)
|
|
138
|
+
else
|
|
139
|
+
Yeptris::FFI.yeptris_serialize(@c_ptr, len)
|
|
140
|
+
end
|
|
141
|
+
Yeptris::FFI::Owned.string(ptr, len)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def serialize_json
|
|
145
|
+
ensure_alive!
|
|
146
|
+
len = ::FFI::MemoryPointer.new(:uint64)
|
|
147
|
+
Yeptris::FFI::Owned.string(Yeptris::FFI.yeptris_serialize_json(@c_ptr, len), len)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def to_s
|
|
151
|
+
serialize
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The Ruby object graph of stream document i (Psych-compatible
|
|
155
|
+
# materialization; alias identity preserved via the memo).
|
|
156
|
+
def to_ruby(index = 0)
|
|
157
|
+
ensure_alive!
|
|
158
|
+
r = root(index)
|
|
159
|
+
r.nil? ? nil : r.to_ruby
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# ---- construction conveniences (TODO.impl/11 phase 3) ----
|
|
163
|
+
|
|
164
|
+
def new_mapping
|
|
165
|
+
ensure_alive!
|
|
166
|
+
wrap_node(Yeptris::FFI.yeptris_node_new_mapping(@c_ptr)) or
|
|
167
|
+
raise Yeptris::Error, "yeptris_node_new_mapping failed"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def new_sequence
|
|
171
|
+
ensure_alive!
|
|
172
|
+
wrap_node(Yeptris::FFI.yeptris_node_new_sequence(@c_ptr)) or
|
|
173
|
+
raise Yeptris::Error, "yeptris_node_new_sequence failed"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# style: :plain / :single_quoted / :double_quoted / :literal / :folded.
|
|
177
|
+
# The value is copied into the document (nothing is borrowed).
|
|
178
|
+
def new_scalar(text, style = :plain)
|
|
179
|
+
ensure_alive!
|
|
180
|
+
code = Yeptris::Node::STYLES.key(style) or
|
|
181
|
+
raise ArgumentError, "unknown scalar style #{style.inspect}"
|
|
182
|
+
text = text.to_s
|
|
183
|
+
n = Yeptris::FFI.yeptris_node_new_scalar(@c_ptr, text, text.bytesize, code)
|
|
184
|
+
wrap_node(n) or raise Yeptris::Error, "yeptris_node_new_scalar failed"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# An alias node: display name + the target it resolves to.
|
|
188
|
+
def new_alias(target, name)
|
|
189
|
+
ensure_alive!
|
|
190
|
+
n = Yeptris::FFI.yeptris_node_new_alias(@c_ptr, target.c_ptr, name, name.bytesize)
|
|
191
|
+
wrap_node(n) or raise Yeptris::Error, "yeptris_node_new_alias failed"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def set_root(node)
|
|
195
|
+
ensure_alive!
|
|
196
|
+
rc = Yeptris::FFI.yeptris_document_set_root(@c_ptr, node.c_ptr)
|
|
197
|
+
Yeptris::FFI.check_status(rc, "yeptris_document_set_root")
|
|
198
|
+
self
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# GC safety net: an explicit #free already ran is fine; a miss here
|
|
202
|
+
# frees C memory that would otherwise leak.
|
|
203
|
+
def self.finalize(c_ptr, freed)
|
|
204
|
+
proc do
|
|
205
|
+
Yeptris::FFI.yeptris_document_free(c_ptr) if freed.state == :alive
|
|
206
|
+
freed.state = :freed
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|