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
data/lib/yeptris/node.rb
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
# A node inside a document. Borrowed C memory: every call routes
|
|
7
|
+
# through the owning document's liveness check — use after free
|
|
8
|
+
# raises Yeptris::FreedError, never a segfault.
|
|
9
|
+
class Yeptris::Node
|
|
10
|
+
include Enumerable
|
|
11
|
+
|
|
12
|
+
attr_reader :c_ptr
|
|
13
|
+
|
|
14
|
+
# @api private — always constructed via Document#wrap_node (identity).
|
|
15
|
+
def initialize(c_ptr, document)
|
|
16
|
+
@c_ptr = c_ptr
|
|
17
|
+
@document = document
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def document
|
|
21
|
+
@document
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
KINDS = {
|
|
25
|
+
Yeptris::FFI::NODE_SCALAR => :scalar,
|
|
26
|
+
Yeptris::FFI::NODE_SEQUENCE => :sequence,
|
|
27
|
+
Yeptris::FFI::NODE_MAPPING => :mapping,
|
|
28
|
+
Yeptris::FFI::NODE_ALIAS => :alias,
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def kind
|
|
32
|
+
KINDS[alive { Yeptris::FFI.yeptris_node_kind(@c_ptr) }]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def scalar?
|
|
36
|
+
kind == :scalar
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def sequence?
|
|
40
|
+
kind == :sequence
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mapping?
|
|
44
|
+
kind == :mapping
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def alias?
|
|
48
|
+
kind == :alias
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Scalar content / alias name (UTF-8 String), nil for collections.
|
|
52
|
+
def value
|
|
53
|
+
len = ::FFI::MemoryPointer.new(:size_t)
|
|
54
|
+
ptr = alive { Yeptris::FFI.yeptris_node_value(@c_ptr, len) }
|
|
55
|
+
return nil if ptr.null?
|
|
56
|
+
|
|
57
|
+
ptr.read_bytes(Yeptris::FFI.read_c_size_t(len)).force_encoding(Encoding::UTF_8)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
STYLES = {
|
|
61
|
+
Yeptris::FFI::STYLE_PLAIN => :plain,
|
|
62
|
+
Yeptris::FFI::STYLE_SINGLE_QUOTED => :single_quoted,
|
|
63
|
+
Yeptris::FFI::STYLE_DOUBLE_QUOTED => :double_quoted,
|
|
64
|
+
Yeptris::FFI::STYLE_LITERAL => :literal,
|
|
65
|
+
Yeptris::FFI::STYLE_FOLDED => :folded,
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
def style
|
|
69
|
+
STYLES[alive { Yeptris::FFI.yeptris_node_style(@c_ptr) }]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Explicit tag URI when present, else nil.
|
|
73
|
+
def tag
|
|
74
|
+
len = ::FFI::MemoryPointer.new(:size_t)
|
|
75
|
+
ptr = alive { Yeptris::FFI.yeptris_node_tag(@c_ptr, len) }
|
|
76
|
+
return nil if ptr.null?
|
|
77
|
+
|
|
78
|
+
ptr.read_bytes(Yeptris::FFI.read_c_size_t(len)).force_encoding(Encoding::UTF_8)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def anchor
|
|
82
|
+
len = ::FFI::MemoryPointer.new(:size_t)
|
|
83
|
+
ptr = alive { Yeptris::FFI.yeptris_node_anchor(@c_ptr, len) }
|
|
84
|
+
return nil if ptr.null?
|
|
85
|
+
|
|
86
|
+
ptr.read_bytes(Yeptris::FFI.read_c_size_t(len)).force_encoding(Encoding::UTF_8)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def alias_target
|
|
90
|
+
t = alive { Yeptris::FFI.yeptris_node_alias_target(@c_ptr) }
|
|
91
|
+
@document.wrap_node(t)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# ---- typed scalar reads (tag id decides eligibility) ----
|
|
95
|
+
|
|
96
|
+
def to_i
|
|
97
|
+
out = ::FFI::MemoryPointer.new(:int64)
|
|
98
|
+
status = alive { Yeptris::FFI.yeptris_node_int(@c_ptr, out) }
|
|
99
|
+
Yeptris::FFI.check_status(status, "yeptris_node_int")
|
|
100
|
+
out.read_int64
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_f
|
|
104
|
+
out = ::FFI::MemoryPointer.new(:double)
|
|
105
|
+
status = alive { Yeptris::FFI.yeptris_node_float(@c_ptr, out) }
|
|
106
|
+
Yeptris::FFI.check_status(status, "yeptris_node_float")
|
|
107
|
+
out.read_double
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_bool
|
|
111
|
+
out = ::FFI::MemoryPointer.new(:int)
|
|
112
|
+
status = alive { Yeptris::FFI.yeptris_node_bool(@c_ptr, out) }
|
|
113
|
+
Yeptris::FFI.check_status(status, "yeptris_node_bool")
|
|
114
|
+
out.read_int != 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
TAGS = {
|
|
118
|
+
Yeptris::FFI::TAG_STR => :str,
|
|
119
|
+
Yeptris::FFI::TAG_INT => :int,
|
|
120
|
+
Yeptris::FFI::TAG_FLOAT => :float,
|
|
121
|
+
Yeptris::FFI::TAG_BOOL => :bool,
|
|
122
|
+
Yeptris::FFI::TAG_NULL => :null,
|
|
123
|
+
Yeptris::FFI::TAG_TIMESTAMP => :timestamp,
|
|
124
|
+
Yeptris::FFI::TAG_BINARY => :binary,
|
|
125
|
+
}.freeze
|
|
126
|
+
|
|
127
|
+
def tag_id
|
|
128
|
+
TAGS[alive { Yeptris::FFI.yeptris_node_tag_id(@c_ptr) }]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# ---- sequence access ----
|
|
132
|
+
|
|
133
|
+
def size
|
|
134
|
+
case kind
|
|
135
|
+
when :sequence then seq_count
|
|
136
|
+
when :mapping then map_count
|
|
137
|
+
else 1
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def seq_count
|
|
142
|
+
alive { Yeptris::FFI.yeptris_node_seq_count(@c_ptr) }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def seq_at(index)
|
|
146
|
+
@document.wrap_node(alive { Yeptris::FFI.yeptris_node_seq_at(@c_ptr, index) })
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def each
|
|
150
|
+
return enum_for(:each) unless block_given?
|
|
151
|
+
raise Yeptris::Error, "#each is for sequences" unless sequence?
|
|
152
|
+
|
|
153
|
+
if Yeptris::FFI::CHILDREN_DRAIN
|
|
154
|
+
# one bulk walk (#168): the per-index seq_at loop paid i sibling
|
|
155
|
+
# hops per element — n^2/2 over an 80k-row sequence
|
|
156
|
+
n = alive { Yeptris::FFI.yeptris_node_seq_count(@c_ptr) }
|
|
157
|
+
buf = ::FFI::MemoryPointer.new(:pointer, n)
|
|
158
|
+
alive { Yeptris::FFI.yeptris_node_children(@c_ptr, buf, n) }
|
|
159
|
+
step = buf.type_size
|
|
160
|
+
n.times { |i| yield @document.wrap_node(buf.get_pointer(i * step)) }
|
|
161
|
+
else
|
|
162
|
+
(0...seq_count).each { |i| yield seq_at(i) }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# ---- mapping access ----
|
|
167
|
+
|
|
168
|
+
def map_count
|
|
169
|
+
alive { Yeptris::FFI.yeptris_node_map_count(@c_ptr) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def [](key)
|
|
173
|
+
raise Yeptris::Error, "#[] is for mappings" unless mapping?
|
|
174
|
+
|
|
175
|
+
key = key.to_s
|
|
176
|
+
@document.wrap_node(
|
|
177
|
+
alive { Yeptris::FFI.yeptris_node_map_get(@c_ptr, key, key.bytesize) }
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def key?(key)
|
|
182
|
+
!self[key].nil?
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Ordered [key, value] pairs.
|
|
186
|
+
def each_pair
|
|
187
|
+
return enum_for(:each_pair) unless block_given?
|
|
188
|
+
raise Yeptris::Error, "#each_pair is for mappings" unless mapping?
|
|
189
|
+
|
|
190
|
+
if Yeptris::FFI::CHILDREN_DRAIN
|
|
191
|
+
# key,value interleaved in one walk — the same #168 cure
|
|
192
|
+
pairs = alive { Yeptris::FFI.yeptris_node_map_count(@c_ptr) }
|
|
193
|
+
buf = ::FFI::MemoryPointer.new(:pointer, pairs * 2)
|
|
194
|
+
alive { Yeptris::FFI.yeptris_node_children(@c_ptr, buf, pairs * 2) }
|
|
195
|
+
step = buf.type_size
|
|
196
|
+
pairs.times do |p|
|
|
197
|
+
yield @document.wrap_node(buf.get_pointer((2 * p) * step)),
|
|
198
|
+
@document.wrap_node(buf.get_pointer((2 * p + 1) * step))
|
|
199
|
+
end
|
|
200
|
+
else
|
|
201
|
+
k = ::FFI::MemoryPointer.new(:pointer)
|
|
202
|
+
v = ::FFI::MemoryPointer.new(:pointer)
|
|
203
|
+
(0...map_count).each do |i|
|
|
204
|
+
rc = alive { Yeptris::FFI.yeptris_node_map_at(@c_ptr, i, k, v) }
|
|
205
|
+
next unless rc.zero?
|
|
206
|
+
|
|
207
|
+
yield @document.wrap_node(k.read_pointer), @document.wrap_node(v.read_pointer)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def keys
|
|
213
|
+
# explicit block: two-arg yield + Symbol#to_proc would call
|
|
214
|
+
# key.first(value) instead of taking the pair
|
|
215
|
+
each_pair.map { |k, _v| k }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# ---- construction (TODO.impl/11 phase 3; errors raise) ----
|
|
219
|
+
|
|
220
|
+
def map_add(key, node)
|
|
221
|
+
key = key.to_s
|
|
222
|
+
rc = alive { Yeptris::FFI.yeptris_node_map_add(@c_ptr, key, key.bytesize, node.c_ptr) }
|
|
223
|
+
Yeptris::FFI.check_status(rc, "yeptris_node_map_add")
|
|
224
|
+
self
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# A pre-built key NODE ( Psych parity: nil keys ride the explicit
|
|
228
|
+
# `!` tag on an empty single-quoted scalar, which the byte-key form
|
|
229
|
+
# cannot carry)
|
|
230
|
+
def map_add_node(key_node, node)
|
|
231
|
+
rc = alive do
|
|
232
|
+
Yeptris::FFI.yeptris_node_map_add_node(@c_ptr, key_node.c_ptr, node.c_ptr)
|
|
233
|
+
end
|
|
234
|
+
Yeptris::FFI.check_status(rc, "yeptris_node_map_add_node")
|
|
235
|
+
self
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def map_set(key, node)
|
|
239
|
+
key = key.to_s
|
|
240
|
+
rc = alive { Yeptris::FFI.yeptris_node_map_set(@c_ptr, key, key.bytesize, node.c_ptr) }
|
|
241
|
+
Yeptris::FFI.check_status(rc, "yeptris_node_map_set")
|
|
242
|
+
self
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def map_del(key)
|
|
246
|
+
key = key.to_s
|
|
247
|
+
alive { Yeptris::FFI.yeptris_node_map_del(@c_ptr, key, key.bytesize) }.zero?
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def seq_add(node)
|
|
251
|
+
rc = alive { Yeptris::FFI.yeptris_node_seq_add(@c_ptr, node.c_ptr) }
|
|
252
|
+
Yeptris::FFI.check_status(rc, "yeptris_node_seq_add")
|
|
253
|
+
self
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Props on synthesized nodes (15's YAMLTree); values copied in.
|
|
257
|
+
def set_anchor(name)
|
|
258
|
+
alive { Yeptris::FFI.yeptris_node_set_anchor(@c_ptr, name, name.bytesize) }.zero?
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def set_tag(tag)
|
|
262
|
+
alive { Yeptris::FFI.yeptris_node_set_tag(@c_ptr, tag, tag.bytesize) }.zero?
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def seq_del(index)
|
|
266
|
+
alive { Yeptris::FFI.yeptris_node_seq_del(@c_ptr, index) }.zero?
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# ---- Ruby materialization (Psych-compatible) ----
|
|
270
|
+
|
|
271
|
+
# Materializes the subtree as native Ruby objects. Aliases preserve
|
|
272
|
+
# object identity (the same Ruby object for every reference), keys
|
|
273
|
+
# materialize with Psych's implicit typing (":sym" -> Symbol under
|
|
274
|
+
# compat), and the anchor memo makes cycles defined by anchors safe.
|
|
275
|
+
def node_id
|
|
276
|
+
alive { Yeptris::FFI.yeptris_node_id(@c_ptr) }
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# The marshal fast path (TODO.restructure/21; #178): one C call
|
|
280
|
+
# turns this subtree into Marshal 4.8 bytes — Marshal.load builds
|
|
281
|
+
# the Ruby objects in C, no per-node FFI. nil when the document
|
|
282
|
+
# carries constructs the format cannot express (merge keys,
|
|
283
|
+
# timestamps, tagged revivals): the caller walks instead. Raises
|
|
284
|
+
# ParseError on engine errors (not on the bail).
|
|
285
|
+
def marshal_fast
|
|
286
|
+
return nil unless Yeptris::FFI::MARSHAL
|
|
287
|
+
|
|
288
|
+
out_p = ::FFI::MemoryPointer.new(:pointer)
|
|
289
|
+
olen_p = ::FFI::MemoryPointer.new(:size_t)
|
|
290
|
+
st = alive { Yeptris::FFI.yeptris_marshal_node(@c_ptr, out_p, olen_p) }
|
|
291
|
+
if st == Yeptris::FFI::ERROR_UNSUPPORTED
|
|
292
|
+
nil
|
|
293
|
+
elsif st != Yeptris::FFI::OK
|
|
294
|
+
raise Yeptris::ParseError, Yeptris::FFI.last_error_message
|
|
295
|
+
else
|
|
296
|
+
bytes = out_p.read_pointer.read_bytes(Yeptris::FFI.read_c_size_t(olen_p))
|
|
297
|
+
bytes.force_encoding(Encoding::ASCII_8BIT)
|
|
298
|
+
::Marshal.load(bytes)
|
|
299
|
+
end
|
|
300
|
+
ensure
|
|
301
|
+
Yeptris::FFI.yeptris_marshal_free(out_p.read_pointer) if out_p
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def to_ruby(memo = nil)
|
|
305
|
+
# readonly documents memoize per node: a second materialization
|
|
306
|
+
# returns the SAME object (the readonly cache is the ground truth
|
|
307
|
+
# for alias identity across calls).
|
|
308
|
+
if @document.readonly?
|
|
309
|
+
rm = @document.readonly_memo
|
|
310
|
+
cached = rm[node_id]
|
|
311
|
+
return cached if cached
|
|
312
|
+
end
|
|
313
|
+
# Marshal fast path (TODO.restructure/21): one C call turns the
|
|
314
|
+
# whole subtree into Ruby objects, preserving alias identity via
|
|
315
|
+
# the object's own object-link table. Only at the TOP-LEVEL entry
|
|
316
|
+
# (memo nil): inside a recursive walk a partial marshal would
|
|
317
|
+
# materialize outside the shared memo and break alias identity.
|
|
318
|
+
# Falls back to the per-node FFI walk on constructs the format
|
|
319
|
+
# cannot express (merge keys, timestamps) and on older builds.
|
|
320
|
+
if Yeptris::FFI::MARSHAL && memo.nil?
|
|
321
|
+
result = marshal_fast
|
|
322
|
+
unless result.nil?
|
|
323
|
+
@document.readonly_memo[node_id] = result if @document.readonly?
|
|
324
|
+
return result
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
if @document.readonly?
|
|
328
|
+
return @document.readonly_memo[node_id] = to_ruby_walk({})
|
|
329
|
+
end
|
|
330
|
+
to_ruby_walk(memo || {})
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def to_ruby_walk(memo)
|
|
334
|
+
cached = memo[node_id]
|
|
335
|
+
return cached if cached
|
|
336
|
+
|
|
337
|
+
case kind
|
|
338
|
+
when :mapping
|
|
339
|
+
h = {}
|
|
340
|
+
memo[node_id] = h
|
|
341
|
+
each_pair do |k, v|
|
|
342
|
+
key = k.to_ruby_key
|
|
343
|
+
val = v.to_ruby(memo)
|
|
344
|
+
if key == "<<" && k.tag != "tag:yaml.org,2002:str"
|
|
345
|
+
# stdlib revive_hash's merge branch: the VALUE's node kind
|
|
346
|
+
# picks the arm; every merge is TypeError-guarded (a bad
|
|
347
|
+
# element keeps the whole '<<' pair literal)
|
|
348
|
+
if v.kind == :alias || v.mapping?
|
|
349
|
+
begin
|
|
350
|
+
h.merge!(val)
|
|
351
|
+
rescue ::TypeError
|
|
352
|
+
h[key] = val
|
|
353
|
+
end
|
|
354
|
+
elsif v.sequence?
|
|
355
|
+
begin
|
|
356
|
+
merged = {}
|
|
357
|
+
val.reverse_each { |e| merged.merge!(e) }
|
|
358
|
+
h.merge!(merged)
|
|
359
|
+
rescue ::TypeError
|
|
360
|
+
h[key] = val
|
|
361
|
+
end
|
|
362
|
+
else
|
|
363
|
+
h[key] = val
|
|
364
|
+
end
|
|
365
|
+
else
|
|
366
|
+
h[key] = val
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
h
|
|
370
|
+
when :sequence
|
|
371
|
+
a = []
|
|
372
|
+
memo[node_id] = a
|
|
373
|
+
each { |e| a << e.to_ruby(memo) }
|
|
374
|
+
a
|
|
375
|
+
when :alias
|
|
376
|
+
t = alias_target
|
|
377
|
+
if t.nil?
|
|
378
|
+
raise ::Yeptris::Psych::AnchorNotDefined,
|
|
379
|
+
"Unknown anchor: #{anchor}"
|
|
380
|
+
end
|
|
381
|
+
t.to_ruby(memo)
|
|
382
|
+
else
|
|
383
|
+
scalar_to_ruby
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Mapping keys: Psych semantics — a plain scalar ":name" under the
|
|
388
|
+
# compat schema scans to a Symbol; everything else materializes as
|
|
389
|
+
# the scalar itself.
|
|
390
|
+
def to_ruby_key
|
|
391
|
+
symbol? ? symbolize : to_ruby
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def scalar_to_ruby
|
|
395
|
+
return symbolize if symbol? && tag_id == :str
|
|
396
|
+
|
|
397
|
+
# stdlib deserialize's tagged-scalar arms: the class tag carries
|
|
398
|
+
# DateTime (dumped tagged — a plain timestamp re-loads as Time)
|
|
399
|
+
if tag == "!ruby/object:DateTime"
|
|
400
|
+
ts = value.to_s.sub(/ (\d)/, 'T\\1').sub(/ ([+-]\d)/, '\\1')
|
|
401
|
+
t = ::Time.xmlschema(ts)
|
|
402
|
+
return ::DateTime.civil(t.year, t.month, t.day, t.hour, t.min, t.sec,
|
|
403
|
+
Rational(t.utc_offset, 86_400)) + Rational(t.subsec, 86_400)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
case tag_id
|
|
407
|
+
when :null then nil
|
|
408
|
+
when :bool then to_bool
|
|
409
|
+
when :int then to_i
|
|
410
|
+
when :float
|
|
411
|
+
# Psych's dot-required float applies ONLY under compat_11; the
|
|
412
|
+
# core schema's regexp has an optional dot (spec 10.3.2), so
|
|
413
|
+
# core_12 exponent-only floats materialize as Floats
|
|
414
|
+
text = value.to_s
|
|
415
|
+
if @document.parse_schema == :compat_11 &&
|
|
416
|
+
!text.include?(".") && !text.include?(":") && !text.start_with?(".")
|
|
417
|
+
text
|
|
418
|
+
else
|
|
419
|
+
to_f
|
|
420
|
+
end
|
|
421
|
+
when :timestamp then ::Yeptris::Materializer.parse_timestamp(value)
|
|
422
|
+
when :binary then value.unpack1("m") # ASCII-8BIT bytes (#168)
|
|
423
|
+
else
|
|
424
|
+
# beyond int64 the C resolver leaves plain scalars :str; Psych
|
|
425
|
+
# materializes Integer (issue #31) — rebuild from the text
|
|
426
|
+
text = value.to_s
|
|
427
|
+
if style == :plain && text.length > 18 &&
|
|
428
|
+
::Yeptris::ValueML::PSYCH_INT_SHAPE.match?(text)
|
|
429
|
+
text.delete(",_").to_i
|
|
430
|
+
else
|
|
431
|
+
value
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Psych's ScalarScanner: plain ":name" (and ":", the null Symbol)
|
|
437
|
+
# scans to a Symbol; quoting defeats it.
|
|
438
|
+
def symbol?
|
|
439
|
+
style == :plain && value&.start_with?(":") && !value.start_with?("::")
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def symbolize
|
|
443
|
+
v = value
|
|
444
|
+
v.length <= 1 ? :"" : v[1..].to_sym
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def alive
|
|
450
|
+
@document.ensure_alive!
|
|
451
|
+
yield
|
|
452
|
+
end
|
|
453
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
|
|
5
|
+
module Yeptris
|
|
6
|
+
module Psych
|
|
7
|
+
# The minimal ClassLoader face the ScalarScanner port drives
|
|
8
|
+
# (stdlib's loader resolves through the class hierarchy; the
|
|
9
|
+
# scanner only needs the date class and symbolize).
|
|
10
|
+
class ClassLoader
|
|
11
|
+
def date
|
|
12
|
+
Date
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def symbolize str
|
|
16
|
+
str.to_sym
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load name
|
|
20
|
+
Object.const_get(name)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
module Psych
|
|
5
|
+
# Minimal Psych::Coder stand-in (encode_with / init_with carry
|
|
6
|
+
# these): tag + one of scalar / seq / map. map is ordered.
|
|
7
|
+
class CoderShim
|
|
8
|
+
attr_reader :type, :tag, :scalar, :seq
|
|
9
|
+
|
|
10
|
+
def initialize(class_name = nil)
|
|
11
|
+
@type = :map
|
|
12
|
+
# Psych's default for encode_with objects: their own class tag
|
|
13
|
+
@tag = class_name ? "!ruby/object:#{class_name}" : nil
|
|
14
|
+
@scalar = nil
|
|
15
|
+
@seq = nil
|
|
16
|
+
@map = nil
|
|
17
|
+
@class_name = class_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def tag=(t)
|
|
21
|
+
@tag = t
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def scalar=(value)
|
|
25
|
+
@type = :scalar
|
|
26
|
+
@scalar = value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def seq=(list)
|
|
30
|
+
@type = :seq
|
|
31
|
+
@seq = list
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def map=(hash)
|
|
35
|
+
@type = :map
|
|
36
|
+
@map = nil
|
|
37
|
+
hash&.each { |k, v| self[k] = v }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def []=(key, value)
|
|
41
|
+
@type = :map
|
|
42
|
+
@map ||= {}
|
|
43
|
+
@map[key.to_s] = value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def [](key)
|
|
47
|
+
@map&.[](key.to_s)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def each(&block)
|
|
51
|
+
@map&.each(&block)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The process-exclusive drop-in (issue #69): rebinds the top-level
|
|
4
|
+
# Psych constant to Yeptris::Psych. Once this runs, stdlib psych must
|
|
5
|
+
# NOT be loaded afterwards — its require would re-open THIS module and
|
|
6
|
+
# clobber constants ("already initialized" warnings, then
|
|
7
|
+
# NameError/NoMethodError at call time). Bundles that cannot
|
|
8
|
+
# guarantee the load order (activesupport et al.) should require
|
|
9
|
+
# "yeptris/psych" only and reference Yeptris::Psych directly.
|
|
10
|
+
require "yeptris" # the autoload declarations (Document, Node, ...) live here
|
|
11
|
+
require "yeptris/psych"
|
|
12
|
+
|
|
13
|
+
# ( Psych-stdlib, if already loaded, stays reachable as
|
|
14
|
+
# ::Psych::ORIGINAL.)
|
|
15
|
+
if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych) &&
|
|
16
|
+
!Yeptris::Psych.const_defined?(:ORIGINAL, false)
|
|
17
|
+
Yeptris::Psych.const_set(:ORIGINAL, ::Psych)
|
|
18
|
+
end
|
|
19
|
+
# class_eval reaches Module-private methods WITHOUT send (the law: no
|
|
20
|
+
# send to private methods); remove_const has no public form, and the
|
|
21
|
+
# rebind is this file's whole purpose
|
|
22
|
+
Object.class_eval { remove_const(:Psych) } if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych)
|
|
23
|
+
::Psych = Yeptris::Psych
|
|
24
|
+
|
|
25
|
+
# #167: third-party gems feature-gate on Psych::VERSION at require
|
|
26
|
+
# time (mechanize 2.14.1's cookie-jar reads it in a class body). The
|
|
27
|
+
# face reports the STDLIB version it replaces: ORIGINAL's when psych
|
|
28
|
+
# was preloaded, else the default-gem spec's version (local lookup —
|
|
29
|
+
# requiring stdlib psych post-rebind is censored by the header).
|
|
30
|
+
unless Yeptris::Psych.const_defined?(:VERSION, false)
|
|
31
|
+
version =
|
|
32
|
+
if Yeptris::Psych.const_defined?(:ORIGINAL, false)
|
|
33
|
+
Yeptris::Psych::ORIGINAL::VERSION
|
|
34
|
+
else
|
|
35
|
+
spec = begin
|
|
36
|
+
::Gem.loaded_specs["psych"] ||
|
|
37
|
+
::Gem::Specification.find_by_name("psych", ::Gem::Requirement.default)
|
|
38
|
+
rescue ::StandardError, ::Gem::Exception, ::LoadError
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
spec&.version&.to_s
|
|
42
|
+
end
|
|
43
|
+
Yeptris::Psych.const_set(:VERSION, version) if version
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# yaml-first boot order: ::YAML still references the ORIGINAL stdlib
|
|
47
|
+
# module object, whose singleton methods route through stdlib's parse
|
|
48
|
+
# stream — the UTF-8 tagging (and every other normalization) never
|
|
49
|
+
# runs (#135's spec/sdo discriminant). Delegate the original's public
|
|
50
|
+
# singleton surface to the Yeptris face so BOTH constants behave
|
|
51
|
+
# identically regardless of which module object a caller holds.
|
|
52
|
+
# ORIGINAL exists only when stdlib psych was already loaded (the
|
|
53
|
+
# drop-in-before-psych order needs no delegation — nothing references
|
|
54
|
+
# the original yet).
|
|
55
|
+
if ::Yeptris::Psych.const_defined?(:ORIGINAL, false)
|
|
56
|
+
::Psych::ORIGINAL.singleton_class.class_eval do
|
|
57
|
+
::Psych::ORIGINAL.singleton_methods(false).each do |m|
|
|
58
|
+
define_method(m) do |*args, **kwargs, &block|
|
|
59
|
+
::Yeptris::Psych.public_send(m, *args, **kwargs, &block)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# TODO.restructure/23 — the typed opt-in marker for arbitrary-object
|
|
4
|
+
# dump/load. Classes include this module and implement
|
|
5
|
+
# encode_with(coder) / init_with(coder) (Psych's coder protocol).
|
|
6
|
+
# The visitors call those methods directly; the library never uses
|
|
7
|
+
# respond_to?, never reads or writes instance variables from outside
|
|
8
|
+
# the object's public surface (the encapsulation law).
|
|
9
|
+
module Yeptris
|
|
10
|
+
module Psych
|
|
11
|
+
module Encodable
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
module Psych
|
|
5
|
+
# The event-level API (TODO.impl/15 phase E): the abstract base
|
|
6
|
+
# Psych::Parser dispatches onto. Every event a YAML stream can
|
|
7
|
+
# produce is a method; the arg shapes are Psych's own (verified
|
|
8
|
+
# differentially against the stdlib recorder in the specs).
|
|
9
|
+
class Handler
|
|
10
|
+
# Scalar styles — the same integers Psych::Nodes::Scalar uses,
|
|
11
|
+
# and the same values YeptrisEventRecord.style carries.
|
|
12
|
+
ANY = 0
|
|
13
|
+
PLAIN = 1
|
|
14
|
+
SINGLE_QUOTED = 2
|
|
15
|
+
DOUBLE_QUOTED = 3
|
|
16
|
+
LITERAL = 4
|
|
17
|
+
FOLDED = 5
|
|
18
|
+
|
|
19
|
+
# Collection styles (Psych::Nodes::Sequence / Mapping).
|
|
20
|
+
BLOCK = 1
|
|
21
|
+
FLOW = 2
|
|
22
|
+
|
|
23
|
+
class DumperOptions
|
|
24
|
+
attr_accessor :line_width, :indentation, :canonical
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@line_width = 0
|
|
28
|
+
@indentation = 2
|
|
29
|
+
@canonical = false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
OPTIONS = DumperOptions.new
|
|
34
|
+
|
|
35
|
+
EVENTS = %i[alias empty end_document end_mapping end_sequence end_stream
|
|
36
|
+
scalar start_document start_mapping start_sequence start_stream].freeze
|
|
37
|
+
|
|
38
|
+
# Called once per stream; +encoding+ is a Psych::Parser
|
|
39
|
+
# encoding constant (we always parse to UTF-8).
|
|
40
|
+
def start_stream(encoding); end
|
|
41
|
+
|
|
42
|
+
# +version+ is [major, minor] when %YAML declared, else nil
|
|
43
|
+
# (yeptris does not carry the directive into the event).
|
|
44
|
+
# +tag_directives+ is a list of [handle, prefix] pairs ([] for
|
|
45
|
+
# us: same directive omission). +implicit+ true without ---.
|
|
46
|
+
def start_document(version, tag_directives, implicit); end
|
|
47
|
+
|
|
48
|
+
def end_document(implicit); end
|
|
49
|
+
|
|
50
|
+
def alias(anchor); end
|
|
51
|
+
|
|
52
|
+
# +style+ is one of the scalar constants above; +plain+ /
|
|
53
|
+
# +quoted+ are the implicit-typing flags (plain: resolvable
|
|
54
|
+
# without a tag).
|
|
55
|
+
def scalar(value, anchor, tag, plain, quoted, style); end
|
|
56
|
+
|
|
57
|
+
def start_sequence(anchor, tag, implicit, style); end
|
|
58
|
+
|
|
59
|
+
def end_sequence; end
|
|
60
|
+
|
|
61
|
+
def start_mapping(anchor, tag, implicit, style); end
|
|
62
|
+
|
|
63
|
+
def end_mapping; end
|
|
64
|
+
|
|
65
|
+
def end_stream; end
|
|
66
|
+
|
|
67
|
+
# libyaml artifact (its NO_EVENT after stream end); the
|
|
68
|
+
# yeptris engine never produces one, so this never fires.
|
|
69
|
+
def empty; end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
module Handlers
|
|
73
|
+
# Captures every event as [name, args] — Psych's own Recorder
|
|
74
|
+
# shape, so recordings replay through any Handler-compatible
|
|
75
|
+
# emitter.
|
|
76
|
+
class Recorder < Handler
|
|
77
|
+
attr_reader :events
|
|
78
|
+
|
|
79
|
+
def initialize
|
|
80
|
+
@events = []
|
|
81
|
+
super()
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Handler::EVENTS.each do |event|
|
|
85
|
+
define_method(event) { |*args| @events << [event, args] }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|