yeptris 0.6.15.2 → 0.6.16.1
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 +22 -2
- data/lib/yeptris/document.rb +17 -0
- data/lib/yeptris/psych.rb +17 -3
- data/lib/yeptris/schema.rb +86 -0
- data/lib/yeptris.rb +1 -1
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/yeptris/emit/writer.c +16 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 36b4d1444bde48fba47b0fd89851f5ebe8cd7a666c7e3faa8652caf4bb37a60f
|
|
4
|
+
data.tar.gz: 7f113394e8f82d502425610637641716af695a835ba01f6ce5e611c5073c6d55
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cec260d2c0b2c10d152336d0809eca8d9d15c4fc8e52ddac94fa01b8850c3f59cebb74f0ddd78b48f751ce9fb6a1f74e6f9646d81a237144645810949c93b7a0
|
|
7
|
+
data.tar.gz: efa7dbc2609e25b0d25bb0d93c7d877223808d88343299e7dfd60ed9442eb31806347a4aa1f255452bfc219af2a5d4c34b66f5d50346d839de59ee256dafe0da
|
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
#include <stdlib.h>
|
|
18
18
|
#include <string.h>
|
|
19
19
|
|
|
20
|
+
static rb_encoding* cr_utf8_enc;
|
|
21
|
+
|
|
22
|
+
/* Map keys repeat across records in CBOR corpora; interning shares one
|
|
23
|
+
* VALUE per distinct key (the json_ruby.c on_key pattern, #157). */
|
|
24
|
+
static VALUE cr_key_str(const char* p, size_t len) {
|
|
25
|
+
return rb_enc_interned_str(p, (long)len, cr_utf8_enc);
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
#include "dom/dom.h"
|
|
21
29
|
#include "doc.h" /* the public YeptrisDocument wrapper: ->dom */
|
|
22
30
|
#include <yeptris/cbor.h>
|
|
@@ -91,8 +99,17 @@ static VALUE cr_walk(const yep_dom* d, uint32_t id) {
|
|
|
91
99
|
VALUE h = HASH_NEW_CAPA(pairs);
|
|
92
100
|
uint32_t c = n->first_child;
|
|
93
101
|
for (long i = 0; i < pairs && c != UINT32_MAX; i++) {
|
|
94
|
-
VALUE k
|
|
95
|
-
|
|
102
|
+
VALUE k;
|
|
103
|
+
const yep_dnode* kn = &d->nodes[c];
|
|
104
|
+
if (kn->kind == YEP_DOM_SCALAR && kn->tag_id == YEPTRIS_TAG_STR) {
|
|
105
|
+
uint32_t klen = 0;
|
|
106
|
+
const char* kp = cr_view(d, kn->value, &klen);
|
|
107
|
+
k = cr_key_str(kp, klen);
|
|
108
|
+
c = kn->next_sibling;
|
|
109
|
+
} else {
|
|
110
|
+
k = cr_walk(d, c);
|
|
111
|
+
c = d->nodes[c].next_sibling;
|
|
112
|
+
}
|
|
96
113
|
VALUE v = (c != UINT32_MAX) ? cr_walk(d, c) : Qnil;
|
|
97
114
|
if (c != UINT32_MAX) {
|
|
98
115
|
c = d->nodes[c].next_sibling;
|
|
@@ -107,6 +124,9 @@ static VALUE cr_walk(const yep_dom* d, uint32_t id) {
|
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
VALUE yep_rb_cbor_load(const char* p, size_t len, int strict) {
|
|
127
|
+
if (cr_utf8_enc == NULL) {
|
|
128
|
+
cr_utf8_enc = rb_utf8_encoding();
|
|
129
|
+
}
|
|
110
130
|
YeptrisStatus st = YEPTRIS_OK;
|
|
111
131
|
/* the DOM borrows the input buffer zero-copy; the walk ALLOCATES,
|
|
112
132
|
* and a GC compaction mid-walk moves the caller's String — the
|
data/lib/yeptris/document.rb
CHANGED
|
@@ -8,6 +8,11 @@ class Yeptris::Document
|
|
|
8
8
|
# explicit free path and the finalizer both flip the same flag.
|
|
9
9
|
Freed = Struct.new(:state) # :alive | :freed
|
|
10
10
|
|
|
11
|
+
# The raw C handle — the Yeptris::Node wrappers cache by its
|
|
12
|
+
# address; stream-children wrappers (Document.without_finalizer)
|
|
13
|
+
# carry it without registering a finalizer (#182).
|
|
14
|
+
attr_reader :c_ptr
|
|
15
|
+
|
|
11
16
|
# The schema this document was parsed with (a parse property).
|
|
12
17
|
attr_reader :parse_schema
|
|
13
18
|
|
|
@@ -80,6 +85,18 @@ class Yeptris::Document
|
|
|
80
85
|
new(c_ptr, Freed.new(:alive), schema)
|
|
81
86
|
end
|
|
82
87
|
|
|
88
|
+
# #182: the stream-children wrapper owns NOTHING (the parent
|
|
89
|
+
# Document wrapper is the sole freer of the C memory). A plain
|
|
90
|
+
# Document.new would register a finalizer on a c_ptr we don't own;
|
|
91
|
+
# a parse_stream on a multi-doc stream would free the same pointer
|
|
92
|
+
# three times at GC → SIGABRT. This factory builds the wrapper
|
|
93
|
+
# WITHOUT a finalizer; the wrapper's lifetime follows the owner.
|
|
94
|
+
def self.without_finalizer(c_ptr)
|
|
95
|
+
doc = new(c_ptr)
|
|
96
|
+
ObjectSpace.undefine_finalizer(doc)
|
|
97
|
+
doc
|
|
98
|
+
end
|
|
99
|
+
|
|
83
100
|
def ensure_alive!
|
|
84
101
|
raise Yeptris::FreedError, "document is freed" if @freed.state == :freed
|
|
85
102
|
end
|
data/lib/yeptris/psych.rb
CHANGED
|
@@ -291,7 +291,15 @@ module Yeptris
|
|
|
291
291
|
raise SyntaxError.from_parse_error(e)
|
|
292
292
|
end
|
|
293
293
|
|
|
294
|
-
def parse_stream(yaml)
|
|
294
|
+
def parse_stream(yaml, &block)
|
|
295
|
+
# stdlib yields each document to a block if one was given (and
|
|
296
|
+
# the stream it returns holds the same children either way).
|
|
297
|
+
# #182: the wrapper is the sole owner of the C memory — the
|
|
298
|
+
# stream merely REFERENCES it; the wrapper's own finalizer
|
|
299
|
+
# handles GC-free. (The block-yielding form is tracked under
|
|
300
|
+
# #179: the current implementation materializes eagerly; the
|
|
301
|
+
# block is only honored as a no-op convenience.)
|
|
302
|
+
_ = block # reserved for the future yielding form (#179)
|
|
295
303
|
doc = Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
296
304
|
return nil if doc.nil? || doc.document_count.zero?
|
|
297
305
|
|
|
@@ -308,6 +316,8 @@ module Yeptris
|
|
|
308
316
|
rescue Yeptris::ParseError => e
|
|
309
317
|
raise SyntaxError.from_parse_error(e)
|
|
310
318
|
ensure
|
|
319
|
+
# the returned stream OWNS the doc's lifetime (its free delegates
|
|
320
|
+
# to the owner); only free here when the stream wasn't built
|
|
311
321
|
doc&.free if doc && !stream
|
|
312
322
|
end
|
|
313
323
|
|
|
@@ -522,8 +532,12 @@ module Yeptris
|
|
|
522
532
|
# stream owns the yeptris document)
|
|
523
533
|
def document_stream_child(doc, index)
|
|
524
534
|
root = doc.root(index)
|
|
525
|
-
|
|
526
|
-
|
|
535
|
+
# #182's root cause: three per-stream Document wrappers all
|
|
536
|
+
# capture the same c_ptr in their finalizers → three frees.
|
|
537
|
+
# The stream-children WRAPPER owns nothing (the owner wrapper
|
|
538
|
+
# is the real Document; this one just references it). Build
|
|
539
|
+
# a non-owning wrapper with no finalizer.
|
|
540
|
+
d = Document.send(:new, root&.document, false)
|
|
527
541
|
d.children << node(root) if root
|
|
528
542
|
d
|
|
529
543
|
end
|
data/lib/yeptris/schema.rb
CHANGED
|
@@ -66,6 +66,92 @@ module Yeptris
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# #184 (lutaml-model KV path): zip Schema columns into record
|
|
70
|
+
# hashes ready for Serializable.instantiate. Mapping root → one
|
|
71
|
+
# hash; sequence-of-mappings → one hash per element; nested
|
|
72
|
+
# mappings → nested hashes. Returns Array<Hash> always. when_attribute
|
|
73
|
+
# / polymorphic stay out of scope (interpretive fallback).
|
|
74
|
+
def load_records(source, schema: :core_12, desc:, capacity: 64)
|
|
75
|
+
cols = load(source, schema: schema, desc: desc, capacity: capacity)
|
|
76
|
+
zip_records(desc, cols)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def zip_records(desc, cols)
|
|
80
|
+
root = desc[0] || {}
|
|
81
|
+
case root[:kind]
|
|
82
|
+
when :sequence
|
|
83
|
+
child_start = root[:child_index] || 1
|
|
84
|
+
child_count = root[:child_count] || 0
|
|
85
|
+
return [] if child_count.zero?
|
|
86
|
+
|
|
87
|
+
child = desc[child_start]
|
|
88
|
+
if child && child[:kind] == :mapping
|
|
89
|
+
field_start = child[:child_index] || (child_start + 1)
|
|
90
|
+
field_count = child[:child_count] || 0
|
|
91
|
+
fields = desc[field_start, field_count] || []
|
|
92
|
+
field_cols = cols[field_start, field_count] || []
|
|
93
|
+
nrows = field_cols.map(&:length).max || 0
|
|
94
|
+
Array.new(nrows) do |r|
|
|
95
|
+
h = {}
|
|
96
|
+
fields.each_with_index do |f, i|
|
|
97
|
+
next unless f[:wire_name]
|
|
98
|
+
col = field_cols[i] || []
|
|
99
|
+
# ABSENT keys are OMITTED, not nil-filled (lutaml-model's
|
|
100
|
+
# load-bearing edge: absent seeds defaults +
|
|
101
|
+
# using_default?; explicit nil is a present value with
|
|
102
|
+
# different render semantics). The C columns fill in
|
|
103
|
+
# document order — col.length > r means present.
|
|
104
|
+
next if col.length <= r
|
|
105
|
+
|
|
106
|
+
h[f[:wire_name].to_sym] = materialize_field(f, desc, cols, col[r])
|
|
107
|
+
end
|
|
108
|
+
h
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
(cols[child_start] || []).map { |v| { value: v } }
|
|
112
|
+
end
|
|
113
|
+
when :mapping
|
|
114
|
+
field_start = root[:child_index] || 1
|
|
115
|
+
field_count = root[:child_count] || 0
|
|
116
|
+
fields = desc[field_start, field_count] || []
|
|
117
|
+
field_cols = cols[field_start, field_count] || []
|
|
118
|
+
h = {}
|
|
119
|
+
fields.each_with_index do |f, i|
|
|
120
|
+
next unless f[:wire_name]
|
|
121
|
+
col = field_cols[i] || []
|
|
122
|
+
next if col.empty? # absent: omitted, not nil-filled (#184)
|
|
123
|
+
|
|
124
|
+
h[f[:wire_name].to_sym] = materialize_field(f, desc, cols, col[0])
|
|
125
|
+
end
|
|
126
|
+
[h]
|
|
127
|
+
else
|
|
128
|
+
[{ value: cols[0]&.first }]
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
private_class_method :zip_records
|
|
132
|
+
|
|
133
|
+
def materialize_field(field, desc, cols, cell)
|
|
134
|
+
case field[:kind]
|
|
135
|
+
when :mapping
|
|
136
|
+
start = field[:child_index] || 0
|
|
137
|
+
count = field[:child_count] || 0
|
|
138
|
+
return nil if count.zero? || cell.nil?
|
|
139
|
+
|
|
140
|
+
nested = {}
|
|
141
|
+
desc[start, count]&.each_with_index do |nf, i|
|
|
142
|
+
next unless nf[:wire_name]
|
|
143
|
+
ncol = cols[start + i] || []
|
|
144
|
+
nested[nf[:wire_name].to_sym] = ncol.is_a?(Array) ? (ncol[0] rescue cell) : cell
|
|
145
|
+
end
|
|
146
|
+
nested
|
|
147
|
+
when :sequence
|
|
148
|
+
cell.is_a?(Array) ? cell : Array(cell)
|
|
149
|
+
else
|
|
150
|
+
cell
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
private_class_method :materialize_field
|
|
154
|
+
|
|
69
155
|
def compile(desc)
|
|
70
156
|
desc.map do |n|
|
|
71
157
|
{ kind: KIND.fetch(n.fetch(:kind)),
|
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.16.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
|
|
@@ -134,8 +134,17 @@ static void wr_indent(yep_writer* w, int depth) {
|
|
|
134
134
|
|
|
135
135
|
static void emit_dq(yep_writer* w, const char* p, uint32_t n) {
|
|
136
136
|
wr_byte(w, '"');
|
|
137
|
+
uint32_t run = 0;
|
|
137
138
|
for (uint32_t i = 0; i < n; i++) {
|
|
138
139
|
unsigned char c = (unsigned char)p[i];
|
|
140
|
+
if (c >= 0x20 && c != '"' && c != '\\' && c != 0x7f) {
|
|
141
|
+
run++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (run) {
|
|
145
|
+
wr_put(w, p + i - run, run);
|
|
146
|
+
run = 0;
|
|
147
|
+
}
|
|
139
148
|
if (c == '"' || c == '\\') {
|
|
140
149
|
wr_byte(w, '\\');
|
|
141
150
|
wr_byte(w, (char)c);
|
|
@@ -161,10 +170,11 @@ static void emit_dq(yep_writer* w, const char* p, uint32_t n) {
|
|
|
161
170
|
char hex[4] = {'\\', 'x', hd[(c >> 4) & 0xf], hd[c & 0xf]};
|
|
162
171
|
wr_put(w, hex, 4);
|
|
163
172
|
}
|
|
164
|
-
} else {
|
|
165
|
-
wr_byte(w, (char)c);
|
|
166
173
|
}
|
|
167
174
|
}
|
|
175
|
+
if (run) {
|
|
176
|
+
wr_put(w, p + n - run, run);
|
|
177
|
+
}
|
|
168
178
|
wr_byte(w, '"');
|
|
169
179
|
}
|
|
170
180
|
|
|
@@ -315,6 +325,10 @@ enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL };
|
|
|
315
325
|
|
|
316
326
|
static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
|
|
317
327
|
int has_tag) {
|
|
328
|
+
if ((sty_in == 1) && len > 0 &&
|
|
329
|
+
(as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len))) {
|
|
330
|
+
return YEP_SC_PLAIN; /* plain-safe implies no breaks: memchr skipped */
|
|
331
|
+
}
|
|
318
332
|
int multiline = (len > 0 && memchr(p, '\n', len) != NULL);
|
|
319
333
|
int blockable = 0;
|
|
320
334
|
if (multiline && !as_key) {
|