yeptris 0.2.0.1-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.
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "time"
5
+
6
+ # A node inside a document. Borrowed C memory: every call routes
7
+ # through the owning document's liveness check — use after free
8
+ # raises Yeptris::FreedError, never a segfault.
9
+ class Yeptris::Node
10
+ include Enumerable
11
+
12
+ attr_reader :c_ptr
13
+
14
+ # @api private — always constructed via Document#wrap_node (identity).
15
+ def initialize(c_ptr, document)
16
+ @c_ptr = c_ptr
17
+ @document = document
18
+ end
19
+
20
+ def document
21
+ @document
22
+ end
23
+
24
+ KINDS = {
25
+ Yeptris::FFI::NODE_SCALAR => :scalar,
26
+ Yeptris::FFI::NODE_SEQUENCE => :sequence,
27
+ Yeptris::FFI::NODE_MAPPING => :mapping,
28
+ Yeptris::FFI::NODE_ALIAS => :alias,
29
+ }.freeze
30
+
31
+ def kind
32
+ KINDS[alive { Yeptris::FFI.yeptris_node_kind(@c_ptr) }]
33
+ end
34
+
35
+ def scalar?
36
+ kind == :scalar
37
+ end
38
+
39
+ def sequence?
40
+ kind == :sequence
41
+ end
42
+
43
+ def mapping?
44
+ kind == :mapping
45
+ end
46
+
47
+ def alias?
48
+ kind == :alias
49
+ end
50
+
51
+ # Scalar content / alias name (UTF-8 String), nil for collections.
52
+ def value
53
+ len = ::FFI::MemoryPointer.new(:uint64)
54
+ ptr = alive { Yeptris::FFI.yeptris_node_value(@c_ptr, len) }
55
+ return nil if ptr.null?
56
+
57
+ ptr.read_bytes(len.read_uint64).force_encoding(Encoding::UTF_8)
58
+ end
59
+
60
+ STYLES = {
61
+ Yeptris::FFI::STYLE_PLAIN => :plain,
62
+ Yeptris::FFI::STYLE_SINGLE_QUOTED => :single_quoted,
63
+ Yeptris::FFI::STYLE_DOUBLE_QUOTED => :double_quoted,
64
+ Yeptris::FFI::STYLE_LITERAL => :literal,
65
+ Yeptris::FFI::STYLE_FOLDED => :folded,
66
+ }.freeze
67
+
68
+ def style
69
+ STYLES[alive { Yeptris::FFI.yeptris_node_style(@c_ptr) }]
70
+ end
71
+
72
+ # Explicit tag URI when present, else nil.
73
+ def tag
74
+ len = ::FFI::MemoryPointer.new(:uint64)
75
+ ptr = alive { Yeptris::FFI.yeptris_node_tag(@c_ptr, len) }
76
+ return nil if ptr.null?
77
+
78
+ ptr.read_bytes(len.read_uint64).force_encoding(Encoding::UTF_8)
79
+ end
80
+
81
+ def anchor
82
+ len = ::FFI::MemoryPointer.new(:uint64)
83
+ ptr = alive { Yeptris::FFI.yeptris_node_anchor(@c_ptr, len) }
84
+ return nil if ptr.null?
85
+
86
+ ptr.read_bytes(len.read_uint64).force_encoding(Encoding::UTF_8)
87
+ end
88
+
89
+ def alias_target
90
+ t = alive { Yeptris::FFI.yeptris_node_alias_target(@c_ptr) }
91
+ @document.wrap_node(t)
92
+ end
93
+
94
+ # ---- typed scalar reads (tag id decides eligibility) ----
95
+
96
+ def to_i
97
+ out = ::FFI::MemoryPointer.new(:int64)
98
+ status = alive { Yeptris::FFI.yeptris_node_int(@c_ptr, out) }
99
+ Yeptris::FFI.check_status(status, "yeptris_node_int")
100
+ out.read_int64
101
+ end
102
+
103
+ def to_f
104
+ out = ::FFI::MemoryPointer.new(:double)
105
+ status = alive { Yeptris::FFI.yeptris_node_float(@c_ptr, out) }
106
+ Yeptris::FFI.check_status(status, "yeptris_node_float")
107
+ out.read_double
108
+ end
109
+
110
+ def to_bool
111
+ out = ::FFI::MemoryPointer.new(:int)
112
+ status = alive { Yeptris::FFI.yeptris_node_bool(@c_ptr, out) }
113
+ Yeptris::FFI.check_status(status, "yeptris_node_bool")
114
+ out.read_int != 0
115
+ end
116
+
117
+ TAGS = {
118
+ Yeptris::FFI::TAG_STR => :str,
119
+ Yeptris::FFI::TAG_INT => :int,
120
+ Yeptris::FFI::TAG_FLOAT => :float,
121
+ Yeptris::FFI::TAG_BOOL => :bool,
122
+ Yeptris::FFI::TAG_NULL => :null,
123
+ Yeptris::FFI::TAG_TIMESTAMP => :timestamp,
124
+ }.freeze
125
+
126
+ def tag_id
127
+ TAGS[alive { Yeptris::FFI.yeptris_node_tag_id(@c_ptr) }]
128
+ end
129
+
130
+ # ---- sequence access ----
131
+
132
+ def size
133
+ case kind
134
+ when :sequence then seq_count
135
+ when :mapping then map_count
136
+ else 1
137
+ end
138
+ end
139
+
140
+ def seq_count
141
+ alive { Yeptris::FFI.yeptris_node_seq_count(@c_ptr) }
142
+ end
143
+
144
+ def seq_at(index)
145
+ @document.wrap_node(alive { Yeptris::FFI.yeptris_node_seq_at(@c_ptr, index) })
146
+ end
147
+
148
+ def each
149
+ return enum_for(:each) unless block_given?
150
+ raise Yeptris::Error, "#each is for sequences" unless sequence?
151
+
152
+ (0...seq_count).each { |i| yield seq_at(i) }
153
+ end
154
+
155
+ # ---- mapping access ----
156
+
157
+ def map_count
158
+ alive { Yeptris::FFI.yeptris_node_map_count(@c_ptr) }
159
+ end
160
+
161
+ def [](key)
162
+ raise Yeptris::Error, "#[] is for mappings" unless mapping?
163
+
164
+ key = key.to_s
165
+ @document.wrap_node(
166
+ alive { Yeptris::FFI.yeptris_node_map_get(@c_ptr, key, key.bytesize) }
167
+ )
168
+ end
169
+
170
+ def key?(key)
171
+ !self[key].nil?
172
+ end
173
+
174
+ # Ordered [key, value] pairs.
175
+ def each_pair
176
+ return enum_for(:each_pair) unless block_given?
177
+ raise Yeptris::Error, "#each_pair is for mappings" unless mapping?
178
+
179
+ k = ::FFI::MemoryPointer.new(:pointer)
180
+ v = ::FFI::MemoryPointer.new(:pointer)
181
+ (0...map_count).each do |i|
182
+ rc = alive { Yeptris::FFI.yeptris_node_map_at(@c_ptr, i, k, v) }
183
+ next unless rc.zero?
184
+
185
+ yield @document.wrap_node(k.read_pointer), @document.wrap_node(v.read_pointer)
186
+ end
187
+ end
188
+
189
+ def keys
190
+ # explicit block: two-arg yield + Symbol#to_proc would call
191
+ # key.first(value) instead of taking the pair
192
+ each_pair.map { |k, _v| k }
193
+ end
194
+
195
+ # ---- construction (TODO.impl/11 phase 3; errors raise) ----
196
+
197
+ def map_add(key, node)
198
+ key = key.to_s
199
+ rc = alive { Yeptris::FFI.yeptris_node_map_add(@c_ptr, key, key.bytesize, node.c_ptr) }
200
+ Yeptris::FFI.check_status(rc, "yeptris_node_map_add")
201
+ self
202
+ end
203
+
204
+ def map_set(key, node)
205
+ key = key.to_s
206
+ rc = alive { Yeptris::FFI.yeptris_node_map_set(@c_ptr, key, key.bytesize, node.c_ptr) }
207
+ Yeptris::FFI.check_status(rc, "yeptris_node_map_set")
208
+ self
209
+ end
210
+
211
+ def map_del(key)
212
+ key = key.to_s
213
+ alive { Yeptris::FFI.yeptris_node_map_del(@c_ptr, key, key.bytesize) }.zero?
214
+ end
215
+
216
+ def seq_add(node)
217
+ rc = alive { Yeptris::FFI.yeptris_node_seq_add(@c_ptr, node.c_ptr) }
218
+ Yeptris::FFI.check_status(rc, "yeptris_node_seq_add")
219
+ self
220
+ end
221
+
222
+ # Props on synthesized nodes (15's YAMLTree); values copied in.
223
+ def set_anchor(name)
224
+ alive { Yeptris::FFI.yeptris_node_set_anchor(@c_ptr, name, name.bytesize) }.zero?
225
+ end
226
+
227
+ def set_tag(tag)
228
+ alive { Yeptris::FFI.yeptris_node_set_tag(@c_ptr, tag, tag.bytesize) }.zero?
229
+ end
230
+
231
+ def seq_del(index)
232
+ alive { Yeptris::FFI.yeptris_node_seq_del(@c_ptr, index) }.zero?
233
+ end
234
+
235
+ # ---- Ruby materialization (Psych-compatible) ----
236
+
237
+ # Materializes the subtree as native Ruby objects. Aliases preserve
238
+ # object identity (the same Ruby object for every reference), keys
239
+ # materialize with Psych's implicit typing (":sym" -> Symbol under
240
+ # compat), and the anchor memo makes cycles defined by anchors safe.
241
+ def node_id
242
+ alive { Yeptris::FFI.yeptris_node_id(@c_ptr) }
243
+ end
244
+
245
+ def to_ruby(memo = nil)
246
+ # readonly documents memoize per node: a second materialization
247
+ # returns the SAME object (the readonly cache is the ground truth
248
+ # for alias identity across calls).
249
+ if @document.readonly?
250
+ rm = @document.readonly_memo
251
+ cached = rm[node_id]
252
+ return cached if cached
253
+ end
254
+ # Marshal fast path (TODO.restructure/21): one C call turns the
255
+ # whole subtree into Ruby objects, preserving alias identity via
256
+ # the object's own object-link table. Only at the TOP-LEVEL entry
257
+ # (memo nil): inside a recursive walk a partial marshal would
258
+ # materialize outside the shared memo and break alias identity.
259
+ # Falls back to the per-node FFI walk on constructs the format
260
+ # cannot express (merge keys, timestamps) and on older builds.
261
+ if Yeptris::FFI::MARSHAL && memo.nil?
262
+ result =
263
+ begin
264
+ out_p = ::FFI::MemoryPointer.new(:pointer)
265
+ olen_p = ::FFI::MemoryPointer.new(:size_t)
266
+ st = Yeptris::FFI.yeptris_marshal_node(@c_ptr, out_p, olen_p)
267
+ if st == Yeptris::FFI::ERROR_UNSUPPORTED
268
+ nil
269
+ elsif st != Yeptris::FFI::OK
270
+ raise Yeptris::ParseError, Yeptris::FFI.last_error_message
271
+ else
272
+ buf = out_p.read_pointer
273
+ len = olen_p.read_uint64
274
+ bytes = buf.read_bytes(len)
275
+ bytes.force_encoding(Encoding::ASCII_8BIT)
276
+ ::Marshal.load(bytes)
277
+ end
278
+ ensure
279
+ Yeptris::FFI.yeptris_marshal_free(out_p.read_pointer) if out_p
280
+ end
281
+ unless result.nil?
282
+ @document.readonly_memo[node_id] = result if @document.readonly?
283
+ return result
284
+ end
285
+ end
286
+ if @document.readonly?
287
+ return @document.readonly_memo[node_id] = to_ruby_walk({})
288
+ end
289
+ to_ruby_walk(memo || {})
290
+ end
291
+
292
+ def to_ruby_walk(memo)
293
+ cached = memo[node_id]
294
+ return cached if cached
295
+
296
+ case kind
297
+ when :mapping
298
+ h = {}
299
+ memo[node_id] = h
300
+ each_pair do |k, v|
301
+ h[k.to_ruby_key] = v.to_ruby(memo)
302
+ end
303
+ h
304
+ when :sequence
305
+ a = []
306
+ memo[node_id] = a
307
+ each { |e| a << e.to_ruby(memo) }
308
+ a
309
+ when :alias
310
+ t = alias_target
311
+ t.nil? ? nil : t.to_ruby(memo)
312
+ else
313
+ scalar_to_ruby
314
+ end
315
+ end
316
+
317
+ # Mapping keys: Psych semantics — a plain scalar ":name" under the
318
+ # compat schema scans to a Symbol; everything else materializes as
319
+ # the scalar itself.
320
+ def to_ruby_key
321
+ symbol? ? symbolize : to_ruby
322
+ end
323
+
324
+ def scalar_to_ruby
325
+ return symbolize if symbol? && tag_id == :str
326
+
327
+ case tag_id
328
+ when :null then nil
329
+ when :bool then to_bool
330
+ when :int then to_i
331
+ when :float
332
+ # Psych's dot-required float applies ONLY under compat_11; the
333
+ # core schema's regexp has an optional dot (spec 10.3.2), so
334
+ # core_12 exponent-only floats materialize as Floats
335
+ text = value.to_s
336
+ if @document.parse_schema == :compat_11 &&
337
+ !text.include?(".") && !text.include?(":") && !text.start_with?(".")
338
+ text
339
+ else
340
+ to_f
341
+ end
342
+ when :timestamp then ::Yeptris::Materializer.parse_timestamp(value)
343
+ else
344
+ # beyond int64 the C resolver leaves plain scalars :str; Psych
345
+ # materializes Integer (issue #31) — rebuild from the text
346
+ text = value.to_s
347
+ if style == :plain && text.length > 18 &&
348
+ ::Yeptris::ValueML::PSYCH_INT_SHAPE.match?(text)
349
+ text.delete(",_").to_i
350
+ else
351
+ value
352
+ end
353
+ end
354
+ end
355
+
356
+ # Psych's ScalarScanner: plain ":name" (and ":", the null Symbol)
357
+ # scans to a Symbol; quoting defeats it.
358
+ def symbol?
359
+ style == :plain && value&.start_with?(":") && !value.start_with?("::")
360
+ end
361
+
362
+ def symbolize
363
+ v = value
364
+ v.length <= 1 ? :"" : v[1..].to_sym
365
+ end
366
+
367
+
368
+
369
+ def alive
370
+ @document.ensure_alive!
371
+ yield
372
+ end
373
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yeptris
4
+ module Psych
5
+ # Minimal Psych::Coder stand-in (encode_with / init_with carry
6
+ # these): tag + one of scalar / seq / map. map is ordered.
7
+ class CoderShim
8
+ attr_reader :type, :tag, :scalar, :seq
9
+
10
+ def initialize(class_name = nil)
11
+ @type = :map
12
+ # Psych's default for encode_with objects: their own class tag
13
+ @tag = class_name ? "!ruby/object:#{class_name}" : nil
14
+ @scalar = nil
15
+ @seq = nil
16
+ @map = nil
17
+ @class_name = class_name
18
+ end
19
+
20
+ def tag=(t)
21
+ @tag = t
22
+ end
23
+
24
+ def scalar=(value)
25
+ @type = :scalar
26
+ @scalar = value
27
+ end
28
+
29
+ def seq=(list)
30
+ @type = :seq
31
+ @seq = list
32
+ end
33
+
34
+ def map=(hash)
35
+ @type = :map
36
+ @map = nil
37
+ hash&.each { |k, v| self[k] = v }
38
+ end
39
+
40
+ def []=(key, value)
41
+ @type = :map
42
+ @map ||= {}
43
+ @map[key.to_s] = value
44
+ end
45
+
46
+ def [](key)
47
+ @map&.[](key.to_s)
48
+ end
49
+
50
+ def each(&block)
51
+ @map&.each(&block)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The process-exclusive drop-in (issue #69): rebinds the top-level
4
+ # Psych constant to Yeptris::Psych. Once this runs, stdlib psych must
5
+ # NOT be loaded afterwards — its require would re-open THIS module and
6
+ # clobber constants ("already initialized" warnings, then
7
+ # NameError/NoMethodError at call time). Bundles that cannot
8
+ # guarantee the load order (activesupport et al.) should require
9
+ # "yeptris/psych" only and reference Yeptris::Psych directly.
10
+ require "yeptris" # the autoload declarations (Document, Node, ...) live here
11
+ require "yeptris/psych"
12
+
13
+ # ( Psych-stdlib, if already loaded, stays reachable as
14
+ # ::Psych::ORIGINAL.)
15
+ if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych) &&
16
+ !Yeptris::Psych.const_defined?(:ORIGINAL, false)
17
+ Yeptris::Psych.const_set(:ORIGINAL, ::Psych)
18
+ end
19
+ # class_eval reaches Module-private methods WITHOUT send (the law: no
20
+ # send to private methods); remove_const has no public form, and the
21
+ # rebind is this file's whole purpose
22
+ Object.class_eval { remove_const(:Psych) } if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych)
23
+ ::Psych = Yeptris::Psych
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO.restructure/23 — the typed opt-in marker for arbitrary-object
4
+ # dump/load. Classes include this module and implement
5
+ # encode_with(coder) / init_with(coder) (Psych's coder protocol).
6
+ # The visitors call those methods directly; the library never uses
7
+ # respond_to?, never reads or writes instance variables from outside
8
+ # the object's public surface (the encapsulation law).
9
+ module Yeptris
10
+ module Psych
11
+ module Encodable
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yeptris
4
+ module Psych
5
+ # The event-level API (TODO.impl/15 phase E): the abstract base
6
+ # Psych::Parser dispatches onto. Every event a YAML stream can
7
+ # produce is a method; the arg shapes are Psych's own (verified
8
+ # differentially against the stdlib recorder in the specs).
9
+ class Handler
10
+ # Scalar styles — the same integers Psych::Nodes::Scalar uses,
11
+ # and the same values YeptrisEventRecord.style carries.
12
+ ANY = 0
13
+ PLAIN = 1
14
+ SINGLE_QUOTED = 2
15
+ DOUBLE_QUOTED = 3
16
+ LITERAL = 4
17
+ FOLDED = 5
18
+
19
+ # Collection styles (Psych::Nodes::Sequence / Mapping).
20
+ BLOCK = 1
21
+ FLOW = 2
22
+
23
+ class DumperOptions
24
+ attr_accessor :line_width, :indentation, :canonical
25
+
26
+ def initialize
27
+ @line_width = 0
28
+ @indentation = 2
29
+ @canonical = false
30
+ end
31
+ end
32
+
33
+ OPTIONS = DumperOptions.new
34
+
35
+ EVENTS = %i[alias empty end_document end_mapping end_sequence end_stream
36
+ scalar start_document start_mapping start_sequence start_stream].freeze
37
+
38
+ # Called once per stream; +encoding+ is a Psych::Parser
39
+ # encoding constant (we always parse to UTF-8).
40
+ def start_stream(encoding); end
41
+
42
+ # +version+ is [major, minor] when %YAML declared, else nil
43
+ # (yeptris does not carry the directive into the event).
44
+ # +tag_directives+ is a list of [handle, prefix] pairs ([] for
45
+ # us: same directive omission). +implicit+ true without ---.
46
+ def start_document(version, tag_directives, implicit); end
47
+
48
+ def end_document(implicit); end
49
+
50
+ def alias(anchor); end
51
+
52
+ # +style+ is one of the scalar constants above; +plain+ /
53
+ # +quoted+ are the implicit-typing flags (plain: resolvable
54
+ # without a tag).
55
+ def scalar(value, anchor, tag, plain, quoted, style); end
56
+
57
+ def start_sequence(anchor, tag, implicit, style); end
58
+
59
+ def end_sequence; end
60
+
61
+ def start_mapping(anchor, tag, implicit, style); end
62
+
63
+ def end_mapping; end
64
+
65
+ def end_stream; end
66
+
67
+ # libyaml artifact (its NO_EVENT after stream end); the
68
+ # yeptris engine never produces one, so this never fires.
69
+ def empty; end
70
+ end
71
+
72
+ module Handlers
73
+ # Captures every event as [name, args] — Psych's own Recorder
74
+ # shape, so recordings replay through any Handler-compatible
75
+ # emitter.
76
+ class Recorder < Handler
77
+ attr_reader :events
78
+
79
+ def initialize
80
+ @events = []
81
+ super()
82
+ end
83
+
84
+ Handler::EVENTS.each do |event|
85
+ define_method(event) { |*args| @events << [event, args] }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yeptris
4
+ module Psych
5
+ # The event parser (TODO.impl/15 phase E): drives a Handler with
6
+ # Psych's exact event shapes over the yeptris recorder — one bulk
7
+ # record drain (the Materializer's seam), then a pure-Ruby
8
+ # dispatch loop. No callback marshaling, no per-event FFI calls.
9
+ class Parser
10
+ attr_accessor :handler
11
+ attr_reader :filename
12
+
13
+ ANY = 0
14
+ UTF8 = 1
15
+ UTF16LE = 2
16
+ UTF16BE = 3
17
+
18
+ def initialize(handler = Handler.new)
19
+ @handler = handler
20
+ @filename = nil
21
+ end
22
+
23
+ def parse(yaml, filename = nil)
24
+ yaml = Yeptris.read_input(yaml)
25
+ yaml = yaml.to_s
26
+ @filename = filename
27
+ flat, arena = Materializer.drain(yaml, schema: :compat_11)
28
+ dispatch(flat, arena)
29
+ self
30
+ rescue ParseError => e
31
+ raise SyntaxError.from_parse_error(e)
32
+ end
33
+
34
+ private
35
+
36
+ STREAM_START = Materializer::STREAM_START
37
+ STREAM_END = Materializer::STREAM_END
38
+ DOCUMENT_START = Materializer::DOCUMENT_START
39
+ DOCUMENT_END = Materializer::DOCUMENT_END
40
+ SEQUENCE_START = Materializer::SEQUENCE_START
41
+ MAPPING_START = Materializer::MAPPING_START
42
+ SCALAR = Materializer::SCALAR
43
+ ALIAS = Materializer::ALIAS
44
+
45
+ EF_FLOW = 1 << 0
46
+ EF_EXPLICIT = 1 << 1
47
+ STYLE_PLAIN = 1
48
+ STYLE_SINGLE_QUOTED = 2
49
+ STYLE_DOUBLE_QUOTED = 3
50
+
51
+ def dispatch(flat, arena)
52
+ h = @handler
53
+ i = 0
54
+ while i < flat.length
55
+ type = flat[i]
56
+ style = flat[i + 1]
57
+ flags = flat[i + 2]
58
+ value = field(flat, arena, i + 6)
59
+ anchor = field(flat, arena, i + 8)
60
+ tag = field(flat, arena, i + 10)
61
+ case type
62
+ when STREAM_START then h.start_stream(UTF8)
63
+ when STREAM_END then h.end_stream
64
+ # version: [] when no %YAML directive (Psych's own shape);
65
+ # a declared directive's [major, minor] is not carried by
66
+ # the record — the one documented divergence
67
+ when DOCUMENT_START then h.start_document([], [], (flags & EF_EXPLICIT).zero?)
68
+ when DOCUMENT_END then h.end_document((flags & EF_EXPLICIT).zero?)
69
+ when SEQUENCE_START
70
+ h.start_sequence(anchor, tag, tag.nil?, (flags & EF_FLOW).zero? ? Handler::BLOCK : Handler::FLOW)
71
+ when Materializer::SEQUENCE_END then h.end_sequence
72
+ when MAPPING_START
73
+ h.start_mapping(anchor, tag, tag.nil?, (flags & EF_FLOW).zero? ? Handler::BLOCK : Handler::FLOW)
74
+ when Materializer::MAPPING_END then h.end_mapping
75
+ when SCALAR
76
+ # A synthesized empty scalar (empty document, '?' empty
77
+ # key) carries style ANY(0); Psych's C emits plain(1)
78
+ # with an empty String value — normalized here, at the
79
+ # boundary, so the emitter's style chooser is untouched
80
+ if style.zero?
81
+ style = STYLE_PLAIN
82
+ end
83
+ value = +"" if value.nil?
84
+ # libyaml's plain_implicit: a plain scalar that carried
85
+ # no explicit tag (an explicit tag kills implicit typing)
86
+ plain = style == STYLE_PLAIN && tag.nil?
87
+ # quoted_implicit covers every explicit style — single,
88
+ # double, literal, folded — when the tag was absent
89
+ quoted = !plain && tag.nil? && style > STYLE_PLAIN
90
+ h.scalar(value, anchor, tag, plain, quoted, style)
91
+ when ALIAS then h.alias(value)
92
+ end
93
+ i += Materializer::FIELDS
94
+ end
95
+ end
96
+
97
+ def field(flat, arena, at)
98
+ len = flat[at + 1]
99
+ return nil if len.zero?
100
+
101
+ arena.byteslice(flat[at], len)
102
+ end
103
+ end
104
+ end
105
+ end