yeptris 0.6.10.1 → 0.6.12.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/lib/yeptris/ffi.rb +1 -0
- data/lib/yeptris/materializer.rb +3 -0
- data/lib/yeptris/node.rb +2 -0
- data/lib/yeptris/psych/drop_in.rb +21 -0
- data/lib/yeptris/psych.rb +15 -2
- data/lib/yeptris/yaml.rb +24 -1
- data/lib/yeptris.rb +1 -1
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/yeptris/build.c +6 -1
- data/vendor/libyeptris/src/yeptris/dom/dom.c +23 -10
- data/vendor/libyeptris/src/yeptris/dom/dom.h +2 -0
- data/vendor/libyeptris/src/yeptris/emit/emit.c +72 -2
- data/vendor/libyeptris/src/yeptris/emit/writer.c +109 -53
- data/vendor/libyeptris/src/yeptris/emit/writer.h +15 -0
- data/vendor/libyeptris/src/yeptris/marshal.c +4 -0
- data/vendor/libyeptris/src/yeptris/memory/pool.c +13 -6
- data/vendor/libyeptris/src/yeptris/parse/engine.c +10 -1
- data/vendor/libyeptris/src/yeptris/parse.c +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffeaeb76cbc3a0a575cf5fce09e5ceff42ae233fa38125c4fb575eb15c2aafab
|
|
4
|
+
data.tar.gz: d6667ffc31e29fd13f97d7bd5378b6a3611c39f849f7a69075d9e90c6e262df6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52298af429a38b41b55adf972d89156e5c214367debc1ccf618fd8e1a438414004861c8b6a7e9a1cc1e5235c68ba07a734ae580da668b3b7751b708102c9d46c
|
|
7
|
+
data.tar.gz: c40301fda3424c9ef0d3d314a71dbf05b9a50af488c12b470f14cb8523912416ceb762de6e73ca6b44a97ac04b549d565113b688333320dabe9f3a2861895e50
|
data/lib/yeptris/ffi.rb
CHANGED
data/lib/yeptris/materializer.rb
CHANGED
|
@@ -37,6 +37,7 @@ module Yeptris
|
|
|
37
37
|
TAG_BOOL = Yeptris::FFI::TAG_BOOL
|
|
38
38
|
TAG_NULL = Yeptris::FFI::TAG_NULL
|
|
39
39
|
TAG_TIMESTAMP = Yeptris::FFI::TAG_TIMESTAMP
|
|
40
|
+
TAG_BINARY = Yeptris::FFI::TAG_BINARY
|
|
40
41
|
TAG_MERGE = 9 # YEPTRIS_TAG_MERGE (resolve.h): a plain '<<' key
|
|
41
42
|
|
|
42
43
|
INF_WORDS = {
|
|
@@ -152,6 +153,8 @@ module Yeptris
|
|
|
152
153
|
float_or_string(value)
|
|
153
154
|
when TAG_TIMESTAMP
|
|
154
155
|
parse_timestamp(value)
|
|
156
|
+
when TAG_BINARY
|
|
157
|
+
value.unpack1("m") # psych to_ruby: !binary -> decode64, ASCII-8BIT (#168)
|
|
155
158
|
else
|
|
156
159
|
value
|
|
157
160
|
end
|
data/lib/yeptris/node.rb
CHANGED
|
@@ -121,6 +121,7 @@ class Yeptris::Node
|
|
|
121
121
|
Yeptris::FFI::TAG_BOOL => :bool,
|
|
122
122
|
Yeptris::FFI::TAG_NULL => :null,
|
|
123
123
|
Yeptris::FFI::TAG_TIMESTAMP => :timestamp,
|
|
124
|
+
Yeptris::FFI::TAG_BINARY => :binary,
|
|
124
125
|
}.freeze
|
|
125
126
|
|
|
126
127
|
def tag_id
|
|
@@ -351,6 +352,7 @@ class Yeptris::Node
|
|
|
351
352
|
to_f
|
|
352
353
|
end
|
|
353
354
|
when :timestamp then ::Yeptris::Materializer.parse_timestamp(value)
|
|
355
|
+
when :binary then value.unpack1("m") # ASCII-8BIT bytes (#168)
|
|
354
356
|
else
|
|
355
357
|
# beyond int64 the C resolver leaves plain scalars :str; Psych
|
|
356
358
|
# materializes Integer (issue #31) — rebuild from the text
|
|
@@ -22,6 +22,27 @@ end
|
|
|
22
22
|
Object.class_eval { remove_const(:Psych) } if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych)
|
|
23
23
|
::Psych = Yeptris::Psych
|
|
24
24
|
|
|
25
|
+
# #167: third-party gems feature-gate on Psych::VERSION at require
|
|
26
|
+
# time (mechanize 2.14.1's cookie-jar reads it in a class body). The
|
|
27
|
+
# face reports the STDLIB version it replaces: ORIGINAL's when psych
|
|
28
|
+
# was preloaded, else the default-gem spec's version (local lookup —
|
|
29
|
+
# requiring stdlib psych post-rebind is censored by the header).
|
|
30
|
+
unless Yeptris::Psych.const_defined?(:VERSION, false)
|
|
31
|
+
version =
|
|
32
|
+
if Yeptris::Psych.const_defined?(:ORIGINAL, false)
|
|
33
|
+
Yeptris::Psych::ORIGINAL::VERSION
|
|
34
|
+
else
|
|
35
|
+
spec = begin
|
|
36
|
+
::Gem.loaded_specs["psych"] ||
|
|
37
|
+
::Gem::Specification.find_by_name("psych", ::Gem::Requirement.default)
|
|
38
|
+
rescue ::StandardError, ::Gem::Exception, ::LoadError
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
spec&.version&.to_s
|
|
42
|
+
end
|
|
43
|
+
Yeptris::Psych.const_set(:VERSION, version) if version
|
|
44
|
+
end
|
|
45
|
+
|
|
25
46
|
# yaml-first boot order: ::YAML still references the ORIGINAL stdlib
|
|
26
47
|
# module object, whose singleton methods route through stdlib's parse
|
|
27
48
|
# stream — the UTF-8 tagging (and every other normalization) never
|
data/lib/yeptris/psych.rb
CHANGED
|
@@ -165,7 +165,18 @@ module Yeptris
|
|
|
165
165
|
def force_utf8_scalars(obj)
|
|
166
166
|
case obj
|
|
167
167
|
when ::String
|
|
168
|
-
|
|
168
|
+
# valid_encoding? on an ASCII-8BIT string is ALWAYS true
|
|
169
|
+
# (it is bytes) — the discriminator is whether the bytes
|
|
170
|
+
# READ as UTF-8: #135's case (UTF-8 scalars the C side
|
|
171
|
+
# materialized as BINARY) re-tags; DECODED !binary bytes
|
|
172
|
+
# that are not UTF-8 stay BINARY (#168). Binary content that
|
|
173
|
+
# happens to be valid UTF-8 re-tags — noted divergence from
|
|
174
|
+
# stdlib, which keeps BINARY; real payloads (zips, bodies)
|
|
175
|
+
# never are.
|
|
176
|
+
if obj.encoding == ::Encoding::ASCII_8BIT && !obj.ascii_only?
|
|
177
|
+
probe = obj.dup.force_encoding(::Encoding::UTF_8)
|
|
178
|
+
obj.force_encoding(::Encoding::UTF_8) if probe.valid_encoding?
|
|
179
|
+
end
|
|
169
180
|
obj
|
|
170
181
|
when ::Hash
|
|
171
182
|
obj.each { |k, v| force_utf8_scalars(k); force_utf8_scalars(v) }
|
|
@@ -307,7 +318,9 @@ module Yeptris
|
|
|
307
318
|
when :scalar, :mapping, :sequence
|
|
308
319
|
tag = node.tag
|
|
309
320
|
unless tag.nil?
|
|
310
|
-
|
|
321
|
+
# both tag spellings pass: the URI form's last segment and
|
|
322
|
+
# the short "!binary" shorthand (#168's cassettes carry it)
|
|
323
|
+
name = tag.split(":").last.sub(/\A!/, "")
|
|
311
324
|
unless %w[str int float bool null timestamp seq map merge value
|
|
312
325
|
binary].include?(name)
|
|
313
326
|
raise DisallowedClass, name
|
data/lib/yeptris/yaml.rb
CHANGED
|
@@ -192,7 +192,19 @@ module Yeptris
|
|
|
192
192
|
obj.each { |e| place(e, emit, blob, off, seen) }
|
|
193
193
|
emit.call(STOP, 0, 0, 0)
|
|
194
194
|
end
|
|
195
|
-
when String
|
|
195
|
+
when String
|
|
196
|
+
if obj.encoding == ::Encoding::ASCII_8BIT && !obj.ascii_only?
|
|
197
|
+
# #168: psych visit_String's binary branch — strict base64
|
|
198
|
+
# + the short !binary tag + literal style (Builder
|
|
199
|
+
# build_string carries the same branch; the tagged-literal
|
|
200
|
+
# writer keeps the block form)
|
|
201
|
+
scalar([obj].pack("m0"), STYLE_LIT, emit, blob, off)
|
|
202
|
+
emit.call(BUILD_TAG, 0, off[0], 7)
|
|
203
|
+
blob << "!binary"
|
|
204
|
+
off[0] += 7
|
|
205
|
+
else
|
|
206
|
+
scalar(obj, STYLE_BY_NAME[string_style(obj)], emit, blob, off)
|
|
207
|
+
end
|
|
196
208
|
when Symbol then scalar(":#{obj}", STYLE_PLAIN, emit, blob, off)
|
|
197
209
|
when Integer then scalar(obj.to_s, STYLE_PLAIN, emit, blob, off)
|
|
198
210
|
when Float then scalar(float_text(obj), STYLE_PLAIN, emit, blob, off)
|
|
@@ -351,6 +363,17 @@ module Yeptris
|
|
|
351
363
|
# visit_String matrix) serves both builders; this side carries
|
|
352
364
|
# the style onto the per-node DOM.
|
|
353
365
|
def build_string(doc, s)
|
|
366
|
+
# psych 5.5 visit_String's binary branch, verbatim: an
|
|
367
|
+
# ASCII-8BIT string with non-ASCII bytes emits as strict base64
|
|
368
|
+
# (pack('m0'), never wrapped) under the short !binary tag with
|
|
369
|
+
# literal style — the writer keeps the block form for tagged
|
|
370
|
+
# literals (#168). BOTH dump paths converge here (the top-level
|
|
371
|
+
# scalar fast route and the YAMLTree visitor).
|
|
372
|
+
if s.encoding == ::Encoding::ASCII_8BIT && !s.ascii_only?
|
|
373
|
+
bin = new_scalar(doc, [s].pack("m0"), :force_lit)
|
|
374
|
+
bin.set_tag("!binary")
|
|
375
|
+
return bin
|
|
376
|
+
end
|
|
354
377
|
case BulkBuilder.string_style(s)
|
|
355
378
|
when :single then new_scalar(doc, s, :force_str_sq)
|
|
356
379
|
when :double then new_scalar(doc, s, :force_str)
|
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.12.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
|
|
@@ -221,12 +221,16 @@ YEPTRIS_API YeptrisStatus yeptris_document_build(YeptrisDocument handle,
|
|
|
221
221
|
int depth = 0;
|
|
222
222
|
uint32_t root = UINT32_MAX;
|
|
223
223
|
uint32_t root_closed = 0;
|
|
224
|
+
uint32_t root_tagged = 0; /* one trailing TAG on the closed root (#168) */
|
|
224
225
|
uint32_t pending_key = UINT32_MAX; /* map frame's buffered key */
|
|
225
226
|
uint32_t last_id = UINT32_MAX; /* TAG applies here (#300) */
|
|
226
227
|
YeptrisStatus rc = YEPTRIS_OK;
|
|
227
228
|
for (size_t i = 0; i < count && rc == YEPTRIS_OK; i++) {
|
|
228
229
|
const YeptrisBuildEntry* e = &entries[i];
|
|
229
|
-
|
|
230
|
+
/* A closed SCALAR root admits exactly one trailing TAG —
|
|
231
|
+
* psych's binary root is `--- !binary |-` (#168); everything
|
|
232
|
+
* after (more ops, a second tag) still rejects */
|
|
233
|
+
if (root_closed && !(e->op == YEPTRIS_BUILD_TAG && !root_tagged && last_id == root)) {
|
|
230
234
|
rc = YEPTRIS_ERROR_PARSE; /* the document ended; more entries */
|
|
231
235
|
break;
|
|
232
236
|
}
|
|
@@ -283,6 +287,7 @@ YEPTRIS_API YeptrisStatus yeptris_document_build(YeptrisDocument handle,
|
|
|
283
287
|
rc = YEPTRIS_ERROR_MEMORY;
|
|
284
288
|
break;
|
|
285
289
|
}
|
|
290
|
+
root_tagged = root_closed;
|
|
286
291
|
continue;
|
|
287
292
|
default:
|
|
288
293
|
rc = YEPTRIS_ERROR_PARSE; /* unknown op */
|
|
@@ -480,17 +480,11 @@ yep_dom* yep_dom_create(const yep_allocator* sys) {
|
|
|
480
480
|
memset(d, 0, sizeof(*d));
|
|
481
481
|
d->sys = sys;
|
|
482
482
|
d->pool = pool;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return NULL;
|
|
488
|
-
}
|
|
483
|
+
/* #157: the handle pool is lazy — two allocations and a mutex per
|
|
484
|
+
* document that decode-only consumers (CBOR load, serialize, free)
|
|
485
|
+
* never touch; yep_dom_handles() materializes it on first use */
|
|
486
|
+
d->handles = NULL;
|
|
489
487
|
if (yep_mutex_init(&d->midx.mu) != 0) {
|
|
490
|
-
yep_free(d->sys, d->anchor_nodes);
|
|
491
|
-
yep_free(d->sys, d->mut_att);
|
|
492
|
-
yep_free(d->sys, d->mut_depth);
|
|
493
|
-
yep_hpool_destroy(d->handles);
|
|
494
488
|
yep_pool_destroy(pool);
|
|
495
489
|
yep_free(sys, d);
|
|
496
490
|
return NULL;
|
|
@@ -499,6 +493,25 @@ yep_dom* yep_dom_create(const yep_allocator* sys) {
|
|
|
499
493
|
return d;
|
|
500
494
|
}
|
|
501
495
|
|
|
496
|
+
/* Lazy handle pool, mirroring the map-index's discipline (mapindex.c):
|
|
497
|
+
* EVERY access rides the dom's lazy-init mutex — read-only sharing
|
|
498
|
+
* means multiple threads may race the FIRST handle creation on a
|
|
499
|
+
* shared document (Threads.ReadOnlySharing; the TSAN catch on the
|
|
500
|
+
* first cut). The uncontended lock costs less than the hpool's own
|
|
501
|
+
* per-allocation lock that follows. */
|
|
502
|
+
struct yep_hpool* yep_dom_handles(yep_dom* d) {
|
|
503
|
+
if (d == NULL || !d->midx.mu_ready) {
|
|
504
|
+
return NULL;
|
|
505
|
+
}
|
|
506
|
+
yep_mutex_lock(&d->midx.mu);
|
|
507
|
+
if (d->handles == NULL) {
|
|
508
|
+
d->handles = yep_hpool_create(d->sys);
|
|
509
|
+
}
|
|
510
|
+
struct yep_hpool* h = d->handles;
|
|
511
|
+
yep_mutex_unlock(&d->midx.mu);
|
|
512
|
+
return h;
|
|
513
|
+
}
|
|
514
|
+
|
|
502
515
|
void yep_dom_destroy(yep_dom* d) {
|
|
503
516
|
if (d == NULL) {
|
|
504
517
|
return;
|
|
@@ -197,6 +197,8 @@ void dom_mut_set_depth(yep_dom* d, uint32_t id, uint16_t depth);
|
|
|
197
197
|
|
|
198
198
|
/* thread-safe handle arena (see hpool.c) */
|
|
199
199
|
struct yep_hpool* yep_hpool_create(const yep_allocator* sys);
|
|
200
|
+
/* Lazy handle-pool acquisition (#157): NULL dom or OOM stays NULL. */
|
|
201
|
+
struct yep_hpool* yep_dom_handles(yep_dom* d);
|
|
200
202
|
void yep_hpool_destroy(struct yep_hpool* p);
|
|
201
203
|
void* yep_hpool_alloc(struct yep_hpool* p, size_t size, size_t align);
|
|
202
204
|
|
|
@@ -39,6 +39,11 @@ YEPTRIS_API size_t yeptris_serialize_into_ex(YeptrisDocument handle,
|
|
|
39
39
|
return 0;
|
|
40
40
|
}
|
|
41
41
|
yep_emitter em;
|
|
42
|
+
em.w.sc_dec = NULL;
|
|
43
|
+
em.w.sc_i = 0;
|
|
44
|
+
em.w.cap = 0;
|
|
45
|
+
em.w.grow = 0;
|
|
46
|
+
em.w.oom = 0;
|
|
42
47
|
em.doc = (const yeptris_document*)handle;
|
|
43
48
|
em.w.p = NULL;
|
|
44
49
|
em.w.last = 0;
|
|
@@ -60,13 +65,20 @@ YEPTRIS_API size_t yeptris_serialize_into_ex(YeptrisDocument handle,
|
|
|
60
65
|
if (!yep_nametab_init(&em.canon_names, yep_system_allocator())) {
|
|
61
66
|
return 0;
|
|
62
67
|
}
|
|
68
|
+
em.w.sc_dec = (uint8_t*)malloc(em.doc->dom->ncount > 0 ? em.doc->dom->ncount : 1);
|
|
69
|
+
if (em.w.sc_dec == NULL) {
|
|
70
|
+
yep_nametab_free(&em.canon_names);
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
63
73
|
size_t need = yep_emit_run(&em, 1);
|
|
64
74
|
if (buf == NULL || cap < need + 1) {
|
|
75
|
+
free(em.w.sc_dec);
|
|
65
76
|
yep_nametab_free(&em.canon_names);
|
|
66
77
|
return need;
|
|
67
78
|
}
|
|
68
79
|
em.w.p = buf;
|
|
69
80
|
size_t wrote = yep_emit_run(&em, 0);
|
|
81
|
+
free(em.w.sc_dec);
|
|
70
82
|
yep_nametab_free(&em.canon_names);
|
|
71
83
|
buf[wrote] = '\0';
|
|
72
84
|
return wrote;
|
|
@@ -82,6 +94,11 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
|
|
|
82
94
|
return NULL;
|
|
83
95
|
}
|
|
84
96
|
yep_emitter em;
|
|
97
|
+
em.w.sc_dec = NULL;
|
|
98
|
+
em.w.sc_i = 0;
|
|
99
|
+
em.w.cap = 0;
|
|
100
|
+
em.w.grow = 0;
|
|
101
|
+
em.w.oom = 0;
|
|
85
102
|
em.doc = (const yeptris_document*)handle;
|
|
86
103
|
em.w.p = NULL;
|
|
87
104
|
em.w.last = 0;
|
|
@@ -103,13 +120,41 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
|
|
|
103
120
|
if (!yep_nametab_init(&em.canon_names, yep_system_allocator())) {
|
|
104
121
|
return NULL;
|
|
105
122
|
}
|
|
106
|
-
|
|
107
|
-
|
|
123
|
+
/* #352 slice 2: ONE wet pass into a growable buffer — the dry
|
|
124
|
+
* exact-sizing pass measured 0.54-0.73x of this route on CI-fresh
|
|
125
|
+
* runners (the emit-split table's 2p/1p column). The estimate
|
|
126
|
+
* starts at the input size (+64) and doubles on demand; a final
|
|
127
|
+
* shrink-to-fit keeps the returned allocation tight. No dry pass,
|
|
128
|
+
* so no decisions table. */
|
|
129
|
+
size_t est = em.doc->dom->input_len + 64;
|
|
130
|
+
if (est < 256) {
|
|
131
|
+
est = 256;
|
|
132
|
+
}
|
|
133
|
+
char* out = (char*)malloc(est);
|
|
108
134
|
if (out == NULL) {
|
|
135
|
+
yep_nametab_free(&em.canon_names);
|
|
109
136
|
return NULL;
|
|
110
137
|
}
|
|
111
138
|
em.w.p = out;
|
|
139
|
+
em.w.cap = est;
|
|
140
|
+
em.w.grow = 1;
|
|
112
141
|
size_t wrote = yep_emit_run(&em, 0);
|
|
142
|
+
em.w.grow = 0;
|
|
143
|
+
/* wr_grow reallocs through w->p — the local `out` is stale after
|
|
144
|
+
* any growth; every post-run touch goes through the writer's
|
|
145
|
+
* pointer (the first round's abort was exactly this aliasing) */
|
|
146
|
+
out = em.w.p;
|
|
147
|
+
if (em.w.oom) {
|
|
148
|
+
free(out);
|
|
149
|
+
yep_nametab_free(&em.canon_names);
|
|
150
|
+
return NULL;
|
|
151
|
+
}
|
|
152
|
+
if (wrote + 1 < em.w.cap) {
|
|
153
|
+
char* tight = (char*)realloc(out, wrote + 1);
|
|
154
|
+
if (tight != NULL) {
|
|
155
|
+
out = tight;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
113
158
|
yep_nametab_free(&em.canon_names);
|
|
114
159
|
out[wrote] = '\0';
|
|
115
160
|
if (len != NULL) {
|
|
@@ -123,6 +168,11 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
|
|
|
123
168
|
return NULL;
|
|
124
169
|
}
|
|
125
170
|
yep_emitter em;
|
|
171
|
+
em.w.sc_dec = NULL;
|
|
172
|
+
em.w.sc_i = 0;
|
|
173
|
+
em.w.cap = 0;
|
|
174
|
+
em.w.grow = 0;
|
|
175
|
+
em.w.oom = 0;
|
|
126
176
|
em.doc = (const yeptris_document*)handle;
|
|
127
177
|
em.w.p = NULL;
|
|
128
178
|
em.w.last = 0;
|
|
@@ -168,6 +218,11 @@ YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len,
|
|
|
168
218
|
return NULL;
|
|
169
219
|
}
|
|
170
220
|
yep_emitter em;
|
|
221
|
+
em.w.sc_dec = NULL;
|
|
222
|
+
em.w.sc_i = 0;
|
|
223
|
+
em.w.cap = 0;
|
|
224
|
+
em.w.grow = 0;
|
|
225
|
+
em.w.oom = 0;
|
|
171
226
|
em.doc = (const yeptris_document*)handle;
|
|
172
227
|
em.w.p = NULL;
|
|
173
228
|
em.w.last = 0;
|
|
@@ -210,6 +265,11 @@ char* yep_serialize_json_compact(const yeptris_document* doc, size_t* len) {
|
|
|
210
265
|
return NULL;
|
|
211
266
|
}
|
|
212
267
|
yep_emitter em;
|
|
268
|
+
em.w.sc_dec = NULL;
|
|
269
|
+
em.w.sc_i = 0;
|
|
270
|
+
em.w.cap = 0;
|
|
271
|
+
em.w.grow = 0;
|
|
272
|
+
em.w.oom = 0;
|
|
213
273
|
em.doc = doc;
|
|
214
274
|
em.w.p = NULL;
|
|
215
275
|
em.w.last = 0;
|
|
@@ -252,6 +312,11 @@ char* yep_serialize_json_pretty(const yeptris_document* doc, size_t* len) {
|
|
|
252
312
|
return NULL;
|
|
253
313
|
}
|
|
254
314
|
yep_emitter em;
|
|
315
|
+
em.w.sc_dec = NULL;
|
|
316
|
+
em.w.sc_i = 0;
|
|
317
|
+
em.w.cap = 0;
|
|
318
|
+
em.w.grow = 0;
|
|
319
|
+
em.w.oom = 0;
|
|
255
320
|
em.doc = doc;
|
|
256
321
|
em.w.p = NULL;
|
|
257
322
|
em.w.last = 0;
|
|
@@ -307,6 +372,11 @@ YEPTRIS_API size_t yeptris_serialize_stream(YeptrisDocument handle,
|
|
|
307
372
|
return 0;
|
|
308
373
|
}
|
|
309
374
|
yep_emitter em;
|
|
375
|
+
em.w.sc_dec = NULL;
|
|
376
|
+
em.w.sc_i = 0;
|
|
377
|
+
em.w.cap = 0;
|
|
378
|
+
em.w.grow = 0;
|
|
379
|
+
em.w.oom = 0;
|
|
310
380
|
em.doc = (const yeptris_document*)handle;
|
|
311
381
|
em.w.p = NULL;
|
|
312
382
|
em.w.last = 0;
|
|
@@ -64,6 +64,24 @@ static void wr_col_update(yep_writer* w, const char* p, uint32_t n) {
|
|
|
64
64
|
w->col += (int)n;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
static void wr_grow(yep_writer* w, size_t need) {
|
|
68
|
+
if (w->oom || w->p == NULL) {
|
|
69
|
+
w->oom = w->p == NULL;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
size_t want = w->len + need + 1;
|
|
73
|
+
if (want < w->cap * 2) {
|
|
74
|
+
want = w->cap * 2;
|
|
75
|
+
}
|
|
76
|
+
char* np = (char*)realloc(w->p, want);
|
|
77
|
+
if (np == NULL) {
|
|
78
|
+
w->oom = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
w->p = np;
|
|
82
|
+
w->cap = want;
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
static void wr_put(yep_writer* w, const char* p, uint32_t n) {
|
|
68
86
|
if (n > 0) {
|
|
69
87
|
w->last = p[n - 1];
|
|
@@ -73,6 +91,13 @@ static void wr_put(yep_writer* w, const char* p, uint32_t n) {
|
|
|
73
91
|
w->len += n;
|
|
74
92
|
return;
|
|
75
93
|
}
|
|
94
|
+
if (w->grow && w->len + n > w->cap) {
|
|
95
|
+
wr_grow(w, n);
|
|
96
|
+
}
|
|
97
|
+
if (w->oom) {
|
|
98
|
+
w->len += n; /* keep counting: the caller fails on oom */
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
76
101
|
memcpy(w->p + w->len, p, n);
|
|
77
102
|
w->len += n;
|
|
78
103
|
wr_maybe_flush(w);
|
|
@@ -89,6 +114,13 @@ static void wr_byte(yep_writer* w, char c) {
|
|
|
89
114
|
w->len++;
|
|
90
115
|
return;
|
|
91
116
|
}
|
|
117
|
+
if (w->grow && w->len + 1 > w->cap) {
|
|
118
|
+
wr_grow(w, 1);
|
|
119
|
+
}
|
|
120
|
+
if (w->oom) {
|
|
121
|
+
w->len++;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
92
124
|
w->p[w->len] = c;
|
|
93
125
|
w->len++;
|
|
94
126
|
wr_maybe_flush(w);
|
|
@@ -275,21 +307,17 @@ static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
|
|
|
275
307
|
}
|
|
276
308
|
}
|
|
277
309
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
/* p may be NULL for an empty view; memchr(NULL, .., 0) is UB by
|
|
287
|
-
* the nonnull attribute even though it reads nothing */
|
|
310
|
+
/* The fidelity-mode emission route for one scalar (#352): the dry
|
|
311
|
+
* pass derives it exactly once; the wet pass consumes the recorded
|
|
312
|
+
* byte and skips the multiline/blockable/safety analysis entirely. */
|
|
313
|
+
enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL };
|
|
314
|
+
#define YEP_SC_MEMO_MIN 32u /* bytes; derive-below, memoize-above */
|
|
315
|
+
|
|
316
|
+
static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
|
|
317
|
+
int has_tag) {
|
|
288
318
|
int multiline = (len > 0 && memchr(p, '\n', len) != NULL);
|
|
289
319
|
int blockable = 0;
|
|
290
320
|
if (multiline && !as_key) {
|
|
291
|
-
/* A block whose body is all whitespace parses back empty, and a
|
|
292
|
-
* raw CR re-parses as a break — both emit double-quoted. */
|
|
293
321
|
int allws = 1;
|
|
294
322
|
int has_cr = 0;
|
|
295
323
|
for (uint32_t i = 0; i < len; i++) {
|
|
@@ -302,68 +330,95 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
|
|
|
302
330
|
}
|
|
303
331
|
blockable = !allws && !has_cr;
|
|
304
332
|
}
|
|
305
|
-
|
|
333
|
+
uint8_t sty = sty_in;
|
|
306
334
|
if (sty == 4 || sty == 5) {
|
|
307
335
|
if (multiline && as_key) {
|
|
308
|
-
|
|
309
|
-
return;
|
|
336
|
+
return YEP_SC_DQ;
|
|
310
337
|
}
|
|
311
338
|
if (multiline && blockable) {
|
|
312
|
-
|
|
313
|
-
return;
|
|
339
|
+
return YEP_SC_LITERAL;
|
|
314
340
|
}
|
|
315
341
|
if (multiline) {
|
|
316
|
-
|
|
317
|
-
return;
|
|
342
|
+
return YEP_SC_DQ;
|
|
318
343
|
}
|
|
319
344
|
if (len == 0) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
345
|
+
return YEP_SC_DQ;
|
|
346
|
+
}
|
|
347
|
+
/* A TAGGED block scalar keeps the block form even single-line
|
|
348
|
+
* (psych's !binary emits `!binary |-` + one base64 line; an
|
|
349
|
+
* untagged single-line literal re-emits plain — libyaml parity,
|
|
350
|
+
* the #290 family) */
|
|
351
|
+
if (has_tag) {
|
|
352
|
+
return YEP_SC_LITERAL;
|
|
324
353
|
}
|
|
325
|
-
sty = 1;
|
|
354
|
+
sty = 1;
|
|
326
355
|
}
|
|
327
356
|
switch (sty) {
|
|
328
|
-
case 1:
|
|
357
|
+
case 1:
|
|
329
358
|
if (len == 0) {
|
|
330
|
-
|
|
331
|
-
emit_dq(w, p, len); /* empty keys must stay visible */
|
|
332
|
-
}
|
|
333
|
-
break; /* an empty plain value emits as nothing (libyaml's
|
|
334
|
-
null rendering, issue #290) */
|
|
359
|
+
return as_key ? YEP_SC_DQ : YEP_SC_NOTHING;
|
|
335
360
|
}
|
|
336
|
-
if (
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
* escapes (digit-leading strings like "5014", indicator
|
|
341
|
-
* leads) single-quotes — double quotes are for text that
|
|
342
|
-
* actually requires escapes (issue #95's matrix) */
|
|
343
|
-
int clean = 1;
|
|
361
|
+
if (as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len)) {
|
|
362
|
+
return YEP_SC_PLAIN;
|
|
363
|
+
}
|
|
364
|
+
if (!multiline) {
|
|
344
365
|
for (uint32_t i = 0; i < len; i++) {
|
|
345
366
|
if ((unsigned char)p[i] < 0x20 || (unsigned char)p[i] == 0x7f) {
|
|
346
|
-
|
|
347
|
-
break;
|
|
367
|
+
return YEP_SC_DQ;
|
|
348
368
|
}
|
|
349
369
|
}
|
|
350
|
-
|
|
351
|
-
emit_sq(w, p, len);
|
|
352
|
-
} else {
|
|
353
|
-
emit_dq(w, p, len);
|
|
354
|
-
}
|
|
355
|
-
} else {
|
|
356
|
-
emit_dq(w, p, len);
|
|
370
|
+
return YEP_SC_SQ;
|
|
357
371
|
}
|
|
358
|
-
|
|
359
|
-
case 2:
|
|
360
|
-
|
|
361
|
-
|
|
372
|
+
return YEP_SC_DQ;
|
|
373
|
+
case 2:
|
|
374
|
+
return multiline ? YEP_SC_DQ : YEP_SC_SQ;
|
|
375
|
+
default:
|
|
376
|
+
return YEP_SC_DQ;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int as_key) {
|
|
381
|
+
yep_view vdec = wv(w, n->value);
|
|
382
|
+
const char* p = (const char*)vdec.p;
|
|
383
|
+
uint32_t len = vdec.len;
|
|
384
|
+
if (w->canonical || w->json) {
|
|
385
|
+
emit_canonical_scalar(w, n);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
(void)0;
|
|
389
|
+
uint8_t route;
|
|
390
|
+
/* The memo pays only for LONG scalars: the derive cost scales with
|
|
391
|
+
* len (safety walk, quote scans) while the memo is a fixed store +
|
|
392
|
+
* cold load — on the short scalars of table-shaped documents the
|
|
393
|
+
* memo measured SLOWER than re-deriving (the #357 first artifact:
|
|
394
|
+
* json-users -36%, scalar-heavy +24%). Below the threshold both
|
|
395
|
+
* passes derive; the consumption-order index covers only the
|
|
396
|
+
* memoized long scalars, in visit order either way. */
|
|
397
|
+
if (w->sc_dec != NULL && len >= YEP_SC_MEMO_MIN) {
|
|
398
|
+
if (w->dry) {
|
|
399
|
+
route = sc_route(w, p, len, as_key, n->style, n->tag.len > 0);
|
|
400
|
+
w->sc_dec[w->sc_i] = route;
|
|
362
401
|
} else {
|
|
363
|
-
|
|
402
|
+
route = w->sc_dec[w->sc_i];
|
|
364
403
|
}
|
|
404
|
+
w->sc_i++;
|
|
405
|
+
} else {
|
|
406
|
+
route = sc_route(w, p, len, as_key, n->style, n->tag.len > 0);
|
|
407
|
+
}
|
|
408
|
+
switch (route) {
|
|
409
|
+
case YEP_SC_NOTHING:
|
|
410
|
+
break; /* an empty plain value emits as nothing (libyaml's null
|
|
411
|
+
rendering, issue #290) */
|
|
412
|
+
case YEP_SC_PLAIN:
|
|
413
|
+
wr_put(w, p, len);
|
|
414
|
+
break;
|
|
415
|
+
case YEP_SC_SQ:
|
|
416
|
+
emit_sq(w, p, len);
|
|
417
|
+
break;
|
|
418
|
+
case YEP_SC_LITERAL:
|
|
419
|
+
emit_literal(w, n, parent_col);
|
|
365
420
|
break;
|
|
366
|
-
default:
|
|
421
|
+
default:
|
|
367
422
|
emit_dq(w, p, len);
|
|
368
423
|
break;
|
|
369
424
|
}
|
|
@@ -694,6 +749,7 @@ size_t yep_emit_run(yep_emitter* em, int dry) {
|
|
|
694
749
|
w->flushed = 0;
|
|
695
750
|
w->sink_aborted = 0;
|
|
696
751
|
w->col = 0;
|
|
752
|
+
w->sc_i = 0;
|
|
697
753
|
yep_nametab_clear(&em->canon_names);
|
|
698
754
|
if (w->json) {
|
|
699
755
|
/* JSON has no multi-document streams and no document marker */
|
|
@@ -40,6 +40,21 @@ typedef struct yep_writer {
|
|
|
40
40
|
int col; /* current output column (wr_* maintains) */
|
|
41
41
|
const char* sv_input; /* compact-view decode bases (dom regions) */
|
|
42
42
|
const char* sv_arena;
|
|
43
|
+
/* #352: per-scalar route decisions, derived once in the dry
|
|
44
|
+
* sizing pass and consumed by the wet write pass (one byte per
|
|
45
|
+
* scalar, indexed by consumption order; NULL = derive per pass) */
|
|
46
|
+
uint8_t* sc_dec;
|
|
47
|
+
uint32_t sc_i;
|
|
48
|
+
/* #352 slice 2: the growable single-pass route — the exact-sizing
|
|
49
|
+
* dry pass measured 0.54-0.73x of the one-pass stream route on
|
|
50
|
+
* CI-fresh runners, so serialize_ex writes wet into a buffer that
|
|
51
|
+
* doubles on demand (cap/grow), like the streaming window. The
|
|
52
|
+
* fixed-buffer routes keep grow=0 and today's trust-the-count
|
|
53
|
+
* behavior. oom marks a failed growth: writes stop touching memory
|
|
54
|
+
* but the byte count keeps running so callers can fail cleanly. */
|
|
55
|
+
size_t cap;
|
|
56
|
+
int grow;
|
|
57
|
+
int oom;
|
|
43
58
|
} yep_writer;
|
|
44
59
|
|
|
45
60
|
typedef struct yep_emitter {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
#include <yeptris/error.h>
|
|
42
42
|
#include <yeptris/marshal.h>
|
|
43
|
+
#include <yeptris/resolve.h> /* YEPTRIS_TAG_BINARY (#168) */
|
|
43
44
|
|
|
44
45
|
typedef struct {
|
|
45
46
|
const char* name; /* into the value arena */
|
|
@@ -487,6 +488,9 @@ static int pre_scan(const yep_value_ctx* c, uint64_t** counts_out, size_t* nopen
|
|
|
487
488
|
case YEP_V_TIMESTAMP:
|
|
488
489
|
return -2;
|
|
489
490
|
case YEP_V_STR:
|
|
491
|
+
if (vals[i].tag_id == YEPTRIS_TAG_BINARY) {
|
|
492
|
+
return -2; /* !binary: the walk base64-decodes (#168) */
|
|
493
|
+
}
|
|
490
494
|
if (vals[i].is_key == 1 && vals[i].len == 2 && c->arena[vals[i].off] == '<' &&
|
|
491
495
|
c->arena[vals[i].off + 1] == '<') {
|
|
492
496
|
return -2; /* merge key: the walk resolves these */
|
|
@@ -45,6 +45,15 @@ void* yep_pool_alloc(yep_pool* pool, size_t size, size_t align) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
yep_block* b = pool->head;
|
|
48
|
+
if (b == NULL) { /* the lazy first block (#157) */
|
|
49
|
+
b = yep_block_new(pool->sys, pool->block_size);
|
|
50
|
+
if (b == NULL) {
|
|
51
|
+
return NULL;
|
|
52
|
+
}
|
|
53
|
+
b->next = NULL;
|
|
54
|
+
pool->head = b;
|
|
55
|
+
pool->blocks = 1;
|
|
56
|
+
}
|
|
48
57
|
uintptr_t base = (uintptr_t)b + sizeof(yep_block);
|
|
49
58
|
uintptr_t start = yep_round_up(base + b->used, align);
|
|
50
59
|
|
|
@@ -88,12 +97,10 @@ yep_pool* yep_pool_create(const yep_allocator* sys, size_t block_size) {
|
|
|
88
97
|
}
|
|
89
98
|
p->sys = sys;
|
|
90
99
|
p->block_size = block_size;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return NULL;
|
|
96
|
-
}
|
|
100
|
+
/* #157: the first block is lazy — one fewer eager allocation per
|
|
101
|
+
* document; pool_alloc materializes it on first use */
|
|
102
|
+
p->head = NULL;
|
|
103
|
+
p->blocks = 0;
|
|
97
104
|
return p;
|
|
98
105
|
}
|
|
99
106
|
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
#include "resolve/resolver.h"
|
|
28
28
|
#include "scan/json.h"
|
|
29
29
|
#include "scan/scan.h"
|
|
30
|
+
#include <yeptris/resolve.h> /* YEPTRIS_TAG_* names (#168) */
|
|
30
31
|
|
|
31
32
|
#define YEP_MAX_DEPTH 1000
|
|
32
33
|
#define YEP_MAX_FOLD_LINES 8192
|
|
@@ -158,7 +159,15 @@ static int emit_now(yep_engine* e, const yep_event* ev) {
|
|
|
158
159
|
/* Resolution happens exactly here: every scalar path flows
|
|
159
160
|
* through this choke point (the resolver is the typing SSOT) */
|
|
160
161
|
if (ev->tag.len > 0) {
|
|
161
|
-
|
|
162
|
+
yep_tag_id tid = yep_tag_from_uri((const char*)ev->tag.p, ev->tag.len);
|
|
163
|
+
/* psych's own dumped form is the LOCAL !binary shorthand,
|
|
164
|
+
* and stdlib to_ruby accepts it beside the core URI (#168)
|
|
165
|
+
* — compat schemas resolve it to BINARY as well */
|
|
166
|
+
if (tid == YEPTRIS_TAG_CUSTOM && ev->tag.len == 7 &&
|
|
167
|
+
memcmp(ev->tag.p, "!binary", 7) == 0 && e->resolver == yep_resolver_compat11()) {
|
|
168
|
+
tid = YEPTRIS_TAG_BINARY;
|
|
169
|
+
}
|
|
170
|
+
((yep_event*)ev)->tag_id = tid;
|
|
162
171
|
} else if (ev->implicit && e->resolver != NULL) {
|
|
163
172
|
((yep_event*)ev)->tag_id = e->resolver->resolve(
|
|
164
173
|
e->resolver->ctx, (const char*)ev->value.p, (uint32_t)ev->value.len);
|
|
@@ -375,7 +375,7 @@ YEPTRIS_API size_t yeptris_document_count(YeptrisDocument handle) {
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
yeptris_node* yep_handle_new(yeptris_document* doc, uint32_t id) {
|
|
378
|
-
yeptris_node* n = yep_hpool_alloc(doc->dom
|
|
378
|
+
yeptris_node* n = yep_hpool_alloc(yep_dom_handles(doc->dom), sizeof(yeptris_node), 16);
|
|
379
379
|
if (n == NULL) {
|
|
380
380
|
return NULL;
|
|
381
381
|
}
|
|
@@ -552,7 +552,7 @@ static YeptrisNode wrap(yeptris_node* base, uint32_t id) {
|
|
|
552
552
|
/* Handles live in the document's thread-safe arena: one
|
|
553
553
|
* document_free reclaims every handle ever handed out (zero-leak
|
|
554
554
|
* contract) while read-only sharing stays safe (TODO.impl/19) */
|
|
555
|
-
yeptris_node* n = yep_hpool_alloc(base->doc->dom
|
|
555
|
+
yeptris_node* n = yep_hpool_alloc(yep_dom_handles(base->doc->dom), sizeof(yeptris_node), 16);
|
|
556
556
|
if (n == NULL) {
|
|
557
557
|
return NULL;
|
|
558
558
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yeptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|