yeptris 0.6.12.3 → 0.6.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5900164f14038b2ef4e6c7b3e7764c335faf037559f9743099787e2ffcadb2f7
4
- data.tar.gz: b539d2090254785c57c6dfd5a08e46200b668b0061619691e779a5c186a3978a
3
+ metadata.gz: c5dd96eb5583028d0da7d38ee4addaf108b8a2495696aacc6f68c43117773da3
4
+ data.tar.gz: b489c5bfc4b41d9cb3d8b836ac4b88f62aaeab5856788412301cde79ee0dfd2f
5
5
  SHA512:
6
- metadata.gz: d837519cc77193279158957e4757a0b2ee6f250467d293422676135b6aee89058eac9dbb48c6b7a5f3eb79ca77cc1e076399f93935ed75d652612244b845db74
7
- data.tar.gz: 5eff7ba15bd3c4314508d226ebaa4d2e3aa0eb0a7cc2887645078acbbecd193cbaa443af6877891b226224719b8955700f8567ec8a0446cc42fa713349993864
6
+ metadata.gz: 4d91f1cdb572d444d4f7a43aec756400375581b3fa7bb1f6bbf62e9ad134ffa392fb1a763f11daffb07ae865e4046b6379a15b7f57eec5fb72c842bb580aee52
7
+ data.tar.gz: 7ac70b81ca8cf423226b48f8e6422686dc6edd9593a94bc82ebc7d0470ee40a9de9f28b4bd0f4ddf1c85ee1e132156fa1d889db8a1af9a788a6f5384f60c08c6
@@ -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.12.3".freeze
6
+ VERSION = "0.6.13.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
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.12
6
+ VERSION 0.6.13
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -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,118 @@ 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
+ /* the columns are the one representation valid on every route
871
+ * (strict: native; lenient fused: lazily materialized from the
872
+ * records; scalar-root lenient: native column writes) */
873
+ if (yeptris_tape_columns((yeptris_json_tape*)t) != 0) {}
874
+ if (t->kinds == NULL) {}
875
+ d->input_base = (const char*)t->_src;
876
+ d->input_len = t->_srclen;
877
+ const char* src = (const char*)t->_src;
878
+ d->depth = 0;
879
+ for (uint32_t i = 0; i < t->count; i++) {
880
+ const uint8_t kind = t->kinds[i];
881
+ const uint32_t len = t->lens[i];
882
+ const uint32_t off = t->offs[i];
883
+ switch (kind) {
884
+ case YEP_T_DOC:
885
+ break; /* the stream boundary record */
886
+ case YEP_T_SEQ_OPEN:
887
+ case YEP_T_MAP_OPEN: {
888
+ if (d->depth >= YEP_DOM_MAX_DEPTH) {}
889
+ uint32_t id =
890
+ dom_open_node(d, kind == YEP_T_SEQ_OPEN ? YEP_DOM_SEQUENCE : YEP_DOM_MAPPING, NULL,
891
+ NULL, 0, 0, 0, 1);
892
+ if (id == UINT32_MAX || dom_place(d, id) != 0) {}
893
+ d->map_pending_key[d->depth] = 0;
894
+ d->stack[d->depth++] = id;
895
+ break;
896
+ }
897
+ case YEP_T_CLOSE:
898
+ if (d->depth == 0) {}
899
+ d->depth--;
900
+ break;
901
+ case YEP_T_NULL:
902
+ case YEP_T_TRUE:
903
+ case YEP_T_FALSE:
904
+ case YEP_T_INT:
905
+ case YEP_T_FLOAT:
906
+ case YEP_T_NUM:
907
+ case YEP_T_STR: {
908
+ yep_tag_id tag = YEPTRIS_TAG_STR;
909
+ uint8_t style = YEP_STYLE_PLAIN;
910
+ int implicit = 0;
911
+ switch (kind) {
912
+ case YEP_T_NULL:
913
+ tag = YEPTRIS_TAG_NULL;
914
+ implicit = 1;
915
+ break;
916
+ case YEP_T_TRUE:
917
+ case YEP_T_FALSE:
918
+ tag = YEPTRIS_TAG_BOOL;
919
+ implicit = 1;
920
+ break;
921
+ case YEP_T_INT:
922
+ case YEP_T_FLOAT:
923
+ case YEP_T_NUM:
924
+ tag = YEPTRIS_TAG_INT;
925
+ implicit = 1;
926
+ if (kind == YEP_T_NUM) {
927
+ /* the lenient route's deferred contract: the span
928
+ * carried NO number grammar at parse — classify
929
+ * now (the tape convert's own authority) */
930
+ size_t adv = 0;
931
+ int flt = 0;
932
+ if (yep_json_number_shape(src + off, len, &adv, &flt) == 0 || adv != len) {
933
+ return -1; /* malformed: the strict route rejected at parse */
934
+ }
935
+ tag = flt ? YEPTRIS_TAG_FLOAT : YEPTRIS_TAG_INT;
936
+ } else if (kind == YEP_T_FLOAT) {
937
+ tag = YEPTRIS_TAG_FLOAT;
938
+ }
939
+ break;
940
+ default:
941
+ style = YEP_STYLE_DOUBLE_QUOTED;
942
+ break;
943
+ }
944
+ uint32_t id =
945
+ dom_open_node(d, YEP_DOM_SCALAR, NULL, NULL, 0, style, (uint8_t)implicit, 0);
946
+ if (id == UINT32_MAX) {
947
+ return -1;
948
+ }
949
+ if (kind == YEP_T_STR) {
950
+ /* the record's span is the INNER bytes (quotes off) */
951
+ if (memchr(src + off, '\\', len) != NULL) {
952
+ /* escaped: unescape into the arena (the fused
953
+ * builder's arm, byte for byte) */
954
+ char* dst = yep_dom_str_tail(d, len);
955
+ if (dst == NULL) {}
956
+ d->nodes[id].value = yep_dom_str_commit(
957
+ d, yep_finish_double_into(src, off, off + len, dst, len));
958
+ } else {
959
+ yep_view v = {src + off, len};
960
+ d->nodes[id].value = dom_str_in(d, &v, 1);
961
+ }
962
+ } else {
963
+ yep_view v = {src + off, len};
964
+ d->nodes[id].value = dom_str_in(d, &v, 1);
965
+ }
966
+ d->nodes[id].tag_id = tag;
967
+ if (dom_place(d, id) != 0) {
968
+ return -1;
969
+ }
970
+ break;
971
+ }
972
+ default:
973
+ return -1; /* a CONT run or an unknown kind: neither reaches a
974
+ strict-legal tape (CONT only past 16 MiB tokens) */
975
+ }
976
+ }
977
+ return d->depth == 0 ? 0 : -1;
978
+ }
979
+
867
980
  void dom_on_flow_rollback(void* ctx) {
868
981
  yep_dom* d = (yep_dom*)ctx;
869
982
  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);
@@ -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; /* node index */
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
- yep_schema_ctx c = {desc, desc_len, cols, dom, 0, 0, -1};
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;
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.12.3
4
+ version: 0.6.13.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-20 00:00:00.000000000 Z
11
+ date: 2026-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi