yeptris 0.6.18.2-arm-linux-eabihf
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 +7 -0
- data/README.adoc +141 -0
- data/ext/build_windows_native.rb +41 -0
- data/ext/libyeptris/extconf.rb +57 -0
- data/ext/yeptris_native/cbor_ruby.c +193 -0
- data/ext/yeptris_native/extconf.rb +47 -0
- data/ext/yeptris_native/json_ruby.c +354 -0
- data/ext/yeptris_native/yeptris_native.c +497 -0
- data/lib/yeptris/cbor.rb +130 -0
- data/lib/yeptris/document.rb +255 -0
- data/lib/yeptris/ffi.rb +426 -0
- data/lib/yeptris/json/descriptor.rb +266 -0
- data/lib/yeptris/json.rb +394 -0
- data/lib/yeptris/materializer.rb +354 -0
- data/lib/yeptris/native-3.4.so +0 -0
- data/lib/yeptris/node.rb +453 -0
- data/lib/yeptris/psych/class_loader.rb +24 -0
- data/lib/yeptris/psych/coder_shim.rb +55 -0
- data/lib/yeptris/psych/drop_in.rb +63 -0
- data/lib/yeptris/psych/encodable.rb +14 -0
- data/lib/yeptris/psych/handler.rb +90 -0
- data/lib/yeptris/psych/parser.rb +105 -0
- data/lib/yeptris/psych/scalar_scanner.rb +149 -0
- data/lib/yeptris/psych/visitors.rb +545 -0
- data/lib/yeptris/psych.rb +622 -0
- data/lib/yeptris/schema.rb +253 -0
- data/lib/yeptris/valueml.rb +455 -0
- data/lib/yeptris/yaml/descriptor.rb +56 -0
- data/lib/yeptris/yaml.rb +422 -0
- data/lib/yeptris.rb +113 -0
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +254 -0
- data/vendor/libyeptris/cmake/yeptris-config.cmake.in +5 -0
- data/vendor/libyeptris/cmake/yeptris.pc.in +11 -0
- data/vendor/libyeptris/src/CMakeLists.txt +167 -0
- data/vendor/libyeptris/src/include/yeptris/api.h +32 -0
- data/vendor/libyeptris/src/include/yeptris/cbor.h +101 -0
- data/vendor/libyeptris/src/include/yeptris/dom.h +190 -0
- data/vendor/libyeptris/src/include/yeptris/emit.h +81 -0
- data/vendor/libyeptris/src/include/yeptris/error.h +41 -0
- data/vendor/libyeptris/src/include/yeptris/events.h +157 -0
- data/vendor/libyeptris/src/include/yeptris/json.h +45 -0
- data/vendor/libyeptris/src/include/yeptris/json.hpp +258 -0
- data/vendor/libyeptris/src/include/yeptris/jsonc_compat.h +115 -0
- data/vendor/libyeptris/src/include/yeptris/marshal.h +52 -0
- data/vendor/libyeptris/src/include/yeptris/parse.h +48 -0
- data/vendor/libyeptris/src/include/yeptris/plan.h +100 -0
- data/vendor/libyeptris/src/include/yeptris/resolve.h +71 -0
- data/vendor/libyeptris/src/include/yeptris/schema.h +121 -0
- data/vendor/libyeptris/src/include/yeptris/tape.h +123 -0
- data/vendor/libyeptris/src/include/yeptris/types.h +38 -0
- data/vendor/libyeptris/src/include/yeptris/values.h +102 -0
- data/vendor/libyeptris/src/include/yeptris/version.h.in +29 -0
- data/vendor/libyeptris/src/include/yeptris/visit.h +77 -0
- data/vendor/libyeptris/src/include/yeptris/yajl_compat.h +135 -0
- data/vendor/libyeptris/src/include/yeptris.h +23 -0
- data/vendor/libyeptris/src/yeptris/build.c +376 -0
- data/vendor/libyeptris/src/yeptris/cbor/cbor.h +25 -0
- data/vendor/libyeptris/src/yeptris/cbor/decode.c +981 -0
- data/vendor/libyeptris/src/yeptris/cbor/encode.c +835 -0
- data/vendor/libyeptris/src/yeptris/cbor/sink.h +58 -0
- data/vendor/libyeptris/src/yeptris/common/chartype.c +41 -0
- data/vendor/libyeptris/src/yeptris/common/chartype.h +77 -0
- data/vendor/libyeptris/src/yeptris/common/cpu.c +81 -0
- data/vendor/libyeptris/src/yeptris/common/cpu.h +40 -0
- data/vendor/libyeptris/src/yeptris/common/error.c +65 -0
- data/vendor/libyeptris/src/yeptris/common/error.h +79 -0
- data/vendor/libyeptris/src/yeptris/common/mutex.h +45 -0
- data/vendor/libyeptris/src/yeptris/common/nametab.c +275 -0
- data/vendor/libyeptris/src/yeptris/common/nametab.h +68 -0
- data/vendor/libyeptris/src/yeptris/common/port.h +74 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text.c +49 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text.h +295 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text_avx2.c +534 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text_neon.c +662 -0
- data/vendor/libyeptris/src/yeptris/common/simd_text_scalar.c +298 -0
- data/vendor/libyeptris/src/yeptris/common/string_view.c +34 -0
- data/vendor/libyeptris/src/yeptris/common/string_view.h +93 -0
- data/vendor/libyeptris/src/yeptris/doc.h +56 -0
- data/vendor/libyeptris/src/yeptris/dom/dom.c +1192 -0
- data/vendor/libyeptris/src/yeptris/dom/dom.h +312 -0
- data/vendor/libyeptris/src/yeptris/dom/hpool.c +107 -0
- data/vendor/libyeptris/src/yeptris/dom/mapindex.c +173 -0
- data/vendor/libyeptris/src/yeptris/dom/mapindex.h +61 -0
- data/vendor/libyeptris/src/yeptris/dom/mutate.c +431 -0
- data/vendor/libyeptris/src/yeptris/emit/emit.c +434 -0
- data/vendor/libyeptris/src/yeptris/emit/float/api.h +38 -0
- data/vendor/libyeptris/src/yeptris/emit/float/dragon.c +405 -0
- data/vendor/libyeptris/src/yeptris/emit/float/floatint.h +231 -0
- data/vendor/libyeptris/src/yeptris/emit/float/print.c +396 -0
- data/vendor/libyeptris/src/yeptris/emit/style.c +81 -0
- data/vendor/libyeptris/src/yeptris/emit/style.h +24 -0
- data/vendor/libyeptris/src/yeptris/emit/writer.c +816 -0
- data/vendor/libyeptris/src/yeptris/emit/writer.h +76 -0
- data/vendor/libyeptris/src/yeptris/encoding/bom.c +31 -0
- data/vendor/libyeptris/src/yeptris/encoding/encoding.h +63 -0
- data/vendor/libyeptris/src/yeptris/encoding/transcode.c +148 -0
- data/vendor/libyeptris/src/yeptris/encoding/utf8_validate.c +178 -0
- data/vendor/libyeptris/src/yeptris/events/capture.c +150 -0
- data/vendor/libyeptris/src/yeptris/events/capture.h +39 -0
- data/vendor/libyeptris/src/yeptris/events/iterparse.c +208 -0
- data/vendor/libyeptris/src/yeptris/events/pull.c +97 -0
- data/vendor/libyeptris/src/yeptris/events/push.c +104 -0
- data/vendor/libyeptris/src/yeptris/events/recorder.c +107 -0
- data/vendor/libyeptris/src/yeptris/events/values.c +527 -0
- data/vendor/libyeptris/src/yeptris/events/values_priv.h +51 -0
- data/vendor/libyeptris/src/yeptris/events/yaml_compat.c +86 -0
- data/vendor/libyeptris/src/yeptris/events/yaml_compat.h +90 -0
- data/vendor/libyeptris/src/yeptris/jsonapi/jsonc_compat.c +687 -0
- data/vendor/libyeptris/src/yeptris/jsonapi/yajl_compat.c +728 -0
- data/vendor/libyeptris/src/yeptris/marshal.c +782 -0
- data/vendor/libyeptris/src/yeptris/memory/allocator.c +20 -0
- data/vendor/libyeptris/src/yeptris/memory/allocator.h +43 -0
- data/vendor/libyeptris/src/yeptris/memory/arena.c +134 -0
- data/vendor/libyeptris/src/yeptris/memory/arena.h +52 -0
- data/vendor/libyeptris/src/yeptris/memory/pool.c +122 -0
- data/vendor/libyeptris/src/yeptris/memory/pool.h +36 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +3698 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.h +65 -0
- data/vendor/libyeptris/src/yeptris/parse/events.h +124 -0
- data/vendor/libyeptris/src/yeptris/parse/numbers.c +349 -0
- data/vendor/libyeptris/src/yeptris/parse/numbers.h +33 -0
- data/vendor/libyeptris/src/yeptris/parse/scalars.c +416 -0
- data/vendor/libyeptris/src/yeptris/parse/scalars.h +75 -0
- data/vendor/libyeptris/src/yeptris/parse.c +737 -0
- data/vendor/libyeptris/src/yeptris/plan.c +824 -0
- data/vendor/libyeptris/src/yeptris/resolve/compat11.c +334 -0
- data/vendor/libyeptris/src/yeptris/resolve/core12.c +143 -0
- data/vendor/libyeptris/src/yeptris/resolve/resolver.h +56 -0
- data/vendor/libyeptris/src/yeptris/resolve/tags.c +58 -0
- data/vendor/libyeptris/src/yeptris/scan/json.c +792 -0
- data/vendor/libyeptris/src/yeptris/scan/json.h +109 -0
- data/vendor/libyeptris/src/yeptris/scan/scan.c +463 -0
- data/vendor/libyeptris/src/yeptris/scan/scan.h +150 -0
- data/vendor/libyeptris/src/yeptris/schema.c +308 -0
- data/vendor/libyeptris/src/yeptris/tape.c +1308 -0
- data/vendor/libyeptris/src/yeptris/tape_in.h +19 -0
- data/vendor/libyeptris/src/yeptris/version.c +7 -0
- data/vendor/libyeptris/src/yeptris/visit/dom_visit.c +115 -0
- data/vendor/libyeptris/src/yeptris/visit/json_visit.c +293 -0
- data/vendor/libyeptris/src/yeptris/visit/yaml_visit.c +127 -0
- metadata +199 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d861d71fda8e06d38693a273a8c3eea7cbe96325af1eb0e4d2fdcc4bf7f08e46
|
|
4
|
+
data.tar.gz: 1120adc922c754da05873a318e2406206606ca966d22b79a686e08192b4a0e4d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a68230754f4f90049a59ea30d8d8abb903c8fdef3f5e26abf5b110fd966d4da973c811d8e4c55750034aaa2207289f9fed60ed50f5065125b7b6854b44c5bce4
|
|
7
|
+
data.tar.gz: eb0d85052b73579c0809a9b1aef3f6be57ee5aff43406b9569f7f77189d23af3471d4bd29b5d9f4815a50bd661e20007a7c93fc6b3a2bac8d842a1a7001da921
|
data/README.adoc
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
= yeptris — YAML for Ruby at libleptris speed
|
|
2
|
+
|
|
3
|
+
An FFI-based (no C extension) Ruby YAML library over
|
|
4
|
+
https://github.com/leptris/yeptris[libyeptris] — the YAML counterpart
|
|
5
|
+
of libleptris. Psych-compatible semantics, one shared library, zero
|
|
6
|
+
compilation at install.
|
|
7
|
+
|
|
8
|
+
== Install (development)
|
|
9
|
+
|
|
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:
|
|
13
|
+
|
|
14
|
+
....
|
|
15
|
+
# the sibling C checkout: ~/src/leptris/yeptris
|
|
16
|
+
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DYEPTRIS_BUILD_SHARED=ON
|
|
17
|
+
cmake --build build
|
|
18
|
+
|
|
19
|
+
cd ~/src/leptris/yeptris-ruby
|
|
20
|
+
YEPTRIS_LIB_PATH=../yeptris/build/src/libyeptris.dylib bundle exec rspec
|
|
21
|
+
....
|
|
22
|
+
|
|
23
|
+
Without `YEPTRIS_LIB_PATH` the spec helper falls back to a vendored
|
|
24
|
+
`lib/platform/<tag>/` copy, then to the sibling checkout's
|
|
25
|
+
`build-validate` — any `libyeptris.{so,dylib,dll}` path works.
|
|
26
|
+
|
|
27
|
+
== Usage
|
|
28
|
+
|
|
29
|
+
[source,ruby]
|
|
30
|
+
----
|
|
31
|
+
require "yeptris"
|
|
32
|
+
|
|
33
|
+
Yeptris::YAML.load("name: yeptris\nrating: 10\n")
|
|
34
|
+
# => {"name" => "yeptris", "rating" => 10}
|
|
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]})
|
|
39
|
+
|
|
40
|
+
doc = Yeptris::Document.parse(config_yaml)
|
|
41
|
+
doc.root["server"]["port"].to_i
|
|
42
|
+
doc.serialize
|
|
43
|
+
----
|
|
44
|
+
|
|
45
|
+
`Yeptris::YAML.load` defaults to Psych's YAML 1.1 implicit typing
|
|
46
|
+
(`yes` is `true`, `017` is octal); pass `schema: :core_12` for YAML
|
|
47
|
+
1.2 core semantics. Dump builds through the library's DOM mutation
|
|
48
|
+
API, so the writer's sizing/escape machinery applies to synthesized
|
|
49
|
+
trees unchanged.
|
|
50
|
+
|
|
51
|
+
Handles are document-scoped: `Document#free` releases everything
|
|
52
|
+
(one C call), a GC finalizer backs it up, and any use after free
|
|
53
|
+
raises `Yeptris::FreedError` — never a segfault.
|
|
54
|
+
|
|
55
|
+
== Two load surfaces: YAML and strict JSON
|
|
56
|
+
|
|
57
|
+
`Yeptris::YAML.load` keeps the Psych contract for EVERY input —
|
|
58
|
+
including JSON-shaped ones. `{"a": [1,]}` is legal flow YAML (spec
|
|
59
|
+
production [141] allows the trailing comma); `"1e3"` is a Psych
|
|
60
|
+
String. These semantics never flip because input happens to look
|
|
61
|
+
like JSON.
|
|
62
|
+
|
|
63
|
+
`Yeptris::JSON.load` is the STRICT RFC 8259 surface — exact
|
|
64
|
+
`JSON.parse` semantics by construction (spec-pinned in
|
|
65
|
+
`spec/json_parity_spec.rb`: every value and every error case,
|
|
66
|
+
Bignum integers, exponent-only floats, duplicate keys). Engines,
|
|
67
|
+
fastest first:
|
|
68
|
+
|
|
69
|
+
1. **Native materializer** (opt-in build; see below): a fused C scan
|
|
70
|
+
→ `VALUE` parser. Order-alternating interleaved profile, 152 KB /
|
|
71
|
+
~29k-value corpus, Ruby 3.4:
|
|
72
|
+
+
|
|
73
|
+
....
|
|
74
|
+
CI referee (gated at 1.05 on both platforms, order-alternating
|
|
75
|
+
interleave, N=200; prefix-hashed token cache):
|
|
76
|
+
ubuntu (x86_64) mean 0.745x vs JSON.parse, 200/200 head-to-head
|
|
77
|
+
macos (arm64) mean 0.759x vs JSON.parse, 169/200 head-to-head
|
|
78
|
+
....
|
|
79
|
+
|
|
80
|
+
The parse-window GC strategy is per-arch and runtime-switchable:
|
|
81
|
+
`Yeptris::Native.gc_mode` / `YEPTRIS_NATIVE_GC` = `:none`
|
|
82
|
+
(steady-state: +0 heap pages, the stdlib's own GC cadence — the
|
|
83
|
+
default on arm64) or `:disable` (no GC during the parse — the
|
|
84
|
+
default on x86_64, faster where pages are free). On a LOADED x86_64
|
|
85
|
+
box prefer `:none`: `:disable` grows ~478 heap pages per 50 parses,
|
|
86
|
+
and page growth under contention is exactly the loaded-box
|
|
87
|
+
regression. `:start` (in-window collection) measured 8x — dead.
|
|
88
|
+
2. **Record-drain fallback** (always available): the strict-JSON
|
|
89
|
+
validator gates, then the value records convert without the Psych
|
|
90
|
+
quirk table — exact parity with engine 1, spec-pinned.
|
|
91
|
+
|
|
92
|
+
The committed profile (`benchmark/json_profile.rb`) is the fair
|
|
93
|
+
benchmark of record — any performance claim runs through it.
|
|
94
|
+
|
|
95
|
+
=== Building the native materializer (opt-in)
|
|
96
|
+
|
|
97
|
+
....
|
|
98
|
+
cd ext/yeptris_native
|
|
99
|
+
YEPTRIS_LIB_PATH=/path/to/libyeptris.dylib ruby extconf.rb && make
|
|
100
|
+
cp native.bundle ../../lib/yeptris/ # or .so on Linux
|
|
101
|
+
....
|
|
102
|
+
|
|
103
|
+
`require "yeptris"` picks it up automatically (`Yeptris::JSON` then
|
|
104
|
+
uses it; missing builds silently use the fallback — the gem installs
|
|
105
|
+
without compiling).
|
|
106
|
+
|
|
107
|
+
== Shipped beyond the original plan
|
|
108
|
+
|
|
109
|
+
* The columnar value drain (bulk, pre-converted typed columns — the
|
|
110
|
+
load fast path) and the bulk DOM builder (one `document_build`
|
|
111
|
+
call per dump).
|
|
112
|
+
* **Ruby Marshal 4.8 emission** (libyeptris >= 0.1.11): the C side
|
|
113
|
+
converts value records into Marshal bytes; one `Marshal.load`
|
|
114
|
+
materializes the whole graph. ~10× faster than the columnar walk
|
|
115
|
+
on JSON-shaped input, ~5× on YAML, ~50× on the per-node DOM walk
|
|
116
|
+
(`Node#to_ruby` becomes bulk). Alias identity preserved through
|
|
117
|
+
object links; merge keys and timestamps return UNSUPPORTED for the
|
|
118
|
+
record-walk fallback.
|
|
119
|
+
* `Yeptris::Psych` — the Psych-compatible namespace with the ported
|
|
120
|
+
Psych suite (143 specs), `.tml` corpora conformance, and the
|
|
121
|
+
Encodable object protocol (`include Yeptris::Psych::Encodable` +
|
|
122
|
+
`encode_with`/`init_with` — no `respond_to?`, no ivar reflection;
|
|
123
|
+
the library never reaches into an object's internals).
|
|
124
|
+
** CO-EXISTENCE (issue #69): `require "yeptris/psych"` loads the
|
|
125
|
+
namespace WITHOUT touching the top-level `Psych` constant — it
|
|
126
|
+
coexists with stdlib psych in ANY load order. The process-
|
|
127
|
+
exclusive drop-in rebind (`::Psych = Yeptris::Psych`) is now
|
|
128
|
+
OPT-IN: `require "yeptris/psych/drop_in"`. Once the drop-in runs,
|
|
129
|
+
stdlib psych must NOT be loaded afterwards (its require re-opens
|
|
130
|
+
the rebound module and clobbers constants). Bundles that cannot
|
|
131
|
+
control the load order (activesupport et al.) should use
|
|
132
|
+
`Yeptris::Psych` / `Yeptris::YAML` directly.
|
|
133
|
+
** `Yeptris::YAML.safe_load(yaml, permitted_classes: [], aliases:
|
|
134
|
+
false)` — Psych's safe_load semantics on the native surface:
|
|
135
|
+
plain data by default; a leaf that would materialize to a
|
|
136
|
+
non-permitted class (a compat_11 date) raises
|
|
137
|
+
`Yeptris::Psych::DisallowedClass`; alias use without `aliases:
|
|
138
|
+
true` raises `Yeptris::Psych::AliasesError`.
|
|
139
|
+
* Lockstep versioning with the C library
|
|
140
|
+
(`{c-semver}.{gem-patch}`); releases published from the C repo's
|
|
141
|
+
workflow through the RubyGems trusted publisher.
|
|
@@ -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
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Builds the vendored libyeptris at gem-install time so EVERY install
|
|
4
|
+
# carries the engine (the pure `ruby`-platform gem has no prebuilt
|
|
5
|
+
# binary). Platform gems vendor the prebuilt lib at the gem root —
|
|
6
|
+
# when one is present this extension no-ops.
|
|
7
|
+
|
|
8
|
+
require "mkmf"
|
|
9
|
+
require "fileutils"
|
|
10
|
+
|
|
11
|
+
gem_root = File.expand_path("../..", __dir__)
|
|
12
|
+
vendored = File.join(gem_root, "vendor", "libyeptris")
|
|
13
|
+
|
|
14
|
+
prebuilt = ["libyeptris.so", "libyeptris.dylib", "libyeptris.dll",
|
|
15
|
+
"yeptris.dll"].any? { |n| File.exist?(File.join(gem_root, n)) }
|
|
16
|
+
|
|
17
|
+
def write_dummy_makefile(reason)
|
|
18
|
+
File.write("Makefile", <<~MAKE)
|
|
19
|
+
all:
|
|
20
|
+
\t@echo "#{reason}"
|
|
21
|
+
install:
|
|
22
|
+
clean:
|
|
23
|
+
MAKE
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if prebuilt
|
|
27
|
+
write_dummy_makefile("yeptris: prebuilt libyeptris ships in this gem; source build skipped")
|
|
28
|
+
elsif !File.directory?(vendored)
|
|
29
|
+
write_dummy_makefile("yeptris: no vendored libyeptris sources; set YEPTRIS_LIB_PATH at require time")
|
|
30
|
+
elsif find_executable("cmake").nil?
|
|
31
|
+
write_dummy_makefile("yeptris: cmake not found; set YEPTRIS_LIB_PATH at require time")
|
|
32
|
+
else
|
|
33
|
+
# Out-of-source into the ext workdir; the shared lib then moves to
|
|
34
|
+
# the GEM ROOT, where ffi.rb's ladder probes it.
|
|
35
|
+
build = File.join(Dir.pwd, "libyeptris-build")
|
|
36
|
+
# BUILD_TESTING (the CMake standard) gates test/ — the vendored
|
|
37
|
+
# tree ships no test dir; the static target must build because the
|
|
38
|
+
# unconditional install(TARGETS yeptris_static) expects it
|
|
39
|
+
args = [
|
|
40
|
+
"-DCMAKE_BUILD_TYPE=Release",
|
|
41
|
+
"-DBUILD_TESTING=OFF",
|
|
42
|
+
"-DYEPTRIS_BUILD_CLI=OFF",
|
|
43
|
+
"-DYEPTRIS_BUILD_BENCHMARKS=OFF",
|
|
44
|
+
"-DYEPTRIS_BUILD_SHARED=ON",
|
|
45
|
+
]
|
|
46
|
+
ok = system("cmake", "-S", vendored, "-B", build, *args) &&
|
|
47
|
+
system("cmake", "--build", build, "--config", "Release")
|
|
48
|
+
lib = Dir.glob(File.join(build, "**", "libyeptris.{so,dylib}")).first
|
|
49
|
+
if ok && lib
|
|
50
|
+
FileUtils.cp(lib, File.join(gem_root, File.basename(lib)))
|
|
51
|
+
write_dummy_makefile("yeptris: built vendored libyeptris at gem install")
|
|
52
|
+
else
|
|
53
|
+
# a failed build must not abort the install — require-time raises
|
|
54
|
+
# the informative ladder error instead
|
|
55
|
+
write_dummy_makefile("yeptris: libyeptris source build failed; set YEPTRIS_LIB_PATH at require time")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/* cbor_ruby.c — CBOR decode → Ruby VALUE in one C pass (#157).
|
|
2
|
+
*
|
|
3
|
+
* The FFI ladder's CBOR.load pays decode + a Marshal emit/load round
|
|
4
|
+
* trip; this walks the decoded DOM directly, building the same VALUEs
|
|
5
|
+
* the materializer builds (tag-driven scalar semantics, insertion-
|
|
6
|
+
* order mappings), with no intermediate representation.
|
|
7
|
+
*/
|
|
8
|
+
#include <ruby.h>
|
|
9
|
+
/* rb_hash_new_capa is Ruby 3.2+; older Rubies grow dynamically (the
|
|
10
|
+
* json_ruby.c guard, verbatim). */
|
|
11
|
+
#if RUBY_API_VERSION_MAJOR > 3 || (RUBY_API_VERSION_MAJOR == 3 && RUBY_API_VERSION_MINOR >= 2)
|
|
12
|
+
#define HASH_NEW_CAPA(n) rb_hash_new_capa(n)
|
|
13
|
+
#else
|
|
14
|
+
#define HASH_NEW_CAPA(n) rb_hash_new()
|
|
15
|
+
#endif
|
|
16
|
+
#include <ruby/encoding.h>
|
|
17
|
+
#include <stdlib.h>
|
|
18
|
+
#include <string.h>
|
|
19
|
+
|
|
20
|
+
static rb_encoding* cr_utf8_enc;
|
|
21
|
+
|
|
22
|
+
/* Map keys repeat across records in CBOR corpora; interning shares one
|
|
23
|
+
* VALUE per distinct key (the json_ruby.c on_key pattern, #157). */
|
|
24
|
+
static VALUE cr_key_str(const char* p, size_t len) {
|
|
25
|
+
return rb_enc_interned_str(p, (long)len, cr_utf8_enc);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#include "cbor/sink.h"
|
|
29
|
+
#include "dom/dom.h"
|
|
30
|
+
#include "doc.h" /* the public YeptrisDocument wrapper: ->dom */
|
|
31
|
+
#include <yeptris/cbor.h>
|
|
32
|
+
#include <yeptris/dom.h>
|
|
33
|
+
#include <yeptris/resolve.h>
|
|
34
|
+
|
|
35
|
+
/* ---- the VALUE sink (#157): bytes -> VALUE in ONE pass ----
|
|
36
|
+
*
|
|
37
|
+
* The decode grammar drives; these callbacks build the same VALUEs
|
|
38
|
+
* the old DOM + cr_walk pair produced (verified by the binding's
|
|
39
|
+
* differential suite): nil/true/false by tag, ints via LL2NUM (the
|
|
40
|
+
* INT64_MIN text parity wart kept), floats via DBL2NUM (no
|
|
41
|
+
* text round trip), strings copied utf8 (byte strings today ride the
|
|
42
|
+
* same utf8 String), map keys interned (values never are).
|
|
43
|
+
*
|
|
44
|
+
* GC: the container stack is a Ruby array (every VALUE reachable);
|
|
45
|
+
* the input buffer is read directly, so decode runs with GC disabled
|
|
46
|
+
* (the caller's String may move otherwise — the #160 bus error). */
|
|
47
|
+
|
|
48
|
+
struct rv_ctx {
|
|
49
|
+
VALUE stack; /* [.., container, (pending-key)?] */
|
|
50
|
+
VALUE root; /* Qnil until the top item closes */
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
static VALUE rv_top(const struct rv_ctx* c) {
|
|
54
|
+
long n = RARRAY_LEN(c->stack);
|
|
55
|
+
return n > 0 ? rb_ary_entry(c->stack, n - 1) : Qnil;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static VALUE rv_attach(struct rv_ctx* c, VALUE v) {
|
|
59
|
+
VALUE top = rv_top(c);
|
|
60
|
+
if (RB_TYPE_P(top, T_ARRAY)) {
|
|
61
|
+
rb_ary_push(top, v);
|
|
62
|
+
return v;
|
|
63
|
+
}
|
|
64
|
+
if (RB_TYPE_P(top, T_HASH)) {
|
|
65
|
+
rb_ary_push(c->stack, v); /* becomes the pending key */
|
|
66
|
+
return v;
|
|
67
|
+
}
|
|
68
|
+
/* a pending key: pop it, land v in the map */
|
|
69
|
+
VALUE k = rb_ary_pop(c->stack);
|
|
70
|
+
VALUE map = rv_top(c);
|
|
71
|
+
if (RB_TYPE_P(map, T_HASH)) {
|
|
72
|
+
rb_hash_aset(map, k, v);
|
|
73
|
+
return v;
|
|
74
|
+
}
|
|
75
|
+
/* the stack emptied: v is the root */
|
|
76
|
+
c->root = v;
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static int rv_text(void* ctx, const char* s, uint32_t n, uint8_t tag_id, int is_key,
|
|
81
|
+
const char* tag, uint32_t tag_len) {
|
|
82
|
+
(void)tag;
|
|
83
|
+
(void)tag_len;
|
|
84
|
+
struct rv_ctx* c = ctx;
|
|
85
|
+
VALUE v;
|
|
86
|
+
switch (tag_id) {
|
|
87
|
+
case YEPTRIS_TAG_NULL:
|
|
88
|
+
v = Qnil;
|
|
89
|
+
break;
|
|
90
|
+
case YEPTRIS_TAG_BOOL:
|
|
91
|
+
v = (n == 4 && memcmp(s, "true", 4) == 0) ? Qtrue : Qfalse;
|
|
92
|
+
break;
|
|
93
|
+
default: /* rendered text (diagnostics, simple(N), key ints) */
|
|
94
|
+
v = rb_utf8_str_new(s, (long)n);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
(void)is_key;
|
|
98
|
+
rv_attach(c, v);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static int rv_int(void* ctx, int negative, uint64_t mag, int is_key, const char* tag,
|
|
103
|
+
uint32_t tag_len) {
|
|
104
|
+
(void)tag;
|
|
105
|
+
(void)tag_len;
|
|
106
|
+
struct rv_ctx* c = ctx;
|
|
107
|
+
VALUE v;
|
|
108
|
+
if (negative && mag == (uint64_t)1 << 63) {
|
|
109
|
+
/* parity: the DOM path's strtoll guard renders INT64_MIN a
|
|
110
|
+
* String today; the differential suite pins it */
|
|
111
|
+
v = rb_utf8_str_new("-9223372036854775808", 20);
|
|
112
|
+
} else {
|
|
113
|
+
v = LL2NUM(negative ? -(int64_t)mag : (int64_t)mag);
|
|
114
|
+
}
|
|
115
|
+
rv_attach(c, v);
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
static int rv_float(void* ctx, double dv, int is_key, const char* tag, uint32_t tag_len) {
|
|
120
|
+
(void)is_key;
|
|
121
|
+
(void)tag;
|
|
122
|
+
(void)tag_len;
|
|
123
|
+
rv_attach(ctx, DBL2NUM(dv));
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static int rv_str(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
128
|
+
const char* tag, uint32_t tag_len) {
|
|
129
|
+
(void)borrowed;
|
|
130
|
+
(void)tag;
|
|
131
|
+
(void)tag_len;
|
|
132
|
+
struct rv_ctx* c = ctx;
|
|
133
|
+
VALUE v = is_key ? rb_enc_interned_str((const char*)p, (long)n, cr_utf8_enc)
|
|
134
|
+
: rb_utf8_str_new((const char*)p, (long)n);
|
|
135
|
+
rv_attach(c, v);
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static int rv_bytes(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
140
|
+
const char* tag, uint32_t tag_len) {
|
|
141
|
+
/* parity: byte strings ride the same utf8 String today */
|
|
142
|
+
return rv_str(ctx, p, n, borrowed, is_key, tag, tag_len);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static int rv_open(void* ctx, int is_map, uint64_t cap, const char* tag, uint32_t tag_len) {
|
|
146
|
+
(void)tag;
|
|
147
|
+
(void)tag_len;
|
|
148
|
+
struct rv_ctx* c = ctx;
|
|
149
|
+
VALUE v;
|
|
150
|
+
if (is_map) {
|
|
151
|
+
v = (cap != UINT64_MAX && cap / 2 <= 4096) ? HASH_NEW_CAPA((long)(cap / 2))
|
|
152
|
+
: rb_hash_new();
|
|
153
|
+
} else {
|
|
154
|
+
v = (cap != UINT64_MAX && cap <= 8192) ? rb_ary_new_capa((long)cap) : rb_ary_new();
|
|
155
|
+
}
|
|
156
|
+
rb_ary_push(c->stack, v);
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static int rv_close(void* ctx, int is_map) {
|
|
161
|
+
(void)is_map;
|
|
162
|
+
struct rv_ctx* c = ctx;
|
|
163
|
+
VALUE v = rb_ary_pop(c->stack);
|
|
164
|
+
rv_attach(c, v);
|
|
165
|
+
return 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
VALUE yep_rb_cbor_load(const char* p, size_t len, int strict) {
|
|
169
|
+
if (cr_utf8_enc == NULL) {
|
|
170
|
+
cr_utf8_enc = rb_utf8_encoding();
|
|
171
|
+
}
|
|
172
|
+
YeptrisStatus st = YEPTRIS_OK;
|
|
173
|
+
/* the decoder reads the caller's buffer directly; GC off across
|
|
174
|
+
* the pass (a compaction would move the String under p — the
|
|
175
|
+
* #160 bus error) */
|
|
176
|
+
VALUE gc_on = rb_gc_disable();
|
|
177
|
+
struct rv_ctx ctx;
|
|
178
|
+
ctx.stack = rb_ary_new();
|
|
179
|
+
ctx.root = Qnil;
|
|
180
|
+
static const yep_cbor_sink RV_SINK = {
|
|
181
|
+
NULL, rv_text, rv_int, rv_float, rv_str, rv_bytes, rv_open, rv_close,
|
|
182
|
+
};
|
|
183
|
+
yep_cbor_sink sink = RV_SINK;
|
|
184
|
+
sink.ctx = &ctx;
|
|
185
|
+
st = yep_cbor_decode_gen((const unsigned char*)p, len, strict, NULL, &sink);
|
|
186
|
+
if (RTEST(gc_on)) {
|
|
187
|
+
rb_gc_enable();
|
|
188
|
+
}
|
|
189
|
+
if (st != YEPTRIS_OK) {
|
|
190
|
+
return Qundef;
|
|
191
|
+
}
|
|
192
|
+
return ctx.root;
|
|
193
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
# libyeptris location: YEPTRIS_LIB_PATH (file or dir), then sibling
|
|
6
|
+
# checkouts. CI sets YEPTRIS_LIB_PATH (the built shared library) and
|
|
7
|
+
# YEPTRIS_SRC (the C checkout) explicitly.
|
|
8
|
+
lib_path = ENV["YEPTRIS_LIB_PATH"]
|
|
9
|
+
candidates = []
|
|
10
|
+
if lib_path
|
|
11
|
+
candidates << File.dirname(lib_path)
|
|
12
|
+
candidates << lib_path if File.directory?(lib_path)
|
|
13
|
+
end
|
|
14
|
+
candidates << File.expand_path("../../../../yeptris/build-validate/src", __dir__)
|
|
15
|
+
candidates << File.expand_path("../../../../yeptris/build/src", __dir__)
|
|
16
|
+
candidates << File.expand_path("../../../yeptris/build-validate/src", __dir__)
|
|
17
|
+
candidates << File.expand_path("../../../yeptris/build/src", __dir__)
|
|
18
|
+
|
|
19
|
+
# Source root: YEPTRIS_SRC (CI / explicit), then sibling checkouts.
|
|
20
|
+
src_roots = [ENV["YEPTRIS_SRC"]].compact
|
|
21
|
+
src_roots << File.expand_path("../../../../yeptris/src", __dir__)
|
|
22
|
+
src_roots << File.expand_path("../../../yeptris/src", __dir__)
|
|
23
|
+
src_root = src_roots.find { |d| d && File.directory?(File.join(d, "include")) }
|
|
24
|
+
abort "yeptris sources not found (set YEPTRIS_SRC)" unless src_root
|
|
25
|
+
|
|
26
|
+
$INCFLAGS << " -I#{src_root}/include -I#{src_root}/yeptris"
|
|
27
|
+
%w[build-validate/generated build/generated].each do |g|
|
|
28
|
+
d = File.join(File.dirname(src_root), g)
|
|
29
|
+
$INCFLAGS << " -I#{d}" if File.directory?(d)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
libdir = candidates.find do |d|
|
|
33
|
+
d && File.directory?(d) && Dir[File.join(d, "libyeptris*.{dylib,so,dll}")].any?
|
|
34
|
+
end
|
|
35
|
+
abort "libyeptris not found (set YEPTRIS_LIB_PATH)" unless libdir
|
|
36
|
+
|
|
37
|
+
$LIBPATH << libdir
|
|
38
|
+
# Platform gems bake a RELATIVE rpath so the bundle finds the
|
|
39
|
+
# vendored libyeptris next to itself; absolute build-time paths
|
|
40
|
+
# would dangle on user machines.
|
|
41
|
+
rpath = ENV["YEPTRIS_RPATH"] || libdir
|
|
42
|
+
$LDFLAGS << " -Wl,-rpath,#{rpath}" if RUBY_PLATFORM =~ /linux|darwin/
|
|
43
|
+
have_library("yeptris", "yep_json_string") or abort "yep_json_string not exported — rebuild libyeptris"
|
|
44
|
+
have_library("yeptris", "yeptris_visit_json") or abort "yeptris_visit_json missing"
|
|
45
|
+
|
|
46
|
+
$CFLAGS << " -O3 -fvisibility=hidden" unless RUBY_PLATFORM =~ /mswin|mingw/
|
|
47
|
+
create_makefile("yeptris/native")
|