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,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