schemurai 3.0.1 → 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.
- checksums.yaml +4 -4
- data/README.md +6 -4
- data/ext/schemurai_native/Cargo.lock +48 -1
- data/ext/schemurai_native/Cargo.toml +3 -1
- data/ext/schemurai_native/extconf.rb +1 -1
- data/ext/schemurai_native/glue.c +1679 -62
- data/ext/schemurai_native/protocol.def +45 -0
- data/ext/schemurai_native/src/budget.rs +49 -0
- data/ext/schemurai_native/src/container.rs +460 -0
- data/ext/schemurai_native/src/content.rs +56 -27
- data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
- data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
- data/ext/schemurai_native/src/diagnostics.rs +1087 -307
- data/ext/schemurai_native/src/enumeration.rs +6 -1
- data/ext/schemurai_native/src/evaluation.rs +49 -20
- data/ext/schemurai_native/src/format.rs +8 -6
- data/ext/schemurai_native/src/graph.rs +37 -0
- data/ext/schemurai_native/src/instance_graph.rs +90 -0
- data/ext/schemurai_native/src/lib.rs +910 -237
- data/ext/schemurai_native/src/memo.rs +11 -38
- data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
- data/ext/schemurai_native/src/number.rs +250 -59
- data/ext/schemurai_native/src/pattern.rs +113 -101
- data/ext/schemurai_native/src/pointer.rs +97 -0
- data/ext/schemurai_native/src/runtime/properties.rs +162 -0
- data/ext/schemurai_native/src/runtime/reports.rs +266 -0
- data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
- data/ext/schemurai_native/src/runtime.rs +1020 -0
- data/ext/schemurai_native/src/scope.rs +60 -16
- data/ext/schemurai_native/src/string.rs +68 -0
- data/ext/schemurai_native/src/tracking.rs +244 -174
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +0 -12
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +9 -48
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/native/diagnostics.rb +4 -3
- data/lib/schemurai/native/evaluator.rb +38 -10
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +18 -26
- data/lib/schemurai/vm/evaluator.rb +87 -545
- data/lib/schemurai/vm/limited_evaluator.rb +7 -22
- metadata +24 -2
data/ext/schemurai_native/glue.c
CHANGED
|
@@ -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
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
95
|
-
|
|
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 {
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
963
|
+
VALUE object = TypedData_Make_Struct(rb_cObject, report_holder, &report_type, h);
|
|
511
964
|
h->report = NULL;
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|