yeptris 0.1.10.0 → 0.1.11.0
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/README.adoc +23 -10
- data/lib/yeptris/ffi.rb +22 -0
- data/lib/yeptris/node.rb +38 -4
- data/lib/yeptris/valueml.rb +39 -0
- data/lib/yeptris/yaml.rb +7 -3
- data/lib/yeptris.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2ddefc4cfb21c80a7238bbca6cfa46293fd0f3ff920a122e37bd6f4e3f78b0e
|
|
4
|
+
data.tar.gz: cfbfe8da20325e11ef811dd9372e464fd1e03a1c0eff21cae254f918c9f5c979
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76f113166f28712bb328a7daf7f5b15f37f06c4ca5ce24dc56651878bded33800e3befbefed6b66f4da0a28a94c595797fb41004572d807a5ebca93a342e1cc6
|
|
7
|
+
data.tar.gz: 273c6e45ff73043c14c24869b3d98207710514dcd055a939d0fa4c5832d133bf5d14686ba9259c712115b4034cb5785c9af6aaa9bbabf929315dd43c313cf997
|
data/README.adoc
CHANGED
|
@@ -7,8 +7,9 @@ compilation at install.
|
|
|
7
7
|
|
|
8
8
|
== Install (development)
|
|
9
9
|
|
|
10
|
-
The
|
|
11
|
-
|
|
10
|
+
The gem loads a shared `libyeptris` — from `YEPTRIS_LIB_PATH`, a
|
|
11
|
+
vendored `lib/platform/<tag>/` copy, or the system paths. For
|
|
12
|
+
development against a local build:
|
|
12
13
|
|
|
13
14
|
....
|
|
14
15
|
# the sibling C checkout: ~/src/leptris/yeptris
|
|
@@ -32,7 +33,9 @@ require "yeptris"
|
|
|
32
33
|
Yeptris::YAML.load("name: yeptris\nrating: 10\n")
|
|
33
34
|
# => {"name" => "yeptris", "rating" => 10}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
# brace mixed hashes on Ruby 3.x: a trailing symbol key otherwise
|
|
37
|
+
# splits the literal into keywords
|
|
38
|
+
Yeptris::YAML.dump({"name" => "yeptris", "tags" => [:yaml, :fast]})
|
|
36
39
|
|
|
37
40
|
doc = Yeptris::Document.parse(config_yaml)
|
|
38
41
|
doc.root["server"]["port"].to_i
|
|
@@ -49,11 +52,21 @@ Handles are document-scoped: `Document#free` releases everything
|
|
|
49
52
|
(one C call), a GC finalizer backs it up, and any use after free
|
|
50
53
|
raises `Yeptris::FreedError` — never a segfault.
|
|
51
54
|
|
|
52
|
-
==
|
|
55
|
+
== Shipped beyond the original plan
|
|
53
56
|
|
|
54
|
-
*
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
* The columnar value drain (bulk, pre-converted typed columns — the
|
|
58
|
+
load fast path) and the bulk DOM builder (one `document_build`
|
|
59
|
+
call per dump).
|
|
60
|
+
* **Ruby Marshal 4.8 emission** (libyeptris >= 0.1.11): the C side
|
|
61
|
+
converts value records into Marshal bytes; one `Marshal.load`
|
|
62
|
+
materializes the whole graph. ~10× faster than the columnar walk
|
|
63
|
+
on JSON-shaped input, ~5× on YAML, ~50× on the per-node DOM walk
|
|
64
|
+
(`Node#to_ruby` becomes bulk). Alias identity preserved through
|
|
65
|
+
object links; merge keys and timestamps return UNSUPPORTED for the
|
|
66
|
+
record-walk fallback.
|
|
67
|
+
* `Yeptris::Psych` — the drop-in namespace with the ported Psych
|
|
68
|
+
suite (143 specs), `.tml` corpora conformance, and the object
|
|
69
|
+
visitor (`encode_with`/`init_with`).
|
|
70
|
+
* Lockstep versioning with the C library
|
|
71
|
+
(`{c-semver}.{gem-patch}`); releases published from the C repo's
|
|
72
|
+
workflow through the RubyGems trusted publisher.
|
data/lib/yeptris/ffi.rb
CHANGED
|
@@ -136,6 +136,25 @@ module Yeptris
|
|
|
136
136
|
:bools, :pointer, :arena, :pointer
|
|
137
137
|
end
|
|
138
138
|
|
|
139
|
+
# Marshal 4.8 emission (TODO.restructure/21): the C side converts
|
|
140
|
+
# value records directly into Ruby's wire format; the binding
|
|
141
|
+
# materializes the whole graph with one core-C Marshal.load call
|
|
142
|
+
# instead of walking per-value records in pure Ruby. Same feature-
|
|
143
|
+
# detect discipline as the columnar drain (older libraries fall
|
|
144
|
+
# back to the record walk).
|
|
145
|
+
MARSHAL = begin
|
|
146
|
+
MARSHAL_ALL_DOCS = 0
|
|
147
|
+
MARSHAL_FIRST_DOC = 1
|
|
148
|
+
attach_function :yeptris_marshal,
|
|
149
|
+
%i[pointer size_t int int pointer pointer], :int
|
|
150
|
+
attach_function :yeptris_marshal_node,
|
|
151
|
+
%i[yeptris_node pointer pointer], :int
|
|
152
|
+
attach_function :yeptris_marshal_free, [:pointer], :void
|
|
153
|
+
true
|
|
154
|
+
rescue ::FFI::NotFoundError
|
|
155
|
+
false
|
|
156
|
+
end
|
|
157
|
+
|
|
139
158
|
# bulk build (TODO.impl/15 phase D): one call raises a document
|
|
140
159
|
BUILD_SCALAR = 1
|
|
141
160
|
BUILD_SEQ = 2
|
|
@@ -229,6 +248,9 @@ module Yeptris
|
|
|
229
248
|
ERROR_MEMORY = 2
|
|
230
249
|
ERROR_DEPTH = 3
|
|
231
250
|
ERROR_ENCODING = 4
|
|
251
|
+
ERROR_IO = 5
|
|
232
252
|
ERROR_ARG = 6
|
|
253
|
+
ERROR_UNSUPPORTED = 7
|
|
254
|
+
ERROR_INTERNAL = 8
|
|
233
255
|
end
|
|
234
256
|
end
|
data/lib/yeptris/node.rb
CHANGED
|
@@ -243,14 +243,48 @@ class Yeptris::Node
|
|
|
243
243
|
end
|
|
244
244
|
|
|
245
245
|
def to_ruby(memo = nil)
|
|
246
|
-
# readonly documents memoize per node:
|
|
247
|
-
#
|
|
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).
|
|
248
249
|
if @document.readonly?
|
|
249
250
|
rm = @document.readonly_memo
|
|
250
251
|
cached = rm[node_id]
|
|
251
252
|
return cached if cached
|
|
252
|
-
|
|
253
|
-
|
|
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({})
|
|
254
288
|
end
|
|
255
289
|
to_ruby_walk(memo || {})
|
|
256
290
|
end
|
data/lib/yeptris/valueml.rb
CHANGED
|
@@ -87,6 +87,35 @@ module Yeptris
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
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
|
+
|
|
90
119
|
def load(yaml, schema: :compat_11)
|
|
91
120
|
docs = load_all(yaml, schema: schema)
|
|
92
121
|
docs.empty? ? nil : docs.first
|
|
@@ -162,6 +191,12 @@ module Yeptris
|
|
|
162
191
|
end
|
|
163
192
|
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
164
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
|
|
165
200
|
slot(docs, stack, pending_key, pending_key_tag, nil, merge_target, flat[i + IS_KEY], flat[i + TAG])
|
|
166
201
|
when V_TS
|
|
167
202
|
v = Materializer.parse_timestamp(arena.byteslice(flat[i + OFF], flat[i + LEN]))
|
|
@@ -315,6 +350,10 @@ module Yeptris
|
|
|
315
350
|
end
|
|
316
351
|
slot(docs, stack, pending_key, pending_key_tag, v, merge_target, ikeys[i], tags[i])
|
|
317
352
|
when V_NULL
|
|
353
|
+
if pending_anchor
|
|
354
|
+
anchors[pending_anchor] = nil
|
|
355
|
+
pending_anchor = nil
|
|
356
|
+
end
|
|
318
357
|
slot(docs, stack, pending_key, pending_key_tag, nil, merge_target, ikeys[i], tags[i])
|
|
319
358
|
when V_TS
|
|
320
359
|
v = Materializer.parse_timestamp(arena.byteslice(offs[i], lens[i]))
|
data/lib/yeptris/yaml.rb
CHANGED
|
@@ -20,10 +20,14 @@ module Yeptris
|
|
|
20
20
|
_drain_all(yaml, schema)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# The
|
|
24
|
-
# era builds),
|
|
25
|
-
# fastest the library offers.
|
|
23
|
+
# The Marshal fast path when the loaded libyeptris has it (>= 0.1.11
|
|
24
|
+
# era builds), falling back to the columnar drain and finally the
|
|
25
|
+
# record drain — one code path, the fastest the library offers.
|
|
26
26
|
def _drain_all(yaml, schema)
|
|
27
|
+
if FFI::MARSHAL
|
|
28
|
+
result = ValueML.load_all_marshal(yaml, schema: schema, mode: :all)
|
|
29
|
+
return result unless result.nil?
|
|
30
|
+
end
|
|
27
31
|
if FFI::COLUMNS
|
|
28
32
|
ValueML.load_all_columns(yaml, schema: schema)
|
|
29
33
|
else
|
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.1.
|
|
6
|
+
VERSION = "0.1.11.0".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
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yeptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|