yeptris 0.1.0 → 0.1.1.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: 1f9152c76936119e3fcc83cc8510e15debf741cc25d22229b225d1023f330cec
4
- data.tar.gz: 6845a69bcb669246e5445601e117a9abfcd96200476fcfd1c41e9b5040775615
3
+ metadata.gz: 321301694ee81cae03622b737dce8d9603d6fffa87cacc280967c3bf7e9e397b
4
+ data.tar.gz: '09892fb7c54261dc8d46c5354a1c55052e6bf1333552bc7632e36cd433706c66'
5
5
  SHA512:
6
- metadata.gz: 1927807c5fbb83fbc986d17a35ed35e6c888b52e045482e1cd96d4733b8aff7a9aa6238dd1b11c80c3f9e2da795b81eaa01c75b11861acffff64ca1bfbdb579b
7
- data.tar.gz: b85ef72350379c4a6e1b5b9ca8fc76aaf0901ea533c05c2dca6223b1eb48679faefe94880bb3d2eb8df6f57dd584c82777af3cffaa37fe3274467b58d562c1b0
6
+ metadata.gz: 2d3e550bfe02ed9abff59332131fa13ae93e0e4ab7bf46b4cbcf8f57670d7592ad2494b2c9aa17c331a504ab3fdca48274bf585cc429a282a8c4d507fd3c5a04
7
+ data.tar.gz: b66b1001c328be6081ddbd53d790a9fbfbff4d4881da5ee74edea0d2065cb5266d876cdc1f3bb4ddcacbc6c41b2087661218f2fb11cd4763136a380073dfbbe2
data/lib/yeptris/ffi.rb CHANGED
@@ -118,6 +118,24 @@ module Yeptris
118
118
  %i[pointer size_t int pointer pointer pointer pointer], :int
119
119
  attach_function :yeptris_value_free, %i[pointer pointer], :void
120
120
 
121
+ # Columnar drain (libyeptris > 0.1.1): the same stream as parallel
122
+ # typed buffers, one carved allocation. Feature-detected — the
123
+ # record API is the fallback on older libraries.
124
+ COLUMNS = begin
125
+ attach_function :yeptris_value_drain_columns, %i[pointer size_t int pointer], :int
126
+ attach_function :yeptris_value_free_columns, [:pointer], :void
127
+ true
128
+ rescue ::FFI::NotFoundError
129
+ false
130
+ end
131
+
132
+ class ValueColumns < ::FFI::Struct
133
+ layout :count, :size_t, :arena_len, :size_t,
134
+ :payloads, :pointer, :offs, :pointer, :lens, :pointer,
135
+ :kinds, :pointer, :tags, :pointer, :is_keys, :pointer,
136
+ :bools, :pointer, :arena, :pointer
137
+ end
138
+
121
139
  # bulk build (TODO.impl/15 phase D): one call raises a document
122
140
  BUILD_SCALAR = 1
123
141
  BUILD_SEQ = 2
@@ -55,6 +55,38 @@ module Yeptris
55
55
  end
56
56
  end
57
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 = yaml.read if yaml.respond_to?(:read)
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
+
58
90
  def load(yaml, schema: :compat_11)
59
91
  docs = load_all(yaml, schema: schema)
60
92
  docs.empty? ? nil : docs.first
@@ -97,14 +129,14 @@ module Yeptris
97
129
  anchors[pending_anchor] = v
98
130
  pending_anchor = nil
99
131
  end
100
- slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat, i)
132
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
101
133
  when V_INT
102
134
  v = flat[i + P64]
103
135
  if pending_anchor
104
136
  anchors[pending_anchor] = v
105
137
  pending_anchor = nil
106
138
  end
107
- slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat, i)
139
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
108
140
  when V_FLOAT
109
141
  text = arena.byteslice(flat[i + OFF], flat[i + LEN])
110
142
  # Psych's float grammar requires the dot (or an inf/nan
@@ -119,7 +151,7 @@ module Yeptris
119
151
  anchors[pending_anchor] = v
120
152
  pending_anchor = nil
121
153
  end
122
- slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat, i)
154
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
123
155
  when V_BOOL
124
156
  text = arena.byteslice(flat[i + OFF], flat[i + LEN])
125
157
  # Psych quirk: single-char y/n stay Strings
@@ -128,16 +160,16 @@ module Yeptris
128
160
  anchors[pending_anchor] = v
129
161
  pending_anchor = nil
130
162
  end
131
- slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat, i)
163
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
132
164
  when V_NULL
