yeptris 0.2.5.3-aarch64-linux → 0.4.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 +4 -4
- data/ext/build_windows_native.rb +41 -0
- data/lib/yeptris/ffi.rb +6 -2
- data/lib/yeptris/json.rb +104 -48
- data/lib/yeptris/native.so +0 -0
- data/lib/yeptris.rb +16 -3
- data/libyeptris.so +0 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0564531041910fb120994e2f4cac8c9f3ddf0b176bf5ae9ca3289cb58f6ac87
|
|
4
|
+
data.tar.gz: 4b6da94d1b0706fdfdd597bc173ca9556e26482a517b391670ef2bb1471f17df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 47c6f2bfe6920039b2e2e0a69f84688cac61bd11e61df76d1f9ec39ccfad227ff5a831b0b7bdbc644829c8dd364bd4b112b7b54183758dee182f66f325371350
|
|
7
|
+
data.tar.gz: 1e7e5b9bbe7b38b3db23b7c16bf03f3981178f6e52f2db99e091a5560c21c2a98a50823b9598bbeb4d93bb14302c34a3f1e5e2fe97eb0de5c3b8c6ac873cc36f
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Builds the native materializer DLL for the CURRENT Ruby and
|
|
4
|
+
# installs it under its minor-versioned name
|
|
5
|
+
# (lib/yeptris/native-<major.minor>.so).
|
|
6
|
+
#
|
|
7
|
+
# The leptris-ruby Windows lesson (#207/#227): a PE DLL cannot
|
|
8
|
+
# resolve Ruby imports lazily — it must bind the build Ruby's
|
|
9
|
+
# x64-ucrt-rubyNNN.dll. One DLL per supported minor therefore ships
|
|
10
|
+
# in the Windows platform gems, and the loader in lib/yeptris.rb
|
|
11
|
+
# picks by RUBY_VERSION at require. Ruby 3.3 has no arm64 build:
|
|
12
|
+
# that cell ships no artifact and falls back loudly to the FFI
|
|
13
|
+
# ladder.
|
|
14
|
+
#
|
|
15
|
+
# Standalone by design: the release workflow runs this under each
|
|
16
|
+
# Ruby minor (3.3/3.4/4.0) with no bundler context.
|
|
17
|
+
|
|
18
|
+
require "rbconfig"
|
|
19
|
+
require "fileutils"
|
|
20
|
+
|
|
21
|
+
root = File.expand_path("..", __dir__)
|
|
22
|
+
ext_dir = File.join(root, "ext", "yeptris_native")
|
|
23
|
+
minor = RUBY_VERSION[/\A\d+\.\d+/]
|
|
24
|
+
|
|
25
|
+
Dir.chdir(ext_dir) do
|
|
26
|
+
system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
|
|
27
|
+
# mkmf's link binds the CURRENT Ruby's runtime DLL — exactly
|
|
28
|
+
# what the versioned naming is for.
|
|
29
|
+
success = system("make")
|
|
30
|
+
abort "make failed under #{RUBY_VERSION}" unless success
|
|
31
|
+
so = Dir.glob("native.{so,dll}").first
|
|
32
|
+
abort "native bundle not produced under #{RUBY_VERSION}" unless so
|
|
33
|
+
dest = File.join(root, "lib", "yeptris", "native-#{minor}.so")
|
|
34
|
+
# The artifact must reference only this minor's Ruby DLL.
|
|
35
|
+
imported = `strings #{so} 2>/dev/null`[/[a-z0-9-]*ruby\d{3,}\.dll/i]
|
|
36
|
+
if imported && !imported.include?("ruby#{minor.delete('.')}")
|
|
37
|
+
abort "#{so} imports #{imported} but was built under #{RUBY_VERSION} — refuse to mis-name it"
|
|
38
|
+
end
|
|
39
|
+
FileUtils.cp(so, dest)
|
|
40
|
+
puts "Installed native materializer for Ruby #{minor} -> #{dest} (#{imported || 'no ruby dll string found'})"
|
|
41
|
+
end
|
data/lib/yeptris/ffi.rb
CHANGED
|
@@ -60,15 +60,19 @@ module Yeptris
|
|
|
60
60
|
attach_function :yeptris_parse_json, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
61
61
|
|
|
62
62
|
# The JSON tape (TODO.restructure/85; the JSON surface's load
|
|
63
|
-
# engine — issue #81): ONE call,
|
|
63
|
+
# engine — issue #81): ONE call, three bulk columns, spans borrow
|
|
64
64
|
# the caller's string (no arena copy, no second validating parse).
|
|
65
|
+
# v2 records: numbers are spans at parse; yeptris_tape_convert
|
|
66
|
+
# materializes them in one bulk call.
|
|
65
67
|
class JsonTape < ::FFI::Struct
|
|
66
68
|
layout :count, :size_t, :kinds, :pointer, :offs, :pointer,
|
|
67
|
-
:lens, :pointer, :
|
|
69
|
+
:lens, :pointer, :int_min, :int64, :_src, :pointer, :_srclen, :size_t,
|
|
70
|
+
:_block, :pointer
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
attach_function :yeptris_parse_json_tape, %i[pointer size_t pointer], :int
|
|
71
74
|
attach_function :yeptris_tape_free, [:pointer], :void
|
|
75
|
+
attach_function :yeptris_tape_convert, %i[pointer size_t size_t pointer pointer], :size_t
|
|
72
76
|
|
|
73
77
|
attach_function :yeptris_document_free, [:yeptris_document], :void
|
|
74
78
|
attach_function :yeptris_document_count, [:yeptris_document], :size_t
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -46,13 +46,63 @@ module Yeptris
|
|
|
46
46
|
# three, which is where medium/large throughput went).
|
|
47
47
|
T_DOC = 0
|
|
48
48
|
T_NULL = 1
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
T_TRUE = 2
|
|
50
|
+
T_FALSE = 3
|
|
51
|
+
T_INT = 4
|
|
52
|
+
T_FLOAT = 5
|
|
53
|
+
T_STR = 6
|
|
54
|
+
T_SEQ_OPEN = 7
|
|
55
|
+
T_MAP_OPEN = 8
|
|
56
|
+
T_CLOSE = 9
|
|
57
|
+
|
|
58
|
+
# The tape keeps RAW string spans (escapes untouched — the C
|
|
59
|
+
# contract); the walk decodes them here. The hot path (no
|
|
60
|
+
# backslash) returns the slice untouched.
|
|
61
|
+
SIMPLE_ESCAPES = {
|
|
62
|
+
'"' => '"'.b, "\\" => "\\".b, "/" => "/".b, "b" => "\b".b, "f" => "\f".b,
|
|
63
|
+
"n" => "\n".b, "r" => "\r".b, "t" => "\t".b
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
def decode_span(src, off, len)
|
|
67
|
+
s = src.byteslice(off, len)
|
|
68
|
+
s.force_encoding(Encoding::UTF_8)
|
|
69
|
+
return s unless s.include?("\\")
|
|
70
|
+
|
|
71
|
+
b = s.b
|
|
72
|
+
out = +"".b
|
|
73
|
+
i = 0
|
|
74
|
+
n = b.bytesize
|
|
75
|
+
while i < n
|
|
76
|
+
c = b.getbyte(i)
|
|
77
|
+
if c != 0x5C || i + 1 >= n
|
|
78
|
+
out << c
|
|
79
|
+
i += 1
|
|
80
|
+
next
|
|
81
|
+
end
|
|
82
|
+
e = b.getbyte(i + 1)
|
|
83
|
+
if e != 0x75 # simple escape
|
|
84
|
+
ch = e.chr
|
|
85
|
+
out << (SIMPLE_ESCAPES[ch] || ch)
|
|
86
|
+
i += 2
|
|
87
|
+
next
|
|
88
|
+
end
|
|
89
|
+
cp = b.byteslice(i + 2, 4).to_i(16)
|
|
90
|
+
if cp >= 0xD800 && cp <= 0xDBFF && i + 12 <= n &&
|
|
91
|
+
b.getbyte(i + 6) == 0x5C && b.getbyte(i + 7) == 0x75
|
|
92
|
+
lo = b.byteslice(i + 8, 4).to_i(16)
|
|
93
|
+
if lo >= 0xDC00 && lo <= 0xDFFF
|
|
94
|
+
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00)
|
|
95
|
+
i += 12
|
|
96
|
+
else
|
|
97
|
+
i += 6
|
|
98
|
+
end
|
|
99
|
+
else
|
|
100
|
+
i += 6
|
|
101
|
+
end
|
|
102
|
+
out << [cp].pack("U").b
|
|
103
|
+
end
|
|
104
|
+
out.force_encoding(Encoding::UTF_8)
|
|
105
|
+
end
|
|
56
106
|
|
|
57
107
|
def tape_engine(source)
|
|
58
108
|
tape = ::Yeptris::FFI::JsonTape.new
|
|
@@ -71,11 +121,16 @@ module Yeptris
|
|
|
71
121
|
kinds = tape[:kinds].read_bytes(n).unpack("C*")
|
|
72
122
|
offs = tape[:offs].read_bytes(n * 4).unpack("V*")
|
|
73
123
|
lens = tape[:lens].read_bytes(n * 4).unpack("V*")
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
124
|
+
# v2: parse records number spans only — ONE bulk convert call
|
|
125
|
+
# materializes them (per-number FFI calls would sink the ladder)
|
|
126
|
+
ivp = ::FFI::MemoryPointer.new(:int64, n, true)
|
|
127
|
+
dvp = ::FFI::MemoryPointer.new(:double, n, true)
|
|
128
|
+
::Yeptris::FFI.yeptris_tape_convert(tape, 0, n, ivp, dvp)
|
|
129
|
+
vals_i = ivp.read_bytes(n * 8).unpack("q<*")
|
|
130
|
+
vals_f = dvp.read_bytes(n * 8).unpack("E*")
|
|
77
131
|
# any integer text beyond int64: every INT rebuilds from its span
|
|
78
|
-
# (exact Bignum; JSON.parse parity)
|
|
132
|
+
# (exact Bignum; JSON.parse parity) — int_min is set by the
|
|
133
|
+
# convert call above
|
|
79
134
|
exact_ints = !tape[:int_min].zero?
|
|
80
135
|
utf8 = src.encoding == Encoding::UTF_8
|
|
81
136
|
|
|
@@ -97,8 +152,7 @@ module Yeptris
|
|
|
97
152
|
stack.pop
|
|
98
153
|
key_sets&.pop
|
|
99
154
|
when T_STR
|
|
100
|
-
text =
|
|
101
|
-
text.force_encoding(Encoding::UTF_8) unless utf8
|
|
155
|
+
text = lens[i] == 0 ? +"".force_encoding(Encoding::UTF_8) : decode_span(src, offs[i], lens[i])
|
|
102
156
|
if pending_key.nil? && !stack.empty? && stack.last.is_a?(Hash)
|
|
103
157
|
if key_sets && key_sets.last.key?(text)
|
|
104
158
|
raise ParseError, %(duplicate key "#{text}" in JSON object)
|
|
@@ -121,9 +175,8 @@ module Yeptris
|
|
|
121
175
|
v = vals_f[i]
|
|
122
176
|
place(docs, stack, pending_key) { v }
|
|
123
177
|
pending_key = nil
|
|
124
|
-
when
|
|
125
|
-
|
|
126
|
-
place(docs, stack, pending_key) { v }
|
|
178
|
+
when T_TRUE, T_FALSE # v2: the kind IS the value
|
|
179
|
+
place(docs, stack, pending_key) { kinds[i] == T_TRUE }
|
|
127
180
|
pending_key = nil
|
|
128
181
|
when T_NULL
|
|
129
182
|
place(docs, stack, pending_key) { nil }
|
|
@@ -249,26 +302,15 @@ module Yeptris
|
|
|
249
302
|
# and null stay plain), Symbols become their name.
|
|
250
303
|
class DumpError < Error; end
|
|
251
304
|
|
|
252
|
-
ENTRY_BYTES = 12
|
|
253
|
-
MAP_ENTRY = [::Yeptris::FFI::BUILD_MAP, 0, 0, 0].pack("CCx2VV").freeze
|
|
254
|
-
SEQ_ENTRY = [::Yeptris::FFI::BUILD_SEQ, 0, 0, 0].pack("CCx2VV").freeze
|
|
255
|
-
END_ENTRY = [::Yeptris::FFI::BUILD_END, 0, 0, 0].pack("CCx2VV").freeze
|
|
256
|
-
CONT_ENTRY = { ::Yeptris::FFI::BUILD_MAP => MAP_ENTRY, ::Yeptris::FFI::BUILD_SEQ => SEQ_ENTRY,
|
|
257
|
-
::Yeptris::FFI::BUILD_END => END_ENTRY }.freeze
|
|
258
|
-
|
|
259
305
|
def dump(obj)
|
|
260
|
-
parts =
|
|
306
|
+
parts = String.new(encoding: Encoding::BINARY,
|
|
307
|
+
capacity: 12 * 16) # 12-byte entries, appended in place
|
|
261
308
|
blob = String.new(encoding: Encoding::BINARY)
|
|
262
309
|
off = [0]
|
|
263
|
-
|
|
264
|
-
parts << (o.zero? && len.zero? && style.zero? ? CONT_ENTRY[op] :
|
|
265
|
-
[op, style, o, len].pack("CCx2VV"))
|
|
266
|
-
end
|
|
267
|
-
place_obj(obj, emit, blob, off)
|
|
310
|
+
place_obj(obj, parts, blob, off)
|
|
268
311
|
doc = ::Yeptris::Document.create
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
rc = doc.build_entries(buf, parts.length, bblob, blob.bytesize)
|
|
312
|
+
# FFI passes String args BY REFERENCE — no MemoryPointer copies
|
|
313
|
+
rc = doc.build_entries(parts, parts.bytesize / 12, blob, blob.bytesize)
|
|
272
314
|
raise DumpError, "document_build failed: #{rc}" unless rc == ::Yeptris::FFI::OK
|
|
273
315
|
out = ::Yeptris::FFI::JSON_EX ? doc.serialize_json_compact : doc.serialize_json
|
|
274
316
|
# JSON.generate emits no trailing newline (the document writer does)
|
|
@@ -277,37 +319,51 @@ module Yeptris
|
|
|
277
319
|
doc&.free
|
|
278
320
|
end
|
|
279
321
|
|
|
280
|
-
|
|
322
|
+
F = ::Yeptris::FFI
|
|
323
|
+
MAP_E = [F::BUILD_MAP, 0, 0, 0].pack("CCx2VV").freeze
|
|
324
|
+
SEQ_E = [F::BUILD_SEQ, 0, 0, 0].pack("CCx2VV").freeze
|
|
325
|
+
END_E = [F::BUILD_END, 0, 0, 0].pack("CCx2VV").freeze
|
|
326
|
+
DQ = F::STYLE_DOUBLE_QUOTED
|
|
327
|
+
PL = F::STYLE_PLAIN
|
|
328
|
+
SC_DQ = [F::BUILD_SCALAR, DQ].pack("CCx2").freeze
|
|
329
|
+
SC_PL = [F::BUILD_SCALAR, PL].pack("CCx2").freeze
|
|
330
|
+
|
|
331
|
+
def place_obj(obj, parts, blob, off)
|
|
281
332
|
case obj
|
|
282
333
|
when Hash
|
|
283
|
-
|
|
334
|
+
parts << MAP_E
|
|
284
335
|
obj.each do |k, v|
|
|
285
336
|
key = k.is_a?(String) ? k : k.is_a?(Symbol) ? k.name : k.to_s
|
|
286
|
-
scalar_json(key,
|
|
287
|
-
place_obj(v,
|
|
337
|
+
scalar_json(key, SC_DQ, parts, blob, off)
|
|
338
|
+
place_obj(v, parts, blob, off)
|
|
288
339
|
end
|
|
289
|
-
|
|
340
|
+
parts << END_E
|
|
290
341
|
when Array
|
|
291
|
-
|
|
292
|
-
obj.each { |e| place_obj(e,
|
|
293
|
-
|
|
342
|
+
parts << SEQ_E
|
|
343
|
+
obj.each { |e| place_obj(e, parts, blob, off) }
|
|
344
|
+
parts << END_E
|
|
294
345
|
when String
|
|
295
|
-
scalar_json(obj,
|
|
346
|
+
scalar_json(obj, SC_DQ, parts, blob, off)
|
|
296
347
|
when Symbol
|
|
297
|
-
scalar_json(obj.name,
|
|
298
|
-
when Integer, Float
|
|
299
|
-
|
|
300
|
-
|
|
348
|
+
scalar_json(obj.name, SC_DQ, parts, blob, off)
|
|
349
|
+
when Integer, Float
|
|
350
|
+
scalar_json(obj.to_s, SC_PL, parts, blob, off)
|
|
351
|
+
when true
|
|
352
|
+
scalar_json("true", SC_PL, parts, blob, off)
|
|
353
|
+
when false
|
|
354
|
+
scalar_json("false", SC_PL, parts, blob, off)
|
|
355
|
+
when nil
|
|
356
|
+
scalar_json("null", SC_PL, parts, blob, off)
|
|
301
357
|
when Date, Time
|
|
302
|
-
scalar_json(obj.iso8601,
|
|
358
|
+
scalar_json(obj.iso8601, SC_DQ, parts, blob, off)
|
|
303
359
|
else
|
|
304
360
|
raise DumpError, "cannot dump #{obj.class}: unsupported object " \
|
|
305
361
|
"(JSON.generate calls to_json on custom types)"
|
|
306
362
|
end
|
|
307
363
|
end
|
|
308
364
|
|
|
309
|
-
def scalar_json(text,
|
|
310
|
-
|
|
365
|
+
def scalar_json(text, prefix, parts, blob, off)
|
|
366
|
+
parts << (prefix + [off[0], text.bytesize].pack("VV"))
|
|
311
367
|
blob << text
|
|
312
368
|
off[0] += text.bytesize
|
|
313
369
|
end
|
data/lib/yeptris/native.so
CHANGED
|
Binary file
|
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.
|
|
6
|
+
VERSION = "0.4.0.1".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
|
|
@@ -70,8 +70,21 @@ end
|
|
|
70
70
|
# Optional C-API materializer (TODO.restructure/22): fused visit →
|
|
71
71
|
# Ruby objects via the Ruby C API. Feature-detected — LoadError leaves
|
|
72
72
|
# the FFI ladder (Marshal → columns → records) as the sole path.
|
|
73
|
+
# Windows names the artifact per Ruby minor (the leptris-ruby #207/
|
|
74
|
+
# #227 lesson): a PE DLL must bind its build Ruby's runtime, so one
|
|
75
|
+
# DLL per supported minor ships and RUBY_VERSION selects. A missing
|
|
76
|
+
# cell (Ruby 3.3 arm64 has no build) falls back LOUDLY to the ladder.
|
|
73
77
|
begin
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
if Gem.win_platform?
|
|
79
|
+
require "yeptris/native-#{RUBY_VERSION[/\A\d+\.\d+/]}"
|
|
80
|
+
else
|
|
81
|
+
require "yeptris/native"
|
|
82
|
+
end
|
|
83
|
+
rescue LoadError => e
|
|
84
|
+
if Gem.win_platform?
|
|
85
|
+
warn "yeptris: no precompiled native materializer for Ruby " \
|
|
86
|
+
"#{RUBY_VERSION[/\A\d+\.\d+/]} on this platform " \
|
|
87
|
+
"(#{e.message}); the FFI ladder carries the load"
|
|
88
|
+
end
|
|
76
89
|
# FFI ladder only
|
|
77
90
|
end
|
data/libyeptris.so
CHANGED
|
Binary file
|
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.
|
|
4
|
+
version: 0.4.0.1
|
|
5
5
|
platform: aarch64-linux
|
|
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-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -34,6 +34,7 @@ extensions: []
|
|
|
34
34
|
extra_rdoc_files: []
|
|
35
35
|
files:
|
|
36
36
|
- README.adoc
|
|
37
|
+
- ext/build_windows_native.rb
|
|
37
38
|
- ext/yeptris_native/extconf.rb
|
|
38
39
|
- ext/yeptris_native/json_ruby.c
|
|
39
40
|
- ext/yeptris_native/yeptris_native.c
|