yeptris 0.2.0.1-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yeptris
4
+ # The neutral Ruby surface (the Psych-compat namespace arrives with
5
+ # the recorder-driven Visitors in phase B; this is the yeptris-native
6
+ # face users target first).
7
+ module YAML
8
+ module_function
9
+
10
+ # Loads the FIRST document of a YAML stream as native Ruby objects.
11
+ # schema: :compat_11 selects Psych/libyaml implicit typing
12
+ # (yes/no, 0o/octal, sexagesimal); :core_12 (default) is YAML 1.2.
13
+ #
14
+ # This surface keeps the Psych contract for EVERY input — including
15
+ # JSON-shaped ones (`{"a": [1,]}` is legal flow YAML; `"1e3"` is a
16
+ # Psych String). Strict RFC 8259 semantics live on Yeptris::JSON
17
+ # (TODO.restructure/31): defaults follow proof, not benchmarks.
18
+ def load(yaml, schema: :compat_11)
19
+ yaml = Yeptris.read_input(yaml)
20
+ yaml = yaml.to_s
21
+ docs = _drain_all(yaml, schema)
22
+ docs.empty? ? nil : docs.first
23
+ end
24
+
25
+ # Psych-semantics safe_load on the native surface (issue #69 —
26
+ # the entry frameworks actually want): plain data only; a leaf
27
+ # whose class is not permitted raises Yeptris::Psych::
28
+ # DisallowedClass; aliases: false raises Yeptris::Psych::
29
+ # AliasesError on alias use. Delegates to the compat namespace's
30
+ # tree walk (correctness first; the fused drain comes later).
31
+ def safe_load(yaml, permitted_classes: [], aliases: false, schema: :compat_11)
32
+ yaml = Yeptris.read_input(yaml)
33
+ Psych.safe_load(yaml.to_s, permitted_classes: permitted_classes, aliases: aliases)
34
+ end
35
+
36
+ # Every document in the stream, in order.
37
+ def load_stream(yaml, schema: :compat_11)
38
+ yaml = Yeptris.read_input(yaml)
39
+ yaml = yaml.to_s
40
+ _drain_all(yaml, schema)
41
+ end
42
+
43
+ # The Marshal fast path when the loaded libyeptris has it (>= 0.1.11
44
+ # era builds), falling back to the columnar drain and finally the
45
+ # record drain — one code path, the fastest the library offers.
46
+ def _drain_all(yaml, schema)
47
+ if FFI::MARSHAL
48
+ result = ValueML.load_all_marshal(yaml, schema: schema, mode: :all)
49
+ return result unless result.nil?
50
+ end
51
+ if FFI::COLUMNS
52
+ ValueML.load_all_columns(yaml, schema: schema)
53
+ else
54
+ ValueML.load_all(yaml, schema: schema)
55
+ end
56
+ end
57
+
58
+ # The Psych-suite port's spelling (spec/psych/): compat typing.
59
+ def parse_yaml(yaml)
60
+ load(yaml)
61
+ end
62
+
63
+ def load_file(path, schema: :compat_11)
64
+ File.open(path, "rb") { |f| load(f, schema: schema) }
65
+ end
66
+
67
+ # Parses without materializing: the first document's root Node.
68
+ def parse(yaml, schema: :core_12)
69
+ doc = Document.parse(yaml, schema: schema)
70
+ return nil if doc.document_count.zero?
71
+
72
+ doc.root(0)
73
+ end
74
+
75
+ # Serializes a Ruby object graph to YAML via the DOM builder
76
+ # (TODO.impl/11 phase 3). Strings are emitted plain only when the
77
+ # resolver round-trips them as strings — everything else takes a
78
+ # quoted style, so dump(load(x)) == x for the scalar types.
79
+ def dump(obj, canonical: false)
80
+ BulkBuilder.dump(obj, canonical: canonical)
81
+ end
82
+
83
+
84
+ # The dump-side mirror of the Materializer's bulk drain (TODO.impl
85
+ # 15 phase D): the tree walks into one flat entry array plus one
86
+ # string blob, and yeptris_document_build raises the DOM in a
87
+ # SINGLE FFI call — per-node FFI is gone. Same semantics as the
88
+ # per-node Builder it replaces (cycle refusal, :symbol scalars,
89
+ # Date/Time iso8601, plain-only-when-it-round-trips strings),
90
+ # with plain_string? pinned to the resolver by a differential
91
+ # spec.
92
+ module BulkBuilder
93
+ SCALAR = Yeptris::FFI::BUILD_SCALAR
94
+ SEQ = Yeptris::FFI::BUILD_SEQ
95
+ MAP = Yeptris::FFI::BUILD_MAP
96
+ STOP = Yeptris::FFI::BUILD_END
97
+ STYLE_PLAIN = 1
98
+ STYLE_DQ = 3
99
+
100
+ module_function
101
+
102
+ # container entries are constant bytes — no pack per op
103
+ MAP_ENTRY = [Yeptris::FFI::BUILD_MAP, 0, 0, 0].pack("CCx2VV").freeze
104
+ SEQ_ENTRY = [Yeptris::FFI::BUILD_SEQ, 0, 0, 0].pack("CCx2VV").freeze
105
+ END_ENTRY = [Yeptris::FFI::BUILD_END, 0, 0, 0].pack("CCx2VV").freeze
106
+ CONT_ENTRY = { Yeptris::FFI::BUILD_MAP => MAP_ENTRY,
107
+ Yeptris::FFI::BUILD_SEQ => SEQ_ENTRY,
108
+ Yeptris::FFI::BUILD_END => END_ENTRY }.freeze
109
+
110
+ def dump(obj, canonical: false)
111
+ parts = []
112
+ blob = String.new(encoding: Encoding::BINARY)
113
+ off = [0]
114
+ # one pack per SCALAR; containers reuse frozen constants
115
+ emit = lambda do |op, style, o, len|
116
+ parts << (o.zero? && len.zero? && style.zero? ? CONT_ENTRY[op] :
117
+ [op, style, o, len].pack("CCx2VV"))
118
+ end
119
+ place(obj, emit, blob, off, {})
120
+ doc = Document.create
121
+ buf = ::FFI::MemoryPointer.from_string(parts.join)
122
+ bblob = ::FFI::MemoryPointer.from_string(blob)
123
+ rc = doc.build_entries(buf, parts.length, bblob, blob.bytesize)
124
+ raise DumpError, "document_build failed: #{rc}" unless rc == FFI::OK
125
+ doc.serialize(canonical: canonical)
126
+ ensure
127
+ doc&.free
128
+ end
129
+
130
+ def place(obj, emit, blob, off, seen)
131
+ case obj
132
+ when Hash
133
+ cycle_guard(obj, seen) do
134
+ emit.call(MAP, 0, 0, 0)
135
+ obj.each do |k, v|
136
+ place(key_text(k), emit, blob, off, seen)
137
+ place(v, emit, blob, off, seen)
138
+ end
139
+ emit.call(STOP, 0, 0, 0)
140
+ end
141
+ when Array
142
+ cycle_guard(obj, seen) do
143
+ emit.call(SEQ, 0, 0, 0)
144
+ obj.each { |e| place(e, emit, blob, off, seen) }
145
+ emit.call(STOP, 0, 0, 0)
146
+ end
147
+ when String then scalar(obj, plain_string?(obj), emit, blob, off)
148
+ when Symbol then scalar(":#{obj}", true, emit, blob, off)
149
+ when Integer, Float then scalar(obj.to_s, true, emit, blob, off)
150
+ when true, false then scalar(obj.to_s, true, emit, blob, off)
151
+ when nil then scalar("null", true, emit, blob, off)
152
+ when Date, Time then scalar(obj.iso8601, true, emit, blob, off)
153
+ else
154
+ raise DumpError,
155
+ "cannot dump #{obj.class}: unsupported object " \
156
+ "(custom to_yaml support lands with the Psych Visitors)"
157
+ end
158
+ end
159
+
160
+ def scalar(text, plain, emit, blob, off)
161
+ # a BINARY blob absorbs any String bytewise (String#<< on
162
+ # ASCII-8BIT is compatible with every encoding) — the old
163
+ # text.b made a throwaway copy of every scalar before the
164
+ # blob's own copy
165
+ emit.call(SCALAR, plain ? STYLE_PLAIN : STYLE_DQ, off[0], text.bytesize)
166
+ blob << text
167
+ off[0] += text.bytesize
168
+ end
169
+
170
+ def key_text(k)
171
+ k = ":#{k}" if k.is_a?(Symbol)
172
+ k.to_s
173
+ end
174
+
175
+ # A plain scalar that the compat resolver re-reads as STR stays
176
+ # plain; anything resolvable (null/bool words, int/float/timestamp
177
+ # shapes, indicators, merge '<<') takes double quotes so the
178
+ # reparse yields String again. Pinned to the C resolver's own
179
+ # verdicts by the differential spec (spec/yaml_spec.rb).
180
+ RESHAPES = %w[~ null Null NULL y Y yes Yes YES n N no No NO true True
181
+ TRUE false False FALSE on On ON off Off OFF <<].freeze
182
+ # the fast lane consults the reshape table per string: an Array
183
+ # scan of 36 words was ~a third of the whole dump walk
184
+ RESHAPES_SET = RESHAPES.each_with_object({}) { |w, h| h[w] = true }.freeze
185
+
186
+ SAFE_WORD = /\A[A-Za-z][A-Za-z0-9_\-.\/ ]*\z/
187
+
188
+ def plain_string?(s)
189
+ # fast lane: letter-started, safe characters incl. spaces — no
190
+ # number/timestamp/indicator shape is possible; only the
191
+ # reshape words need the set lookup
192
+ if SAFE_WORD.match?(s) && !s.end_with?(" ") && !RESHAPES_SET[s]
193
+ return true
194
+ end
195
+ return false if s.empty? || s != s.strip
196
+ return false if s.match?(/[\n\t]/)
197
+ c = s[0]
198
+ return false if "#,[]{}&*!|>'\"%@`".include?(c)
199
+ return false if "-?:".include?(c) && (s.length == 1 || s[1] =~ /[ \t]/)
200
+ return false if s.include?(": ") || s.end_with?(":") || s.include?(" #")
201
+ return false if RESHAPES_SET[s]
202
+ # compat's float grammar REQUIRES the dot ("1e3" re-reads as a
203
+ # String and may dump plain); ints/sexagesimals still reshape
204
+ return false if s.match?(/\A[-+]?(0|[1-9][0-9_]*)(:[0-5]?[0-9])+\z/)
205
+ return false if s.match?(/\A[-+]?(0|[1-9][0-9_]*)\z/)
206
+ return false if s.match?(/\A[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?([.:][0-9_:.]*)?\z/)
207
+ return false if s.match?(/\A[-+]?(0x[0-9a-fA-F_]+|0b[01_]+|0o?[0-7_]+)\z/)
208
+ return false if s.match?(/\A[-+]?\.(inf|Inf|INF)\z|\A\.(nan|NaN|NAN)\z/)
209
+ !s.match?(/\A\d{4}-\d\d?-\d\d?([Tt ]|$)/)
210
+ end
211
+
212
+ def cycle_guard(obj, seen)
213
+ id = obj.object_id
214
+ raise DumpError, "cycle detected: cannot dump recursive #{obj.class}" if seen[id]
215
+
216
+ seen[id] = true
217
+ out = yield
218
+ seen.delete(id)
219
+ out
220
+ end
221
+ end
222
+
223
+ # From-scratch builder over the public construction API.
224
+ module Builder
225
+ module_function
226
+
227
+ def build(doc, obj, seen = {})
228
+ case obj
229
+ when Hash then build_map(doc, obj, seen)
230
+ when Array then build_seq(doc, obj, seen)
231
+ when String then build_string(doc, obj)
232
+ when Symbol then new_scalar(doc, ":#{obj}")
233
+ when Integer, Float then new_scalar(doc, obj.to_s)
234
+ when true, false then new_scalar(doc, obj.to_s)
235
+ when nil then new_scalar(doc, "null")
236
+ when Date, Time then new_scalar(doc, obj.iso8601)
237
+ else
238
+ raise DumpError,
239
+ "cannot dump #{obj.class}: unsupported object " \
240
+ "(custom to_yaml support lands with the Psych Visitors)"
241
+ end
242
+ end
243
+
244
+ def build_map(doc, h, seen)
245
+ cycle_guard(h, seen) do
246
+ m = doc.new_mapping
247
+ h.each { |k, v| m.map_add(key_text(k), build(doc, v, seen)) }
248
+ m
249
+ end
250
+ end
251
+
252
+ def build_seq(doc, a, seen)
253
+ cycle_guard(a, seen) do
254
+ s = doc.new_sequence
255
+ a.each { |e| s.seq_add(build(doc, e, seen)) }
256
+ s
257
+ end
258
+ end
259
+
260
+ # A plain scalar that re-resolves to STR stays plain (nice
261
+ # round-trips); anything ambiguous is double-quoted so the
262
+ # reparse yields String again.
263
+ def build_string(doc, s)
264
+ n = new_scalar(doc, s)
265
+ n.tag_id == :str ? n : new_scalar(doc, s, :force_str)
266
+ end
267
+
268
+ def key_text(k)
269
+ k = ":#{k}" if k.is_a?(Symbol)
270
+ k.to_s
271
+ end
272
+
273
+ def new_scalar(doc, text, mode = nil)
274
+ doc.new_scalar(text.to_s,
275
+ mode == :force_str ? :double_quoted : :plain)
276
+ end
277
+
278
+ def cycle_guard(obj, seen)
279
+ id = obj.object_id
280
+ raise DumpError, "cycle detected: cannot dump recursive #{obj.class}" if seen[id]
281
+
282
+ seen[id] = true
283
+ out = yield
284
+ seen.delete(id)
285
+ out
286
+ end
287
+ end
288
+ end
289
+ end
data/lib/yeptris.rb ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yeptris
4
+ # The gem's version lives in the parent namespace's file — the last
5
+ # internal require (yeptris/version) retired with it.
6
+ VERSION = "0.2.0.1".freeze
7
+ # The error hierarchy lives in THIS file (the parent namespace's
8
+ # own file): nested constants do not trigger a parent-constant
9
+ # autoload, and the law forbids internal requires — defining the
10
+ # hierarchy here makes it eager by construction.
11
+
12
+ # The base error for everything this library raises deliberately.
13
+ class Error < StandardError; end
14
+
15
+ # The input is not valid YAML (or valid for the requested mode).
16
+ # message carries the C parser's line/column detail.
17
+ class ParseError < Error; end
18
+
19
+ # A handle was used after its document was freed. Raised, never a
20
+ # segfault: the Document is the sole C-memory owner and every Node
21
+ # checks liveness through it.
22
+ class FreedError < Error; end
23
+
24
+ # Building a document from a Ruby object graph hit something the
25
+ # builder refuses (cycles, unsupported objects).
26
+ class DumpError < Error; end
27
+
28
+ # Input coercion — the ONE place the input boundary is typed
29
+ # (no respond_to? duck-probing): IO-like objects read, Strings
30
+ # pass through, anything else must be stringable and is.
31
+ def self.read_input(source)
32
+ case source
33
+ when String then source
34
+ when IO, StringIO then source.read
35
+ else source.to_s
36
+ end
37
+ end
38
+
39
+ autoload :Document, "yeptris/document"
40
+ autoload :Node, "yeptris/node"
41
+ autoload :YAML, "yeptris/yaml"
42
+ autoload :JSON, "yeptris/json"
43
+ autoload :Materializer, "yeptris/materializer"
44
+ autoload :ValueML, "yeptris/valueml"
45
+ autoload :Psych, "yeptris/psych"
46
+ end
47
+
48
+ # Eager native-library resolution (leptris-ruby lesson): fail at
49
+ # require time, not at first parse. The ffi require MUST come after
50
+ # the autoload registrations — ffi.rb opens module Yeptris, and
51
+ # requiring it first would shadow the manifest (leptris-ruby#53).
52
+ begin
53
+ require "yeptris/ffi"
54
+ rescue LoadError => e
55
+ raise LoadError, <<~MSG
56
+ Yeptris could not load the native libyeptris library.
57
+ Set YEPTRIS_LIB_PATH to a libyeptris.{so,dylib,dll}, or use the
58
+ platform gem that vendors it.
59
+ (Underlying error: #{e.message})
60
+ MSG
61
+ end
62
+
63
+ # Optional C-API materializer (TODO.restructure/22): fused visit →
64
+ # Ruby objects via the Ruby C API. Feature-detected — LoadError leaves
65
+ # the FFI ladder (Marshal → columns → records) as the sole path.
66
+ begin
67
+ require "yeptris/native"
68
+ rescue LoadError
69
+ # FFI ladder only
70
+ end
data/libyeptris.so ADDED
Binary file
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yeptris
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0.1
5
+ platform: aarch64-linux
6
+ authors:
7
+ - Ribose Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ description: A Ruby YAML library over libyeptris — Psych-compatible semantics with
28
+ libleptris-class performance, and a fused native JSON materializer that outperforms
29
+ JSON.parse on JSON-shaped input.
30
+ email:
31
+ - open.source@ribose.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - README.adoc
37
+ - ext/yeptris_native/extconf.rb
38
+ - ext/yeptris_native/json_ruby.c
39
+ - ext/yeptris_native/yeptris_native.c
40
+ - lib/yeptris.rb
41
+ - lib/yeptris/document.rb
42
+ - lib/yeptris/ffi.rb
43
+ - lib/yeptris/json.rb
44
+ - lib/yeptris/materializer.rb
45
+ - lib/yeptris/native.so
46
+ - lib/yeptris/node.rb
47
+ - lib/yeptris/psych.rb
48
+ - lib/yeptris/psych/coder_shim.rb
49
+ - lib/yeptris/psych/drop_in.rb
50
+ - lib/yeptris/psych/encodable.rb
51
+ - lib/yeptris/psych/handler.rb
52
+ - lib/yeptris/psych/parser.rb
53
+ - lib/yeptris/psych/visitors.rb
54
+ - lib/yeptris/valueml.rb
55
+ - lib/yeptris/yaml.rb
56
+ - libyeptris.so
57
+ homepage: https://github.com/leptris/yeptris
58
+ licenses:
59
+ - MIT
60
+ metadata:
61
+ homepage_uri: https://github.com/leptris/yeptris
62
+ source_code_uri: https://github.com/leptris/yeptris
63
+ changelog_uri: https://github.com/leptris/yeptris/releases
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '3.1'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.5.22
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: 'The YAML counterpart of libleptris: ultra-performance YAML 1.2 for Ruby'
83
+ test_files: []