yeptris 0.6.51.0 → 0.6.52.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a140e44f7568dcc17cebcacaacd71aa08762fbd95316514f7095550326ed7b83
4
- data.tar.gz: eb554c000a449f1fb573708c0b7f256984aa64cd33666ec4da97cf1905c1fc64
3
+ metadata.gz: 39841fd37a06d428edeecabd2ec9b89ebbf3b0576e3a113a43b36eca0a0c67d0
4
+ data.tar.gz: 3fae8c03db5dbb3ddc31f0711a36cf6851b3abb7883e334dc161105070c6f6df
5
5
  SHA512:
6
- metadata.gz: 57e467a5138b4e7e98fafa311625a45af0187a9635331a5c70494ba4f2ead7630bc12ae29e641eb52d9565f87f9488dea205612404b9d20683a52ff94a7a8807
7
- data.tar.gz: 626fb65660cd98c68270e582d692987a9eb270d068d94e2b0a97b34d4b15e3ba3a916e8a6937cfd4a1fbb5100b43278f47972c871f38d99f74193e49246ccd3a
6
+ metadata.gz: 288cf0885ea87923b8974793617a8420ac6115df05b03615170b6d4abb7d779227412904d324951dcd81009e99a3240b9cc0bb77be2087fd6d4b03d43f0c572c
7
+ data.tar.gz: 1556db96baf38b002a65a0b79e0dc112de10a5bddd808e66600ada366c33c6fd408590da118f7d2d47d2f605c427bd2ae886b0d0246b2785bea7eaab2882e836
@@ -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.52.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
@@ -379,7 +379,8 @@ static int e_comment_at(const yep_engine* e) {
379
379
  }
380
380
 
381
381
  static int e_at_eol(yep_engine* e) {
382
- return e->pos >= e->len || e->p[e->pos] == '\n' || e->p[e->pos] == '\r' || e_comment_at(e);
382
+ return e->pos >= e->len || e->p[e->pos] == '\n' || e->p[e->pos] == '\r' || e_comment_at(e) ||
383
+ yep_scan_mb_break_len(e->p, e->len, e->pos) > 0;
383
384
  }
384
385
 
385
386
  static int e_skip_to_eol(yep_engine* e) {
@@ -3890,6 +3891,16 @@ static int e_parse_value(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
3890
3891
  if (e->pos >= e->len) {
3891
3892
  goto empty_value;
3892
3893
  }
3894
+ /* a raw NEL/LS/PS here is this line's break: consume it exactly
3895
+ * as the BLANK branch below consumes an LF-ended empty line —
3896
+ * yep_scan_line's line model does not treat a break byte at the
3897
+ * loop head as a line end, so without this the props-only line
3898
+ * re-entered e_node on the break byte (rows 1-2) and the marker
3899
+ * became the scalar's value (row 5's consume-first form) */
3900
+ if (yep_scan_mb_break_len(e->p, e->len, e->pos) > 0) {
3901
+ e_line_done(e, e->pos);
3902
+ continue;
3903
+ }
3893
3904
  yep_line_info li;
3894
3905
  if (e->pos == e->line_start) {
3895
3906
  /* seed the memo: the fast arms re-read this line's facts —
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.51.0
4
+ version: 0.6.52.0
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-10-30 00:00:00.000000000 Z
11
+ date: 2026-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi