yeptris 0.6.13.1 → 0.6.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5dd96eb5583028d0da7d38ee4addaf108b8a2495696aacc6f68c43117773da3
4
- data.tar.gz: b489c5bfc4b41d9cb3d8b836ac4b88f62aaeab5856788412301cde79ee0dfd2f
3
+ metadata.gz: 0033737c829c7beb3ae83ca816033386b4fe23b53968f2b0ff40a74c97aac469
4
+ data.tar.gz: c27accdae4e4807500d4edbfa39228b003c70e5380de3f259bd5315dc9a2dc6f
5
5
  SHA512:
6
- metadata.gz: 4d91f1cdb572d444d4f7a43aec756400375581b3fa7bb1f6bbf62e9ad134ffa392fb1a763f11daffb07ae865e4046b6379a15b7f57eec5fb72c842bb580aee52
7
- data.tar.gz: 7ac70b81ca8cf423226b48f8e6422686dc6edd9593a94bc82ebc7d0470ee40a9de9f28b4bd0f4ddf1c85ee1e132156fa1d889db8a1af9a788a6f5384f60c08c6
6
+ metadata.gz: ca7d477d959df73f231e443f15ceef0403774129e2df0af49d94e2f926d24c380cbacf6e92d88e9488715c028de0533e93fa82ca039ee129dd13b90f4b4badd0
7
+ data.tar.gz: 54b35dfe9c5399157ffbbb1e33b058afbe523e36222771d000ff697ccaccd059d5b049a3383ac2fbad61086d4548d3bc23343f600311c4c8438535d2a87cc2a8
@@ -26,18 +26,23 @@ class Yeptris::Document
26
26
  def self.parse(yaml, schema: :core_12, max_depth: 0)
27
27
  yaml = Yeptris.read_input(yaml)
28
28
  yaml = yaml.to_s
29
+ status = ::FFI::MemoryPointer.new(:int)
29
30
  doc =
30
31
  if schema == :core_12 && max_depth.zero?
31
- Yeptris::FFI.yeptris_parse(yaml, yaml.bytesize, nil)
32
+ Yeptris::FFI.yeptris_parse(yaml, yaml.bytesize, status)
32
33
  else
33
34
  opts = Yeptris::FFI::ParseOptions.new
34
35
  opts[:schema] = schema == :compat_11 ? Yeptris::FFI::SCHEMA_11_COMPAT : Yeptris::FFI::SCHEMA_12_CORE
35
36
  opts[:max_depth] = max_depth
36
- Yeptris::FFI.yeptris_parse_ex(yaml, yaml.bytesize, opts, nil)
37
+ Yeptris::FFI.yeptris_parse_ex(yaml, yaml.bytesize, opts, status)
37
38
  end
38
39
  if doc.null?
39
- # the status out-param is skipped: the thread-local error
40
- # channel carries the failure detail (measurable on small docs)
40
+ # NULL + OK is the legal empty stream (no documents): nil, like
41
+ # stdlib. The status out-param is the discriminator the TLS
42
+ # error message cannot serve (a stale error from an earlier
43
+ # parse persists on the thread).
44
+ return nil if status.read_int.zero?
45
+
41
46
  raise Yeptris::ParseError,
42
47
  "parse failed: #{Yeptris::FFI.last_error_message}"
43
48
  end
data/lib/yeptris/ffi.rb CHANGED
@@ -174,6 +174,8 @@ module Yeptris
174
174
  attach_function :yeptris_node_bool, %i[yeptris_node pointer], :yeptris_status
175
175
  attach_function :yeptris_node_seq_count, [:yeptris_node], :size_t
176
176
  attach_function :yeptris_node_seq_at, %i[yeptris_node size_t], :yeptris_node
177
+ attach_function :yeptris_node_children,
178
+ %i[yeptris_node pointer size_t], :size_t
177
179
  attach_function :yeptris_node_map_count, [:yeptris_node], :size_t
178
180
  attach_function :yeptris_node_map_get, %i[yeptris_node pointer size_t], :yeptris_node
179
181
  attach_function :yeptris_node_map_at,
@@ -227,6 +229,10 @@ module Yeptris
227
229
  attach_function :yeptris_marshal_node,
228
230
  %i[yeptris_node pointer pointer], :int
229
231
  attach_function :yeptris_marshal_free, [:pointer], :void
232
+ # The bulk child drain (#168's quadratic): O(n) iteration for
233
+ # #each/#each_pair. Engines without it fall back to the per-index
234
+ # walk (correct, quadratic).
235
+ CHILDREN_DRAIN = !(@missing ||= []).include?(:yeptris_node_children)
230
236
  MARSHAL = !(@missing ||= []).include?(:yeptris_marshal_node)
231
237
 
232
238
  # bulk build (TODO.impl/15 phase D): one call raises a document
data/lib/yeptris/node.rb CHANGED
@@ -150,7 +150,17 @@ class Yeptris::Node
150
150
  return enum_for(:each) unless block_given?
151
151
  raise Yeptris::Error, "#each is for sequences" unless sequence?
152
152
 
153
- (0...seq_count).each { |i| yield seq_at(i) }
153
+ if Yeptris::FFI::CHILDREN_DRAIN
154
+ # one bulk walk (#168): the per-index seq_at loop paid i sibling
155
+ # hops per element — n^2/2 over an 80k-row sequence
156
+ n = alive { Yeptris::FFI.yeptris_node_seq_count(@c_ptr) }
157
+ buf = ::FFI::MemoryPointer.new(:pointer, n)
158
+ alive { Yeptris::FFI.yeptris_node_children(@c_ptr, buf, n) }
159
+ step = buf.type_size
160
+ n.times { |i| yield @document.wrap_node(buf.get_pointer(i * step)) }
161
+ else
162
+ (0...seq_count).each { |i| yield seq_at(i) }
163
+ end
154
164
  end
155
165
 
156
166
  # ---- mapping access ----
@@ -177,13 +187,25 @@ class Yeptris::Node
177
187
  return enum_for(:each_pair) unless block_given?
178
188
  raise Yeptris::Error, "#each_pair is for mappings" unless mapping?
179
189
 
180
- k = ::FFI::MemoryPointer.new(:pointer)
181
- v = ::FFI::MemoryPointer.new(:pointer)
182
- (0...map_count).each do |i|
183
- rc = alive { Yeptris::FFI.yeptris_node_map_at(@c_ptr, i, k, v) }
184
- next unless rc.zero?
190
+ if Yeptris::FFI::CHILDREN_DRAIN
191
+ # key,value interleaved in one walk — the same #168 cure
192
+ pairs = alive { Yeptris::FFI.yeptris_node_map_count(@c_ptr) }
193
+ buf = ::FFI::MemoryPointer.new(:pointer, pairs * 2)
194
+ alive { Yeptris::FFI.yeptris_node_children(@c_ptr, buf, pairs * 2) }
195
+ step = buf.type_size
196
+ pairs.times do |p|
197
+ yield @document.wrap_node(buf.get_pointer((2 * p) * step)),
198
+ @document.wrap_node(buf.get_pointer((2 * p + 1) * step))
199
+ end
200
+ else
201
+ k = ::FFI::MemoryPointer.new(:pointer)
202
+ v = ::FFI::MemoryPointer.new(:pointer)
203
+ (0...map_count).each do |i|
204
+ rc = alive { Yeptris::FFI.yeptris_node_map_at(@c_ptr, i, k, v) }
205
+ next unless rc.zero?
185
206
 
