yeptris 0.6.51.0-arm-linux-eabihf → 0.6.53.0-arm-linux-eabihf

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1762c833f5c3ee978bdf6b43d59e7ae4523ff6edba01ade64354eddd3f3cfbf9
4
- data.tar.gz: a84e2415681f867a3aa4b1d45ee16ccfcb66213bd6f8494905ec9d420663c217
3
+ metadata.gz: 49aed5138b8f170e696098a0698b5033e98759f5e95e03828fdb49b323e92da7
4
+ data.tar.gz: '0029418e8dd3ed5d41d89683414a9b29fcc29a40a4dd5cb8e4eb53141ff96c13'
5
5
  SHA512:
6
- metadata.gz: c87d565da52486b460e944c1a04aa89f100118de2ad0255e31a0d0497dbc7d2b5c588531b7097f2212087006a0cec2a39d44734159605b060c16029bfd76940a
7
- data.tar.gz: 352d9550314e65f0c920d97ac1859d36fdb0b3492acd965be1f7568452e057fbe30dbf5ad7b10fa09ee15e70c0844bbdce691f77a8f7479654fb2cc94e1d32fd
6
+ metadata.gz: b6d55c01b48054dd92faa635b018514ef6fda5d897a6d6813716c72faa9ea61360435ad002df22b45c33cb5fbda8ed88f9fd8febe9502e667ce023e3dd1d478f
7
+ data.tar.gz: c2904a905ad014413a43ffb8cb2b12dba583a2e3f83fa6753c4b4724b43ca652f9fed770bf6d2eb55f98819b5db1260fd7a8293252d005d8c2f090995162a092
@@ -11,8 +11,8 @@
11
11
 
12
12
  #define YEPTRIS_VERSION_MAJOR 0
13
13
  #define YEPTRIS_VERSION_MINOR 6
14
- #define YEPTRIS_VERSION_PATCH 51
15
- #define YEPTRIS_VERSION_STRING "0.6.51"
14
+ #define YEPTRIS_VERSION_PATCH 53
15
+ #define YEPTRIS_VERSION_STRING "0.6.53"
16
16
 
17
17
  #ifdef __cplusplus
18
18
  extern "C" {
@@ -483,7 +483,17 @@ RUBY_FUNC_EXPORTED void Init_native(void) {
483
483
  rb_define_singleton_method(mNat, "shape_mode", native_shape_mode, 0);
484
484
  rb_define_singleton_method(mNat, "shape_mode=", native_shape_mode_set, 1);
485
485
  rb_define_singleton_method(mNat, "scan_time", native_scan_time, 2);
486
- rb_define_const(mNat, "AVAILABLE", Qtrue);
486
+ /* the leptris bridge vendors this same ext (leptris's
487
+ * lib/leptris/xml/native_3_3.so), so both gems can co-initialize
488
+ * in one process: the second init re-ran rb_define_const and
489
+ * warned "already initialized constant Yeptris::Native::AVAILABLE"
490
+ * — which moxml treated as an adapter load failure and fell back
491
+ * to nokogiri for the rest of the process (yeptris-ruby#336).
492
+ * Re-registration is idempotent: skip the const when the first
493
+ * init (either gem's copy) already defined it. */
494
+ if (!rb_const_defined_at(mNat, rb_intern("AVAILABLE"))) {
495
+ rb_define_const(mNat, "AVAILABLE", Qtrue);
496
+ }
487
497
  const char* env = getenv("YEPTRIS_NATIVE_GC");
488
498
  if (env != NULL && strcmp(env, "none") == 0) yep_rb_set_gc_mode(1);
489
499
  else if (env != NULL && strcmp(env, "start") == 0) yep_rb_set_gc_mode(2);
Binary file
@@ -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
@@ -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
- safe_load(yaml, permitted_classes: permitted_classes, aliases: aliases)
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,
@@ -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 = if flat[i + B] == 1 && text.length > 18 &&
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 = if bools[i] == 1 && kt.length > 1 && kt.start_with?(":") &&
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 = if bools[i] == 1 && text.length > 18 &&
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.51.0".freeze
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
data/libyeptris.so CHANGED
Binary file
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.51
6
+ VERSION 0.6.53
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -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 bit (issue #352 lane 2): the bit
146
- * is "verified clean" of the dq-forced byte set — the serializer's
147
- * sc_route then skips its per-derive rescan. The scan is the SSOT
148
- * predicate (yep_scan_dq_forced); every value assignment routes
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
- yep_free(d->sys, d->mut_att);
225
- yep_free(d->sys, d->mut_depth);
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, uint16_t key_col) {
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
- uint16_t key_col, uint16_t val_col) {
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, uint16_t key_col, uint16_t val_col);
287
- int dom_on_block_open(void* ctx, const yep_view* key, uint32_t line, uint16_t key_col);
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
- uint16_t key_col, uint16_t val_col) {
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, uint16_t key_col) {
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 <string.h>
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
- if (len == 0) {
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 yep_style_plain_safe(p, len);
20
+ return yep_scan_plain_safe(p, len);
203
21
  }