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.
- checksums.yaml +7 -0
- data/README.adoc +141 -0
- data/ext/yeptris_native/extconf.rb +47 -0
- data/ext/yeptris_native/json_ruby.c +343 -0
- data/ext/yeptris_native/yeptris_native.c +440 -0
- data/lib/yeptris/document.rb +221 -0
- data/lib/yeptris/ffi.rb +256 -0
- data/lib/yeptris/json.rb +159 -0
- data/lib/yeptris/materializer.rb +347 -0
- data/lib/yeptris/native.so +0 -0
- data/lib/yeptris/node.rb +373 -0
- data/lib/yeptris/psych/coder_shim.rb +55 -0
- data/lib/yeptris/psych/drop_in.rb +23 -0
- data/lib/yeptris/psych/encodable.rb +14 -0
- data/lib/yeptris/psych/handler.rb +90 -0
- data/lib/yeptris/psych/parser.rb +105 -0
- data/lib/yeptris/psych/visitors.rb +383 -0
- data/lib/yeptris/psych.rb +368 -0
- data/lib/yeptris/valueml.rb +455 -0
- data/lib/yeptris/yaml.rb +289 -0
- data/lib/yeptris.rb +70 -0
- data/libyeptris.so +0 -0
- metadata +83 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
# Value-stream materialization (TODO.impl/15 phase F): one drain of
|
|
5
|
+
# PRE-CONVERTED typed values from the C side, then a minimal Ruby
|
|
6
|
+
# walk — no per-scalar parsing (the number kernels already ran),
|
|
7
|
+
# key/value pairing rides the entries' is_key bit, anchors arrive
|
|
8
|
+
# as uniform entries decorating the value they bind. The Psych
|
|
9
|
+
# quirks re-decide from tag_id + the raw bytes every entry carries.
|
|
10
|
+
module ValueML
|
|
11
|
+
DOC = 0
|
|
12
|
+
V_NULL = 1
|
|
13
|
+
V_BOOL = 2
|
|
14
|
+
V_INT = 3
|
|
15
|
+
V_FLOAT = 4
|
|
16
|
+
V_STR = 5
|
|
17
|
+
|
|
18
|
+
# Psych's integer shape (scalar_scanner): sign optional, no
|
|
19
|
+
# leading zero, '_' / ',' separators only between digits. Beyond
|
|
20
|
+
# int64 the C resolver leaves the scalar a string; Psych
|
|
21
|
+
# materializes Integer (issue #31) — every walk rebuilds it.
|
|
22
|
+
PSYCH_INT_SHAPE = /\A[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*)\z/.freeze
|
|
23
|
+
V_TS = 6
|
|
24
|
+
SEQ_OPEN = 7
|
|
25
|
+
MAP_OPEN = 8
|
|
26
|
+
CLOSE = 9
|
|
27
|
+
ALIAS = 10
|
|
28
|
+
ANCHOR = 11
|
|
29
|
+
|
|
30
|
+
FIELDS = 7
|
|
31
|
+
VALUE_SIZE = 24
|
|
32
|
+
# kind, tag, is_key, b | off, len | pad4 | payload (INT/FLOAT bits)
|
|
33
|
+
UNPACK = "C4V2x4q<"
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
def load_all(yaml, schema: :compat_11)
|
|
38
|
+
yaml = Yeptris.read_input(yaml)
|
|
39
|
+
yaml = yaml.to_s
|
|
40
|
+
vals_p = ::FFI::MemoryPointer.new(:pointer)
|
|
41
|
+
count_p = ::FFI::MemoryPointer.new(:uint64)
|
|
42
|
+
arena_p = ::FFI::MemoryPointer.new(:pointer)
|
|
43
|
+
alen_p = ::FFI::MemoryPointer.new(:uint64)
|
|
44
|
+
st = FFI.yeptris_value_drain(
|
|
45
|
+
yaml, yaml.bytesize,
|
|
46
|
+
schema == :compat_11 ? FFI::SCHEMA_11_COMPAT : FFI::SCHEMA_12_CORE,
|
|
47
|
+
vals_p, count_p, arena_p, alen_p
|
|
48
|
+
)
|
|
49
|
+
raise ParseError, FFI.last_error_message if st != FFI::OK
|
|
50
|
+
|
|
51
|
+
vals = vals_p.read_pointer
|
|
52
|
+
arena = arena_p.read_pointer
|
|
53
|
+
begin
|
|
54
|
+
count = count_p.read_uint64
|
|
55
|
+
flat = vals.read_bytes(count * VALUE_SIZE).unpack(UNPACK * count)
|
|
56
|
+
arena_bytes = arena.read_bytes(alen_p.read_uint64)
|
|
57
|
+
arena_bytes.force_encoding(Encoding::UTF_8)
|
|
58
|
+
walk(flat, arena_bytes, schema == :compat_11)
|
|
59
|
+
ensure
|
|
60
|
+
FFI.yeptris_value_free(vals, arena)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Columnar path (libyeptris > 0.1.1): whole columns unpack in one
|
|
65
|
+
# call each, and the walk indexes tight arrays (no 7-slot stride).
|
|
66
|
+
# Semantics are IDENTICAL to the record walk — the two bodies are
|
|
67
|
+
# maintained in lockstep; field-access shape is the only difference.
|
|
68
|
+
def load_all_columns(yaml, schema: :compat_11)
|
|
69
|
+
yaml = Yeptris.read_input(yaml)
|
|
70
|
+
yaml = yaml.to_s
|
|
71
|
+
cols = FFI::ValueColumns.new
|
|
72
|
+
st = FFI.yeptris_value_drain_columns(
|
|
73
|
+
yaml, yaml.bytesize,
|
|
74
|
+
schema == :compat_11 ? FFI::SCHEMA_11_COMPAT : FFI::SCHEMA_12_CORE,
|
|
75
|
+
cols
|
|
76
|
+
)
|
|
77
|
+
raise ParseError, FFI.last_error_message if st != FFI::OK
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
n = cols[:count]
|
|
81
|
+
kinds = cols[:kinds].read_bytes(n).unpack("C*")
|
|
82
|
+
tags = cols[:tags].read_bytes(n).unpack("C*")
|
|
83
|
+
ikeys = cols[:is_keys].read_bytes(n).unpack("C*")
|
|
84
|
+
bools = cols[:bools].read_bytes(n).unpack("C*")
|
|
85
|
+
offs = cols[:offs].read_bytes(n * 4).unpack("V*")
|
|
86
|
+
lens = cols[:lens].read_bytes(n * 4).unpack("V*")
|
|
87
|
+
pays = cols[:payloads].read_bytes(n * 8).unpack("q<*")
|
|
88
|
+
arena_bytes = cols[:arena].read_bytes(cols[:arena_len])
|
|
89
|
+
arena_bytes.force_encoding(Encoding::UTF_8)
|
|
90
|
+
walk_columns(kinds, tags, ikeys, bools, offs, lens, pays, arena_bytes,
|
|
91
|
+
schema == :compat_11)
|
|
92
|
+
ensure
|
|
93
|
+
FFI.yeptris_value_free_columns(cols)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The Marshal fast path (TODO.restructure/21): the C side emits
|
|
98
|
+
# Ruby Marshal 4.8 bytes; one Marshal.load call (a core-C routine)
|
|
99
|
+
# materializes the whole object graph. ~10x faster than walking
|
|
100
|
+
# the columns in pure Ruby on JSON-shaped input; ~5x on YAML. Falls
|
|
101
|
+
# back to load_all_columns on the constructs the format cannot
|
|
102
|
+
# express (merge keys, timestamps — rare; the record walk handles
|
|
103
|
+
# them). Feature-detected: older libyeptris returns the walk.
|
|
104
|
+
def load_all_marshal(yaml, schema: :compat_11, mode: :first)
|
|
105
|
+
yaml = Yeptris.read_input(yaml)
|
|
106
|
+
yaml = yaml.to_s
|
|
107
|
+
out_p = ::FFI::MemoryPointer.new(:pointer)
|
|
108
|
+
olen_p = ::FFI::MemoryPointer.new(:size_t)
|
|
109
|
+
mode_id = mode == :all ? FFI::MARSHAL_ALL_DOCS : FFI::MARSHAL_FIRST_DOC
|
|
110
|
+
st = FFI.yeptris_marshal(
|
|
111
|
+
yaml, yaml.bytesize,
|
|
112
|
+
schema == :compat_11 ? FFI::SCHEMA_11_COMPAT : FFI::SCHEMA_12_CORE,
|
|
113
|
+
mode_id, out_p, olen_p
|
|
114
|
+
)
|
|
115
|
+
return nil if st == FFI::ERROR_UNSUPPORTED
|
|
116
|
+
raise ParseError, FFI.last_error_message if st != FFI::OK
|
|
117
|
+
buf = out_p.read_pointer
|
|
118
|
+
len = olen_p.read_uint64
|
|
119
|
+
bytes = buf.read_bytes(len)
|
|
120
|
+
bytes.force_encoding(Encoding::ASCII_8BIT)
|
|
121
|
+
::Marshal.load(bytes)
|
|
122
|
+
ensure
|
|
123
|
+
FFI.yeptris_marshal_free(buf) if buf && !buf.null?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def load(yaml, schema: :compat_11)
|
|
127
|
+
docs = load_all(yaml, schema: schema)
|
|
128
|
+
docs.empty? ? nil : docs.first
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# field offsets in the unpacked 7-tuple
|
|
132
|
+
KIND = 0
|
|
133
|
+
TAG = 1
|
|
134
|
+
IS_KEY = 2
|
|
135
|
+
B = 3
|
|
136
|
+
OFF = 4
|
|
137
|
+
LEN = 5
|
|
138
|
+
P64 = 6
|
|
139
|
+
|
|
140
|
+
def walk(flat, arena, compat = true)
|
|
141
|
+
docs = []
|
|
142
|
+
stack = []
|
|
143
|
+
anchors = {}
|
|
144
|
+
pending_key = []
|
|
145
|
+
pending_key_tag = []
|
|
146
|
+
pending_anchor = nil
|
|
147
|
+
merge_target = []
|
|
148
|
+
|
|
149
|
+
i = 0
|
|
150
|
+
n = flat.length
|
|
151
|
+
while i < n
|
|
152
|
+
kind = flat[i + KIND]
|
|
153
|
+
case kind
|
|
154
|
+
when V_STR
|
|
155
|
+
text = arena.byteslice(flat[i + OFF], flat[i + LEN])
|
|
156
|
+
# implicit-plain ':name' scans to a Symbol (Psych's
|
|
157
|
+
# ScalarScanner); quoted ':x' stays a String
|
|
158
|
+
v = if flat[i + B] == 1 && text.length > 18 &&
|
|
159
|
+
PSYCH_INT_SHAPE.match?(text)
|
|
160
|
+
text.delete(",_").to_i
|
|
161
|
+
elsif flat[i + B] == 1 && text.length > 1 &&
|
|
162
|
+
text.start_with?(":") && !text.start_with?("::")
|
|
163
|
+
text[1..].to_sym
|
|
164
|
+
else
|
|
165
|
+
text
|
|
166
|
+
end
|
|
167
|
+
if pending_anchor
|
|
168
|
+
anchors[pending_anchor] = v
|
|
169
|
+
pending_anchor = nil
|
|
170
|
+
end
|
|
171
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
172
|
+
when V_INT
|
|
173
|
+
v = flat[i + P64]
|
|
174
|
+
if pending_anchor
|
|
175
|
+
anchors[pending_anchor] = v
|
|
176
|
+
pending_anchor = nil
|
|
177
|
+
end
|
|
178
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
179
|
+
when V_FLOAT
|
|
180
|
+
text = arena.byteslice(flat[i + OFF], flat[i + LEN])
|
|
181
|
+
# Psych's float grammar requires the dot (or an inf/nan
|
|
182
|
+
# word, or sexagesimal ':') — exponent-only forms are
|
|
183
|
+
# Strings even when the compat tag says FLOAT
|
|
184
|
+
v = if !compat || text.include?(".") || text.include?(":") || text.start_with?(".")
|
|
185
|
+
[flat[i + P64]].pack("q<").unpack1("E")
|
|
186
|
+
else
|
|
187
|
+
text
|
|
188
|
+
end
|
|
189
|
+
if pending_anchor
|
|
190
|
+
anchors[pending_anchor] = v
|
|
191
|
+
pending_anchor = nil
|
|
192
|
+
end
|
|
193
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
194
|
+
when V_BOOL
|
|
195
|
+
text = arena.byteslice(flat[i + OFF], flat[i + LEN])
|
|
196
|
+
# Psych quirk: single-char y/n stay Strings
|
|
197
|
+
v = text.length == 1 ? text : flat[i + B] == 1
|
|
198
|
+
if pending_anchor
|
|
199
|
+
anchors[pending_anchor] = v
|
|
200
|
+
pending_anchor = nil
|
|
201
|
+
end
|
|
202
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
203
|
+
when V_NULL
|
|
204
|
+
# the pending anchor binds to the null (an anchored empty
|
|
205
|
+
# scalar) — without this it would leak onto the next value
|
|
206
|
+
if pending_anchor
|
|
207
|
+
anchors[pending_anchor] = nil
|
|
208
|
+
pending_anchor = nil
|
|
209
|
+
end
|
|
210
|
+
slot(docs, stack, pending_key, pending_key_tag, nil, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
211
|
+
when V_TS
|
|
212
|
+
v = Materializer.parse_timestamp(arena.byteslice(flat[i + OFF], flat[i + LEN]))
|
|
213
|
+
if pending_anchor
|
|
214
|
+
anchors[pending_anchor] = v
|
|
215
|
+
pending_anchor = nil
|
|
216
|
+
end
|
|
217
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
218
|
+
when MAP_OPEN
|
|
219
|
+
h = {}
|
|
220
|
+
if pending_anchor
|
|
221
|
+
anchors[pending_anchor] = h
|
|
222
|
+
pending_anchor = nil
|
|
223
|
+
end
|
|
224
|
+
merge_target.push(slot(docs, stack, pending_key, pending_key_tag, h, merge_target,
|
|
225
|
+
flat[i + IS_KEY], flat[i + TAG]))
|
|
226
|
+
stack.push(h)
|
|
227
|
+
pending_key.push(nil)
|
|
228
|
+
pending_key_tag.push(nil)
|
|
229
|
+
when SEQ_OPEN
|
|
230
|
+
a = []
|
|
231
|
+
if pending_anchor
|
|
232
|
+
anchors[pending_anchor] = a
|
|
233
|
+
pending_anchor = nil
|
|
234
|
+
end
|
|
235
|
+
merge_target.push(slot(docs, stack, pending_key, pending_key_tag, a, merge_target,
|
|
236
|
+
flat[i + IS_KEY], flat[i + TAG]))
|
|
237
|
+
stack.push(a)
|
|
238
|
+
pending_key.push(nil)
|
|
239
|
+
pending_key_tag.push(nil)
|
|
240
|
+
when CLOSE
|
|
241
|
+
closed = stack.pop
|
|
242
|
+
pending_key.pop
|
|
243
|
+
pending_key_tag.pop
|
|
244
|
+
target = merge_target.pop
|
|
245
|
+
Materializer.merge_into(target, closed) if target
|
|
246
|
+
when DOC
|
|
247
|
+
docs.push(nil)
|
|
248
|
+
when ALIAS
|
|
249
|
+
v = anchors[arena.byteslice(flat[i + OFF], flat[i + LEN])]
|
|
250
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
251
|
+
when ANCHOR
|
|
252
|
+
pending_anchor = arena.byteslice(flat[i + OFF], flat[i + LEN])
|
|
253
|
+
end
|
|
254
|
+
i += FIELDS
|
|
255
|
+
end
|
|
256
|
+
docs
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# The columnar twin of walk above (lockstep: same semantics, same
|
|
260
|
+
# order; fields come from tight per-kind arrays, stride 1).
|
|
261
|
+
def walk_columns(kinds, tags, ikeys, bools, offs, lens, pays, arena, compat = true)
|
|
262
|
+
docs = []
|
|
263
|
+
stack = []
|
|
264
|
+
anchors = {}
|
|
265
|
+
pending_key = []
|
|
266
|
+
pending_key_tag = []
|
|
267
|
+
pending_anchor = nil
|
|
268
|
+
merge_target = []
|
|
269
|
+
|
|
270
|
+
i = 0
|
|
271
|
+
n = kinds.length
|
|
272
|
+
while i < n
|
|
273
|
+
# the dominant shape: a str:str pair inside one map — place
|
|
274
|
+
# both directly, same conversions as the STR arm (the ':sym'
|
|
275
|
+
# scan rides is_key's text; anchors/merges fall through)
|
|
276
|
+
if kinds[i] == V_STR && ikeys[i] == 1 && i + 1 < n &&
|
|
277
|
+
kinds[i + 1] == V_STR && ikeys[i + 1] == 0 &&
|
|
278
|
+
pending_anchor.nil? && !stack.empty? && stack.last.is_a?(Hash)
|
|
279
|
+
kt = arena.byteslice(offs[i], lens[i])
|
|
280
|
+
if kt != "<<"
|
|
281
|
+
key = if bools[i] == 1 && kt.length > 1 && kt.start_with?(":") &&
|
|
282
|
+
!kt.start_with?("::")
|
|
283
|
+
kt[1..].to_sym
|
|
284
|
+
else
|
|
285
|
+
kt
|
|
286
|
+
end
|
|
287
|
+
vt = arena.byteslice(offs[i + 1], lens[i + 1])
|
|
288
|
+
stack.last[key] =
|
|
289
|
+
if bools[i + 1] == 1 && vt.length > 1 && vt.start_with?(":") &&
|
|
290
|
+
!vt.start_with?("::")
|
|
291
|
+
vt[1..].to_sym
|
|
292
|
+
else
|
|
293
|
+
vt
|
|
294
|
+
end
|
|
295
|
+
i += 2
|
|
296
|
+
next
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
case kinds[i]
|
|
300
|
+
when V_STR
|
|
301
|
+
text = arena.byteslice(offs[i], lens[i])
|
|
302
|
+
v = if bools[i] == 1 && text.length > 18 &&
|
|
303
|
+
PSYCH_INT_SHAPE.match?(text)
|
|
304
|
+
text.delete(",_").to_i
|
|
305
|
+
elsif bools[i] == 1 && text.length > 1 &&
|
|
306
|
+
text.start_with?(":") && !text.start_with?("::")
|
|
307
|
+
text[1..].to_sym
|
|
308
|
+
else
|
|
309
|
+
text
|
|
310
|
+
end
|
|
311
|
+
if pending_anchor
|
|
312
|
+
anchors[pending_anchor] = v
|
|
313
|
+
pending_anchor = nil
|
|
314
|
+
end
|
|
315
|
+
# inline the dominant placement (Hash parent, value slot,
|
|
316
|
+
# non-merge key): a slot() call per pair was measurable
|
|
317
|
+
if !stack.empty?
|
|
318
|
+
parent = stack.last
|
|
319
|
+
if parent.is_a?(Hash) && ikeys[i] == 0 && (k = pending_key[-1]) && k != "<<"
|
|
320
|
+
parent[k] = v
|
|
321
|
+
pending_key[-1] = nil
|
|
322
|
+
else
|
|
323
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
|
|
324
|
+
end
|
|
325
|
+
else
|
|
326
|
+
docs[-1] = v
|
|
327
|
+
end
|
|
328
|
+
when V_INT
|
|
329
|
+
v = pays[i]
|
|
330
|
+
if pending_anchor
|
|
331
|
+
anchors[pending_anchor] = v
|
|
332
|
+
pending_anchor = nil
|
|
333
|
+
end
|
|
334
|
+
if !stack.empty?
|
|
335
|
+
parent = stack.last
|
|
336
|
+
if parent.is_a?(Hash) && ikeys[i] == 0 && (k = pending_key[-1]) && k != "<<"
|
|
337
|
+
parent[k] = v
|
|
338
|
+
pending_key[-1] = nil
|
|
339
|
+
else
|
|
340
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
|
|
341
|
+
end
|
|
342
|
+
else
|
|
343
|
+
docs[-1] = v
|
|
344
|
+
end
|
|
345
|
+
when V_FLOAT
|
|
346
|
+
text = arena.byteslice(offs[i], lens[i])
|
|
347
|
+
v = if !compat || text.include?(".") || text.include?(":") || text.start_with?(".")
|
|
348
|
+
[pays[i]].pack("q<").unpack1("E")
|
|
349
|
+
else
|
|
350
|
+
text
|
|
351
|
+
end
|
|
352
|
+
if pending_anchor
|
|
353
|
+
anchors[pending_anchor] = v
|
|
354
|
+
pending_anchor = nil
|
|
355
|
+
end
|
|
356
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
|
|
357
|
+
when V_BOOL
|
|
358
|
+
text = arena.byteslice(offs[i], lens[i])
|
|
359
|
+
v = text.length == 1 ? text : bools[i] == 1
|
|
360
|
+
if pending_anchor
|
|
361
|
+
anchors[pending_anchor] = v
|
|
362
|
+
pending_anchor = nil
|
|
363
|
+
end
|
|
364
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
|
|
365
|
+
when V_NULL
|
|
366
|
+
if pending_anchor
|
|
367
|
+
anchors[pending_anchor] = nil
|
|
368
|
+
pending_anchor = nil
|
|
369
|
+
end
|
|
370
|
+
slot(docs, stack, pending_key, pending_key_tag, nil, merge_target, ikeys[i], tags[i])
|
|
371
|
+
when V_TS
|
|
372
|
+
v = Materializer.parse_timestamp(arena.byteslice(offs[i], lens[i]))
|
|
373
|
+
if pending_anchor
|
|
374
|
+
anchors[pending_anchor] = v
|
|
375
|
+
pending_anchor = nil
|
|
376
|
+
end
|
|
377
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
|
|
378
|
+
when MAP_OPEN
|
|
379
|
+
h = {}
|
|
380
|
+
if pending_anchor
|
|
381
|
+
anchors[pending_anchor] = h
|
|
382
|
+
pending_anchor = nil
|
|
383
|
+
end
|
|
384
|
+
merge_target.push(slot(docs, stack, pending_key, pending_key_tag, h, merge_target,
|
|
385
|
+
ikeys[i], tags[i]))
|
|
386
|
+
stack.push(h)
|
|
387
|
+
pending_key.push(nil)
|
|
388
|
+
pending_key_tag.push(nil)
|
|
389
|
+
when SEQ_OPEN
|
|
390
|
+
a = []
|
|
391
|
+
if pending_anchor
|
|
392
|
+
anchors[pending_anchor] = a
|
|
393
|
+
pending_anchor = nil
|
|
394
|
+
end
|
|
395
|
+
merge_target.push(slot(docs, stack, pending_key, pending_key_tag, a, merge_target,
|
|
396
|
+
ikeys[i], tags[i]))
|
|
397
|
+
stack.push(a)
|
|
398
|
+
pending_key.push(nil)
|
|
399
|
+
pending_key_tag.push(nil)
|
|
400
|
+
when CLOSE
|
|
401
|
+
closed = stack.pop
|
|
402
|
+
pending_key.pop
|
|
403
|
+
pending_key_tag.pop
|
|
404
|
+
target = merge_target.pop
|
|
405
|
+
Materializer.merge_into(target, closed) if target
|
|
406
|
+
when DOC
|
|
407
|
+
docs.push(nil)
|
|
408
|
+
when ALIAS
|
|
409
|
+
v = anchors[arena.byteslice(offs[i], lens[i])]
|
|
410
|
+
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
|
|
411
|
+
when ANCHOR
|
|
412
|
+
pending_anchor = arena.byteslice(offs[i], lens[i])
|
|
413
|
+
end
|
|
414
|
+
i += 1
|
|
415
|
+
end
|
|
416
|
+
docs
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Places a completed value: document root, sequence entry, or a
|
|
420
|
+
# map's key (is_key) / value (completing the pending pair).
|
|
421
|
+
# Returns the merge TARGET when the value is a still-empty
|
|
422
|
+
# container placed under a '<<' key — the open-site caller pushes
|
|
423
|
+
# it so the matching CLOSE merges into it (an inline map's
|
|
424
|
+
# contents arrive after its open); scalar merges apply now.
|
|
425
|
+
def slot(docs, stack, pending_key, pending_key_tag, v, _merge_target, is_key, tag)
|
|
426
|
+
if stack.empty?
|
|
427
|
+
docs[-1] = v
|
|
428
|
+
return nil
|
|
429
|
+
end
|
|
430
|
+
parent = stack.last
|
|
431
|
+
if parent.is_a?(Array)
|
|
432
|
+
parent.push(v)
|
|
433
|
+
return nil
|
|
434
|
+
end
|
|
435
|
+
if is_key == 1
|
|
436
|
+
pending_key[-1] = v
|
|
437
|
+
pending_key_tag[-1] = tag
|
|
438
|
+
return nil
|
|
439
|
+
end
|
|
440
|
+
key = pending_key[-1]
|
|
441
|
+
pending_key[-1] = nil
|
|
442
|
+
if key == "<<" && pending_key_tag[-1] == 9 # TAG_MERGE
|
|
443
|
+
if (v.is_a?(Hash) || v.is_a?(Array)) && v.empty?
|
|
444
|
+
parent # deferred: contents arrive after the open
|
|
445
|
+
else
|
|
446
|
+
Materializer.merge_into(parent, v)
|
|
447
|
+
nil
|
|
448
|
+
end
|
|
449
|
+
else
|
|
450
|
+
parent[key] = v
|
|
451
|
+
nil
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|