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.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/README.adoc +141 -0
  3. data/ext/build_windows_native.rb +41 -0
  4. data/ext/libyeptris/extconf.rb +57 -0
  5. data/ext/yeptris_native/cbor_ruby.c +193 -0
  6. data/ext/yeptris_native/extconf.rb +47 -0
  7. data/ext/yeptris_native/json_ruby.c +354 -0
  8. data/ext/yeptris_native/yeptris_native.c +497 -0
  9. data/lib/yeptris/cbor.rb +130 -0
  10. data/lib/yeptris/document.rb +255 -0
  11. data/lib/yeptris/ffi.rb +426 -0
  12. data/lib/yeptris/json/descriptor.rb +266 -0
  13. data/lib/yeptris/json.rb +394 -0
  14. data/lib/yeptris/materializer.rb +354 -0
  15. data/lib/yeptris/native-3.4.so +0 -0
  16. data/lib/yeptris/node.rb +453 -0
  17. data/lib/yeptris/psych/class_loader.rb +24 -0
  18. data/lib/yeptris/psych/coder_shim.rb +55 -0
  19. data/lib/yeptris/psych/drop_in.rb +63 -0
  20. data/lib/yeptris/psych/encodable.rb +14 -0
  21. data/lib/yeptris/psych/handler.rb +90 -0
  22. data/lib/yeptris/psych/parser.rb +105 -0
  23. data/lib/yeptris/psych/scalar_scanner.rb +149 -0
  24. data/lib/yeptris/psych/visitors.rb +545 -0
  25. data/lib/yeptris/psych.rb +622 -0
  26. data/lib/yeptris/schema.rb +253 -0
  27. data/lib/yeptris/valueml.rb +455 -0
  28. data/lib/yeptris/yaml/descriptor.rb +56 -0
  29. data/lib/yeptris/yaml.rb +422 -0
  30. data/lib/yeptris.rb +113 -0
  31. data/libyeptris.so +0 -0
  32. data/vendor/libyeptris/CMakeLists.txt +254 -0
  33. data/vendor/libyeptris/cmake/yeptris-config.cmake.in +5 -0
  34. data/vendor/libyeptris/cmake/yeptris.pc.in +11 -0
  35. data/vendor/libyeptris/src/CMakeLists.txt +167 -0
  36. data/vendor/libyeptris/src/include/yeptris/api.h +32 -0
  37. data/vendor/libyeptris/src/include/yeptris/cbor.h +101 -0
  38. data/vendor/libyeptris/src/include/yeptris/dom.h +190 -0
  39. data/vendor/libyeptris/src/include/yeptris/emit.h +81 -0
  40. data/vendor/libyeptris/src/include/yeptris/error.h +41 -0
  41. data/vendor/libyeptris/src/include/yeptris/events.h +157 -0
  42. data/vendor/libyeptris/src/include/yeptris/json.h +45 -0
  43. data/vendor/libyeptris/src/include/yeptris/json.hpp +258 -0
  44. data/vendor/libyeptris/src/include/yeptris/jsonc_compat.h +115 -0
  45. data/vendor/libyeptris/src/include/yeptris/marshal.h +52 -0
  46. data/vendor/libyeptris/src/include/yeptris/parse.h +48 -0
  47. data/vendor/libyeptris/src/include/yeptris/plan.h +100 -0
  48. data/vendor/libyeptris/src/include/yeptris/resolve.h +71 -0
  49. data/vendor/libyeptris/src/include/yeptris/schema.h +121 -0
  50. data/vendor/libyeptris/src/include/yeptris/tape.h +123 -0
  51. data/vendor/libyeptris/src/include/yeptris/types.h +38 -0
  52. data/vendor/libyeptris/src/include/yeptris/values.h +102 -0
  53. data/vendor/libyeptris/src/include/yeptris/version.h.in +29 -0
  54. data/vendor/libyeptris/src/include/yeptris/visit.h +77 -0
  55. data/vendor/libyeptris/src/include/yeptris/yajl_compat.h +135 -0
  56. data/vendor/libyeptris/src/include/yeptris.h +23 -0
  57. data/vendor/libyeptris/src/yeptris/build.c +376 -0
  58. data/vendor/libyeptris/src/yeptris/cbor/cbor.h +25 -0
  59. data/vendor/libyeptris/src/yeptris/cbor/decode.c +981 -0
  60. data/vendor/libyeptris/src/yeptris/cbor/encode.c +835 -0
  61. data/vendor/libyeptris/src/yeptris/cbor/sink.h +58 -0
  62. data/vendor/libyeptris/src/yeptris/common/chartype.c +41 -0
  63. data/vendor/libyeptris/src/yeptris/common/chartype.h +77 -0
  64. data/vendor/libyeptris/src/yeptris/common/cpu.c +81 -0
  65. data/vendor/libyeptris/src/yeptris/common/cpu.h +40 -0
  66. data/vendor/libyeptris/src/yeptris/common/error.c +65 -0
  67. data/vendor/libyeptris/src/yeptris/common/error.h +79 -0
  68. data/vendor/libyeptris/src/yeptris/common/mutex.h +45 -0
  69. data/vendor/libyeptris/src/yeptris/common/nametab.c +275 -0
  70. data/vendor/libyeptris/src/yeptris/common/nametab.h +68 -0
  71. data/vendor/libyeptris/src/yeptris/common/port.h +74 -0
  72. data/vendor/libyeptris/src/yeptris/common/simd_text.c +49 -0
  73. data/vendor/libyeptris/src/yeptris/common/simd_text.h +295 -0
  74. data/vendor/libyeptris/src/yeptris/common/simd_text_avx2.c +534 -0
  75. data/vendor/libyeptris/src/yeptris/common/simd_text_neon.c +662 -0
  76. data/vendor/libyeptris/src/yeptris/common/simd_text_scalar.c +298 -0
  77. data/vendor/libyeptris/src/yeptris/common/string_view.c +34 -0
  78. data/vendor/libyeptris/src/yeptris/common/string_view.h +93 -0
  79. data/vendor/libyeptris/src/yeptris/doc.h +56 -0
  80. data/vendor/libyeptris/src/yeptris/dom/dom.c +1192 -0
  81. data/vendor/libyeptris/src/yeptris/dom/dom.h +312 -0
  82. data/vendor/libyeptris/src/yeptris/dom/hpool.c +107 -0
  83. data/vendor/libyeptris/src/yeptris/dom/mapindex.c +173 -0
  84. data/vendor/libyeptris/src/yeptris/dom/mapindex.h +61 -0
  85. data/vendor/libyeptris/src/yeptris/dom/mutate.c +431 -0
  86. data/vendor/libyeptris/src/yeptris/emit/emit.c +434 -0
  87. data/vendor/libyeptris/src/yeptris/emit/float/api.h +38 -0
  88. data/vendor/libyeptris/src/yeptris/emit/float/dragon.c +405 -0
  89. data/vendor/libyeptris/src/yeptris/emit/float/floatint.h +231 -0
  90. data/vendor/libyeptris/src/yeptris/emit/float/print.c +396 -0
  91. data/vendor/libyeptris/src/yeptris/emit/style.c +81 -0
  92. data/vendor/libyeptris/src/yeptris/emit/style.h +24 -0
  93. data/vendor/libyeptris/src/yeptris/emit/writer.c +816 -0
  94. data/vendor/libyeptris/src/yeptris/emit/writer.h +76 -0
  95. data/vendor/libyeptris/src/yeptris/encoding/bom.c +31 -0
  96. data/vendor/libyeptris/src/yeptris/encoding/encoding.h +63 -0
  97. data/vendor/libyeptris/src/yeptris/encoding/transcode.c +148 -0
  98. data/vendor/libyeptris/src/yeptris/encoding/utf8_validate.c +178 -0
  99. data/vendor/libyeptris/src/yeptris/events/capture.c +150 -0
  100. data/vendor/libyeptris/src/yeptris/events/capture.h +39 -0
  101. data/vendor/libyeptris/src/yeptris/events/iterparse.c +208 -0
  102. data/vendor/libyeptris/src/yeptris/events/pull.c +97 -0
  103. data/vendor/libyeptris/src/yeptris/events/push.c +104 -0
  104. data/vendor/libyeptris/src/yeptris/events/recorder.c +107 -0
  105. data/vendor/libyeptris/src/yeptris/events/values.c +527 -0
  106. data/vendor/libyeptris/src/yeptris/events/values_priv.h +51 -0
  107. data/vendor/libyeptris/src/yeptris/events/yaml_compat.c +86 -0
  108. data/vendor/libyeptris/src/yeptris/events/yaml_compat.h +90 -0
  109. data/vendor/libyeptris/src/yeptris/jsonapi/jsonc_compat.c +687 -0
  110. data/vendor/libyeptris/src/yeptris/jsonapi/yajl_compat.c +728 -0
  111. data/vendor/libyeptris/src/yeptris/marshal.c +782 -0
  112. data/vendor/libyeptris/src/yeptris/memory/allocator.c +20 -0
  113. data/vendor/libyeptris/src/yeptris/memory/allocator.h +43 -0
  114. data/vendor/libyeptris/src/yeptris/memory/arena.c +134 -0
  115. data/vendor/libyeptris/src/yeptris/memory/arena.h +52 -0
  116. data/vendor/libyeptris/src/yeptris/memory/pool.c +122 -0
  117. data/vendor/libyeptris/src/yeptris/memory/pool.h +36 -0
  118. data/vendor/libyeptris/src/yeptris/parse/engine.c +3698 -0
  119. data/vendor/libyeptris/src/yeptris/parse/engine.h +65 -0
  120. data/vendor/libyeptris/src/yeptris/parse/events.h +124 -0
  121. data/vendor/libyeptris/src/yeptris/parse/numbers.c +349 -0
  122. data/vendor/libyeptris/src/yeptris/parse/numbers.h +33 -0
  123. data/vendor/libyeptris/src/yeptris/parse/scalars.c +416 -0
  124. data/vendor/libyeptris/src/yeptris/parse/scalars.h +75 -0
  125. data/vendor/libyeptris/src/yeptris/parse.c +737 -0
  126. data/vendor/libyeptris/src/yeptris/plan.c +824 -0
  127. data/vendor/libyeptris/src/yeptris/resolve/compat11.c +334 -0
  128. data/vendor/libyeptris/src/yeptris/resolve/core12.c +143 -0
  129. data/vendor/libyeptris/src/yeptris/resolve/resolver.h +56 -0
  130. data/vendor/libyeptris/src/yeptris/resolve/tags.c +58 -0
  131. data/vendor/libyeptris/src/yeptris/scan/json.c +792 -0
  132. data/vendor/libyeptris/src/yeptris/scan/json.h +109 -0
  133. data/vendor/libyeptris/src/yeptris/scan/scan.c +463 -0
  134. data/vendor/libyeptris/src/yeptris/scan/scan.h +150 -0
  135. data/vendor/libyeptris/src/yeptris/schema.c +308 -0
  136. data/vendor/libyeptris/src/yeptris/tape.c +1308 -0
  137. data/vendor/libyeptris/src/yeptris/tape_in.h +19 -0
  138. data/vendor/libyeptris/src/yeptris/version.c +7 -0
  139. data/vendor/libyeptris/src/yeptris/visit/dom_visit.c +115 -0
  140. data/vendor/libyeptris/src/yeptris/visit/json_visit.c +293 -0
  141. data/vendor/libyeptris/src/yeptris/visit/yaml_visit.c +127 -0
  142. 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
@@ -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