186
- yield @document.wrap_node(k.read_pointer), @document.wrap_node(v.read_pointer)
207
+ yield @document.wrap_node(k.read_pointer), @document.wrap_node(v.read_pointer)
208
+ end
187
209
  end
188
210
  end
189
211
 
@@ -254,6 +276,31 @@ class Yeptris::Node
254
276
  alive { Yeptris::FFI.yeptris_node_id(@c_ptr) }
255
277
  end
256
278
 
279
+ # The marshal fast path (TODO.restructure/21; #178): one C call
280
+ # turns this subtree into Marshal 4.8 bytes — Marshal.load builds
281
+ # the Ruby objects in C, no per-node FFI. nil when the document
282
+ # carries constructs the format cannot express (merge keys,
283
+ # timestamps, tagged revivals): the caller walks instead. Raises
284
+ # ParseError on engine errors (not on the bail).
285
+ def marshal_fast
286
+ return nil unless Yeptris::FFI::MARSHAL
287
+
288
+ out_p = ::FFI::MemoryPointer.new(:pointer)
289
+ olen_p = ::FFI::MemoryPointer.new(:size_t)
290
+ st = alive { Yeptris::FFI.yeptris_marshal_node(@c_ptr, out_p, olen_p) }
291
+ if st == Yeptris::FFI::ERROR_UNSUPPORTED
292
+ nil
293
+ elsif st != Yeptris::FFI::OK
294
+ raise Yeptris::ParseError, Yeptris::FFI.last_error_message
295
+ else
296
+ bytes = out_p.read_pointer.read_bytes(olen_p.read_uint64)
297
+ bytes.force_encoding(Encoding::ASCII_8BIT)
298
+ ::Marshal.load(bytes)
299
+ end
300
+ ensure
301
+ Yeptris::FFI.yeptris_marshal_free(out_p.read_pointer) if out_p
302
+ end
303
+
257
304
  def to_ruby(memo = nil)
258
305
  # readonly documents memoize per node: a second materialization
259
306
  # returns the SAME object (the readonly cache is the ground truth
@@ -271,25 +318,7 @@ class Yeptris::Node
271
318
  # Falls back to the per-node FFI walk on constructs the format
272
319
  # cannot express (merge keys, timestamps) and on older builds.
273
320
  if Yeptris::FFI::MARSHAL && memo.nil?
274
- result =
275
- begin
276
- out_p = ::FFI::MemoryPointer.new(:pointer)
277
- olen_p = ::FFI::MemoryPointer.new(:size_t)
278
- st = Yeptris::FFI.yeptris_marshal_node(@c_ptr, out_p, olen_p)
279
- if st == Yeptris::FFI::ERROR_UNSUPPORTED
280
- nil
281
- elsif st != Yeptris::FFI::OK
282
- raise Yeptris::ParseError, Yeptris::FFI.last_error_message
283
- else
284
- buf = out_p.read_pointer
285
- len = olen_p.read_uint64
286
- bytes = buf.read_bytes(len)
287
- bytes.force_encoding(Encoding::ASCII_8BIT)
288
- ::Marshal.load(bytes)
289
- end
290
- ensure
291
- Yeptris::FFI.yeptris_marshal_free(out_p.read_pointer) if out_p
292
- end
321
+ result = marshal_fast
293
322
  unless result.nil?
294
323
  @document.readonly_memo[node_id] = result if @document.readonly?
295
324
  return result
@@ -310,7 +339,32 @@ class Yeptris::Node
310
339
  h = {}
311
340
  memo[node_id] = h
312
341
  each_pair do |k, v|
313
- h[k.to_ruby_key] = v.to_ruby(memo)
342
+ key = k.to_ruby_key
343
+ val = v.to_ruby(memo)
344
+ if key == "<<" && k.tag != "tag:yaml.org,2002:str"
345
+ # stdlib revive_hash's merge branch: the VALUE's node kind
346
+ # picks the arm; every merge is TypeError-guarded (a bad
347
+ # element keeps the whole '<<' pair literal)
348
+ if v.kind == :alias || v.mapping?
349
+ begin
350
+ h.merge!(val)
351
+ rescue ::TypeError
352
+ h[key] = val
353
+ end
354
+ elsif v.sequence?
355
+ begin
356
+ merged = {}
357
+ val.reverse_each { |e| merged.merge!(e) }
358
+ h.merge!(merged)
359
+ rescue ::TypeError
360
+ h[key] = val
361
+ end
362
+ else
363
+ h[key] = val
364
+ end
365
+ else
366
+ h[key] = val
367
+ end
314
368
  end
315
369
  h
316
370
  when :sequence
@@ -320,7 +374,11 @@ class Yeptris::Node
320
374
  a
321
375
  when :alias
322
376
  t = alias_target
323
- t.nil? ? nil : t.to_ruby(memo)
377
+ if t.nil?
378
+ raise ::Yeptris::Psych::AnchorNotDefined,
379
+ "Unknown anchor: #{anchor}"
380
+ end
381
+ t.to_ruby(memo)
324
382
  else
325
383
  scalar_to_ruby
326
384
  end
@@ -188,6 +188,13 @@ module Yeptris
188
188
  key_node = @tree.new_scalar("", :single_quoted)
189
189
  key_node.set_tag("!")
190
190
  m.map_add_node(key_node, visit(v))
191
+ elsif k.is_a?(::String) && k == "<<"
192
+ # stdlib yaml_tree: a literal chevron key carries the
193
+ # explicit !!str tag (single-quoted) so it does not
194
+ # re-load as a merge
195
+ key_node = @tree.new_scalar("<<", :single_quoted)
196
+ key_node.set_tag("!!str")
197
+ m.map_add_node(key_node, visit(v))
191
198
  else
192
199
  m.map_add(key_text(k), visit(v))
193
200
  end
@@ -415,10 +422,38 @@ module Yeptris
415
422
  end
416
423
  end
417
424
 
425
+ # stdlib visit_hash's merge branch, verbatim in structure: the
426
+ # key's SHAPE decides (an explicit !!str '<<' is a literal key,
427
+ # not a merge), the VALUE's node kind picks the arm, and every
428
+ # merge is TypeError-guarded (a bad element keeps the whole
429
+ # '<<' pair literal — merges are all-or-nothing).
418
430
  def hash_into(h, node)
419
431
  anchors[node.anchor] = h if node.anchor
420
432
  node.children.each_slice(2) do |k, v|
