yeptris 0.6.16.1-powerpc64le-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 +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 +154 -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 +454 -0
- data/lib/yeptris/cbor.rb +130 -0
- data/lib/yeptris/document.rb +255 -0
- data/lib/yeptris/ffi.rb +415 -0
- data/lib/yeptris/json/descriptor.rb +266 -0
- data/lib/yeptris/json.rb +390 -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/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/visitors.rb +545 -0
- data/lib/yeptris/psych.rb +604 -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 +119 -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 +848 -0
- data/vendor/libyeptris/src/yeptris/cbor/encode.c +835 -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 +203 -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 +802 -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 +196 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f2c0bf8e9c352656a7c1aa4f89187c16aae129d35dc008cb2d423221406809f3
|
|
4
|
+
data.tar.gz: 1b75ebd0156e5eb559dd67f5890569755ac292895bf00976dc07286ca341e2c4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b475c0f8b52e5464f88f6dcc9244690af709d00f5c6d1248dc4c69e92e4644a678993253f4d8269b4a9d29c46031c10ab90486d19e135fa2b7a59bfedb3d5699
|
|
7
|
+
data.tar.gz: a6b7f26d39b94b5b9fdc43146cdd01d598d2617d0887af7b7e5338588af534979450696a5269f89f326a3c3abd69104356a477d788b2eb7764b0f2431f5a374e
|
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,154 @@
|
|
|
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 "dom/dom.h"
|
|
29
|
+
#include "doc.h" /* the public YeptrisDocument wrapper: ->dom */
|
|
30
|
+
#include <yeptris/cbor.h>
|
|
31
|
+
#include <yeptris/dom.h>
|
|
32
|
+
#include <yeptris/resolve.h>
|
|
33
|
+
|
|
34
|
+
static const char* cr_view(const yep_dom* d, yep_sview sv, uint32_t* len) {
|
|
35
|
+
*len = sv.len;
|
|
36
|
+
if (sv.len == 0) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
return (sv.off & YEP_SV_INPUT) ? (d->str + (sv.off & YEP_SV_OFF)) : (d->input_base + sv.off);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static VALUE cr_scalar(const yep_dom* d, const yep_dnode* n) {
|
|
43
|
+
uint32_t len = 0;
|
|
44
|
+
const char* p = cr_view(d, n->value, &len);
|
|
45
|
+
switch (n->tag_id) {
|
|
46
|
+
case YEPTRIS_TAG_NULL:
|
|
47
|
+
return Qnil;
|
|
48
|
+
case YEPTRIS_TAG_BOOL:
|
|
49
|
+
if (len == 1) { /* Psych: "y"/"n" stay Strings */
|
|
50
|
+
return rb_utf8_str_new(p, len);
|
|
51
|
+
}
|
|
52
|
+
return (len == 4 && memcmp(p, "true", 4) == 0) ? Qtrue : Qfalse;
|
|
53
|
+
case YEPTRIS_TAG_INT: {
|
|
54
|
+
char buf[32];
|
|
55
|
+
size_t cp = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1;
|
|
56
|
+
memcpy(buf, p, cp);
|
|
57
|
+
buf[cp] = '\0';
|
|
58
|
+
char* end = NULL;
|
|
59
|
+
long long v = strtoll(buf, &end, 10);
|
|
60
|
+
if (end != buf && *end == '\0' && end == buf + len && v > INT64_MIN) {
|
|
61
|
+
return LL2NUM(v);
|
|
62
|
+
}
|
|
63
|
+
return rb_utf8_str_new(p, len); /* int_or_string's fallback */
|
|
64
|
+
}
|
|
65
|
+
case YEPTRIS_TAG_FLOAT: {
|
|
66
|
+
char buf[64];
|
|
67
|
+
size_t cp = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1;
|
|
68
|
+
memcpy(buf, p, cp);
|
|
69
|
+
buf[cp] = '\0';
|
|
70
|
+
char* end = NULL;
|
|
71
|
+
double v = strtod(buf, &end);
|
|
72
|
+
if (end != buf && end != buf + len) {
|
|
73
|
+
return rb_utf8_str_new(p, len); /* float_or_string's fallback */
|
|
74
|
+
}
|
|
75
|
+
return DBL2NUM(v);
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
return rb_utf8_str_new(p, len);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static VALUE cr_walk(const yep_dom* d, uint32_t id) {
|
|
83
|
+
const yep_dnode* n = &d->nodes[id];
|
|
84
|
+
switch (n->kind) {
|
|
85
|
+
case YEP_DOM_SCALAR:
|
|
86
|
+
return cr_scalar(d, n);
|
|
87
|
+
case YEP_DOM_SEQUENCE: {
|
|
88
|
+
long cnt = (long)n->count;
|
|
89
|
+
VALUE a = rb_ary_new_capa(cnt);
|
|
90
|
+
uint32_t c = n->first_child;
|
|
91
|
+
for (long i = 0; i < cnt && c != UINT32_MAX; i++) {
|
|
92
|
+
rb_ary_push(a, cr_walk(d, c));
|
|
93
|
+
c = d->nodes[c].next_sibling;
|
|
94
|
+
}
|
|
95
|
+
return a;
|
|
96
|
+
}
|
|
97
|
+
case YEP_DOM_MAPPING: {
|
|
98
|
+
long pairs = (long)(n->count / 2);
|
|
99
|
+
VALUE h = HASH_NEW_CAPA(pairs);
|
|
100
|
+
uint32_t c = n->first_child;
|
|
101
|
+
for (long i = 0; i < pairs && c != UINT32_MAX; i++) {
|
|
102
|
+
VALUE k;
|
|
103
|
+
const yep_dnode* kn = &d->nodes[c];
|
|
104
|
+
if (kn->kind == YEP_DOM_SCALAR && kn->tag_id == YEPTRIS_TAG_STR) {
|
|
105
|
+
uint32_t klen = 0;
|
|
106
|
+
const char* kp = cr_view(d, kn->value, &klen);
|
|
107
|
+
k = cr_key_str(kp, klen);
|
|
108
|
+
c = kn->next_sibling;
|
|
109
|
+
} else {
|
|
110
|
+
k = cr_walk(d, c);
|
|
111
|
+
c = d->nodes[c].next_sibling;
|
|
112
|
+
}
|
|
113
|
+
VALUE v = (c != UINT32_MAX) ? cr_walk(d, c) : Qnil;
|
|
114
|
+
if (c != UINT32_MAX) {
|
|
115
|
+
c = d->nodes[c].next_sibling;
|
|
116
|
+
}
|
|
117
|
+
rb_hash_aset(h, k, v);
|
|
118
|
+
}
|
|
119
|
+
return h;
|
|
120
|
+
}
|
|
121
|
+
default: /* ALIAS: CBOR decode never produces one */
|
|
122
|
+
return Qnil;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
VALUE yep_rb_cbor_load(const char* p, size_t len, int strict) {
|
|
127
|
+
if (cr_utf8_enc == NULL) {
|
|
128
|
+
cr_utf8_enc = rb_utf8_encoding();
|
|
129
|
+
}
|
|
130
|
+
YeptrisStatus st = YEPTRIS_OK;
|
|
131
|
+
/* the DOM borrows the input buffer zero-copy; the walk ALLOCATES,
|
|
132
|
+
* and a GC compaction mid-walk moves the caller's String — the
|
|
133
|
+
* borrowed base dangles (the #160 CI bus error on 3.2/linux).
|
|
134
|
+
* Same discipline as the JSON walk: GC off across decode+walk */
|
|
135
|
+
VALUE gc_on = rb_gc_disable();
|
|
136
|
+
/* YeptrisDocument is a WRAPPER (doc.h) — ->dom is the tree; a raw
|
|
137
|
+
* yep_dom* cast read the wrapper's fields as the dom (the CI
|
|
138
|
+
* segfaults: docs at the wrong offset) */
|
|
139
|
+
yeptris_document* doc = (yeptris_document*)yeptris_cbor_decode(
|
|
140
|
+
p, len, strict ? YEPTRIS_CBOR_STRICT : 0, &st);
|
|
141
|
+
yep_dom* d = (doc != NULL) ? doc->dom : NULL;
|
|
142
|
+
if (d == NULL) {
|
|
143
|
+
if (RTEST(gc_on)) {
|
|
144
|
+
rb_gc_enable();
|
|
145
|
+
}
|
|
146
|
+
return Qundef;
|
|
147
|
+
}
|
|
148
|
+
VALUE v = (d->dcount > 0 && d->docs[0] != UINT32_MAX) ? cr_walk(d, d->docs[0]) : Qnil;
|
|
149
|
+
yeptris_document_free((YeptrisDocument)doc);
|
|
150
|
+
if (RTEST(gc_on)) {
|
|
151
|
+
rb_gc_enable();
|
|
152
|
+
}
|
|
153
|
+
return v;
|
|
154
|
+
}
|
|
@@ -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")
|