schemurai 3.0.0 → 3.0.2

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -4
  3. data/ext/schemurai_native/Cargo.lock +55 -1
  4. data/ext/schemurai_native/Cargo.toml +4 -1
  5. data/ext/schemurai_native/extconf.rb +1 -1
  6. data/ext/schemurai_native/glue.c +1679 -62
  7. data/ext/schemurai_native/protocol.def +45 -0
  8. data/ext/schemurai_native/src/budget.rs +49 -0
  9. data/ext/schemurai_native/src/container.rs +460 -0
  10. data/ext/schemurai_native/src/content.rs +74 -27
  11. data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
  12. data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
  13. data/ext/schemurai_native/src/diagnostics.rs +1087 -307
  14. data/ext/schemurai_native/src/enumeration.rs +6 -1
  15. data/ext/schemurai_native/src/evaluation.rs +49 -20
  16. data/ext/schemurai_native/src/format.rs +126 -14
  17. data/ext/schemurai_native/src/graph.rs +37 -0
  18. data/ext/schemurai_native/src/instance_graph.rs +90 -0
  19. data/ext/schemurai_native/src/integer_scan.rs +132 -0
  20. data/ext/schemurai_native/src/lib.rs +917 -238
  21. data/ext/schemurai_native/src/memo.rs +11 -38
  22. data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
  23. data/ext/schemurai_native/src/number.rs +250 -59
  24. data/ext/schemurai_native/src/pattern.rs +147 -109
  25. data/ext/schemurai_native/src/pointer.rs +97 -0
  26. data/ext/schemurai_native/src/runtime/properties.rs +162 -0
  27. data/ext/schemurai_native/src/runtime/reports.rs +266 -0
  28. data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
  29. data/ext/schemurai_native/src/runtime.rs +1020 -0
  30. data/ext/schemurai_native/src/scope.rs +60 -16
  31. data/ext/schemurai_native/src/string.rs +68 -0
  32. data/ext/schemurai_native/src/tracking.rs +244 -174
  33. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  34. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  35. data/lib/schemurai/container_checks.rb +534 -0
  36. data/lib/schemurai/decimal.rb +11 -0
  37. data/lib/schemurai/dialect.rb +0 -12
  38. data/lib/schemurai/ecma_regexp.rb +46 -0
  39. data/lib/schemurai/evaluation_pool.rb +66 -0
  40. data/lib/schemurai/evaluator.rb +9 -48
  41. data/lib/schemurai/json_pointer.rb +11 -0
  42. data/lib/schemurai/native/diagnostics.rb +4 -3
  43. data/lib/schemurai/native/evaluator.rb +38 -10
  44. data/lib/schemurai/native/null_numeric.rb +23 -0
  45. data/lib/schemurai/number_checks.rb +128 -0
  46. data/lib/schemurai/string_checks.rb +166 -0
  47. data/lib/schemurai/type_checks.rb +59 -0
  48. data/lib/schemurai/value_equality.rb +72 -0
  49. data/lib/schemurai/version.rb +1 -1
  50. data/lib/schemurai/vm/compiler.rb +18 -26
  51. data/lib/schemurai/vm/evaluator.rb +87 -545
  52. data/lib/schemurai/vm/limited_evaluator.rb +7 -22
  53. metadata +25 -2
@@ -9,6 +9,15 @@
9
9
 
10
10
  static ID id_resolve, id_schemurai, id_resolution_error;
11
11
  static ID id_root, id_compile, id_graph, id_dynamic_anchor, id_dynamic_anchor_names, id_dynamic_anchor_count;
12
+ static ID identity_methods[5];
13
+ /* Classification only requests metadata while Rust is active. Ruby method
14
+ * lookup, which can create callable entries, runs after that evaluation exits. */
15
+ static _Thread_local const void *active_caches;
16
+ extern int sn_identity_kind(const void *, VALUE);
17
+ /* Runtime cursors fit the positive Fixnum range, checked by Rust. */
18
+ VALUE sn_runtime_ordinal(size_t ordinal) {
19
+ return ((VALUE)ordinal << 1) | RUBY_FIXNUM_FLAG;
20
+ }
12
21
 
13
22
  /* The C layer adapts CRuby APIs to Rust's opaque VALUEs. */
14
23
  #ifdef HAVE_RB_HASH_STLIKE_FOREACH
@@ -19,14 +28,55 @@ extern int rb_hash_stlike_foreach(VALUE, st_foreach_callback_func *, st_data_t);
19
28
  typedef struct { void *engine; const void *roots; VALUE program; int compiling; } holder;
20
29
  extern void *sn_new(void);
21
30
  extern int sn_compile(void *, VALUE, VALUE, int, int, int, VALUE *);
22
- extern int sn_valid(const void *, const void *, size_t, VALUE);
31
+ extern int sn_valid(const void *, const void *, size_t, VALUE, size_t);
23
32
  extern size_t sn_node_index(const void *, VALUE);
33
+ extern int sn_callback_kind(const void *, size_t, int *);
34
+ extern int sn_reference_context(const void *);
35
+ extern void sn_scopes_inherit(const void *, VALUE, VALUE, VALUE, int);
36
+ extern size_t sn_references_len(const void *, int);
37
+ extern VALUE sn_references_at(const void *, size_t, int);
38
+ extern void sn_reference_pair(void *, VALUE, VALUE);
39
+ extern int sn_scopes_enabled(const void *);
40
+ extern size_t sn_scopes_len(const void *);
41
+ extern VALUE sn_scopes_at(const void *, size_t);
42
+ extern void sn_scopes_accept(const void *, VALUE);
43
+ extern size_t sn_scopes_unwind(const void *);
44
+
24
45
  extern size_t sn_root_index(const void *);
46
+ extern int sn_has_unresolved_references(const void *);
25
47
  extern size_t sn_root_count(const void *);
26
48
  extern VALUE sn_root_at(const void *, size_t);
27
49
  extern void *sn_caches_new(void);
28
50
  extern void sn_caches_free(void *);
29
51
  extern size_t sn_caches_size(const void *);
52
+ extern void sn_caches_runtime(const void *, const void *);
53
+ extern int sn_caches_pending(const void *, VALUE *, VALUE *, int *);
54
+ extern void sn_caches_mark(const void *);
55
+ extern void sn_caches_begin(const void *);
56
+ extern void sn_caches_observe(const void *);
57
+ extern int sn_caches_take_reorder(const void *);
58
+ extern int sn_caches_rescue_regexp(const void *);
59
+ extern int sn_caches_frame_error(const void *);
60
+ extern size_t sn_pointer_parts(const void *);
61
+ extern void sn_pointer_part(const void *, size_t, const unsigned char **, size_t *, VALUE *);
62
+ extern void sn_pointer_result(const void *, VALUE);
63
+ typedef struct {
64
+ const unsigned char *keyword;
65
+ size_t keyword_len;
66
+ int opcode;
67
+ VALUE operand, instance;
68
+ size_t detail;
69
+ VALUE frame;
70
+ } message_arguments;
71
+ extern void sn_message_arguments(const void *, message_arguments *);
72
+ extern void sn_message_result(const void *, VALUE);
73
+ typedef struct { VALUE rules, instance; size_t child; int required, scalar_kind, scalar_flags; } contains_arguments;
74
+ extern void sn_contains_arguments(const void *, contains_arguments *);
75
+ extern void sn_caches_child(const void *, const void *, int, int);
76
+ extern int sn_caches_is_scalar(const void *);
77
+ extern int sn_caches_deadline_expired(const void *);
78
+ typedef struct { VALUE ordinal, rules, instance; } scalar_arguments;
79
+ extern void sn_scalar_arguments(const void *, scalar_arguments *);
30
80
  extern void sn_free(void *);
31
81
  extern const void *sn_roots(const void *);
32
82
  extern void sn_roots_mark(const void *);
