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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Yeptris::Document
|
|
4
|
+
attr_reader :c_ptr
|
|
5
|
+
|
|
6
|
+
# Shared between the instance and its GC finalizer (Procs close over
|
|
7
|
+
# variables by reference) — the leptris-ruby double-free fix: the
|
|
8
|
+
# explicit free path and the finalizer both flip the same flag.
|
|
9
|
+
Freed = Struct.new(:state) # :alive | :freed
|
|
10
|
+
|
|
11
|
+
# The raw C handle — the Yeptris::Node wrappers cache by its
|
|
12
|
+
# address; stream-children wrappers (Document.without_finalizer)
|
|
13
|
+
# carry it without registering a finalizer (#182).
|
|
14
|
+
attr_reader :c_ptr
|
|
15
|
+
|
|
16
|
+
# The schema this document was parsed with (a parse property).
|
|
17
|
+
attr_reader :parse_schema
|
|
18
|
+
|
|
19
|
+
def initialize(c_ptr = nil, freed = Freed.new(:alive), parse_schema = :core_12)
|
|
20
|
+
@c_ptr = c_ptr
|
|
21
|
+
@freed = freed
|
|
22
|
+
@parse_schema = parse_schema
|
|
23
|
+
@readonly = false
|
|
24
|
+
# Strong wrapper cache keyed on the C node address: the same C node
|
|
25
|
+
# always yields the same Ruby object (identity for aliases/eql?),
|
|
26
|
+
# cleared at free — no stale entries, no GC-race weak maps.
|
|
27
|
+
@wrapper_cache = {}
|
|
28
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(c_ptr)) unless c_ptr.null?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.parse(yaml, schema: :core_12, max_depth: 0)
|
|
32
|
+
yaml = Yeptris.read_input(yaml)
|
|
33
|
+
yaml = yaml.to_s
|
|
34
|
+
status = ::FFI::MemoryPointer.new(:int)
|
|
35
|
+
doc =
|
|
36
|
+
if schema == :core_12 && max_depth.zero?
|
|
37
|
+
Yeptris::FFI.yeptris_parse(yaml, yaml.bytesize, status)
|
|
38
|
+
else
|
|
39
|
+
opts = Yeptris::FFI::ParseOptions.new
|
|
40
|
+
opts[:schema] = schema == :compat_11 ? Yeptris::FFI::SCHEMA_11_COMPAT : Yeptris::FFI::SCHEMA_12_CORE
|
|
41
|
+
opts[:max_depth] = max_depth
|
|
42
|
+
Yeptris::FFI.yeptris_parse_ex(yaml, yaml.bytesize, opts, status)
|
|
43
|
+
end
|
|
44
|
+
if doc.null?
|
|
45
|
+
# NULL + OK is the legal empty stream (no documents): nil, like
|
|
46
|
+
# stdlib. The status out-param is the discriminator — the TLS
|
|
47
|
+
# error message cannot serve (a stale error from an earlier
|
|
48
|
+
# parse persists on the thread).
|
|
49
|
+
return nil if status.read_int.zero?
|
|
50
|
+
|
|
51
|
+
raise Yeptris::ParseError,
|
|
52
|
+
"parse failed: #{Yeptris::FFI.last_error_message}"
|
|
53
|
+
end
|
|
54
|
+
wrap_schema(doc, schema)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.parse_json(json)
|
|
58
|
+
json = Yeptris.read_input(json)
|
|
59
|
+
json = json.to_s
|
|
60
|
+
doc = Yeptris::FFI.yeptris_parse_json(json, json.bytesize, nil)
|
|
61
|
+
raise Yeptris::ParseError,
|
|
62
|
+
"json parse failed: #{Yeptris::FFI.last_error_message}" if doc.null?
|
|
63
|
+
|
|
64
|
+
wrap_schema(doc, :core_12) # strict JSON is core by construction
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# An empty document for from-scratch construction (TODO.impl/11 p3).
|
|
68
|
+
def self.create
|
|
69
|
+
doc = Yeptris::FFI.yeptris_document_new
|
|
70
|
+
raise Yeptris::Error, "yeptris_document_new failed" if doc.null?
|
|
71
|
+
|
|
72
|
+
wrap(doc)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @api private
|
|
76
|
+
def self.wrap(c_ptr)
|
|
77
|
+
new(c_ptr)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The schema the document was parsed with (:core_12 / :compat_11) —
|
|
81
|
+
# host scalar policies (Psych's dot-required float) are conditioned
|
|
82
|
+
# on it. The parse constructors set the real one; plain wrap keeps
|
|
83
|
+
# the core_12 default (yeptris_parse's default).
|
|
84
|
+
def self.wrap_schema(c_ptr, schema)
|
|
85
|
+
new(c_ptr, Freed.new(:alive), schema)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# #182: the stream-children wrapper owns NOTHING (the parent
|
|
89
|
+
# Document wrapper is the sole freer of the C memory). A plain
|
|
90
|
+
# Document.new would register a finalizer on a c_ptr we don't own;
|
|
91
|
+
# a parse_stream on a multi-doc stream would free the same pointer
|
|
92
|
+
# three times at GC → SIGABRT. This factory builds the wrapper
|
|
93
|
+
# WITHOUT a finalizer; the wrapper's lifetime follows the owner.
|
|
94
|
+
def self.without_finalizer(c_ptr)
|
|
95
|
+
doc = new(c_ptr)
|
|
96
|
+
ObjectSpace.undefine_finalizer(doc)
|
|
97
|
+
doc
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def ensure_alive!
|
|
101
|
+
raise Yeptris::FreedError, "document is freed" if @freed.state == :freed
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def free
|
|
105
|
+
return if @freed.state == :freed
|
|
106
|
+
|
|
107
|
+
@freed.state = :freed
|
|
108
|
+
# stop the GC finalizer BEFORE the C free — it captures the same
|
|
109
|
+
# pointer, and its firing after this free is the double free
|
|
110
|
+
ObjectSpace.undefine_finalizer(self)
|
|
111
|
+
@wrapper_cache.clear
|
|
112
|
+
Yeptris::FFI.yeptris_document_free(@c_ptr)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def freed?
|
|
116
|
+
@freed.state == :freed
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def readonly!
|
|
120
|
+
@readonly = true
|
|
121
|
+
self
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def readonly?
|
|
125
|
+
@readonly
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @api private — memo table for readonly materialization: node ids
|
|
129
|
+
# that already produced their Ruby object keep it (leptris pattern:
|
|
130
|
+
# readonly documents never change, so the memo is forever valid).
|
|
131
|
+
def readonly_memo
|
|
132
|
+
@readonly_memo ||= {}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @api private — the single Node construction path. Query handles
|
|
136
|
+
# are transient C allocations; the wrapper cache is keyed on the
|
|
137
|
+
# STABLE node id (yeptris_node_id), so the same node always yields
|
|
138
|
+
# the same Ruby object no matter which query produced the handle.
|
|
139
|
+
def wrap_node(c_ptr)
|
|
140
|
+
ensure_alive!
|
|
141
|
+
return nil if c_ptr.null?
|
|
142
|
+
|
|
143
|
+
id = Yeptris::FFI.yeptris_node_id(c_ptr)
|
|
144
|
+
@wrapper_cache[id] ||= Yeptris::Node.new(c_ptr, self)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def document_count
|
|
148
|
+
ensure_alive!
|
|
149
|
+
Yeptris::FFI.yeptris_document_count(@c_ptr)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Root node of stream document i (0-based).
|
|
153
|
+
def root(index = 0)
|
|
154
|
+
ensure_alive!
|
|
155
|
+
wrap_node(Yeptris::FFI.yeptris_document_root(@c_ptr, index))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Bulk build (TODO.impl/15 phase D): one call raises the whole
|
|
159
|
+
# tree from a flat entry array + blob (see YAML::BulkBuilder).
|
|
160
|
+
def build_entries(entries, count, blob, blob_len)
|
|
161
|
+
ensure_alive!
|
|
162
|
+
Yeptris::FFI.yeptris_document_build(@c_ptr, entries, count, blob, blob_len)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def serialize(canonical: false, best_width: 0, explicit_doc_start: false)
|
|
166
|
+
ensure_alive!
|
|
167
|
+
len = ::FFI::MemoryPointer.new(:size_t)
|
|
168
|
+
ptr =
|
|
169
|
+
if canonical || best_width.positive? || explicit_doc_start
|
|
170
|
+
opts = Yeptris::FFI::EmitOptions.new
|
|
171
|
+
opts[:size] = Yeptris::FFI::EmitOptions.size
|
|
172
|
+
opts[:canonical] = canonical ? 1 : 0
|
|
173
|
+
opts[:best_width] = best_width
|
|
174
|
+
opts[:explicit_doc_start] = explicit_doc_start ? 1 : 0
|
|
175
|
+
Yeptris::FFI.yeptris_serialize_ex(@c_ptr, opts, len)
|
|
176
|
+
else
|
|
177
|
+
Yeptris::FFI.yeptris_serialize(@c_ptr, len)
|
|
178
|
+
end
|
|
179
|
+
Yeptris::FFI::Owned.string(ptr, len)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def serialize_json_compact
|
|
183
|
+
len = ::FFI::MemoryPointer.new(:size_t)
|
|
184
|
+
Yeptris::FFI::Owned.string(Yeptris::FFI.yeptris_serialize_json_ex(@c_ptr, len, 1), len)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def serialize_json
|
|
188
|
+
ensure_alive!
|
|
189
|
+
len = ::FFI::MemoryPointer.new(:size_t)
|
|
190
|
+
Yeptris::FFI::Owned.string(Yeptris::FFI.yeptris_serialize_json(@c_ptr, len), len)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def to_s
|
|
194
|
+
serialize
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The Ruby object graph of stream document i (Psych-compatible
|
|
198
|
+
# materialization; alias identity preserved via the memo).
|
|
199
|
+
def to_ruby(index = 0)
|
|
200
|
+
ensure_alive!
|
|
201
|
+
r = root(index)
|
|
202
|
+
r.nil? ? nil : r.to_ruby
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# ---- construction conveniences (TODO.impl/11 phase 3) ----
|
|
206
|
+
|
|
207
|
+
def new_mapping
|
|
208
|
+
ensure_alive!
|
|
209
|
+
wrap_node(Yeptris::FFI.yeptris_node_new_mapping(@c_ptr)) or
|
|
210
|
+
raise Yeptris::Error, "yeptris_node_new_mapping failed"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def new_sequence
|
|
214
|
+
ensure_alive!
|
|
215
|
+
wrap_node(Yeptris::FFI.yeptris_node_new_sequence(@c_ptr)) or
|
|
216
|
+
raise Yeptris::Error, "yeptris_node_new_sequence failed"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# style: :plain / :single_quoted / :double_quoted / :literal / :folded.
|
|
220
|
+
# The value is copied into the document (nothing is borrowed).
|
|
221
|
+
def new_scalar(text, style = :plain)
|
|
222
|
+
ensure_alive!
|
|
223
|
+
code = Yeptris::Node::STYLES.key(style) or
|
|
224
|
+
raise ArgumentError, "unknown scalar style #{style.inspect}"
|
|
225
|
+
text = text.to_s
|
|
226
|
+
n = Yeptris::FFI.yeptris_node_new_scalar(@c_ptr, text, text.bytesize, code)
|
|
227
|
+
wrap_node(n) or raise Yeptris::Error, "yeptris_node_new_scalar failed"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# An alias node: display name + the target it resolves to.
|
|
231
|
+
def new_alias(target, name)
|
|
232
|
+
ensure_alive!
|
|
233
|
+
n = Yeptris::FFI.yeptris_node_new_alias(@c_ptr, target.c_ptr, name, name.bytesize)
|
|
234
|
+
wrap_node(n) or raise Yeptris::Error, "yeptris_node_new_alias failed"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def set_root(node)
|
|
238
|
+
ensure_alive!
|
|
239
|
+
rc = Yeptris::FFI.yeptris_document_set_root(@c_ptr, node.c_ptr)
|
|
240
|
+
Yeptris::FFI.check_status(rc, "yeptris_document_set_root")
|
|
241
|
+
self
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# GC safety net (#182): the proc captures ONLY the raw pointer — no
|
|
245
|
+
# Ruby objects, no wrapper, no freed-flag struct. A finalizer that
|
|
246
|
+
# closes over the owning wrapper races that wrapper's own sweep
|
|
247
|
+
# (the classic use-after-free: freed?/free against half-collected
|
|
248
|
+
# ivars, then a garbage C pointer into yeptris_document_free →
|
|
249
|
+
# SIGABRT). Explicit #free undefines this finalizer BEFORE the C
|
|
250
|
+
# free, so the double free cannot happen; finalizers run on the
|
|
251
|
+
# main thread, so there is no cross-thread window.
|
|
252
|
+
def self.finalize(c_ptr)
|
|
253
|
+
proc { Yeptris::FFI.yeptris_document_free(c_ptr) }
|
|
254
|
+
end
|
|
255
|
+
end
|
data/lib/yeptris/ffi.rb
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Yeptris
|
|
6
|
+
# Every public C declaration, attached exactly once (the leptris-ruby
|
|
7
|
+
# seam discipline: status checking and owned-pointer reading live in
|
|
8
|
+
# check_status / read_owned_string, never hand-rolled at call sites).
|
|
9
|
+
module FFI
|
|
10
|
+
extend ::FFI::Library
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
ffi_lib [
|
|
14
|
+
ENV["YEPTRIS_LIB_PATH"],
|
|
15
|
+
File.expand_path("../../libyeptris.dylib", __dir__),
|
|
16
|
+
File.expand_path("../../libyeptris.so", __dir__),
|
|
17
|
+
File.expand_path("../../libyeptris.dll", __dir__),
|
|
18
|
+
"/usr/local/lib/libyeptris.dylib",
|
|
19
|
+
"/usr/local/lib/libyeptris.so",
|
|
20
|
+
"yeptris",
|
|
21
|
+
].compact
|
|
22
|
+
rescue LoadError => e
|
|
23
|
+
raise LoadError, <<~MSG
|
|
24
|
+
yeptris: cannot load the libyeptris library.
|
|
25
|
+
Set YEPTRIS_LIB_PATH to a libyeptris.{so,dylib,dll}, or vendor
|
|
26
|
+
the library next to the gem's lib/ directory.
|
|
27
|
+
(Underlying error: #{e.message})
|
|
28
|
+
MSG
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
# #138's class, hit again by the oiml-cs report: a stale
|
|
33
|
+
# libyeptris on the machine (the Windows base-name dedupe makes
|
|
34
|
+
# a system copy win) lacks newer symbols, and a raising attach
|
|
35
|
+
# aborts this file mid-evaluation — every constant after it
|
|
36
|
+
# never defines, so the next autoload dies with
|
|
37
|
+
# 'uninitialized constant Yeptris::FFI::NODE_SCALAR'. Attaches
|
|
38
|
+
# now record the miss and define a raising stub of the same
|
|
39
|
+
# name: the load completes, optional surfaces fail with a clear
|
|
40
|
+
# message at USE, and the ESSENTIAL self-check at the bottom of
|
|
41
|
+
# this file raises the friendly load error for truly old
|
|
42
|
+
# engines.
|
|
43
|
+
def attach_function(name, args, ret, opts = {})
|
|
44
|
+
super
|
|
45
|
+
rescue ::FFI::NotFoundError, ::FFI::TypeError
|
|
46
|
+
define_singleton_method(name) do |*_a, **_kw|
|
|
47
|
+
raise ::FFI::NotFoundError,
|
|
48
|
+
"yeptris: #{name} is unavailable — the loaded " \
|
|
49
|
+
"libyeptris is older than this gem. Update the engine " \
|
|
50
|
+
"library (or clear the stale copy shadowing it)."
|
|
51
|
+
end
|
|
52
|
+
(@missing ||= []) << name
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
typedef :pointer, :yeptris_document
|
|
59
|
+
typedef :pointer, :yeptris_node
|
|
60
|
+
typedef :pointer, :yeptris_status_out
|
|
61
|
+
typedef :int, :yeptris_status
|
|
62
|
+
|
|
63
|
+
# YeptrisParseOptions (parse.h): schema, max_depth, strict,
|
|
64
|
+
# tab_policy, recover. ABI-frozen field order.
|
|
65
|
+
class ParseOptions < ::FFI::Struct
|
|
66
|
+
layout :schema, :int,
|
|
67
|
+
:max_depth, :int,
|
|
68
|
+
:strict, :int,
|
|
69
|
+
:tab_policy, :int,
|
|
70
|
+
:recover, :int
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# yeptris_emit_options (emit.h): versioned by size.
|
|
74
|
+
class EmitOptions < ::FFI::Struct
|
|
75
|
+
layout :size, :uint32,
|
|
76
|
+
:canonical, :int,
|
|
77
|
+
:best_width, :int,
|
|
78
|
+
:explicit_doc_start, :int
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
attach_function :yeptris_version, [], :string
|
|
82
|
+
|
|
83
|
+
attach_function :yeptris_last_error, [:pointer, :pointer], :string
|
|
84
|
+
|
|
85
|
+
attach_function :yeptris_parse, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
86
|
+
attach_function :yeptris_parse_ex,
|
|
87
|
+
%i[pointer size_t pointer yeptris_status_out], :yeptris_document
|
|
88
|
+
attach_function :yeptris_parse_json, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
89
|
+
|
|
90
|
+
# The JSON tape (TODO.restructure/85; the JSON surface's load
|
|
91
|
+
# engine — issue #81): ONE call, three bulk columns, spans borrow
|
|
92
|
+
# the caller's string (no arena copy, no second validating parse).
|
|
93
|
+
# v2 records: numbers are spans at parse; yeptris_tape_convert
|
|
94
|
+
# materializes them in one bulk call.
|
|
95
|
+
# v3 (libyeptris 0.6.9): the interleaved records (recs) are the
|
|
96
|
+
# primary storage on the lenient route; the strict route keeps the
|
|
97
|
+
# columns eager. The layout MUST mirror yeptris_json_tape — a stale
|
|
98
|
+
# layout makes C write past the FFI buffer (a silent heap overflow
|
|
99
|
+
# that only some allocators catch; the 0.6.9.1 windows crash).
|
|
100
|
+
class JsonTape < ::FFI::Struct
|
|
101
|
+
layout :count, :size_t, :kinds, :pointer, :offs, :pointer,
|
|
102
|
+
:lens, :pointer, :recs, :pointer, :_cols_ready, :int,
|
|
103
|
+
:_rec_primary, :int, :int_min, :int64, :_src, :pointer,
|
|
104
|
+
:_srclen, :size_t, :_block, :pointer
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
attach_function :yeptris_parse_json_tape, %i[pointer size_t pointer], :int
|
|
108
|
+
attach_function :yeptris_tape_free, [:pointer], :void
|
|
109
|
+
attach_function :yeptris_tape_convert, %i[pointer size_t size_t pointer pointer], :size_t
|
|
110
|
+
|
|
111
|
+
# the compiled plan walk (#293 / TODO.restructure/87): compile a
|
|
112
|
+
# strict-JSON spec once, apply it to a parsed tape in one C pass,
|
|
113
|
+
# read the typed COLUMNAR result
|
|
114
|
+
attach_function :yeptris_plan_compile, %i[pointer size_t pointer], :pointer
|
|
115
|
+
attach_function :yeptris_plan_free, [:pointer], :void
|
|
116
|
+
attach_function :yeptris_plan_column_count, [:pointer], :size_t
|
|
117
|
+
attach_function :yeptris_tape_plan_walk, %i[pointer pointer pointer], :pointer
|
|
118
|
+
attach_function :yeptris_plan_result_free, [:pointer], :void
|
|
119
|
+
attach_function :yeptris_plan_result_rows, [:pointer], :size_t
|
|
120
|
+
attach_function :yeptris_plan_result_kind, %i[pointer size_t], :int
|
|
121
|
+
attach_function :yeptris_plan_result_ints, %i[pointer size_t], :pointer
|
|
122
|
+
attach_function :yeptris_plan_result_floats, %i[pointer size_t], :pointer
|
|
123
|
+
attach_function :yeptris_plan_result_str_offs, %i[pointer size_t], :pointer
|
|
124
|
+
attach_function :yeptris_plan_result_str_lens, %i[pointer size_t], :pointer
|
|
125
|
+
attach_function :yeptris_plan_result_nulls, %i[pointer size_t], :pointer
|
|
126
|
+
|
|
127
|
+
# the DOM (YAML) leg of the plan walk (#293 slice three): same
|
|
128
|
+
# compiled plan over a parsed document; str columns expose
|
|
129
|
+
# (ptr,len) views into the document's regions
|
|
130
|
+
attach_function :yeptris_document_plan_walk, %i[yeptris_document pointer pointer], :pointer
|
|
131
|
+
attach_function :yeptris_plan_result_strs, %i[pointer size_t], :pointer
|
|
132
|
+
|
|
133
|
+
# yeptris_plan_str (plan.h): one string view
|
|
134
|
+
class PlanStr < ::FFI::Struct
|
|
135
|
+
layout :p, :pointer, :len, :size_t
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
attach_function :yeptris_document_free, [:yeptris_document], :void
|
|
139
|
+
attach_function :yeptris_document_count, [:yeptris_document], :size_t
|
|
140
|
+
attach_function :yeptris_document_root, [:yeptris_document, :size_t], :yeptris_node
|
|
141
|
+
|
|
142
|
+
# construction (TODO.impl/11 phase 3)
|
|
143
|
+
attach_function :yeptris_document_new, [], :yeptris_document
|
|
144
|
+
attach_function :yeptris_document_set_root,
|
|
145
|
+
%i[yeptris_document yeptris_node], :int
|
|
146
|
+
attach_function :yeptris_node_new_mapping, [:yeptris_document], :yeptris_node
|
|
147
|
+
attach_function :yeptris_node_new_sequence, [:yeptris_document], :yeptris_node
|
|
148
|
+
attach_function :yeptris_node_new_scalar,
|
|
149
|
+
%i[yeptris_document pointer size_t int], :yeptris_node
|
|
150
|
+
attach_function :yeptris_node_map_add,
|
|
151
|
+
%i[yeptris_node pointer size_t yeptris_node], :int
|
|
152
|
+
attach_function :yeptris_node_map_add_node,
|
|
153
|
+
%i[yeptris_node yeptris_node yeptris_node], :int
|
|
154
|
+
attach_function :yeptris_node_map_set,
|
|
155
|
+
%i[yeptris_node pointer size_t yeptris_node], :int
|
|
156
|
+
attach_function :yeptris_node_map_del, %i[yeptris_node pointer size_t], :int
|
|
157
|
+
attach_function :yeptris_node_seq_add, %i[yeptris_node yeptris_node], :int
|
|
158
|
+
attach_function :yeptris_node_seq_del, %i[yeptris_node size_t], :int
|
|
159
|
+
attach_function :yeptris_node_set_anchor, %i[yeptris_node pointer size_t], :int
|
|
160
|
+
attach_function :yeptris_node_set_tag, %i[yeptris_node pointer size_t], :int
|
|
161
|
+
attach_function :yeptris_node_new_alias,
|
|
162
|
+
%i[yeptris_document yeptris_node pointer size_t], :yeptris_node
|
|
163
|
+
|
|
164
|
+
attach_function :yeptris_node_kind, [:yeptris_node], :int
|
|
165
|
+
attach_function :yeptris_node_id, [:yeptris_node], :uint32
|
|
166
|
+
attach_function :yeptris_node_value, %i[yeptris_node pointer], :pointer
|
|
167
|
+
attach_function :yeptris_node_style, [:yeptris_node], :int
|
|
168
|
+
attach_function :yeptris_node_tag, %i[yeptris_node pointer], :pointer
|
|
169
|
+
attach_function :yeptris_node_anchor, %i[yeptris_node pointer], :pointer
|
|
170
|
+
attach_function :yeptris_node_alias_target, [:yeptris_node], :yeptris_node
|
|
171
|
+
attach_function :yeptris_node_tag_id, [:yeptris_node], :int
|
|
172
|
+
attach_function :yeptris_node_int, %i[yeptris_node pointer], :yeptris_status
|
|
173
|
+
attach_function :yeptris_node_float, %i[yeptris_node pointer], :yeptris_status
|
|
174
|
+
attach_function :yeptris_node_bool, %i[yeptris_node pointer], :yeptris_status
|
|
175
|
+
attach_function :yeptris_node_seq_count, [:yeptris_node], :size_t
|
|
176
|
+
attach_function :yeptris_node_seq_at, %i[yeptris_node size_t], :yeptris_node
|
|
177
|
+
attach_function :yeptris_node_children,
|
|
178
|
+
%i[yeptris_node pointer size_t], :size_t
|
|
179
|
+
attach_function :yeptris_node_map_count, [:yeptris_node], :size_t
|
|
180
|
+
attach_function :yeptris_node_map_get, %i[yeptris_node pointer size_t], :yeptris_node
|
|
181
|
+
attach_function :yeptris_node_map_at,
|
|
182
|
+
%i[yeptris_node size_t pointer pointer], :int
|
|
183
|
+
|
|
184
|
+
attach_function :yeptris_tag_uri, [:int], :string
|
|
185
|
+
|
|
186
|
+
# recorder (TODO.impl/12): bulk records + string arena, one drain
|
|
187
|
+
attach_function :yeptris_recorder_new, [], :pointer
|
|
188
|
+
attach_function :yeptris_recorder_new_ex, [:int], :pointer
|
|
189
|
+
attach_function :yeptris_recorder_feed,
|
|
190
|
+
%i[pointer pointer size_t int], :int
|
|
191
|
+
attach_function :yeptris_recorder_records, %i[pointer pointer], :pointer
|
|
192
|
+
attach_function :yeptris_recorder_arena, %i[pointer pointer], :pointer
|
|
193
|
+
attach_function :yeptris_recorder_free, [:pointer], :void
|
|
194
|
+
|
|
195
|
+
# value stream (TODO.impl/15 phase F): one drain of pre-converted
|
|
196
|
+
# typed values — the materialization fast path
|
|
197
|
+
attach_function :yeptris_value_drain,
|
|
198
|
+
%i[pointer size_t int pointer pointer pointer pointer], :int
|
|
199
|
+
attach_function :yeptris_value_free, %i[pointer pointer], :void
|
|
200
|
+
|
|
201
|
+
# Columnar drain (libyeptris > 0.1.1): the same stream as parallel
|
|
202
|
+
# typed buffers, one carved allocation. Feature-detected — the
|
|
203
|
+
# record API is the fallback on older libraries.
|
|
204
|
+
COLUMNS = begin
|
|
205
|
+
attach_function :yeptris_value_drain_columns, %i[pointer size_t int pointer], :int
|
|
206
|
+
attach_function :yeptris_value_free_columns, [:pointer], :void
|
|
207
|
+
true
|
|
208
|
+
rescue ::FFI::NotFoundError
|
|
209
|
+
false
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
class ValueColumns < ::FFI::Struct
|
|
213
|
+
layout :count, :size_t, :arena_len, :size_t,
|
|
214
|
+
:payloads, :pointer, :offs, :pointer, :lens, :pointer,
|
|
215
|
+
:kinds, :pointer, :tags, :pointer, :is_keys, :pointer,
|
|
216
|
+
:bools, :pointer, :arena, :pointer
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Marshal 4.8 emission (TODO.restructure/21): the C side converts
|
|
220
|
+
# value records directly into Ruby's wire format; the binding
|
|
221
|
+
# materializes the whole graph with one core-C Marshal.load call
|
|
222
|
+
# instead of walking per-value records in pure Ruby. Same feature-
|
|
223
|
+
# detect discipline as the columnar drain (older libraries fall
|
|
224
|
+
# back to the record walk).
|
|
225
|
+
MARSHAL_ALL_DOCS = 0
|
|
226
|
+
MARSHAL_FIRST_DOC = 1
|
|
227
|
+
attach_function :yeptris_marshal,
|
|
228
|
+
%i[pointer size_t int int pointer pointer], :int
|
|
229
|
+
attach_function :yeptris_marshal_node,
|
|
230
|
+
%i[yeptris_node pointer pointer], :int
|
|
231
|
+
attach_function :yeptris_marshal_free, [:pointer], :void
|
|
232
|
+
# The bulk child drain (#168's quadratic): O(n) iteration for
|
|
233
|
+
# #each/#each_pair. Engines without it fall back to the per-index
|
|
234
|
+
# walk (correct, quadratic).
|
|
235
|
+
CHILDREN_DRAIN = !(@missing ||= []).include?(:yeptris_node_children)
|
|
236
|
+
MARSHAL = !(@missing ||= []).include?(:yeptris_marshal_node)
|
|
237
|
+
|
|
238
|
+
# bulk build (TODO.impl/15 phase D): one call raises a document
|
|
239
|
+
BUILD_SCALAR = 1
|
|
240
|
+
BUILD_SEQ = 2
|
|
241
|
+
BUILD_MAP = 3
|
|
242
|
+
BUILD_END = 4
|
|
243
|
+
BUILD_TAG = 5
|
|
244
|
+
|
|
245
|
+
# the ABI-pinned shape; the bulk builder packs these bytes
|
|
246
|
+
# directly (12 per entry)
|
|
247
|
+
class BuildEntry < ::FFI::Struct
|
|
248
|
+
layout op: :uint8, style: :uint8, reserved: :uint16, off: :uint32, len: :uint32
|
|
249
|
+
end
|
|
250
|
+
attach_function :yeptris_document_build,
|
|
251
|
+
%i[yeptris_document pointer size_t pointer size_t], :int
|
|
252
|
+
|
|
253
|
+
attach_function :yeptris_serialize, %i[yeptris_document pointer], :pointer
|
|
254
|
+
attach_function :yeptris_serialize_ex,
|
|
255
|
+
%i[yeptris_document pointer pointer], :pointer
|
|
256
|
+
attach_function :yeptris_serialize_json, %i[yeptris_document pointer], :pointer
|
|
257
|
+
|
|
258
|
+
# compact JSON generation (JSON.generate's shape); absent on
|
|
259
|
+
# libraries before v0.2.5 — the JSON surface feature-detects
|
|
260
|
+
JSON_EX = begin
|
|
261
|
+
attach_function :yeptris_serialize_json_ex, %i[yeptris_document pointer int], :pointer
|
|
262
|
+
true
|
|
263
|
+
rescue ::FFI::NotFoundError
|
|
264
|
+
false
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# CBOR (RFC 8949, TODO.cbor): absent on libraries before the
|
|
268
|
+
# codec's release — the CBOR module feature-detects
|
|
269
|
+
attach_function :yeptris_cbor_decode, %i[pointer size_t uint32 yeptris_status_out],
|
|
270
|
+
:yeptris_document
|
|
271
|
+
callback :cbor_item_cb, %i[pointer yeptris_document size_t], :int
|
|
272
|
+
attach_function :yeptris_cbor_decode_sequence,
|
|
273
|
+
%i[pointer size_t uint32 cbor_item_cb pointer yeptris_status_out], :size_t
|
|
274
|
+
attach_function :yeptris_cbor_encode, %i[yeptris_document uint32 pointer], :pointer
|
|
275
|
+
attach_function :yeptris_cbor_encode_sequence,
|
|
276
|
+
%i[pointer size_t uint32 pointer], :pointer
|
|
277
|
+
CBOR_EX = !(@missing ||= []).include?(:yeptris_cbor_decode)
|
|
278
|
+
|
|
279
|
+
# Owned char* results (serialize*): one reader, freed exactly once.
|
|
280
|
+
# The release goes through yeptris_free (libyeptris's own
|
|
281
|
+
# allocator-matching free) because ffi's :free attach has no
|
|
282
|
+
# Windows process-symbol fallback — libyeptris.dll doesn't export
|
|
283
|
+
# libc free, so resolving :free against it fails on Windows.
|
|
284
|
+
# A raised attach ABORTS this file mid-evaluation — everything
|
|
285
|
+
# after it (NODE_SCALAR and the kind constants below) never
|
|
286
|
+
# defines, and a lazily autoloaded node.rb then dies with
|
|
287
|
+
# 'uninitialized constant' (#138). Every fallback attaches too.
|
|
288
|
+
YEPTRIS_FREE = begin
|
|
289
|
+
attach_function :yeptris_free, [:pointer], :void
|
|
290
|
+
:direct
|
|
291
|
+
rescue ::FFI::NotFoundError
|
|
292
|
+
begin
|
|
293
|
+
# libyeptris < v0.6.6: POSIX resolves :free through the
|
|
294
|
+
# process symbol table, so the libc attach still works there.
|
|
295
|
+
attach_function :yeptris_free, :free, [:pointer], :void
|
|
296
|
+
:libc
|
|
297
|
+
rescue ::FFI::NotFoundError
|
|
298
|
+
# no owned-buffer release on this lib+platform combination;
|
|
299
|
+
# Owned.string leaks instead of killing the whole file
|
|
300
|
+
:none
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# C out-params are size_t — native width (8 everywhere we ship
|
|
305
|
+
# except ILP32 armv7, where it is 4). The ffi gem (1.17.4) has no
|
|
306
|
+
# read_size_t, so dispatch on the measured type size: read_uint64
|
|
307
|
+
# on a 4-byte :size_t buffer ran out of bounds on arm-linux (the
|
|
308
|
+
# 0.6.18.1 armv7 smoke, valueml.rb:118).
|
|
309
|
+
SIZE_T_WIDTH = ::FFI.type_size(:size_t)
|
|
310
|
+
|
|
311
|
+
def self.read_c_size_t(ptr)
|
|
312
|
+
SIZE_T_WIDTH == 8 ? ptr.read_uint64 : ptr.read_uint32
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
module Owned
|
|
316
|
+
module_function
|
|
317
|
+
|
|
318
|
+
# Reads a NUL-terminated malloc'd C string into an Encoding
|
|
319
|
+
# UTF_8 String, then frees the buffer. len_out (nullable) is a
|
|
320
|
+
# MemoryPointer carrying the byte length from the producing call.
|
|
321
|
+
def string(ptr, len_out = nil)
|
|
322
|
+
return nil if ptr.null?
|
|
323
|
+
|
|
324
|
+
len = len_out && Yeptris::FFI.read_c_size_t(len_out)
|
|
325
|
+
s = if len && len > 0
|
|
326
|
+
ptr.read_bytes(len).force_encoding(Encoding::UTF_8)
|
|
327
|
+
else
|
|
328
|
+
ptr.read_string.force_encoding(Encoding::UTF_8)
|
|
329
|
+
end
|
|
330
|
+
::Yeptris::FFI.yeptris_free(ptr) if ::Yeptris::FFI::YEPTRIS_FREE != :none
|
|
331
|
+
s
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
module_function
|
|
336
|
+
|
|
337
|
+
# Non-OK status -> ParseError carrying the C error channel's
|
|
338
|
+
# message with line/column. NULL-document failures route through
|
|
339
|
+
# here too (parse detail lives on the same channel).
|
|
340
|
+
def check_status(status, action)
|
|
341
|
+
return if status.zero?
|
|
342
|
+
|
|
343
|
+
raise Yeptris::ParseError, "#{action} failed: #{last_error_message}"
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def last_error_message
|
|
347
|
+
line = ::FFI::MemoryPointer.new(:uint32)
|
|
348
|
+
col = ::FFI::MemoryPointer.new(:uint32)
|
|
349
|
+
msg = yeptris_last_error(line, col)
|
|
350
|
+
detail = msg.to_s
|
|
351
|
+
l = line.read_uint32
|
|
352
|
+
c = col.read_uint32
|
|
353
|
+
l.positive? ? "#{detail} at line #{l}, column #{c}" : detail
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Pinned enum values (test_abi): the constants the binding relies
|
|
357
|
+
# on without a C header at runtime.
|
|
358
|
+
NODE_SCALAR = 0
|
|
359
|
+
NODE_SEQUENCE = 1
|
|
360
|
+
NODE_MAPPING = 2
|
|
361
|
+
NODE_ALIAS = 3
|
|
362
|
+
|
|
363
|
+
STYLE_PLAIN = 1
|
|
364
|
+
STYLE_SINGLE_QUOTED = 2
|
|
365
|
+
STYLE_DOUBLE_QUOTED = 3
|
|
366
|
+
STYLE_LITERAL = 4
|
|
367
|
+
STYLE_FOLDED = 5
|
|
368
|
+
|
|
369
|
+
TAG_STR = 0
|
|
370
|
+
TAG_INT = 1
|
|
371
|
+
TAG_FLOAT = 2
|
|
372
|
+
TAG_BOOL = 3
|
|
373
|
+
TAG_NULL = 4
|
|
374
|
+
TAG_TIMESTAMP = 5
|
|
375
|
+
TAG_BINARY = 8
|
|
376
|
+
|
|
377
|
+
# the schema-descriptor API (issue #238; TODO.restructure/83)
|
|
378
|
+
DESC_ABI = 1
|
|
379
|
+
SCHEMA_ERROR = 9
|
|
380
|
+
|
|
381
|
+
class DescNode < ::FFI::Struct
|
|
382
|
+
layout :wire_name, :pointer, :kind, :uint8, :type_tag, :uint8,
|
|
383
|
+
:flags, :uint16, :child_index, :uint32, :child_count, :uint32,
|
|
384
|
+
:reserved, :uint32
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
class SchemaColumn < ::FFI::Struct
|
|
388
|
+
layout :data, :pointer, :capacity, :uint32, :count, :uint32
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
attach_function :yeptris_schema_load,
|
|
392
|
+
%i[pointer size_t int pointer uint32 uint32 pointer], :int
|
|
393
|
+
|
|
394
|
+
SCHEMA_12_CORE = 0
|
|
395
|
+
SCHEMA_11_COMPAT = 1
|
|
396
|
+
|
|
397
|
+
OK = 0
|
|
398
|
+
ERROR_PARSE = 1
|
|
399
|
+
ERROR_MEMORY = 2
|
|
400
|
+
ERROR_DEPTH = 3
|
|
401
|
+
ERROR_ENCODING = 4
|
|
402
|
+
ERROR_IO = 5
|
|
403
|
+
ERROR_ARG = 6
|
|
404
|
+
ERROR_UNSUPPORTED = 7
|
|
405
|
+
ERROR_INTERNAL = 8
|
|
406
|
+
# The load-time essentials (the read path + the ownership
|
|
407
|
+
# contract): absent on engines far older than the optional
|
|
408
|
+
# surfaces — raise the friendly error once every constant has
|
|
409
|
+
# fully defined, so no partial-module state leaks into autoloads
|
|
410
|
+
# (the #138 class, closed at the root).
|
|
411
|
+
ESSENTIAL = %i[
|
|
412
|
+
yeptris_version yeptris_last_error yeptris_parse yeptris_parse_ex
|
|
413
|
+
yeptris_parse_json yeptris_document_free yeptris_document_count
|
|
414
|
+
yeptris_document_root yeptris_node_kind yeptris_node_value
|
|
415
|
+
yeptris_node_tag_id yeptris_node_style yeptris_document_new
|
|
416
|
+
yeptris_document_set_root yeptris_serialize yeptris_serialize_ex
|
|
417
|
+
].freeze
|
|
418
|
+
missing_core = ESSENTIAL & (@missing ||= [])
|
|
419
|
+
raise ::FFI::NotFoundError,
|
|
420
|
+
"yeptris: the loaded libyeptris is older than this gem " \
|
|
421
|
+
"requires (missing: #{missing_core.join(', ')}). Update the " \
|
|
422
|
+
"engine library, or clear the stale copy shadowing it." \
|
|
423
|
+
unless missing_core.empty?
|
|
424
|
+
|
|
425
|
+
end
|
|
426
|
+
end
|