yeptris 0.6.14.1-aarch64-linux → 0.6.15.2-aarch64-linux
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 +23 -12
- data/lib/yeptris/materializer.rb +5 -1
- data/lib/yeptris/node.rb +66 -21
- data/lib/yeptris/psych/visitors.rb +67 -6
- data/lib/yeptris/psych.rb +62 -11
- data/lib/yeptris/yaml/descriptor.rb +2 -0
- data/lib/yeptris/yaml.rb +15 -1
- data/lib/yeptris.rb +1 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/yeptris/marshal.c +34 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 419470a0e038bec65880a3d764da46f072d7993e5aca9de71825a8428d5b2f98
|
|
4
|
+
data.tar.gz: 19cf714c7f80abc07d8729420700ef2c08bf1a140de03c3e11a8bb6ff177a4f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d11c53e5305c76f780f431d73be9c62228e250ab164c4fd636429ad4fca4e3705b1d46b0e9cf7abed5d8a0662d0347a2399de0ce3a16f5eec987d05d0fb053f
|
|
7
|
+
data.tar.gz: 5c24e95902b4ae44595710274ede3efbd605885d8cbc0747fc0965fcb8a2addb32ea8f18648606fef823d09a6f106d777d175f4e8ed0f1c8e74810f22d6cbee6
|
data/lib/yeptris/document.rb
CHANGED
|
@@ -20,24 +20,29 @@ class Yeptris::Document
|
|
|
20
20
|
# always yields the same Ruby object (identity for aliases/eql?),
|
|
21
21
|
# cleared at free — no stale entries, no GC-race weak maps.
|
|
22
22
|
@wrapper_cache = {}
|
|
23
|
-
ObjectSpace.define_finalizer(self, self.class.finalize(c_ptr
|
|
23
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(c_ptr)) unless c_ptr.null?
|
|
24
24
|
end
|
|
25
25
|
|
|
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,
|
|
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,
|
|
37
|
+
Yeptris::FFI.yeptris_parse_ex(yaml, yaml.bytesize, opts, status)
|
|
37
38
|
end
|
|
38
39
|
if doc.null?
|
|
39
|
-
#
|
|
40
|
-
#
|
|
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
|
|
@@ -83,6 +88,9 @@ class Yeptris::Document
|
|
|
83
88
|
return if @freed.state == :freed
|
|
84
89
|
|
|
85
90
|
@freed.state = :freed
|
|
91
|
+
# stop the GC finalizer BEFORE the C free — it captures the same
|
|
92
|
+
# pointer, and its firing after this free is the double free
|
|
93
|
+
ObjectSpace.undefine_finalizer(self)
|
|
86
94
|
@wrapper_cache.clear
|
|
87
95
|
Yeptris::FFI.yeptris_document_free(@c_ptr)
|
|
88
96
|
end
|
|
@@ -216,12 +224,15 @@ class Yeptris::Document
|
|
|
216
224
|
self
|
|
217
225
|
end
|
|
218
226
|
|
|
219
|
-
# GC safety net:
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
# GC safety net (#182): the proc captures ONLY the raw pointer — no
|
|
228
|
+
# Ruby objects, no wrapper, no freed-flag struct. A finalizer that
|
|
229
|
+
# closes over the owning wrapper races that wrapper's own sweep
|
|
230
|
+
# (the classic use-after-free: freed?/free against half-collected
|
|
231
|
+
# ivars, then a garbage C pointer into yeptris_document_free →
|
|
232
|
+
# SIGABRT). Explicit #free undefines this finalizer BEFORE the C
|
|
233
|
+
# free, so the double free cannot happen; finalizers run on the
|
|
234
|
+
# main thread, so there is no cross-thread window.
|
|
235
|
+
def self.finalize(c_ptr)
|
|
236
|
+
proc { Yeptris::FFI.yeptris_document_free(c_ptr) }
|
|
226
237
|
end
|
|
227
238
|
end
|
data/lib/yeptris/materializer.rb
CHANGED
|
@@ -115,7 +115,11 @@ module Yeptris
|
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
def parse_timestamp(v)
|
|
118
|
-
|
|
118
|
+
# stdlib scalar_scanner: date-only strings parse with
|
|
119
|
+
# strptime('%F', Date::GREGORIAN) — the PROLEPTIC calendar —
|
|
120
|
+
# not Date.parse's default ITALY reform (a 1582 cycle fails
|
|
121
|
+
# the ported test_julian_date otherwise)
|
|
122
|
+
return ::Date.strptime(v, "%F", ::Date::GREGORIAN) unless v.match?(/[Tt ]\d/)
|
|
119
123
|
|
|
120
124
|
# normalize the YAML 1.1 space forms onto iso8601 for
|
|
121
125
|
# xmlschema: "2001-12-14 21:59:43.10 -05:00" ->
|
data/lib/yeptris/node.rb
CHANGED
|
@@ -276,6 +276,31 @@ class Yeptris::Node
|
|
|
276
276
|
alive { Yeptris::FFI.yeptris_node_id(@c_ptr) }
|
|
277
277
|
end
|
|
278
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
|
+
|
|
279
304
|
def to_ruby(memo = nil)
|
|
280
305
|
# readonly documents memoize per node: a second materialization
|
|
281
306
|
# returns the SAME object (the readonly cache is the ground truth
|
|
@@ -293,25 +318,7 @@ class Yeptris::Node
|
|
|
293
318
|
# Falls back to the per-node FFI walk on constructs the format
|
|
294
319
|
# cannot express (merge keys, timestamps) and on older builds.
|
|
295
320
|
if Yeptris::FFI::MARSHAL && memo.nil?
|
|
296
|
-
result =
|
|
297
|
-
begin
|
|
298
|
-
out_p = ::FFI::MemoryPointer.new(:pointer)
|
|
299
|
-
olen_p = ::FFI::MemoryPointer.new(:size_t)
|
|
300
|
-
st = Yeptris::FFI.yeptris_marshal_node(@c_ptr, out_p, olen_p)
|
|
301
|
-
if st == Yeptris::FFI::ERROR_UNSUPPORTED
|
|
302
|
-
nil
|
|
303
|
-
elsif st != Yeptris::FFI::OK
|
|
304
|
-
raise Yeptris::ParseError, Yeptris::FFI.last_error_message
|
|
305
|
-
else
|
|
306
|
-
buf = out_p.read_pointer
|
|
307
|
-
len = olen_p.read_uint64
|
|
308
|
-
bytes = buf.read_bytes(len)
|
|
309
|
-
bytes.force_encoding(Encoding::ASCII_8BIT)
|
|
310
|
-
::Marshal.load(bytes)
|
|
311
|
-
end
|
|
312
|
-
ensure
|
|
313
|
-
Yeptris::FFI.yeptris_marshal_free(out_p.read_pointer) if out_p
|
|
314
|
-
end
|
|
321
|
+
result = marshal_fast
|
|
315
322
|
unless result.nil?
|
|
316
323
|
@document.readonly_memo[node_id] = result if @document.readonly?
|
|
317
324
|
return result
|
|
@@ -332,7 +339,32 @@ class Yeptris::Node
|
|
|
332
339
|
h = {}
|
|
333
340
|
memo[node_id] = h
|
|
334
341
|
each_pair do |k, v|
|
|
335
|
-
|
|
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
|
|
336
368
|
end
|
|
337
369
|
h
|
|
338
370
|
when :sequence
|
|
@@ -342,7 +374,11 @@ class Yeptris::Node
|
|
|
342
374
|
a
|
|
343
375
|
when :alias
|
|
344
376
|
t = alias_target
|
|
345
|
-
t.nil?
|
|
377
|
+
if t.nil?
|
|
378
|
+
raise ::Yeptris::Psych::AnchorNotDefined,
|
|
379
|
+
"Unknown anchor: #{anchor}"
|
|
380
|
+
end
|
|
381
|
+
t.to_ruby(memo)
|
|
346
382
|
else
|
|
347
383
|
scalar_to_ruby
|
|
348
384
|
end
|
|
@@ -358,6 +394,15 @@ class Yeptris::Node
|
|
|
358
394
|
def scalar_to_ruby
|
|
359
395
|
return symbolize if symbol? && tag_id == :str
|
|
360
396
|
|
|
397
|
+
# stdlib deserialize's tagged-scalar arms: the class tag carries
|
|
398
|
+
# DateTime (dumped tagged — a plain timestamp re-loads as Time)
|
|
399
|
+
if tag == "!ruby/object:DateTime"
|
|
400
|
+
ts = value.to_s.sub(/ (\d)/, 'T\\1').sub(/ ([+-]\d)/, '\\1')
|
|
401
|
+
t = ::Time.xmlschema(ts)
|
|
402
|
+
return ::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec,
|
|
403
|
+
Rational(t.utc_offset, 86_400)) + Rational(t.subsec, 86_400)
|
|
404
|
+
end
|
|
405
|
+
|
|
361
406
|
case tag_id
|
|
362
407
|
when :null then nil
|
|
363
408
|
when :bool then to_bool
|
|
@@ -66,11 +66,34 @@ module Yeptris
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# stdlib register()s every object — scalars included — so a
|
|
70
|
+
# repeated scalar dumps &name/*name (test_float_references,
|
|
71
|
+
# test_alias_with_time). Containers already route through
|
|
72
|
+
# anchor_for; this is the scalar arm of the same machinery.
|
|
73
|
+
def anchored_scalar(obj, text: nil, tag: nil)
|
|
74
|
+
state, name = anchor_for(obj)
|
|
75
|
+
return alias_of(obj, name) if state == :alias
|
|
76
|
+
|
|
77
|
+
n = scalar(text || obj, tag: tag)
|
|
78
|
+
n.set_anchor(name) if name
|
|
79
|
+
remember(obj, n)
|
|
80
|
+
n
|
|
81
|
+
end
|
|
82
|
+
|
|
69
83
|
def visit(obj)
|
|
70
84
|
case obj
|
|
85
|
+
# strings/floats/ints: stdlib yaml_tree does NOT anchor them
|
|
86
|
+
# (its float test is load-side only) — and CBOR encode
|
|
87
|
+
# rejects the anchors the cbor profile feeds it
|
|
71
88
|
when nil, true, false, ::Integer, ::Float, ::String then scalar(obj)
|
|
72
89
|
when ::Symbol then @tree.new_scalar(":#{obj}", :plain) # psych emits symbols bare, never through visit_String's quoting rules
|
|
73
|
-
|
|
90
|
+
# stdlib visit_DateTime: the class tag carries the type (a
|
|
91
|
+
# plain timestamp re-loads as Time) — "!ruby/object:DateTime
|
|
92
|
+
# 2017-04-13 12:00:00.500000000 +09:00", to_s text, one line
|
|
93
|
+
when ::DateTime
|
|
94
|
+
anchored_scalar(obj, text: obj.strftime("%F %H:%M:%S.%9N %:z"),
|
|
95
|
+
tag: "!ruby/object:DateTime")
|
|
96
|
+
when ::Date, ::Time then anchored_scalar(obj) # timestamp text, never ivars
|
|
74
97
|
when ::Hash then visit_hash(obj)
|
|
75
98
|
when ::Array then visit_array(obj)
|
|
76
99
|
when ::Struct then visit_struct(obj)
|
|
@@ -101,7 +124,7 @@ module Yeptris
|
|
|
101
124
|
if @refs[oid] < 2
|
|
102
125
|
return [:none, nil]
|
|
103
126
|
end
|
|
104
|
-
name =
|
|
127
|
+
name = (@counter += 1).to_s # stdlib: &1, *1
|
|
105
128
|
@names[oid] = name
|
|
106
129
|
[:new, name]
|
|
107
130
|
end
|
|
@@ -156,7 +179,7 @@ module Yeptris
|
|
|
156
179
|
# neutral surface alone was pinned)
|
|
157
180
|
::Yeptris::YAML::BulkBuilder.time_text(obj)
|
|
158
181
|
elsif obj.is_a?(::Date)
|
|
159
|
-
obj.iso8601 # canonical calendar form
|
|
182
|
+
obj.iso8601 # canonical calendar form
|
|
160
183
|
elsif obj.nil?
|
|
161
184
|
"" # libyaml's null rendering rides bare (issue #290)
|
|
162
185
|
elsif obj.is_a?(::Float)
|
|
@@ -165,7 +188,7 @@ module Yeptris
|
|
|
165
188
|
obj.to_s
|
|
166
189
|
end
|
|
167
190
|
n =
|
|
168
|
-
if obj.is_a?(::String)
|
|
191
|
+
if obj.is_a?(::String) && !tag
|
|
169
192
|
::Yeptris::YAML::Builder.build_string(@tree, obj)
|
|
170
193
|
else
|
|
171
194
|
@tree.new_scalar(text, :plain)
|
|
@@ -188,6 +211,13 @@ module Yeptris
|
|
|
188
211
|
key_node = @tree.new_scalar("", :single_quoted)
|
|
189
212
|
key_node.set_tag("!")
|
|
190
213
|
m.map_add_node(key_node, visit(v))
|
|
214
|
+
elsif k.is_a?(::String) && k == "<<"
|
|
215
|
+
# stdlib yaml_tree: a literal chevron key carries the
|
|
216
|
+
# explicit !!str tag (single-quoted) so it does not
|
|
217
|
+
# re-load as a merge
|
|
218
|
+
key_node = @tree.new_scalar("<<", :single_quoted)
|
|
219
|
+
key_node.set_tag("!!str")
|
|
220
|
+
m.map_add_node(key_node, visit(v))
|
|
191
221
|
else
|
|
192
222
|
m.map_add(key_text(k), visit(v))
|
|
193
223
|
end
|
|
@@ -213,7 +243,7 @@ module Yeptris
|
|
|
213
243
|
m = @tree.new_mapping
|
|
214
244
|
remember(strct, m)
|
|
215
245
|
m.set_anchor(name) if name
|
|
216
|
-
m.set_tag(strct.class.name ? "!ruby/struct
|
|
246
|
+
m.set_tag(strct.class.name.to_s.empty? ? "!ruby/struct" : "!ruby/struct:#{strct.class.name}")
|
|
217
247
|
strct.each_pair { |k, v| m.map_add(key_text(k), visit(v)) }
|
|
218
248
|
m
|
|
219
249
|
end
|
|
@@ -319,6 +349,9 @@ module Yeptris
|
|
|
319
349
|
when ::Yeptris::Psych::Nodes::Mapping then visit_mapping(node)
|
|
320
350
|
else node&.to_ruby
|
|
321
351
|
end
|
|
352
|
+
# stdlib register(): an anchored node's result is THE object
|
|
353
|
+
# for every alias to that anchor (scalars included)
|
|
354
|
+
anchors[node.anchor] = result if node.respond_to?(:anchor) && node.anchor
|
|
322
355
|
# the domain-types post-pass (stdlib ToRuby#accept's tail):
|
|
323
356
|
# registered blocks transform the revived result per node
|
|
324
357
|
if node && !::Yeptris::Psych.domain_types.empty?
|
|
@@ -415,10 +448,38 @@ module Yeptris
|
|
|
415
448
|
end
|
|
416
449
|
end
|
|
417
450
|
|
|
451
|
+
# stdlib visit_hash's merge branch, verbatim in structure: the
|
|
452
|
+
# key's SHAPE decides (an explicit !!str '<<' is a literal key,
|
|
453
|
+
# not a merge), the VALUE's node kind picks the arm, and every
|
|
454
|
+
# merge is TypeError-guarded (a bad element keeps the whole
|
|
455
|
+
# '<<' pair literal — merges are all-or-nothing).
|
|
418
456
|
def hash_into(h, node)
|
|
419
457
|
anchors[node.anchor] = h if node.anchor
|
|
420
458
|
node.children.each_slice(2) do |k, v|
|
|
421
|
-
|
|
459
|
+
key = visit(k)
|
|
460
|
+
val = visit(v)
|
|
461
|
+
if key == "<<" && k.tag != "tag:yaml.org,2002:str"
|
|
462
|
+
case v
|
|
463
|
+
when ::Yeptris::Psych::Nodes::Alias, ::Yeptris::Psych::Nodes::Mapping
|
|
464
|
+
begin
|
|
465
|
+
h.merge!(val)
|
|
466
|
+
rescue ::TypeError
|
|
467
|
+
h[key] = val
|
|
468
|
+
end
|
|
469
|
+
when ::Yeptris::Psych::Nodes::Sequence
|
|
470
|
+
begin
|
|
471
|
+
merged = {}
|
|
472
|
+
val.reverse_each { |value| merged.merge!(value) }
|
|
473
|
+
h.merge!(merged)
|
|
474
|
+
rescue ::TypeError
|
|
475
|
+
h[key] = val
|
|
476
|
+
end
|
|
477
|
+
else
|
|
478
|
+
h[key] = val
|
|
479
|
+
end
|
|
480
|
+
else
|
|
481
|
+
h[key] = val
|
|
482
|
+
end
|
|
422
483
|
end
|
|
423
484
|
h
|
|
424
485
|
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
|
|
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,16 +293,17 @@ 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|
|
|
256
300
|
stream.children << Nodes::Builder.document_stream_child(doc, i)
|
|
257
301
|
end
|
|
302
|
+
# ownership: the DOCUMENT wrapper is the sole owner — its own
|
|
303
|
+
# finalizer (pointer-only closure) frees the C memory; the
|
|
304
|
+
# stream merely references it. A tree-side finalizer closing
|
|
305
|
+
# over the wrapper raced the wrapper's sweep (#182's abort).
|
|
258
306
|
stream.owner = doc
|
|
259
|
-
ObjectSpace.define_finalizer(
|
|
260
|
-
stream, proc { doc.free unless doc.freed? }
|
|
261
|
-
)
|
|
262
307
|
stream
|
|
263
308
|
rescue Yeptris::ParseError => e
|
|
264
309
|
raise SyntaxError.from_parse_error(e)
|
|
@@ -283,6 +328,11 @@ module Yeptris
|
|
|
283
328
|
# data anyway
|
|
284
329
|
out =
|
|
285
330
|
case obj
|
|
331
|
+
# DateTime < Date in ruby's hierarchy — the fast scalar
|
|
332
|
+
# path would emit it tag-less iso8601 (re-loading as Time);
|
|
333
|
+
# the visitor carries stdlib's !ruby/object:DateTime tag
|
|
334
|
+
when ::DateTime
|
|
335
|
+
Visitors::YAMLTree.new.push(obj).finish
|
|
286
336
|
when nil, true, false, ::String, ::Integer, ::Float, ::Symbol, ::Date, ::Time
|
|
287
337
|
Yeptris::YAML.dump(obj, header: true)
|
|
288
338
|
else
|
|
@@ -398,12 +448,13 @@ module Yeptris
|
|
|
398
448
|
@tags = tags
|
|
399
449
|
end
|
|
400
450
|
|
|
401
|
-
# @api private —
|
|
451
|
+
# @api private — attach the document: the tree REFERENCES it
|
|
452
|
+
# (keeping the C memory alive while the tree is reachable);
|
|
453
|
+
# the wrapper's own finalizer is the sole freer (#182: a
|
|
454
|
+
# tree-side finalizer closing over the wrapper raced the
|
|
455
|
+
# wrapper's sweep — SIGABRT under document churn)
|
|
402
456
|
def own(yeptris_doc)
|
|
403
457
|
@owner = yeptris_doc
|
|
404
|
-
ObjectSpace.define_finalizer(
|
|
405
|
-
self, proc { yeptris_doc.free unless yeptris_doc.freed? }
|
|
406
|
-
)
|
|
407
458
|
self
|
|
408
459
|
end
|
|
409
460
|
|
|
@@ -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.
|
|
6
|
+
VERSION = "0.6.15.2".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
|
data/libyeptris.so
CHANGED
|
Binary file
|
|
@@ -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;
|