421
- h[visit(k)] = visit(v)
433
+ key = visit(k)
434
+ val = visit(v)
435
+ if key == "<<" && k.tag != "tag:yaml.org,2002:str"
436
+ case v
437
+ when ::Yeptris::Psych::Nodes::Alias, ::Yeptris::Psych::Nodes::Mapping
438
+ begin
439
+ h.merge!(val)
440
+ rescue ::TypeError
441
+ h[key] = val
442
+ end
443
+ when ::Yeptris::Psych::Nodes::Sequence
444
+ begin
445
+ merged = {}
446
+ val.reverse_each { |value| merged.merge!(value) }
447
+ h.merge!(merged)
448
+ rescue ::TypeError
449
+ h[key] = val
450
+ end
451
+ else
452
+ h[key] = val
453
+ end
454
+ else
455
+ h[key] = val
456
+ end
422
457
  end
423
458
  h
424
459
  end
data/lib/yeptris/psych.rb CHANGED
@@ -91,6 +91,9 @@ module Yeptris
91
91
  # object instance exists.
92
92
  autoload :Encodable, "yeptris/psych/encodable"
93
93
  class Error < StandardError; end
94
+
95
+ # stdlib psych 5: a *foo with no matching &foo anchor
96
+ class AnchorNotDefined < Error; end
94
97
  # Psych's exact interface (issue #32): same constructor arity,
95
98
  # same reader set (file/line/column/offset/problem/context), same
96
99
  # message shape — drop-in consumers' rescues and constructors
@@ -143,6 +146,31 @@ module Yeptris
143
146
  # !ruby/set, encode_with/init_with, alias identity. Plain-data
144
147
  # loads should use load/safe_load (the Materializer fast path).
145
148
  def unsafe_load(yaml, **)
149
+ # #178: the marshal fast path. Nodes::Builder's tree is per-node
150
+ # FFI (kind/tag/anchor/value + a walk per container) — the whole
151
+ # 11 MB relaton index paid ~9.6 s there. When the document is
152
+ # plain data (no timestamps/merge keys/tagged revivals), ONE C
153
+ # call materializes the tree as Marshal bytes and Marshal.load
154
+ # builds the objects in C (~1 s end to end). Construct-heavy
155
+ # documents keep the full revival walk below, byte for byte.
156
+ if Yeptris::FFI::MARSHAL && ::Yeptris::Psych.domain_types.empty?
157
+ begin
158
+ doc = ::Yeptris::Document.parse(yaml, schema: :compat_11)
159
+ return nil if doc.nil? || doc.document_count.zero?
160
+
161
+ root = doc.root(0)
162
+ fast = root&.marshal_fast
163
+ unless fast.nil?
164
+ doc.free
165
+ return fast
166
+ end
167
+ tree = Nodes::Builder.document(doc) # ownership: the tree frees
168
+ return Visitors::ToRuby.visit(tree.children.first)
169
+ rescue ::Yeptris::ParseError => e
170
+ doc&.free unless doc&.freed?
171
+ translate_parse_error(e)
172
+ end
173
+ end
146
174
  tree = parse(yaml)
147
175
  return nil if tree.nil?
148
176
 
@@ -150,11 +178,16 @@ module Yeptris
150
178
  end
151
179
 
152
180
  def safe_load(yaml, permitted_classes: [::Date, ::Time], aliases: false, **)
153
- doc = Yeptris::Document.parse(yaml, schema: :compat_11)
154
181
  begin
182
+ doc = Yeptris::Document.parse(yaml, schema: :compat_11)
183
+ return nil if doc.nil? # the legal empty stream
184
+
155
185
  force_utf8_scalars(walk_safe(doc.root(0), permitted_classes, aliases))
186
+ rescue ::Yeptris::ParseError => e
187
+ doc&.free unless doc&.freed?
188
+ translate_parse_error(e)
156
189
  ensure
157
- doc.free
190
+ doc&.free
158
191
  end
159
192
  end
160
193
 
@@ -230,6 +263,8 @@ module Yeptris
230
263
  # children share one C document, so their handles would all
231
264
  # resolve to the first document's tree
232
265
  doc = Yeptris::Document.parse(yaml, schema: :compat_11)
266
+ return nil if doc.nil? # the legal empty stream
267
+
233
268
  begin
234
269
  (0...doc.document_count).map { |i| doc.root(i).to_ruby }
235
270
  ensure
@@ -238,9 +273,18 @@ module Yeptris
238
273
  end
239
274
 
240
275
  # The first document's node tree (no Ruby materialization).
276
+ def translate_parse_error(e)
277
+ # yeptris rejects *foo without &foo at PARSE time (stdlib
278
+ # raises at visit) — surface it as stdlib's class so
279
+ # consumers' rescues keep working
280
+ raise AnchorNotDefined, e.message if e.message.include?("undefined anchor")
281
+
282
+ raise SyntaxError.from_parse_error(e)
283
+ end
284
+
241
285
  def parse(yaml)
242
286
  doc = Yeptris::Document.parse(yaml, schema: :compat_11)
243
- return nil if doc.document_count.zero?
287
+ return nil if doc.nil? || doc.document_count.zero?
244
288
 
245
289
  Nodes::Builder.document(doc)
246
290
  rescue Yeptris::ParseError => e
@@ -249,7 +293,7 @@ module Yeptris
249
293
 
250
294
  def parse_stream(yaml)
251
295
  doc = Yeptris::Document.parse(yaml, schema: :compat_11)
252
- return nil if doc.document_count.zero?
296
+ return nil if doc.nil? || doc.document_count.zero?
253
297
 
254
298
  stream = Nodes::Stream.new
255
299
  (0...doc.document_count).each do |i|
@@ -33,6 +33,8 @@ class Yeptris::YAML::Descriptor < ::Yeptris::JSON::Descriptor
33
33
  def walk(yaml)
34
34
  src = ::Yeptris.read_input(yaml).to_s
35
35
  doc = ::Yeptris::Document.parse(src, schema: @schema)
36
+ return [] if doc.nil? # the legal empty stream
37
+
36
38
  begin
37
39
  st = ::FFI::MemoryPointer.new(:int)
38
40
  raw = ::Yeptris::FFI.yeptris_document_plan_walk(doc.c_ptr, @handle, st)
data/lib/yeptris/yaml.rb CHANGED
@@ -73,7 +73,7 @@ module Yeptris
73
73
  # Parses without materializing: the first document's root Node.
74
74
  def parse(yaml, schema: :core_12)
75
75
  doc = Document.parse(yaml, schema: schema)
76
- return nil if doc.document_count.zero?
76
+ return nil if doc.nil? || doc.document_count.zero?
77
77
 
78
78
  doc.root(0)
79
79
  end
@@ -177,6 +177,14 @@ module Yeptris
177
177
  off[0] += 1
178
178
  elsif k.is_a?(Symbol)
179
179
  scalar(":#{k}", STYLE_PLAIN, emit, blob, off)
180
+ elsif k.is_a?(String) && k == "<<"
181
+ # stdlib yaml_tree: a literal chevron key carries the
182
+ # explicit !!str tag (single-quoted) so it does not
183
+ # re-load as a merge
184
+ scalar("<<", STYLE_SQ, emit, blob, off)
185
+ emit.call(BUILD_TAG, 0, off[0], 5)
186
+ blob << "!!str"
187
+ off[0] += 5
180
188
  elsif k.is_a?(String)
181
189
  place(k, emit, blob, off, seen)
182
190
  else
@@ -343,6 +351,12 @@ module Yeptris
343
351
  key_node = doc.new_scalar("", :single_quoted)
