teptris 0.3.22.0 → 0.3.23.0

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.
@@ -19,9 +19,10 @@ enum {
19
19
  ST_NUMBER = 1 << 2, /* float or integer */
20
20
  ST_BOOLEAN = 1 << 3,
21
21
  ST_OBJECT = 1 << 4,
22
- ST_ARRAY = 1 << 5
22
+ ST_ARRAY = 1 << 5,
23
+ ST_NULL = 1 << 6 /* the JSON null type (policy 1: no TOML value) */
23
24
  };
24
- #define ST_ANY 0x3Fu
25
+ #define ST_ANY 0x7Fu
25
26
 
26
27
  enum {
27
28
  FMT_NONE = 0,
@@ -109,11 +110,25 @@ struct sch_node {
109
110
  sch_node *uneval_props; /* unevaluatedProperties: frame-local */
110
111
  sch_node *uneval_items; /* unevaluatedItems: frame-local */
111
112
  sch_node *content_schema; /* contentSchema: asserts decoded content */
113
+ /* the dynamic anchors inside this node's own subtree (self
114
+ * included, document order): pushed as one scope frame whenever
115
+ * this node is applied — the dynamic scope grows per entered
116
+ * subschema, not per traversed anchor */
117
+ struct sch_scope_entry {
118
+ const char *name;
119
+ struct sch_node *node;
120
+ const jv *res; /* the anchor's own resource (nearest $id) */
121
+ } *self_scope;
122
+ size_t n_self_scope;
123
+ const jv *res_id; /* the resource root this node sits in */
124
+ bool dynamic_recursive; /* the ref site was a $recursiveRef */
112
125
  const char *dynamic_anchor; /* $dynamicAnchor name carried here */
113
- const char *recursive_anchor; /* "$" when $recursiveAnchor is true */
126
+ const char *recursive_anchor; /* "" when $recursiveAnchor is true */
114
127
  const char *dynamic_ref; /* "#name" fragment for
115
128
  * $dynamicRef / $recursiveRef */
116
129
  sch_node *dynamic_ref_target; /* compile-resolved static fallback */
130
+ bool ref_only; /* draft-07-and-older: this $ref ignores siblings */
131
+ bool enum_empty; /* "enum": [] matches nothing */
117
132
  sch_node *contains;
118
133
  long min_contains, max_contains;
119
134
  bool has_contains, has_min_contains, has_max_contains;
@@ -158,6 +173,8 @@ struct teptris_schema {
158
173
  char err[256];
159
174
  };
160
175
 
176
+ typedef struct teptris_resource teptris_resource;
177
+
161
178
  typedef struct {
162
179
  teptris_arena *a;
163
180
  jv *root;
@@ -168,26 +185,76 @@ typedef struct {
168
185
  bool in_progress;
169
186
  } *memo;
170
187
  size_t n_memo, cap_memo;
188
+ /* pointer-keyed hash over the memo (compile is O(n), not O(n^2)):
189
+ * open addressing, slot = entry index + 1, 0 = empty */
190
+ uint32_t *memo_hash;
191
+ size_t memo_hash_cap; /* power of two, 0 = absent */
192
+ /* the same treatment for the per-node base table */
193
+ uint32_t *node_base_hash;
194
+ size_t node_base_hash_cap;
171
195
  /* dependency-list capacities (grow-in-place across keywords) */
172
196
  size_t cap_dep_req, cap_dep_schemas;
173
- /* anchors: "$anchor": name -> subtree */
197
+ /* anchors: "$anchor": name -> subtree (doc-wide; the $anchor
198
+ * space of every loaded resource joins it) */
174
199
  struct {
175
200
  const char *name;
176
201
  jv *target;
177
202
  } *anchors;
178
203
  size_t n_anchors, cap_anchors;
179
- /* $dynamicAnchor / $recursiveAnchor:true targets (the static
180
- * fallback for $dynamicRef / $recursiveRef) */
204
+ /* $dynamicAnchor targets (the static fallback for $dynamicRef) */
181
205
  struct {
182
206
  const char *name;
183
207
  jv *target;
184
208
  } *dyn_anchors;
185
209
  size_t n_dyn_anchors, cap_dyn_anchors;
186
210
  jv *rec_anchor_target;
211
+ /* the resource model: every base URI this compile has materialized */
212
+ teptris_resource *resources;
213
+ size_t n_resources, cap_resources;
214
+ /* the resource the walk is currently inside */
215
+ jv *cur_root; /* its root jv ("#" resolves here) */
216
+ const char *cur_abs; /* its base URI (NULL in local-only mode) */
217
+ const char *cur_dir; /* its fs dir (relative refs join against it) */
218
+ bool ref_exclusive; /* draft-07-and-older: $ref ignores siblings */
219
+ char base_dir[1024]; /* fs dir of the main schema ("" = none) */
220
+ /* the base every node was pre-passed with: compile looks each
221
+ * node's base up here (pointers reach subtrees whose $id chain
222
+ * the document walk would otherwise miss) */
223
+ /* scratch accumulator for attach_self_scope */
224
+ struct sch_scope_entry *entry_acc;
225
+ size_t n_entry_acc, cap_entry_acc;
226
+ /* which standard vocabularies the dialect keeps (8.1.2: a custom
227
+ * metaschema's $vocabulary decides; absent = all of them) */
228
+ unsigned vocab_mask;
229
+ /* standard vocabulary gates (the core vocabulary is mandatory and
230
+ * never gated) */
231
+ #define TEPTRIS_VOC_APPLICATOR 1u
232
+ #define TEPTRIS_VOC_UNEVALUATED 2u
233
+ #define TEPTRIS_VOC_VALIDATION 4u
234
+ #define TEPTRIS_VOC_FORMAT 8u
235
+ #define TEPTRIS_VOC_CONTENT 16u
236
+ #define TEPTRIS_VOC_ALL 31u
237
+
238
+ struct node_base {
239
+ const jv *node;
240
+ const char *abs;
241
+ const char *dir;
242
+ jv *root;
243
+ bool recursive;
244
+ } *node_bases;
245
+ size_t n_node_bases, cap_node_bases;
187
246
  char *err;
188
247
  size_t err_cap;
189
248
  } sctx;
190
249
 
250
+ /* a materialized resource: a base URI naming a jv subtree */
251
+ struct teptris_resource {
252
+ const char *abs; /* base URI (file://... or empty for local-only) */
253
+ jv *root; /* the resource's root jv ("#" resolves here) */
254
+ sch_node *node; /* compiled root (memo-resolved) */
255
+ bool recursive_anchor; /* $recursiveAnchor: true on the root */
256
+ };
257
+
191
258
  /* the tagged value the validator walks: converted from a document's
192
259
  * DOM once per validate call, or synthesized (schema-side enum/const
193
260
  * values, propertyNames strings) */
@@ -231,6 +298,9 @@ sch_node *sch_resolve_pointer(sctx *c, const char *pointer);
231
298
  * with |mant| < 10^18; false for non-finite d */
232
299
  bool sch_decimal_of_double(double d, int64_t *mant, int *exp);
233
300
 
301
+ /* refs.c: the resource loader used by $ref resolution */
302
+ jv *sch_load_resource(sctx *c, const char *abs_uri);
303
+
234
304
  /* formats.c */
235
305
  /* the string-side format battery (the taplo parity set); datetimes
236
306
  * reach it through their canonical text like any other string */
@@ -43,11 +43,17 @@ typedef struct {
43
43
  size_t n_idx, cap_idx;
44
44
  } *frames;
45
45
  int n_frames, cap_frames;
46
- /* dynamic-scope stack for $dynamicRef / $recursiveRef: the
47
- * innermost anchor on the evaluation path wins */
46
+ /* dynamic-scope stack for $dynamicRef / $recursiveRef: one frame
47
+ * per applied schema object, in evaluation order. Each entry
48
+ * names its anchor and the frame owner (the applied object), so
49
+ * resolution walks RESOURCES outermost-first (2020-12 8.2.3.2:
50
+ * the outermost resource in the dynamic scope that defines the
51
+ * fragment wins, its first anchor in document order) */
48
52
  struct dyn_scope {
49
53
  const char *name;
50
54
  sch_node *node;
55
+ const jv *res; /* the anchor's resource (nearest $id) */
56
+ sch_node *owner; /* the applied object that entered it */
51
57
  } *scopes;
52
58
  int n_scopes, cap_scopes;
53
59
  } vctx;
@@ -313,8 +319,9 @@ static void frames_free(vctx *vc) {
313
319
 
314
320
  /* ------------------------------------------------- dynamic scopes -- */
315
321
 
316
- static bool scope_push(vctx *vc, const char *name, sch_node *node) {
317
- if (name == NULL || node == NULL) return true;
322
+ static bool scope_push(vctx *vc, const char *name, sch_node *node,
323
+ const jv *res, sch_node *owner) {
324
+ if (name == NULL || node == NULL || owner == NULL) return true;
318
325
  if (vc->n_scopes == vc->cap_scopes) {
319
326
  vc->cap_scopes = vc->cap_scopes == 0 ? 16 : vc->cap_scopes * 2;
320
327
  void *ns = realloc(vc->scopes, vc->cap_scopes * sizeof(*vc->scopes));
@@ -323,14 +330,47 @@ static bool scope_push(vctx *vc, const char *name, sch_node *node) {
323
330
  }
324
331
  vc->scopes[vc->n_scopes].name = name;
325
332
  vc->scopes[vc->n_scopes].node = node;
333
+ vc->scopes[vc->n_scopes].res = res;
334
+ vc->scopes[vc->n_scopes].owner = owner;
326
335
  vc->n_scopes++;
327
336
  return true;
328
337
  }
329
338
 
330
- /* the innermost dynamic anchor for a name on the evaluation path */
339
+ /* 2020-12 $dynamicRef: walk the RESOURCES of the evaluation path
340
+ * outermost-first (the resource of each applied object, in frame
341
+ * order); the first resource that DEFINES an identically-named
342
+ * $dynamicAnchor — the anchor's nearest-$id resource anywhere in the
343
+ * stack — wins, and its first matching anchor in document order is
344
+ * the target (2020-12 8.2.3.2 + appendix C) */
331
345
  static sch_node *scope_resolve(vctx *vc, const char *name) {
332
- for (int i = vc->n_scopes - 1; i >= 0; i--) {
333
- if (strcmp(vc->scopes[i].name, name) == 0) return vc->scopes[i].node;
346
+ for (int i = 0; i < vc->n_scopes; i++) {
347
+ const jv *res = vc->scopes[i].owner->res_id;
348
+ bool seen = false;
349
+ for (int k = 0; k < i; k++) {
350
+ if (vc->scopes[k].owner->res_id == res) {
351
+ seen = true;
352
+ break;
353
+ }
354
+ }
355
+ if (seen) continue;
356
+ for (int j = 0; j < vc->n_scopes; j++) {
357
+ if (vc->scopes[j].res != res) continue;
358
+ if (strcmp(vc->scopes[j].name, name) == 0) {
359
+ return vc->scopes[j].node;
360
+ }
361
+ }
362
+ }
363
+ return NULL;
364
+ }
365
+
366
+ /* 2019-09 $recursiveRef: the first resource on the evaluation path
367
+ * (outermost first) whose ROOT carries $recursiveAnchor: true; the
368
+ * target is that resource's root */
369
+ static sch_node *scope_resolve_recursive(vctx *vc) {
370
+ for (int i = 0; i < vc->n_scopes; i++) {
371
+ if (vc->scopes[i].owner->recursive_anchor != NULL) {
372
+ return vc->scopes[i].owner;
373
+ }
334
374
  }
335
375
  return NULL;
336
376
  }
@@ -346,6 +386,15 @@ static bool sv_equal(const sch_svalue *a, const sch_svalue *b) {
346
386
  * equals a datetime text */
347
387
  int ad = a->kind >= SV_DATETIME;
348
388
  int bd = b->kind >= SV_DATETIME;
389
+ /* numeric identity per JSON Schema: an integer and a float
390
+ * with the same value are equal (bools stay distinct - their
391
+ * kinds differ, so they never reach this branch) */
392
+ if ((a->kind == SV_INT || a->kind == SV_FLOAT) &&
393
+ (b->kind == SV_INT || b->kind == SV_FLOAT)) {
394
+ long double av = a->kind == SV_INT ? (long double)a->i : (long double)a->f;
395
+ long double bv = b->kind == SV_INT ? (long double)b->i : (long double)b->f;
396
+ return av == bv;
397
+ }
349
398
  if ((ad || bd) && (a->kind == SV_STRING || b->kind == SV_STRING)) {
350
399
  const sch_svalue *dt = ad ? a : b;
351
400
  const sch_svalue *st = ad ? b : a;
@@ -676,15 +725,31 @@ static int sv_to_st(const sch_svalue *v) {
676
725
  switch (v->kind) {
677
726
  case SV_STRING: return ST_STRING;
678
727
  case SV_INT: return ST_INTEGER | ST_NUMBER;
679
- case SV_FLOAT: return ST_NUMBER;
728
+ case SV_FLOAT:
729
+ /* a float with a zero fractional part is an integer (the
730
+ * 2020-12 rule the suite tests) */
731
+ return (v->f == v->f && v->f != INFINITY && v->f != -INFINITY &&
732
+ v->f == (double)(int64_t)v->f)
733
+ ? ST_INTEGER | ST_NUMBER
734
+ : ST_NUMBER;
680
735
  case SV_BOOL: return ST_BOOLEAN;
681
736
  case SV_TABLE: return ST_OBJECT;
682
737
  case SV_ARRAY: return ST_ARRAY;
683
738
  case SV_DATETIME: case SV_DATE: case SV_TIME: return ST_STRING;
739
+ case SV_NULL: return ST_NULL;
684
740
  default: return 0;
685
741
  }
686
742
  }
687
743
 
744
+ /* one application = one scope frame: every anchor inside the applied
745
+ * schema's own subtree is in scope below it, owned by this object */
746
+ static void scope_frame_push(vctx *vc, sch_node *n) {
747
+ for (size_t i = 0; i < n->n_self_scope; i++) {
748
+ scope_push(vc, n->self_scope[i].name, n->self_scope[i].node,
749
+ n->self_scope[i].res, n);
750
+ }
751
+ }
752
+
688
753
  static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
689
754
  if (n == NULL || v == NULL) return;
690
755
  if (vc->depth >= MAX_DEPTH) {
@@ -697,8 +762,7 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
697
762
  * scope for the instance path below it */
698
763
  frame_push(vc, v);
699
764
  int scopes_base = vc->n_scopes;
700
- scope_push(vc, n->dynamic_anchor, n);
701
- scope_push(vc, n->recursive_anchor, n);
765
+ scope_frame_push(vc, n);
702
766
 
703
767
  /* a dynamic $ref resolves through the innermost scope entered on
704
768
  * the evaluation path; the compile-resolved target is the static
@@ -706,7 +770,8 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
706
770
  if (n->dynamic_ref != NULL) {
707
771
  const char *name = n->dynamic_ref[0] == '#' ? n->dynamic_ref + 1
708
772
  : n->dynamic_ref;
709
- sch_node *target = scope_resolve(vc, name);
773
+ sch_node *target = n->dynamic_recursive ? scope_resolve_recursive(vc)
774
+ : scope_resolve(vc, name);
710
775
  if (target == NULL) target = n->dynamic_ref_target;
711
776
  if (target != NULL) {
712
777
  if (vc->ref_depth >= MAX_DEPTH) {
@@ -715,6 +780,7 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
715
780
  vc->ref_depth++;
716
781
  spush(vc, "$dynamicRef");
717
782
  val(vc, target, v);
783
+ vc->n_scopes = scopes_base;
718
784
  spop(vc);
719
785
  vc->ref_depth--;
720
786
  }
@@ -731,9 +797,16 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
731
797
  vc->ref_depth++;
732
798
  spush(vc, "$ref");
733
799
  val(vc, n->ref, v);
800
+ vc->n_scopes = scopes_base;
734
801
  spop(vc);
735
802
  vc->ref_depth--;
736
803
  }
804
+ /* draft 07 and older: the $ref replaces every sibling */
805
+ if (n->ref_only) {
806
+ vc->n_scopes = scopes_base;
807
+ frame_pop(vc);
808
+ return;
809
+ }
737
810
  }
738
811
 
739
812
  if (n->is_false) {
@@ -767,6 +840,9 @@ static void val(vctx *vc, sch_node *n, const sch_svalue *v) {
767
840
  verr(vc, "value does not equal the constant");
768
841
  }
769
842
  }
843
+ if (n->enum_empty) {
844
+ verr(vc, "value does not match the empty enum (nothing matches)");
845
+ }
770
846
  if (n->n_enum > 0) {
771
847
  bool hit = false;
772
848
  for (size_t i = 0; i < n->n_enum && !hit; i++) {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Teptris
4
- VERSION = "0.3.22.0"
4
+ VERSION = "0.3.23.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.22.0
4
+ version: 0.3.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - teptris contributors
@@ -56,6 +56,10 @@ files:
56
56
  - ext/teptris_ext/engine/src/teptris/schema/formats.c
57
57
  - ext/teptris_ext/engine/src/teptris/schema/json_read.c
58
58
  - ext/teptris_ext/engine/src/teptris/schema/json_read.h
59
+ - ext/teptris_ext/engine/src/teptris/schema/metaschemas.c
60
+ - ext/teptris_ext/engine/src/teptris/schema/metaschemas.h
61
+ - ext/teptris_ext/engine/src/teptris/schema/refs.c
62
+ - ext/teptris_ext/engine/src/teptris/schema/refs.h
59
63
  - ext/teptris_ext/engine/src/teptris/schema/regex.c
60
64
  - ext/teptris_ext/engine/src/teptris/schema/regex.h
61
65
  - ext/teptris_ext/engine/src/teptris/schema/schema.c