133
- slot(docs, stack, pending_key, pending_key_tag, nil, merge_target, flat, i)
165
+ slot(docs, stack, pending_key, pending_key_tag, nil, merge_target, flat[i + IS_KEY], flat[i + TAG])
134
166
  when V_TS
135
167
  v = Materializer.parse_timestamp(arena.byteslice(flat[i + OFF], flat[i + LEN]))
136
168
  if pending_anchor
137
169
  anchors[pending_anchor] = v
138
170
  pending_anchor = nil
139
171
  end
140
- slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat, i)
172
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
141
173
  when MAP_OPEN
142
174
  h = {}
143
175
  if pending_anchor
@@ -145,7 +177,7 @@ module Yeptris
145
177
  pending_anchor = nil
146
178
  end
147
179
  merge_target.push(slot(docs, stack, pending_key, pending_key_tag, h, merge_target,
148
- flat, i))
180
+ flat[i + IS_KEY], flat[i + TAG]))
149
181
  stack.push(h)
150
182
  pending_key.push(nil)
151
183
  pending_key_tag.push(nil)
@@ -156,7 +188,7 @@ module Yeptris
156
188
  pending_anchor = nil
157
189
  end
158
190
  merge_target.push(slot(docs, stack, pending_key, pending_key_tag, a, merge_target,
159
- flat, i))
191
+ flat[i + IS_KEY], flat[i + TAG]))
160
192
  stack.push(a)
161
193
  pending_key.push(nil)
162
194
  pending_key_tag.push(nil)
@@ -170,7 +202,7 @@ module Yeptris
170
202
  docs.push(nil)
171
203
  when ALIAS
172
204
  v = anchors[arena.byteslice(flat[i + OFF], flat[i + LEN])]
173
- slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat, i)
205
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
174
206
  when ANCHOR
175
207
  pending_anchor = arena.byteslice(flat[i + OFF], flat[i + LEN])
176
208
  end
@@ -179,13 +211,140 @@ module Yeptris
179
211
  docs
180
212
  end
181
213
 
214
+ # The columnar twin of walk above (lockstep: same semantics, same
215
+ # order; fields come from tight per-kind arrays, stride 1).
216
+ def walk_columns(kinds, tags, ikeys, bools, offs, lens, pays, arena)
217
+ docs = []
218
+ stack = []
219
+ anchors = {}
220
+ pending_key = []
221
+ pending_key_tag = []
222
+ pending_anchor = nil
223
+ merge_target = []
224
+
225
+ i = 0
226
+ n = kinds.length
227
+ while i < n
228
+ case kinds[i]
229
+ when V_STR
230
+ text = arena.byteslice(offs[i], lens[i])
231
+ v = if bools[i] == 1 && text.length > 1 &&
232
+ text.start_with?(":") && !text.start_with?("::")
233
+ text[1..].to_sym
234
+ else
235
+ text
236
+ end
237
+ if pending_anchor
238
+ anchors[pending_anchor] = v
239
+ pending_anchor = nil
240
+ end
241
+ # inline the dominant placement (Hash parent, value slot,
242
+ # non-merge key): a slot() call per pair was measurable
243
+ if !stack.empty?
244
+ parent = stack.last
245
+ if parent.is_a?(Hash) && ikeys[i] == 0 && (k = pending_key[-1]) && k != "<<"
246
+ parent[k] = v
247
+ pending_key[-1] = nil
248
+ else
249
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
250
+ end
251
+ else
252
+ docs[-1] = v
253
+ end
254
+ when V_INT
255
+ v = pays[i]
256
+ if pending_anchor
257
+ anchors[pending_anchor] = v
258
+ pending_anchor = nil
259
+ end
260
+ if !stack.empty?
261
+ parent = stack.last
262
+ if parent.is_a?(Hash) && ikeys[i] == 0 && (k = pending_key[-1]) && k != "<<"
263
+ parent[k] = v
264
+ pending_key[-1] = nil
265
+ else
266
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
267
+ end
268
+ else
269
+ docs[-1] = v
270
+ end
271
+ when V_FLOAT
272
+ text = arena.byteslice(offs[i], lens[i])
273
+ v = if text.include?(".") || text.include?(":") || text.start_with?(".")
274
+ [pays[i]].pack("q<").unpack1("E")
275
+ else
276
+ text
277
+ end
278
+ if pending_anchor
279
+ anchors[pending_anchor] = v
280
+ pending_anchor = nil
281
+ end
282
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
283
+ when V_BOOL
284
+ text = arena.byteslice(offs[i], lens[i])
285
+ v = text.length == 1 ? text : bools[i] == 1
286
+ if pending_anchor
287
+ anchors[pending_anchor] = v
288
+ pending_anchor = nil
289
+ end
290
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
291
+ when V_NULL
292
+ slot(docs, stack, pending_key, pending_key_tag, nil, merge_target, ikeys[i], tags[i])
293
+ when V_TS
294
+ v = Materializer.parse_timestamp(arena.byteslice(offs[i], lens[i]))
295
+ if pending_anchor
296
+ anchors[pending_anchor] = v
297
+ pending_anchor = nil
298
+ end
299
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
300
+ when MAP_OPEN
301
+ h = {}
302
+ if pending_anchor
303
+ anchors[pending_anchor] = h
304
+ pending_anchor = nil
305
+ end
306
+ merge_target.push(slot(docs, stack, pending_key, pending_key_tag, h, merge_target,
307
+ ikeys[i], tags[i]))
308
+ stack.push(h)
309
+ pending_key.push(nil)
310
+ pending_key_tag.push(nil)
311
+ when SEQ_OPEN
312
+ a = []
313
+ if pending_anchor
314
+ anchors[pending_anchor] = a
315
+ pending_anchor = nil
316
+ end
317
+ merge_target.push(slot(docs, stack, pending_key, pending_key_tag, a, merge_target,
318
+ ikeys[i], tags[i]))
319
+ stack.push(a)
320
+ pending_key.push(nil)
321
+ pending_key_tag.push(nil)
322
+ when CLOSE
323
+ closed = stack.pop
324
+ pending_key.pop
325
+ pending_key_tag.pop
326
+ target = merge_target.pop
327
+ Materializer.merge_into(target, closed) if target
328
+ when DOC
329
+ docs.push(nil)
330
+ when ALIAS
331
+ v = anchors[arena.byteslice(offs[i], lens[i])]
332
+ slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
333
+ when ANCHOR
334
+ pending_anchor = arena.byteslice(offs[i], lens[i])
335
+ end
336
+ i += 1
337
+ end
338
+ docs
339
+ end
340
+
182
341
  # Places a completed value: document root, sequence entry, or a
