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