yeptris 0.6.50.0-powerpc64le-linux → 0.6.52.0-powerpc64le-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 +4 -4
- data/ext/yeptris_native/version.h +2 -2
- data/ext/yeptris_native/yeptris_native.c +11 -1
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris/document.rb +12 -5
- data/lib/yeptris/json.rb +16 -7
- data/lib/yeptris/psych/parser.rb +4 -0
- data/lib/yeptris/psych.rb +15 -2
- data/lib/yeptris/valueml.rb +23 -31
- data/lib/yeptris.rb +1 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/include/yeptris/cbor.h +49 -0
- data/vendor/libyeptris/src/yeptris/cbor/decode.c +22 -0
- data/vendor/libyeptris/src/yeptris/cbor/sink.h +7 -29
- data/vendor/libyeptris/src/yeptris/events/values.c +13 -0
- data/vendor/libyeptris/src/yeptris/events/values_priv.h +2 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +12 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0c07adf577e2e780cf60cca0edc2597d9b0c092e58eb9455615e5c3f761a6c1
|
|
4
|
+
data.tar.gz: af94b80492df46bcef17f16e180aab09b6782c271a3a4baa09293613e636d458
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 715691702baccf4f08175887df320a73918d131aae2211866bf12349fae3e3201088a2a0981f289c3b517e8f91d4e46322edb3eec16e7b0406b1636eca586b9b
|
|
7
|
+
data.tar.gz: 3ffe41b6d9fa191c93121537da91a862b83467d37d3b930cc2dc94986fd7ebd939122700e4ed2cfd6096fd30fd5f20a957e0da428d46d616078278d5ad13d0b3
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
#define YEPTRIS_VERSION_MAJOR 0
|
|
13
13
|
#define YEPTRIS_VERSION_MINOR 6
|
|
14
|
-
#define YEPTRIS_VERSION_PATCH
|
|
15
|
-
#define YEPTRIS_VERSION_STRING "0.6.
|
|
14
|
+
#define YEPTRIS_VERSION_PATCH 51
|
|
15
|
+
#define YEPTRIS_VERSION_STRING "0.6.51"
|
|
16
16
|
|
|
17
17
|
#ifdef __cplusplus
|
|
18
18
|
extern "C" {
|
|
@@ -483,7 +483,17 @@ RUBY_FUNC_EXPORTED void Init_native(void) {
|
|
|
483
483
|
rb_define_singleton_method(mNat, "shape_mode", native_shape_mode, 0);
|
|
484
484
|
rb_define_singleton_method(mNat, "shape_mode=", native_shape_mode_set, 1);
|
|
485
485
|
rb_define_singleton_method(mNat, "scan_time", native_scan_time, 2);
|
|
486
|
-
|
|
486
|
+
/* the leptris bridge vendors this same ext (leptris's
|
|
487
|
+
* lib/leptris/xml/native_3_3.so), so both gems can co-initialize
|
|
488
|
+
* in one process: the second init re-ran rb_define_const and
|
|
489
|
+
* warned "already initialized constant Yeptris::Native::AVAILABLE"
|
|
490
|
+
* — which moxml treated as an adapter load failure and fell back
|
|
491
|
+
* to nokogiri for the rest of the process (yeptris-ruby#336).
|
|
492
|
+
* Re-registration is idempotent: skip the const when the first
|
|
493
|
+
* init (either gem's copy) already defined it. */
|
|
494
|
+
if (!rb_const_defined_at(mNat, rb_intern("AVAILABLE"))) {
|
|
495
|
+
rb_define_const(mNat, "AVAILABLE", Qtrue);
|
|
496
|
+
}
|
|
487
497
|
const char* env = getenv("YEPTRIS_NATIVE_GC");
|
|
488
498
|
if (env != NULL && strcmp(env, "none") == 0) yep_rb_set_gc_mode(1);
|
|
489
499
|
else if (env != NULL && strcmp(env, "start") == 0) yep_rb_set_gc_mode(2);
|
data/lib/yeptris/3.4/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/document.rb
CHANGED
|
@@ -16,7 +16,7 @@ class Yeptris::Document
|
|
|
16
16
|
# The schema this document was parsed with (a parse property).
|
|
17
17
|
attr_reader :parse_schema
|
|
18
18
|
|
|
19
|
-
def initialize(c_ptr = nil, freed = Freed.new(:alive), parse_schema = :core_12)
|
|
19
|
+
def initialize(c_ptr = nil, freed = Freed.new(:alive), parse_schema = :core_12, source = nil)
|
|
20
20
|
# The pointer is produced by the FFI parse/create calls; a nil (or
|
|
21
21
|
# non-pointer) argument is an API misuse — Document.parse and
|
|
22
22
|
# Document.create are the entry points. Fail with that guidance
|
|
@@ -30,6 +30,13 @@ class Yeptris::Document
|
|
|
30
30
|
@c_ptr = c_ptr
|
|
31
31
|
@freed = freed
|
|
32
32
|
@parse_schema = parse_schema
|
|
33
|
+
# the C document BORROWS the input: string values are zero-copy
|
|
34
|
+
# views over the caller's bytes (parse.h/doc.h) — the FFI call only
|
|
35
|
+
# pins the String for the call's duration, so the document must
|
|
36
|
+
# retain it or the next GC frees memory every later node.value/
|
|
37
|
+
# tag read still points into (audit 2026-10-31: parse(io.read) was
|
|
38
|
+
# collectable immediately)
|
|
39
|
+
@source = source
|
|
33
40
|
@readonly = false
|
|
34
41
|
# Strong wrapper cache keyed on the C node address: the same C node
|
|
35
42
|
# always yields the same Ruby object (identity for aliases/eql?),
|
|
@@ -61,7 +68,7 @@ class Yeptris::Document
|
|
|
61
68
|
raise Yeptris::ParseError,
|
|
62
69
|
"parse failed: #{Yeptris::FFI.last_error_message}"
|
|
63
70
|
end
|
|
64
|
-
wrap_schema(doc, schema)
|
|
71
|
+
wrap_schema(doc, schema, yaml)
|
|
65
72
|
end
|
|
66
73
|
|
|
67
74
|
def self.parse_json(json)
|
|
@@ -71,7 +78,7 @@ class Yeptris::Document
|
|
|
71
78
|
raise Yeptris::ParseError,
|
|
72
79
|
"json parse failed: #{Yeptris::FFI.last_error_message}" if doc.null?
|
|
73
80
|
|
|
74
|
-
wrap_schema(doc, :core_12) # strict JSON is core by construction
|
|
81
|
+
wrap_schema(doc, :core_12, json) # strict JSON is core by construction
|
|
75
82
|
end
|
|
76
83
|
|
|
77
84
|
# An empty document for from-scratch construction (TODO.impl/11 p3).
|
|
@@ -99,8 +106,8 @@ class Yeptris::Document
|
|
|
99
106
|
# host scalar policies (Psych's dot-required float) are conditioned
|
|
100
107
|
# on it. The parse constructors set the real one; plain wrap keeps
|
|
101
108
|
# the core_12 default (yeptris_parse's default).
|
|
102
|
-
def self.wrap_schema(c_ptr, schema)
|
|
103
|
-
new(c_ptr, Freed.new(:alive), schema)
|
|
109
|
+
def self.wrap_schema(c_ptr, schema, source = nil)
|
|
110
|
+
new(c_ptr, Freed.new(:alive), schema, source)
|
|
104
111
|
end
|
|
105
112
|
|
|
106
113
|
# #182: the stream-children wrapper owns NOTHING (the parent
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -34,12 +34,21 @@ module Yeptris
|
|
|
34
34
|
|
|
35
35
|
module_function
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
# allow_duplicate_key: opts out of the resolved json gem's strict
|
|
38
|
+
# duplicate-key contract (json >= 3 rejects by default) — last value
|
|
39
|
+
# wins, exactly the historical JSON.parse contract. System-boundary
|
|
40
|
+
# parsers need it: third-party feeds (Crossref book records keyed by
|
|
41
|
+
# ISBN, NIST's pubs export keyed by " ", the UN search API's blank
|
|
42
|
+
# keys) genuinely carry duplicates, and rejecting them breaks the fetch
|
|
43
|
+
# rather than flagging the data (relaton/relaton#413, the ask in
|
|
44
|
+
# yeptris-ruby#335). Default stays the json-gem-parity strictness.
|
|
45
|
+
def load(source, allow_duplicate_key: false)
|
|
46
|
+
strict = STRICT_DUPLICATE_KEYS && !allow_duplicate_key
|
|
38
47
|
source = ::Yeptris.read_input(source)
|
|
39
48
|
source = source.to_s
|
|
40
49
|
if defined?(::Yeptris::Native)
|
|
41
50
|
begin
|
|
42
|
-
return ::Yeptris::Native.load_json(source,
|
|
51
|
+
return ::Yeptris::Native.load_json(source, strict)
|
|
43
52
|
rescue ::Yeptris::ParseError => e
|
|
44
53
|
raise ParseError, e.message
|
|
45
54
|
end
|
|
@@ -53,9 +62,9 @@ module Yeptris
|
|
|
53
62
|
# none) answered last-wins under json >= 3 — found by canon's
|
|
54
63
|
# dependent CI, whose parity pin now names the lane.
|
|
55
64
|
return marshal_engine(
|
|
56
|
-
source,
|
|
65
|
+
source, strict ? ::Yeptris::FFI::MARSHAL_STRICT_DUP_KEYS : 0
|
|
57
66
|
) if ::Yeptris::FFI::MARSHAL_STRICT_DUP
|
|
58
|
-
tape_engine(source)
|
|
67
|
+
tape_engine(source, strict)
|
|
59
68
|
end
|
|
60
69
|
|
|
61
70
|
# The marshal engine (#278): yeptris_parse_json gates the strict
|
|
@@ -156,19 +165,19 @@ module Yeptris
|
|
|
156
165
|
out.force_encoding(Encoding::UTF_8)
|
|
157
166
|
end
|
|
158
167
|
|
|
159
|
-
def tape_engine(source)
|
|
168
|
+
def tape_engine(source, strict_dup = STRICT_DUPLICATE_KEYS)
|
|
160
169
|
tape = ::Yeptris::FFI::JsonTape.new
|
|
161
170
|
rc = ::Yeptris::FFI.yeptris_parse_json_tape(source, source.bytesize, tape)
|
|
162
171
|
raise ParseError, ::Yeptris::FFI.last_error_message if rc != ::Yeptris::FFI::OK
|
|
163
172
|
|
|
164
173
|
begin
|
|
165
|
-
walk_tape(source, tape)
|
|
174
|
+
walk_tape(source, tape, strict_dup)
|
|
166
175
|
ensure
|
|
167
176
|
::Yeptris::FFI.yeptris_tape_free(tape)
|
|
168
177
|
end
|
|
169
178
|
end
|
|
170
179
|
|
|
171
|
-
def walk_tape(src, tape, strict_dup
|
|
180
|
+
def walk_tape(src, tape, strict_dup)
|
|
172
181
|
n = tape[:count]
|
|
173
182
|
kinds = tape[:kinds].read_bytes(n).unpack("C*")
|
|
174
183
|
offs = tape[:offs].read_bytes(n * 4).unpack("V*")
|
data/lib/yeptris/psych/parser.rb
CHANGED
|
@@ -25,6 +25,10 @@ module Yeptris
|
|
|
25
25
|
yaml = yaml.to_s
|
|
26
26
|
@filename = filename
|
|
27
27
|
flat, arena = Materializer.drain(yaml, schema: :compat_11)
|
|
28
|
+
# the engine's arena is UTF-8 bytes; without the tag every
|
|
29
|
+
# scalar reached handlers as ASCII-8BIT (stdlib Psych hands
|
|
30
|
+
# UTF-8; TreeBuilder then propagated binary into node values)
|
|
31
|
+
arena.force_encoding(Encoding::UTF_8)
|
|
28
32
|
dispatch(flat, arena)
|
|
29
33
|
self
|
|
30
34
|
rescue ParseError => e
|
data/lib/yeptris/psych.rb
CHANGED
|
@@ -143,8 +143,21 @@ module Yeptris
|
|
|
143
143
|
# Psych 5: load is safe — plain data structures only. Tagged
|
|
144
144
|
# nodes raise DisallowedClass unless their class is permitted
|
|
145
145
|
# (Date/Time/Symbol are built in; they are plain data here).
|
|
146
|
-
def load(yaml, permitted_classes: [::Date, ::Time], aliases: false,
|
|
147
|
-
|
|
146
|
+
def load(yaml, permitted_classes: [::Date, ::Time], aliases: false,
|
|
147
|
+
symbolize_names: false, fallback: nil, filename: nil, **)
|
|
148
|
+
res = safe_load(yaml, permitted_classes: permitted_classes, aliases: aliases,
|
|
149
|
+
symbolize_names: symbolize_names)
|
|
150
|
+
if res.nil? && !fallback.nil?
|
|
151
|
+
# stdlib's fallback: answers when the stream is empty or has
|
|
152
|
+
# NO documents — a document whose content IS nil still loads
|
|
153
|
+
# as nil. The safe path cannot tell those two apart, so the
|
|
154
|
+
# corner re-checks the count (only when a fallback rides).
|
|
155
|
+
doc = ::Yeptris::Document.parse(yaml, schema: :compat_11)
|
|
156
|
+
empty = doc.nil? || doc.document_count.zero?
|
|
157
|
+
doc&.free
|
|
158
|
+
return fallback if empty
|
|
159
|
+
end
|
|
160
|
+
res
|
|
148
161
|
end
|
|
149
162
|
|
|
150
163
|
# Full revival over the Nodes tree: !ruby/object, !ruby/struct,
|
data/lib/yeptris/valueml.rb
CHANGED
|
@@ -20,6 +20,25 @@ module Yeptris
|
|
|
20
20
|
# int64 the C resolver leaves the scalar a string; Psych
|
|
21
21
|
# materializes Integer (issue #31) — every walk rebuilds it.
|
|
22
22
|
PSYCH_INT_SHAPE = /\A[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*)\z/.freeze
|
|
23
|
+
|
|
24
|
+
# The typed-string conversion law, ONE place: a big-int-shaped
|
|
25
|
+
# string rebuilds to Integer, an implicit-plain ':name' scans to a
|
|
26
|
+
# Symbol (Psych's ScalarScanner; quoted ':x' stays a String).
|
|
27
|
+
# Every str-column conversion rides this — the pair fast path
|
|
28
|
+
# drifted once and handed bignums/symbol-keys through as Strings
|
|
29
|
+
# (audit 2026-10-31).
|
|
30
|
+
def self.str_to_value(text, typed)
|
|
31
|
+
return text unless typed
|
|
32
|
+
|
|
33
|
+
if text.length > 18 && PSYCH_INT_SHAPE.match?(text)
|
|
34
|
+
text.delete(",_").to_i
|
|
35
|
+
elsif text.length > 1 && text.start_with?(":") && !text.start_with?("::")
|
|
36
|
+
text[1..].to_sym
|
|
37
|
+
else
|
|
38
|
+
text
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
23
42
|
V_TS = 6
|
|
24
43
|
SEQ_OPEN = 7
|
|
25
44
|
MAP_OPEN = 8
|
|
@@ -155,15 +174,7 @@ module Yeptris
|
|
|
155
174
|
text = arena.byteslice(flat[i + OFF], flat[i + LEN])
|
|
156
175
|
# implicit-plain ':name' scans to a Symbol (Psych's
|
|
157
176
|
# ScalarScanner); quoted ':x' stays a String
|
|
158
|
-
v =
|
|
159
|
-
PSYCH_INT_SHAPE.match?(text)
|
|
160
|
-
text.delete(",_").to_i
|
|
161
|
-
elsif flat[i + B] == 1 && text.length > 1 &&
|
|
162
|
-
text.start_with?(":") && !text.start_with?("::")
|
|
163
|
-
text[1..].to_sym
|
|
164
|
-
else
|
|
165
|
-
text
|
|
166
|
-
end
|
|
177
|
+
v = str_to_value(text, flat[i + B] == 1)
|
|
167
178
|
if pending_anchor
|
|
168
179
|
anchors[pending_anchor] = v
|
|
169
180
|
pending_anchor = nil
|
|
@@ -284,20 +295,9 @@ module Yeptris
|
|
|
284
295
|
pending_anchor.nil? && !stack.empty? && stack.last.is_a?(Hash)
|
|
285
296
|
kt = arena.byteslice(offs[i], lens[i])
|
|
286
297
|
if kt != "<<"
|
|
287
|
-
key =
|
|
288
|
-
!kt.start_with?("::")
|
|
289
|
-
kt[1..].to_sym
|
|
290
|
-
else
|
|
291
|
-
kt
|
|
292
|
-
end
|
|
298
|
+
key = str_to_value(kt, bools[i] == 1)
|
|
293
299
|
vt = arena.byteslice(offs[i + 1], lens[i + 1])
|
|
294
|
-
stack.last[key] =
|
|
295
|
-
if bools[i + 1] == 1 && vt.length > 1 && vt.start_with?(":") &&
|
|
296
|
-
!vt.start_with?("::")
|
|
297
|
-
vt[1..].to_sym
|
|
298
|
-
else
|
|
299
|
-
vt
|
|
300
|
-
end
|
|
300
|
+
stack.last[key] = str_to_value(vt, bools[i + 1] == 1)
|
|
301
301
|
i += 2
|
|
302
302
|
next
|
|
303
303
|
end
|
|
@@ -305,15 +305,7 @@ module Yeptris
|
|
|
305
305
|
case kinds[i]
|
|
306
306
|
when V_STR
|
|
307
307
|
text = arena.byteslice(offs[i], lens[i])
|
|
308
|
-
v =
|
|
309
|
-
PSYCH_INT_SHAPE.match?(text)
|
|
310
|
-
text.delete(",_").to_i
|
|
311
|
-
elsif bools[i] == 1 && text.length > 1 &&
|
|
312
|
-
text.start_with?(":") && !text.start_with?("::")
|
|
313
|
-
text[1..].to_sym
|
|
314
|
-
else
|
|
315
|
-
text
|
|
316
|
-
end
|
|
308
|
+
v = str_to_value(text, bools[i] == 1)
|
|
317
309
|
if pending_anchor
|
|
318
310
|
anchors[pending_anchor] = v
|
|
319
311
|
pending_anchor = nil
|
data/lib/yeptris.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Yeptris
|
|
4
4
|
# The gem's version lives in the parent namespace's file — the last
|
|
5
5
|
# internal require (yeptris/version) retired with it.
|
|
6
|
-
VERSION = "0.6.
|
|
6
|
+
VERSION = "0.6.52.0".freeze
|
|
7
7
|
|
|
8
8
|
# Informational notices are OPT-IN (yeptris-ruby#217): consumers
|
|
9
9
|
# embed yeptris inside CLIs that assert clean stderr, and a
|
data/libyeptris.so
CHANGED
|
Binary file
|
|
@@ -53,6 +53,55 @@ extern "C" {
|
|
|
53
53
|
YEPTRIS_API YeptrisDocument yeptris_cbor_decode(const void* buf, size_t len, uint32_t opts,
|
|
54
54
|
YeptrisStatus* status);
|
|
55
55
|
|
|
56
|
+
/* The SAX decode face (#742): yeptris_cbor_decode's grammar driving
|
|
57
|
+
* consumer callbacks — items land as they parse, no document, no
|
|
58
|
+
* nodes. Measured (perf ledger 2026-10-29): the scan-only route reads
|
|
59
|
+
* ~2.1x the DOM entry on the same corpus (DOM construction is over
|
|
60
|
+
* half the decode lane), so validation, counting, and streaming
|
|
61
|
+
* transforms belong here. Items arrive in encoding order; map items
|
|
62
|
+
* set is_key on the key half. Strings with borrowed=1 point INTO buf —
|
|
63
|
+
* keep it valid while you hold them; borrowed=0 is decoder scratch.
|
|
64
|
+
* tag ("N N N", outermost first; NULL when none) and tag_len are
|
|
65
|
+
* callback-call-local. open's cap is the expected item count (maps
|
|
66
|
+
* count pairs*2; UINT64_MAX = indefinite); close fires for both the
|
|
67
|
+
* definite-complete and the break path, after the last child. The
|
|
68
|
+
* rendered text forms match the DOM ledger exactly (numbers-as-text,
|
|
69
|
+
* false/true/null/undefined, simple(N)).
|
|
70
|
+
*
|
|
71
|
+
* Every callback returns 0 on sink failure — the decode then reports
|
|
72
|
+
* YEPTRIS_ERROR_MEMORY — and may raise through host languages; the
|
|
73
|
+
* decoder owns no host state across them except the indefinite buffer,
|
|
74
|
+
* which its own exit paths free. All seven callbacks and sax itself
|
|
75
|
+
* must be non-NULL (YEPTRIS_ERROR_ARG otherwise): the loop dispatches
|
|
76
|
+
* unconditionally.
|
|
77
|
+
*
|
|
78
|
+
* consumed == NULL: trailing bytes after the one data item fail
|
|
79
|
+
* YEPTRIS_ERROR_PARSE. consumed != NULL: on success it receives the
|
|
80
|
+
* byte count of the items decoded — walk a CBOR Sequence (RFC 8742)
|
|
81
|
+
* by advancing buf by it. On failure *status (may be NULL) carries the
|
|
82
|
+
* code and yeptris_last_error() the message with the byte offset.
|
|
83
|
+
*
|
|
84
|
+
* Memory: none — the decoder allocates only its indefinite-chunk
|
|
85
|
+
* scratch and frees it on every exit. */
|
|
86
|
+
typedef struct yeptris_cbor_sax {
|
|
87
|
+
void* ctx;
|
|
88
|
+
int (*text)(void* ctx, const char* s, uint32_t n, uint8_t tag_id, int is_key, const char* tag,
|
|
89
|
+
uint32_t tag_len);
|
|
90
|
+
int (*int_val)(void* ctx, int negative, uint64_t mag, int is_key, const char* tag,
|
|
91
|
+
uint32_t tag_len);
|
|
92
|
+
int (*float_val)(void* ctx, double dv, int is_key, const char* tag, uint32_t tag_len);
|
|
93
|
+
int (*str_val)(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
94
|
+
const char* tag, uint32_t tag_len);
|
|
95
|
+
int (*bytes_val)(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
96
|
+
const char* tag, uint32_t tag_len);
|
|
97
|
+
int (*open)(void* ctx, int is_map, uint64_t cap, const char* tag, uint32_t tag_len);
|
|
98
|
+
int (*close)(void* ctx, int is_map);
|
|
99
|
+
} yeptris_cbor_sax;
|
|
100
|
+
|
|
101
|
+
YEPTRIS_API YeptrisStatus yeptris_cbor_decode_sax(const void* buf, size_t len, uint32_t opts,
|
|
102
|
+
const yeptris_cbor_sax* sax, size_t* consumed,
|
|
103
|
+
YeptrisStatus* status);
|
|
104
|
+
|
|
56
105
|
/* CBOR Sequences (RFC 8742): top-level data items, concatenated —
|
|
57
106
|
* no framing bytes. Iterates the buffer, one document per item; the
|
|
58
107
|
* callback OWNS each item (free it with yeptris_document_free) and
|
|
@@ -1032,3 +1032,25 @@ YEPTRIS_API YeptrisDocument yeptris_cbor_decode(const void* buf, size_t len, uin
|
|
|
1032
1032
|
}
|
|
1033
1033
|
return (YeptrisDocument)doc;
|
|
1034
1034
|
}
|
|
1035
|
+
|
|
1036
|
+
YEPTRIS_API YeptrisStatus yeptris_cbor_decode_sax(const void* buf, size_t len, uint32_t opts,
|
|
1037
|
+
const yeptris_cbor_sax* sax, size_t* consumed,
|
|
1038
|
+
YeptrisStatus* status) {
|
|
1039
|
+
if ((buf == NULL && len != 0) || sax == NULL || (opts & ~YEPTRIS_CBOR_STRICT) != 0 ||
|
|
1040
|
+
sax->text == NULL || sax->int_val == NULL || sax->float_val == NULL ||
|
|
1041
|
+
sax->str_val == NULL || sax->bytes_val == NULL || sax->open == NULL || sax->close == NULL) {
|
|
1042
|
+
if (status != NULL) {
|
|
1043
|
+
*status = YEPTRIS_ERROR_ARG;
|
|
1044
|
+
}
|
|
1045
|
+
return YEPTRIS_ERROR_ARG;
|
|
1046
|
+
}
|
|
1047
|
+
/* yeptris_cbor_sax IS yep_cbor_sink (sink.h aliases the public
|
|
1048
|
+
* face): the decoder drives the caller's vtable directly. */
|
|
1049
|
+
YeptrisStatus st = (YeptrisStatus)yep_cbor_decode_gen((const unsigned char*)buf, len,
|
|
1050
|
+
(opts & YEPTRIS_CBOR_STRICT) != 0,
|
|
1051
|
+
consumed, (const yep_cbor_sink*)sax);
|
|
1052
|
+
if (status != NULL) {
|
|
1053
|
+
*status = st;
|
|
1054
|
+
}
|
|
1055
|
+
return st;
|
|
1056
|
+
}
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* source of CBOR semantics; a sink implements the item constructors
|
|
5
5
|
* on top of it. Two sinks exist: the DOM builder (the default, the
|
|
6
6
|
* yeptris_cbor_decode public path) and the native ext's VALUE builder
|
|
7
|
-
* (one pass, no intermediate).
|
|
7
|
+
* (one pass, no intermediate). The sink vtable IS the public SAX face
|
|
8
|
+
* (yeptris_cbor_sax, <yeptris/cbor.h> — #742): the alias keeps the
|
|
9
|
+
* decoder driving the same layout the public entry hands over, so
|
|
10
|
+
* yeptris_cbor_decode_sax is a straight passthrough. Callback/field
|
|
11
|
+
* contracts are documented there; sinks receive the resolver's tag_id
|
|
8
12
|
* and the pending semantic-tag chain in its text form ("N N N",
|
|
9
13
|
* outermost first — NULL when the item carries no chain).
|
|
10
14
|
*/
|
|
@@ -15,35 +19,9 @@
|
|
|
15
19
|
#include <stdint.h>
|
|
16
20
|
|
|
17
21
|
#include <yeptris/api.h> /* YEPTRIS_API (the ext links these) */
|
|
22
|
+
#include <yeptris/cbor.h>
|
|
18
23
|
|
|
19
|
-
typedef
|
|
20
|
-
void* ctx;
|
|
21
|
-
/* All callbacks return 0 on sink failure (the decoder reports
|
|
22
|
-
* MEMORY). They may raise through host languages (longjmp): the
|
|
23
|
-
* decoder owns no host-managed state across them except the
|
|
24
|
-
* indefinite buffer, which its own exit paths free. */
|
|
25
|
-
/* is_key: 1 = the item lands in the enclosing map's key slot
|
|
26
|
-
* (sinks key-intern strings; the DOM ignores it). Rendered text:
|
|
27
|
-
* numbers-as-text, false/true/null/undefined, simple(N) — the
|
|
28
|
-
* exact bytes the DOM path stores. */
|
|
29
|
-
int (*text)(void* ctx, const char* s, uint32_t n, uint8_t tag_id, int is_key, const char* tag,
|
|
30
|
-
uint32_t tag_len);
|
|
31
|
-
/* Known integers: value = negative ? -(mag) : mag, mag <= 2^63. */
|
|
32
|
-
int (*int_val)(void* ctx, int negative, uint64_t mag, int is_key, const char* tag,
|
|
33
|
-
uint32_t tag_len);
|
|
34
|
-
int (*float_val)(void* ctx, double dv, int is_key, const char* tag, uint32_t tag_len);
|
|
35
|
-
/* Strings. borrowed: 1 = p is the input (a sink may record the
|
|
36
|
-
* offset), 0 = the decoder's scratch buffer (copy or take it). */
|
|
37
|
-
int (*str_val)(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
38
|
-
const char* tag, uint32_t tag_len);
|
|
39
|
-
int (*bytes_val)(void* ctx, const unsigned char* p, uint32_t n, int borrowed, int is_key,
|
|
40
|
-
const char* tag, uint32_t tag_len);
|
|
41
|
-
/* Containers. cap: expected item count (maps: pairs*2), UINT64_MAX
|
|
42
|
-
* for indefinite. close fires for BOTH the definite-complete and
|
|
43
|
-
* the break paths, after the last child. */
|
|
44
|
-
int (*open)(void* ctx, int is_map, uint64_t cap, const char* tag, uint32_t tag_len);
|
|
45
|
-
int (*close)(void* ctx, int is_map);
|
|
46
|
-
} yep_cbor_sink;
|
|
24
|
+
typedef yeptris_cbor_sax yep_cbor_sink;
|
|
47
25
|
|
|
48
26
|
/* The generic engine: the grammar, driving `sink`. Same reject/
|
|
49
27
|
* encoding/depth semantics as the DOM entry. */
|
|
@@ -72,6 +72,16 @@ void yep_val_put(yep_value_ctx* c, const YeptrisValue* v) {
|
|
|
72
72
|
void yep_val_slot(yep_value_ctx* c, YeptrisValue* v) {
|
|
73
73
|
if (c->depth > 0 && c->depth <= YEP_V_MAX_DEPTH) {
|
|
74
74
|
int di = c->depth - 1;
|
|
75
|
+
if (c->is_seq[di]) {
|
|
76
|
+
/* a sequence entry never takes a key slot — the toggle
|
|
77
|
+
* flagged every ODD-position entry is_key=1, and the
|
|
78
|
+
* marshal strict-dup scan then false-flagged arrays with a
|
|
79
|
+
* repeated value at two odd positions ("[\"a\",\"b\",\"a\"]"
|
|
80
|
+
* = "duplicate key in object"; yeptris-ruby#333, the
|
|
81
|
+
* yeptris#680 class resurfacing through the value
|
|
82
|
+
* stream's own parity) */
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
75
85
|
if (c->key_pend[di] == 0) {
|
|
76
86
|
v->is_key = 1;
|
|
77
87
|
c->key_pend[di] = 1;
|
|
@@ -169,6 +179,7 @@ static int transform(yep_value_ctx* c, const yep_rec_store* store) {
|
|
|
169
179
|
yep_val_put(c, &v);
|
|
170
180
|
if (c->depth < YEP_V_MAX_DEPTH) {
|
|
171
181
|
c->key_pend[c->depth] = 0;
|
|
182
|
+
c->is_seq[c->depth] = 1;
|
|
172
183
|
}
|
|
173
184
|
c->depth++;
|
|
174
185
|
continue;
|
|
@@ -179,6 +190,7 @@ static int transform(yep_value_ctx* c, const yep_rec_store* store) {
|
|
|
179
190
|
yep_val_put(c, &v);
|
|
180
191
|
if (c->depth < YEP_V_MAX_DEPTH) {
|
|
181
192
|
c->key_pend[c->depth] = 0;
|
|
193
|
+
c->is_seq[c->depth] = 0;
|
|
182
194
|
}
|
|
183
195
|
c->depth++;
|
|
184
196
|
continue;
|
|
@@ -285,6 +297,7 @@ static int lin_node(yep_value_ctx* c, const yep_dom* d, uint32_t id) {
|
|
|
285
297
|
yep_val_put(c, &v);
|
|
286
298
|
if (c->depth < YEP_V_MAX_DEPTH) {
|
|
287
299
|
c->key_pend[c->depth] = 0;
|
|
300
|
+
c->is_seq[c->depth] = (n->kind == YEP_DOM_SEQUENCE);
|
|
288
301
|
}
|
|
289
302
|
c->depth++;
|
|
290
303
|
if (lin_children(c, d, n) != 0) {
|
|
@@ -24,6 +24,8 @@ typedef struct yep_value_ctx {
|
|
|
24
24
|
int oom;
|
|
25
25
|
int unsupported; /* linearize: construct the emitter cannot express */
|
|
26
26
|
uint8_t key_pend[YEP_V_MAX_DEPTH]; /* per open map: key slot taken */
|
|
27
|
+
uint8_t is_seq[YEP_V_MAX_DEPTH]; /* per open frame: 1 = sequence —
|
|
28
|
+
* entries never take key slots */
|
|
27
29
|
int depth;
|
|
28
30
|
} yep_value_ctx;
|
|
29
31
|
|
|
@@ -379,7 +379,8 @@ static int e_comment_at(const yep_engine* e) {
|
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
static int e_at_eol(yep_engine* e) {
|
|
382
|
-
return e->pos >= e->len || e->p[e->pos] == '\n' || e->p[e->pos] == '\r' || e_comment_at(e)
|
|
382
|
+
return e->pos >= e->len || e->p[e->pos] == '\n' || e->p[e->pos] == '\r' || e_comment_at(e) ||
|
|
383
|
+
yep_scan_mb_break_len(e->p, e->len, e->pos) > 0;
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
static int e_skip_to_eol(yep_engine* e) {
|
|
@@ -3890,6 +3891,16 @@ static int e_parse_value(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
|
|
|
3890
3891
|
if (e->pos >= e->len) {
|
|
3891
3892
|
goto empty_value;
|
|
3892
3893
|
}
|
|
3894
|
+
/* a raw NEL/LS/PS here is this line's break: consume it exactly
|
|
3895
|
+
* as the BLANK branch below consumes an LF-ended empty line —
|
|
3896
|
+
* yep_scan_line's line model does not treat a break byte at the
|
|
3897
|
+
* loop head as a line end, so without this the props-only line
|
|
3898
|
+
* re-entered e_node on the break byte (rows 1-2) and the marker
|
|
3899
|
+
* became the scalar's value (row 5's consume-first form) */
|
|
3900
|
+
if (yep_scan_mb_break_len(e->p, e->len, e->pos) > 0) {
|
|
3901
|
+
e_line_done(e, e->pos);
|
|
3902
|
+
continue;
|
|
3903
|
+
}
|
|
3893
3904
|
yep_line_info li;
|
|
3894
3905
|
if (e->pos == e->line_start) {
|
|
3895
3906
|
/* seed the memo: the fast arms re-read this line's facts —
|