@@ -75,42 +125,165 @@ static VALUE initialize(VALUE self, VALUE program, VALUE compiler, VALUE format,
75
125
  int status = sn_compile(h->engine, program, compiler, RTEST(format), RTEST(content),
76
126
  RB_TYPE_P(program, T_ARRAY), &error);
77
127
  h->compiling = 0;
78
- if (status < 0) {
128
+ if (status != SN_VERDICT_VALID) {
79
129
  sn_free(h->engine);
80
130
  h->engine = NULL;
81
131
  h->roots = NULL;
82
132
  if (status == SN_STATUS_EXCEPTION) rb_exc_raise(error);
133
+ if (status == SN_VERDICT_UNSUPPORTED) rb_raise(rb_eRuntimeError, "native compiler encountered an unsupported instruction or operand");
83
134
  rb_raise(rb_eRuntimeError, "native compiler panicked");
84
135
  }
85
136
  return self;
86
137
  }
87
- static VALUE valid(VALUE self, VALUE instance) {
88
- holder *h;
89
- TypedData_Get_Struct(self, holder, &engine_type, h);
90
- if (!h->engine) rb_raise(rb_eRuntimeError, "uninitialized native program");
91
- if (h->compiling) rb_raise(rb_eRuntimeError, "native program is still compiling");
92
- int status = sn_valid(h->engine, NULL, sn_root_index(h->engine), instance);
93
- RB_GC_GUARD(instance);
94
- if (status < 0) rb_raise(rb_eRuntimeError, "native evaluator panicked");
95
- return status == SN_VERDICT_UNSUPPORTED ? Qnil : (status ? Qtrue : Qfalse);
138
+ static void check_evaluation_status(int status, const char *panic_message) {
139
+ const char *decimal = NULL;
140
+ switch (status) {
141
+ case SN_STATUS_DECIMAL_NAN: decimal = "NaN"; break;
142
+ case SN_STATUS_DECIMAL_POSITIVE_INFINITY: decimal = "Infinity"; break;
143
+ case SN_STATUS_DECIMAL_NEGATIVE_INFINITY: decimal = "-Infinity"; break;
144
+ }
145
+ if (decimal) rb_raise(rb_eArgError, "invalid value for convert(): \"%s\"", decimal);
146
+ if (status == SN_STATUS_STACK_ERROR) rb_raise(rb_eSysStackError, "stack level too deep");
147
+ if (status < 0) rb_raise(rb_eRuntimeError, "%s", panic_message);
96
148
  }
149
+ static VALUE evaluate_runner(VALUE, VALUE, VALUE, int);
150
+ static VALUE build_runner(VALUE, size_t);
97
151
  /* Search caches and root selection belong to the owning validator. The Ruby
98
152
  * owner keeps the shared engine and all its GC roots alive. */
99
- typedef struct { VALUE owner; void *caches; size_t root; } runner_holder;
100
- static void runner_mark(void *ptr) { rb_gc_mark(((runner_holder *)ptr)->owner); }
153
+ typedef struct { size_t max_errors; uint32_t enabled; double timeout; } native_limits;
154
+ typedef struct {
155
+ VALUE owner, parent, active_budget, callback_scope, child_callbacks;
156
+ VALUE scope_state, active_scope_state;
157
+ VALUE report_seed, report_results, error_frame, report_sink;
158
+ VALUE report_state, active_report_state;
159
+ VALUE ref_state, vm_active, active_ref_state, active_vm_state;
160
+ int continuation, scalar_mode, report_sink_done;
161
+ VALUE regexps, limited_regexps, pointers, messages, frames;
162
+ VALUE number_state, limited_number_state, string_state, limited_string_state;
163
+ void *caches;
164
+ size_t root;
165
+ int busy, callback_context, scope_owner, closed_limited;
166
+ int callback_kind, callback_flags;
167
+ native_limits active_limits;
168
+ st_table *matches, *frame_indices;
169
+ } runner_holder;
170
+ static int mark_match(st_data_t key, st_data_t value, st_data_t context) {
171
+ rb_gc_mark((VALUE)key);
172
+ return ST_CONTINUE;
173
+ }
174
+ static int mark_pattern_matches(st_data_t key, st_data_t value, st_data_t context) {
175
+ rb_gc_mark((VALUE)key);
176
+ st_foreach((st_table *)value, mark_match, 0);
177
+ return ST_CONTINUE;
178
+ }
179
+ static int release_matches(st_data_t key, st_data_t value, st_data_t context) {
180
+ st_free_table((st_table *)value);
181
+ return ST_CONTINUE;
182
+ }
183
+ static int mark_operation(st_data_t key, st_data_t value, st_data_t context) {
184
+ st_foreach((st_table *)value, mark_pattern_matches, 0);
185
+ return ST_CONTINUE;
186
+ }
187
+ static int release_operation(st_data_t key, st_data_t value, st_data_t context) {
188
+ st_foreach((st_table *)value, release_matches, 0);
189
+ st_free_table((st_table *)value);
190
+ return ST_CONTINUE;
191
+ }
192
+ static void runner_mark(void *ptr) {
193
+ runner_holder *r = ptr;
194
+ rb_gc_mark(r->owner);
195
+ rb_gc_mark(r->parent);
196
+ rb_gc_mark(r->active_budget);
197
+ rb_gc_mark(r->callback_scope);
198
+ rb_gc_mark(r->child_callbacks);
199
+ rb_gc_mark(r->report_state);
200
+ rb_gc_mark(r->active_report_state);
201
+ rb_gc_mark(r->report_sink);
202
+ rb_gc_mark(r->report_seed);
203
+ rb_gc_mark(r->error_frame);
204
+ rb_gc_mark(r->report_results);
205
+ rb_gc_mark(r->scope_state);
206
+ rb_gc_mark(r->active_scope_state);
207
+ rb_gc_mark(r->ref_state);
208
+ rb_gc_mark(r->vm_active);
209
+ rb_gc_mark(r->active_ref_state);
210
+ rb_gc_mark(r->active_vm_state);
211
+ rb_gc_mark(r->regexps);
212
+ rb_gc_mark(r->limited_regexps);
213
+ rb_gc_mark(r->pointers);
214
+ rb_gc_mark(r->messages);
215
+ rb_gc_mark(r->frames);
216
+ rb_gc_mark(r->number_state);
217
+ rb_gc_mark(r->string_state);
218
+ rb_gc_mark(r->limited_string_state);
219
+ rb_gc_mark(r->limited_number_state);
220
+ if (r->caches) sn_caches_mark(r->caches);
221
+ if (r->matches) st_foreach(r->matches, mark_operation, 0);
222
+ }
101
223
  static void runner_release(void *ptr) {
102
224
  runner_holder *h = ptr;
103
225
  if (h->caches) sn_caches_free(h->caches);
226
+ if (h->frame_indices) st_free_table(h->frame_indices);
227
+ if (h->matches) {
228
+ st_foreach(h->matches, release_operation, 0);
229
+ st_free_table(h->matches);
230
+ }
104
231
  xfree(h);
105
232
  }
233
+ static int match_size(st_data_t key, st_data_t value, st_data_t context) {
234
+ *(size_t *)context += st_memsize((st_table *)value);
235
+ return ST_CONTINUE;
236
+ }
237
+ static int operation_size(st_data_t key, st_data_t value, st_data_t context) {
238
+ *(size_t *)context += st_memsize((st_table *)value);
239
+ st_foreach((st_table *)value, match_size, context);
240
+ return ST_CONTINUE;
241
+ }
106
242
  static size_t runner_size(const void *ptr) {
107
243
  const runner_holder *h = ptr;
108
- return sizeof(*h) + (h->caches ? sn_caches_size(h->caches) : 0);
244
+ size_t bytes = sizeof(*h) + (h->caches ? sn_caches_size(h->caches) : 0);
245
+ if (h->frame_indices) bytes += st_memsize(h->frame_indices);
246
+ if (h->matches) {
247
+ bytes += st_memsize(h->matches);
248
+ st_foreach(h->matches, operation_size, (st_data_t)&bytes);
249
+ }
250
+ return bytes;
251
+ }
252
+ /* Numeric-key lookups only: Rust calls this while borrowing instance storage.
253
+ * Table mutations, Ruby calls and allocations happen after Rust returns. */
254
+ int sn_cached_runtime(const void *matches, int operation, VALUE source, VALUE instance) {
255
+ st_data_t sources, bucket, result;
256
+ if (st_lookup((st_table *)matches, (st_data_t)operation, &sources)
257
+ && st_lookup((st_table *)sources, (st_data_t)source, &bucket)
258
+ && st_lookup((st_table *)bucket, (st_data_t)instance, &result)) return (int)result;
259
+ return SN_VERDICT_UNSUPPORTED;
109
260
  }
110
261
  static const rb_data_type_t runner_type = {
111
262
  "Schemurai::Native::Runner", {runner_mark, runner_release, runner_size, NULL, {NULL}},
112
263
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
113
264
  };
265
+ /* Share evaluator caches, never runtime observations or borrowed views.
266
+ * Reentrant independent bounded validations own their limited state. */
267
+ static void copy_states(runner_holder *target, const runner_holder *source, int limited, int merge) {
268
+ #define COPY_STATE(field) if (!merge || !NIL_P(source->field)) target->field = source->field
269
+ COPY_STATE(regexps);
270
+ COPY_STATE(number_state);
271
+ COPY_STATE(string_state);
272
+ if (limited) {
273
+ COPY_STATE(limited_regexps);
274
+ COPY_STATE(limited_number_state);
275
+ COPY_STATE(limited_string_state);
276
+ }
277
+ #undef COPY_STATE
278
+ }
279
+ static void publish_states(runner_holder *runner) {
280
+ if (!NIL_P(runner->parent)) {
281
+ runner_holder *parent;
282
+ TypedData_Get_Struct(runner->parent, runner_holder, &runner_type, parent);
283
+ copy_states(parent, runner, runner->callback_context, 1);
284
+ publish_states(parent);
285
+ }
286
+ }
114
287
  static holder *ready_program(VALUE self) {
115
288
  holder *h;
116
289
  TypedData_Get_Struct(self, holder, &engine_type, h);
@@ -118,6 +291,9 @@ static holder *ready_program(VALUE self) {
118
291
  if (h->compiling) rb_raise(rb_eRuntimeError, "native program is still compiling");
119
292
  return h;
120
293
  }
294
+ static VALUE has_unresolved_references(VALUE self) {
295
+ return sn_has_unresolved_references(ready_program(self)->engine) ? Qtrue : Qfalse;
296
+ }
121
297
  static VALUE make_shareable(VALUE self) {
122
298
  holder *h = ready_program(self);
123
299
  /* TypedData's hidden references must be shareable too. Freeze temporary
@@ -131,18 +307,52 @@ static VALUE runner(VALUE self, VALUE root) {
131
307
  holder *h = ready_program(self);
132
308
  size_t index = sn_node_index(h->engine, root);
133
309
  if (index == SIZE_MAX) rb_raise(rb_eArgError, "root was not compiled in this native program");
310
+ return build_runner(self, index);
311
+ }
312
+ static VALUE build_runner(VALUE self, size_t index) {
134
313
  VALUE klass = rb_const_get(rb_obj_class(self), rb_intern_const("Runner"));
135
314
  runner_holder *r;
136
315
  VALUE object = TypedData_Make_Struct(klass, runner_holder, &runner_type, r);
137
316
  r->owner = self;
317
+ r->parent = Qnil;
318
+ r->active_budget = Qnil;
319
+ r->callback_scope = Qnil;
320
+ r->child_callbacks = Qnil;
321
+ r->report_seed = r->report_results = r->error_frame = r->report_sink = Qnil;
322
+ r->report_state = r->active_report_state = Qnil;
323
+ r->scope_state = Qnil;
324
+ r->active_scope_state = Qnil;
325
+ r->ref_state = r->vm_active = r->active_ref_state = r->active_vm_state = Qnil;
326
+ r->scope_owner = 0;
327
+ r->closed_limited = 0;
328
+ r->callback_kind = SN_CALLBACK_RUNTIME;
329
+ r->callback_flags = 0;
330
+ r->active_limits = (native_limits){0};
331
+ r->busy = 0;
332
+ r->callback_context = 0;
333
+ r->continuation = r->scalar_mode = r->report_sink_done = 0;
334
+ r->regexps = Qnil;
335
+ r->limited_regexps = Qnil;
336
+ r->pointers = Qnil;
337
+ r->messages = Qnil;
338
+ r->frames = Qnil;
339
+ r->number_state = Qnil;
340
+ r->string_state = Qnil;
341
+ r->limited_string_state = Qnil;
342
+ r->limited_number_state = Qnil;
138
343
  r->root = index;
344
+ r->caches = NULL;
345
+ r->matches = NULL;
346
+ r->frame_indices = NULL;
347
+ r->frame_indices = st_init_numtable();
348
+ r->matches = st_init_numtable();
139
349
  r->caches = sn_caches_new();
350
+ sn_caches_runtime(r->caches, r->matches);
140
351
  RB_GC_GUARD(self);
141
352
  return object;
142
353
  }
143
- typedef struct { size_t max_errors; uint32_t enabled; double timeout; } native_limits;
144
354
  typedef struct { uint32_t kind; size_t attempted; } native_outcome;
145
- extern int sn_valid_with_limits(const void *, const void *, size_t, VALUE, const native_limits *, native_outcome *);
355
+ extern int sn_valid_with_limits(const void *, const void *, size_t, VALUE, size_t, const native_limits *, native_outcome *);
146
356
  static native_limits read_limits(VALUE budget) {
147
357
  native_limits limits = {0};
148
358
  VALUE cap = rb_funcall(budget, rb_intern("native_max_errors"), 0);
@@ -156,27 +366,57 @@ static void finish_budget(VALUE budget, const native_outcome *outcome) {
156
366
  if (outcome->kind) rb_funcall(budget, rb_intern("raise_native_limit"), 2, UINT2NUM(outcome->kind), SIZET2NUM(outcome->attempted));
157
367
  }
158
368
  static VALUE runner_valid_with_budget(VALUE self, VALUE instance, VALUE budget) {
159
- runner_holder *r;
160
- TypedData_Get_Struct(self, runner_holder, &runner_type, r);
161
- holder *h = ready_program(r->owner);
162
- native_limits limits = read_limits(budget);
163
- native_outcome outcome = {0};
164
- int status = sn_valid_with_limits(h->engine, r->caches, r->root, instance, &limits, &outcome);
165
- if (status < 0) rb_raise(rb_eRuntimeError, "native evaluator panicked");
166
- finish_budget(budget, &outcome);
167
- RB_GC_GUARD(instance);
168
- RB_GC_GUARD(self);
169
- return status == SN_VERDICT_UNSUPPORTED ? Qnil : (status ? Qtrue : Qfalse);
369
+ return evaluate_runner(self, instance, budget, 0);
170
370
  }
171
371
  static VALUE runner_valid(VALUE self, VALUE instance) {
172
- runner_holder *r;
173
- TypedData_Get_Struct(self, runner_holder, &runner_type, r);
174
- holder *h = ready_program(r->owner);
175
- int status = sn_valid(h->engine, r->caches, r->root, instance);
176
- RB_GC_GUARD(instance);
177
- RB_GC_GUARD(self);
178
- if (status < 0) rb_raise(rb_eRuntimeError, "native evaluator panicked");
179
- return status == SN_VERDICT_UNSUPPORTED ? Qnil : (status ? Qtrue : Qfalse);
372
+ return evaluate_runner(self, instance, Qnil, 0);
373
+ }
374
+ static VALUE runner_share_scalar_state(VALUE self, VALUE number_state, VALUE regexps, VALUE scopes, VALUE references, VALUE active, VALUE reports) {
375
+ runner_holder *runner;
376
+ TypedData_Get_Struct(self, runner_holder, &runner_type, runner);
377
+ if (runner->busy) rb_raise(rb_eRuntimeError, "cannot replace active scalar state");
378
+ Check_Type(scopes, T_ARRAY);
379
+ runner->scope_state = scopes;
380
+ Check_Type(reports, T_ARRAY);
381
+ runner->report_state = reports;
382
+ Check_Type(references, T_ARRAY);
383
+ Check_Type(active, T_HASH);
384
+ runner->ref_state = references;
385
+ runner->vm_active = active;
386
+ runner->number_state = number_state;
387
+ runner->regexps = regexps;
388
+ runner->string_state = Qnil;
389
+ return self;
390
+ }
391
+ /* Library-owned identity maps: these callbacks only copy VALUE pairs to Rust.
392
+ * No Ruby allocation, dispatch or mutation occurs while Rust borrows its Vec. */
393
+ typedef struct { void *pairs; VALUE program; } reference_walk;
394
+ static int reference_instance(VALUE instance, VALUE unused, VALUE context) {
395
+ reference_walk *walk = (reference_walk *)context;
396
+ sn_reference_pair(walk->pairs, walk->program, instance);
397
+ return ST_CONTINUE;
398
+ }
399
+ static int reference_program(VALUE program, VALUE instances, VALUE context) {
400
+ reference_walk walk = {(void *)context, program};
401
+ rb_hash_foreach(instances, reference_instance, (VALUE)&walk);
402
+ return ST_CONTINUE;
403
+ }
404
+ void sn_references_read(VALUE active, void *pairs) {
405
+ if (!NIL_P(active)) rb_hash_foreach(active, reference_program, (VALUE)pairs);
406
+ }
407
+ static VALUE runner_reference_active(VALUE self, VALUE program, VALUE instance) {
408
+ runner_holder *runner;
409
+ TypedData_Get_Struct(self, runner_holder, &runner_type, runner);
410
+ if (NIL_P(runner->ref_state)) return Qfalse;
411
+ for (long i = 0; i < RARRAY_LEN(runner->ref_state); i += 2)
412
+ if (rb_ary_entry(runner->ref_state, i) == program &&
413
+ rb_ary_entry(runner->ref_state, i + 1) == instance) return Qtrue;
414
+ return Qfalse;
415
+ }
416
+ static VALUE valid(VALUE self, VALUE instance) {
417
+ holder *h = ready_program(self);
418
+ VALUE run = build_runner(self, sn_root_index(h->engine));
419
+ return runner_valid(run, instance);
180
420
  }
181
421
  void sn_pin(VALUE value) { rb_gc_mark(value); }
182
422
  VALUE sn_nil(void) { return Qnil; }
@@ -203,7 +443,11 @@ int sn_shallow_kind(VALUE value) {
203
443
  return SN_TYPE_NUMBER | ((isfinite(n) && trunc(n) == n) ? SN_TYPE_INTEGER : 0);
204
444
  }
205
445
  if (RB_TYPE_P(value, T_STRING)) return CLASS_OF(value) == rb_cString ? SN_TYPE_STRING : SN_TYPE_UNSUPPORTED;
206
- return SN_TYPE_UNSUPPORTED; /* Out-of-domain objects are handled by the Ruby VM. */
446
+ /* Ordinary Object and Symbol equality is identity. Singleton classes still
447
+ * retain runtime dispatch, including custom equality/hash/type methods. */
448
+ if ((RB_SYMBOL_P(value) && CLASS_OF(value) == rb_cSymbol) || CLASS_OF(value) == rb_cObject)
449
+ return sn_identity_kind(active_caches, CLASS_OF(value));
450
+ return SN_TYPE_UNSUPPORTED; /* Unusual objects request Ruby predicates or value primitives. */
207
451
  }
208
452
  int sn_kind(VALUE value) {
209
453
  int kind = sn_shallow_kind(value);
@@ -284,6 +528,7 @@ int sn_small_hash(VALUE value, VALUE *entries) {
284
528
  return h.count;
285
529
  }
286
530
  int sn_true(VALUE value) { return value == Qtrue; }
531
+ int sn_truthy(VALUE value) { return RTEST(value); }
287
532
  VALUE sn_fixnum_flag(void) {
288
533
  /* Enable direct immediate decoding only for the header encoding Rust
289
534
  * understands. Zero disables its hints and retains the public adapters. */
@@ -363,6 +608,15 @@ int sn_integer1024(VALUE value, uint64_t *words) {
363
608
  return rb_integer_pack(value, words, 16, sizeof(uint64_t), 0,
364
609
  INTEGER_PACK_LSWORD_FIRST | INTEGER_PACK_NATIVE_BYTE_ORDER);
365
610
  }
611
+ /* Ordinary Integer scalar operands only. These public adapters neither invoke
612
+ * Ruby methods nor allocate Ruby objects, so borrowed container views stay valid. */
613
+ size_t sn_integer_word_count(VALUE value) {
614
+ return rb_absint_numwords(value, 64, NULL);
615
+ }
616
+ int sn_integer_words(VALUE value, uint64_t *words, size_t count) {
617
+ return rb_integer_pack(value, words, count, sizeof(uint64_t), 0,
618
+ INTEGER_PACK_LSWORD_FIRST | INTEGER_PACK_NATIVE_BYTE_ORDER);
619
+ }
366
620
  int sn_is_bignum(VALUE value) { return RB_TYPE_P(value, T_BIGNUM); }
367
621
  /* CRuby's rb_hash dispatches T_BIGNUM directly to its digit hash and returns
368
622
  * a Fixnum: no Ruby method dispatch or allocation. Never pass other types,
@@ -476,11 +730,14 @@ typedef struct {
476
730
  size_t instance_len;
477
731
  const unsigned char *schema_path;
478
732
  size_t schema_len;
733
+ VALUE instance_pointer, schema_pointer;
479
734
  int opcode;
480
735
  VALUE operand, instance;
481
736
  size_t detail;
737
+ VALUE message;
482
738
  } diagnostic_record;
483
739
  typedef struct { void *report; } report_holder;
740
+ typedef struct { VALUE buffer, properties, items; } runtime_locations;
484
741
  extern void *sn_diagnose(const void *, const void *, size_t, VALUE, int *);
485
742
  extern void *sn_diagnose_with_limits(const void *, const void *, size_t, VALUE, int *, const native_limits *, native_outcome *);
486
743
  extern size_t sn_report_len(const void *);
@@ -488,6 +745,9 @@ extern size_t sn_report_size(const void *);
488
745
  extern void sn_report_record(const void *, size_t, diagnostic_record *);
489
746
  extern void sn_report_mark(const void *);
490
747
  extern void sn_report_free(void *);
748
+ extern void *sn_report_seed(const void *);
749
+ extern void sn_report_inherit(const void *, const void *);
750
+ extern void sn_report_accept(const void *, const void *);
491
751
  static void report_mark(void *ptr) {
492
752
  report_holder *h = ptr;
493
753
  if (h->report) sn_report_mark(h->report);
@@ -505,60 +765,1395 @@ static const rb_data_type_t report_type = {
505
765
  "Schemurai::Native::Diagnostics", {report_mark, report_release, report_size, NULL, {NULL}},
506
766
  NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
507
767
  };
508
- static VALUE collect_diagnostics(VALUE self, VALUE instance, holder *engine, const void *caches, size_t root, VALUE budget) {
768
+ typedef struct {
769
+ VALUE self, instance, budget;
770
+ runner_holder *runner;
771
+ holder *engine;
772
+ report_holder *report;
773
+ int status;
774
+ native_limits limits;
775
+ int limits_read;
776
+ int diagnostics;
777
+ } evaluation_call;
778
+
779
+ /* Retained Ruby blocks keep their evaluator cache scope. Bounded calls own
780
+ * their caches; unbounded scopes continue sharing the runner's persistent state. */
781
+ static void close_callback_scope(runner_holder *runner) {
782
+ if (NIL_P(runner->callback_scope)) return;
783
+ runner_holder *scope;
784
+ TypedData_Get_Struct(runner->callback_scope, runner_holder, &runner_type, scope);
785
+ if (!NIL_P(scope->active_budget)) {
786
+ scope->parent = Qnil;
787
+ scope->closed_limited = 1;
788
+ scope->regexps = Qnil;
789
+ scope->number_state = Qnil;
790
+ scope->string_state = Qnil;
791
+ }
792
+ scope->active_budget = Qnil;
793
+ scope->active_limits = (native_limits){0};
794
+ scope->callback_context = 0;
795
+ sn_caches_begin(scope->caches);
796
+ runner->callback_scope = Qnil;
797
+ }
798
+ static VALUE clear_runtime(VALUE context) {
799
+ evaluation_call *call = (evaluation_call *)context;
800
+ st_foreach(call->runner->matches, release_operation, 0);
801
+ st_clear(call->runner->matches);
802
+ call->runner->report_results = Qnil;
803
+ call->runner->pointers = Qnil;
804
+ call->runner->messages = Qnil;
805
+ call->runner->frames = Qnil;
806
+ call->runner->limited_number_state = Qnil;
807
+ call->runner->limited_string_state = Qnil;
808
+ call->runner->limited_regexps = Qnil;
809
+ st_clear(call->runner->frame_indices);
810
+ sn_caches_runtime(call->runner->caches, call->runner->matches);
811
+ sn_caches_begin(call->runner->caches);
812
+ return Qnil;
813
+ }
814
+ /* Rust owns scope snapshots; the Ruby Array is the evaluator's live scope.
815
+ * Publishing happens only after native frames and scope-vector loans unwind. */
816
+ static void publish_scope(runner_holder *runner) {
817
+ if (!sn_scopes_enabled(runner->caches)) return;
818
+ size_t count = sn_scopes_len(runner->caches);
819
+ int same = (size_t)RARRAY_LEN(runner->active_scope_state) == count;
820
+ for (size_t i = 0; same && i < count; i++)
821
+ same = rb_ary_entry(runner->active_scope_state, i) == sn_scopes_at(runner->caches, i);
822
+ if (same) return;
823
+ if (!count) { rb_ary_clear(runner->active_scope_state); return; }
824
+ VALUE state = rb_ary_new_capa(count);
825
+ for (size_t i = 0; i < count; i++) rb_ary_push(state, sn_scopes_at(runner->caches, i));
826
+ rb_ary_replace(runner->active_scope_state, state);
827
+ RB_GC_GUARD(state);
828
+ }
829
+ static void publish_references(runner_holder *runner, int initial) {
830
+ if (!sn_scopes_enabled(runner->caches)) return;
831
+ size_t count = sn_references_len(runner->caches, initial);
832
+ int same = (size_t)RARRAY_LEN(runner->active_ref_state) == count;
833
+ for (size_t i = 0; same && i < count; i++)
834
+ same = rb_ary_entry(runner->active_ref_state, i) == sn_references_at(runner->caches, i, initial);
835
+ if (same) return;
836
+ if (!count) { rb_ary_clear(runner->active_ref_state); return; }
837
+ VALUE state = rb_ary_new_capa(count);
838
+ for (size_t i = 0; i < count; i++) rb_ary_push(state, sn_references_at(runner->caches, i, initial));
839
+ rb_ary_replace(runner->active_ref_state, state);
840
+ RB_GC_GUARD(state);
841
+ }
842
+ static void evaluate_native(evaluation_call *call) {
843
+ runner_holder *r = call->runner;
844
+ native_outcome outcome = {0};
845
+ if (!NIL_P(call->budget) && !call->limits_read) {
846
+ call->limits = read_limits(call->budget);
847
+ call->limits_read = 1;
848
+ }
849
+ r->active_limits = call->limits;
850
+ const void *previous_caches = active_caches;
851
+ active_caches = r->caches;
852
+ if (call->report) {
853
+ if (NIL_P(call->budget)) {
854
+ call->report->report = sn_diagnose(call->engine->engine, r->caches, r->root,
855
+ call->instance, &call->status);
856
+ } else {
857
+ call->report->report = sn_diagnose_with_limits(call->engine->engine, r->caches, r->root,
858
+ call->instance, &call->status, &call->limits, &outcome);
859
+ }
860
+ } else if (NIL_P(call->budget)) {
861
+ call->status = sn_valid(call->engine->engine, r->caches, r->root, call->instance, 0);
862
+ } else {
863
+ call->status = sn_valid_with_limits(call->engine->engine, r->caches, r->root,
864
+ call->instance, 0, &call->limits, &outcome);
865
+ }
866
+ active_caches = previous_caches;
867
+ publish_scope(r);
868
+ publish_references(r, 0);
869
+ check_evaluation_status(call->status,
870
+ call->report ? "native diagnostics panicked" : "native evaluator panicked");
871
+ if (!NIL_P(call->budget)) finish_budget(call->budget, &outcome);
872
+ }
873
+ static VALUE callback_scope(evaluation_call *call) {
874
+ runner_holder *runner = call->runner;
875
+ /* Nested callbacks share the original public invocation's budget lifetime.
876
+ * Returning from one child must not close a block its caller retained. */
877
+ if (runner->callback_context) {
878
+ VALUE object = runner->parent;
879
+ while (!NIL_P(object)) {
880
+ runner_holder *scope;
881
+ TypedData_Get_Struct(object, runner_holder, &runner_type, scope);
882
+ if (scope->scope_owner) return object;
883
+ object = scope->parent;
884
+ }
885
+ rb_raise(rb_eRuntimeError, "native callback lost its invocation scope");
886
+ }
887
+ if (NIL_P(runner->callback_scope)) {
888
+ VALUE object = build_runner(runner->owner, runner->root);
889
+ runner_holder *scope;
890
+ TypedData_Get_Struct(object, runner_holder, &runner_type, scope);
891
+ scope->parent = call->self;
892
+ scope->scope_owner = 1;
893
+ scope->scope_state = runner->active_scope_state;
894
+ scope->report_state = runner->active_report_state;
895
+ scope->ref_state = runner->active_ref_state;
896
+ scope->vm_active = runner->active_vm_state;
897
+ scope->callback_context = 1;
898
+ scope->active_budget = call->budget;
899
+ scope->active_limits = call->limits;
900
+ copy_states(scope, runner, 1, 0);
901
+ sn_caches_child(scope->caches, runner->caches, !NIL_P(call->budget), 1);
902
+ runner->callback_scope = object;
903
+ }
904
+ return runner->callback_scope;
905
+ }
906
+ extern void *sn_report_clone(const void *);
907
+ extern void *sn_report_partial(const void *);
908
+ extern void *sn_report_callback_seed(const void *);
909
+ extern void *sn_report_live(const void *);
910
+ extern void sn_report_effect(const void *, const void *);
911
+ extern void *sn_callback_report_result(const void *, int);
912
+ extern void *sn_report_interrupted(const void *);
913
+ extern void *sn_array_report_child(const void *, size_t, const unsigned char *, size_t, size_t, int);
914
+ extern void *sn_array_report_error(const void *, const void *, size_t, VALUE, int, VALUE);
915
+ extern int sn_array_report_adopt(void *, const void *);
916
+ extern void sn_array_report_record(void *, size_t);
917
+ extern int sn_array_report_contains(const void *, size_t, int);
918
+ extern int sn_report_silent(const void *);
919
+ extern void sn_report_child_validity(void *, int);
920
+ extern void sn_report_clear_collection(void *, int);
921
+ extern void *sn_report_context(int, size_t, VALUE, VALUE);
922
+ extern size_t sn_report_context_count(const void *, size_t);
923
+ extern int sn_report_context_reset(const void *);
924
+ extern int sn_report_verdict(const void *);
925
+ extern void sn_array_report_finish(void *, int);
926
+ extern void sn_container_report_finish(void *, int, const runtime_locations *);
927
+ extern VALUE sn_report_items_set(const void *, int);
928
+ extern void sn_caches_reuse_buffers(const void *);
929
+ /* Mutable accumulation never spans Ruby calls. While Rust mutates the report,
930
+ * immutable seeds/results retain every Ruby handle for stopped-Ractor GC. */
931
+ typedef struct {
932
+ VALUE owner, scope, seed, results, instance, self;
933
+ size_t node;
934
+ void *report;
935
+ int busy;
936
+ } report_collector;
937
+ static void report_collector_mark(void *ptr) {
938
+ report_collector *c = ptr;
939
+ rb_gc_mark(c->owner); rb_gc_mark(c->scope); rb_gc_mark(c->seed);
940
+ rb_gc_mark(c->results); rb_gc_mark(c->instance);
941
+ if (c->report && !c->busy) sn_report_mark(c->report);
942
+ }
943
+ static void report_collector_free(void *ptr) {
944
+ report_collector *c = ptr;
945
+ if (c->report) sn_report_free(c->report);
946
+ xfree(c);
947
+ }
948
+ static size_t report_collector_size(const void *ptr) {
949
+ const report_collector *c = ptr;
950
+ return sizeof(*c) + (c->report && !c->busy ? sn_report_size(c->report) : 0);
951
+ }
952
+ static const rb_data_type_t report_collector_type = {
953
+ "Schemurai::Native::ReportCollector", {report_collector_mark, report_collector_free, report_collector_size, NULL, {NULL}},
954
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
955
+ };
956
+ static report_collector *get_report_collector(VALUE self) {
957
+ report_collector *c;
958
+ TypedData_Get_Struct(self, report_collector, &report_collector_type, c);
959
+ return c;
960
+ }
961
+ static VALUE collector_report_object(void) {
509
962
  report_holder *h;
510
- VALUE owner = TypedData_Make_Struct(rb_cObject, report_holder, &report_type, h);
963
+ VALUE object = TypedData_Make_Struct(rb_cObject, report_holder, &report_type, h);
511
964
  h->report = NULL;
512
- int status = 0;
513
- native_outcome outcome = {0};
514
- if (NIL_P(budget)) {
515
- h->report = sn_diagnose(engine->engine, caches, root, instance, &status);
965
+ return object;
966
+ }
967
+ static VALUE collector_runner(report_collector *c, size_t node, VALUE seed, VALUE error_frame) {
968
+ runner_holder *scope;
969
+ TypedData_Get_Struct(c->scope, runner_holder, &runner_type, scope);
970
+ if (scope->closed_limited) rb_funcall(Qnil, rb_intern_const("checkpoint"), 0);
971
+ if (scope->scope_owner && !NIL_P(scope->parent)) {
972
+ runner_holder *owner;
973
+ TypedData_Get_Struct(scope->parent, runner_holder, &runner_type, owner);
974
+ copy_states(scope, owner, !NIL_P(scope->active_budget), 0);
975
+ }
976
+ VALUE callback = build_runner(c->owner, node);
977
+ runner_holder *r;
978
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, r);
979
+ r->parent = c->scope;
980
+ r->callback_context = r->continuation = r->scalar_mode = 1;
981
+ r->report_seed = seed;
982
+ r->error_frame = error_frame;
983
+ return callback;
984
+ }
985
+ static VALUE collector_run(report_collector *c, size_t node, VALUE instance, VALUE seed, VALUE error_frame, int mode, VALUE sink) {
986
+ VALUE callback = collector_runner(c, node, seed, error_frame);
987
+ if (!NIL_P(sink)) {
988
+ runner_holder *child;
989
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, child);
990
+ child->report_sink = sink;
991
+ }
992
+ runner_holder *scope;
993
+ TypedData_Get_Struct(c->scope, runner_holder, &runner_type, scope);
994
+ VALUE result = evaluate_runner(callback, instance, scope->active_budget, mode);
995
+ if (NIL_P(result)) rb_raise(rb_eRuntimeError, "native container child lost its coverage proof");
996
+ RB_GC_GUARD(seed); RB_GC_GUARD(error_frame); RB_GC_GUARD(callback);
997
+ return result;
998
+ }
999
+ static size_t collector_node(report_collector *c, VALUE program) {
1000
+ size_t node = sn_node_index(ready_program(c->owner)->engine, program);
1001
+ if (node == SIZE_MAX) rb_raise(rb_eRuntimeError, "native container child was not compiled");
1002
+ return node;
1003
+ }
1004
+ static int collector_adopt(report_collector *c, VALUE result) {
1005
+ rb_ary_push(c->results, result);
1006
+ report_holder *h;
1007
+ TypedData_Get_Struct(result, report_holder, &report_type, h);
1008
+ c->busy = 1;
1009
+ int verdict = sn_array_report_adopt(c->report, h->report);
1010
+ c->busy = 0;
1011
+ return verdict;
1012
+ }
1013
+ static VALUE collector_evaluate(VALUE self, VALUE program, VALUE instance, VALUE index, VALUE keyword, VALUE child_segment) {
1014
+ report_collector *c = get_report_collector(self);
1015
+ size_t node = collector_node(c, program);
1016
+ if (sn_report_silent(c->report)) return collector_run(c, node, instance, Qnil, Qnil, 0, Qnil);
1017
+ size_t at = NUM2SIZET(index), child = NIL_P(child_segment) ? SIZE_MAX : NUM2SIZET(child_segment);
1018
+ StringValue(keyword);
1019
+ VALUE seed_object = collector_report_object();
1020
+ report_holder *seed;
1021
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1022
+ seed->report = sn_array_report_child(c->report, at, (const unsigned char *)RSTRING_PTR(keyword), RSTRING_LEN(keyword), child, 0);
1023
+ VALUE result = collector_run(c, node, instance, seed_object, Qnil, 2, c->self);
1024
+ int verdict = collector_adopt(c, result);
1025
+ report_holder *completed;
1026
+ TypedData_Get_Struct(result, report_holder, &report_type, completed);
1027
+ if (sn_report_context_reset(completed->report) == 1) {
1028
+ /* This child seed owns the evaluate_at path scope. Public validity
1029
+ * reentry cleared its buffers, so run the original Ruby ensure pops. */
1030
+ int pops = NIL_P(child_segment) ? 2 : 3;
1031
+ while (pops-- > 0) rb_funcall(Qnil, rb_intern_const("pop"), 0);
1032
+ }
1033
+ RB_GC_GUARD(self); RB_GC_GUARD(seed_object); RB_GC_GUARD(keyword); RB_GC_GUARD(result);
1034
+ return verdict == SN_VERDICT_VALID ? Qtrue : Qfalse;
1035
+ }
1036
+ static VALUE collector_trial(VALUE self, VALUE program, VALUE instance) {
1037
+ report_collector *c = get_report_collector(self);
1038
+ VALUE seed_object = collector_report_object();
1039
+ report_holder *seed;
1040
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1041
+ seed->report = sn_array_report_child(c->report, 0, NULL, 0, SIZE_MAX, 1);
1042
+ VALUE result = collector_run(c, collector_node(c, program), instance, seed_object, Qnil, 2, Qnil);
1043
+ report_holder *completed;
1044
+ TypedData_Get_Struct(result, report_holder, &report_type, completed);
1045
+ int verdict = sn_report_verdict(completed->report);
1046
+ RB_GC_GUARD(self); RB_GC_GUARD(seed_object); RB_GC_GUARD(result);
1047
+ return verdict == SN_VERDICT_VALID ? Qtrue : Qfalse;
1048
+ }
1049
+ static VALUE collector_error(VALUE self, VALUE keyword, VALUE frame) {
1050
+ report_collector *c = get_report_collector(self);
1051
+ const char *name = StringValueCStr(keyword);
1052
+ int kind;
1053
+ if (!strcmp(name, "type")) kind = 0;
1054
+ else if (!strcmp(name, "maxItems")) kind = 1;
1055
+ else if (!strcmp(name, "minItems")) kind = 2;
1056
+ else if (!strcmp(name, "uniqueItems")) kind = 3;
1057
+ else if (!strcmp(name, "contains")) kind = 4;
1058
+ else rb_raise(rb_eArgError, "unknown array diagnostic");
1059
+ VALUE seed_object = collector_report_object();
1060
+ report_holder *seed;
1061
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1062
+ seed->report = sn_array_report_error(c->report, ready_program(c->owner)->engine,
1063
+ c->node, c->instance, kind, INT2FIX(0));
1064
+ VALUE result = collector_run(c, c->node, c->instance, seed_object, frame, 2, c->self);
1065
+ collector_adopt(c, result);
1066
+ RB_GC_GUARD(self); RB_GC_GUARD(seed_object); RB_GC_GUARD(result);
1067
+ return Qnil;
1068
+ }
1069
+ static VALUE collector_record(VALUE self, VALUE index) {
1070
+ report_collector *c = get_report_collector(self);
1071
+ size_t at = NUM2SIZET(index);
1072
+ c->busy = 1;
1073
+ sn_array_report_record(c->report, at);
1074
+ c->busy = 0;
1075
+ return self;
1076
+ }
1077
+ static VALUE collector_contains(VALUE self, VALUE index, VALUE prior) {
1078
+ report_collector *c = get_report_collector(self);
1079
+ VALUE set = sn_report_items_set(c->report, RTEST(prior));
1080
+ if (!NIL_P(set)) return rb_funcall(set, rb_intern_const("include?"), 1, index);
1081
+ return sn_array_report_contains(c->report, NUM2SIZET(index), RTEST(prior)) ? Qtrue : Qfalse;
1082
+ }
1083
+ static VALUE collector_context(report_collector *c) {
1084
+ runner_holder *scope;
1085
+ TypedData_Get_Struct(c->scope, runner_holder, &runner_type, scope);
1086
+ return NIL_P(scope->report_state) ? Qnil : rb_ary_entry(scope->report_state, 0);
1087
+ }
1088
+ static VALUE collector_silent(VALUE self) {
1089
+ VALUE current = collector_context(get_report_collector(self));
1090
+ if (NIL_P(current)) return Qtrue;
1091
+ if (rb_typeddata_is_kind_of(current, &report_collector_type))
1092
+ return sn_report_silent(get_report_collector(current)->report) ? Qtrue : Qfalse;
1093
+ VALUE state = rb_funcall(current, rb_intern_const("native_report_context"), 0);
1094
+ return NIL_P(rb_ary_entry(state, 0)) ? Qtrue : Qfalse;
1095
+ }
1096
+ static runtime_locations read_runtime_locations(VALUE buffer) {
1097
+ runtime_locations locations = {buffer, Qnil, Qnil};
1098
+ if (!NIL_P(buffer)) {
1099
+ locations.properties = rb_funcall(buffer, rb_intern_const("evaluated_properties"), 0);
1100
+ locations.items = rb_funcall(buffer, rb_intern_const("evaluated_items"), 0);
1101
+ }
1102
+ return locations;
1103
+ }
1104
+ static VALUE collector_finish(VALUE self, VALUE valid, VALUE buffer) {
1105
+ report_collector *c = get_report_collector(self);
1106
+ runtime_locations locations = read_runtime_locations(buffer);
1107
+ if (!NIL_P(buffer)) rb_ary_push(c->results, buffer);
1108
+ c->busy = 1;
1109
+ sn_container_report_finish(c->report, RTEST(valid), &locations);
1110
+ c->busy = 0;
1111
+ VALUE result = collector_report_object();
1112
+ report_holder *h;
1113
+ TypedData_Get_Struct(result, report_holder, &report_type, h);
1114
+ h->report = sn_report_clone(c->report);
1115
+ RB_GC_GUARD(self); RB_GC_GUARD(buffer);
1116
+ return result;
1117
+ }
1118
+ static VALUE collector_evaluation_buffer(VALUE self) {
1119
+ report_collector *c = get_report_collector(self);
1120
+ runner_holder *scope;
1121
+ TypedData_Get_Struct(c->scope, runner_holder, &runner_type, scope);
1122
+ VALUE pool = rb_ary_entry(scope->report_state, 1);
1123
+ if (NIL_P(pool)) {
1124
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1125
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1126
+ VALUE klass = rb_const_get(internal, rb_intern_const("EvaluationPool"));
1127
+ pool = rb_class_new_instance(0, NULL, klass);
1128
+ rb_ary_store(scope->report_state, 1, pool);
1129
+ }
1130
+ VALUE result = rb_funcall(pool, rb_intern_const("acquire"), 0);
1131
+ RB_GC_GUARD(pool); RB_GC_GUARD(self);
1132
+ return result;
1133
+ }
1134
+ extern void *sn_object_report_child(const void *, VALUE, const unsigned char *, size_t, VALUE, int);
1135
+ extern void *sn_object_report_error(const void *, const void *, size_t, VALUE, int, VALUE);
1136
+ extern void *sn_object_report_properties(const void *, const void *, int);
1137
+ extern VALUE sn_property_locations_set(const void *, size_t *);
1138
+ extern VALUE sn_property_locations_key(const void *, size_t);
1139
+ extern void sn_property_locations_mark(const void *);
1140
+ extern size_t sn_property_locations_size(const void *);
1141
+ extern void sn_property_locations_free(void *);
1142
+ typedef struct { void *locations; } property_locations_holder;
1143
+ static void property_locations_mark(void *ptr) {
1144
+ property_locations_holder *h = ptr;
1145
+ if (h->locations) sn_property_locations_mark(h->locations);
1146
+ }
1147
+ static void property_locations_free(void *ptr) {
1148
+ property_locations_holder *h = ptr;
1149
+ if (h->locations) sn_property_locations_free(h->locations);
1150
+ xfree(h);
1151
+ }
1152
+ static size_t property_locations_size(const void *ptr) {
1153
+ const property_locations_holder *h = ptr;
1154
+ return sizeof(*h) + (h->locations ? sn_property_locations_size(h->locations) : 0);
1155
+ }
1156
+ static const rb_data_type_t property_locations_type = {
1157
+ "Schemurai::Native::PropertyLocations", {property_locations_mark, property_locations_free, property_locations_size, NULL, {NULL}},
1158
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
1159
+ };
1160
+ static VALUE report_properties(VALUE owner, const void *report, int prior) {
1161
+ property_locations_holder *h;
1162
+ VALUE object = TypedData_Make_Struct(rb_cObject, property_locations_holder, &property_locations_type, h);
1163
+ h->locations = NULL;
1164
+ h->locations = sn_object_report_properties(report, ready_program(owner)->engine, prior);
1165
+ size_t count;
1166
+ VALUE set = sn_property_locations_set(h->locations, &count);
1167
+ if (!NIL_P(set) && !count) return set;
1168
+ VALUE klass = rb_const_get(rb_cObject, rb_intern_const("Set"));
1169
+ VALUE result = rb_class_new_instance(0, NULL, klass);
1170
+ for (size_t i = 0; i < count; i++)
1171
+ rb_funcall(result, rb_intern_const("add"), 1, sn_property_locations_key(h->locations, i));
1172
+ if (!NIL_P(set)) rb_funcall(result, rb_intern_const("merge"), 1, set);
1173
+ rb_obj_freeze(result);
1174
+ RB_GC_GUARD(object);
1175
+ return result;
1176
+ }
1177
+ static VALUE collector_properties(VALUE self, VALUE prior) {
1178
+ report_collector *c = get_report_collector(self);
1179
+ VALUE result = report_properties(c->owner, c->report, RTEST(prior));
1180
+ RB_GC_GUARD(self);
1181
+ return result;
1182
+ }
1183
+ static VALUE compatibility_object_callback(report_collector *, VALUE, VALUE);
1184
+ static VALUE collector_object_evaluate(VALUE self, VALUE program, VALUE instance, VALUE segment, VALUE keyword, VALUE child, VALUE flags, VALUE annotations) {
1185
+ report_collector *c = get_report_collector(self);
1186
+ size_t node = collector_node(c, program);
1187
+ VALUE current = collector_context(c);
1188
+ if (!NIL_P(current) && !rb_typeddata_is_kind_of(current, &report_collector_type)) {
1189
+ VALUE args = rb_ary_new_from_args(8, self, program, instance, segment, keyword, child, flags, annotations);
1190
+ VALUE result = compatibility_object_callback(c, current, args);
1191
+ RB_GC_GUARD(args);
1192
+ return result;
1193
+ }
1194
+ report_collector *target = NIL_P(current) ? NULL : get_report_collector(current);
1195
+ if (!RTEST(annotations) && (!target || sn_report_silent(target->report))) {
1196
+ VALUE inherited = Qnil;
1197
+ if (target) {
1198
+ inherited = collector_report_object();
1199
+ report_holder *seed;
1200
+ TypedData_Get_Struct(inherited, report_holder, &report_type, seed);
1201
+ seed->report = sn_report_callback_seed(target->report);
1202
+ }
1203
+ VALUE result = collector_run(c, node, instance, inherited, Qnil, 0, current);
1204
+ if (target && !RTEST(result)) {
1205
+ target->busy = 1;
1206
+ sn_report_child_validity(target->report, 0);
1207
+ target->busy = 0;
1208
+ }
1209
+ RB_GC_GUARD(inherited); RB_GC_GUARD(current);
1210
+ return result;
1211
+ }
1212
+ StringValue(keyword);
1213
+ int bits = NUM2INT(flags) | (RTEST(annotations) ? 4 : 0);
1214
+ int paths = target && !sn_report_silent(target->report);
1215
+ VALUE seed_object = collector_report_object();
1216
+ report_holder *seed;
1217
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1218
+ VALUE empty_seed = Qnil;
1219
+ const void *source;
1220
+ if (target) source = target->report;
1221
+ else {
1222
+ empty_seed = collector_report_object();
1223
+ report_holder *empty;
1224
+ TypedData_Get_Struct(empty_seed, report_holder, &report_type, empty);
1225
+ empty->report = sn_report_context(1, 0, Qnil, Qnil);
1226
+ source = empty->report;
1227
+ }
1228
+ seed->report = sn_object_report_child(source, segment, (const unsigned char *)RSTRING_PTR(keyword), RSTRING_LEN(keyword), child, bits);
1229
+ VALUE result = collector_run(c, node, instance, seed_object, Qnil, 2, current);
1230
+ report_holder *completed;
1231
+ TypedData_Get_Struct(result, report_holder, &report_type, completed);
1232
+ int verdict = target ? collector_adopt(target, result) : sn_report_verdict(completed->report);
1233
+ if (paths && sn_report_context_reset(completed->report) == 1) {
1234
+ int pops = 1 + !!(bits & 2) + !(bits & 1);
1235
+ while (pops-- > 0) rb_funcall(Qnil, rb_intern_const("pop"), 0);
1236
+ }
1237
+ VALUE output = verdict == SN_VERDICT_VALID ? Qtrue : Qfalse;
1238
+ if (RTEST(annotations)) {
1239
+ VALUE properties = report_properties(c->owner, completed->report, 0);
1240
+ output = rb_ary_new_from_args(2, output, properties);
1241
+ RB_GC_GUARD(properties);
1242
+ }
1243
+ RB_GC_GUARD(current); RB_GC_GUARD(empty_seed);
1244
+ RB_GC_GUARD(self); RB_GC_GUARD(seed_object); RB_GC_GUARD(result); RB_GC_GUARD(keyword);
1245
+ return output;
1246
+ }
1247
+ static VALUE collector_object_error(VALUE self, VALUE keyword, VALUE frame) {
1248
+ report_collector *c = get_report_collector(self);
1249
+ const char *name = StringValueCStr(keyword);
1250
+ int kind;
1251
+ if (!strcmp(name, "type")) kind = 0;
1252
+ else if (!strcmp(name, "maxProperties")) kind = 1;
1253
+ else if (!strcmp(name, "minProperties")) kind = 2;
1254
+ else if (!strcmp(name, "required")) kind = 3;
1255
+ else if (!strcmp(name, "dependencies")) kind = 4;
1256
+ else if (!strcmp(name, "dependentRequired")) kind = 5;
1257
+ else rb_raise(rb_eArgError, "unknown object diagnostic");
1258
+ VALUE seed_object = collector_report_object();
1259
+ report_holder *seed;
1260
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1261
+ seed->report = sn_object_report_error(c->report, ready_program(c->owner)->engine, c->node, c->instance, kind, INT2FIX(0));
1262
+ VALUE result = collector_run(c, c->node, c->instance, seed_object, frame, 2, c->self);
1263
+ collector_adopt(c, result);
1264
+ RB_GC_GUARD(self); RB_GC_GUARD(seed_object); RB_GC_GUARD(result);
1265
+ return Qnil;
1266
+ }
1267
+ static VALUE collector_invocation_budget(VALUE self) {
1268
+ runner_holder *scope;
1269
+ TypedData_Get_Struct(get_report_collector(self)->scope, runner_holder, &runner_type, scope);
1270
+ return scope->active_budget;
1271
+ }
1272
+ static VALUE collector_regexp_cache(VALUE self) {
1273
+ runner_holder *scope;
1274
+ TypedData_Get_Struct(get_report_collector(self)->scope, runner_holder, &runner_type, scope);
1275
+ VALUE *slot = NIL_P(scope->active_budget) ? &scope->regexps : &scope->limited_regexps;
1276
+ if (NIL_P(*slot)) *slot = rb_hash_new();
1277
+ publish_states(scope);
1278
+ return *slot;
1279
+ }
1280
+ typedef struct { VALUE state, collector, previous, frame; int trial, completed; } report_context_call;
1281
+ static VALUE collect_container_frame(VALUE context) {
1282
+ report_context_call *call = (report_context_call *)context;
1283
+ VALUE result = rb_funcall(call->frame, rb_intern_const("collect"), 0);
1284
+ call->completed = 1;
1285
+ return result;
1286
+ }
1287
+ static VALUE restore_report_context(VALUE context) {
1288
+ report_context_call *call = (report_context_call *)context;
1289
+ if (!NIL_P(call->frame) && !call->completed && !NIL_P(call->previous)
1290
+ && rb_typeddata_is_kind_of(call->previous, &report_collector_type)) {
1291
+ VALUE interrupted = collector_report_object();
1292
+ report_holder *partial;
1293
+ TypedData_Get_Struct(interrupted, report_holder, &report_type, partial);
1294
+ partial->report = sn_report_partial(get_report_collector(call->collector)->report);
1295
+ collector_adopt(get_report_collector(call->previous), interrupted);
1296
+ RB_GC_GUARD(interrupted);
1297
+ }
1298
+ if (call->trial || rb_ary_entry(call->state, 0) == call->collector)
1299
+ rb_ary_store(call->state, 0, call->previous);
1300
+ return Qnil;
1301
+ }
1302
+ static VALUE make_report_collector(VALUE owner, VALUE scope, VALUE seed_object, VALUE instance, size_t node) {
1303
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1304
+ VALUE native = rb_const_get(schemurai, rb_intern_const("Native"));
1305
+ VALUE klass = rb_const_get(native, rb_intern_const("ReportCollector"));
1306
+ report_collector *c;
1307
+ VALUE collector = TypedData_Make_Struct(klass, report_collector, &report_collector_type, c);
1308
+ c->self = collector;
1309
+ c->owner = owner; c->scope = scope; c->seed = seed_object; c->results = Qnil;
1310
+ c->instance = instance; c->node = node; c->report = NULL; c->busy = 0;
1311
+ c->results = rb_ary_new();
1312
+ report_holder *seed;
1313
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1314
+ c->report = sn_report_clone(seed->report);
1315
+ return collector;
1316
+ }
1317
+ static VALUE container_report_runtime(evaluation_call *call, int object) {
1318
+ runner_holder *r = call->runner;
1319
+ contains_arguments arguments;
1320
+ sn_contains_arguments(r->caches, &arguments);
1321
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1322
+ VALUE seed_object = collector_report_object();
1323
+ report_holder *seed;
1324
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1325
+ seed->report = sn_report_seed(r->caches);
1326
+ VALUE collector = make_report_collector(r->owner, callback_scope(call), seed_object, arguments.instance, arguments.child);
1327
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1328
+ VALUE checks = rb_const_get(internal, rb_intern_const("ContainerChecks"));
1329
+ const char *frame_name = object ? (NIL_P(call->budget) ? "ObjectReportFrame" : "LimitedObjectReportFrame")
1330
+ : (NIL_P(call->budget) ? "ArrayReportFrame" : "LimitedArrayReportFrame");
1331
+ VALUE frame_class = rb_const_get(checks, rb_intern(frame_name));
1332
+ VALUE args[] = {arguments.rules, arguments.instance, call->budget, INT2FIX(arguments.required), collector};
1333
+ VALUE frame = rb_class_new_instance(5, args, frame_class);
1334
+ report_context_call context = {r->active_report_state, collector, rb_ary_entry(r->active_report_state, 0), frame, sn_report_silent(seed->report), 0};
1335
+ rb_ary_store(context.state, 0, collector);
1336
+ VALUE result = rb_ensure(collect_container_frame, (VALUE)&context, restore_report_context, (VALUE)&context);
1337
+ if (NIL_P(r->report_results)) r->report_results = rb_ary_new();
1338
+ rb_ary_push(r->report_results, result);
1339
+ report_holder *completed;
1340
+ TypedData_Get_Struct(result, report_holder, &report_type, completed);
1341
+ sn_report_accept(r->caches, completed->report);
1342
+ RB_GC_GUARD(collector); RB_GC_GUARD(frame); RB_GC_GUARD(result);
1343
+ return Qtrue;
1344
+ }
1345
+
1346
+ /* Each borrowed native text piece is copied before any Ruby method is called.
1347
+ * The owning runner keeps the request and its name handles alive across GC. */
1348
+ static VALUE build_pointer(runner_holder *runner) {
1349
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1350
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1351
+ VALUE helper = rb_const_get(internal, rb_intern_const("JsonPointer"));
1352
+ VALUE output = rb_utf8_str_new("", 0);
1353
+ size_t count = sn_pointer_parts(runner->caches);
1354
+ for (size_t index = 0; index < count; index++) {
1355
+ const unsigned char *data;
1356
+ size_t length;
1357
+ VALUE key;
1358
+ sn_pointer_part(runner->caches, index, &data, &length, &key);
1359
+ if (length) rb_str_append(output, rb_utf8_str_new((const char *)data, length));
1360
+ if (key != Qundef) {
1361
+ VALUE escaped = rb_funcall(helper, rb_intern_const("escape"), 1, key);
1362
+ rb_str_append(output, escaped);
1363
+ }
1364
+ }
1365
+ RB_GC_GUARD(helper);
1366
+ return output;
1367
+ }
1368
+ static VALUE null_numeric(VALUE instance, int operation) {
1369
+ int selector = operation - SN_RUNTIME_NULL_NUMBER;
1370
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1371
+ VALUE native = rb_const_get(schemurai, rb_intern_const("Native"));
1372
+ VALUE helper = rb_const_get(native, rb_intern_const("NullNumeric"));
1373
+ return rb_funcall(helper, rb_intern_const("valid?"), 3, instance,
1374
+ INT2FIX(selector >> 1), (selector & 1) ? Qtrue : Qfalse);
1375
+ }
1376
+ static VALUE build_message(runner_holder *runner) {
1377
+ message_arguments arguments;
1378
+ sn_message_arguments(runner->caches, &arguments);
1379
+ VALUE keyword = rb_utf8_str_new((const char *)arguments.keyword, arguments.keyword_len);
1380
+ if (arguments.frame != Qundef) {
1381
+ st_data_t index;
1382
+ if (!st_lookup(runner->frame_indices, (st_data_t)arguments.frame, &index)) rb_raise(rb_eRuntimeError, "missing runtime frame");
1383
+ VALUE frame = rb_ary_entry(runner->frames, (long)index);
1384
+ VALUE result = rb_funcall(frame, rb_intern_const("message"), 2, keyword, arguments.detail ? Qtrue : Qfalse);
1385
+ RB_GC_GUARD(frame);
1386
+ RB_GC_GUARD(keyword);
1387
+ return result;
1388
+ }
1389
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1390
+ VALUE native = rb_const_get(schemurai, rb_intern_const("Native"));
1391
+ VALUE helper = rb_const_get(native, rb_intern_const("Diagnostics"));
1392
+ VALUE result = rb_funcall(helper, rb_intern_const("message"), 5, keyword,
1393
+ INT2NUM(arguments.opcode), arguments.operand, arguments.instance, SIZET2NUM(arguments.detail));
1394
+ RB_GC_GUARD(keyword);
1395
+ RB_GC_GUARD(helper);
1396
+ return result;
1397
+ }
1398
+ static VALUE scalar_step(runner_holder *runner, int operation, VALUE budget) {
1399
+ int container = operation >= SN_RUNTIME_ARRAY_HEADER;
1400
+ int object = operation >= SN_RUNTIME_OBJECT_HEADER;
1401
+ int string = !container && operation >= SN_RUNTIME_STRING;
1402
+ int base = container ? (object ? SN_RUNTIME_OBJECT_HEADER : SN_RUNTIME_ARRAY_HEADER)
1403
+ : (string ? SN_RUNTIME_STRING : SN_RUNTIME_NUMBER);
1404
+ int selector = operation - base;
1405
+ int flags = selector & (string ? 255 : 63);
1406
+ scalar_arguments arguments;
1407
+ sn_scalar_arguments(runner->caches, &arguments);
1408
+ st_data_t stored;
1409
+ VALUE frame;
1410
+ if (st_lookup(runner->frame_indices, (st_data_t)arguments.ordinal, &stored)) {
1411
+ frame = rb_ary_entry(runner->frames, (long)stored);
1412
+ } else {
1413
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1414
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1415
+ VALUE checks = rb_const_get(internal, rb_intern_const(container ? "ContainerChecks" : (string ? "StringChecks" : "NumberChecks")));
1416
+ VALUE state = budget;
1417
+ if (!container) {
1418
+ VALUE *state_slot = string
1419
+ ? (NIL_P(budget) ? &runner->string_state : &runner->limited_string_state)
1420
+ : (NIL_P(budget) ? &runner->number_state : &runner->limited_number_state);
1421
+ if (NIL_P(*state_slot)) {
1422
+ VALUE state_class = rb_const_get(checks, rb_intern_const("State"));
1423
+ if (string) {
1424
+ VALUE *regexps = NIL_P(budget) ? &runner->regexps : &runner->limited_regexps;
1425
+ if (NIL_P(*regexps)) *regexps = rb_hash_new();
1426
+ VALUE args[] = {*regexps, budget};
1427
+ *state_slot = rb_class_new_instance(2, args, state_class);
1428
+ } else {
1429
+ *state_slot = rb_class_new_instance(0, NULL, state_class);
1430
+ if (!NIL_P(budget)) rb_funcall(*state_slot, rb_intern_const("budget="), 1, budget);
1431
+ }
1432
+ }
1433
+ state = *state_slot;
1434
+ }
1435
+ const char *name = "Frame";
1436
+ if (container) name = object ? "ObjectFrame" : (NIL_P(budget) ? "ArrayFrame" : "LimitedArrayFrame");
1437
+ VALUE klass = rb_const_get(checks, rb_intern_const(name));
1438
+ VALUE args[] = {arguments.rules, arguments.instance, state, INT2FIX(flags)};
1439
+ frame = rb_class_new_instance((container || string) ? 4 : 3, args, klass);
1440
+ if (NIL_P(runner->frames)) runner->frames = rb_ary_new();
1441
+ size_t index = RARRAY_LEN(runner->frames);
1442
+ rb_ary_push(runner->frames, frame);
1443
+ /* Array indices remain valid if compaction moves a frame. */
1444
+ st_insert(runner->frame_indices, (st_data_t)arguments.ordinal, (st_data_t)index);
1445
+ }
1446
+ publish_states(runner);
1447
+ int step = selector >> (string ? 8 : 6);
1448
+ VALUE result;
1449
+ if (container) {
1450
+ switch (step) {
1451
+ case 0: result = rb_funcall(frame, rb_intern_const("valid?"), 0); break;
1452
+ case 1: result = rb_funcall(frame, rb_intern_const("container?"), 0); break;
1453
+ default: result = rb_funcall(frame, rb_intern_const("check"), 1, INT2FIX(step)); break;
1454
+ }
1455
+ } else if (string) {
1456
+ switch (step) {
1457
+ case 0: result = rb_funcall(frame, rb_intern_const("valid?"), 0); break;
1458
+ case 1: result = rb_funcall(frame, rb_intern_const("string?"), 0); break;
1459
+ case 2: result = rb_funcall(frame, rb_intern_const("prepare"), 0); break;
1460
+ default:
1461
+ result = step >= 8
1462
+ ? rb_funcall(frame, rb_intern_const("invalid_pattern?"), 0)
1463
+ : rb_funcall(frame, rb_intern_const("check"), 1, INT2FIX(step));
1464
+ break;
1465
+ }
1466
+ } else switch (step) {
1467
+ case 0: result = rb_funcall(frame, rb_intern_const("valid?"), 1, INT2FIX(selector & 63)); break;
1468
+ case 1: result = rb_funcall(frame, rb_intern_const("numeric?"), 0); break;
1469
+ case 2: result = rb_funcall(frame, rb_intern_const("integral?"), 0); break;
1470
+ case 3: result = rb_funcall(frame, rb_intern_const("prepare"), 0); break;
1471
+ default: result = rb_funcall(frame, rb_intern_const("check"), 1, INT2FIX(1 << (step - 4))); break;
1472
+ }
1473
+ RB_GC_GUARD(frame);
1474
+ return result;
1475
+ }
1476
+ /* Share Ruby type dispatch with ordinary runtime requests. Boolean and type
1477
+ * callbacks have no per-yield observations or scalar cache writes. */
1478
+ static VALUE type_predicate(VALUE instance, int selector) {
1479
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1480
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1481
+ VALUE helper = rb_const_get(internal, rb_intern_const("TypeChecks"));
1482
+ return rb_funcall(helper, rb_intern_const("valid?"), 3, instance,
1483
+ INT2FIX(selector >> 1), (selector & 1) ? Qtrue : Qfalse);
1484
+ }
1485
+ extern VALUE sn_properties_part(const void *, size_t, size_t *, size_t *);
1486
+ extern VALUE sn_properties_key(const void *, size_t, size_t);
1487
+ extern size_t sn_properties_item(const void *, size_t, size_t);
1488
+ extern void sn_properties_result(const void *, const runtime_locations *);
1489
+ static VALUE merge_runtime_properties(evaluation_call *call) {
1490
+ runner_holder *r = call->runner;
1491
+ VALUE args[6] = {Qnil, Qnil, Qnil, Qnil, Qnil, Qnil};
1492
+ for (size_t side = 0; side < 2; side++) {
1493
+ size_t properties, items;
1494
+ size_t at = side * 3;
1495
+ args[at] = sn_properties_part(r->caches, side, &properties, &items);
1496
+ args[at + 1] = rb_ary_new_capa(properties);
1497
+ args[at + 2] = rb_ary_new_capa(items);
1498
+ for (size_t i = 0; i < properties; i++)
1499
+ rb_ary_push(args[at + 1], sn_properties_key(r->caches, side, i));
1500
+ for (size_t i = 0; i < items; i++)
1501
+ rb_ary_push(args[at + 2], SIZET2NUM(sn_properties_item(r->caches, side, i)));
1502
+ }
1503
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1504
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1505
+ VALUE checks = rb_const_get(internal, rb_intern_const("ContainerChecks"));
1506
+ VALUE helper = rb_const_get(checks, rb_intern_const("Properties"));
1507
+ VALUE result = rb_funcallv(helper, rb_intern_const("merge"), 6, args);
1508
+ runtime_locations locations = read_runtime_locations(result);
1509
+ if (NIL_P(r->report_results)) r->report_results = rb_ary_new();
1510
+ rb_ary_push(r->report_results, result);
1511
+ sn_properties_result(r->caches, &locations);
1512
+ for (size_t i = 0; i < 6; i++) RB_GC_GUARD(args[i]);
1513
+ RB_GC_GUARD(result);
1514
+ return Qtrue;
1515
+ }
1516
+ typedef struct { evaluation_call *call; VALUE source, instance; int kind; } runtime_call;
1517
+ static VALUE call_runtime(VALUE context) {
1518
+ runtime_call *operation = (runtime_call *)context;
1519
+ evaluation_call *call = operation->call;
1520
+ VALUE source = operation->source, instance = operation->instance;
1521
+ runner_holder *r = call->runner;
1522
+ /* Staged frames keep Ruby checks at their VM method boundaries. An extra
1523
+ * check between assertions or error-rendering steps could overtake a later
1524
+ * Ruby exception. The native deadline continues across every retry. */
1525
+ int staged = (operation->kind >= SN_RUNTIME_STRING && operation->kind < SN_RUNTIME_EQUAL)
1526
+ || operation->kind >= SN_RUNTIME_ARRAY_HEADER || sn_caches_frame_error(r->caches)
1527
+ || (r->callback_context && (!r->continuation || r->scalar_mode))
1528
+ || operation->kind == SN_RUNTIME_SCALAR_CONTAINS
1529
+ || operation->kind == SN_RUNTIME_ARRAY_CHILDREN || operation->kind == SN_RUNTIME_OBJECT_CHILDREN
1530
+ || operation->kind == SN_RUNTIME_NATIVE_CHILD || operation->kind == SN_RUNTIME_NATIVE_REPORT
1531
+ || operation->kind == SN_RUNTIME_ARRAY_REPORT || operation->kind == SN_RUNTIME_OBJECT_REPORT || operation->kind == SN_RUNTIME_PATH_POP || operation->kind == SN_RUNTIME_PROPERTY_MERGE;
1532
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1533
+ if (operation->kind == SN_RUNTIME_PROPERTY_MERGE) return merge_runtime_properties(call);
1534
+ if (operation->kind == SN_RUNTIME_PATH_POP) {
1535
+ rb_funcall(Qnil, rb_intern_const("pop"), 0);
1536
+ return Qtrue;
1537
+ }
1538
+ if (operation->kind == SN_RUNTIME_CALLBACK_CHECKPOINT) {
1539
+ if (!NIL_P(call->budget)) rb_funcall(call->budget, rb_intern_const("checkpoint"), 0);
1540
+ return Qtrue;
1541
+ }
1542
+ if (operation->kind == SN_RUNTIME_ARRAY_REPORT || operation->kind == SN_RUNTIME_OBJECT_REPORT)
1543
+ return container_report_runtime(call, operation->kind == SN_RUNTIME_OBJECT_REPORT);
1544
+ if (operation->kind == SN_RUNTIME_NATIVE_REPORT) {
1545
+ contains_arguments arguments;
1546
+ sn_contains_arguments(r->caches, &arguments);
1547
+ report_holder *seed;
1548
+ VALUE seed_object = TypedData_Make_Struct(rb_cObject, report_holder, &report_type, seed);
1549
+ seed->report = NULL;
1550
+ seed->report = sn_report_seed(r->caches);
1551
+ VALUE callback = build_runner(r->owner, arguments.child);
1552
+ runner_holder *child;
1553
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, child);
1554
+ child->parent = callback_scope(call);
1555
+ child->callback_context = child->continuation = 1;
1556
+ child->scalar_mode = sn_caches_is_scalar(r->caches);
1557
+ child->report_seed = seed_object;
1558
+ child->report_sink = rb_ary_entry(r->active_report_state, 0);
1559
+ VALUE result = evaluate_runner(callback, instance, call->budget, 2);
1560
+ if (NIL_P(result)) rb_raise(rb_eRuntimeError, "native report continuation lost its coverage proof");
1561
+ if (NIL_P(r->report_results)) r->report_results = rb_ary_new();
1562
+ rb_ary_push(r->report_results, result);
1563
+ report_holder *completed;
1564
+ TypedData_Get_Struct(result, report_holder, &report_type, completed);
1565
+ sn_report_accept(r->caches, completed->report);
1566
+ RB_GC_GUARD(result);
1567
+ RB_GC_GUARD(seed_object);
1568
+ RB_GC_GUARD(callback);
1569
+ return Qtrue;
1570
+ }
1571
+ if (operation->kind == SN_RUNTIME_NATIVE_CHILD) {
1572
+ contains_arguments arguments;
1573
+ sn_contains_arguments(r->caches, &arguments);
1574
+ VALUE callback = build_runner(r->owner, arguments.child);
1575
+ runner_holder *child;
1576
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, child);
1577
+ child->parent = callback_scope(call);
1578
+ child->callback_context = child->continuation = 1;
1579
+ child->scalar_mode = sn_caches_is_scalar(r->caches);
1580
+ VALUE result = evaluate_runner(callback, instance, call->budget, 0);
1581
+ if (NIL_P(result)) rb_raise(rb_eRuntimeError, "native continuation lost its coverage proof");
1582
+ RB_GC_GUARD(callback);
1583
+ return result;
1584
+ }
1585
+ if (operation->kind == SN_RUNTIME_SCALAR_CONTAINS || operation->kind == SN_RUNTIME_ARRAY_CHILDREN
1586
+ || operation->kind == SN_RUNTIME_OBJECT_CHILDREN) {
1587
+ contains_arguments arguments;
1588
+ sn_contains_arguments(r->caches, &arguments);
1589
+ VALUE callback = build_runner(r->owner, arguments.child);
1590
+ runner_holder *child;
1591
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, child);
1592
+ child->parent = callback_scope(call);
1593
+ child->callback_kind = arguments.scalar_kind;
1594
+ child->callback_flags = arguments.scalar_flags;
1595
+ child->callback_context = 1;
1596
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1597
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1598
+ VALUE checks = rb_const_get(internal, rb_intern_const("ContainerChecks"));
1599
+ const char *frame_name = operation->kind == SN_RUNTIME_SCALAR_CONTAINS
1600
+ ? (NIL_P(call->budget) ? "ContainsFrame" : "LimitedContainsFrame")
1601
+ : operation->kind == SN_RUNTIME_ARRAY_CHILDREN
1602
+ ? (NIL_P(call->budget) ? "ArrayChildrenFrame" : "LimitedArrayChildrenFrame")
1603
+ : (NIL_P(call->budget) ? "ObjectChildrenFrame" : "LimitedObjectChildrenFrame");
1604
+ VALUE klass = rb_const_get(checks, rb_intern(frame_name));
1605
+ VALUE args[] = {arguments.rules, arguments.instance, call->budget, INT2FIX(arguments.required), callback};
1606
+ VALUE frame = rb_class_new_instance(5, args, klass);
1607
+ VALUE result = rb_funcall(frame, rb_intern_const("valid?"), 0);
1608
+ RB_GC_GUARD(callback);
1609
+ RB_GC_GUARD(frame);
1610
+ return result;
1611
+ }
1612
+ if (operation->kind >= SN_RUNTIME_ARRAY_HEADER ||
1613
+ (operation->kind >= SN_RUNTIME_NUMBER && operation->kind < SN_RUNTIME_EQUAL)) {
1614
+ VALUE result = scalar_step(r, operation->kind, operation->call->budget);
1615
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1616
+ return result;
1617
+ }
1618
+ if (operation->kind >= SN_RUNTIME_EQUAL) {
1619
+ scalar_arguments arguments;
1620
+ sn_scalar_arguments(r->caches, &arguments);
1621
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1622
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1623
+ VALUE helper = rb_const_get(internal, rb_intern_const("ValueEquality"));
1624
+ VALUE klass = rb_const_get(helper, rb_intern_const(NIL_P(call->budget) ? "Runtime" : "LimitedRuntime"));
1625
+ VALUE runtime = rb_class_new_instance(1, &call->budget, klass);
1626
+ VALUE result = rb_funcall(runtime, rb_intern_const("evaluate"), 3,
1627
+ INT2FIX(operation->kind - SN_RUNTIME_EQUAL), arguments.rules, arguments.instance);
1628
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1629
+ RB_GC_GUARD(runtime);
1630
+ return result;
1631
+ }
1632
+ if (operation->kind >= SN_RUNTIME_TYPE) {
1633
+ VALUE result = type_predicate(instance, operation->kind - SN_RUNTIME_TYPE);
1634
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1635
+ return result;
1636
+ }
1637
+ if (operation->kind == SN_RUNTIME_DIAGNOSTIC_MESSAGE) {
1638
+ VALUE message = build_message(r);
1639
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1640
+ return message;
1641
+ }
1642
+ if (operation->kind == SN_RUNTIME_REQUIRED_MISSING) {
1643
+ VALUE present = rb_funcallv_public(instance, rb_intern_const("key?"), 1, &source);
1644
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1645
+ return RTEST(present) ? Qfalse : Qtrue;
1646
+ }
1647
+ if (operation->kind >= SN_RUNTIME_NULL_NUMBER) {
1648
+ VALUE result = null_numeric(instance, operation->kind);
1649
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1650
+ return result;
1651
+ }
1652
+ if (operation->kind == SN_RUNTIME_IDENTITY_METHODS) {
1653
+ for (size_t index = 0; index < sizeof(identity_methods) / sizeof(*identity_methods); index++)
1654
+ if (!rb_method_basic_definition_p(source, identity_methods[index])) return Qfalse;
1655
+ return Qtrue;
1656
+ }
1657
+ if (operation->kind == SN_RUNTIME_POINTER) {
1658
+ VALUE pointer = build_pointer(r);
1659
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1660
+ return pointer;
1661
+ }
1662
+ if (operation->kind == SN_RUNTIME_JSON) {
1663
+ VALUE json = rb_const_get(rb_cObject, rb_intern_const("JSON"));
1664
+ rb_funcall(json, rb_intern_const("parse"), 1, instance);
1665
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1666
+ return Qtrue;
1667
+ }
1668
+ VALUE *regexps = NIL_P(call->budget) ? &r->regexps : &r->limited_regexps;
1669
+ if (NIL_P(*regexps)) *regexps = rb_hash_new();
1670
+ VALUE regexp = rb_hash_lookup2(*regexps, source, Qundef);
1671
+ if (regexp == Qundef) {
1672
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1673
+ VALUE internal = rb_const_get(schemurai, rb_intern_const("Internal"));
1674
+ VALUE compiler = rb_const_get(internal, rb_intern_const("EcmaRegexp"));
1675
+ regexp = rb_funcall(compiler, id_compile, 2, source, call->budget);
1676
+ rb_hash_aset(*regexps, source, regexp);
1677
+ }
1678
+ VALUE result = rb_funcall(regexp, rb_intern_const("match?"), 1, instance);
1679
+ if (!NIL_P(call->budget) && !staged) rb_funcall(call->budget, rb_intern_const("check_deadline!"), 0);
1680
+ RB_GC_GUARD(regexp);
1681
+ return result;
1682
+ }
1683
+ static void evaluate_runtime(evaluation_call *call, VALUE source, VALUE instance, int kind) {
1684
+ runtime_call operation = {call, source, instance, kind};
1685
+ VALUE previous = rb_errinfo();
1686
+ int state = 0;
1687
+ runner_holder *runner = call->runner;
1688
+ VALUE seed_object = collector_report_object();
1689
+ report_holder *seed;
1690
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
1691
+ seed->report = sn_report_live(runner->caches);
1692
+ report_context_call report_context = {runner->active_report_state, Qnil, Qnil, Qnil, 0, 0};
1693
+ if (seed->report) {
1694
+ report_context.trial = sn_report_silent(seed->report);
1695
+ report_context.collector = make_report_collector(runner->owner, callback_scope(call), seed_object, instance, runner->root);
1696
+ report_context.previous = rb_ary_entry(report_context.state, 0);
1697
+ rb_ary_store(report_context.state, 0, report_context.collector);
1698
+ if (NIL_P(runner->report_results)) runner->report_results = rb_ary_new();
1699
+ rb_ary_push(runner->report_results, report_context.collector);
1700
+ }
1701
+ VALUE result = rb_protect(call_runtime, (VALUE)&operation, &state);
1702
+ if (!NIL_P(report_context.collector)) {
1703
+ sn_report_effect(runner->caches, get_report_collector(report_context.collector)->report);
1704
+ restore_report_context((VALUE)&report_context);
1705
+ }
1706
+ RB_GC_GUARD(seed_object); RB_GC_GUARD(report_context.collector);
1707
+ if (sn_scopes_enabled(call->runner->caches))
1708
+ sn_scopes_accept(call->runner->caches, call->runner->active_scope_state);
1709
+ int verdict;
1710
+ if (state) {
1711
+ VALUE error = rb_errinfo();
1712
+ if (rb_obj_is_kind_of(error, rb_eRegexpError) && sn_caches_rescue_regexp(call->runner->caches)) {
1713
+ rb_set_errinfo(previous);
1714
+ RB_GC_GUARD(previous);
1715
+ return;
1716
+ }
1717
+ if (kind == SN_RUNTIME_JSON) {
1718
+ VALUE json = rb_const_get(rb_cObject, rb_intern_const("JSON"));
1719
+ VALUE parser_error = rb_const_get(json, rb_intern_const("ParserError"));
1720
+ if (!rb_obj_is_kind_of(error, rb_eArgError) && !rb_obj_is_kind_of(error, parser_error)) rb_jump_tag(state);
1721
+ verdict = SN_VERDICT_INVALID;
1722
+ } else {
1723
+ if (kind != SN_RUNTIME_REGEXP_ASSERTION || !rb_obj_is_kind_of(error, rb_eRegexpError)) rb_jump_tag(state);
1724
+ verdict = SN_REGEXP_INVALID;
1725
+ }
1726
+ rb_set_errinfo(previous);
516
1727
  } else {
517
- native_limits limits = read_limits(budget);
518
- h->report = sn_diagnose_with_limits(engine->engine, caches, root, instance, &status, &limits, &outcome);
1728
+ verdict = RTEST(result) ? SN_VERDICT_VALID : SN_VERDICT_INVALID;
1729
+ }
1730
+ runner_holder *r = call->runner;
1731
+ if (kind == SN_RUNTIME_DIAGNOSTIC_MESSAGE) {
1732
+ /* Keep results rooted even if another Ractor stops Rust while its
1733
+ * message table has an active mutable RefCell borrow. */
1734
+ if (NIL_P(r->messages)) r->messages = rb_ary_new();
1735
+ rb_ary_push(r->messages, result);
1736
+ sn_message_result(r->caches, result);
1737
+ RB_GC_GUARD(result);
1738
+ return;
1739
+ }
1740
+ if (kind == SN_RUNTIME_POINTER) {
1741
+ if (NIL_P(r->pointers)) r->pointers = rb_ary_new();
1742
+ rb_ary_push(r->pointers, result);
1743
+ sn_pointer_result(r->caches, result);
1744
+ RB_GC_GUARD(result);
1745
+ return;
1746
+ }
1747
+ st_data_t sources, bucket;
1748
+ if (!st_lookup(r->matches, (st_data_t)kind, &sources)) {
1749
+ sources = (st_data_t)st_init_numtable();
1750
+ st_insert(r->matches, (st_data_t)kind, sources);
1751
+ }
1752
+ if (!st_lookup((st_table *)sources, (st_data_t)source, &bucket)) {
1753
+ bucket = (st_data_t)st_init_numtable();
1754
+ st_insert((st_table *)sources, (st_data_t)source, bucket);
519
1755
  }
520
- if (status < 0) rb_raise(rb_eRuntimeError, "native diagnostics panicked");
521
- if (!NIL_P(budget)) finish_budget(budget, &outcome);
522
- if (status == SN_VERDICT_UNSUPPORTED) return Qnil;
523
- size_t count = sn_report_len(h->report);
1756
+ st_insert((st_table *)bucket, (st_data_t)instance, verdict);
1757
+ RB_GC_GUARD(previous);
1758
+ RB_GC_GUARD(source);
1759
+ RB_GC_GUARD(instance);
1760
+ }
1761
+ /* Each retry starts after Rust has returned and dropped all borrowed views,
1762
+ * memo entries and partial reports. Reached scalar methods use occurrence keys.
1763
+ * The original Ruby deadline survives every pass and runtime operation. */
1764
+ static VALUE retry_runtime(VALUE context) {
1765
+ evaluation_call *call = (evaluation_call *)context;
1766
+ VALUE source = Qnil, instance = Qnil;
1767
+ int operation = 0;
1768
+ /* Reordered fast scans may have requested an operation the VM would never
1769
+ * reach. Replay in observable order before invoking any Ruby method. */
1770
+ sn_caches_observe(call->runner->caches);
1771
+ evaluate_native(call);
1772
+ while (call->status == SN_VERDICT_UNSUPPORTED &&
1773
+ sn_caches_pending(call->runner->caches, &source, &instance, &operation)) {
1774
+ if (sn_caches_take_reorder(call->runner->caches)) {
1775
+ sn_caches_observe(call->runner->caches);
1776
+ evaluate_native(call);
1777
+ continue;
1778
+ }
1779
+ evaluate_runtime(call, source, instance, operation);
1780
+ sn_caches_runtime(call->runner->caches, call->runner->matches);
1781
+ evaluate_native(call);
1782
+ }
1783
+ return Qnil;
1784
+ }
1785
+ static VALUE report_rows(const void *report, VALUE budget) {
1786
+ size_t count = sn_report_len(report);
524
1787
  VALUE output = rb_ary_new_capa(count);
525
1788
  for (size_t index = 0; index < count; index++) {
526
1789
  if (!NIL_P(budget) && index % 256 == 0) rb_funcall(budget, rb_intern("check_deadline!"), 0);
527
1790
  diagnostic_record r;
528
- sn_report_record(h->report, index, &r);
529
- VALUE row = rb_ary_new_capa(7);
1791
+ sn_report_record(report, index, &r);
1792
+ VALUE row = rb_ary_new_capa(NIL_P(r.message) ? 7 : 8);
530
1793
  rb_ary_push(output, row);
531
1794
  rb_ary_push(row, rb_utf8_str_new((const char *)r.keyword, r.keyword_len));
532
- rb_ary_push(row, rb_utf8_str_new((const char *)r.instance_path, r.instance_len));
533
- rb_ary_push(row, rb_utf8_str_new((const char *)r.schema_path, r.schema_len));
1795
+ rb_ary_push(row, NIL_P(r.instance_pointer) ? rb_utf8_str_new((const char *)r.instance_path, r.instance_len) : rb_str_dup(r.instance_pointer));
1796
+ rb_ary_push(row, NIL_P(r.schema_pointer) ? rb_utf8_str_new((const char *)r.schema_path, r.schema_len) : rb_str_dup(r.schema_pointer));
534
1797
  rb_ary_push(row, INT2NUM(r.opcode));
535
1798
  rb_ary_push(row, r.operand);
536
1799
  rb_ary_push(row, r.instance);
537
1800
  rb_ary_push(row, SIZET2NUM(r.detail));
1801
+ if (!NIL_P(r.message)) rb_ary_push(row, rb_str_dup(r.message));
1802
+ }
1803
+ return output;
1804
+ }
1805
+ static VALUE evaluate_runner_body(VALUE context) {
1806
+ evaluation_call call = *(evaluation_call *)context;
1807
+ VALUE self = call.self, instance = call.instance, budget = call.budget;
1808
+ runner_holder *r = call.runner;
1809
+ int diagnostics = call.diagnostics;
1810
+ report_holder *h = NULL;
1811
+ VALUE owner = Qnil;
1812
+ if (diagnostics) {
1813
+ owner = TypedData_Make_Struct(rb_cObject, report_holder, &report_type, h);
1814
+ h->report = NULL;
1815
+ call.report = h;
1816
+ }
1817
+ clear_runtime((VALUE)&call);
1818
+ r->active_budget = budget;
1819
+ r->report_sink_done = 0;
1820
+ if (!NIL_P(r->report_seed)) {
1821
+ report_holder *seed;
1822
+ TypedData_Get_Struct(r->report_seed, report_holder, &report_type, seed);
1823
+ sn_report_inherit(r->caches, seed->report);
1824
+ }
1825
+ if (!NIL_P(r->error_frame)) {
1826
+ r->frames = rb_ary_new_from_args(1, r->error_frame);
1827
+ st_insert(r->frame_indices, (st_data_t)INT2FIX(0), 0);
1828
+ }
1829
+ if (!NIL_P(r->parent)) {
1830
+ runner_holder *parent;
1831
+ TypedData_Get_Struct(r->parent, runner_holder, &runner_type, parent);
1832
+ copy_states(r, parent, r->callback_context, 0);
1833
+ if (r->callback_context) {
1834
+ call.limits = parent->active_limits;
1835
+ call.limits_read = 1;
1836
+ sn_caches_child(r->caches, parent->caches, !NIL_P(budget), !r->continuation || r->scalar_mode);
1837
+ }
1838
+ }
1839
+ int scoped = sn_reference_context(call.engine->engine);
1840
+ if (r->callback_context) {
1841
+ runner_holder *scope;
1842
+ VALUE object = callback_scope(&call);
1843
+ TypedData_Get_Struct(object, runner_holder, &runner_type, scope);
1844
+ if (scoped && NIL_P(scope->scope_state)) scope->scope_state = rb_ary_new();
1845
+ r->active_scope_state = scope->scope_state;
1846
+ r->active_report_state = scope->report_state;
1847
+ if (scoped && NIL_P(scope->ref_state)) scope->ref_state = rb_ary_new();
1848
+ r->active_ref_state = scope->ref_state;
1849
+ r->active_vm_state = scope->vm_active;
1850
+ } else if (NIL_P(budget)) {
1851
+ if (NIL_P(r->report_state)) r->report_state = rb_ary_new();
1852
+ r->active_report_state = r->report_state;
1853
+ VALUE current_report = rb_ary_entry(r->active_report_state, 0);
1854
+ if (!NIL_P(current_report)) {
1855
+ if (rb_typeddata_is_kind_of(current_report, &report_collector_type)) {
1856
+ report_collector *collector = get_report_collector(current_report);
1857
+ collector->busy = 1;
1858
+ sn_report_clear_collection(collector->report, !diagnostics);
1859
+ collector->busy = 0;
1860
+ } else {
1861
+ rb_funcall(current_report, rb_intern_const("native_clear_collection"), 1, diagnostics ? Qtrue : Qfalse);
1862
+ }
1863
+ }
1864
+ rb_ary_store(r->active_report_state, 0, Qnil);
1865
+ VALUE pool = rb_ary_entry(r->active_report_state, 1);
1866
+ if (!NIL_P(pool)) rb_funcall(pool, rb_intern_const("reset"), 0);
1867
+ if (scoped && NIL_P(r->scope_state)) r->scope_state = rb_ary_new();
1868
+ r->active_scope_state = r->scope_state;
1869
+ if (scoped && NIL_P(r->ref_state)) r->ref_state = rb_ary_new();
1870
+ r->active_ref_state = r->ref_state;
1871
+ r->active_vm_state = r->vm_active;
1872
+ if (!NIL_P(r->active_scope_state)) rb_ary_clear(r->active_scope_state);
1873
+ } else {
1874
+ r->active_report_state = rb_ary_new();
1875
+ r->active_scope_state = scoped ? rb_ary_new() : Qnil;
1876
+ r->active_ref_state = scoped ? rb_ary_new() : Qnil;
1877
+ r->active_vm_state = Qnil;
1878
+ }
1879
+ sn_scopes_inherit(r->caches, r->active_scope_state, r->active_ref_state, r->active_vm_state, scoped);
1880
+ VALUE pool = rb_ary_entry(r->active_report_state, 1);
1881
+ if (!NIL_P(pool) && RTEST(rb_funcall(pool, rb_intern_const("used?"), 0)))
1882
+ sn_caches_reuse_buffers(r->caches);
1883
+ evaluate_native(&call);
1884
+ VALUE source = Qnil, requested = Qnil;
1885
+ int operation = 0;
1886
+ if (call.status == SN_VERDICT_UNSUPPORTED &&
1887
+ sn_caches_pending(r->caches, &source, &requested, &operation)) {
1888
+ retry_runtime((VALUE)&call);
1889
+ }
1890
+ if (call.status == SN_VERDICT_UNSUPPORTED)
1891
+ rb_raise(rb_eRuntimeError, "native execution lost its coverage proof");
1892
+ if (!diagnostics) {
1893
+ if (call.status != SN_VERDICT_UNSUPPORTED && !NIL_P(r->report_sink)) {
1894
+ VALUE completed = collector_report_object();
1895
+ report_holder *h;
1896
+ TypedData_Get_Struct(completed, report_holder, &report_type, h);
1897
+ h->report = sn_callback_report_result(r->caches, call.status);
1898
+ collector_adopt(get_report_collector(r->report_sink), completed);
1899
+ r->report_sink_done = 1;
1900
+ RB_GC_GUARD(completed);
1901
+ }
1902
+ VALUE result = call.status ? Qtrue : Qfalse;
1903
+ RB_GC_GUARD(owner);
1904
+ RB_GC_GUARD(instance);
1905
+ RB_GC_GUARD(self);
1906
+ return result;
538
1907
  }
1908
+ if (diagnostics == 2) {
1909
+ r->report_sink_done = 1;
1910
+ RB_GC_GUARD(instance);
1911
+ RB_GC_GUARD(self);
1912
+ return owner;
1913
+ }
1914
+ if (sn_report_silent(h->report)) {
1915
+ RB_GC_GUARD(owner);
1916
+ return Qfalse;
1917
+ }
1918
+ VALUE output = report_rows(h->report, budget);
539
1919
  RB_GC_GUARD(owner);
540
1920
  RB_GC_GUARD(instance);
541
1921
  RB_GC_GUARD(self);
542
1922
  return output;
543
1923
  }
1924
+ static VALUE finish_evaluation(VALUE context) {
1925
+ evaluation_call *call = (evaluation_call *)context;
1926
+ runner_holder *runner = call->runner;
1927
+ if (!NIL_P(runner->report_sink) && !runner->report_sink_done) {
1928
+ VALUE interrupted = collector_report_object();
1929
+ report_holder *partial;
1930
+ TypedData_Get_Struct(interrupted, report_holder, &report_type, partial);
1931
+ partial->report = sn_report_interrupted(runner->caches);
1932
+ if (partial->report) collector_adopt(get_report_collector(runner->report_sink), interrupted);
1933
+ RB_GC_GUARD(interrupted);
1934
+ }
1935
+ publish_references(runner, 1);
1936
+ size_t unwind = sn_scopes_unwind(runner->caches);
1937
+ while (unwind-- > 0) rb_ary_pop(runner->active_scope_state);
1938
+ publish_states(runner);
1939
+ close_callback_scope(runner);
1940
+ clear_runtime(context);
1941
+ runner->active_budget = Qnil;
1942
+ runner->active_scope_state = Qnil;
1943
+ runner->active_report_state = Qnil;
1944
+ runner->active_ref_state = runner->active_vm_state = Qnil;
1945
+ runner->active_limits = (native_limits){0};
1946
+ runner->busy = 0;
1947
+ return Qnil;
1948
+ }
1949
+ static VALUE evaluate_runner(VALUE self, VALUE instance, VALUE budget, int diagnostics) {
1950
+ runner_holder *runner;
1951
+ TypedData_Get_Struct(self, runner_holder, &runner_type, runner);
1952
+ if (runner->busy) {
1953
+ VALUE nested = build_runner(runner->owner, runner->root);
1954
+ runner_holder *child;
1955
+ TypedData_Get_Struct(nested, runner_holder, &runner_type, child);
1956
+ child->parent = self;
1957
+ child->callback_context = runner->callback_context;
1958
+ child->continuation = runner->continuation;
1959
+ child->scalar_mode = runner->scalar_mode;
1960
+ child->report_seed = runner->report_seed;
1961
+ child->report_sink = runner->report_sink;
1962
+ child->error_frame = runner->error_frame;
1963
+ child->scope_state = runner->scope_state;
1964
+ child->report_state = runner->report_state;
1965
+ child->ref_state = runner->ref_state;
1966
+ child->vm_active = runner->vm_active;
1967
+ VALUE result = evaluate_runner(nested, instance, budget, diagnostics);
1968
+ RB_GC_GUARD(nested);
1969
+ return result;
1970
+ }
1971
+ evaluation_call call = {self, instance, budget, runner, ready_program(runner->owner), NULL, 0, {0}, 0, diagnostics};
1972
+ runner->busy = 1;
1973
+ VALUE result = rb_ensure(evaluate_runner_body, (VALUE)&call, finish_evaluation, (VALUE)&call);
1974
+ RB_GC_GUARD(self);
1975
+ return result;
1976
+ }
1977
+ typedef struct {
1978
+ VALUE target, state, collector, callback, instance, budget, object_args;
1979
+ size_t baseline;
1980
+ } compatibility_report_call;
1981
+ static VALUE call_compatibility_report(VALUE context) {
1982
+ compatibility_report_call *call = (compatibility_report_call *)context;
1983
+ rb_ary_store(call->state, 0, call->collector);
1984
+ if (!NIL_P(call->object_args)) {
1985
+ VALUE args[7];
1986
+ for (long i = 0; i < 7; i++) args[i] = rb_ary_entry(call->object_args, i + 1);
1987
+ return rb_funcallv(rb_ary_entry(call->object_args, 0), rb_intern_const("evaluate_object"), 7, args);
1988
+ }
1989
+ return evaluate_runner(call->callback, call->instance, call->budget, 0);
1990
+ }
1991
+ static VALUE finish_compatibility_report(VALUE context) {
1992
+ compatibility_report_call *call = (compatibility_report_call *)context;
1993
+ report_collector *collector = get_report_collector(call->collector);
1994
+ VALUE rows = report_rows(collector->report, Qnil);
1995
+ VALUE schemurai = rb_const_get(rb_cObject, id_schemurai);
1996
+ VALUE native = rb_const_get(schemurai, rb_intern_const("Native"));
1997
+ VALUE diagnostics = rb_const_get(native, rb_intern_const("Diagnostics"));
1998
+ VALUE errors = rb_ary_new_capa(RARRAY_LEN(rows));
1999
+ for (long i = 0; i < RARRAY_LEN(rows); i++)
2000
+ rb_ary_push(errors, rb_funcall(diagnostics, rb_intern_const("error"), 1, rb_ary_entry(rows, i)));
2001
+ size_t count = sn_report_context_count(collector->report, call->baseline);
2002
+ rb_funcall(call->target, rb_intern_const("native_append_report"), 3, errors, SIZET2NUM(count),
2003
+ INT2NUM(sn_report_context_reset(collector->report)));
2004
+ RB_GC_GUARD(rows); RB_GC_GUARD(errors);
2005
+ return Qnil;
2006
+ }
2007
+ static VALUE make_compatibility_collector(VALUE owner, VALUE scope, size_t node, VALUE target, VALUE instance, size_t *baseline) {
2008
+ VALUE data = rb_funcall(target, rb_intern_const("native_report_context"), 0);
2009
+ *baseline = NUM2SIZET(rb_ary_entry(data, 1));
2010
+ VALUE seed_object = collector_report_object();
2011
+ report_holder *seed;
2012
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
2013
+ seed->report = sn_report_context(NIL_P(rb_ary_entry(data, 0)), *baseline,
2014
+ rb_ary_entry(data, 2), rb_ary_entry(data, 3));
2015
+ VALUE collector = make_report_collector(owner, scope, seed_object, instance, node);
2016
+ RB_GC_GUARD(data); RB_GC_GUARD(seed_object);
2017
+ return collector;
2018
+ }
2019
+ static VALUE compatibility_object_callback(report_collector *c, VALUE target, VALUE args) {
2020
+ runner_holder *scope;
2021
+ TypedData_Get_Struct(c->scope, runner_holder, &runner_type, scope);
2022
+ size_t baseline;
2023
+ VALUE collector = make_compatibility_collector(c->owner, c->scope, c->node, target, rb_ary_entry(args, 2), &baseline);
2024
+ compatibility_report_call call = {target, scope->report_state, collector, Qnil, Qnil, Qnil, args, baseline};
2025
+ VALUE result = rb_ensure(call_compatibility_report, (VALUE)&call, finish_compatibility_report, (VALUE)&call);
2026
+ RB_GC_GUARD(collector); RB_GC_GUARD(args);
2027
+ return result;
2028
+ }
2029
+ static VALUE compatibility_report_callback(runner_holder *recipe, runner_holder *scope, VALUE target, VALUE instance) {
2030
+ size_t baseline;
2031
+ VALUE collector = make_compatibility_collector(recipe->owner, recipe->parent, recipe->root, target, instance, &baseline);
2032
+ VALUE seed_object = get_report_collector(collector)->seed;
2033
+ VALUE callback = build_runner(recipe->owner, recipe->root);
2034
+ runner_holder *child;
2035
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, child);
2036
+ child->parent = recipe->parent;
2037
+ child->callback_context = 1;
2038
+ child->report_seed = seed_object;
2039
+ child->report_sink = collector;
2040
+ compatibility_report_call call = {target, scope->report_state, collector, callback, instance, scope->active_budget, Qnil, baseline};
2041
+ VALUE result = rb_ensure(call_compatibility_report, (VALUE)&call, finish_compatibility_report, (VALUE)&call);
2042
+ RB_GC_GUARD(seed_object); RB_GC_GUARD(collector); RB_GC_GUARD(callback);
2043
+ return result;
2044
+ }
2045
+ /* A recipe can outlive the method that received its block. Each invocation
2046
+ * owns observations, while its evaluator's budget/cache scope is shared. */
2047
+ static VALUE scalar_callback(VALUE self, VALUE instance) {
2048
+ runner_holder *recipe, *parent;
2049
+ TypedData_Get_Struct(self, runner_holder, &runner_type, recipe);
2050
+ if (NIL_P(recipe->parent) || !recipe->callback_context) rb_raise(rb_eRuntimeError, "not a scalar callback");
2051
+ TypedData_Get_Struct(recipe->parent, runner_holder, &runner_type, parent);
2052
+ /* LimitedEvaluator's retained block reaches its cleared budget first. */
2053
+ if (parent->closed_limited) rb_funcall(Qnil, rb_intern_const("checkpoint"), 0);
2054
+ if (parent->scope_owner && !NIL_P(parent->parent)) {
2055
+ runner_holder *owner;
2056
+ TypedData_Get_Struct(parent->parent, runner_holder, &runner_type, owner);
2057
+ copy_states(parent, owner, !NIL_P(parent->active_budget), 0);
2058
+ }
2059
+ VALUE budget = parent->active_budget;
2060
+ if (!NIL_P(budget) && recipe->callback_kind != SN_CALLBACK_RUNTIME) {
2061
+ /* Direct Boolean/type dispatch has one entry and one instruction. */
2062
+ rb_funcall(budget, rb_intern_const("checkpoint"), 0);
2063
+ rb_funcall(budget, rb_intern_const("checkpoint"), 0);
2064
+ }
2065
+ VALUE result;
2066
+ VALUE current = NIL_P(parent->report_state) ? Qnil : rb_ary_entry(parent->report_state, 0);
2067
+ if (!NIL_P(current) && recipe->callback_kind == SN_CALLBACK_RUNTIME
2068
+ && !rb_typeddata_is_kind_of(current, &report_collector_type)) {
2069
+ result = compatibility_report_callback(recipe, parent, current, instance);
2070
+ } else if (!NIL_P(current) && recipe->callback_kind == SN_CALLBACK_RUNTIME) {
2071
+ report_collector *collector = get_report_collector(current);
2072
+ VALUE seed_object = collector_report_object();
2073
+ report_holder *seed;
2074
+ TypedData_Get_Struct(seed_object, report_holder, &report_type, seed);
2075
+ seed->report = sn_report_callback_seed(collector->report);
2076
+ VALUE callback = build_runner(recipe->owner, recipe->root);
2077
+ runner_holder *child;
2078
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, child);
2079
+ child->parent = recipe->parent;
2080
+ child->callback_context = 1;
2081
+ child->report_seed = seed_object;
2082
+ child->report_sink = current;
2083
+ result = evaluate_runner(callback, instance, budget, 0);
2084
+ RB_GC_GUARD(current); RB_GC_GUARD(callback); RB_GC_GUARD(seed_object);
2085
+ } else switch (recipe->callback_kind) {
2086
+ case SN_CALLBACK_BOOLEAN: result = recipe->callback_flags ? Qtrue : Qfalse; break;
2087
+ case SN_CALLBACK_TYPE: result = RTEST(type_predicate(instance, recipe->callback_flags)) ? Qtrue : Qfalse; break;
2088
+ default: result = evaluate_runner(self, instance, budget, 0); break;
2089
+ }
2090
+ if (NIL_P(result)) rb_raise(rb_eRuntimeError, "scalar callback lost its native coverage proof");
2091
+ if (!NIL_P(budget) && sn_caches_deadline_expired(parent->caches))
2092
+ rb_funcall(budget, rb_intern_const("raise_native_limit"), 2, INT2FIX(2), INT2FIX(0));
2093
+ RB_GC_GUARD(self);
2094
+ return result;
2095
+ }
2096
+ /* Ruby selects the child using the original rules and indexed methods. The
2097
+ * compiled Program is an opaque identity key, never interpreted in Ruby. */
2098
+ static VALUE runner_evaluate_child(VALUE self, VALUE program, VALUE instance) {
2099
+ runner_holder *factory;
2100
+ TypedData_Get_Struct(self, runner_holder, &runner_type, factory);
2101
+ if (NIL_P(factory->parent) || !factory->callback_context)
2102
+ rb_raise(rb_eRuntimeError, "not a native child factory");
2103
+ if (NIL_P(factory->child_callbacks)) factory->child_callbacks = rb_hash_new();
2104
+ VALUE callback = rb_hash_lookup2(factory->child_callbacks, program, Qundef);
2105
+ if (callback == Qundef) {
2106
+ holder *owner = ready_program(factory->owner);
2107
+ size_t index = sn_node_index(owner->engine, program);
2108
+ if (index == SIZE_MAX) rb_raise(rb_eRuntimeError, "native child was not compiled");
2109
+ callback = build_runner(factory->owner, index);
2110
+ runner_holder *child;
2111
+ TypedData_Get_Struct(callback, runner_holder, &runner_type, child);
2112
+ child->parent = factory->parent;
2113
+ child->callback_context = 1;
2114
+ child->callback_kind = sn_callback_kind(owner->engine, index, &child->callback_flags);
2115
+ rb_hash_aset(factory->child_callbacks, program, callback);
2116
+ }
2117
+ VALUE result = scalar_callback(callback, instance);
2118
+ RB_GC_GUARD(self);
2119
+ return result;
2120
+ }
2121
+ static runner_holder *factory_scope(VALUE self) {
2122
+ runner_holder *factory, *scope;
2123
+ TypedData_Get_Struct(self, runner_holder, &runner_type, factory);
2124
+ if (NIL_P(factory->parent) || !factory->callback_context)
2125
+ rb_raise(rb_eRuntimeError, "not a native child factory");
2126
+ TypedData_Get_Struct(factory->parent, runner_holder, &runner_type, scope);
2127
+ return scope;
2128
+ }
2129
+ static VALUE runner_invocation_budget(VALUE self) {
2130
+ return factory_scope(self)->active_budget;
2131
+ }
2132
+ static VALUE runner_regexp_cache(VALUE self) {
2133
+ runner_holder *scope = factory_scope(self);
2134
+ VALUE *slot = NIL_P(scope->active_budget) ? &scope->regexps : &scope->limited_regexps;
2135
+ if (NIL_P(*slot)) *slot = rb_hash_new();
2136
+ publish_states(scope);
2137
+ return *slot;
2138
+ }
544
2139
  static VALUE diagnostics(VALUE self, VALUE instance) {
545
2140
  holder *h = ready_program(self);
546
- return collect_diagnostics(self, instance, h, NULL, sn_root_index(h->engine), Qnil);
2141
+ VALUE run = build_runner(self, sn_root_index(h->engine));
2142
+ return evaluate_runner(run, instance, Qnil, 1);
547
2143
  }
