yeptris 0.6.16.1-aarch64-linux → 0.6.17.1-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/yeptris_native/cbor_ruby.c +138 -99
- data/ext/yeptris_native/yeptris_native.c +43 -0
- data/lib/yeptris/json.rb +8 -4
- data/lib/yeptris/native-3.3.so +0 -0
- data/lib/yeptris/psych/class_loader.rb +24 -0
- data/lib/yeptris/psych/scalar_scanner.rb +149 -0
- data/lib/yeptris/psych.rb +23 -5
- data/lib/yeptris.rb +1 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/include/yeptris/schema.h +4 -2
- data/vendor/libyeptris/src/yeptris/cbor/decode.c +310 -177
- data/vendor/libyeptris/src/yeptris/cbor/sink.h +58 -0
- data/vendor/libyeptris/src/yeptris/emit/writer.c +16 -2
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64ddefe5a0f3a289ff538849a99d7214d8068898a50d01f5c14cac178885d7c2
|
|
4
|
+
data.tar.gz: cd9384a1b05f3c04f390a322b5a7f7e4b8cb5f17224bbd45a2ee04fbf00f6ded
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78c6aa4ea3a6b1b802af4cf106f92dad2cdd168cb70506c48c971eae37511148a9600b379a8cd340966e3bd83224654fadbf7629ff820da6e661d61e277d0ffb
|
|
7
|
+
data.tar.gz: 8c6e9f8989af0f628fc352107c35571edd75c79284c6795cd26b81cc53732a364b9b6d55d3d6ae703cd5af9dbe0ceddb76ca0c6f05f6405c715a9ee01cab99f5
|
|
@@ -25,102 +25,144 @@ static VALUE cr_key_str(const char* p, size_t len) {
|
|
|
25
25
|
return rb_enc_interned_str(p, (long)len, cr_utf8_enc);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
#include "cbor/sink.h"
|
|
28
29
|
#include "dom/dom.h"
|
|
29
30
|
#include "doc.h" /* the public YeptrisDocument wrapper: ->dom */
|
|
30
31
|
#include <yeptris/cbor.h>
|
|
31
32
|
#include <yeptris/dom.h>
|
|
32
33
|
#include <yeptris/resolve.h>
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
/* ---- the VALUE sink (#157): bytes -> VALUE in ONE pass ----
|
|
36
|
+
*
|
|
37
|
+
* The decode grammar drives; these callbacks build the same VALUEs
|
|
38
|
+
* the old DOM + cr_walk pair produced (verified by the binding's
|
|
39
|
+
* differential suite): nil/true/false by tag, ints via LL2NUM (the
|
|
40
|
+
* INT64_MIN text parity wart kept), floats via DBL2NUM (no
|
|
41
|
+
* text round trip), strings copied utf8 (byte strings today ride the
|
|
42
|
+
* same utf8 String), map keys interned (values never are).
|
|
43
|
+
*
|
|
44
|
+
* GC: the container stack is a Ruby array (every VALUE reachable);
|
|
45
|
+
* the input buffer is read directly, so decode runs with GC disabled
|
|
46
|
+
* (the caller's String may move otherwise — the #160 bus error). */
|
|
47
|
+
|
|
48
|
+
struct rv_ctx {
|
|
49
|
+
VALUE stack; /* [.., container, (pending-key)?] */
|
|
50
|
+
VALUE root; /* Qnil until the top item closes */
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
static VALUE rv_top(const struct rv_ctx* c) {
|
|
54
|
+
long n = RARRAY_LEN(c->stack);
|
|
55
|
+
return n > 0 ? rb_ary_entry(c->stack, n - 1) : Qnil;
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
static VALUE
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return Qnil;
|
|
48
|
-
case YEPTRIS_TAG_BOOL:
|
|
49
|
-
if (len == 1) { /* Psych: "y"/"n" stay Strings */
|
|
50
|
-
return rb_utf8_str_new(p, len);
|
|
51
|
-
}
|
|
52
|
-
return (len == 4 && memcmp(p, "true", 4) == 0) ? Qtrue : Qfalse;
|
|
53
|
-
case YEPTRIS_TAG_INT: {
|
|
54
|
-
char buf[32];
|
|
55
|
-
size_t cp = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1;
|
|
56
|
-
memcpy(buf, p, cp);
|
|
57
|
-
buf[cp] = '\0';
|
|
58
|
-
char* end = NULL;
|
|
59
|
-
long long v = strtoll(buf, &end, 10);
|
|
60
|
-
if (end != buf && *end == '\0' && end == buf + len && v > INT64_MIN) {
|
|
61
|
-
return LL2NUM(v);
|
|
62
|
-
}
|
|
63
|
-
return rb_utf8_str_new(p, len); /* int_or_string's fallback */
|
|
58
|
+
static VALUE rv_attach(struct rv_ctx* c, VALUE v) {
|
|
59
|
+
VALUE top = rv_top(c);
|
|
60
|
+
if (RB_TYPE_P(top, T_ARRAY)) {
|
|
61
|
+
rb_ary_push(top, v);
|
|
62
|
+
return v;
|
|
64
63
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
memcpy(buf, p, cp);
|
|
69
|
-
buf[cp] = '\0';
|
|
70
|
-
char* end = NULL;
|
|
71
|
-
double v = strtod(buf, &end);
|
|
72
|
-
if (end != buf && end != buf + len) {
|
|
73
|
-
return rb_utf8_str_new(p, len); /* float_or_string's fallback */
|
|
74
|
-
}
|
|
75
|
-
return DBL2NUM(v);
|
|
64
|
+
if (RB_TYPE_P(top, T_HASH)) {
|
|
65
|
+
rb_ary_push(c->stack, v); /* becomes the pending key */
|
|
66
|
+
return v;
|
|
76
67
|
}
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
/* a pending key: pop it, land v in the map */
|
|
69
|
+
VALUE k = rb_ary_pop(c->stack);
|
|
70
|
+
VALUE map = rv_top(c);
|
|
71
|
+
if (RB_TYPE_P(map, T_HASH)) {
|
|
72
|
+
rb_hash_aset(map, k, v);
|
|
73
|
+
return v;
|
|
79
74
|
}
|
|
75
|
+
/* the stack emptied: v is the root */
|
|
76
|
+
c->root = v;
|
|
77
|
+
return v;
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
static
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
static int rv_text(void* ctx, const char* s, uint32_t n, uint8_t tag_id, int is_key,
|
|
81
|
+
const char* tag, uint32_t tag_len) {
|
|
82
|
+
(void)tag;
|
|
83
|
+
(void)tag_len;
|
|
84
|
+
struct rv_ctx* c = ctx;
|
|
85
|
+
VALUE v;
|
|
86
|
+
switch (tag_id) {
|
|
87
|
+
case YEPTRIS_TAG_NULL:
|
|
88
|
+
v = Qnil;
|
|
89
|
+
break;
|
|
90
|
+
case YEPTRIS_TAG_BOOL:
|
|
91
|
+
v = (n == 4 && memcmp(s, "true", 4) == 0) ? Qtrue : Qfalse;
|
|
92
|
+
break;
|
|
93
|
+
default: /* rendered text (diagnostics, simple(N), key ints) */
|
|
94
|
+
v = rb_utf8_str_new(s, (long)n);
|
|
95
|
+
break;
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (c != UINT32_MAX) {
|
|
115
|
-
c = d->nodes[c].next_sibling;
|
|
116
|
-
}
|
|
117
|
-
rb_hash_aset(h, k, v);
|
|
118
|
-
}
|
|
119
|
-
return h;
|
|
97
|
+
(void)is_key;
|
|
98
|
+
rv_attach(c, v);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static int rv_int(void* ctx, int negative, uint64_t mag, int is_key, const char* tag,
|
|
103
|
+
uint32_t tag_len) {
|
|
104
|
+
(void)tag;
|
|
105
|
+
(void)tag_len;
|
|
106
|
+
struct rv_ctx* c = ctx;
|
|
107
|
+
VALUE v;
|
|
108
|
+
if (negative && mag == (uint64_t)1 << 63) {
|
|
109
|
+
/* parity: the DOM path's strtoll guard renders INT64_MIN a
|
|
110
|
+
* String today; the differential suite pins it */
|
|
111
|
+
v = rb_utf8_str_new("-9223372036854775808", 20);
|
|
112
|
+
} else {
|
|
113
|
+
v = LL2NUM(negative ? -(int64_t)mag : (int64_t)mag);
|
|
120
114
|
}
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
rv_attach(c, v);
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static int rv_float(void* ctx, double dv, int is_key, const char* tag, uint32_t tag_len) {
|
|
120
|
+
(void)is_key;
|
|
121
|
+
(void)tag;
|
|
122
|
+
(void)tag_len;
|
|
123
|
+
rv_attach(ctx, DBL2NUM(dv));
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static int rv_str(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
128
|
+
const char* tag, uint32_t tag_len) {
|
|
129
|
+
(void)borrowed;
|
|
130
|
+
(void)tag;
|
|
131
|
+
(void)tag_len;
|
|
132
|
+
struct rv_ctx* c = ctx;
|
|
133
|
+
VALUE v = is_key ? rb_enc_interned_str((const char*)p, (long)n, cr_utf8_enc)
|
|
134
|
+
: rb_utf8_str_new((const char*)p, (long)n);
|
|
135
|
+
rv_attach(c, v);
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static int rv_bytes(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
140
|
+
const char* tag, uint32_t tag_len) {
|
|
141
|
+
/* parity: byte strings ride the same utf8 String today */
|
|
142
|
+
return rv_str(ctx, p, n, borrowed, is_key, tag, tag_len);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static int rv_open(void* ctx, int is_map, uint64_t cap, const char* tag, uint32_t tag_len) {
|
|
146
|
+
(void)tag;
|
|
147
|
+
(void)tag_len;
|
|
148
|
+
struct rv_ctx* c = ctx;
|
|
149
|
+
VALUE v;
|
|
150
|
+
if (is_map) {
|
|
151
|
+
v = (cap != UINT64_MAX && cap / 2 <= 4096) ? HASH_NEW_CAPA((long)(cap / 2))
|
|
152
|
+
: rb_hash_new();
|
|
153
|
+
} else {
|
|
154
|
+
v = (cap != UINT64_MAX && cap <= 8192) ? rb_ary_new_capa((long)cap) : rb_ary_new();
|
|
123
155
|
}
|
|
156
|
+
rb_ary_push(c->stack, v);
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static int rv_close(void* ctx, int is_map) {
|
|
161
|
+
(void)is_map;
|
|
162
|
+
struct rv_ctx* c = ctx;
|
|
163
|
+
VALUE v = rb_ary_pop(c->stack);
|
|
164
|
+
rv_attach(c, v);
|
|
165
|
+
return 1;
|
|
124
166
|
}
|
|
125
167
|
|
|
126
168
|
VALUE yep_rb_cbor_load(const char* p, size_t len, int strict) {
|
|
@@ -128,27 +170,24 @@ VALUE yep_rb_cbor_load(const char* p, size_t len, int strict) {
|
|
|
128
170
|
cr_utf8_enc = rb_utf8_encoding();
|
|
129
171
|
}
|
|
130
172
|
YeptrisStatus st = YEPTRIS_OK;
|
|
131
|
-
/* the
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* Same discipline as the JSON walk: GC off across decode+walk */
|
|
173
|
+
/* the decoder reads the caller's buffer directly; GC off across
|
|
174
|
+
* the pass (a compaction would move the String under p — the
|
|
175
|
+
* #160 bus error) */
|
|
135
176
|
VALUE gc_on = rb_gc_disable();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
return Qundef;
|
|
147
|
-
}
|
|
148
|
-
VALUE v = (d->dcount > 0 && d->docs[0] != UINT32_MAX) ? cr_walk(d, d->docs[0]) : Qnil;
|
|
149
|
-
yeptris_document_free((YeptrisDocument)doc);
|
|
177
|
+
struct rv_ctx ctx;
|
|
178
|
+
ctx.stack = rb_ary_new();
|
|
179
|
+
ctx.root = Qnil;
|
|
180
|
+
static const yep_cbor_sink RV_SINK = {
|
|
181
|
+
NULL, rv_text, rv_int, rv_float, rv_str, rv_bytes, rv_open, rv_close,
|
|
182
|
+
};
|
|
183
|
+
yep_cbor_sink sink = RV_SINK;
|
|
184
|
+
sink.ctx = &ctx;
|
|
185
|
+
st = yep_cbor_decode_gen((const unsigned char*)p, len, strict, NULL, &sink);
|
|
150
186
|
if (RTEST(gc_on)) {
|
|
151
187
|
rb_gc_enable();
|
|
152
188
|
}
|
|
153
|
-
|
|
189
|
+
if (st != YEPTRIS_OK) {
|
|
190
|
+
return Qundef;
|
|
191
|
+
}
|
|
192
|
+
return ctx.root;
|
|
154
193
|
}
|
|
@@ -423,8 +423,51 @@ static VALUE native_gc_mode_set(VALUE self, VALUE mode) {
|
|
|
423
423
|
return mode;
|
|
424
424
|
}
|
|
425
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
|
+
|
|
426
468
|
RUBY_FUNC_EXPORTED void Init_native(void) {
|
|
427
469
|
utf8_enc = rb_utf8_encoding();
|
|
470
|
+
native_self_check();
|
|
428
471
|
VALUE mYep = rb_define_module("Yeptris");
|
|
429
472
|
VALUE mNat = rb_define_module_under(mYep, "Native");
|
|
430
473
|
rb_define_singleton_method(mNat, "load_json", native_load_json, -1);
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -119,7 +119,7 @@ module Yeptris
|
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
def walk_tape(src, tape)
|
|
122
|
+
def walk_tape(src, tape, strict_dup = STRICT_DUPLICATE_KEYS)
|
|
123
123
|
n = tape[:count]
|
|
124
124
|
kinds = tape[:kinds].read_bytes(n).unpack("C*")
|
|
125
125
|
offs = tape[:offs].read_bytes(n * 4).unpack("V*")
|
|
@@ -139,7 +139,7 @@ module Yeptris
|
|
|
139
139
|
|
|
140
140
|
docs = [nil] # record 0 (DOC) pre-consumed — the root's slot
|
|
141
141
|
stack = []
|
|
142
|
-
key_sets =
|
|
142
|
+
key_sets = strict_dup ? [{}] : nil
|
|
143
143
|
pending_key = nil
|
|
144
144
|
i = 1
|
|
145
145
|
while i < n
|
|
@@ -152,8 +152,12 @@ module Yeptris
|
|
|
152
152
|
place(docs, stack, pending_key) { {} }
|
|
153
153
|
pending_key = nil
|
|
154
154
|
when T_CLOSE
|
|
155
|
+
# Key frames are pushed per MAP_OPEN — an ARRAY close must
|
|
156
|
+
# not pop the enclosing map's frame (issue found by ea's CI:
|
|
157
|
+
# {"a":[1],"b":[2],"c":[3]} exhausted the frames and the next
|
|
158
|
+
# map key hit key_sets.last.key? on nil under strict mode).
|
|
159
|
+
key_sets&.pop if stack.last.is_a?(Hash)
|
|
155
160
|
stack.pop
|
|
156
|
-
key_sets&.pop
|
|
157
161
|
when T_STR
|
|
158
162
|
# empty-string literals are frozen+shared since Ruby 3.4
|
|
159
163
|
# (and `+""` binds after the method chain anyway), so the
|
|
@@ -256,8 +260,8 @@ module Yeptris
|
|
|
256
260
|
place(docs, stack, pending_key) { {} }
|
|
257
261
|
pending_key = nil
|
|
258
262
|
when ValueML::CLOSE
|
|
263
|
+
key_sets&.pop if stack.last.is_a?(Hash)
|
|
259
264
|
stack.pop
|
|
260
|
-
key_sets&.pop
|
|
261
265
|
when ValueML::V_STR
|
|
262
266
|
text = arena.byteslice(offs[i], lens[i])
|
|
263
267
|
if ikeys[i] == 1 && !stack.empty? && stack.last.is_a?(Hash)
|
data/lib/yeptris/native-3.3.so
CHANGED
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module Yeptris
|
|
6
|
+
module Psych
|
|
7
|
+
# The minimal ClassLoader face the ScalarScanner port drives
|
|
8
|
+
# (stdlib's loader resolves through the class hierarchy; the
|
|
9
|
+
# scanner only needs the date class and symbolize).
|
|
10
|
+
class ClassLoader
|
|
11
|
+
def date
|
|
12
|
+
Date
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def symbolize str
|
|
16
|
+
str.to_sym
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load name
|
|
20
|
+
Object.const_get(name)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module Yeptris
|
|
6
|
+
module Psych
|
|
7
|
+
###
|
|
8
|
+
# Scan scalars for built in types — the stdlib Psych::ScalarScanner
|
|
9
|
+
# port (#179): same regexes, same precedence, same edge verdicts.
|
|
10
|
+
# The load path types through the C resolver; this class is the
|
|
11
|
+
# standalone API stdlib code (and Psych's own tests) drive directly.
|
|
12
|
+
class ScalarScanner
|
|
13
|
+
# Taken from http://yaml.org/type/timestamp.html
|
|
14
|
+
TIME = /^-?\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d\d:\d\d(?:\.\d*)?(?:\s*(?:Z|[-+]\d{1,2}:?(?:\d\d)?))?$/
|
|
15
|
+
|
|
16
|
+
# Taken from http://yaml.org/type/float.html
|
|
17
|
+
# Base 60, [-+]inf and NaN are handled separately
|
|
18
|
+
FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10))$/x
|
|
19
|
+
|
|
20
|
+
# Taken from http://yaml.org/type/int.html and modified to ensure at least one numerical symbol exists
|
|
21
|
+
INTEGER_STRICT = /^(?:[-+]?0b[_]*[0-1][0-1_]* (?# base 2)
|
|
22
|
+
|[-+]?0[_]*[0-7][0-7_]* (?# base 8)
|
|
23
|
+
|[-+]?(0|[1-9][0-9_]*) (?# base 10)
|
|
24
|
+
|[-+]?0x[_]*[0-9a-fA-F][0-9a-fA-F_]* (?# base 16))$/x
|
|
25
|
+
|
|
26
|
+
# Same as above, but allows commas.
|
|
27
|
+
# Not to YML spec, but kept for backwards compatibility
|
|
28
|
+
INTEGER_LEGACY = /^(?:[-+]?0b[_,]*[0-1][0-1_,]* (?# base 2)
|
|
29
|
+
|[-+]?0[_,]*[0-7][0-7_,]* (?# base 8)
|
|
30
|
+
|[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*) (?# base 10)
|
|
31
|
+
|[-+]?0x[_,]*[0-9a-fA-F][0-9a-fA-F_,]* (?# base 16))$/x
|
|
32
|
+
|
|
33
|
+
BOOLEAN_TRUE = /^(yes|true|on)$/i
|
|
34
|
+
BOOLEAN_FALSE = /^(no|false|off)$/i
|
|
35
|
+
|
|
36
|
+
attr_reader :class_loader
|
|
37
|
+
|
|
38
|
+
# Create a new scanner
|
|
39
|
+
def initialize class_loader = ClassLoader, strict_integer: false, parse_symbols: true
|
|
40
|
+
@symbol_cache = {}
|
|
41
|
+
@class_loader = class_loader
|
|
42
|
+
@strict_integer = strict_integer
|
|
43
|
+
@parse_symbols = parse_symbols
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Tokenize +string+ returning the Ruby object
|
|
47
|
+
def tokenize string
|
|
48
|
+
return nil if string.empty?
|
|
49
|
+
return @symbol_cache[string] if @symbol_cache.key?(string)
|
|
50
|
+
integer_regex = @strict_integer ? INTEGER_STRICT : INTEGER_LEGACY
|
|
51
|
+
# Check for a String type, being careful not to get caught by hash keys, hex values, and
|
|
52
|
+
# special floats (e.g., -.inf).
|
|
53
|
+
if string.match?(%r{^[^\d.:-]?[[:alpha:]_\s!@#$%\^&*(){}<>|/\\~;=]+}) || string.match?(/\n/)
|
|
54
|
+
return string if string.length > 5
|
|
55
|
+
|
|
56
|
+
if string.match?(/^[^ytonf~]/i)
|
|
57
|
+
string
|
|
58
|
+
elsif string == '~' || string.match?(/^null$/i)
|
|
59
|
+
nil
|
|
60
|
+
elsif string.match?(BOOLEAN_TRUE)
|
|
61
|
+
true
|
|
62
|
+
elsif string.match?(BOOLEAN_FALSE)
|
|
63
|
+
false
|
|
64
|
+
else
|
|
65
|
+
string
|
|
66
|
+
end
|
|
67
|
+
elsif string.match?(TIME)
|
|
68
|
+
begin
|
|
69
|
+
parse_time string
|
|
70
|
+
rescue ArgumentError
|
|
71
|
+
string
|
|
72
|
+
end
|
|
73
|
+
elsif string.match?(/^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/)
|
|
74
|
+
begin
|
|
75
|
+
class_loader.date.strptime(string, '%F', Date::GREGORIAN)
|
|
76
|
+
rescue ArgumentError
|
|
77
|
+
string
|
|
78
|
+
end
|
|
79
|
+
elsif string.match?(/^\+?\.inf$/i)
|
|
80
|
+
Float::INFINITY
|
|
81
|
+
elsif string.match?(/^-\.inf$/i)
|
|
82
|
+
-Float::INFINITY
|
|
83
|
+
elsif string.match?(/^\.nan$/i)
|
|
84
|
+
Float::NAN
|
|
85
|
+
elsif @parse_symbols && string.match?(/^:./)
|
|
86
|
+
if string =~ /^:(["'])(.*)\1/
|
|
87
|
+
@symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, ''))
|
|
88
|
+
else
|
|
89
|
+
@symbol_cache[string] = class_loader.symbolize(string.sub(/^:/, ''))
|
|
90
|
+
end
|
|
91
|
+
elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}$/)
|
|
92
|
+
i = 0
|
|
93
|
+
string.split(':').each_with_index do |n, e|
|
|
94
|
+
i += (n.to_i * 60**(e - 2).abs)
|
|
95
|
+
end
|
|
96
|
+
i
|
|
97
|
+
elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}\.[0-9_]*$/)
|
|
98
|
+
i = 0
|
|
99
|
+
string.split(':').each_with_index do |n, e|
|
|
100
|
+
i += (n.to_f * 60**(e - 2).abs)
|
|
101
|
+
end
|
|
102
|
+
i
|
|
103
|
+
elsif string.match?(FLOAT)
|
|
104
|
+
if string.match?(/\A[-+]?\.\Z/)
|
|
105
|
+
string
|
|
106
|
+
else
|
|
107
|
+
Float(string.delete(',_').gsub(/\.([Ee]|$)/, '\1'))
|
|
108
|
+
end
|
|
109
|
+
elsif string.match?(integer_regex)
|
|
110
|
+
parse_int string
|
|
111
|
+
else
|
|
112
|
+
string
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Parse and return an int from +string+
|
|
117
|
+
def parse_int string
|
|
118
|
+
Integer(string.delete(',_'))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
###
|
|
122
|
+
# Parse and return a Time from +string+
|
|
123
|
+
def parse_time string
|
|
124
|
+
date, time = *(string.split(/[Tt]|\s+/, 2))
|
|
125
|
+
(yy, m, dd) = date.match(/^(-?\d{4})-(\d{1,2})-(\d{1,2})/).captures.map { |x| x.to_i }
|
|
126
|
+
md = time.match(/(\d+:\d+:\d+)(?:\.(\d*))?\s*(Z|[-+]\d+(:\d\d)?)?/)
|
|
127
|
+
|
|
128
|
+
(hh, mm, ss) = md[1].split(':').map { |x| x.to_i }
|
|
129
|
+
us = (md[2] ? Rational("0.#{md[2]}") : 0) * 1_000_000
|
|
130
|
+
|
|
131
|
+
time = Time.utc(yy, m, dd, hh, mm, ss, us)
|
|
132
|
+
|
|
133
|
+
return time if 'Z' == md[3]
|
|
134
|
+
return Time.at(time.to_i, us) unless md[3]
|
|
135
|
+
|
|
136
|
+
tz = md[3].match(/^([+\-]?\d{1,2})\:?(\d{1,2})?$/)[1..-1].compact.map { |digit| Integer(digit, 10) }
|
|
137
|
+
offset = tz.first * 3600
|
|
138
|
+
|
|
139
|
+
if offset < 0
|
|
140
|
+
offset -= ((tz[1] || 0) * 60)
|
|
141
|
+
else
|
|
142
|
+
offset += ((tz[1] || 0) * 60)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
Time.new(yy, m, dd, hh, mm, ss + us / 1_000_000r, offset)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/yeptris/psych.rb
CHANGED
|
@@ -22,6 +22,9 @@ require "yeptris"
|
|
|
22
22
|
# document without materializing.
|
|
23
23
|
module Yeptris
|
|
24
24
|
module Psych
|
|
25
|
+
autoload :ClassLoader, "yeptris/psych/class_loader"
|
|
26
|
+
autoload :ScalarScanner, "yeptris/psych/scalar_scanner"
|
|
27
|
+
|
|
25
28
|
# The tag registries (Psych's class-level API, #95 bug 4):
|
|
26
29
|
# load_tags maps a serialized tag to the Class that revives it;
|
|
27
30
|
# dump_tags overrides the emitted tag for a Class. Consulted by
|
|
@@ -258,18 +261,28 @@ module Yeptris
|
|
|
258
261
|
::Yeptris::Psych.dump(obj, io, options)
|
|
259
262
|
end
|
|
260
263
|
|
|
261
|
-
def load_stream(yaml, **kwargs)
|
|
264
|
+
def load_stream(yaml, **kwargs, &block)
|
|
262
265
|
# materialize each document's root directly — the stream
|
|
263
266
|
# children share one C document, so their handles would all
|
|
264
|
-
# resolve to the first document's tree
|
|
267
|
+
# resolve to the first document's tree. stdlib's block form
|
|
268
|
+
# yields each loaded document's object (#179 round 4).
|
|
265
269
|
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
266
270
|
return nil if doc.nil? # the legal empty stream
|
|
267
271
|
|
|
268
272
|
begin
|
|
269
|
-
(0...doc.document_count).map { |i| doc.root(i).to_ruby }
|
|
273
|
+
docs = (0...doc.document_count).map { |i| doc.root(i).to_ruby }
|
|
270
274
|
ensure
|
|
271
275
|
doc.free
|
|
272
276
|
end
|
|
277
|
+
docs.each { |d| block.call(d) } if block
|
|
278
|
+
docs
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# safe_load_stream: stdlib's surface — the stream form of
|
|
282
|
+
# safe_load. Our load_stream is already safe-by-default
|
|
283
|
+
# (Psych 5 semantics), so this is the yielding wrapper.
|
|
284
|
+
def safe_load_stream(yaml, **kwargs, &block)
|
|
285
|
+
load_stream(yaml, **kwargs, &block)
|
|
273
286
|
end
|
|
274
287
|
|
|
275
288
|
# The first document's node tree (no Ruby materialization).
|
|
@@ -299,13 +312,14 @@ module Yeptris
|
|
|
299
312
|
# handles GC-free. (The block-yielding form is tracked under
|
|
300
313
|
# #179: the current implementation materializes eagerly; the
|
|
301
314
|
# block is only honored as a no-op convenience.)
|
|
302
|
-
_ = block # reserved for the future yielding form (#179)
|
|
303
315
|
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
304
316
|
return nil if doc.nil? || doc.document_count.zero?
|
|
305
317
|
|
|
306
318
|
stream = Nodes::Stream.new
|
|
307
319
|
(0...doc.document_count).each do |i|
|
|
308
|
-
|
|
320
|
+
child = Nodes::Builder.document_stream_child(doc, i)
|
|
321
|
+
stream.children << child
|
|
322
|
+
block.call(child) if block # stdlib's yielding form (#179 round 4)
|
|
309
323
|
end
|
|
310
324
|
# ownership: the DOCUMENT wrapper is the sole owner — its own
|
|
311
325
|
# finalizer (pointer-only closure) frees the C memory; the
|
|
@@ -450,6 +464,10 @@ module Yeptris
|
|
|
450
464
|
# tree stay valid while the tree is reachable; a GC finalizer
|
|
451
465
|
# releases the C memory when it is not.
|
|
452
466
|
class Document < Node
|
|
467
|
+
# The document's root as a Ruby object (the stream-yield face).
|
|
468
|
+
def to_ruby
|
|
469
|
+
children.first&.to_ruby
|
|
470
|
+
end
|
|
453
471
|
attr_reader :version, :tags
|
|
454
472
|
|
|
455
473
|
def initialize(version = [], tags = {})
|
data/lib/yeptris.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Yeptris
|
|
4
4
|
# The gem's version lives in the parent namespace's file — the last
|
|
5
5
|
# internal require (yeptris/version) retired with it.
|
|
6
|
-
VERSION = "0.6.
|
|
6
|
+
VERSION = "0.6.17.1".freeze
|
|
7
7
|
# The error hierarchy lives in THIS file (the parent namespace's
|
|
8
8
|
# own file): nested constants do not trigger a parent-constant
|
|
9
9
|
# autoload, and the law forbids internal requires — defining the
|
data/libyeptris.so
CHANGED
|
Binary file
|
|
@@ -65,7 +65,9 @@ enum {
|
|
|
65
65
|
(default: last, as at parse) */
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
/* One plan node.
|
|
68
|
+
/* One plan node. ABI-pinned: a pointer plus 16 fixed-width bytes —
|
|
69
|
+
* 24 on LP64, 20 on 32-bit (the hosts' FFI mirrors size :pointer
|
|
70
|
+
* per-process, so the layout contract is the POINTER plus the tail). */
|
|
69
71
|
typedef struct yeptris_desc_node {
|
|
70
72
|
const char* wire_name; /* mapping key to match (NULL: the root) */
|
|
71
73
|
uint8_t kind; /* YEP_SK_* */
|
|
@@ -77,7 +79,7 @@ typedef struct yeptris_desc_node {
|
|
|
77
79
|
uint32_t reserved; /* must be 0 (ABI headroom) */
|
|
78
80
|
} yeptris_desc_node;
|
|
79
81
|
|
|
80
|
-
YEPTRIS_CT_ASSERT(sizeof(yeptris_desc_node) ==
|
|
82
|
+
YEPTRIS_CT_ASSERT(sizeof(yeptris_desc_node) == sizeof(const char*) + 16);
|
|
81
83
|
|
|
82
84
|
/* One column's output. Caller allocates the payload array (sized by
|
|
83
85
|
* expectation); the call fills it and sets count. No per-value FFI:
|