344
352
  key_node.set_tag("!")
345
353
  m.map_add_node(key_node, build(doc, v, seen))
354
+ elsif k.is_a?(::String) && k == "<<"
355
+ # stdlib yaml_tree: a literal chevron key carries the
356
+ # explicit !!str tag so it does not re-load as a merge
357
+ key_node = doc.new_scalar("<<", :single_quoted)
358
+ key_node.set_tag("!!str")
359
+ m.map_add_node(key_node, build(doc, v, seen))
346
360
  else
347
361
  m.map_add(key_text(k), build(doc, v, seen))
348
362
  end
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.13.1".freeze
6
+ VERSION = "0.6.15.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
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.13
6
+ VERSION 0.6.15
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -76,6 +76,15 @@ YEPTRIS_API YeptrisNode yeptris_node_map_get(YeptrisNode node, const char* key,
76
76
  YEPTRIS_API int yeptris_node_map_at(YeptrisNode node, size_t index, YeptrisNode* key,
77
77
  YeptrisNode* value);
78
78
 
79
+ /* Bulk children drain — the O(n) iteration primitive (ruby #168: the
80
+ * per-index seq_at/map_at walks make an 80k-row sequence O(n^2)). One
81
+ * call walks the child list once and fills out[0..cap) with handles:
82
+ * sequence elements in order; mapping KEY,VALUE pairs interleaved.
83
+ * Returns the total child count (mapping pairs count twice); out==NULL
84
+ * or cap==0 returns the count without allocating handles. Handles come
85
+ * from the document's pool — document_free reclaims them. */
86
+ YEPTRIS_API size_t yeptris_node_children(YeptrisNode node, YeptrisNode* out, size_t cap);
87
+
79
88
  /* ---- Construction (TODO.impl/11 phase 3) ----
80
89
  *
81
90
  * Build documents from scratch and mutate them; the emitter and every
@@ -734,6 +734,7 @@ static YeptrisDocument cbor_wrap(yep_dom* dom, const void* buf, const yep_alloca
734
734
  d->transcoded_len = 0;
735
735
  d->input = (const char*)buf;
736
736
  d->finish_pool = NULL;
737
+ d->lazy_tape = NULL; /* field-by-field ctor: no garbage for free */
737
738
  return (YeptrisDocument)d;
738
739
  }
739
740
 
