yeptris 0.1.0
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 +59 -0
- data/lib/yeptris/document.rb +209 -0
- data/lib/yeptris/error.rb +19 -0
- data/lib/yeptris/ffi.rb +216 -0
- data/lib/yeptris/materializer.rb +338 -0
- data/lib/yeptris/node.rb +320 -0
- data/lib/yeptris/psych/coder_shim.rb +55 -0
- data/lib/yeptris/psych/handler.rb +90 -0
- data/lib/yeptris/psych/parser.rb +105 -0
- data/lib/yeptris/psych/visitors.rb +364 -0
- data/lib/yeptris/psych.rb +315 -0
- data/lib/yeptris/valueml.rb +218 -0
- data/lib/yeptris/version.rb +5 -0
- data/lib/yeptris/yaml.rb +247 -0
- data/lib/yeptris.rb +29 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1f9152c76936119e3fcc83cc8510e15debf741cc25d22229b225d1023f330cec
|
|
4
|
+
data.tar.gz: 6845a69bcb669246e5445601e117a9abfcd96200476fcfd1c41e9b5040775615
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1927807c5fbb83fbc986d17a35ed35e6c888b52e045482e1cd96d4733b8aff7a9aa6238dd1b11c80c3f9e2da795b81eaa01c75b11861acffff64ca1bfbdb579b
|
|
7
|
+
data.tar.gz: b85ef72350379c4a6e1b5b9ca8fc76aaf0901ea533c05c2dca6223b1eb48679faefe94880bb3d2eb8df6f57dd584c82777af3cffaa37fe3274467b58d562c1b0
|
data/README.adoc
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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 native library is vendored in the platform gems (TODO.impl/15
|
|
11
|
+
phase D). For development against a local build:
|
|
12
|
+
|
|
13
|
+
....
|
|
14
|
+
# the sibling C checkout: ~/src/leptris/yeptris
|
|
15
|
+
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DYEPTRIS_BUILD_SHARED=ON
|
|
16
|
+
cmake --build build
|
|
17
|
+
|
|
18
|
+
cd ~/src/leptris/yeptris-ruby
|
|
19
|
+
YEPTRIS_LIB_PATH=../yeptris/build/src/libyeptris.dylib bundle exec rspec
|
|
20
|
+
....
|
|
21
|
+
|
|
22
|
+
Without `YEPTRIS_LIB_PATH` the spec helper falls back to a vendored
|
|
23
|
+
`lib/platform/<tag>/` copy, then to the sibling checkout's
|
|
24
|
+
`build-validate` — any `libyeptris.{so,dylib,dll}` path works.
|
|
25
|
+
|
|
26
|
+
== Usage
|
|
27
|
+
|
|
28
|
+
[source,ruby]
|
|
29
|
+
----
|
|
30
|
+
require "yeptris"
|
|
31
|
+
|
|
32
|
+
Yeptris::YAML.load("name: yeptris\nrating: 10\n")
|
|
33
|
+
# => {"name" => "yeptris", "rating" => 10}
|
|
34
|
+
|
|
35
|
+
Yeptris::YAML.dump("name" => "yeptris", tags: [:yaml, :fast])
|
|
36
|
+
|
|
37
|
+
doc = Yeptris::Document.parse(config_yaml)
|
|
38
|
+
doc.root["server"]["port"].to_i
|
|
39
|
+
doc.serialize
|
|
40
|
+
----
|
|
41
|
+
|
|
42
|
+
`Yeptris::YAML.load` defaults to Psych's YAML 1.1 implicit typing
|
|
43
|
+
(`yes` is `true`, `017` is octal); pass `schema: :core_12` for YAML
|
|
44
|
+
1.2 core semantics. Dump builds through the library's DOM mutation
|
|
45
|
+
API, so the writer's sizing/escape machinery applies to synthesized
|
|
46
|
+
trees unchanged.
|
|
47
|
+
|
|
48
|
+
Handles are document-scoped: `Document#free` releases everything
|
|
49
|
+
(one C call), a GC finalizer backs it up, and any use after free
|
|
50
|
+
raises `Yeptris::FreedError` — never a segfault.
|
|
51
|
+
|
|
52
|
+
== Roadmap
|
|
53
|
+
|
|
54
|
+
* Phase B: recorder-driven Ruby materialization (bulk event records,
|
|
55
|
+
the FFI tax paid per chunk, not per node) + the `Yeptris::Psych`
|
|
56
|
+
drop-in namespace and the ported Psych suite.
|
|
57
|
+
* Phase C: `.tml` corpora, readonly mode, YAMLTree dumps.
|
|
58
|
+
* Phase D: vendored precompiled platform gems, lockstep versioning
|
|
59
|
+
with the C library, symbol audit task.
|
|
@@ -0,0 +1,209 @@
|
|
|
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
|
+
def initialize(c_ptr = nil, freed = Freed.new(:alive))
|
|
12
|
+
@c_ptr = c_ptr
|
|
13
|
+
@freed = freed
|
|
14
|
+
@readonly = false
|
|
15
|
+
# Strong wrapper cache keyed on the C node address: the same C node
|
|
16
|
+
# always yields the same Ruby object (identity for aliases/eql?),
|
|
17
|
+
# cleared at free — no stale entries, no GC-race weak maps.
|
|
18
|
+
@wrapper_cache = {}
|
|
19
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(c_ptr, freed)) unless c_ptr.null?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.parse(yaml, schema: :core_12, max_depth: 0)
|
|
23
|
+
yaml = yaml.read if yaml.respond_to?(:read)
|
|
24
|
+
yaml = yaml.to_s
|
|
25
|
+
doc =
|
|
26
|
+
if schema == :core_12 && max_depth.zero?
|
|
27
|
+
Yeptris::FFI.yeptris_parse(yaml, yaml.bytesize, nil)
|
|
28
|
+
else
|
|
29
|
+
opts = Yeptris::FFI::ParseOptions.new
|
|
30
|
+
opts[:schema] = schema == :compat_11 ? Yeptris::FFI::SCHEMA_11_COMPAT : Yeptris::FFI::SCHEMA_12_CORE
|
|
31
|
+
opts[:max_depth] = max_depth
|
|
32
|
+
Yeptris::FFI.yeptris_parse_ex(yaml, yaml.bytesize, opts, nil)
|
|
33
|
+
end
|
|
34
|
+
if doc.null?
|
|
35
|
+
# the status out-param is skipped: the thread-local error
|
|
36
|
+
# channel carries the failure detail (measurable on small docs)
|
|
37
|
+
raise Yeptris::ParseError,
|
|
38
|
+
"parse failed: #{Yeptris::FFI.last_error_message}"
|
|
39
|
+
end
|
|
40
|
+
wrap(doc)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.parse_json(json)
|
|
44
|
+
json = json.read if json.respond_to?(:read)
|
|
45
|
+
json = json.to_s
|
|
46
|
+
doc = Yeptris::FFI.yeptris_parse_json(json, json.bytesize, nil)
|
|
47
|
+
raise Yeptris::ParseError,
|
|
48
|
+
"json parse failed: #{Yeptris::FFI.last_error_message}" if doc.null?
|
|
49
|
+
|
|
50
|
+
wrap(doc)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# An empty document for from-scratch construction (TODO.impl/11 p3).
|
|
54
|
+
def self.create
|
|
55
|
+
doc = Yeptris::FFI.yeptris_document_new
|
|
56
|
+
raise Yeptris::Error, "yeptris_document_new failed" if doc.null?
|
|
57
|
+
|
|
58
|
+
wrap(doc)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @api private
|
|
62
|
+
def self.wrap(c_ptr)
|
|
63
|
+
new(c_ptr)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def ensure_alive!
|
|
67
|
+
raise Yeptris::FreedError, "document is freed" if @freed.state == :freed
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def free
|
|
71
|
+
return if @freed.state == :freed
|
|
72
|
+
|
|
73
|
+
@freed.state = :freed
|
|
74
|
+
@wrapper_cache.clear
|
|
75
|
+
Yeptris::FFI.yeptris_document_free(@c_ptr)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def freed?
|
|
79
|
+
@freed.state == :freed
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def readonly!
|
|
83
|
+
@readonly = true
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def readonly?
|
|
88
|
+
@readonly
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @api private — memo table for readonly materialization: node ids
|
|
92
|
+
# that already produced their Ruby object keep it (leptris pattern:
|
|
93
|
+
# readonly documents never change, so the memo is forever valid).
|
|
94
|
+
def readonly_memo
|
|
95
|
+
@readonly_memo ||= {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @api private — the single Node construction path. Query handles
|
|
99
|
+
# are transient C allocations; the wrapper cache is keyed on the
|
|
100
|
+
# STABLE node id (yeptris_node_id), so the same node always yields
|
|
101
|
+
# the same Ruby object no matter which query produced the handle.
|
|
102
|
+
def wrap_node(c_ptr)
|
|
103
|
+
ensure_alive!
|
|
104
|
+
return nil if c_ptr.null?
|
|
105
|
+
|
|
106
|
+
id = Yeptris::FFI.yeptris_node_id(c_ptr)
|
|
107
|
+
@wrapper_cache[id] ||= Yeptris::Node.new(c_ptr, self)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def document_count
|
|
111
|
+
ensure_alive!
|
|
112
|
+
Yeptris::FFI.yeptris_document_count(@c_ptr)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Root node of stream document i (0-based).
|
|
116
|
+
def root(index = 0)
|
|
117
|
+
ensure_alive!
|
|
118
|
+
wrap_node(Yeptris::FFI.yeptris_document_root(@c_ptr, index))
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Bulk build (TODO.impl/15 phase D): one call raises the whole
|
|
122
|
+
# tree from a flat entry array + blob (see YAML::BulkBuilder).
|
|
123
|
+
def build_entries(entries, count, blob, blob_len)
|
|
124
|
+
ensure_alive!
|
|
125
|
+
Yeptris::FFI.yeptris_document_build(@c_ptr, entries, count, blob, blob_len)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def serialize(canonical: false, best_width: 0)
|
|
129
|
+
ensure_alive!
|
|
130
|
+
len = ::FFI::MemoryPointer.new(:uint64)
|
|
131
|
+
ptr =
|
|
132
|
+
if canonical || best_width.positive?
|
|
133
|
+
opts = Yeptris::FFI::EmitOptions.new
|
|
134
|
+
opts[:size] = Yeptris::FFI::EmitOptions.size
|
|
135
|
+
opts[:canonical] = canonical ? 1 : 0
|
|
136
|
+
opts[:best_width] = best_width
|
|
137
|
+
Yeptris::FFI.yeptris_serialize_ex(@c_ptr, opts, len)
|
|
138
|
+
else
|
|
139
|
+
Yeptris::FFI.yeptris_serialize(@c_ptr, len)
|
|
140
|
+
end
|
|
141
|
+
Yeptris::FFI::Owned.string(ptr, len)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def serialize_json
|
|
145
|
+
ensure_alive!
|
|
146
|
+
len = ::FFI::MemoryPointer.new(:uint64)
|
|
147
|
+
Yeptris::FFI::Owned.string(Yeptris::FFI.yeptris_serialize_json(@c_ptr, len), len)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def to_s
|
|
151
|
+
serialize
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The Ruby object graph of stream document i (Psych-compatible
|
|
155
|
+
# materialization; alias identity preserved via the memo).
|
|
156
|
+
def to_ruby(index = 0)
|
|
157
|
+
ensure_alive!
|
|
158
|
+
r = root(index)
|
|
159
|
+
r.nil? ? nil : r.to_ruby
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# ---- construction conveniences (TODO.impl/11 phase 3) ----
|
|
163
|
+
|
|
164
|
+
def new_mapping
|
|
165
|
+
ensure_alive!
|
|
166
|
+
wrap_node(Yeptris::FFI.yeptris_node_new_mapping(@c_ptr)) or
|
|
167
|
+
raise Yeptris::Error, "yeptris_node_new_mapping failed"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def new_sequence
|
|
171
|
+
ensure_alive!
|
|
172
|
+
wrap_node(Yeptris::FFI.yeptris_node_new_sequence(@c_ptr)) or
|
|
173
|
+
raise Yeptris::Error, "yeptris_node_new_sequence failed"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# style: :plain / :single_quoted / :double_quoted / :literal / :folded.
|
|
177
|
+
# The value is copied into the document (nothing is borrowed).
|
|
178
|
+
def new_scalar(text, style = :plain)
|
|
179
|
+
ensure_alive!
|
|
180
|
+
code = Yeptris::Node::STYLES.key(style) or
|
|
181
|
+
raise ArgumentError, "unknown scalar style #{style.inspect}"
|
|
182
|
+
text = text.to_s
|
|
183
|
+
n = Yeptris::FFI.yeptris_node_new_scalar(@c_ptr, text, text.bytesize, code)
|
|
184
|
+
wrap_node(n) or raise Yeptris::Error, "yeptris_node_new_scalar failed"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# An alias node: display name + the target it resolves to.
|
|
188
|
+
def new_alias(target, name)
|
|
189
|
+
ensure_alive!
|
|
190
|
+
n = Yeptris::FFI.yeptris_node_new_alias(@c_ptr, target.c_ptr, name, name.bytesize)
|
|
191
|
+
wrap_node(n) or raise Yeptris::Error, "yeptris_node_new_alias failed"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def set_root(node)
|
|
195
|
+
ensure_alive!
|
|
196
|
+
rc = Yeptris::FFI.yeptris_document_set_root(@c_ptr, node.c_ptr)
|
|
197
|
+
Yeptris::FFI.check_status(rc, "yeptris_document_set_root")
|
|
198
|
+
self
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# GC safety net: an explicit #free already ran is fine; a miss here
|
|
202
|
+
# frees C memory that would otherwise leak.
|
|
203
|
+
def self.finalize(c_ptr, freed)
|
|
204
|
+
proc do
|
|
205
|
+
Yeptris::FFI.yeptris_document_free(c_ptr) if freed.state == :alive
|
|
206
|
+
freed.state = :freed
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
# The base error for everything this library raises deliberately.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# The input is not valid YAML (or valid for the requested mode).
|
|
8
|
+
# message carries the C parser's line/column detail.
|
|
9
|
+
class ParseError < Error; end
|
|
10
|
+
|
|
11
|
+
# A handle was used after its document was freed. Raised, never a
|
|
12
|
+
# segfault: the Document is the sole C-memory owner and every Node
|
|
13
|
+
# checks liveness through it.
|
|
14
|
+
class FreedError < Error; end
|
|
15
|
+
|
|
16
|
+
# Building a document from a Ruby object graph hit something the
|
|
17
|
+
# builder refuses (cycles, unsupported objects).
|
|
18
|
+
class DumpError < Error; end
|
|
19
|
+
end
|
data/lib/yeptris/ffi.rb
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
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
|
+
typedef :pointer, :yeptris_document
|
|
32
|
+
typedef :pointer, :yeptris_node
|
|
33
|
+
typedef :pointer, :yeptris_status_out
|
|
34
|
+
typedef :int, :yeptris_status
|
|
35
|
+
|
|
36
|
+
# YeptrisParseOptions (parse.h): schema, max_depth, strict,
|
|
37
|
+
# tab_policy, recover. ABI-frozen field order.
|
|
38
|
+
class ParseOptions < ::FFI::Struct
|
|
39
|
+
layout :schema, :int,
|
|
40
|
+
:max_depth, :int,
|
|
41
|
+
:strict, :int,
|
|
42
|
+
:tab_policy, :int,
|
|
43
|
+
:recover, :int
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# yeptris_emit_options (emit.h): versioned by size.
|
|
47
|
+
class EmitOptions < ::FFI::Struct
|
|
48
|
+
layout :size, :uint32,
|
|
49
|
+
:canonical, :int,
|
|
50
|
+
:best_width, :int
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
attach_function :yeptris_version, [], :string
|
|
54
|
+
|
|
55
|
+
attach_function :yeptris_last_error, [:pointer, :pointer], :string
|
|
56
|
+
|
|
57
|
+
attach_function :yeptris_parse, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
58
|
+
attach_function :yeptris_parse_ex,
|
|
59
|
+
%i[pointer size_t pointer yeptris_status_out], :yeptris_document
|
|
60
|
+
attach_function :yeptris_parse_json, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
61
|
+
|
|
62
|
+
attach_function :yeptris_document_free, [:yeptris_document], :void
|
|
63
|
+
attach_function :yeptris_document_count, [:yeptris_document], :size_t
|
|
64
|
+
attach_function :yeptris_document_root, [:yeptris_document, :size_t], :yeptris_node
|
|
65
|
+
|
|
66
|
+
# construction (TODO.impl/11 phase 3)
|
|
67
|
+
attach_function :yeptris_document_new, [], :yeptris_document
|
|
68
|
+
attach_function :yeptris_document_set_root,
|
|
69
|
+
%i[yeptris_document yeptris_node], :int
|
|
70
|
+
attach_function :yeptris_node_new_mapping, [:yeptris_document], :yeptris_node
|
|
71
|
+
attach_function :yeptris_node_new_sequence, [:yeptris_document], :yeptris_node
|
|
72
|
+
attach_function :yeptris_node_new_scalar,
|
|
73
|
+
%i[yeptris_document pointer size_t int], :yeptris_node
|
|
74
|
+
attach_function :yeptris_node_map_add,
|
|
75
|
+
%i[yeptris_node pointer size_t yeptris_node], :int
|
|
76
|
+
attach_function :yeptris_node_map_set,
|
|
77
|
+
%i[yeptris_node pointer size_t yeptris_node], :int
|
|
78
|
+
attach_function :yeptris_node_map_del, %i[yeptris_node pointer size_t], :int
|
|
79
|
+
attach_function :yeptris_node_seq_add, %i[yeptris_node yeptris_node], :int
|
|
80
|
+
attach_function :yeptris_node_seq_del, %i[yeptris_node size_t], :int
|
|
81
|
+
attach_function :yeptris_node_set_anchor, %i[yeptris_node pointer size_t], :int
|
|
82
|
+
attach_function :yeptris_node_set_tag, %i[yeptris_node pointer size_t], :int
|
|
83
|
+
attach_function :yeptris_node_new_alias,
|
|
84
|
+
%i[yeptris_document yeptris_node pointer size_t], :yeptris_node
|
|
85
|
+
|
|
86
|
+
attach_function :yeptris_node_kind, [:yeptris_node], :int
|
|
87
|
+
attach_function :yeptris_node_id, [:yeptris_node], :uint32
|
|
88
|
+
attach_function :yeptris_node_value, %i[yeptris_node pointer], :pointer
|
|
89
|
+
attach_function :yeptris_node_style, [:yeptris_node], :int
|
|
90
|
+
attach_function :yeptris_node_tag, %i[yeptris_node pointer], :pointer
|
|
91
|
+
attach_function :yeptris_node_anchor, %i[yeptris_node pointer], :pointer
|
|
92
|
+
attach_function :yeptris_node_alias_target, [:yeptris_node], :yeptris_node
|
|
93
|
+
attach_function :yeptris_node_tag_id, [:yeptris_node], :int
|
|
94
|
+
attach_function :yeptris_node_int, %i[yeptris_node pointer], :yeptris_status
|
|
95
|
+
attach_function :yeptris_node_float, %i[yeptris_node pointer], :yeptris_status
|
|
96
|
+
attach_function :yeptris_node_bool, %i[yeptris_node pointer], :yeptris_status
|
|
97
|
+
attach_function :yeptris_node_seq_count, [:yeptris_node], :size_t
|
|
98
|
+
attach_function :yeptris_node_seq_at, %i[yeptris_node size_t], :yeptris_node
|
|
99
|
+
attach_function :yeptris_node_map_count, [:yeptris_node], :size_t
|
|
100
|
+
attach_function :yeptris_node_map_get, %i[yeptris_node pointer size_t], :yeptris_node
|
|
101
|
+
attach_function :yeptris_node_map_at,
|
|
102
|
+
%i[yeptris_node size_t pointer pointer], :int
|
|
103
|
+
|
|
104
|
+
attach_function :yeptris_tag_uri, [:int], :string
|
|
105
|
+
|
|
106
|
+
# recorder (TODO.impl/12): bulk records + string arena, one drain
|
|
107
|
+
attach_function :yeptris_recorder_new, [], :pointer
|
|
108
|
+
attach_function :yeptris_recorder_new_ex, [:int], :pointer
|
|
109
|
+
attach_function :yeptris_recorder_feed,
|
|
110
|
+
%i[pointer pointer size_t int], :int
|
|
111
|
+
attach_function :yeptris_recorder_records, %i[pointer pointer], :pointer
|
|
112
|
+
attach_function :yeptris_recorder_arena, %i[pointer pointer], :pointer
|
|
113
|
+
attach_function :yeptris_recorder_free, [:pointer], :void
|
|
114
|
+
|
|
115
|
+
# value stream (TODO.impl/15 phase F): one drain of pre-converted
|
|
116
|
+
# typed values — the materialization fast path
|
|
117
|
+
attach_function :yeptris_value_drain,
|
|
118
|
+
%i[pointer size_t int pointer pointer pointer pointer], :int
|
|
119
|
+
attach_function :yeptris_value_free, %i[pointer pointer], :void
|
|
120
|
+
|
|
121
|
+
# bulk build (TODO.impl/15 phase D): one call raises a document
|
|
122
|
+
BUILD_SCALAR = 1
|
|
123
|
+
BUILD_SEQ = 2
|
|
124
|
+
BUILD_MAP = 3
|
|
125
|
+
BUILD_END = 4
|
|
126
|
+
|
|
127
|
+
# the ABI-pinned shape; the bulk builder packs these bytes
|
|
128
|
+
# directly (12 per entry)
|
|
129
|
+
class BuildEntry < ::FFI::Struct
|
|
130
|
+
layout op: :uint8, style: :uint8, reserved: :uint16, off: :uint32, len: :uint32
|
|
131
|
+
end
|
|
132
|
+
attach_function :yeptris_document_build,
|
|
133
|
+
%i[yeptris_document pointer size_t pointer size_t], :int
|
|
134
|
+
|
|
135
|
+
attach_function :yeptris_serialize, %i[yeptris_document pointer], :pointer
|
|
136
|
+
attach_function :yeptris_serialize_ex,
|
|
137
|
+
%i[yeptris_document pointer pointer], :pointer
|
|
138
|
+
attach_function :yeptris_serialize_json, %i[yeptris_document pointer], :pointer
|
|
139
|
+
|
|
140
|
+
# Owned char* results (serialize*): one reader, freed exactly once.
|
|
141
|
+
# The buffers are plain malloc'd C memory (the header contract says
|
|
142
|
+
# "caller frees"), so the release is libc free.
|
|
143
|
+
attach_function :c_free, :free, [:pointer], :void
|
|
144
|
+
|
|
145
|
+
module Owned
|
|
146
|
+
module_function
|
|
147
|
+
|
|
148
|
+
# Reads a NUL-terminated malloc'd C string into an Encoding
|
|
149
|
+
# UTF_8 String, then frees the buffer. len_out (nullable) is a
|
|
150
|
+
# MemoryPointer carrying the byte length from the producing call.
|
|
151
|
+
def string(ptr, len_out = nil)
|
|
152
|
+
return nil if ptr.null?
|
|
153
|
+
|
|
154
|
+
len = len_out&.read_uint64
|
|
155
|
+
s = if len && len > 0
|
|
156
|
+
ptr.read_bytes(len).force_encoding(Encoding::UTF_8)
|
|
157
|
+
else
|
|
158
|
+
ptr.read_string.force_encoding(Encoding::UTF_8)
|
|
159
|
+
end
|
|
160
|
+
::Yeptris::FFI.c_free(ptr)
|
|
161
|
+
s
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
module_function
|
|
166
|
+
|
|
167
|
+
# Non-OK status -> ParseError carrying the C error channel's
|
|
168
|
+
# message with line/column. NULL-document failures route through
|
|
169
|
+
# here too (parse detail lives on the same channel).
|
|
170
|
+
def check_status(status, action)
|
|
171
|
+
return if status.zero?
|
|
172
|
+
|
|
173
|
+
raise Yeptris::ParseError, "#{action} failed: #{last_error_message}"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def last_error_message
|
|
177
|
+
line = ::FFI::MemoryPointer.new(:uint32)
|
|
178
|
+
col = ::FFI::MemoryPointer.new(:uint32)
|
|
179
|
+
msg = yeptris_last_error(line, col)
|
|
180
|
+
detail = msg.to_s
|
|
181
|
+
l = line.read_uint32
|
|
182
|
+
c = col.read_uint32
|
|
183
|
+
l.positive? ? "#{detail} at line #{l}, column #{c}" : detail
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Pinned enum values (test_abi): the constants the binding relies
|
|
187
|
+
# on without a C header at runtime.
|
|
188
|
+
NODE_SCALAR = 0
|
|
189
|
+
NODE_SEQUENCE = 1
|
|
190
|
+
NODE_MAPPING = 2
|
|
191
|
+
NODE_ALIAS = 3
|
|
192
|
+
|
|
193
|
+
STYLE_PLAIN = 1
|
|
194
|
+
STYLE_SINGLE_QUOTED = 2
|
|
195
|
+
STYLE_DOUBLE_QUOTED = 3
|
|
196
|
+
STYLE_LITERAL = 4
|
|
197
|
+
STYLE_FOLDED = 5
|
|
198
|
+
|
|
199
|
+
TAG_STR = 0
|
|
200
|
+
TAG_INT = 1
|
|
201
|
+
TAG_FLOAT = 2
|
|
202
|
+
TAG_BOOL = 3
|
|
203
|
+
TAG_NULL = 4
|
|
204
|
+
TAG_TIMESTAMP = 5
|
|
205
|
+
|
|
206
|
+
SCHEMA_12_CORE = 0
|
|
207
|
+
SCHEMA_11_COMPAT = 1
|
|
208
|
+
|
|
209
|
+
OK = 0
|
|
210
|
+
ERROR_PARSE = 1
|
|
211
|
+
ERROR_MEMORY = 2
|
|
212
|
+
ERROR_DEPTH = 3
|
|
213
|
+
ERROR_ENCODING = 4
|
|
214
|
+
ERROR_ARG = 6
|
|
215
|
+
end
|
|
216
|
+
end
|