183
342
  # map's key (is_key) / value (completing the pending pair).
184
343
  # Returns the merge TARGET when the value is a still-empty
185
344
  # container placed under a '<<' key — the open-site caller pushes
186
345
  # it so the matching CLOSE merges into it (an inline map's
187
346
  # contents arrive after its open); scalar merges apply now.
188
- def slot(docs, stack, pending_key, pending_key_tag, v, _merge_target, flat, i)
347
+ def slot(docs, stack, pending_key, pending_key_tag, v, _merge_target, is_key, tag)
189
348
  if stack.empty?
190
349
  docs[-1] = v
191
350
  return nil
@@ -195,9 +354,9 @@ module Yeptris
195
354
  parent.push(v)
196
355
  return nil
197
356
  end
198
- if flat[i + IS_KEY] == 1
357
+ if is_key == 1
199
358
  pending_key[-1] = v
200
- pending_key_tag[-1] = flat[i + TAG]
359
+ pending_key_tag[-1] = tag
201
360
  return nil
202
361
  end
203
362
  key = pending_key[-1]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yeptris
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1.1"
5
5
  end
data/lib/yeptris/yaml.rb CHANGED
@@ -11,12 +11,24 @@ module Yeptris
11
11
  # schema: :compat_11 selects Psych/libyaml implicit typing
12
12
  # (yes/no, 0o/octal, sexagesimal); :core_12 (default) is YAML 1.2.
13
13
  def load(yaml, schema: :compat_11)
14
- ValueML.load(yaml, schema: schema)
14
+ docs = _drain_all(yaml, schema)
15
+ docs.empty? ? nil : docs.first
15
16
  end
16
17
 
17
18
  # Every document in the stream, in order.
18
19
  def load_stream(yaml, schema: :compat_11)
19
- ValueML.load_all(yaml, schema: schema)
20
+ _drain_all(yaml, schema)
21
+ end
22
+
23
+ # The columnar drain when the loaded libyeptris has it (>= 0.1.2
24
+ # era builds), the record drain otherwise — one code path, the
25
+ # fastest the library offers.
26
+ def _drain_all(yaml, schema)
27
+ if FFI::COLUMNS
28
+ ValueML.load_all_columns(yaml, schema: schema)
29
+ else
30
+ ValueML.load_all(yaml, schema: schema)
31
+ end
20
32
  end
21
33
 
22
34
  # The Psych-suite port's spelling (spec/psych/): compat typing.
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.1.0
4
+ version: 0.1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.