@@ -839,6 +840,7 @@ YEPTRIS_API YeptrisDocument yeptris_cbor_decode(const void* buf, size_t len, uin
839
840
  doc->transcoded_len = 0;
840
841
  doc->input = (const char*)buf;
841
842
  doc->finish_pool = NULL;
843
+ doc->lazy_tape = NULL; /* field-by-field ctor: no garbage for free */
842
844
  if (status != NULL) {
843
845
  *status = YEPTRIS_OK;
844
846
  }
@@ -703,7 +703,10 @@ static void cbor_write_item(cenc* e, uint32_t id, uint8_t* out, size_t* pos) {
703
703
  static int cbor_run(YeptrisDocument handle, uint32_t opts, uint8_t* out, size_t cap,
704
704
  size_t* out_len, int* written) {
705
705
  yeptris_document* doc = (yeptris_document*)handle;
706
- yep_dom* d = doc->dom;
706
+ yep_dom* d = yep_doc_dom(doc); /* #342 lazy: materialize on encode */
707
+ if (d == NULL) {
708
+ return YEPTRIS_ERROR_MEMORY;
709
+ }
707
710
  if (d->dcount == 0) {
708
711
  return YEPTRIS_ERROR_ARG;
709
712
  }
@@ -22,16 +22,35 @@ typedef struct yeptris_document {
22
22
  void* finish_pool; /* engine finish pool: resolved tags, folded and
23
23
  escaped scalars outlive the engine through the
24
24
  document */
25
+ void* lazy_tape; /* #342 slice 2: the parsed tape when the tree is
26
+ * deferred (gate-clean strict JSON). dom==NULL until
27
+ * the first tree access materializes via dom_from_tape;
28
+ * freed with the document. void* to avoid a header
29
+ * cycle — parse.c casts to yeptris_json_tape* */
25
30
  } yeptris_document;
26
31
 
27
32
  /* Node handle: a (document, node-id) pair so nodes stay usable even if
28
33
  * the node pool grows (ids are stable; pointers are not). Defined here
29
34
  * (not parse.c) since the query layer and the builder share it. */
35
+ /* #342 slice 2: the lazy-tree choke point. Returns the document's dom,
36
+ * materializing it from lazy_tape on first access (NULL when materialization
37
+ * fails — callers treat it as an empty/unusable tree). Every consumer that
38
+ * reads ->dom on a parse-produced document routes through here; the builder
39
+ * and CBOR decode paths set dom eagerly and are unaffected. parse.c owns it. */
30
40
  typedef struct yeptris_node {
31
41
  yeptris_document* doc;
32
42
  uint32_t id;
33
43
  } yeptris_node;
34
44
 
45
+ #ifdef __cplusplus
46
+ extern "C" {
47
+ #endif
48
+
49
+ yep_dom* yep_doc_dom(yeptris_document* doc);
35
50
  yeptris_node* yep_handle_new(yeptris_document* doc, uint32_t id);
36
51
 
52
+ #ifdef __cplusplus
53
+ }
54
+ #endif
55
+
37
56
  #endif /* YEP_DOC_H */
@@ -866,12 +866,18 @@ int dom_on_flow_commit(void* ctx) {
866
866
  }
867
867
 
868
868
  int dom_from_tape(yep_dom* d, const yeptris_json_tape* t) {
869
- if (d == NULL || t == NULL || t->_src == NULL) {}
869
+ if (d == NULL || t == NULL || t->_src == NULL) {
870
+ return -1;
871
+ }
870
872
  /* the columns are the one representation valid on every route
871
873
  * (strict: native; lenient fused: lazily materialized from the
872
874
  * records; scalar-root lenient: native column writes) */
873
- if (yeptris_tape_columns((yeptris_json_tape*)t) != 0) {}
874
- if (t->kinds == NULL) {}
875
+ if (yeptris_tape_columns((yeptris_json_tape*)t) != 0) {
876
+ return -1;
877
+ }
878
+ if (t->kinds == NULL) {
879
+ return -1;
880
+ }
875
881
  d->input_base = (const char*)t->_src;
876
882
  d->input_len = t->_srclen;
877
883
  const char* src = (const char*)t->_src;
@@ -885,17 +891,23 @@ int dom_from_tape(yep_dom* d, const yeptris_json_tape* t) {
885
891
  break; /* the stream boundary record */
886
892
  case YEP_T_SEQ_OPEN:
887
893
  case YEP_T_MAP_OPEN: {
888
- if (d->depth >= YEP_DOM_MAX_DEPTH) {}
894
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {
895
+ return -1;
896
+ }
889
897
  uint32_t id =
890
898
  dom_open_node(d, kind == YEP_T_SEQ_OPEN ? YEP_DOM_SEQUENCE : YEP_DOM_MAPPING, NULL,
891
899
  NULL, 0, 0, 0, 1);
892
- if (id == UINT32_MAX || dom_place(d, id) != 0) {}
900
+ if (id == UINT32_MAX || dom_place(d, id) != 0) {
901
+ return -1;
902
+ }
893
903
  d->map_pending_key[d->depth] = 0;
894
904
  d->stack[d->depth++] = id;
895
905
  break;
896
906
  }
897
907
  case YEP_T_CLOSE:
898
- if (d->depth == 0) {}
908
+ if (d->depth == 0) {
909
+ return -1;
910
+ }
899
911
  d->depth--;
900
912
  break;
901
913
  case YEP_T_NULL:
@@ -952,7 +964,9 @@ int dom_from_tape(yep_dom* d, const yeptris_json_tape* t) {
952
964
  /* escaped: unescape into the arena (the fused
953
965
  * builder's arm, byte for byte) */
954
966
  char* dst = yep_dom_str_tail(d, len);
955
- if (dst == NULL) {}
967
+ if (dst == NULL) {
968
+ return -1;
969
+ }
956
970
  d->nodes[id].value = yep_dom_str_commit(
957
971
  d, yep_finish_double_into(src, off, off + len, dst, len));
958
972
  } else {
@@ -45,6 +45,9 @@ YEPTRIS_API size_t yeptris_serialize_into_ex(YeptrisDocument handle,
45
45
  em.w.grow = 0;
46
46
  em.w.oom = 0;
47
47
  em.doc = (const yeptris_document*)handle;
48
+ if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
49
+ return 0;
50
+ }
48
51
  em.w.p = NULL;
49
52
  em.w.last = 0;
50
53
  em.w.force_flow = 0;
@@ -100,6 +103,9 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
100
103
  em.w.grow = 0;
101
104
  em.w.oom = 0;
102
105
  em.doc = (const yeptris_document*)handle;
106
+ if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
107
+ return NULL;
108
+ }
103
109
  em.w.p = NULL;
104
110
  em.w.last = 0;
105
111
  em.w.force_flow = 0;
@@ -174,6 +180,9 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
174
180
  em.w.grow = 0;
175
181
  em.w.oom = 0;
176
182
  em.doc = (const yeptris_document*)handle;
183
+ if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
184
+ return NULL;
185
+ }
177
186
  em.w.p = NULL;
178
187
  em.w.last = 0;
179
188
  em.w.force_flow = 0;
@@ -224,6 +233,9 @@ YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len,
224
233
  em.w.grow = 0;
225
234
  em.w.oom = 0;
226
235
  em.doc = (const yeptris_document*)handle;
236
+ if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
237
+ return NULL;
238
+ }
227
239
  em.w.p = NULL;
228
240
  em.w.last = 0;
229
241
  em.w.force_flow = 0;
@@ -378,6 +390,9 @@ YEPTRIS_API size_t yeptris_serialize_stream(YeptrisDocument handle,
378
390
  em.w.grow = 0;
379
391
  em.w.oom = 0;
380
392
  em.doc = (const yeptris_document*)handle;
393
+ if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
394
+ return 0;
395
+ }
381
396
  em.w.p = NULL;
382
397
  em.w.last = 0;
383
398
  em.w.force_flow = 0;
@@ -721,6 +721,34 @@ YEPTRIS_API YeptrisStatus yeptris_marshal(const char* data, size_t len, YeptrisS
721
721
  return st;
722
722
  }
723
723
 
724
+ /* #178 (ruby #168's fast path): the value records carry the resolver's
725
+ * verdict, NOT the source's explicit tag — a `!ruby/object` mapping
726
+ * would marshal as a plain hash, silently dropping the tag (psych's
727
+ * visitor revives it). One cheap pre-walk: any explicitly tagged node
728
+ * in the subtree → UNSUPPORTED, the host walks. Explicit CORE tags
729
+ * (!!str and friends) ride the same bail — correct, just conservative;
730
+ * bulk data is untagged. */
731
+ static int dom_subtree_tagged(const yep_dom* d, uint32_t id) {
732
+ const yep_dnode* n = yep_dom_node(d, id);
733
+ if (n == NULL) {
734
+ return 0;
735
+ }
736
+ if (n->tag.len != 0) {
737
+ return 1;
738
+ }
739
+ for (uint32_t c = n->first_child; c != UINT32_MAX;) {
740
+ const yep_dnode* cn = yep_dom_node(d, c);
741
+ if (cn == NULL) {
742
+ break;
743
+ }
744
+ if (dom_subtree_tagged(d, c) != 0) {
745
+ return 1;
746
+ }
747
+ c = cn->next_sibling;
748
+ }
749
+ return 0;
750
+ }
751
+
724
752
  YEPTRIS_API YeptrisStatus yeptris_marshal_node(YeptrisNode node, char** out, size_t* out_len) {
725
753
  if (node == NULL || out == NULL || out_len == NULL) {
726
754
  return YEPTRIS_ERROR_ARG;
@@ -728,6 +756,12 @@ YEPTRIS_API YeptrisStatus yeptris_marshal_node(YeptrisNode node, char** out, siz
728
756
  *out = NULL;
729
757
  *out_len = 0;
730
758
  yeptris_node* h = (yeptris_node*)node;
759
+ if (dom_subtree_tagged(h->doc->dom, h->id) != 0) {
760
+ yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
761
+ "marshal: explicitly tagged node not expressible; "
762
+ "fall back to the value walk");
763
+ return YEPTRIS_ERROR_UNSUPPORTED;
764
+ }
731
765
  yep_value_ctx* c = NULL;
732
766
  if (yep_values_from_dom(h->doc->dom, h->id, 0, &c) != 0) {
733
767
  return YEPTRIS_ERROR_MEMORY;
@@ -12,6 +12,7 @@
12
12
  #include "parse/numbers.h"
13
13
  #include "resolve/resolver.h"
14
14
  #include "scan/json.h"
15
+ #include "tape_in.h"
15
16
 
16
17
  #include "parse/events.h"
17
18
  #include <errno.h>
@@ -38,6 +39,59 @@ YEPTRIS_API YeptrisDocument yeptris_parse(const char* buf, size_t len, YeptrisSt
38
39
  return yeptris_parse_ex(buf, len, NULL, status);
39
40
  }
40
41
 
42
+ /* #342 slice 2: materialize the deferred tree on first access. The
43
+ * gate-clean JSON route carries the parsed TAPE (simdjson's design:
44
+ * its "DOM" is a tape) and pays dom_from_tape only when a consumer
45
+ * touches the tree — parse-only workloads never build nodes. */
46
+ yep_dom* yep_doc_dom(yeptris_document* doc) {
47
+ if (doc == NULL) {
48
+ return NULL;
49
+ }
50
+ if (doc->dom == NULL && doc->lazy_tape != NULL) {
51
+ yeptris_json_tape* t = (yeptris_json_tape*)doc->lazy_tape;
52
+ yep_dom* dom = yep_dom_create(doc->sys);
53
+ if (dom != NULL && dom_from_tape(dom, t) == 0) {
54
+ doc->dom = dom;
55
+ yeptris_tape_free(t);
56
+ yep_free(doc->sys, t);
57
+ doc->lazy_tape = NULL;
58
+ } else {
59
+ yep_dom_destroy(dom);
60
+ dom = NULL; /* the tape stays: a later access retries, or
61
+ free drops it (the materializer only fails on
62
+ malformed NUM spans, which the strict gate
63
+ already rejected at parse) */
64
+ }
65
+ return dom;
66
+ }
67
+ return doc->dom;
68
+ }
69
+
70
+ /* The strict-JSON document wrapper (both routes share it). */
71
+ /* The lazy route's acceptance debt: the fused lenient walk records
72
+ * number runs UNVALIDATED (its simdjson-style deferred contract).
73
+ * parse_json's contract is RFC 8259 at parse time, so the deferred
74
+ * spans settle HERE — full-span shape check per NUM record, decoding
75
+ * the packed records directly (no columns build at parse; the walk
76
+ * guarantees recs are primary on this route). */
77
+ static int lazy_nums_settled(const yeptris_json_tape* t) {
78
+ const char* p = (const char*)t->_src;
79
+ for (uint32_t i = 0; i < t->count; i++) {
80
+ uint64_t r = t->recs[i];
81
+ if ((uint8_t)(r & 0xFFu) != YEP_T_NUM) {
82
+ continue;
83
+ }
84
+ uint32_t len = (uint32_t)((r >> 8) & 0x7FFFFFu);
85
+ uint32_t off = (uint32_t)(r >> 32);
86
+ size_t adv = 0;
87
+ int flt = 0;
88
+ if (yep_json_number_shape(p + off, len, &adv, &flt) == 0 || adv != (size_t)len) {
89
+ return -1;
90
+ }
91
+ }
92
+ return 0;
93
+ }
94
+
41
95
  /* The strict-JSON document wrapper (both routes share it). */
42
96
  static YeptrisDocument yep_json_doc_wrap(yep_dom* dom, const char* buf, size_t len,
43
97
  const yep_allocator* sys, YeptrisStatus* status) {
@@ -52,6 +106,7 @@ static YeptrisDocument yep_json_doc_wrap(yep_dom* dom, const char* buf, size_t l
52
106
  }
53
107
  doc->dom = dom;
54
108
  doc->sys = sys;
109
+ doc->lazy_tape = NULL;
55
110
  doc->schema = YEPTRIS_SCHEMA_12_CORE; /* strict JSON is core by construction */
56
111
  doc->transcoded = NULL;
57
112
  doc->transcoded_len = 0;
@@ -75,41 +130,44 @@ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, Yept
75
130
  * builds. Rejects, non-opener roots, and surprises fall to the
76
131
  * original sequence below, whose error precedence is byte-for-byte
77
132
  * the pinned behavior (json-suite-strict gates this). */
78
- if (len > 0 && !yep_text_active()->gate_scan(buf, len)) {
133
+ int gated = len > 0 && yep_text_active()->gate_scan(buf, len);
134
+ if (!gated) {
79
135
  size_t off = 0;
80
136
  while (off < len &&
81
137
  (buf[off] == ' ' || buf[off] == '\t' || buf[off] == '\n' || buf[off] == '\r')) {
82
138
  off++;
83
139
  }
84
- if (off < len && (buf[off] == '[' || buf[off] == '{')) {
140
+ /* tabs: the lenient walk's own pinned acceptance (its route
141
+ * takes them); the strict routes reject them (ErrorParity's
142
+ * pinned agreement). A tab-carrying buffer must NOT take the
143
+ * lazy walk — the validating sequence below reports exactly
144
+ * the pinned reject. */
145
+ if (off < len && (buf[off] == '[' || buf[off] == '{') && memchr(buf, '\t', len) == NULL) {
146
+ /* #342 slice 2: the fused LENIENT walk (one pass, records
147
+ * only — no node building) settles the deferred number
148
+ * grammar inline, then the tape rides the document and
149
+ * dom_from_tape builds nodes on the first tree access.
150
+ * A reject or malformed span falls to the original
151
+ * sequence below, whose error precedence is byte-for-byte
152
+ * the pinned behavior. */
85
153
  const yep_allocator* sys = yep_system_allocator();
86
- yep_dom* dom = yep_dom_create(sys);
87
- if (dom == NULL) {
154
+ yeptris_json_tape* t = yep_alloc(sys, sizeof(*t));
155
+ if (t == NULL) {
88
156
  st = YEPTRIS_ERROR_MEMORY;
89
157
  goto jfail;
90
158
  }
91
- dom->input_base = buf;
92
- dom->input_len = len;
93
- dom->flow_strict = 1;
94
- size_t close = 0;
95
- int rc = dom_on_flow_build(dom, buf, off, len, 1, 0, (yep_view){0}, (yep_view){0}, 0,
96
- YEP_DOM_MAX_DEPTH, &close);
97
- dom->flow_strict = 0;
98
- int tail_ok = 0;
99
- if (rc == 1) {
100
- /* trailing garbage after the closer is a reject the
101
- * fallback reports exactly as before */
102
- size_t t = close + 1;
103
- while (t < len &&
104
- (buf[t] == ' ' || buf[t] == '\t' || buf[t] == '\n' || buf[t] == '\r')) {
105
- t++;
159
+ if (yep_tape_walk_lenient_fused(buf, len, off, t) == YEPTRIS_OK &&
160
+ lazy_nums_settled(t) == 0) {
161
+ YeptrisDocument h = yep_json_doc_wrap(NULL, buf, len, sys, status);
162
+ if (h != NULL) {
163
+ ((yeptris_document*)h)->lazy_tape = t;
164
+ return h;
106
165
  }
107
- tail_ok = (t == len);
166
+ yeptris_tape_free(t); /* wrap failed (memory): below */
167
+ } else {
168
+ yeptris_tape_free(t); /* reject/malformed: below */
108
169
  }
109
- if (rc == 1 && tail_ok && dom_on_flow_commit(dom) > 0) {
110
- return yep_json_doc_wrap(dom, buf, len, sys, status);
111
- }
112
- yep_dom_destroy(dom); /* reject/rollback: the sequence below */
170
+ yep_free(sys, t);
113
171
  }
114
172
  }
115
173
  size_t verr = 0;
@@ -120,8 +178,7 @@ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, Yept
120
178
  goto jfail;
121
179
  }
122
180
  size_t uerr = 0;
123
- if (yep_text_active()->gate_scan(buf, len) &&
124
- !yep_utf8_validate((const unsigned char*)buf, len, &uerr)) {
181
+ if (gated && !yep_utf8_validate((const unsigned char*)buf, len, &uerr)) {
125
182
  yep_error_set(yep_error_tls(), YEP_ERR_ENCODING, 0, 0, uerr, "ill-formed UTF-8 at byte %zu",
126
183
  uerr);
127
184
  st = YEPTRIS_ERROR_ENCODING;
@@ -343,6 +400,8 @@ engine_enter:
343
400
  * or an arena copy (dom_ev_str) — nothing references it */
344
401
  yep_pool_destroy(finish);
345
402
  doc->finish_pool = NULL;
403
+ doc->lazy_tape = NULL; /* field-by-field ctor: leave no garbage
404
+ * (document_free frees a non-NULL tape) */
346
405
  return (YeptrisDocument)doc;
347
406
 
348
407
  fail:
@@ -363,6 +422,10 @@ YEPTRIS_API void yeptris_document_free(YeptrisDocument handle) {
363
422
  if (doc == NULL) {
364
423
  return;
365
424
  }
425
+ if (doc->lazy_tape != NULL) { /* never materialized: free stays free */
426
+ yeptris_tape_free((yeptris_json_tape*)doc->lazy_tape);
427
+ yep_free(doc->sys, doc->lazy_tape);
428
+ }
366
429
  yep_dom_destroy(doc->dom);
367
430
  yep_pool_destroy((yep_pool*)doc->finish_pool);
368
431
  yep_free(doc->sys, doc->transcoded);
@@ -371,7 +434,7 @@ YEPTRIS_API void yeptris_document_free(YeptrisDocument handle) {
371
434
 
372
435
  YEPTRIS_API size_t yeptris_document_count(YeptrisDocument handle) {
373
436
  yeptris_document* doc = (yeptris_document*)handle;
374
- return doc ? doc->dom->dcount : 0;
437
+ return doc ? yep_doc_dom(doc)->dcount : 0;
375
438
  }
376
439
 
377
440
  yeptris_node* yep_handle_new(yeptris_document* doc, uint32_t id) {
@@ -390,10 +453,14 @@ static YeptrisNode node_new_handle(yeptris_document* doc, uint32_t id) {
390
453
 
391
454
  YEPTRIS_API YeptrisNode yeptris_document_root(YeptrisDocument handle, size_t index) {
392
455
  yeptris_document* doc = (yeptris_document*)handle;
393
- if (doc == NULL || index >= doc->dom->dcount) {
456
+ if (doc == NULL) {
394
457
  return NULL;
395
458
  }
396
- return node_new_handle(doc, doc->dom->docs[index]);
459
+ yep_dom* dom = yep_doc_dom(doc);
460
+ if (dom == NULL || index >= dom->dcount) {
461
+ return NULL;
462
+ }
463
+ return node_new_handle(doc, dom->docs[index]);
397
464
  }
398
465
 
399
466
  static const yep_dnode* node_of(YeptrisNode handle) {
@@ -588,6 +655,24 @@ YEPTRIS_API YeptrisNode yeptris_node_seq_at(YeptrisNode handle, size_t index) {
588
655
  return wrap((yeptris_node*)handle, id);
589
656
  }
590
657
 
658
+ YEPTRIS_API size_t yeptris_node_children(YeptrisNode handle, YeptrisNode* out, size_t cap) {
659
+ const yep_dnode* n = node_of(handle);
660
+ if (n == NULL || (n->kind != YEP_DOM_SEQUENCE && n->kind != YEP_DOM_MAPPING)) {
661
+ return 0;
662
+ }
663
+ size_t count = 0;
664
+ uint32_t id = n->first_child;
665
+ while (id != UINT32_MAX) {
666
+ if (out != NULL && count < cap) {
667
+ out[count] = wrap((yeptris_node*)handle, id);
668
+ }
669
+ count++;
670
+ const yep_dnode* cur = yep_dom_node(((yeptris_node*)handle)->doc->dom, id);
671
+ id = cur ? cur->next_sibling : UINT32_MAX;
672
+ }
673
+ return count;
674
+ }
675
+
591
676
  YEPTRIS_API size_t yeptris_node_map_count(YeptrisNode handle) {
592
677
  const yep_dnode* n = node_of(handle);
593
678
  return (n && n->kind == YEP_DOM_MAPPING) ? n->count / 2 : 0;
@@ -79,12 +79,16 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
79
79
  }
80
80
  return NULL;
81
81
  }
82
- const yeptris_document* d = (const yeptris_document*)doc;
83
- const yep_dnode* root = yep_dom_node(d->dom, d->dom->docs[0]);
84
- yeptris_plan* plan = calloc(1, sizeof(*plan));
82
+ yeptris_document* d = (yeptris_document*)doc;
83
+ yeptris_plan* plan = calloc(1, sizeof(*plan)); /* before any goto: out frees it */
85
84
  if (plan == NULL) {
86
85
  goto mem;
87
86
  }
87
+ yep_dom* dd = yep_doc_dom(d); /* #342 lazy: the spec parse may defer */
88
+ if (dd == NULL) {
89
+ goto mem;
90
+ }
91
+ const yep_dnode* root = yep_dom_node(dd, dd->docs[0]);
88
92
  if (root == NULL || root->kind != 2) { /* the spec root is a mapping */
89
93
  goto bad;
90
94
  }
@@ -95,10 +99,10 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
95
99
  int saw_kind = 0;
96
100
  uint32_t pair = root->first_child;
97
101
  while (pair != UINT32_MAX) {
98
- const yep_dnode* kn = yep_dom_node(d->dom, pair);
99
- const yep_dnode* vn = yep_dom_node(d->dom, kn->next_sibling);
100
- yep_view kv = sv_view(d->dom, kn->value);
101
- yep_view vv = sv_view(d->dom, vn->value);
102
+ const yep_dnode* kn = yep_dom_node(dd, pair);
103
+ const yep_dnode* vn = yep_dom_node(dd, kn->next_sibling);
104
+ yep_view kv = sv_view(dd, kn->value);
105
+ yep_view vv = sv_view(dd, vn->value);
102
106
  if (kv.len == 4 && memcmp(kv.p, "kind", 4) == 0) {
103
107
  saw_kind = 1;
104
108
  if (!(vv.len == 3 && (memcmp(vv.p, "seq", 3) == 0 || memcmp(vv.p, "map", 3) == 0))) {
@@ -130,7 +134,7 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
130
134
  if (path_node->tag_id != YEPTRIS_TAG_STR) {
131
135
  goto bad; /* the path is a string, not a number */
132
136
  }
133
- yep_view vv = sv_view(d->dom, path_node->value);
137
+ yep_view vv = sv_view(dd, path_node->value);
134
138
  if (vv.len != 0) { /* "" rides the seq root like no path */
135
139
  plan->seg_off = malloc(sizeof(uint32_t));
136
140
  plan->seg_len = malloc(sizeof(uint32_t));
@@ -159,11 +163,11 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
159
163
  size_t off = 0;
160
164
  uint32_t cn = path_node->first_child;
161
165
  for (size_t i = 0; i < n; i++) {
162
- const yep_dnode* seg = yep_dom_node(d->dom, cn);
166
+ const yep_dnode* seg = yep_dom_node(dd, cn);
163
167
  if (seg == NULL || seg->kind != 0 || seg->tag_id != YEPTRIS_TAG_STR) {
164
168
  goto bad; /* every segment is a string */
165
169
  }
166
- yep_view sv = sv_view(d->dom, seg->value);
170
+ yep_view sv = sv_view(dd, seg->value);
167
171
  if (sv.len == 0) {
168
172
  goto bad;
169
173
  }
@@ -191,7 +195,7 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
191
195
  }
192
196
  uint32_t cn = children->first_child;
193
197
  for (size_t i = 0; i < plan->ncols; i++) {
194
- const yep_dnode* leaf = yep_dom_node(d->dom, cn);
198
+ const yep_dnode* leaf = yep_dom_node(dd, cn);
195
199
  if (leaf == NULL || leaf->kind != 2) {
196
200
  goto bad;
197
201
  }
@@ -200,10 +204,10 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
200
204
  int kind = -1;
201
205
  uint32_t lp = leaf->first_child;
202
206
  while (lp != UINT32_MAX) {
203
- const yep_dnode* lk = yep_dom_node(d->dom, lp);
204
- const yep_dnode* lv = yep_dom_node(d->dom, lk->next_sibling);
205
- yep_view lkv = sv_view(d->dom, lk->value);
206
- yep_view lvv = sv_view(d->dom, lv->value);
207
+ const yep_dnode* lk = yep_dom_node(dd, lp);
208
+ const yep_dnode* lv = yep_dom_node(dd, lk->next_sibling);
209
+ yep_view lkv = sv_view(dd, lk->value);
210
+ yep_view lvv = sv_view(dd, lv->value);
207
211
  if (lkv.len == 4 && memcmp(lkv.p, "name", 4) == 0) {
208
212
  if (lv->kind != 0 || lvv.len == 0) {
209
213
  goto bad;
@@ -657,15 +661,15 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
657
661
  if (st != NULL) {
658
662
  *st = YEPTRIS_OK;
659
663
  }
660
- const yeptris_document* d = (const yeptris_document*)doc;
661
- if (doc == NULL || plan == NULL || d->dom == NULL || d->dom->dcount == 0) {
664
+ const yep_dom* dd = doc == NULL ? NULL : yep_doc_dom((yeptris_document*)doc);
665
+ if (doc == NULL || plan == NULL || dd == NULL || dd->dcount == 0) {
662
666
  if (st != NULL) {
663
667
  *st = YEPTRIS_ERROR_ARG;
664
668
  }
665
669
  return NULL;
666
670
  }
667
- const yep_dnode* root = yep_dom_node(d->dom, d->dom->docs[0]);
668
- const yep_dnode* container = dom_find_rows(d->dom, root, plan);
671
+ const yep_dnode* root = yep_dom_node(dd, dd->docs[0]);
672
+ const yep_dnode* container = dom_find_rows(dd, root, plan);
669
673
  if (container == NULL) {
670
674
  if (st != NULL) {
671
675
  *st = YEPTRIS_ERROR_PARSE; /* document shape disagreement */
@@ -676,7 +680,7 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
676
680
  size_t rows = 0;
677
681
  for (uint32_t cur = container->first_child; cur != UINT32_MAX;) {
678
682
  const yep_dnode* row;
679
- cur = dom_rows_next(d->dom, container, cur, &row);
683
+ cur = dom_rows_next(dd, container, cur, &row);
680
684
  if (row != NULL) {
681
685
  rows++;
682
686
  }
@@ -690,15 +694,15 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
690
694
  size_t row_i = 0;
691
695
  for (uint32_t cur = container->first_child; cur != UINT32_MAX && row_i < rows;) {
692
696
  const yep_dnode* row;
693
- cur = dom_rows_next(d->dom, container, cur, &row);
697
+ cur = dom_rows_next(dd, container, cur, &row);
694
698
  if (row == NULL) {
695
699
  continue;
696
700
  }
697
701
  uint32_t cn = row->first_child;
698
702
  while (cn != UINT32_MAX) {
699
- const yep_dnode* k = yep_dom_node(d->dom, cn);
700
- const yep_dnode* v = yep_dom_node(d->dom, k->next_sibling);
701
- yep_view kv = sv_view(d->dom, k->value);
703
+ const yep_dnode* k = yep_dom_node(dd, cn);
704
+ const yep_dnode* v = yep_dom_node(dd, k->next_sibling);
705
+ yep_view kv = sv_view(dd, k->value);
702
706
  int col_idx = -1;
703
707
  if (k->kind == YEP_DOM_SCALAR) {
704
708
  for (size_t c = 0; c < plan->ncols; c++) {
@@ -710,10 +714,10 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
710
714
  }
711
715
  }
712
716
  if (col_idx >= 0) {
713
- const yep_dnode* val = dom_alias_final(d->dom, v);
717
+ const yep_dnode* val = dom_alias_final(dd, v);
714
718
  yep_result_col* col = &r->cols[col_idx];
715
719
  if (val != NULL && val->kind == YEP_DOM_SCALAR && val->tag_id != YEPTRIS_TAG_NULL) {
716
- yep_view sv = sv_view(d->dom, val->value);
720
+ yep_view sv = sv_view(dd, val->value);
717
721
  int filled = 0;
718
722
  switch (col->kind) {
719
723
  case YEP_PLAN_STR:
@@ -601,8 +601,8 @@ reject:
601
601
  * (the classify scan carries no accept/reject structure) and
602
602
  * yeptris_tape_convert owns validation. Everything else matches
603
603
  * tape_walk state for state. */
604
- static YeptrisStatus tape_walk_lnt_fused(const char* p, size_t len, size_t open,
605
- yeptris_json_tape* t) {
604
+ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
605
+ yeptris_json_tape* t) {
606
606
  if (tape_carve(t, len) != YEPTRIS_OK) {
607
607
  return YEPTRIS_ERROR_MEMORY;
608
608
  }
@@ -1090,7 +1090,7 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape_lenient(const char* source, si
1090
1090
  }
1091
1091
  free(idx);
1092
1092
  #endif
1093
- return tape_walk_lnt_fused(source, len, at, tape);
1093
+ return yep_tape_walk_lenient_fused(source, len, at, tape);
1094
1094
  }
1095
1095
 
1096
1096
  /* scalar root: one record, same deferred split for numbers */
@@ -0,0 +1,19 @@
1
+ /* tape_in.h — the tape module's internal seams (not public ABI).
2
+ *
3
+ * parse.c's #342 slice-2 lazy route drives the fused lenient walk
4
+ * directly: the gate and opener checks it already ran must not be
5
+ * repeated (the lenient public entry re-gates). The walk's contract
6
+ * is the public entry's: gate-clean buffer, p[open] is '[' or '{',
7
+ * tail must be whitespace-only. */
8
+ #ifndef YEP_TAPE_IN_H
9
+ #define YEP_TAPE_IN_H
10
+
11
+ #include <stddef.h>
12
+
13
+ #include <yeptris/tape.h>
14
+
15
+ /* tape.c (was tape_walk_lnt_fused) */
16
+ YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
17
+ yeptris_json_tape* t);
18
+
19
+ #endif
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.13.1
4
+ version: 0.6.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -164,6 +164,7 @@ files:
164
164
  - vendor/libyeptris/src/yeptris/scan/scan.h
165
165
  - vendor/libyeptris/src/yeptris/schema.c
166
166
  - vendor/libyeptris/src/yeptris/tape.c
167
+ - vendor/libyeptris/src/yeptris/tape_in.h
167
168
  - vendor/libyeptris/src/yeptris/version.c
168
169
  - vendor/libyeptris/src/yeptris/visit/dom_visit.c
169
170
  - vendor/libyeptris/src/yeptris/visit/json_visit.c