yeptris 0.6.12.3 → 0.6.14.1
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/lib/yeptris/ffi.rb +6 -0
- data/lib/yeptris/node.rb +29 -7
- data/lib/yeptris/schema.rb +37 -3
- data/lib/yeptris.rb +1 -1
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/include/yeptris/dom.h +9 -0
- data/vendor/libyeptris/src/yeptris/cbor/decode.c +2 -0
- data/vendor/libyeptris/src/yeptris/cbor/encode.c +4 -1
- data/vendor/libyeptris/src/yeptris/doc.h +19 -0
- data/vendor/libyeptris/src/yeptris/dom/dom.c +127 -0
- data/vendor/libyeptris/src/yeptris/dom/dom.h +10 -0
- data/vendor/libyeptris/src/yeptris/emit/emit.c +15 -0
- data/vendor/libyeptris/src/yeptris/parse.c +114 -29
- data/vendor/libyeptris/src/yeptris/plan.c +30 -26
- data/vendor/libyeptris/src/yeptris/schema.c +72 -2
- data/vendor/libyeptris/src/yeptris/tape.c +3 -3
- data/vendor/libyeptris/src/yeptris/tape_in.h +19 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60e8ec13d8221947494e0f163b57a619a3bb6d4f179f53d3f9b324e0ae0a4ffb
|
|
4
|
+
data.tar.gz: 6eda638121e069206be5d900cc044c942b84e74989d6482255b0e9177aab13f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbe32b83a5b017191c37a5978325516a5f99a7d85819ead1dcd97c030fec14f586acfb6d5965275cf1416c74f4dbeecb08abea77074ce769f51340ef3c1e9a7a
|
|
7
|
+
data.tar.gz: 9b6db9635acbdc9e83017626594b8417fec8c2953e38d17878bf2e99e5a4e0976f4087862890f78a23155ea3eb34d8ca8afeb87e536991fa80091b626e40875e
|
data/lib/yeptris/ffi.rb
CHANGED
|
@@ -174,6 +174,8 @@ module Yeptris
|
|
|
174
174
|
attach_function :yeptris_node_bool, %i[yeptris_node pointer], :yeptris_status
|
|
175
175
|
attach_function :yeptris_node_seq_count, [:yeptris_node], :size_t
|
|
176
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
|
|
177
179
|
attach_function :yeptris_node_map_count, [:yeptris_node], :size_t
|
|
178
180
|
attach_function :yeptris_node_map_get, %i[yeptris_node pointer size_t], :yeptris_node
|
|
179
181
|
attach_function :yeptris_node_map_at,
|
|
@@ -227,6 +229,10 @@ module Yeptris
|
|
|
227
229
|
attach_function :yeptris_marshal_node,
|
|
228
230
|
%i[yeptris_node pointer pointer], :int
|
|
229
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)
|
|
230
236
|
MARSHAL = !(@missing ||= []).include?(:yeptris_marshal_node)
|
|
231
237
|
|
|
232
238
|
# bulk build (TODO.impl/15 phase D): one call raises a document
|
data/lib/yeptris/node.rb
CHANGED
|
@@ -150,7 +150,17 @@ class Yeptris::Node
|
|
|
150
150
|
return enum_for(:each) unless block_given?
|
|
151
151
|
raise Yeptris::Error, "#each is for sequences" unless sequence?
|
|
152
152
|
|
|
153
|
-
|
|
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
|
|
154
164
|
end
|
|
155
165
|
|
|
156
166
|
# ---- mapping access ----
|
|
@@ -177,13 +187,25 @@ class Yeptris::Node
|
|
|
177
187
|
return enum_for(:each_pair) unless block_given?
|
|
178
188
|
raise Yeptris::Error, "#each_pair is for mappings" unless mapping?
|
|
179
189
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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?
|
|
185
206
|
|
|
186
|
-
|
|
207
|
+
yield @document.wrap_node(k.read_pointer), @document.wrap_node(v.read_pointer)
|
|
208
|
+
end
|
|
187
209
|
end
|
|
188
210
|
end
|
|
189
211
|
|
data/lib/yeptris/schema.rb
CHANGED
|
@@ -9,13 +9,37 @@ module Yeptris
|
|
|
9
9
|
module_function
|
|
10
10
|
|
|
11
11
|
KIND = { scalar: 0, sequence: 1, mapping: 2, callback: 3 }.freeze
|
|
12
|
-
TYPE = { str: 0, int: 1, float: 2, bool: 3, null: 4 }.freeze
|
|
12
|
+
TYPE = { str: 0, int: 1, float: 2, bool: 3, null: 4, any: 5 }.freeze
|
|
13
13
|
REQUIRED = 1 << 0
|
|
14
|
+
FIRST_WINS = 1 << 1 # duplicates within one mapping: first kept
|
|
15
|
+
|
|
16
|
+
# The :any verdict (#238's headroom): the resolver's own typing,
|
|
17
|
+
# one 24-byte record per value — Integer/Float/true/false/nil, a
|
|
18
|
+
# zero-copy String for str/timestamp spans
|
|
19
|
+
ANY_SIZE = 24 # yeptris_value.h's YeptrisValue: kind@0 tag_id@1
|
|
20
|
+
# is_key@2 b@3 off@4 len@8 p@16 — the C layout, ABI-pinned
|
|
21
|
+
ANY_KIND = { 1 => :null, 2 => :bool, 3 => :int, 4 => :float,
|
|
22
|
+
5 => :str, 6 => :timestamp }.freeze
|
|
14
23
|
|
|
15
24
|
class Error < Yeptris::Error; end
|
|
25
|
+
|
|
26
|
+
# #238's executed headroom (ST_ANY/FIRST_WINS) rides the C tag
|
|
27
|
+
# AFTER v0.6.12; probe the engine rather than trusting versions
|
|
28
|
+
class << self
|
|
29
|
+
def headroom_supported?
|
|
30
|
+
return @headroom_supported unless @headroom_supported.nil?
|
|
31
|
+
@headroom_supported = begin
|
|
32
|
+
load("x: 1\n",
|
|
33
|
+
desc: [{ kind: :mapping, child_index: 1, child_count: 1 },
|
|
34
|
+
{ wire_name: "x", kind: :scalar, type: :any }]) && true
|
|
35
|
+
rescue Error, Yeptris::ParseError, Yeptris::Error
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
16
40
|
class RequiredMissing < Error; end
|
|
17
41
|
|
|
18
|
-
ELEMENT_SIZE = { 0 => 16, 1 => 8, 2 => 8, 3 => 1, 4 => 1 }.freeze # by TYPE value
|
|
42
|
+
ELEMENT_SIZE = { 0 => 16, 1 => 8, 2 => 8, 3 => 1, 4 => 1, 5 => ANY_SIZE }.freeze # by TYPE value
|
|
19
43
|
|
|
20
44
|
# A materialized span: zero-copy off/len into the source plus the
|
|
21
45
|
# node's byte offset (the CALLBACK escape hatch).
|
|
@@ -47,7 +71,7 @@ module Yeptris
|
|
|
47
71
|
{ kind: KIND.fetch(n.fetch(:kind)),
|
|
48
72
|
type: TYPE.fetch(n[:type] || :str),
|
|
49
73
|
wire: n[:wire_name],
|
|
50
|
-
flags: n[:required] ? REQUIRED : 0,
|
|
74
|
+
flags: (n[:required] ? REQUIRED : 0) | (n[:first_wins] ? FIRST_WINS : 0),
|
|
51
75
|
child_index: n[:child_index] || 0,
|
|
52
76
|
child_count: n[:child_count] || 0 }
|
|
53
77
|
end
|
|
@@ -120,6 +144,16 @@ module Yeptris
|
|
|
120
144
|
when TYPE[:float] then data.get_double(k * 8)
|
|
121
145
|
when TYPE[:bool] then data.get_uchar(k) == 1
|
|
122
146
|
when TYPE[:null] then nil
|
|
147
|
+
when TYPE[:any]
|
|
148
|
+
rec = k * ANY_SIZE
|
|
149
|
+
case ANY_KIND[data.get_uint8(rec)]
|
|
150
|
+
when :int then data.get_int64(rec + 16)
|
|
151
|
+
when :float then data.get_double(rec + 16)
|
|
152
|
+
when :bool then data.get_uint8(rec + 3) == 1
|
|
153
|
+
when :null then nil
|
|
154
|
+
else # str/timestamp: the zero-copy span
|
|
155
|
+
source.byteslice(data.get_uint32(rec + 4), data.get_uint32(rec + 8))
|
|
156
|
+
end
|
|
123
157
|
else
|
|
124
158
|
off = data.get_uint32(k * 16)
|
|
125
159
|
len = data.get_uint32(k * 16 + 4)
|
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.14.1".freeze
|
|
7
7
|
# The error hierarchy lives in THIS file (the parent namespace's
|
|
8
8
|
# own file): nested constants do not trigger a parent-constant
|
|
9
9
|
# autoload, and the law forbids internal requires — defining the
|
|
@@ -76,6 +76,15 @@ YEPTRIS_API YeptrisNode yeptris_node_map_get(YeptrisNode node, const char* key,
|
|
|
76
76
|
YEPTRIS_API int yeptris_node_map_at(YeptrisNode node, size_t index, YeptrisNode* key,
|
|
77
77
|
YeptrisNode* value);
|
|
78
78
|
|
|
79
|
+
/* Bulk children drain — the O(n) iteration primitive (ruby #168: the
|
|
80
|
+
* per-index seq_at/map_at walks make an 80k-row sequence O(n^2)). One
|
|
81
|
+
* call walks the child list once and fills out[0..cap) with handles:
|
|
82
|
+
* sequence elements in order; mapping KEY,VALUE pairs interleaved.
|
|
83
|
+
* Returns the total child count (mapping pairs count twice); out==NULL
|
|
84
|
+
* or cap==0 returns the count without allocating handles. Handles come
|
|
85
|
+
* from the document's pool — document_free reclaims them. */
|
|
86
|
+
YEPTRIS_API size_t yeptris_node_children(YeptrisNode node, YeptrisNode* out, size_t cap);
|
|
87
|
+
|
|
79
88
|
/* ---- Construction (TODO.impl/11 phase 3) ----
|
|
80
89
|
*
|
|
81
90
|
* Build documents from scratch and mutate them; the emitter and every
|
|
@@ -734,6 +734,7 @@ static YeptrisDocument cbor_wrap(yep_dom* dom, const void* buf, const yep_alloca
|
|
|
734
734
|
d->transcoded_len = 0;
|
|
735
735
|
d->input = (const char*)buf;
|
|
736
736
|
d->finish_pool = NULL;
|
|
737
|
+
d->lazy_tape = NULL; /* field-by-field ctor: no garbage for free */
|
|
737
738
|
return (YeptrisDocument)d;
|
|
738
739
|
}
|
|
739
740
|
|
|
@@ -839,6 +840,7 @@ YEPTRIS_API YeptrisDocument yeptris_cbor_decode(const void* buf, size_t len, uin
|
|
|
839
840
|
doc->transcoded_len = 0;
|
|
840
841
|
doc->input = (const char*)buf;
|
|
841
842
|
doc->finish_pool = NULL;
|
|
843
|
+
doc->lazy_tape = NULL; /* field-by-field ctor: no garbage for free */
|
|
842
844
|
if (status != NULL) {
|
|
843
845
|
*status = YEPTRIS_OK;
|
|
844
846
|
}
|
|
@@ -703,7 +703,10 @@ static void cbor_write_item(cenc* e, uint32_t id, uint8_t* out, size_t* pos) {
|
|
|
703
703
|
static int cbor_run(YeptrisDocument handle, uint32_t opts, uint8_t* out, size_t cap,
|
|
704
704
|
size_t* out_len, int* written) {
|
|
705
705
|
yeptris_document* doc = (yeptris_document*)handle;
|
|
706
|
-
yep_dom* d = doc
|
|
706
|
+
yep_dom* d = yep_doc_dom(doc); /* #342 lazy: materialize on encode */
|
|
707
|
+
if (d == NULL) {
|
|
708
|
+
return YEPTRIS_ERROR_MEMORY;
|
|
709
|
+
}
|
|
707
710
|
if (d->dcount == 0) {
|
|
708
711
|
return YEPTRIS_ERROR_ARG;
|
|
709
712
|
}
|
|
@@ -22,16 +22,35 @@ typedef struct yeptris_document {
|
|
|
22
22
|
void* finish_pool; /* engine finish pool: resolved tags, folded and
|
|
23
23
|
escaped scalars outlive the engine through the
|
|
24
24
|
document */
|
|
25
|
+
void* lazy_tape; /* #342 slice 2: the parsed tape when the tree is
|
|
26
|
+
* deferred (gate-clean strict JSON). dom==NULL until
|
|
27
|
+
* the first tree access materializes via dom_from_tape;
|
|
28
|
+
* freed with the document. void* to avoid a header
|
|
29
|
+
* cycle — parse.c casts to yeptris_json_tape* */
|
|
25
30
|
} yeptris_document;
|
|
26
31
|
|
|
27
32
|
/* Node handle: a (document, node-id) pair so nodes stay usable even if
|
|
28
33
|
* the node pool grows (ids are stable; pointers are not). Defined here
|
|
29
34
|
* (not parse.c) since the query layer and the builder share it. */
|
|
35
|
+
/* #342 slice 2: the lazy-tree choke point. Returns the document's dom,
|
|
36
|
+
* materializing it from lazy_tape on first access (NULL when materialization
|
|
37
|
+
* fails — callers treat it as an empty/unusable tree). Every consumer that
|
|
38
|
+
* reads ->dom on a parse-produced document routes through here; the builder
|
|
39
|
+
* and CBOR decode paths set dom eagerly and are unaffected. parse.c owns it. */
|
|
30
40
|
typedef struct yeptris_node {
|
|
31
41
|
yeptris_document* doc;
|
|
32
42
|
uint32_t id;
|
|
33
43
|
} yeptris_node;
|
|
34
44
|
|
|
45
|
+
#ifdef __cplusplus
|
|
46
|
+
extern "C" {
|
|
47
|
+
#endif
|
|
48
|
+
|
|
49
|
+
yep_dom* yep_doc_dom(yeptris_document* doc);
|
|
35
50
|
yeptris_node* yep_handle_new(yeptris_document* doc, uint32_t id);
|
|
36
51
|
|
|
52
|
+
#ifdef __cplusplus
|
|
53
|
+
}
|
|
54
|
+
#endif
|
|
55
|
+
|
|
37
56
|
#endif /* YEP_DOC_H */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* dom.c — event-built DOM (builder sink + storage). */
|
|
2
2
|
|
|
3
|
+
#include <stdio.h>
|
|
3
4
|
#include <string.h>
|
|
4
5
|
|
|
5
6
|
#include "common/simd_text.h"
|
|
@@ -864,6 +865,132 @@ int dom_on_flow_commit(void* ctx) {
|
|
|
864
865
|
return 1;
|
|
865
866
|
}
|
|
866
867
|
|
|
868
|
+
int dom_from_tape(yep_dom* d, const yeptris_json_tape* t) {
|
|
869
|
+
if (d == NULL || t == NULL || t->_src == NULL) {
|
|
870
|
+
return -1;
|
|
871
|
+
}
|
|
872
|
+
/* the columns are the one representation valid on every route
|
|
873
|
+
* (strict: native; lenient fused: lazily materialized from the
|
|
874
|
+
* records; scalar-root lenient: native column writes) */
|
|
875
|
+
if (yeptris_tape_columns((yeptris_json_tape*)t) != 0) {
|
|
876
|
+
return -1;
|
|
877
|
+
}
|
|
878
|
+
if (t->kinds == NULL) {
|
|
879
|
+
return -1;
|
|
880
|
+
}
|
|
881
|
+
d->input_base = (const char*)t->_src;
|
|
882
|
+
d->input_len = t->_srclen;
|
|
883
|
+
const char* src = (const char*)t->_src;
|
|
884
|
+
d->depth = 0;
|
|
885
|
+
for (uint32_t i = 0; i < t->count; i++) {
|
|
886
|
+
const uint8_t kind = t->kinds[i];
|
|
887
|
+
const uint32_t len = t->lens[i];
|
|
888
|
+
const uint32_t off = t->offs[i];
|
|
889
|
+
switch (kind) {
|
|
890
|
+
case YEP_T_DOC:
|
|
891
|
+
break; /* the stream boundary record */
|
|
892
|
+
case YEP_T_SEQ_OPEN:
|
|
893
|
+
case YEP_T_MAP_OPEN: {
|
|
894
|
+
if (d->depth >= YEP_DOM_MAX_DEPTH) {
|
|
895
|
+
return -1;
|
|
896
|
+
}
|
|
897
|
+
uint32_t id =
|
|
898
|
+
dom_open_node(d, kind == YEP_T_SEQ_OPEN ? YEP_DOM_SEQUENCE : YEP_DOM_MAPPING, NULL,
|
|
899
|
+
NULL, 0, 0, 0, 1);
|
|
900
|
+
if (id == UINT32_MAX || dom_place(d, id) != 0) {
|
|
901
|
+
return -1;
|
|
902
|
+
}
|
|
903
|
+
d->map_pending_key[d->depth] = 0;
|
|
904
|
+
d->stack[d->depth++] = id;
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
case YEP_T_CLOSE:
|
|
908
|
+
if (d->depth == 0) {
|
|
909
|
+
return -1;
|
|
910
|
+
}
|
|
911
|
+
d->depth--;
|
|
912
|
+
break;
|
|
913
|
+
case YEP_T_NULL:
|
|
914
|
+
case YEP_T_TRUE:
|
|
915
|
+
case YEP_T_FALSE:
|
|
916
|
+
case YEP_T_INT:
|
|
917
|
+
case YEP_T_FLOAT:
|
|
918
|
+
case YEP_T_NUM:
|
|
919
|
+
case YEP_T_STR: {
|
|
920
|
+
yep_tag_id tag = YEPTRIS_TAG_STR;
|
|
921
|
+
uint8_t style = YEP_STYLE_PLAIN;
|
|
922
|
+
int implicit = 0;
|
|
923
|
+
switch (kind) {
|
|
924
|
+
case YEP_T_NULL:
|
|
925
|
+
tag = YEPTRIS_TAG_NULL;
|
|
926
|
+
implicit = 1;
|
|
927
|
+
break;
|
|
928
|
+
case YEP_T_TRUE:
|
|
929
|
+
case YEP_T_FALSE:
|
|
930
|
+
tag = YEPTRIS_TAG_BOOL;
|
|
931
|
+
implicit = 1;
|
|
932
|
+
break;
|
|
933
|
+
case YEP_T_INT:
|
|
934
|
+
case YEP_T_FLOAT:
|
|
935
|
+
case YEP_T_NUM:
|
|
936
|
+
tag = YEPTRIS_TAG_INT;
|
|
937
|
+
implicit = 1;
|
|
938
|
+
if (kind == YEP_T_NUM) {
|
|
939
|
+
/* the lenient route's deferred contract: the span
|
|
940
|
+
* carried NO number grammar at parse — classify
|
|
941
|
+
* now (the tape convert's own authority) */
|
|
942
|
+
size_t adv = 0;
|
|
943
|
+
int flt = 0;
|
|
944
|
+
if (yep_json_number_shape(src + off, len, &adv, &flt) == 0 || adv != len) {
|
|
945
|
+
return -1; /* malformed: the strict route rejected at parse */
|
|
946
|
+
}
|
|
947
|
+
tag = flt ? YEPTRIS_TAG_FLOAT : YEPTRIS_TAG_INT;
|
|
948
|
+
} else if (kind == YEP_T_FLOAT) {
|
|
949
|
+
tag = YEPTRIS_TAG_FLOAT;
|
|
950
|
+
}
|
|
951
|
+
break;
|
|
952
|
+
default:
|
|
953
|
+
style = YEP_STYLE_DOUBLE_QUOTED;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
uint32_t id =
|
|
957
|
+
dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, style, (uint8_t)implicit, 0);
|
|
958
|
+
if (id == UINT32_MAX) {
|
|
959
|
+
return -1;
|
|
960
|
+
}
|
|
961
|
+
if (kind == YEP_T_STR) {
|
|
962
|
+
/* the record's span is the INNER bytes (quotes off) */
|
|
963
|
+
if (memchr(src + off, '\\', len) != NULL) {
|
|
964
|
+
/* escaped: unescape into the arena (the fused
|
|
965
|
+
* builder's arm, byte for byte) */
|
|
966
|
+
char* dst = yep_dom_str_tail(d, len);
|
|
967
|
+
if (dst == NULL) {
|
|
968
|
+
return -1;
|
|
969
|
+
}
|
|
970
|
+
d->nodes[id].value = yep_dom_str_commit(
|
|
971
|
+
d, yep_finish_double_into(src, off, off + len, dst, len));
|
|
972
|
+
} else {
|
|
973
|
+
yep_view v = {src + off, len};
|
|
974
|
+
d->nodes[id].value = dom_str_in(d, &v, 1);
|
|
975
|
+
}
|
|
976
|
+
} else {
|
|
977
|
+
yep_view v = {src + off, len};
|
|
978
|
+
d->nodes[id].value = dom_str_in(d, &v, 1);
|
|
979
|
+
}
|
|
980
|
+
d->nodes[id].tag_id = tag;
|
|
981
|
+
if (dom_place(d, id) != 0) {
|
|
982
|
+
return -1;
|
|
983
|
+
}
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
default:
|
|
987
|
+
return -1; /* a CONT run or an unknown kind: neither reaches a
|
|
988
|
+
strict-legal tape (CONT only past 16 MiB tokens) */
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return d->depth == 0 ? 0 : -1;
|
|
992
|
+
}
|
|
993
|
+
|
|
867
994
|
void dom_on_flow_rollback(void* ctx) {
|
|
868
995
|
yep_dom* d = (yep_dom*)ctx;
|
|
869
996
|
if (d->flow_staged) {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include <stdint.h>
|
|
19
19
|
|
|
20
20
|
#include <yeptris/api.h> /* YEPTRIS_CT_ASSERT (the layout gates) */
|
|
21
|
+
#include <yeptris/tape.h> /* yeptris_json_tape (dom_from_tape) */ /* YEPTRIS_CT_ASSERT (the layout gates) */
|
|
21
22
|
|
|
22
23
|
#include "../common/simd_text.h"
|
|
23
24
|
|
|
@@ -246,6 +247,15 @@ void yep_mut_set_depths(yep_dom* d, uint32_t id, uint16_t depth);
|
|
|
246
247
|
* strictly-validated span [open, close] directly through the DOM's own
|
|
247
248
|
* placement/link/anchor laws — node-for-node identical to the event
|
|
248
249
|
* path (the flow-direct-diff ctest pins it). 1 = built, <0 = abort. */
|
|
250
|
+
/* dom_from_tape — the lazy-DOM seam (#342 slice 1): builds the node
|
|
251
|
+
* tree from the tape's records, mirroring dom_on_flow_build's arms
|
|
252
|
+
* (borrowed spans for clean strings, an arena unescape for escaped
|
|
253
|
+
* ones, typed literals by kind). The records are pre-validated, so
|
|
254
|
+
* the walk is placement only. Returns 0, or -1 (allocation/depth/an
|
|
255
|
+
* unexpected record). STRICT tapes only — YEP_T_NUM (the lenient
|
|
256
|
+
* route's deferred numbers) is rejected. */
|
|
257
|
+
int dom_from_tape(yep_dom* d, const yeptris_json_tape* t);
|
|
258
|
+
|
|
249
259
|
int dom_on_flow_build(void* ctx, const char* p, size_t open, size_t len, uint32_t line,
|
|
250
260
|
size_t line_start, yep_view anchor, yep_view tag, uint32_t anchor_id,
|
|
251
261
|
int max_depth, size_t* close);
|
|
@@ -45,6 +45,9 @@ YEPTRIS_API size_t yeptris_serialize_into_ex(YeptrisDocument handle,
|
|
|
45
45
|
em.w.grow = 0;
|
|
46
46
|
em.w.oom = 0;
|
|
47
47
|
em.doc = (const yeptris_document*)handle;
|
|
48
|
+
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
48
51
|
em.w.p = NULL;
|
|
49
52
|
em.w.last = 0;
|
|
50
53
|
em.w.force_flow = 0;
|
|
@@ -100,6 +103,9 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
|
|
|
100
103
|
em.w.grow = 0;
|
|
101
104
|
em.w.oom = 0;
|
|
102
105
|
em.doc = (const yeptris_document*)handle;
|
|
106
|
+
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
107
|
+
return NULL;
|
|
108
|
+
}
|
|
103
109
|
em.w.p = NULL;
|
|
104
110
|
em.w.last = 0;
|
|
105
111
|
em.w.force_flow = 0;
|
|
@@ -174,6 +180,9 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
|
|
|
174
180
|
em.w.grow = 0;
|
|
175
181
|
em.w.oom = 0;
|
|
176
182
|
em.doc = (const yeptris_document*)handle;
|
|
183
|
+
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
184
|
+
return NULL;
|
|
185
|
+
}
|
|
177
186
|
em.w.p = NULL;
|
|
178
187
|
em.w.last = 0;
|
|
179
188
|
em.w.force_flow = 0;
|
|
@@ -224,6 +233,9 @@ YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len,
|
|
|
224
233
|
em.w.grow = 0;
|
|
225
234
|
em.w.oom = 0;
|
|
226
235
|
em.doc = (const yeptris_document*)handle;
|
|
236
|
+
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
237
|
+
return NULL;
|
|
238
|
+
}
|
|
227
239
|
em.w.p = NULL;
|
|
228
240
|
em.w.last = 0;
|
|
229
241
|
em.w.force_flow = 0;
|
|
@@ -378,6 +390,9 @@ YEPTRIS_API size_t yeptris_serialize_stream(YeptrisDocument handle,
|
|
|
378
390
|
em.w.grow = 0;
|
|
379
391
|
em.w.oom = 0;
|
|
380
392
|
em.doc = (const yeptris_document*)handle;
|
|
393
|
+
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
394
|
+
return 0;
|
|
395
|
+
}
|
|
381
396
|
em.w.p = NULL;
|
|
382
397
|
em.w.last = 0;
|
|
383
398
|
em.w.force_flow = 0;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include "parse/numbers.h"
|
|
13
13
|
#include "resolve/resolver.h"
|
|
14
14
|
#include "scan/json.h"
|
|
15
|
+
#include "tape_in.h"
|
|
15
16
|
|
|
16
17
|
#include "parse/events.h"
|
|
17
18
|
#include <errno.h>
|
|
@@ -38,6 +39,59 @@ YEPTRIS_API YeptrisDocument yeptris_parse(const char* buf, size_t len, YeptrisSt
|
|
|
38
39
|
return yeptris_parse_ex(buf, len, NULL, status);
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/* #342 slice 2: materialize the deferred tree on first access. The
|
|
43
|
+
* gate-clean JSON route carries the parsed TAPE (simdjson's design:
|
|
44
|
+
* its "DOM" is a tape) and pays dom_from_tape only when a consumer
|
|
45
|
+
* touches the tree — parse-only workloads never build nodes. */
|
|
46
|
+
yep_dom* yep_doc_dom(yeptris_document* doc) {
|
|
47
|
+
if (doc == NULL) {
|
|
48
|
+
return NULL;
|
|
49
|
+
}
|
|
50
|
+
if (doc->dom == NULL && doc->lazy_tape != NULL) {
|
|
51
|
+
yeptris_json_tape* t = (yeptris_json_tape*)doc->lazy_tape;
|
|
52
|
+
yep_dom* dom = yep_dom_create(doc->sys);
|
|
53
|
+
if (dom != NULL && dom_from_tape(dom, t) == 0) {
|
|
54
|
+
doc->dom = dom;
|
|
55
|
+
yeptris_tape_free(t);
|
|
56
|
+
yep_free(doc->sys, t);
|
|
57
|
+
doc->lazy_tape = NULL;
|
|
58
|
+
} else {
|
|
59
|
+
yep_dom_destroy(dom);
|
|
60
|
+
dom = NULL; /* the tape stays: a later access retries, or
|
|
61
|
+
free drops it (the materializer only fails on
|
|
62
|
+
malformed NUM spans, which the strict gate
|
|
63
|
+
already rejected at parse) */
|
|
64
|
+
}
|
|
65
|
+
return dom;
|
|
66
|
+
}
|
|
67
|
+
return doc->dom;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* The strict-JSON document wrapper (both routes share it). */
|
|
71
|
+
/* The lazy route's acceptance debt: the fused lenient walk records
|
|
72
|
+
* number runs UNVALIDATED (its simdjson-style deferred contract).
|
|
73
|
+
* parse_json's contract is RFC 8259 at parse time, so the deferred
|
|
74
|
+
* spans settle HERE — full-span shape check per NUM record, decoding
|
|
75
|
+
* the packed records directly (no columns build at parse; the walk
|
|
76
|
+
* guarantees recs are primary on this route). */
|
|
77
|
+
static int lazy_nums_settled(const yeptris_json_tape* t) {
|
|
78
|
+
const char* p = (const char*)t->_src;
|
|
79
|
+
for (uint32_t i = 0; i < t->count; i++) {
|
|
80
|
+
uint64_t r = t->recs[i];
|
|
81
|
+
if ((uint8_t)(r & 0xFFu) != YEP_T_NUM) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
uint32_t len = (uint32_t)((r >> 8) & 0x7FFFFFu);
|
|
85
|
+
uint32_t off = (uint32_t)(r >> 32);
|
|
86
|
+
size_t adv = 0;
|
|
87
|
+
int flt = 0;
|
|
88
|
+
if (yep_json_number_shape(p + off, len, &adv, &flt) == 0 || adv != (size_t)len) {
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
41
95
|
/* The strict-JSON document wrapper (both routes share it). */
|
|
42
96
|
static YeptrisDocument yep_json_doc_wrap(yep_dom* dom, const char* buf, size_t len,
|
|
43
97
|
const yep_allocator* sys, YeptrisStatus* status) {
|
|
@@ -52,6 +106,7 @@ static YeptrisDocument yep_json_doc_wrap(yep_dom* dom, const char* buf, size_t l
|
|
|
52
106
|
}
|
|
53
107
|
doc->dom = dom;
|
|
54
108
|
doc->sys = sys;
|
|
109
|
+
doc->lazy_tape = NULL;
|
|
55
110
|
doc->schema = YEPTRIS_SCHEMA_12_CORE; /* strict JSON is core by construction */
|
|
56
111
|
doc->transcoded = NULL;
|
|
57
112
|
doc->transcoded_len = 0;
|
|
@@ -75,41 +130,44 @@ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, Yept
|
|
|
75
130
|
* builds. Rejects, non-opener roots, and surprises fall to the
|
|
76
131
|
* original sequence below, whose error precedence is byte-for-byte
|
|
77
132
|
* the pinned behavior (json-suite-strict gates this). */
|
|
78
|
-
|
|
133
|
+
int gated = len > 0 && yep_text_active()->gate_scan(buf, len);
|
|
134
|
+
if (!gated) {
|
|
79
135
|
size_t off = 0;
|
|
80
136
|
while (off < len &&
|
|
81
137
|
(buf[off] == ' ' || buf[off] == '\t' || buf[off] == '\n' || buf[off] == '\r')) {
|
|
82
138
|
off++;
|
|
83
139
|
}
|
|
84
|
-
|
|
140
|
+
/* tabs: the lenient walk's own pinned acceptance (its route
|
|
141
|
+
* takes them); the strict routes reject them (ErrorParity's
|
|
142
|
+
* pinned agreement). A tab-carrying buffer must NOT take the
|
|
143
|
+
* lazy walk — the validating sequence below reports exactly
|
|
144
|
+
* the pinned reject. */
|
|
145
|
+
if (off < len && (buf[off] == '[' || buf[off] == '{') && memchr(buf, '\t', len) == NULL) {
|
|
146
|
+
/* #342 slice 2: the fused LENIENT walk (one pass, records
|
|
147
|
+
* only — no node building) settles the deferred number
|
|
148
|
+
* grammar inline, then the tape rides the document and
|
|
149
|
+
* dom_from_tape builds nodes on the first tree access.
|
|
150
|
+
* A reject or malformed span falls to the original
|
|
151
|
+
* sequence below, whose error precedence is byte-for-byte
|
|
152
|
+
* the pinned behavior. */
|
|
85
153
|
const yep_allocator* sys = yep_system_allocator();
|
|
86
|
-
|
|
87
|
-
if (
|
|
154
|
+
yeptris_json_tape* t = yep_alloc(sys, sizeof(*t));
|
|
155
|
+
if (t == NULL) {
|
|
88
156
|
st = YEPTRIS_ERROR_MEMORY;
|
|
89
157
|
goto jfail;
|
|
90
158
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
dom->flow_strict = 0;
|
|
98
|
-
int tail_ok = 0;
|
|
99
|
-
if (rc == 1) {
|
|
100
|
-
/* trailing garbage after the closer is a reject the
|
|
101
|
-
* fallback reports exactly as before */
|
|
102
|
-
size_t t = close + 1;
|
|
103
|
-
while (t < len &&
|
|
104
|
-
(buf[t] == ' ' || buf[t] == '\t' || buf[t] == '\n' || buf[t] == '\r')) {
|
|
105
|
-
t++;
|
|
159
|
+
if (yep_tape_walk_lenient_fused(buf, len, off, t) == YEPTRIS_OK &&
|
|
160
|
+
lazy_nums_settled(t) == 0) {
|
|
161
|
+
YeptrisDocument h = yep_json_doc_wrap(NULL, buf, len, sys, status);
|
|
162
|
+
if (h != NULL) {
|
|
163
|
+
((yeptris_document*)h)->lazy_tape = t;
|
|
164
|
+
return h;
|
|
106
165
|
}
|
|
107
|
-
|
|
166
|
+
yeptris_tape_free(t); /* wrap failed (memory): below */
|
|
167
|
+
} else {
|
|
168
|
+
yeptris_tape_free(t); /* reject/malformed: below */
|
|
108
169
|
}
|
|
109
|
-
|
|
110
|
-
return yep_json_doc_wrap(dom, buf, len, sys, status);
|
|
111
|
-
}
|
|
112
|
-
yep_dom_destroy(dom); /* reject/rollback: the sequence below */
|
|
170
|
+
yep_free(sys, t);
|
|
113
171
|
}
|
|
114
172
|
}
|
|
115
173
|
size_t verr = 0;
|
|
@@ -120,8 +178,7 @@ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, Yept
|
|
|
120
178
|
goto jfail;
|
|
121
179
|
}
|
|
122
180
|
size_t uerr = 0;
|
|
123
|
-
if (
|
|
124
|
-
!yep_utf8_validate((const unsigned char*)buf, len, &uerr)) {
|
|
181
|
+
if (gated && !yep_utf8_validate((const unsigned char*)buf, len, &uerr)) {
|
|
125
182
|
yep_error_set(yep_error_tls(), YEP_ERR_ENCODING, 0, 0, uerr, "ill-formed UTF-8 at byte %zu",
|
|
126
183
|
uerr);
|
|
127
184
|
st = YEPTRIS_ERROR_ENCODING;
|
|
@@ -343,6 +400,8 @@ engine_enter:
|
|
|
343
400
|
* or an arena copy (dom_ev_str) — nothing references it */
|
|
344
401
|
yep_pool_destroy(finish);
|
|
345
402
|
doc->finish_pool = NULL;
|
|
403
|
+
doc->lazy_tape = NULL; /* field-by-field ctor: leave no garbage
|
|
404
|
+
* (document_free frees a non-NULL tape) */
|
|
346
405
|
return (YeptrisDocument)doc;
|
|
347
406
|
|
|
348
407
|
fail:
|
|
@@ -363,6 +422,10 @@ YEPTRIS_API void yeptris_document_free(YeptrisDocument handle) {
|
|
|
363
422
|
if (doc == NULL) {
|
|
364
423
|
return;
|
|
365
424
|
}
|
|
425
|
+
if (doc->lazy_tape != NULL) { /* never materialized: free stays free */
|
|
426
|
+
yeptris_tape_free((yeptris_json_tape*)doc->lazy_tape);
|
|
427
|
+
yep_free(doc->sys, doc->lazy_tape);
|
|
428
|
+
}
|
|
366
429
|
yep_dom_destroy(doc->dom);
|
|
367
430
|
yep_pool_destroy((yep_pool*)doc->finish_pool);
|
|
368
431
|
yep_free(doc->sys, doc->transcoded);
|
|
@@ -371,7 +434,7 @@ YEPTRIS_API void yeptris_document_free(YeptrisDocument handle) {
|
|
|
371
434
|
|
|
372
435
|
YEPTRIS_API size_t yeptris_document_count(YeptrisDocument handle) {
|
|
373
436
|
yeptris_document* doc = (yeptris_document*)handle;
|
|
374
|
-
return doc ? doc->
|
|
437
|
+
return doc ? yep_doc_dom(doc)->dcount : 0;
|
|
375
438
|
}
|
|
376
439
|
|
|
377
440
|
yeptris_node* yep_handle_new(yeptris_document* doc, uint32_t id) {
|
|
@@ -390,10 +453,14 @@ static YeptrisNode node_new_handle(yeptris_document* doc, uint32_t id) {
|
|
|
390
453
|
|
|
391
454
|
YEPTRIS_API YeptrisNode yeptris_document_root(YeptrisDocument handle, size_t index) {
|
|
392
455
|
yeptris_document* doc = (yeptris_document*)handle;
|
|
393
|
-
if (doc == NULL
|
|
456
|
+
if (doc == NULL) {
|
|
394
457
|
return NULL;
|
|
395
458
|
}
|
|
396
|
-
|
|
459
|
+
yep_dom* dom = yep_doc_dom(doc);
|
|
460
|
+
if (dom == NULL || index >= dom->dcount) {
|
|
461
|
+
return NULL;
|
|
462
|
+
}
|
|
463
|
+
return node_new_handle(doc, dom->docs[index]);
|
|
397
464
|
}
|
|
398
465
|
|
|
399
466
|
static const yep_dnode* node_of(YeptrisNode handle) {
|
|
@@ -588,6 +655,24 @@ YEPTRIS_API YeptrisNode yeptris_node_seq_at(YeptrisNode handle, size_t index) {
|
|
|
588
655
|
return wrap((yeptris_node*)handle, id);
|
|
589
656
|
}
|
|
590
657
|
|
|
658
|
+
YEPTRIS_API size_t yeptris_node_children(YeptrisNode handle, YeptrisNode* out, size_t cap) {
|
|
659
|
+
const yep_dnode* n = node_of(handle);
|
|
660
|
+
if (n == NULL || (n->kind != YEP_DOM_SEQUENCE && n->kind != YEP_DOM_MAPPING)) {
|
|
661
|
+
return 0;
|
|
662
|
+
}
|
|
663
|
+
size_t count = 0;
|
|
664
|
+
uint32_t id = n->first_child;
|
|
665
|
+
while (id != UINT32_MAX) {
|
|
666
|
+
if (out != NULL && count < cap) {
|
|
667
|
+
out[count] = wrap((yeptris_node*)handle, id);
|
|
668
|
+
}
|
|
669
|
+
count++;
|
|
670
|
+
const yep_dnode* cur = yep_dom_node(((yeptris_node*)handle)->doc->dom, id);
|
|
671
|
+
id = cur ? cur->next_sibling : UINT32_MAX;
|
|
672
|
+
}
|
|
673
|
+
return count;
|
|
674
|
+
}
|
|
675
|
+
|
|
591
676
|
YEPTRIS_API size_t yeptris_node_map_count(YeptrisNode handle) {
|
|
592
677
|
const yep_dnode* n = node_of(handle);
|
|
593
678
|
return (n && n->kind == YEP_DOM_MAPPING) ? n->count / 2 : 0;
|
|
@@ -79,12 +79,16 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
|
|
|
79
79
|
}
|
|
80
80
|
return NULL;
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
yeptris_plan* plan = calloc(1, sizeof(*plan));
|
|
82
|
+
yeptris_document* d = (yeptris_document*)doc;
|
|
83
|
+
yeptris_plan* plan = calloc(1, sizeof(*plan)); /* before any goto: out frees it */
|
|
85
84
|
if (plan == NULL) {
|
|
86
85
|
goto mem;
|
|
87
86
|
}
|
|
87
|
+
yep_dom* dd = yep_doc_dom(d); /* #342 lazy: the spec parse may defer */
|
|
88
|
+
if (dd == NULL) {
|
|
89
|
+
goto mem;
|
|
90
|
+
}
|
|
91
|
+
const yep_dnode* root = yep_dom_node(dd, dd->docs[0]);
|
|
88
92
|
if (root == NULL || root->kind != 2) { /* the spec root is a mapping */
|
|
89
93
|
goto bad;
|
|
90
94
|
}
|
|
@@ -95,10 +99,10 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
|
|
|
95
99
|
int saw_kind = 0;
|
|
96
100
|
uint32_t pair = root->first_child;
|
|
97
101
|
while (pair != UINT32_MAX) {
|
|
98
|
-
const yep_dnode* kn = yep_dom_node(
|
|
99
|
-
const yep_dnode* vn = yep_dom_node(
|
|
100
|
-
yep_view kv = sv_view(
|
|
101
|
-
yep_view vv = sv_view(
|
|
102
|
+
const yep_dnode* kn = yep_dom_node(dd, pair);
|
|
103
|
+
const yep_dnode* vn = yep_dom_node(dd, kn->next_sibling);
|
|
104
|
+
yep_view kv = sv_view(dd, kn->value);
|
|
105
|
+
yep_view vv = sv_view(dd, vn->value);
|
|
102
106
|
if (kv.len == 4 && memcmp(kv.p, "kind", 4) == 0) {
|
|
103
107
|
saw_kind = 1;
|
|
104
108
|
if (!(vv.len == 3 && (memcmp(vv.p, "seq", 3) == 0 || memcmp(vv.p, "map", 3) == 0))) {
|
|
@@ -130,7 +134,7 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
|
|
|
130
134
|
if (path_node->tag_id != YEPTRIS_TAG_STR) {
|
|
131
135
|
goto bad; /* the path is a string, not a number */
|
|
132
136
|
}
|
|
133
|
-
yep_view vv = sv_view(
|
|
137
|
+
yep_view vv = sv_view(dd, path_node->value);
|
|
134
138
|
if (vv.len != 0) { /* "" rides the seq root like no path */
|
|
135
139
|
plan->seg_off = malloc(sizeof(uint32_t));
|
|
136
140
|
plan->seg_len = malloc(sizeof(uint32_t));
|
|
@@ -159,11 +163,11 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
|
|
|
159
163
|
size_t off = 0;
|
|
160
164
|
uint32_t cn = path_node->first_child;
|
|
161
165
|
for (size_t i = 0; i < n; i++) {
|
|
162
|
-
const yep_dnode* seg = yep_dom_node(
|
|
166
|
+
const yep_dnode* seg = yep_dom_node(dd, cn);
|
|
163
167
|
if (seg == NULL || seg->kind != 0 || seg->tag_id != YEPTRIS_TAG_STR) {
|
|
164
168
|
goto bad; /* every segment is a string */
|
|
165
169
|
}
|
|
166
|
-
yep_view sv = sv_view(
|
|
170
|
+
yep_view sv = sv_view(dd, seg->value);
|
|
167
171
|
if (sv.len == 0) {
|
|
168
172
|
goto bad;
|
|
169
173
|
}
|
|
@@ -191,7 +195,7 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
|
|
|
191
195
|
}
|
|
192
196
|
uint32_t cn = children->first_child;
|
|
193
197
|
for (size_t i = 0; i < plan->ncols; i++) {
|
|
194
|
-
const yep_dnode* leaf = yep_dom_node(
|
|
198
|
+
const yep_dnode* leaf = yep_dom_node(dd, cn);
|
|
195
199
|
if (leaf == NULL || leaf->kind != 2) {
|
|
196
200
|
goto bad;
|
|
197
201
|
}
|
|
@@ -200,10 +204,10 @@ YEPTRIS_API yeptris_plan* yeptris_plan_compile(const char* spec, size_t len, Yep
|
|
|
200
204
|
int kind = -1;
|
|
201
205
|
uint32_t lp = leaf->first_child;
|
|
202
206
|
while (lp != UINT32_MAX) {
|
|
203
|
-
const yep_dnode* lk = yep_dom_node(
|
|
204
|
-
const yep_dnode* lv = yep_dom_node(
|
|
205
|
-
yep_view lkv = sv_view(
|
|
206
|
-
yep_view lvv = sv_view(
|
|
207
|
+
const yep_dnode* lk = yep_dom_node(dd, lp);
|
|
208
|
+
const yep_dnode* lv = yep_dom_node(dd, lk->next_sibling);
|
|
209
|
+
yep_view lkv = sv_view(dd, lk->value);
|
|
210
|
+
yep_view lvv = sv_view(dd, lv->value);
|
|
207
211
|
if (lkv.len == 4 && memcmp(lkv.p, "name", 4) == 0) {
|
|
208
212
|
if (lv->kind != 0 || lvv.len == 0) {
|
|
209
213
|
goto bad;
|
|
@@ -657,15 +661,15 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
|
|
|
657
661
|
if (st != NULL) {
|
|
658
662
|
*st = YEPTRIS_OK;
|
|
659
663
|
}
|
|
660
|
-
const
|
|
661
|
-
if (doc == NULL || plan == NULL ||
|
|
664
|
+
const yep_dom* dd = doc == NULL ? NULL : yep_doc_dom((yeptris_document*)doc);
|
|
665
|
+
if (doc == NULL || plan == NULL || dd == NULL || dd->dcount == 0) {
|
|
662
666
|
if (st != NULL) {
|
|
663
667
|
*st = YEPTRIS_ERROR_ARG;
|
|
664
668
|
}
|
|
665
669
|
return NULL;
|
|
666
670
|
}
|
|
667
|
-
const yep_dnode* root = yep_dom_node(
|
|
668
|
-
const yep_dnode* container = dom_find_rows(
|
|
671
|
+
const yep_dnode* root = yep_dom_node(dd, dd->docs[0]);
|
|
672
|
+
const yep_dnode* container = dom_find_rows(dd, root, plan);
|
|
669
673
|
if (container == NULL) {
|
|
670
674
|
if (st != NULL) {
|
|
671
675
|
*st = YEPTRIS_ERROR_PARSE; /* document shape disagreement */
|
|
@@ -676,7 +680,7 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
|
|
|
676
680
|
size_t rows = 0;
|
|
677
681
|
for (uint32_t cur = container->first_child; cur != UINT32_MAX;) {
|
|
678
682
|
const yep_dnode* row;
|
|
679
|
-
cur = dom_rows_next(
|
|
683
|
+
cur = dom_rows_next(dd, container, cur, &row);
|
|
680
684
|
if (row != NULL) {
|
|
681
685
|
rows++;
|
|
682
686
|
}
|
|
@@ -690,15 +694,15 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
|
|
|
690
694
|
size_t row_i = 0;
|
|
691
695
|
for (uint32_t cur = container->first_child; cur != UINT32_MAX && row_i < rows;) {
|
|
692
696
|
const yep_dnode* row;
|
|
693
|
-
cur = dom_rows_next(
|
|
697
|
+
cur = dom_rows_next(dd, container, cur, &row);
|
|
694
698
|
if (row == NULL) {
|
|
695
699
|
continue;
|
|
696
700
|
}
|
|
697
701
|
uint32_t cn = row->first_child;
|
|
698
702
|
while (cn != UINT32_MAX) {
|
|
699
|
-
const yep_dnode* k = yep_dom_node(
|
|
700
|
-
const yep_dnode* v = yep_dom_node(
|
|
701
|
-
yep_view kv = sv_view(
|
|
703
|
+
const yep_dnode* k = yep_dom_node(dd, cn);
|
|
704
|
+
const yep_dnode* v = yep_dom_node(dd, k->next_sibling);
|
|
705
|
+
yep_view kv = sv_view(dd, k->value);
|
|
702
706
|
int col_idx = -1;
|
|
703
707
|
if (k->kind == YEP_DOM_SCALAR) {
|
|
704
708
|
for (size_t c = 0; c < plan->ncols; c++) {
|
|
@@ -710,10 +714,10 @@ yeptris_document_plan_walk(YeptrisDocument doc, const yeptris_plan* plan, Yeptri
|
|
|
710
714
|
}
|
|
711
715
|
}
|
|
712
716
|
if (col_idx >= 0) {
|
|
713
|
-
const yep_dnode* val = dom_alias_final(
|
|
717
|
+
const yep_dnode* val = dom_alias_final(dd, v);
|
|
714
718
|
yep_result_col* col = &r->cols[col_idx];
|
|
715
719
|
if (val != NULL && val->kind == YEP_DOM_SCALAR && val->tag_id != YEPTRIS_TAG_NULL) {
|
|
716
|
-
yep_view sv = sv_view(
|
|
720
|
+
yep_view sv = sv_view(dd, val->value);
|
|
717
721
|
int filled = 0;
|
|
718
722
|
switch (col->kind) {
|
|
719
723
|
case YEP_PLAN_STR:
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
#include <string.h>
|
|
14
14
|
|
|
15
15
|
#include <yeptris/schema.h>
|
|
16
|
+
#include <yeptris/values.h> /* YeptrisValue: ST_ANY records (#238) */
|
|
16
17
|
|
|
17
18
|
#include "dom/dom.h"
|
|
18
19
|
#include "memory/allocator.h"
|
|
@@ -27,7 +28,9 @@ typedef struct {
|
|
|
27
28
|
const yep_dom* dom;
|
|
28
29
|
int oom;
|
|
29
30
|
int overflow;
|
|
30
|
-
int type_mismatch;
|
|
31
|
+
int type_mismatch; /* node index */
|
|
32
|
+
uint32_t* seen_map; /* per-node: the mapping instance that last
|
|
33
|
+
* emitted — YEP_SF_FIRST_WINS skips a repeat */
|
|
31
34
|
} yep_schema_ctx;
|
|
32
35
|
|
|
33
36
|
static int col_put(yep_schema_ctx* c, uint32_t node, const void* rec, size_t size) {
|
|
@@ -84,6 +87,56 @@ static int emit_scalar(yep_schema_ctx* c, uint32_t node, uint32_t at) {
|
|
|
84
87
|
case YEP_ST_NULL:
|
|
85
88
|
col_put(c, node, &(uint8_t){0}, 0);
|
|
86
89
|
return 1;
|
|
90
|
+
case YEP_ST_ANY: {
|
|
91
|
+
/* the resolver's verdict, verbatim: a YeptrisValue record
|
|
92
|
+
* (#238's headroom) — kind/tag_id from the node, the span
|
|
93
|
+
* borrowed, and the numeric payload converted when the
|
|
94
|
+
* verdict says so (the values-drain's conversion, DOM-side) */
|
|
95
|
+
YeptrisValue r = {0, 0, 0, 0, 0, 0, 0};
|
|
96
|
+
r.tag_id = n->tag_id;
|
|
97
|
+
r.off = (uint32_t)((const char*)v.p - c->dom->input_base);
|
|
98
|
+
r.len = v.len;
|
|
99
|
+
switch (n->tag_id) {
|
|
100
|
+
case YEPTRIS_TAG_NULL:
|
|
101
|
+
r.kind = YEP_V_NULL;
|
|
102
|
+
break;
|
|
103
|
+
case YEPTRIS_TAG_BOOL: {
|
|
104
|
+
r.kind = YEP_V_BOOL;
|
|
105
|
+
r.b = (uint8_t)(v.len > 0 && (v.p[0] == 't' || v.p[0] == 'T' || v.p[0] == 'y' ||
|
|
106
|
+
v.p[0] == 'Y' || v.p[0] == 'o' || v.p[0] == '1'));
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case YEPTRIS_TAG_INT:
|
|
110
|
+
case YEPTRIS_TAG_FLOAT: {
|
|
111
|
+
int64_t iv = 0;
|
|
112
|
+
double dv = 0;
|
|
113
|
+
int shape = 0;
|
|
114
|
+
size_t end = 0;
|
|
115
|
+
if (v.len != 0 && v.p != NULL &&
|
|
116
|
+
yep_json_number_scan(v.p, v.len, &end, &shape, &iv, &dv) != 0 && end == v.len) {
|
|
117
|
+
if (n->tag_id == YEPTRIS_TAG_INT) {
|
|
118
|
+
r.kind = YEP_V_INT;
|
|
119
|
+
r.p = (uint64_t)iv;
|
|
120
|
+
} else {
|
|
121
|
+
r.kind = YEP_V_FLOAT;
|
|
122
|
+
double fd = shape == 0 ? (double)iv : dv;
|
|
123
|
+
memcpy(&r.p, &fd, sizeof(fd));
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
r.kind = YEP_V_STR; /* the resolver said number, the
|
|
127
|
+
bytes disagree: keep the span */
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case YEPTRIS_TAG_TIMESTAMP:
|
|
132
|
+
r.kind = YEP_V_TIMESTAMP;
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
r.kind = YEP_V_STR;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
return col_put(c, node, &r, sizeof(r));
|
|
139
|
+
}
|
|
87
140
|
default:
|
|
88
141
|
return -1;
|
|
89
142
|
}
|
|
@@ -140,10 +193,17 @@ static int emit(yep_schema_ctx* c, uint32_t node, uint32_t at) {
|
|
|
140
193
|
size_t wl = strlen(cd->wire_name ? cd->wire_name : "");
|
|
141
194
|
if (cd->wire_name != NULL && key.len == wl &&
|
|
142
195
|
memcmp(key.p, cd->wire_name, wl) == 0) {
|
|
196
|
+
if ((cd->flags & YEP_SF_FIRST_WINS) && c->seen_map != NULL &&
|
|
197
|
+
c->seen_map[ci] == at + 1) {
|
|
198
|
+
break; /* a repeat within THIS mapping: first wins */
|
|
199
|
+
}
|
|
143
200
|
int rc = emit(c, ci, val);
|
|
144
201
|
if (rc != 1) {
|
|
145
202
|
return rc;
|
|
146
203
|
}
|
|
204
|
+
if ((cd->flags & YEP_SF_FIRST_WINS) && c->seen_map != NULL) {
|
|
205
|
+
c->seen_map[ci] = at + 1;
|
|
206
|
+
}
|
|
147
207
|
break;
|
|
148
208
|
}
|
|
149
209
|
}
|
|
@@ -178,6 +238,7 @@ YEPTRIS_API YeptrisStatus yeptris_schema_load(const char* source, size_t len, Ye
|
|
|
178
238
|
const yep_allocator* sys = yep_system_allocator();
|
|
179
239
|
yep_engine* eng = yep_engine_create(sys);
|
|
180
240
|
yep_dom* dom = yep_dom_create(sys);
|
|
241
|
+
uint32_t* seen_map_ptr = NULL;
|
|
181
242
|
YeptrisStatus st = YEPTRIS_OK;
|
|
182
243
|
yep_sink sink = {.on_event = yep_dom_on_event,
|
|
183
244
|
.ctx = dom,
|
|
@@ -194,6 +255,9 @@ YEPTRIS_API YeptrisStatus yeptris_schema_load(const char* source, size_t len, Ye
|
|
|
194
255
|
}
|
|
195
256
|
if (schema == YEPTRIS_SCHEMA_11_COMPAT) {
|
|
196
257
|
yep_engine_set_resolver(eng, yep_resolver_compat11());
|
|
258
|
+
dom->resolver = yep_resolver_compat11(); /* the DOM's direct
|
|
259
|
+
builders resolve their own tag_ids —
|
|
260
|
+
ST_ANY reads the NODE's verdict */
|
|
197
261
|
}
|
|
198
262
|
dom->input_base = source;
|
|
199
263
|
dom->input_len = len;
|
|
@@ -204,7 +268,12 @@ YEPTRIS_API YeptrisStatus yeptris_schema_load(const char* source, size_t len, Ye
|
|
|
204
268
|
}
|
|
205
269
|
|
|
206
270
|
{
|
|
207
|
-
|
|
271
|
+
uint32_t* seen = seen_map_ptr = calloc(desc_len, sizeof(uint32_t));
|
|
272
|
+
if (seen == NULL) {
|
|
273
|
+
st = YEPTRIS_ERROR_MEMORY;
|
|
274
|
+
goto out;
|
|
275
|
+
}
|
|
276
|
+
yep_schema_ctx c = {desc, desc_len, cols, dom, 0, 0, -1, seen};
|
|
208
277
|
int rc = emit(&c, 0, dom->docs[0]);
|
|
209
278
|
if (rc < 0 && c.type_mismatch >= 0) {
|
|
210
279
|
yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, (uint32_t)c.type_mismatch,
|
|
@@ -232,6 +301,7 @@ YEPTRIS_API YeptrisStatus yeptris_schema_load(const char* source, size_t len, Ye
|
|
|
232
301
|
}
|
|
233
302
|
|
|
234
303
|
out:
|
|
304
|
+
free(seen_map_ptr);
|
|
235
305
|
yep_dom_destroy(dom);
|
|
236
306
|
yep_engine_destroy(eng);
|
|
237
307
|
return st;
|
|
@@ -601,8 +601,8 @@ reject:
|
|
|
601
601
|
* (the classify scan carries no accept/reject structure) and
|
|
602
602
|
* yeptris_tape_convert owns validation. Everything else matches
|
|
603
603
|
* tape_walk state for state. */
|
|
604
|
-
|
|
605
|
-
|
|
604
|
+
YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
|
|
605
|
+
yeptris_json_tape* t) {
|
|
606
606
|
if (tape_carve(t, len) != YEPTRIS_OK) {
|
|
607
607
|
return YEPTRIS_ERROR_MEMORY;
|
|
608
608
|
}
|
|
@@ -1090,7 +1090,7 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape_lenient(const char* source, si
|
|
|
1090
1090
|
}
|
|
1091
1091
|
free(idx);
|
|
1092
1092
|
#endif
|
|
1093
|
-
return
|
|
1093
|
+
return yep_tape_walk_lenient_fused(source, len, at, tape);
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
1096
|
/* scalar root: one record, same deferred split for numbers */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* tape_in.h — the tape module's internal seams (not public ABI).
|
|
2
|
+
*
|
|
3
|
+
* parse.c's #342 slice-2 lazy route drives the fused lenient walk
|
|
4
|
+
* directly: the gate and opener checks it already ran must not be
|
|
5
|
+
* repeated (the lenient public entry re-gates). The walk's contract
|
|
6
|
+
* is the public entry's: gate-clean buffer, p[open] is '[' or '{',
|
|
7
|
+
* tail must be whitespace-only. */
|
|
8
|
+
#ifndef YEP_TAPE_IN_H
|
|
9
|
+
#define YEP_TAPE_IN_H
|
|
10
|
+
|
|
11
|
+
#include <stddef.h>
|
|
12
|
+
|
|
13
|
+
#include <yeptris/tape.h>
|
|
14
|
+
|
|
15
|
+
/* tape.c (was tape_walk_lnt_fused) */
|
|
16
|
+
YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
|
|
17
|
+
yeptris_json_tape* t);
|
|
18
|
+
|
|
19
|
+
#endif
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yeptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.14.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -164,6 +164,7 @@ files:
|
|
|
164
164
|
- vendor/libyeptris/src/yeptris/scan/scan.h
|
|
165
165
|
- vendor/libyeptris/src/yeptris/schema.c
|
|
166
166
|
- vendor/libyeptris/src/yeptris/tape.c
|
|
167
|
+
- vendor/libyeptris/src/yeptris/tape_in.h
|
|
167
168
|
- vendor/libyeptris/src/yeptris/version.c
|
|
168
169
|
- vendor/libyeptris/src/yeptris/visit/dom_visit.c
|
|
169
170
|
- vendor/libyeptris/src/yeptris/visit/json_visit.c
|