yeptris 0.6.51.0 → 0.6.53.0
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/document.rb +12 -5
- data/lib/yeptris/psych/parser.rb +4 -0
- data/lib/yeptris/psych.rb +15 -2
- data/lib/yeptris/valueml.rb +23 -31
- data/lib/yeptris.rb +1 -1
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/yeptris/cbor/from_marshal.c +7 -0
- data/vendor/libyeptris/src/yeptris/dom/dom.c +15 -8
- data/vendor/libyeptris/src/yeptris/dom/dom.h +8 -2
- data/vendor/libyeptris/src/yeptris/dom/ytape.c +2 -2
- data/vendor/libyeptris/src/yeptris/emit/emit.c +4 -0
- data/vendor/libyeptris/src/yeptris/emit/style.c +9 -191
- data/vendor/libyeptris/src/yeptris/emit/writer.c +149 -35
- data/vendor/libyeptris/src/yeptris/marshal.c +10 -5
- data/vendor/libyeptris/src/yeptris/parse/engine.c +197 -68
- data/vendor/libyeptris/src/yeptris/parse/events.h +2 -2
- data/vendor/libyeptris/src/yeptris/resolve/compat11.c +2 -1
- data/vendor/libyeptris/src/yeptris/resolve/core12.c +3 -1
- data/vendor/libyeptris/src/yeptris/scan/json.c +6 -0
- data/vendor/libyeptris/src/yeptris/scan/scan.c +202 -1
- data/vendor/libyeptris/src/yeptris/scan/scan.h +12 -1
- 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: a0e5e948f53ed1b11237b64e44a5f2e955ade6eea4d981a14fee60a82bf627a0
|
|
4
|
+
data.tar.gz: 2e60f505d82007a3741d9752dd6ceb5461d6fb40dcf16236bc093401905ba998
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bdf54e45a4a513912696189a3ad46d94d4a956d18fe9232826482194e260c3efa89965fcd87c024ee00b17e62b0b4fa90dde2500ffabb362b3d5637c913452ea
|
|
7
|
+
data.tar.gz: c0a353f61250ee46dc7aa23ec7dbf28a1cfeff0800f5d1eece0db02ee3999874c1e9381bc6aaa442658ea85a3d537f2b9534526ad4da566ebe6ed3f135787615
|
data/lib/yeptris/document.rb
CHANGED
|
@@ -16,7 +16,7 @@ class Yeptris::Document
|
|
|
16
16
|
# The schema this document was parsed with (a parse property).
|
|
17
17
|
attr_reader :parse_schema
|
|
18
18
|
|
|
19
|
-
def initialize(c_ptr = nil, freed = Freed.new(:alive), parse_schema = :core_12)
|
|
19
|
+
def initialize(c_ptr = nil, freed = Freed.new(:alive), parse_schema = :core_12, source = nil)
|
|
20
20
|
# The pointer is produced by the FFI parse/create calls; a nil (or
|
|
21
21
|
# non-pointer) argument is an API misuse — Document.parse and
|
|
22
22
|
# Document.create are the entry points. Fail with that guidance
|
|
@@ -30,6 +30,13 @@ class Yeptris::Document
|
|
|
30
30
|
@c_ptr = c_ptr
|
|
31
31
|
@freed = freed
|
|
32
32
|
@parse_schema = parse_schema
|
|
33
|
+
# the C document BORROWS the input: string values are zero-copy
|
|
34
|
+
# views over the caller's bytes (parse.h/doc.h) — the FFI call only
|
|
35
|
+
# pins the String for the call's duration, so the document must
|
|
36
|
+
# retain it or the next GC frees memory every later node.value/
|
|
37
|
+
# tag read still points into (audit 2026-10-31: parse(io.read) was
|
|
38
|
+
# collectable immediately)
|
|
39
|
+
@source = source
|
|
33
40
|
@readonly = false
|
|
34
41
|
# Strong wrapper cache keyed on the C node address: the same C node
|
|
35
42
|
# always yields the same Ruby object (identity for aliases/eql?),
|
|
@@ -61,7 +68,7 @@ class Yeptris::Document
|
|
|
61
68
|
raise Yeptris::ParseError,
|
|
62
69
|
"parse failed: #{Yeptris::FFI.last_error_message}"
|
|
63
70
|
end
|
|
64
|
-
wrap_schema(doc, schema)
|
|
71
|
+
wrap_schema(doc, schema, yaml)
|
|
65
72
|
end
|
|
66
73
|
|
|
67
74
|
def self.parse_json(json)
|
|
@@ -71,7 +78,7 @@ class Yeptris::Document
|
|
|
71
78
|
raise Yeptris::ParseError,
|
|
72
79
|
"json parse failed: #{Yeptris::FFI.last_error_message}" if doc.null?
|
|
73
80
|
|
|
74
|
-
wrap_schema(doc, :core_12) # strict JSON is core by construction
|
|
81
|
+
wrap_schema(doc, :core_12, json) # strict JSON is core by construction
|
|
75
82
|
end
|
|
76
83
|
|
|
77
84
|
# An empty document for from-scratch construction (TODO.impl/11 p3).
|
|
@@ -99,8 +106,8 @@ class Yeptris::Document
|
|
|
99
106
|
# host scalar policies (Psych's dot-required float) are conditioned
|
|
100
107
|
# on it. The parse constructors set the real one; plain wrap keeps
|
|
101
108
|
# the core_12 default (yeptris_parse's default).
|
|
102
|
-
def self.wrap_schema(c_ptr, schema)
|
|
103
|
-
new(c_ptr, Freed.new(:alive), schema)
|
|
109
|
+
def self.wrap_schema(c_ptr, schema, source = nil)
|
|
110
|
+
new(c_ptr, Freed.new(:alive), schema, source)
|
|
104
111
|
end
|
|
105
112
|
|
|
106
113
|
# #182: the stream-children wrapper owns NOTHING (the parent
|
data/lib/yeptris/psych/parser.rb
CHANGED
|
@@ -25,6 +25,10 @@ module Yeptris
|
|
|
25
25
|
yaml = yaml.to_s
|
|
26
26
|
@filename = filename
|
|
27
27
|
flat, arena = Materializer.drain(yaml, schema: :compat_11)
|
|
28
|
+
# the engine's arena is UTF-8 bytes; without the tag every
|
|
29
|
+
# scalar reached handlers as ASCII-8BIT (stdlib Psych hands
|
|
30
|
+
# UTF-8; TreeBuilder then propagated binary into node values)
|
|
31
|
+
arena.force_encoding(Encoding::UTF_8)
|
|
28
32
|
dispatch(flat, arena)
|
|
29
33
|
self
|
|
30
34
|
rescue ParseError => e
|
data/lib/yeptris/psych.rb
CHANGED
|
@@ -143,8 +143,21 @@ module Yeptris
|
|
|
143
143
|
# Psych 5: load is safe — plain data structures only. Tagged
|
|
144
144
|
# nodes raise DisallowedClass unless their class is permitted
|
|
145
145
|
# (Date/Time/Symbol are built in; they are plain data here).
|
|
146
|
-
def load(yaml, permitted_classes: [::Date, ::Time], aliases: false,
|
|
147
|
-
|
|
146
|
+
def load(yaml, permitted_classes: [::Date, ::Time], aliases: false,
|
|
147
|
+
symbolize_names: false, fallback: nil, filename: nil, **)
|
|
148
|
+
res = safe_load(yaml, permitted_classes: permitted_classes, aliases: aliases,
|
|
149
|
+
symbolize_names: symbolize_names)
|
|
150
|
+
if res.nil? && !fallback.nil?
|
|
151
|
+
# stdlib's fallback: answers when the stream is empty or has
|
|
152
|
+
# NO documents — a document whose content IS nil still loads
|
|
153
|
+
# as nil. The safe path cannot tell those two apart, so the
|
|
154
|
+
# corner re-checks the count (only when a fallback rides).
|
|
155
|
+
doc = ::Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
156
|
+
empty = doc.nil? || doc.document_count.zero?
|
|
157
|
+
doc&.free
|
|
158
|
+
return fallback if empty
|
|
159
|
+
end
|
|
160
|
+
res
|
|
148
161
|
end
|
|
149
162
|
|
|
150
163
|
# Full revival over the Nodes tree: !ruby/object, !ruby/struct,
|
data/lib/yeptris/valueml.rb
CHANGED
|
@@ -20,6 +20,25 @@ module Yeptris
|
|
|
20
20
|
# int64 the C resolver leaves the scalar a string; Psych
|
|
21
21
|
# materializes Integer (issue #31) — every walk rebuilds it.
|
|
22
22
|
PSYCH_INT_SHAPE = /\A[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*)\z/.freeze
|
|
23
|
+
|
|
24
|
+
# The typed-string conversion law, ONE place: a big-int-shaped
|
|
25
|
+
# string rebuilds to Integer, an implicit-plain ':name' scans to a
|
|
26
|
+
# Symbol (Psych's ScalarScanner; quoted ':x' stays a String).
|
|
27
|
+
# Every str-column conversion rides this — the pair fast path
|
|
28
|
+
# drifted once and handed bignums/symbol-keys through as Strings
|
|
29
|
+
# (audit 2026-10-31).
|
|
30
|
+
def self.str_to_value(text, typed)
|
|
31
|
+
return text unless typed
|
|
32
|
+
|
|
33
|
+
if text.length > 18 && PSYCH_INT_SHAPE.match?(text)
|
|
34
|
+
text.delete(",_").to_i
|
|
35
|
+
elsif text.length > 1 && text.start_with?(":") && !text.start_with?("::")
|
|
36
|
+
text[1..].to_sym
|
|
37
|
+
else
|
|
38
|
+
text
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
23
42
|
V_TS = 6
|
|
24
43
|
SEQ_OPEN = 7
|
|
25
44
|
MAP_OPEN = 8
|
|
@@ -155,15 +174,7 @@ module Yeptris
|
|
|
155
174
|
text = arena.byteslice(flat[i + OFF], flat[i + LEN])
|
|
156
175
|
# implicit-plain ':name' scans to a Symbol (Psych's
|
|
157
176
|
# ScalarScanner); quoted ':x' stays a String
|
|
158
|
-
v =
|
|
159
|
-
PSYCH_INT_SHAPE.match?(text)
|
|
160
|
-
text.delete(",_").to_i
|
|
161
|
-
elsif flat[i + B] == 1 && text.length > 1 &&
|
|
162
|
-
text.start_with?(":") && !text.start_with?("::")
|
|
163
|
-
text[1..].to_sym
|
|
164
|
-
else
|
|
165
|
-
text
|
|
166
|
-
end
|
|
177
|
+
v = str_to_value(text, flat[i + B] == 1)
|
|
167
178
|
if pending_anchor
|
|
168
179
|
anchors[pending_anchor] = v
|
|
169
180
|
pending_anchor = nil
|
|
@@ -284,20 +295,9 @@ module Yeptris
|
|
|
284
295
|
pending_anchor.nil? && !stack.empty? && stack.last.is_a?(Hash)
|
|
285
296
|
kt = arena.byteslice(offs[i], lens[i])
|
|
286
297
|
if kt != "<<"
|
|
287
|
-
key =
|
|
288
|
-
!kt.start_with?("::")
|
|
289
|
-
kt[1..].to_sym
|
|
290
|
-
else
|
|
291
|
-
kt
|
|
292
|
-
end
|
|
298
|
+
key = str_to_value(kt, bools[i] == 1)
|
|
293
299
|
vt = arena.byteslice(offs[i + 1], lens[i + 1])
|
|
294
|
-
stack.last[key] =
|
|
295
|
-
if bools[i + 1] == 1 && vt.length > 1 && vt.start_with?(":") &&
|
|
296
|
-
!vt.start_with?("::")
|
|
297
|
-
vt[1..].to_sym
|
|
298
|
-
else
|
|
299
|
-
vt
|
|
300
|
-
end
|
|
300
|
+
stack.last[key] = str_to_value(vt, bools[i + 1] == 1)
|
|
301
301
|
i += 2
|
|
302
302
|
next
|
|
303
303
|
end
|
|
@@ -305,15 +305,7 @@ module Yeptris
|
|
|
305
305
|
case kinds[i]
|
|
306
306
|
when V_STR
|
|
307
307
|
text = arena.byteslice(offs[i], lens[i])
|
|
308
|
-
v =
|
|
309
|
-
PSYCH_INT_SHAPE.match?(text)
|
|
310
|
-
text.delete(",_").to_i
|
|
311
|
-
elsif bools[i] == 1 && text.length > 1 &&
|
|
312
|
-
text.start_with?(":") && !text.start_with?("::")
|
|
313
|
-
text[1..].to_sym
|
|
314
|
-
else
|
|
315
|
-
text
|
|
316
|
-
end
|
|
308
|
+
v = str_to_value(text, bools[i] == 1)
|
|
317
309
|
if pending_anchor
|
|
318
310
|
anchors[pending_anchor] = v
|
|
319
311
|
pending_anchor = nil
|
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.53.0".freeze
|
|
7
7
|
|
|
8
8
|
# Informational notices are OPT-IN (yeptris-ruby#217): consumers
|
|
9
9
|
# embed yeptris inside CLIs that assert clean stderr, and a
|
|
@@ -687,6 +687,13 @@ static int cm_value(cm_ctx* c, cm_buf* out) {
|
|
|
687
687
|
return cm_fail(c, YEPTRIS_ERROR_UNSUPPORTED); /* the host loop raises */
|
|
688
688
|
}
|
|
689
689
|
double d = strtod(tmp, NULL);
|
|
690
|
+
if (!isfinite(d)) {
|
|
691
|
+
/* the literal spellings gate above; a strtod OVERFLOW
|
|
692
|
+
* ("1e999") manufactures the same non-finite without
|
|
693
|
+
* spelling it — the same strict reject (Ruby streams only
|
|
694
|
+
* ever carry finites or the literal inf/nan spellings) */
|
|
695
|
+
return cm_fail(c, YEPTRIS_ERROR_UNSUPPORTED);
|
|
696
|
+
}
|
|
690
697
|
uint8_t fw = yep_cbor_float_width(d);
|
|
691
698
|
if (cm_reserve(c, out, 9) != 0) {
|
|
692
699
|
return -1;
|
|
@@ -142,14 +142,16 @@ static yep_sview dom_str_in(yep_dom* d, const yep_view* v, int borrowed) {
|
|
|
142
142
|
* given metadata; tag/anchor views encode through dom_str_in (tags and
|
|
143
143
|
* event-path anchors copy to the arena; input-backed views borrow).
|
|
144
144
|
* tag_id stays 0 — the caller resolves (scalars) or leaves 0. */
|
|
145
|
-
/* Value assign + the emit-side fact
|
|
146
|
-
* is "verified clean" of the dq-forced byte set
|
|
147
|
-
*
|
|
148
|
-
*
|
|
145
|
+
/* Value assign + the emit-side fact bits (issues #352 lanes 2-3):
|
|
146
|
+
* no_exotic is "verified clean" of the dq-forced byte set (sc_route
|
|
147
|
+
* skips its rescan); plain_ok is the full plain-safety verdict (the
|
|
148
|
+
* emitter skips its walk — the block kernel's 35% frame). Both facts
|
|
149
|
+
* ride the scan/'s SSOT predicates; every value assignment routes
|
|
149
150
|
* through here so no path can leave a dirty scalar marked clean. */
|
|
150
151
|
static void dom_set_value(yep_dom* d, uint32_t id, const yep_view* v, int borrowed) {
|
|
151
152
|
d->nodes[id].value = dom_str_in(d, v, borrowed);
|
|
152
153
|
d->nodes[id].no_exotic = !yep_scan_dq_forced(v->p, v->len);
|
|
154
|
+
d->nodes[id].plain_ok = (uint8_t)yep_scan_plain_safe(v->p, v->len);
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
uint32_t dom_open_node(yep_dom* d, uint8_t kind, const yep_view* tag, const yep_view* anchor,
|
|
@@ -221,8 +223,13 @@ static int dom_anchor_set(yep_dom* d, uint32_t ordinal, uint32_t node) {
|
|
|
221
223
|
if (d->anchor_nodes != NULL) {
|
|
222
224
|
memcpy(na, d->anchor_nodes, d->anchor_nodes_cap * sizeof(*na));
|
|
223
225
|
yep_free(d->sys, d->anchor_nodes);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
/* ONLY the anchor table: the mut side tables belong to
|
|
227
|
+
* dom_mut_grow alone (it allocates, copies and frees them
|
|
228
|
+
* together with the cap that tracks their size). These two
|
|
229
|
+
* frees were copy-paste debris from that path -- harmless
|
|
230
|
+
* today only because the mutation tables cannot exist
|
|
231
|
+
* during a parse -- but the ownership must not rely on
|
|
232
|
+
* that accident (audit 2026-10-31). */
|
|
226
233
|
}
|
|
227
234
|
d->anchor_nodes = na;
|
|
228
235
|
d->anchor_nodes_cap = cap;
|
|
@@ -1139,7 +1146,7 @@ void dom_on_flow_rollback(void* ctx) {
|
|
|
1139
1146
|
* call — the DOM stack mirrors what the MAP_START event would have
|
|
1140
1147
|
* pushed. 0 only for the depth cap (the engine then takes the events,
|
|
1141
1148
|
* which own the error). */
|
|
1142
|
-
int dom_on_block_open(void* ctx, const yep_view* key, uint32_t line,
|
|
1149
|
+
int dom_on_block_open(void* ctx, const yep_view* key, uint32_t line, uint32_t key_col) {
|
|
1143
1150
|
yep_dom* d = (yep_dom*)ctx;
|
|
1144
1151
|
if (d->depth >= YEP_DOM_MAX_DEPTH) {
|
|
1145
1152
|
return 0;
|
|
@@ -1187,7 +1194,7 @@ int dom_on_block_item(void* ctx, const yep_block_value* v) {
|
|
|
1187
1194
|
* happen when the engine resolved the name) falls back to the two
|
|
1188
1195
|
* events with nothing built. */
|
|
1189
1196
|
int dom_on_block_pair(void* ctx, const yep_view* key, const yep_block_value* v, uint32_t line,
|
|
1190
|
-
|
|
1197
|
+
uint32_t key_col, uint32_t val_col) {
|
|
1191
1198
|
yep_dom* d = (yep_dom*)ctx;
|
|
1192
1199
|
uint32_t target = 0;
|
|
1193
1200
|
if (v->cls == YEP_LVAL_ALIAS) {
|
|
@@ -68,6 +68,12 @@ typedef struct yep_dnode {
|
|
|
68
68
|
* byte set (#352 lane 2): the serializer's
|
|
69
69
|
* sc_route skips its rescan. Set at every
|
|
70
70
|
* value assignment; 0 = unknown = scan. */
|
|
71
|
+
uint8_t plain_ok; /* value bytes verified yep_scan_plain_safe
|
|
72
|
+
* (#352 lane 3): the emitter skips its
|
|
73
|
+
* plain-safety walk (the block kernel's 35%
|
|
74
|
+
* frame). Same invariant as no_exotic: set
|
|
75
|
+
* at every dom_set_value; 0 = unknown =
|
|
76
|
+
* derive. */
|
|
71
77
|
uint8_t tag_id; /* resolved tag (resolve/resolver.h) */
|
|
72
78
|
uint32_t first_child; /* child link (mappings: key,value,key,value…) */
|
|
73
79
|
uint32_t last_child;
|
|
@@ -283,8 +289,8 @@ int dom_on_scalar(void* ctx, const yep_view* value, const yep_view* tag, const y
|
|
|
283
289
|
int borrowed);
|
|
284
290
|
|
|
285
291
|
int dom_on_block_pair(void* ctx, const yep_view* key, const struct yep_block_value* v,
|
|
286
|
-
uint32_t line,
|
|
287
|
-
int dom_on_block_open(void* ctx, const yep_view* key, uint32_t line,
|
|
292
|
+
uint32_t line, uint32_t key_col, uint32_t val_col);
|
|
293
|
+
int dom_on_block_open(void* ctx, const yep_view* key, uint32_t line, uint32_t key_col);
|
|
288
294
|
int dom_on_block_item(void* ctx, const yep_block_value* v);
|
|
289
295
|
|
|
290
296
|
/* Direct DOM construction from a strict-validated JSON buffer
|
|
@@ -241,7 +241,7 @@ static int yt_on_scalar(void* ctx, const yep_view* value, const yep_view* tag,
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
static int yt_on_block_pair(void* ctx, const yep_view* key, const yep_block_value* v, uint32_t line,
|
|
244
|
-
|
|
244
|
+
uint32_t key_col, uint32_t val_col) {
|
|
245
245
|
(void)line;
|
|
246
246
|
(void)key_col;
|
|
247
247
|
(void)val_col; /* yep_dnode carries no positions (64-2c) */
|
|
@@ -278,7 +278,7 @@ static int yt_on_block_pair(void* ctx, const yep_view* key, const yep_block_valu
|
|
|
278
278
|
return 1; /* handled: the engine skips the two events */
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
static int yt_on_block_open(void* ctx, const yep_view* key, uint32_t line,
|
|
281
|
+
static int yt_on_block_open(void* ctx, const yep_view* key, uint32_t line, uint32_t key_col) {
|
|
282
282
|
(void)line;
|
|
283
283
|
(void)key_col;
|
|
284
284
|
yep_ytape* t = (yep_ytape*)ctx;
|
|
@@ -551,6 +551,10 @@ YEPTRIS_API size_t yeptris_serialize_stream(YeptrisDocument handle,
|
|
|
551
551
|
return 0;
|
|
552
552
|
}
|
|
553
553
|
em.w.p = window;
|
|
554
|
+
/* the writer owns the window size now: wr_put's chunked path and
|
|
555
|
+
* the fused member write bounds-check against it (a scalar larger
|
|
556
|
+
* than the window used to memcpy straight past it) */
|
|
557
|
+
em.w.cap = YEP_EMIT_WATERMARK * 2;
|
|
554
558
|
em.w.sink_aborted = 0;
|
|
555
559
|
size_t total = yep_emit_run(&em, 0);
|
|
556
560
|
if (em.w.len > 0 && !em.w.sink_aborted) {
|
|
@@ -1,203 +1,21 @@
|
|
|
1
|
-
/* style.c — the style rule table (TODO.impl/13).
|
|
1
|
+
/* style.c — the style rule table (TODO.impl/13).
|
|
2
|
+
*
|
|
3
|
+
* The plain-safety predicate LIVES in scan/scan.c (yep_scan_plain_safe,
|
|
4
|
+
* #352 lane 3): bytes→facts is scan/'s alone, and the DOM's parse-time
|
|
5
|
+
* fact bit and this emitter side read the ONE walk so the verdicts
|
|
6
|
+
* cannot drift. These are the emit-side names over it. */
|
|
2
7
|
|
|
3
8
|
#include "style.h"
|
|
4
9
|
|
|
5
|
-
#include
|
|
6
|
-
|
|
7
|
-
static int is_blank(char c) {
|
|
8
|
-
return c == ' ' || c == '\t';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static int is_break(char c) {
|
|
12
|
-
return c == '\n' || c == '\r';
|
|
13
|
-
}
|
|
10
|
+
#include "../scan/scan.h"
|
|
14
11
|
|
|
15
12
|
int yep_style_plain_safe(const char* p, uint32_t len) {
|
|
16
|
-
|
|
17
|
-
return 0; /* empty needs quotes or an empty-value slot */
|
|
18
|
-
}
|
|
19
|
-
if (is_blank(p[0]) || is_blank(p[len - 1])) {
|
|
20
|
-
return 0;
|
|
21
|
-
}
|
|
22
|
-
if (len >= 3 && (unsigned char)p[0] == 0xef && (unsigned char)p[1] == 0xbb &&
|
|
23
|
-
(unsigned char)p[2] == 0xbf) {
|
|
24
|
-
/* a LEADING U+FEFF re-parses as a document BOM and vanishes —
|
|
25
|
-
* the emitted form must carry its escape ("\uFEFF", the
|
|
26
|
-
* nightly's run 38258674337); mid-token BOMs ride plain */
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
switch (p[0]) {
|
|
30
|
-
case ':':
|
|
31
|
-
/* libyaml (Psych symbol dumps ":name"): a leading colon only
|
|
32
|
-
* indicates when followed by a blank/EOL — the interior rule
|
|
33
|
-
* below covers it; ":name" stays plain */
|
|
34
|
-
break;
|
|
35
|
-
case '-':
|
|
36
|
-
case '?':
|
|
37
|
-
/* indicators only before a blank or EOL: "-5" and "?x" are
|
|
38
|
-
* plain (libyaml emits negative numbers plain; Psych, too) —
|
|
39
|
-
* but fall through: the document-marker check below owns
|
|
40
|
-
* the bare "---"/"..." (libyaml quotes those) */
|
|
41
|
-
if (len == 1 || is_blank(p[1])) {
|
|
42
|
-
return 0;
|
|
43
|
-
}
|
|
44
|
-
break;
|
|
45
|
-
case ',':
|
|
46
|
-
case '[':
|
|
47
|
-
case ']':
|
|
48
|
-
case '{':
|
|
49
|
-
case '}':
|
|
50
|
-
case '#':
|
|
51
|
-
case '&':
|
|
52
|
-
case '*':
|
|
53
|
-
case '!':
|
|
54
|
-
case '|':
|
|
55
|
-
case '>':
|
|
56
|
-
case '\'':
|
|
57
|
-
case '"':
|
|
58
|
-
case '%':
|
|
59
|
-
case '@':
|
|
60
|
-
case '`':
|
|
61
|
-
return 0;
|
|
62
|
-
default:
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
/* The interior scan in SWAR words: every reject condition is a
|
|
66
|
-
* byte-class adjacency, so one masked word test is exact —
|
|
67
|
-
* break/tab bytes reject directly; ':' rejects with a blank at its
|
|
68
|
-
* NEXT lane (or the span's EOL); '#' with a blank at its PREVIOUS
|
|
69
|
-
* lane (the span's first byte is exempt — the head switch already
|
|
70
|
-
* handled a leading '#'). Cross-word adjacency rides one carried
|
|
71
|
-
* byte per boundary; the tail runs the byte loop, whose rules read
|
|
72
|
-
* real neighbors and need no carries. */
|
|
73
|
-
{
|
|
74
|
-
const uint64_t ONES = 0x0101010101010101ull;
|
|
75
|
-
const uint64_t HIGH = 0x8080808080808080ull;
|
|
76
|
-
#define YST_ZDET(w, ch) \
|
|
77
|
-
((((w) ^ (uint64_t)(ch) * ONES) - ONES) & ~((w) ^ (uint64_t)(ch) * ONES) & HIGH)
|
|
78
|
-
/* the all-boring accept (#742): a span of pure [0-9A-Za-z_]
|
|
79
|
-
* makes EVERY rule in this function vacuous — no indicators
|
|
80
|
-
* (the head switch), no blanks at the ends, no BOM, no
|
|
81
|
-
* ': '/' #' adjacency, no breaks, and no ---/... marker — so
|
|
82
|
-
* one range test per word accepts without the six-mask walk.
|
|
83
|
-
* Anything else falls through to the exact scan below. */
|
|
84
|
-
{
|
|
85
|
-
uint32_t bi = 0;
|
|
86
|
-
int boring = 1;
|
|
87
|
-
while (bi + 8 <= len) {
|
|
88
|
-
uint64_t bw;
|
|
89
|
-
memcpy(&bw, p + bi, 8);
|
|
90
|
-
if (bw & HIGH) {
|
|
91
|
-
boring = 0;
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
/* per-lane unsigned range checks via borrow bits: alpha
|
|
95
|
-
* is (t - 'a') and ('z' - t) both borrow-free with
|
|
96
|
-
* t = w|0x20 (A-Z fold); digits bound 0x30..0x39 */
|
|
97
|
-
uint64_t t = bw | (ONES * 0x20);
|
|
98
|
-
uint64_t bad = ((t - ONES * 0x61) | (ONES * 0x7A - t)) & HIGH; /* alpha */
|
|
99
|
-
bad |= ((bw - ONES * 0x30) | (ONES * 0x39 - bw)) & HIGH; /* digit */
|
|
100
|
-
bad &= ~YST_ZDET(bw, '_'); /* the one extra boring byte */
|
|
101
|
-
if (bad != 0) {
|
|
102
|
-
boring = 0;
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
bi += 8;
|
|
106
|
-
}
|
|
107
|
-
if (boring) {
|
|
108
|
-
for (; bi < len; bi++) {
|
|
109
|
-
char c = p[bi];
|
|
110
|
-
int a = (c | 0x20);
|
|
111
|
-
if (!((c >= '0' && c <= '9') || (a >= 'a' && a <= 'z') || c == '_')) {
|
|
112
|
-
boring = 0;
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (boring) {
|
|
117
|
-
return 1;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
uint32_t i = 0;
|
|
122
|
-
int prev_blank7 = 0;
|
|
123
|
-
while (i + 8 <= len) {
|
|
124
|
-
uint64_t w;
|
|
125
|
-
memcpy(&w, p + i, 8);
|
|
126
|
-
if (w & HIGH) {
|
|
127
|
-
/* multibyte content: the byte loop owns the rest — a raw
|
|
128
|
-
* LS/PS/NEL rides in a scalar value since the quote-aware
|
|
129
|
-
* normalizer (#638) and is a line break on re-parse, so
|
|
130
|
-
* the value must be quoted, not plain (the nightly's
|
|
131
|
-
* round 18, crash-1e79b2e3) */
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
uint64_t space = YST_ZDET(w, ' ');
|
|
135
|
-
uint64_t tab = YST_ZDET(w, '\t');
|
|
136
|
-
uint64_t blank = space | tab;
|
|
137
|
-
uint64_t colon = YST_ZDET(w, ':');
|
|
138
|
-
uint64_t hash = YST_ZDET(w, '#');
|
|
139
|
-
/* tab and the breaks reject outright */
|
|
140
|
-
if (tab | YST_ZDET(w, '\n') | YST_ZDET(w, '\r')) {
|
|
141
|
-
return 0;
|
|
142
|
-
}
|
|
143
|
-
/* ':' + blank next: (blank >> 8) pulls lane i+1's flag down
|
|
144
|
-
* into lane i; the word's last lane takes the peeked byte
|
|
145
|
-
* (EOL at the span end counts as blank — the rule rejects
|
|
146
|
-
* there) */
|
|
147
|
-
if (colon) {
|
|
148
|
-
int next_blank = i + 8 < len ? is_blank(p[i + 8]) : 1;
|
|
149
|
-
if (colon & ((blank >> 8) | (next_blank ? (uint64_t)0x80 << 56 : 0))) {
|
|
150
|
-
return 0;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
/* '#' + blank prev: (blank << 8) lifts lane i-1's flag up
|
|
154
|
-
* into lane i; lane 0 takes the carried last-lane flag */
|
|
155
|
-
if (hash && (hash & ((blank << 8) | (prev_blank7 ? 0x80 : 0)))) {
|
|
156
|
-
return 0;
|
|
157
|
-
}
|
|
158
|
-
prev_blank7 = (int)((blank >> 63) & 1); /* lane 7's flag is its top bit */
|
|
159
|
-
i += 8;
|
|
160
|
-
}
|
|
161
|
-
#undef YST_ZDET
|
|
162
|
-
for (; i < len; i++) {
|
|
163
|
-
char c = p[i];
|
|
164
|
-
if (is_break(c) || c == '\t') {
|
|
165
|
-
return 0;
|
|
166
|
-
}
|
|
167
|
-
if ((unsigned char)c == 0xE2 && i + 2 < len && (unsigned char)p[i + 1] == 0x80 &&
|
|
168
|
-
((unsigned char)p[i + 2] & 0xFE) == 0xA8) {
|
|
169
|
-
return 0; /* a raw LS/PS re-parses as a line break */
|
|
170
|
-
}
|
|
171
|
-
if ((unsigned char)c == 0xC2 && i + 1 < len && (unsigned char)p[i + 1] == 0x85) {
|
|
172
|
-
return 0; /* a raw NEL ditto */
|
|
173
|
-
}
|
|
174
|
-
if (c == ':') {
|
|
175
|
-
if (i + 1 >= len || is_blank(p[i + 1])) {
|
|
176
|
-
return 0;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (c == '#') {
|
|
180
|
-
if (i > 0 && is_blank(p[i - 1])) {
|
|
181
|
-
return 0;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
/* document markers */
|
|
187
|
-
if (len >= 3 && (memcmp(p, "---", 3) == 0 || memcmp(p, "...", 3) == 0) &&
|
|
188
|
-
(len == 3 || p[3] == ' ' || p[3] == '\t' || p[3] == '\n' || p[3] == '\r')) {
|
|
189
|
-
/* a PREFIX marker is worse than the bare one: emitted plain at
|
|
190
|
-
* line start, "--- ..." re-reads as a document marker (the
|
|
191
|
-
* nightly's "--- --- >" folded-plain crasher) — libyaml quotes
|
|
192
|
-
* both */
|
|
193
|
-
return 0;
|
|
194
|
-
}
|
|
195
|
-
return 1;
|
|
13
|
+
return yep_scan_plain_safe(p, len);
|
|
196
14
|
}
|
|
197
15
|
|
|
198
16
|
int yep_style_plain_key_safe(const char* p, uint32_t len) {
|
|
199
17
|
/* plain_safe's colon rule is the key rule too: a colon only ends a
|
|
200
18
|
* key when followed by a blank/EOL, so "a:b" and ":name" are plain
|
|
201
19
|
* keys (libyaml emits Psych's symbol dumps exactly this way) */
|
|
202
|
-
return
|
|
20
|
+
return yep_scan_plain_safe(p, len);
|
|
203
21
|
}
|