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.
- checksums.yaml +7 -0
- data/README.adoc +141 -0
- data/ext/yeptris_native/extconf.rb +47 -0
- data/ext/yeptris_native/json_ruby.c +343 -0
- data/ext/yeptris_native/yeptris_native.c +440 -0
- data/lib/yeptris/document.rb +221 -0
- data/lib/yeptris/ffi.rb +256 -0
- data/lib/yeptris/json.rb +159 -0
- data/lib/yeptris/materializer.rb +347 -0
- data/lib/yeptris/native.so +0 -0
- data/lib/yeptris/node.rb +373 -0
- data/lib/yeptris/psych/coder_shim.rb +55 -0
- data/lib/yeptris/psych/drop_in.rb +23 -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/visitors.rb +383 -0
- data/lib/yeptris/psych.rb +368 -0
- data/lib/yeptris/valueml.rb +455 -0
- data/lib/yeptris/yaml.rb +289 -0
- data/lib/yeptris.rb +70 -0
- data/libyeptris.so +0 -0
- metadata +83 -0
data/lib/yeptris/ffi.rb
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
module Yeptris
|
|
6
|
+
# Every public C declaration, attached exactly once (the leptris-ruby
|
|
7
|
+
# seam discipline: status checking and owned-pointer reading live in
|
|
8
|
+
# check_status / read_owned_string, never hand-rolled at call sites).
|
|
9
|
+
module FFI
|
|
10
|
+
extend ::FFI::Library
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
ffi_lib [
|
|
14
|
+
ENV["YEPTRIS_LIB_PATH"],
|
|
15
|
+
File.expand_path("../../libyeptris.dylib", __dir__),
|
|
16
|
+
File.expand_path("../../libyeptris.so", __dir__),
|
|
17
|
+
File.expand_path("../../libyeptris.dll", __dir__),
|
|
18
|
+
"/usr/local/lib/libyeptris.dylib",
|
|
19
|
+
"/usr/local/lib/libyeptris.so",
|
|
20
|
+
"yeptris",
|
|
21
|
+
].compact
|
|
22
|
+
rescue LoadError => e
|
|
23
|
+
raise LoadError, <<~MSG
|
|
24
|
+
yeptris: cannot load the libyeptris library.
|
|
25
|
+
Set YEPTRIS_LIB_PATH to a libyeptris.{so,dylib,dll}, or vendor
|
|
26
|
+
the library next to the gem's lib/ directory.
|
|
27
|
+
(Underlying error: #{e.message})
|
|
28
|
+
MSG
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
typedef :pointer, :yeptris_document
|
|
32
|
+
typedef :pointer, :yeptris_node
|
|
33
|
+
typedef :pointer, :yeptris_status_out
|
|
34
|
+
typedef :int, :yeptris_status
|
|
35
|
+
|
|
36
|
+
# YeptrisParseOptions (parse.h): schema, max_depth, strict,
|
|
37
|
+
# tab_policy, recover. ABI-frozen field order.
|
|
38
|
+
class ParseOptions < ::FFI::Struct
|
|
39
|
+
layout :schema, :int,
|
|
40
|
+
:max_depth, :int,
|
|
41
|
+
:strict, :int,
|
|
42
|
+
:tab_policy, :int,
|
|
43
|
+
:recover, :int
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# yeptris_emit_options (emit.h): versioned by size.
|
|
47
|
+
class EmitOptions < ::FFI::Struct
|
|
48
|
+
layout :size, :uint32,
|
|
49
|
+
:canonical, :int,
|
|
50
|
+
:best_width, :int
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
attach_function :yeptris_version, [], :string
|
|
54
|
+
|
|
55
|
+
attach_function :yeptris_last_error, [:pointer, :pointer], :string
|
|
56
|
+
|
|
57
|
+
attach_function :yeptris_parse, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
58
|
+
attach_function :yeptris_parse_ex,
|
|
59
|
+
%i[pointer size_t pointer yeptris_status_out], :yeptris_document
|
|
60
|
+
attach_function :yeptris_parse_json, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
61
|
+
|
|
62
|
+
attach_function :yeptris_document_free, [:yeptris_document], :void
|
|
63
|
+
attach_function :yeptris_document_count, [:yeptris_document], :size_t
|
|
64
|
+
attach_function :yeptris_document_root, [:yeptris_document, :size_t], :yeptris_node
|
|
65
|
+
|
|
66
|
+
# construction (TODO.impl/11 phase 3)
|
|
67
|
+
attach_function :yeptris_document_new, [], :yeptris_document
|
|
68
|
+
attach_function :yeptris_document_set_root,
|
|
69
|
+
%i[yeptris_document yeptris_node], :int
|
|
70
|
+
attach_function :yeptris_node_new_mapping, [:yeptris_document], :yeptris_node
|
|
71
|
+
attach_function :yeptris_node_new_sequence, [:yeptris_document], :yeptris_node
|
|
72
|
+
attach_function :yeptris_node_new_scalar,
|
|
73
|
+
%i[yeptris_document pointer size_t int], :yeptris_node
|
|
74
|
+
attach_function :yeptris_node_map_add,
|
|
75
|
+
%i[yeptris_node pointer size_t yeptris_node], :int
|
|
76
|
+
attach_function :yeptris_node_map_set,
|
|
77
|
+
%i[yeptris_node pointer size_t yeptris_node], :int
|
|
78
|
+
attach_function :yeptris_node_map_del, %i[yeptris_node pointer size_t], :int
|
|
79
|
+
attach_function :yeptris_node_seq_add, %i[yeptris_node yeptris_node], :int
|
|
80
|
+
attach_function :yeptris_node_seq_del, %i[yeptris_node size_t], :int
|
|
81
|
+
attach_function :yeptris_node_set_anchor, %i[yeptris_node pointer size_t], :int
|
|
82
|
+
attach_function :yeptris_node_set_tag, %i[yeptris_node pointer size_t], :int
|
|
83
|
+
attach_function :yeptris_node_new_alias,
|
|
84
|
+
%i[yeptris_document yeptris_node pointer size_t], :yeptris_node
|
|
85
|
+
|
|
86
|
+
attach_function :yeptris_node_kind, [:yeptris_node], :int
|
|
87
|
+
attach_function :yeptris_node_id, [:yeptris_node], :uint32
|
|
88
|
+
attach_function :yeptris_node_value, %i[yeptris_node pointer], :pointer
|
|
89
|
+
attach_function :yeptris_node_style, [:yeptris_node], :int
|
|
90
|
+
attach_function :yeptris_node_tag, %i[yeptris_node pointer], :pointer
|
|
91
|
+
attach_function :yeptris_node_anchor, %i[yeptris_node pointer], :pointer
|
|
92
|
+
attach_function :yeptris_node_alias_target, [:yeptris_node], :yeptris_node
|
|
93
|
+
attach_function :yeptris_node_tag_id, [:yeptris_node], :int
|
|
94
|
+
attach_function :yeptris_node_int, %i[yeptris_node pointer], :yeptris_status
|
|
95
|
+
attach_function :yeptris_node_float, %i[yeptris_node pointer], :yeptris_status
|
|
96
|
+
attach_function :yeptris_node_bool, %i[yeptris_node pointer], :yeptris_status
|
|
97
|
+
attach_function :yeptris_node_seq_count, [:yeptris_node], :size_t
|
|
98
|
+
attach_function :yeptris_node_seq_at, %i[yeptris_node size_t], :yeptris_node
|
|
99
|
+
attach_function :yeptris_node_map_count, [:yeptris_node], :size_t
|
|
100
|
+
attach_function :yeptris_node_map_get, %i[yeptris_node pointer size_t], :yeptris_node
|
|
101
|
+
attach_function :yeptris_node_map_at,
|
|
102
|
+
%i[yeptris_node size_t pointer pointer], :int
|
|
103
|
+
|
|
104
|
+
attach_function :yeptris_tag_uri, [:int], :string
|
|
105
|
+
|
|
106
|
+
# recorder (TODO.impl/12): bulk records + string arena, one drain
|
|
107
|
+
attach_function :yeptris_recorder_new, [], :pointer
|
|
108
|
+
attach_function :yeptris_recorder_new_ex, [:int], :pointer
|
|
109
|
+
attach_function :yeptris_recorder_feed,
|
|
110
|
+
%i[pointer pointer size_t int], :int
|
|
111
|
+
attach_function :yeptris_recorder_records, %i[pointer pointer], :pointer
|
|
112
|
+
attach_function :yeptris_recorder_arena, %i[pointer pointer], :pointer
|
|
113
|
+
attach_function :yeptris_recorder_free, [:pointer], :void
|
|
114
|
+
|
|
115
|
+
# value stream (TODO.impl/15 phase F): one drain of pre-converted
|
|
116
|
+
# typed values — the materialization fast path
|
|
117
|
+
attach_function :yeptris_value_drain,
|
|
118
|
+
%i[pointer size_t int pointer pointer pointer pointer], :int
|
|
119
|
+
attach_function :yeptris_value_free, %i[pointer pointer], :void
|
|
120
|
+
|
|
121
|
+
# Columnar drain (libyeptris > 0.1.1): the same stream as parallel
|
|
122
|
+
# typed buffers, one carved allocation. Feature-detected — the
|
|
123
|
+
# record API is the fallback on older libraries.
|
|
124
|
+
COLUMNS = begin
|
|
125
|
+
attach_function :yeptris_value_drain_columns, %i[pointer size_t int pointer], :int
|
|
126
|
+
attach_function :yeptris_value_free_columns, [:pointer], :void
|
|
127
|
+
true
|
|
128
|
+
rescue ::FFI::NotFoundError
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class ValueColumns < ::FFI::Struct
|
|
133
|
+
layout :count, :size_t, :arena_len, :size_t,
|
|
134
|
+
:payloads, :pointer, :offs, :pointer, :lens, :pointer,
|
|
135
|
+
:kinds, :pointer, :tags, :pointer, :is_keys, :pointer,
|
|
136
|
+
:bools, :pointer, :arena, :pointer
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Marshal 4.8 emission (TODO.restructure/21): the C side converts
|
|
140
|
+
# value records directly into Ruby's wire format; the binding
|
|
141
|
+
# materializes the whole graph with one core-C Marshal.load call
|
|
142
|
+
# instead of walking per-value records in pure Ruby. Same feature-
|
|
143
|
+
# detect discipline as the columnar drain (older libraries fall
|
|
144
|
+
# back to the record walk).
|
|
145
|
+
MARSHAL = begin
|
|
146
|
+
MARSHAL_ALL_DOCS = 0
|
|
147
|
+
MARSHAL_FIRST_DOC = 1
|
|
148
|
+
attach_function :yeptris_marshal,
|
|
149
|
+
%i[pointer size_t int int pointer pointer], :int
|
|
150
|
+
attach_function :yeptris_marshal_node,
|
|
151
|
+
%i[yeptris_node pointer pointer], :int
|
|
152
|
+
attach_function :yeptris_marshal_free, [:pointer], :void
|
|
153
|
+
true
|
|
154
|
+
rescue ::FFI::NotFoundError
|
|
155
|
+
false
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# bulk build (TODO.impl/15 phase D): one call raises a document
|
|
159
|
+
BUILD_SCALAR = 1
|
|
160
|
+
BUILD_SEQ = 2
|
|
161
|
+
BUILD_MAP = 3
|
|
162
|
+
BUILD_END = 4
|
|
163
|
+
|
|
164
|
+
# the ABI-pinned shape; the bulk builder packs these bytes
|
|
165
|
+
# directly (12 per entry)
|
|
166
|
+
class BuildEntry < ::FFI::Struct
|
|
167
|
+
layout op: :uint8, style: :uint8, reserved: :uint16, off: :uint32, len: :uint32
|
|
168
|
+
end
|
|
169
|
+
attach_function :yeptris_document_build,
|
|
170
|
+
%i[yeptris_document pointer size_t pointer size_t], :int
|
|
171
|
+
|
|
172
|
+
attach_function :yeptris_serialize, %i[yeptris_document pointer], :pointer
|
|
173
|
+
attach_function :yeptris_serialize_ex,
|
|
174
|
+
%i[yeptris_document pointer pointer], :pointer
|
|
175
|
+
attach_function :yeptris_serialize_json, %i[yeptris_document pointer], :pointer
|
|
176
|
+
|
|
177
|
+
# Owned char* results (serialize*): one reader, freed exactly once.
|
|
178
|
+
# The buffers are plain malloc'd C memory (the header contract says
|
|
179
|
+
# "caller frees"), so the release is libc free.
|
|
180
|
+
attach_function :c_free, :free, [:pointer], :void
|
|
181
|
+
|
|
182
|
+
module Owned
|
|
183
|
+
module_function
|
|
184
|
+
|
|
185
|
+
# Reads a NUL-terminated malloc'd C string into an Encoding
|
|
186
|
+
# UTF_8 String, then frees the buffer. len_out (nullable) is a
|
|
187
|
+
# MemoryPointer carrying the byte length from the producing call.
|
|
188
|
+
def string(ptr, len_out = nil)
|
|
189
|
+
return nil if ptr.null?
|
|
190
|
+
|
|
191
|
+
len = len_out&.read_uint64
|
|
192
|
+
s = if len && len > 0
|
|
193
|
+
ptr.read_bytes(len).force_encoding(Encoding::UTF_8)
|
|
194
|
+
else
|
|
195
|
+
ptr.read_string.force_encoding(Encoding::UTF_8)
|
|
196
|
+
end
|
|
197
|
+
::Yeptris::FFI.c_free(ptr)
|
|
198
|
+
s
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
module_function
|
|
203
|
+
|
|
204
|
+
# Non-OK status -> ParseError carrying the C error channel's
|
|
205
|
+
# message with line/column. NULL-document failures route through
|
|
206
|
+
# here too (parse detail lives on the same channel).
|
|
207
|
+
def check_status(status, action)
|
|
208
|
+
return if status.zero?
|
|
209
|
+
|
|
210
|
+
raise Yeptris::ParseError, "#{action} failed: #{last_error_message}"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def last_error_message
|
|
214
|
+
line = ::FFI::MemoryPointer.new(:uint32)
|
|
215
|
+
col = ::FFI::MemoryPointer.new(:uint32)
|
|
216
|
+
msg = yeptris_last_error(line, col)
|
|
217
|
+
detail = msg.to_s
|
|
218
|
+
l = line.read_uint32
|
|
219
|
+
c = col.read_uint32
|
|
220
|
+
l.positive? ? "#{detail} at line #{l}, column #{c}" : detail
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Pinned enum values (test_abi): the constants the binding relies
|
|
224
|
+
# on without a C header at runtime.
|
|
225
|
+
NODE_SCALAR = 0
|
|
226
|
+
NODE_SEQUENCE = 1
|
|
227
|
+
NODE_MAPPING = 2
|
|
228
|
+
NODE_ALIAS = 3
|
|
229
|
+
|
|
230
|
+
STYLE_PLAIN = 1
|
|
231
|
+
STYLE_SINGLE_QUOTED = 2
|
|
232
|
+
STYLE_DOUBLE_QUOTED = 3
|
|
233
|
+
STYLE_LITERAL = 4
|
|
234
|
+
STYLE_FOLDED = 5
|
|
235
|
+
|
|
236
|
+
TAG_STR = 0
|
|
237
|
+
TAG_INT = 1
|
|
238
|
+
TAG_FLOAT = 2
|
|
239
|
+
TAG_BOOL = 3
|
|
240
|
+
TAG_NULL = 4
|
|
241
|
+
TAG_TIMESTAMP = 5
|
|
242
|
+
|
|
243
|
+
SCHEMA_12_CORE = 0
|
|
244
|
+
SCHEMA_11_COMPAT = 1
|
|
245
|
+
|
|
246
|
+
OK = 0
|
|
247
|
+
ERROR_PARSE = 1
|
|
248
|
+
ERROR_MEMORY = 2
|
|
249
|
+
ERROR_DEPTH = 3
|
|
250
|
+
ERROR_ENCODING = 4
|
|
251
|
+
ERROR_IO = 5
|
|
252
|
+
ERROR_ARG = 6
|
|
253
|
+
ERROR_UNSUPPORTED = 7
|
|
254
|
+
ERROR_INTERNAL = 8
|
|
255
|
+
end
|
|
256
|
+
end
|
data/lib/yeptris/json.rb
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
# The STRICT JSON surface (TODO.restructure/31).
|
|
5
|
+
#
|
|
6
|
+
# `Yeptris::JSON.load` targets EXACT `JSON.parse` semantics — that
|
|
7
|
+
# is its parity target, spec-pinned (spec/json_parity_spec.rb).
|
|
8
|
+
# It is deliberately separate from Yeptris::YAML: the YAML surface
|
|
9
|
+
# keeps the Psych contract for every input (JSON-shaped included),
|
|
10
|
+
# so the two never drift into each other's semantics.
|
|
11
|
+
#
|
|
12
|
+
# Engines, fastest first (both exact — the parity spec runs against
|
|
13
|
+
# whichever is loaded):
|
|
14
|
+
# 1. the native materializer (fused C scan → VALUE; opt-in build)
|
|
15
|
+
# 2. the record drain + strict conversion walk (always available)
|
|
16
|
+
module JSON
|
|
17
|
+
class Error < ::Yeptris::Error; end
|
|
18
|
+
class ParseError < Error; end
|
|
19
|
+
|
|
20
|
+
# json gem 3.0 made duplicate keys an error by DEFAULT; 2.x is
|
|
21
|
+
# last-wins. The parity target is the RESOLVED json gem's own
|
|
22
|
+
# behavior — strictness follows it (issue #37, found by canon's
|
|
23
|
+
# CI where json 3.0.0 resolved while the dev box had 2.x).
|
|
24
|
+
require "json"
|
|
25
|
+
STRICT_DUPLICATE_KEYS = Gem::Version.new(::JSON::VERSION) >= Gem::Version.new("3")
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
def load(source)
|
|
30
|
+
source = ::Yeptris.read_input(source)
|
|
31
|
+
source = source.to_s
|
|
32
|
+
if defined?(::Yeptris::Native)
|
|
33
|
+
begin
|
|
34
|
+
return ::Yeptris::Native.load_json(source, STRICT_DUPLICATE_KEYS)
|
|
35
|
+
rescue ::Yeptris::ParseError => e
|
|
36
|
+
raise ParseError, e.message
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
strict_fallback(source)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The always-available engine: the strict-JSON validator gates
|
|
43
|
+
# (parse_json raises on anything RFC 8259 rejects), then the
|
|
44
|
+
# value records convert WITHOUT the Psych quirk table — floats
|
|
45
|
+
# are always Floats, bools always bools ("1e3" is 1000.0 here
|
|
46
|
+
# and a String on the YAML surface; each surface is its own
|
|
47
|
+
# contract).
|
|
48
|
+
def strict_fallback(source)
|
|
49
|
+
begin
|
|
50
|
+
gate = ::Yeptris::Document.parse_json(source)
|
|
51
|
+
rescue ::Yeptris::ParseError => e
|
|
52
|
+
raise ParseError, e.message
|
|
53
|
+
end
|
|
54
|
+
begin
|
|
55
|
+
cols = ::Yeptris::FFI::ValueColumns.new
|
|
56
|
+
st = ::Yeptris::FFI.yeptris_value_drain_columns(
|
|
57
|
+
source, source.bytesize, ::Yeptris::FFI::SCHEMA_12_CORE, cols
|
|
58
|
+
)
|
|
59
|
+
raise ParseError, ::Yeptris::FFI.last_error_message if st != ::Yeptris::FFI::OK
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
walk_strict(cols, STRICT_DUPLICATE_KEYS)
|
|
63
|
+
ensure
|
|
64
|
+
::Yeptris::FFI.yeptris_value_free_columns(cols)
|
|
65
|
+
end
|
|
66
|
+
ensure
|
|
67
|
+
gate.free
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Placement mechanics mirror ValueML.walk_columns; the CONVERSION
|
|
72
|
+
# is the strict-JSON one (no ':sym' scan, no y/n quirk, no
|
|
73
|
+
# dot-required floats). Anchors/aliases/timestamps cannot occur
|
|
74
|
+
# in strict JSON — reaching them is an internal error.
|
|
75
|
+
def walk_strict(cols, strict_dup = STRICT_DUPLICATE_KEYS)
|
|
76
|
+
n = cols[:count]
|
|
77
|
+
kinds = cols[:kinds].read_bytes(n).unpack("C*")
|
|
78
|
+
ikeys = cols[:is_keys].read_bytes(n).unpack("C*")
|
|
79
|
+
bools = cols[:bools].read_bytes(n).unpack("C*")
|
|
80
|
+
offs = cols[:offs].read_bytes(n * 4).unpack("V*")
|
|
81
|
+
lens = cols[:lens].read_bytes(n * 4).unpack("V*")
|
|
82
|
+
pays = cols[:payloads].read_bytes(n * 8).unpack("q<*")
|
|
83
|
+
arena = cols[:arena_len].zero? ? +"" : cols[:arena].read_bytes(cols[:arena_len])
|
|
84
|
+
arena.force_encoding(Encoding::UTF_8)
|
|
85
|
+
|
|
86
|
+
docs = []
|
|
87
|
+
stack = []
|
|
88
|
+
key_sets = strict_dup ? [{}] : nil
|
|
89
|
+
pending_key = nil
|
|
90
|
+
i = 0
|
|
91
|
+
while i < n
|
|
92
|
+
case kinds[i]
|
|
93
|
+
when ValueML::DOC
|
|
94
|
+
docs.push(nil)
|
|
95
|
+
when ValueML::SEQ_OPEN
|
|
96
|
+
place(docs, stack, pending_key) { [] }
|
|
97
|
+
pending_key = nil
|
|
98
|
+
when ValueML::MAP_OPEN
|
|
99
|
+
key_sets&.push({})
|
|
100
|
+
place(docs, stack, pending_key) { {} }
|
|
101
|
+
pending_key = nil
|
|
102
|
+
when ValueML::CLOSE
|
|
103
|
+
stack.pop
|
|
104
|
+
key_sets&.pop
|
|
105
|
+
when ValueML::V_STR
|
|
106
|
+
text = arena.byteslice(offs[i], lens[i])
|
|
107
|
+
if ikeys[i] == 1 && !stack.empty? && stack.last.is_a?(Hash)
|
|
108
|
+
if strict_dup && key_sets.last.key?(text)
|
|
109
|
+
raise ParseError, %(duplicate key "#{text}" in JSON object)
|
|
110
|
+
end
|
|
111
|
+
key_sets&.last&.store(text, true)
|
|
112
|
+
pending_key = text
|
|
113
|
+
else
|
|
114
|
+
# Records carry int64 payloads: an integer-beyond-int64
|
|
115
|
+
# degrades to a PLAIN string (b==1). In strict JSON a
|
|
116
|
+
# plain (unquoted) scalar can ONLY be a number — every
|
|
117
|
+
# real string is quoted and arrives b==0 — so rebuild the
|
|
118
|
+
# exact Integer (JSON.parse parity, Bignum included).
|
|
119
|
+
if bools[i] == 1
|
|
120
|
+
place(docs, stack, pending_key) { Integer(text, 10) }
|
|
121
|
+
else
|
|
122
|
+
place(docs, stack, pending_key) { text }
|
|
123
|
+
end
|
|
124
|
+
pending_key = nil
|
|
125
|
+
end
|
|
126
|
+
when ValueML::V_INT
|
|
127
|
+
place(docs, stack, pending_key) { pays[i] }
|
|
128
|
+
pending_key = nil
|
|
129
|
+
when ValueML::V_FLOAT
|
|
130
|
+
place(docs, stack, pending_key) { [pays[i]].pack("q<").unpack1("E") }
|
|
131
|
+
pending_key = nil
|
|
132
|
+
when ValueML::V_BOOL
|
|
133
|
+
place(docs, stack, pending_key) { bools[i] == 1 }
|
|
134
|
+
pending_key = nil
|
|
135
|
+
when ValueML::V_NULL
|
|
136
|
+
place(docs, stack, pending_key) { nil }
|
|
137
|
+
pending_key = nil
|
|
138
|
+
else
|
|
139
|
+
raise Error, "internal: impossible record #{kinds[i]} in strict JSON"
|
|
140
|
+
end
|
|
141
|
+
i += 1
|
|
142
|
+
end
|
|
143
|
+
docs.empty? ? nil : docs.first
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def place(docs, stack, key)
|
|
147
|
+
v = yield
|
|
148
|
+
if stack.empty?
|
|
149
|
+
docs[-1] = v
|
|
150
|
+
elsif key
|
|
151
|
+
stack.last[key] = v
|
|
152
|
+
else
|
|
153
|
+
stack.last.push(v)
|
|
154
|
+
end
|
|
155
|
+
stack.push(v) if v.is_a?(Array) || v.is_a?(Hash)
|
|
156
|
+
v
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|