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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53ba5f8ec2b0bce5440f6c728f78af3703a0ea3656b8c752a6bcacaf3362528f
4
- data.tar.gz: e6ac1792bbf42a5bec4fbe2bcebdd060b22a21a0be62decfeea59216301c70a6
3
+ metadata.gz: b0564531041910fb120994e2f4cac8c9f3ddf0b176bf5ae9ca3289cb58f6ac87
4
+ data.tar.gz: 4b6da94d1b0706fdfdd597bc173ca9556e26482a517b391670ef2bb1471f17df
5
5
  SHA512:
6
- metadata.gz: eeb00725be0541b489ace917c0e45480f0bf17056adaadb39835c9cdcce0bb44885fcf899624bc8d3d4331547b16c351ded8122783141b9acff641c6f126e701
7
- data.tar.gz: add571ff504fd8ae8e5b1ee899ac8029762ee138277cd6ccd0830cdd9fd7a18126f5f3a8d13fd24736fe7f7e88ae551bfc989598ce071b34c087d1a93f174c89
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, four bulk columns, spans borrow
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, :vals, :pointer, :int_min, :int64, :_block, :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
- T_BOOL = 2
50
- T_INT = 3
51
- T_FLOAT = 4
52
- T_STR = 5
53
- T_SEQ_OPEN = 6
54
- T_MAP_OPEN = 7
55
- T_CLOSE = 8
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
- raw = tape[:vals].read_bytes(n * 8)
75
- vals_i = raw.unpack("q<*")
76
- vals_f = raw.unpack("E*")
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 = src.byteslice(offs[i], lens[i])
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 T_BOOL
125
- v = vals_i[i] == 1
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
- emit = lambda do |op, style, o, len|
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
- buf = ::FFI::MemoryPointer.from_string(parts.join)
270
- bblob = ::FFI::MemoryPointer.from_string(blob)
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
- def place_obj(obj, emit, blob, off)
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
- emit.call(::Yeptris::FFI::BUILD_MAP, 0, 0, 0)
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, ::Yeptris::FFI::STYLE_DOUBLE_QUOTED, emit, blob, off)
287
- place_obj(v, emit, blob, off)
337
+ scalar_json(key, SC_DQ, parts, blob, off)
338
+ place_obj(v, parts, blob, off)
288
339
  end
289
- emit.call(::Yeptris::FFI::BUILD_END, 0, 0, 0)
340
+ parts << END_E
290
341
  when Array
291
- emit.call(::Yeptris::FFI::BUILD_SEQ, 0, 0, 0)
292
- obj.each { |e| place_obj(e, emit, blob, off) }
293
- emit.call(::Yeptris::FFI::BUILD_END, 0, 0, 0)
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, ::Yeptris::FFI::STYLE_DOUBLE_QUOTED, emit, blob, off)
346
+ scalar_json(obj, SC_DQ, parts, blob, off)
296
347
  when Symbol
297
- scalar_json(obj.name, ::Yeptris::FFI::STYLE_DOUBLE_QUOTED, emit, blob, off)
298
- when Integer, Float, true, false, nil
299
- text = obj.nil? ? "null" : obj.to_s
300
- scalar_json(text, ::Yeptris::FFI::STYLE_PLAIN, emit, blob, off)
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, ::Yeptris::FFI::STYLE_DOUBLE_QUOTED, emit, blob, off)
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, style, emit, blob, off)
310
- emit.call(::Yeptris::FFI::BUILD_SCALAR, style, off[0], text.bytesize)
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
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.2.5.3".freeze
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
- require "yeptris/native"
75
- rescue LoadError
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.2.5.3
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-14 00:00:00.000000000 Z
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