@scriptc/runtime 0.0.5 → 0.0.6
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.
- package/package.json +1 -1
- package/src/scr_assert.c +31 -0
- package/src/scr_inspect.c +82 -0
- package/src/scr_json.c +146 -0
- package/src/scr_runtime.h +52 -1
package/package.json
CHANGED
package/src/scr_assert.c
CHANGED
|
@@ -150,6 +150,37 @@ void scr_assert_ok(bool pass, ScrStr *message) {
|
|
|
150
150
|
|
|
151
151
|
/* SameValue over doubles — Object.is, the comparison of strictEqual AND
|
|
152
152
|
* deepStrictEqual for numbers (NaN equals NaN; +0 and -0 differ). */
|
|
153
|
+
/* ── deepStrictEqual over cyclic values ────────────────────────────────
|
|
154
|
+
* RECURSIVE record types permit reference cycles; Node's deep equality
|
|
155
|
+
* memoizes (value1, value2) pairs so equal cyclic structures compare
|
|
156
|
+
* true instead of recursing forever. The compiler-emitted per-type
|
|
157
|
+
* helpers over cycle-capable types wrap their walks in enter/leave: a
|
|
158
|
+
* PAIR already being compared answers equal (the coinductive step —
|
|
159
|
+
* Node's memo behavior exactly). The stack is global (comparisons never
|
|
160
|
+
* interleave; the emitted walks cannot throw mid-compare). */
|
|
161
|
+
static struct { const void *a, *b; } *g_deq_stack;
|
|
162
|
+
static size_t g_deq_len;
|
|
163
|
+
static size_t g_deq_cap;
|
|
164
|
+
|
|
165
|
+
bool scr_assert_deq_enter(const void *a, const void *b) {
|
|
166
|
+
for (size_t i = 0; i < g_deq_len; i++) {
|
|
167
|
+
if (g_deq_stack[i].a == a && g_deq_stack[i].b == b) return true;
|
|
168
|
+
}
|
|
169
|
+
if (g_deq_len == g_deq_cap) {
|
|
170
|
+
g_deq_cap = g_deq_cap ? g_deq_cap * 2 : 8;
|
|
171
|
+
g_deq_stack = realloc(g_deq_stack, g_deq_cap * sizeof *g_deq_stack);
|
|
172
|
+
if (!g_deq_stack) scr_assert_oom();
|
|
173
|
+
}
|
|
174
|
+
g_deq_stack[g_deq_len].a = a;
|
|
175
|
+
g_deq_stack[g_deq_len].b = b;
|
|
176
|
+
g_deq_len++;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
void scr_assert_deq_leave(void) {
|
|
181
|
+
if (g_deq_len > 0) g_deq_len--;
|
|
182
|
+
}
|
|
183
|
+
|
|
153
184
|
bool scr_assert_same_value_f64(double a, double b) {
|
|
154
185
|
if (a == b) return a != 0.0 || signbit(a) == signbit(b);
|
|
155
186
|
return isnan(a) && isnan(b);
|
package/src/scr_inspect.c
CHANGED
|
@@ -245,10 +245,13 @@ static double g_cur_depth; /* ctx.currentDepth (last-entered composite) */
|
|
|
245
245
|
|
|
246
246
|
static InspFrame *insp_top(void) { return &g_frames[g_nframes - 1]; }
|
|
247
247
|
|
|
248
|
+
static void insp_circ_reset(void); /* circular-reference state, below */
|
|
249
|
+
|
|
248
250
|
/* Begin a non-empty composite: formatRaw's `recurseTimes += 1;
|
|
249
251
|
* ctx.currentDepth = recurseTimes` plus the uniform +2 the children
|
|
250
252
|
* format under. `recurse` is the CHILDREN's recursion depth. */
|
|
251
253
|
void scr_insp_begin(double recurse) {
|
|
254
|
+
if (recurse == 1) insp_circ_reset(); /* a fresh top-level value: Node's per-inspect ctx */
|
|
252
255
|
if (g_nframes == g_frames_cap) {
|
|
253
256
|
g_frames_cap = g_frames_cap ? g_frames_cap * 2 : 8;
|
|
254
257
|
g_frames = realloc(g_frames, g_frames_cap * sizeof(InspFrame));
|
|
@@ -277,6 +280,85 @@ void scr_insp_entry(ScrStr *s, bool is_num) {
|
|
|
277
280
|
if (!is_num) f->all_num = false;
|
|
278
281
|
}
|
|
279
282
|
|
|
283
|
+
/* ── circular references (Node's <ref *N> / [Circular *N]) ────────────
|
|
284
|
+
* Recursive record/class types permit runtime reference cycles, and Node
|
|
285
|
+
* renders them with formatValue's seen/circular machinery: a value
|
|
286
|
+
* already ON the current traversal stack renders as `[Circular *N]` (N
|
|
287
|
+
* assigned at first detection, in discovery order), and every rendering
|
|
288
|
+
* of a so-numbered value gets the `<ref *N> ` prefix at its close. The
|
|
289
|
+
* compiler-emitted helpers over CYCLE-CAPABLE composites drive exactly
|
|
290
|
+
* that protocol: circ_check first (before the empty-literal and depth
|
|
291
|
+
* answers — Node's order: a circular value beyond the depth budget still
|
|
292
|
+
* says Circular), seen_push after begin, ref_wrap around end's result.
|
|
293
|
+
* The circular MAP persists for one whole top-level inspect — begin(1)
|
|
294
|
+
* is the per-value reset (a root composite's first frame). */
|
|
295
|
+
typedef struct {
|
|
296
|
+
const void *ptr;
|
|
297
|
+
int id; /* circular id (0 while only on the stack) */
|
|
298
|
+
} InspSeenEnt;
|
|
299
|
+
|
|
300
|
+
static InspSeenEnt *g_seen;
|
|
301
|
+
static size_t g_nseen;
|
|
302
|
+
static size_t g_seen_cap;
|
|
303
|
+
/* Detection-numbered circular targets (persist across the call). */
|
|
304
|
+
static const void *g_circ[64];
|
|
305
|
+
static int g_ncirc;
|
|
306
|
+
|
|
307
|
+
static void insp_circ_reset(void) {
|
|
308
|
+
g_nseen = 0;
|
|
309
|
+
g_ncirc = 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
static int insp_circ_id(const void *v) {
|
|
313
|
+
for (int i = 0; i < g_ncirc; i++) {
|
|
314
|
+
if (g_circ[i] == v) return i + 1;
|
|
315
|
+
}
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
double scr_insp_circ_check(const void *v) {
|
|
320
|
+
for (size_t i = 0; i < g_nseen; i++) {
|
|
321
|
+
if (g_seen[i].ptr != v) continue;
|
|
322
|
+
int id = insp_circ_id(v);
|
|
323
|
+
if (id == 0 && g_ncirc < (int)(sizeof g_circ / sizeof *g_circ)) {
|
|
324
|
+
g_circ[g_ncirc++] = v;
|
|
325
|
+
id = g_ncirc;
|
|
326
|
+
}
|
|
327
|
+
return (double)id;
|
|
328
|
+
}
|
|
329
|
+
return 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
void scr_insp_seen_push(const void *v) {
|
|
333
|
+
if (g_nseen == g_seen_cap) {
|
|
334
|
+
g_seen_cap = g_seen_cap ? g_seen_cap * 2 : 8;
|
|
335
|
+
g_seen = realloc(g_seen, g_seen_cap * sizeof(InspSeenEnt));
|
|
336
|
+
if (!g_seen) insp_oom();
|
|
337
|
+
}
|
|
338
|
+
g_seen[g_nseen].ptr = v;
|
|
339
|
+
g_seen[g_nseen].id = 0;
|
|
340
|
+
g_nseen++;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
ScrStr *scr_insp_circular(double id) {
|
|
344
|
+
char buf[32];
|
|
345
|
+
int n = snprintf(buf, sizeof buf, "[Circular *%.0f]", id);
|
|
346
|
+
return scr_str_new(buf, (size_t)n);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
ScrStr *scr_insp_ref_wrap(const void *v, ScrStr *s) {
|
|
350
|
+
if (g_nseen > 0) g_nseen--;
|
|
351
|
+
int id = insp_circ_id(v);
|
|
352
|
+
if (id == 0) return scr_str_retain(s);
|
|
353
|
+
char buf[32];
|
|
354
|
+
int n = snprintf(buf, sizeof buf, "<ref *%d> ", id);
|
|
355
|
+
ScrStr *out = scr_str_alloc_raw(s->len + (size_t)n, s->len + (size_t)n);
|
|
356
|
+
memcpy(out->data, buf, (size_t)n);
|
|
357
|
+
memcpy(out->data + n, s->data, s->len);
|
|
358
|
+
out->data[out->len] = '\0';
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
|
|
280
362
|
/* remainingText: the "... N more items" tail entry. */
|
|
281
363
|
ScrStr *scr_insp_more_items(double remaining) {
|
|
282
364
|
char buf[64];
|
package/src/scr_json.c
CHANGED
|
@@ -55,6 +55,9 @@ void scr_jb_init(ScrJsonBuf *b) {
|
|
|
55
55
|
b->data = NULL;
|
|
56
56
|
b->len = 0;
|
|
57
57
|
b->cap = 0;
|
|
58
|
+
b->seen = NULL;
|
|
59
|
+
b->seen_len = 0;
|
|
60
|
+
b->seen_cap = 0;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
static void scr_jb_grow(ScrJsonBuf *b, size_t need) {
|
|
@@ -76,6 +79,7 @@ static void scr_jb_grow(ScrJsonBuf *b, size_t need) {
|
|
|
76
79
|
/* Abandon a buffer (parser error paths). */
|
|
77
80
|
static void scr_jb_dispose(ScrJsonBuf *b) {
|
|
78
81
|
if (b->data) scr_str_release(SCR_JB_STR(b));
|
|
82
|
+
free(b->seen);
|
|
79
83
|
scr_jb_init(b);
|
|
80
84
|
}
|
|
81
85
|
|
|
@@ -143,6 +147,10 @@ void scr_jb_put_json_str(ScrJsonBuf *b, const ScrStr *s) {
|
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
ScrStr *scr_jb_finish(ScrJsonBuf *b) {
|
|
150
|
+
free(b->seen);
|
|
151
|
+
b->seen = NULL;
|
|
152
|
+
b->seen_len = 0;
|
|
153
|
+
b->seen_cap = 0;
|
|
146
154
|
if (!b->data) return scr_str_new("", 0);
|
|
147
155
|
ScrStr *s = SCR_JB_STR(b);
|
|
148
156
|
s->len = b->len;
|
|
@@ -154,6 +162,144 @@ ScrStr *scr_jb_finish(ScrJsonBuf *b) {
|
|
|
154
162
|
return s;
|
|
155
163
|
}
|
|
156
164
|
|
|
165
|
+
/* ── circular-structure detection ──────────────────────────────────────
|
|
166
|
+
* RECURSIVE record types permit runtime reference cycles; JSON.stringify
|
|
167
|
+
* of a cyclic value throws V8's exact TypeError. The compiler-emitted
|
|
168
|
+
* walkers over cycle-CAPABLE containers (records whose shape carries a
|
|
169
|
+
* collector header, arrays of them, tuple shapes alike) bracket their
|
|
170
|
+
* bodies with enter/leave and stamp the current member edge before each
|
|
171
|
+
* cycle-capable member write; everything acyclic pays nothing. Detection
|
|
172
|
+
* is STACK membership (a DAG serializes the shared subtree twice, exactly
|
|
173
|
+
* like Node — only a path back to an ancestor is circular).
|
|
174
|
+
*
|
|
175
|
+
* The message mirrors V8's ConstructCircularStructureErrorMessage byte
|
|
176
|
+
* for byte: the starting object (where the repeat lands), one line per
|
|
177
|
+
* hop from it to the top of the stack ("property 'x' -> object with
|
|
178
|
+
* constructor 'Y'" / "index N -> ..."), the middle elided as "..." when
|
|
179
|
+
* there are more than three hops (first two + last one shown), and the
|
|
180
|
+
* closing edge. Constructor names are exactly 'Object'/'Array' — the only
|
|
181
|
+
* containers JSON-safe types admit. */
|
|
182
|
+
typedef struct ScrJsonSeenEnt {
|
|
183
|
+
const void *ptr;
|
|
184
|
+
bool is_array;
|
|
185
|
+
const char *prop; /* static property edge (emitted C literal) */
|
|
186
|
+
const ScrStr *prop_str; /* overflow-key edge (borrowed for the write) */
|
|
187
|
+
size_t index; /* array/tuple index edge */
|
|
188
|
+
} ScrJsonSeenEnt;
|
|
189
|
+
|
|
190
|
+
static void scr_jb_put_edge(ScrJsonBuf *m, const ScrJsonSeenEnt *e) {
|
|
191
|
+
if (e->prop || e->prop_str) {
|
|
192
|
+
scr_jb_puts(m, "property '");
|
|
193
|
+
if (e->prop) scr_jb_puts(m, e->prop);
|
|
194
|
+
else scr_jb_write(m, e->prop_str->data, e->prop_str->len);
|
|
195
|
+
scr_jb_putc(m, '\'');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
scr_jb_puts(m, "index ");
|
|
199
|
+
scr_jb_put_f64(m, (double)e->index);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static void scr_jb_put_ctor(ScrJsonBuf *m, bool is_array) {
|
|
203
|
+
scr_jb_puts(m, is_array ? "object with constructor 'Array'" : "object with constructor 'Object'");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
bool scr_jb_enter(ScrJsonBuf *b, const void *v, bool is_array) {
|
|
207
|
+
for (size_t i = 0; i < b->seen_len; i++) {
|
|
208
|
+
if (b->seen[i].ptr != v) continue;
|
|
209
|
+
ScrJsonBuf m;
|
|
210
|
+
scr_jb_init(&m);
|
|
211
|
+
scr_jb_puts(&m, "Converting circular structure to JSON\n --> starting at ");
|
|
212
|
+
scr_jb_put_ctor(&m, b->seen[i].is_array);
|
|
213
|
+
const size_t n = b->seen_len;
|
|
214
|
+
const size_t hops = n - 1 - i; /* intermediate lines (j = i+1 .. n-1) */
|
|
215
|
+
for (size_t j = i + 1; j < n; j++) {
|
|
216
|
+
if (hops > 3 && j - (i + 1) == 2) {
|
|
217
|
+
scr_jb_puts(&m, "\n | ...");
|
|
218
|
+
j = n - 2; /* the loop increment lands on the LAST hop */
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
scr_jb_puts(&m, "\n | ");
|
|
222
|
+
scr_jb_put_edge(&m, &b->seen[j - 1]);
|
|
223
|
+
scr_jb_puts(&m, " -> ");
|
|
224
|
+
scr_jb_put_ctor(&m, b->seen[j].is_array);
|
|
225
|
+
}
|
|
226
|
+
scr_jb_puts(&m, "\n --- ");
|
|
227
|
+
scr_jb_put_edge(&m, &b->seen[n - 1]);
|
|
228
|
+
scr_jb_puts(&m, " closes the circle");
|
|
229
|
+
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&m));
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (b->seen_len == b->seen_cap) {
|
|
233
|
+
size_t cap = b->seen_cap ? b->seen_cap * 2 : 8;
|
|
234
|
+
ScrJsonSeenEnt *grown = realloc(b->seen, cap * sizeof *grown);
|
|
235
|
+
if (!grown) scr_json_oom();
|
|
236
|
+
b->seen = grown;
|
|
237
|
+
b->seen_cap = cap;
|
|
238
|
+
}
|
|
239
|
+
ScrJsonSeenEnt *e = &b->seen[b->seen_len++];
|
|
240
|
+
e->ptr = v;
|
|
241
|
+
e->is_array = is_array;
|
|
242
|
+
e->prop = NULL;
|
|
243
|
+
e->prop_str = NULL;
|
|
244
|
+
e->index = 0;
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
void scr_jb_leave(ScrJsonBuf *b) {
|
|
249
|
+
if (b->seen_len > 0) b->seen_len--;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void scr_jb_edge_prop(ScrJsonBuf *b, const char *name) {
|
|
253
|
+
ScrJsonSeenEnt *e = &b->seen[b->seen_len - 1];
|
|
254
|
+
e->prop = name;
|
|
255
|
+
e->prop_str = NULL;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
void scr_jb_edge_key(ScrJsonBuf *b, const ScrStr *key) {
|
|
259
|
+
ScrJsonSeenEnt *e = &b->seen[b->seen_len - 1];
|
|
260
|
+
e->prop = NULL;
|
|
261
|
+
e->prop_str = key;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
void scr_jb_edge_idx(ScrJsonBuf *b, size_t i) {
|
|
265
|
+
ScrJsonSeenEnt *e = &b->seen[b->seen_len - 1];
|
|
266
|
+
e->prop = NULL;
|
|
267
|
+
e->prop_str = NULL;
|
|
268
|
+
e->index = i;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* ── circular guard for the typed→dyn converters (sc_td_*) ────────────
|
|
272
|
+
* A recursive-typed value crossing into a checked-dynamic slot DEEP-
|
|
273
|
+
* COPIES into the DOM; a cyclic value has no finite copy. Node never
|
|
274
|
+
* copies (an unknown-typed binding shares the reference), so there is no
|
|
275
|
+
* Node-exact error to throw — the conversion TRAPS loudly instead
|
|
276
|
+
* (SEMANTICS.md documents the divergence). The emitted converters over
|
|
277
|
+
* cycle-capable containers bracket their walks with enter/leave; the
|
|
278
|
+
* stack is global (conversions never interleave). */
|
|
279
|
+
static const void **g_td_seen;
|
|
280
|
+
static size_t g_td_nseen;
|
|
281
|
+
static size_t g_td_cap;
|
|
282
|
+
|
|
283
|
+
void scr_dyn_from_enter(const void *v) {
|
|
284
|
+
for (size_t i = 0; i < g_td_nseen; i++) {
|
|
285
|
+
if (g_td_seen[i] == v) {
|
|
286
|
+
scr_trap("scriptc: cannot convert a circular structure into a checked-dynamic value "
|
|
287
|
+
"(unknown-typed slots deep-copy; break the cycle first)\n");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (g_td_nseen == g_td_cap) {
|
|
291
|
+
g_td_cap = g_td_cap ? g_td_cap * 2 : 8;
|
|
292
|
+
const void **grown = realloc(g_td_seen, g_td_cap * sizeof *grown);
|
|
293
|
+
if (!grown) scr_json_oom();
|
|
294
|
+
g_td_seen = grown;
|
|
295
|
+
}
|
|
296
|
+
g_td_seen[g_td_nseen++] = v;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
void scr_dyn_from_leave(void) {
|
|
300
|
+
if (g_td_nseen > 0) g_td_nseen--;
|
|
301
|
+
}
|
|
302
|
+
|
|
157
303
|
/* ── DOM lifecycle ─────────────────────────────────────────────────────
|
|
158
304
|
* Parse/release churn (a JSON round-trip loop allocates and frees every
|
|
159
305
|
* node each iteration) runs on freelists instead of calloc/free: one list
|
package/src/scr_runtime.h
CHANGED
|
@@ -2917,14 +2917,48 @@ extern void scr_dyn_listener_fire_err(ScrClosure *cb, ScrStr *msg);
|
|
|
2917
2917
|
|
|
2918
2918
|
/* Output buffer for the compiler-emitted type-directed JSON serializers.
|
|
2919
2919
|
* Value-typed and stack-allocated by the emitted code; _finish hands the
|
|
2920
|
-
* bytes over as a +1 ScrStr and frees the buffer storage
|
|
2920
|
+
* bytes over as a +1 ScrStr and frees the buffer storage (including the
|
|
2921
|
+
* circular-detection stack below). */
|
|
2922
|
+
struct ScrJsonSeenEnt;
|
|
2921
2923
|
typedef struct {
|
|
2922
2924
|
char *data;
|
|
2923
2925
|
size_t len;
|
|
2924
2926
|
size_t cap;
|
|
2927
|
+
/* Circular-structure detection for RECURSIVE record types (cyclic
|
|
2928
|
+
* values must throw Node's exact TypeError, never recurse forever):
|
|
2929
|
+
* the stack of container values currently being serialized, each with
|
|
2930
|
+
* its outgoing edge label (the member being written). Only walkers over
|
|
2931
|
+
* cycle-CAPABLE types (the collector-fixpoint set) maintain it; acyclic
|
|
2932
|
+
* types keep the zero-cost path. */
|
|
2933
|
+
struct ScrJsonSeenEnt *seen;
|
|
2934
|
+
size_t seen_len;
|
|
2935
|
+
size_t seen_cap;
|
|
2925
2936
|
} ScrJsonBuf;
|
|
2926
2937
|
|
|
2927
2938
|
void scr_jb_init(ScrJsonBuf *b);
|
|
2939
|
+
/* Push a container onto the circular-detection stack before serializing
|
|
2940
|
+
* its members. If `v` is already ON the stack, throws V8's exact
|
|
2941
|
+
* "Converting circular structure to JSON" TypeError (the --> starting at /
|
|
2942
|
+
* |property/index hops/--- closes the circle rendering, ellipsis rules
|
|
2943
|
+
* included) and returns false — the caller returns immediately; the
|
|
2944
|
+
* emitted stringify site runs the pending check. `is_array` picks the
|
|
2945
|
+
* constructor name in the message ('Array' for arrays AND tuple shapes —
|
|
2946
|
+
* their JS values are arrays — 'Object' for records). */
|
|
2947
|
+
bool scr_jb_enter(ScrJsonBuf *b, const void *v, bool is_array);
|
|
2948
|
+
void scr_jb_leave(ScrJsonBuf *b);
|
|
2949
|
+
/* Record the edge currently being serialized on the stack top: a static
|
|
2950
|
+
* property name (emitted C literal), an overflow key (borrowed for the
|
|
2951
|
+
* duration of the member write), or an array/tuple index. */
|
|
2952
|
+
void scr_jb_edge_prop(ScrJsonBuf *b, const char *name);
|
|
2953
|
+
void scr_jb_edge_key(ScrJsonBuf *b, const ScrStr *key);
|
|
2954
|
+
void scr_jb_edge_idx(ScrJsonBuf *b, size_t i);
|
|
2955
|
+
|
|
2956
|
+
/* Circular guard for the compiler-emitted typed→dyn converters (sc_td_*
|
|
2957
|
+
* over cycle-capable containers): enter TRAPS on a value already being
|
|
2958
|
+
* converted (a cyclic value has no finite DOM copy — Node shares the
|
|
2959
|
+
* reference instead; SEMANTICS.md), else pushes. */
|
|
2960
|
+
void scr_dyn_from_enter(const void *v);
|
|
2961
|
+
void scr_dyn_from_leave(void);
|
|
2928
2962
|
void scr_jb_putc(ScrJsonBuf *b, char c);
|
|
2929
2963
|
void scr_jb_puts(ScrJsonBuf *b, const char *s);
|
|
2930
2964
|
/* JS JSON.stringify number rules: NaN/±Infinity → null, -0 → 0, else
|
|
@@ -4095,6 +4129,18 @@ ScrStr *scr_insp_jsval(ScrJsval *v, double recurse, double depth);
|
|
|
4095
4129
|
#endif
|
|
4096
4130
|
void scr_insp_begin(double recurse);
|
|
4097
4131
|
void scr_insp_entry(ScrStr *s, bool is_num);
|
|
4132
|
+
/* Circular references, Node-exact (<ref *N> / [Circular *N]): the
|
|
4133
|
+
* compiler-emitted helpers over CYCLE-CAPABLE composites call circ_check
|
|
4134
|
+
* FIRST (a value already on the traversal stack answers its circular id,
|
|
4135
|
+
* assigned in discovery order — the caller renders scr_insp_circular and
|
|
4136
|
+
* descends no further), seen_push after begin, and ref_wrap around end's
|
|
4137
|
+
* result (pops the stack; values in the circular map gain the "<ref *N> "
|
|
4138
|
+
* prefix). State resets at each top-level value's first frame
|
|
4139
|
+
* (scr_insp_begin(1)). ref_wrap BORROWS s and returns +1. */
|
|
4140
|
+
double scr_insp_circ_check(const void *v);
|
|
4141
|
+
void scr_insp_seen_push(const void *v);
|
|
4142
|
+
ScrStr *scr_insp_circular(double id);
|
|
4143
|
+
ScrStr *scr_insp_ref_wrap(const void *v, ScrStr *s);
|
|
4098
4144
|
ScrStr *scr_insp_more_items(double remaining); /* "... N more items" */
|
|
4099
4145
|
ScrStr *scr_insp_key(ScrStr *k); /* bare-or-quoted property-name ladder; +1 */
|
|
4100
4146
|
ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse,
|
|
@@ -5039,6 +5085,11 @@ void scr_assert_fail_msg(ScrStr *message); /* takes ownership; always throws */
|
|
|
5039
5085
|
ScrStr *scr_assert_inspect_str(const ScrStr *s); /* util.inspect quoting; +1 */
|
|
5040
5086
|
void scr_assert_ok(bool pass, ScrStr *message);
|
|
5041
5087
|
bool scr_assert_same_value_f64(double a, double b); /* Object.is */
|
|
5088
|
+
/* deepStrictEqual over cyclic values: pair memo (enter answers true for
|
|
5089
|
+
* a pair already being compared — Node's coinductive memo; leave pops).
|
|
5090
|
+
* The compiler-emitted helpers over cycle-capable types wrap with these. */
|
|
5091
|
+
bool scr_assert_deq_enter(const void *a, const void *b);
|
|
5092
|
+
void scr_assert_deq_leave(void);
|
|
5042
5093
|
void scr_assert_eq_f64(double a, double b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
|
5043
5094
|
void scr_assert_eq_str(ScrStr *a, ScrStr *b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
|
5044
5095
|
void scr_assert_eq_bool(bool a, bool b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|