548
2144
  static VALUE runner_diagnostics(VALUE self, VALUE instance) {
549
- runner_holder *r;
550
- TypedData_Get_Struct(self, runner_holder, &runner_type, r);
551
- holder *h = ready_program(r->owner);
552
- return collect_diagnostics(self, instance, h, r->caches, r->root, Qnil);
2145
+ return evaluate_runner(self, instance, Qnil, 1);
553
2146
  }
554
2147
  static VALUE runner_diagnostics_with_budget(VALUE self, VALUE instance, VALUE budget) {
555
- runner_holder *r;
556
- TypedData_Get_Struct(self, runner_holder, &runner_type, r);
557
- holder *h = ready_program(r->owner);
558
- return collect_diagnostics(self, instance, h, r->caches, r->root, budget);
2148
+ return evaluate_runner(self, instance, budget, 1);
559
2149
  }
560
2150
  void Init_schemurai_native(void) {
561
2151
  /* Cache fixed IDs explicitly, independent of compiler-specific macros. */
2152
+ identity_methods[0] = rb_intern_const("is_a?");
2153
+ identity_methods[1] = rb_intern_const("class");
2154
+ identity_methods[2] = rb_intern_const("instance_of?");
2155
+ identity_methods[3] = rb_intern_const("==");
2156
+ identity_methods[4] = rb_intern_const("hash");
562
2157
  id_resolve = rb_intern_const("resolve");
563
2158
  id_schemurai = rb_intern_const("Schemurai");
564
2159
  id_resolution_error = rb_intern_const("ResolutionError");
@@ -589,8 +2184,31 @@ void Init_schemurai_native(void) {
589
2184
  rb_define_method(klass, "diagnostics", diagnostics, 1);
590
2185
  rb_define_method(klass, "make_shareable", make_shareable, 0);
591
2186
  rb_define_method(klass, "runner", runner, 1);
2187
+ rb_define_method(klass, "unresolved_references?", has_unresolved_references, 0);
2188
+ VALUE collector_class = rb_define_class_under(native, "ReportCollector", rb_cObject);
2189
+ rb_undef_alloc_func(collector_class);
2190
+ rb_define_method(collector_class, "evaluate", collector_evaluate, 5);
2191
+ rb_define_method(collector_class, "trial", collector_trial, 2);
2192
+ rb_define_method(collector_class, "error", collector_error, 2);
2193
+ rb_define_method(collector_class, "record_item", collector_record, 1);
2194
+ rb_define_method(collector_class, "include_item?", collector_contains, 2);
2195
+ rb_define_method(collector_class, "silent?", collector_silent, 0);
2196
+ rb_define_method(collector_class, "finish", collector_finish, 2);
2197
+ rb_define_method(collector_class, "evaluation_buffer", collector_evaluation_buffer, 0);
2198
+ rb_define_method(collector_class, "properties", collector_properties, 1);
2199
+ rb_define_method(collector_class, "evaluate_object", collector_object_evaluate, 7);
2200
+ rb_define_method(collector_class, "object_error", collector_object_error, 2);
2201
+ rb_define_method(collector_class, "finish_object", collector_finish, 2);
2202
+ rb_define_method(collector_class, "invocation_budget", collector_invocation_budget, 0);
2203
+ rb_define_method(collector_class, "regexp_cache", collector_regexp_cache, 0);
592
2204
  VALUE runner_class = rb_define_class_under(klass, "Runner", rb_cObject);
593
2205
  rb_undef_alloc_func(runner_class);
2206
+ rb_define_method(runner_class, "call", scalar_callback, 1);
2207
+ rb_define_method(runner_class, "evaluate_child", runner_evaluate_child, 2);
2208
+ rb_define_method(runner_class, "invocation_budget", runner_invocation_budget, 0);
2209
+ rb_define_method(runner_class, "regexp_cache", runner_regexp_cache, 0);
2210
+ rb_define_method(runner_class, "share_scalar_state", runner_share_scalar_state, 6);
2211
+ rb_define_method(runner_class, "reference_active?", runner_reference_active, 2);
594
2212
  rb_define_method(runner_class, "valid?", runner_valid, 1);
595
2213
  rb_define_method(runner_class, "valid_with_budget", runner_valid_with_budget, 2);
596
2214
  rb_define_method(runner_class, "diagnostics", runner_diagnostics, 1);
@@ -695,7 +2313,6 @@ static int string_utf8(VALUE value) {
695
2313
  int encoding = RB_ENCODING_GET(value);
696
2314
  return encoding == utf8_index ? SN_ENCODING_UTF8 : (encoding == ascii_index ? SN_ENCODING_ASCII : SN_ENCODING_OTHER);
697
2315
  }
698
- int sn_string_utf8(VALUE value) { return string_utf8(value); }
699
2316
  static int string_view(VALUE value, const unsigned char **data, size_t *length) {
700
2317
  int encoding = string_utf8(value);
701
2318
  string_raw_view(value, data, length);