@scriptc/runtime 0.0.9 → 0.0.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scriptc/runtime",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "scriptc native runtime — C sources, compiled into every scriptc binary",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scriptc.dev",
package/src/scr_assert.c CHANGED
@@ -439,6 +439,11 @@ static bool scr_assert_dyn_same_value(const ScrDyn *a, const ScrDyn *b) {
439
439
  case SCR_DYN_PROMISE:
440
440
  /* Identity is the PROMISE (the strict_eq stance). */
441
441
  return a->v.promise == b->v.promise;
442
+ case SCR_DYN_JSVAL:
443
+ /* Identity is the ENGINE VALUE (the strict_eq stance): the engine's
444
+ * === — exact for SameValue too, since wrap-time scalar
445
+ * normalization leaves only reference kinds behind. */
446
+ return a == b || scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell);
442
447
  default:
443
448
  return a == b; /* ARR/OBJ/BYTES: node identity */
444
449
  }
@@ -454,7 +459,16 @@ static bool scr_assert_dyn_same_value(const ScrDyn *a, const ScrDyn *b) {
454
459
  * memo here where Node carries one (documented divergence). */
455
460
  static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
456
461
  if (a == b) return true;
457
- if (a->kind != b->kind) return false;
462
+ if (a->kind != b->kind) {
463
+ /* A MIXED comparison with an island side (wrapped engine object vs
464
+ * DOM data): Node walks both structurally — a plain `false` would
465
+ * mint a fabricated AssertionError for values Node may call equal.
466
+ * Loud fence (the long-tail lane's structural walk). */
467
+ if (a->kind == SCR_DYN_JSVAL || b->kind == SCR_DYN_JSVAL) {
468
+ scr_dyn_isl_fence(a->kind == SCR_DYN_JSVAL ? a : b, "deepStrictEqual");
469
+ }
470
+ return false;
471
+ }
458
472
  switch (a->kind) {
459
473
  case SCR_DYN_UNDEF:
460
474
  case SCR_DYN_NULL:
@@ -493,6 +507,7 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
493
507
  return true;
494
508
  }
495
509
  case SCR_DYN_OBJ: {
510
+ if (a->null_proto != b->null_proto) return false; /* the prototype gate */
496
511
  if (a->v.obj.len != b->v.obj.len) return false;
497
512
  for (size_t i = 0; i < a->v.obj.len; i++) {
498
513
  const ScrDynEntry *ent = &a->v.obj.entries[i];
@@ -501,6 +516,14 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
501
516
  }
502
517
  return true;
503
518
  }
519
+ case SCR_DYN_JSVAL:
520
+ /* Node walks the engine objects structurally — a walk this runtime
521
+ * cannot run. Engine-identical answers true honestly; anything else
522
+ * throws the loud ladder (scr_assert_eq_dyn propagates the pending
523
+ * exception INSTEAD of minting an AssertionError). */
524
+ if (scr_dyn_jsval_ops()->strict_eq(a->v.jsval.cell, b->v.jsval.cell)) return true;
525
+ scr_dyn_isl_fence(a, "deepStrictEqual");
526
+ return false;
504
527
  }
505
528
  return false; /* unreachable */
506
529
  }
@@ -653,15 +676,26 @@ static void scr_assert_cf_value(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
653
676
  ab_char(b, ']');
654
677
  return;
655
678
  }
679
+ case SCR_DYN_JSVAL: {
680
+ /* The depth-elided placeholder (the handle stance): Node renders
681
+ * the engine object's property dump — internals this walker does
682
+ * not model; the text only appears inside FAILURE reports, which
683
+ * already diverge in format. */
684
+ ab_cstr(b, "[island value]");
685
+ return;
686
+ }
656
687
  case SCR_DYN_OBJ: {
688
+ /* The null-proto dictionary renders with Node's prefix in the
689
+ * failure diff too (assertion_error.js inspects both sides). */
657
690
  if (d->v.obj.len == 0) {
658
- ab_cstr(b, "{}");
691
+ ab_cstr(b, d->null_proto ? "[Object: null prototype] {}" : "{}");
659
692
  return;
660
693
  }
661
694
  if (depth == 0) {
662
- ab_cstr(b, "[Object]");
695
+ ab_cstr(b, d->null_proto ? "[Object: null prototype]" : "[Object]");
663
696
  return;
664
697
  }
698
+ if (d->null_proto) ab_cstr(b, "[Object: null prototype] ");
665
699
  /* Render every entry, then sort the RENDERED texts (Node's
666
700
  * sorted:true sorts formatted entries, quotes included). */
667
701
  ScrCfEntry *ents = malloc(d->v.obj.len * sizeof *ents);
@@ -893,7 +927,8 @@ static bool scr_assert_print_myers(ScrAssertBuf *b, const ScrDiffOp *diff, size_
893
927
  /* Is this DOM kind `typeof == "object" && != null` to assertion_error.js? */
894
928
  static bool scr_assert_dyn_is_object(const ScrDyn *d) {
895
929
  return d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ || d->kind == SCR_DYN_BYTES ||
896
- d->kind == SCR_DYN_HANDLE || d->kind == SCR_DYN_PROMISE;
930
+ d->kind == SCR_DYN_HANDLE || d->kind == SCR_DYN_PROMISE ||
931
+ scr_dyn_isl_typeof_is(d, "object"); /* engine-held: the engine's typeof */
897
932
  }
898
933
 
899
934
  /* The failing EQUAL operators over dyn operands — createErrDiff. */
@@ -1065,6 +1100,10 @@ static void scr_assert_dyn_neq_fail(ScrDyn *a, bool deep, ScrStr *msg, bool has_
1065
1100
  void scr_assert_eq_dyn(ScrDyn *a, ScrDyn *b, bool negated, bool deep,
1066
1101
  ScrStr *msg, bool has_msg) {
1067
1102
  bool same = deep ? scr_assert_dyn_deep_eq(a, b) : scr_assert_dyn_same_value(a, b);
1103
+ /* The deep walk's JSVAL arm fences loudly (an island value it cannot
1104
+ * compare structurally): propagate THAT, never a fabricated
1105
+ * AssertionError over a comparison that did not run. */
1106
+ if (scr_exc_pending()) return;
1068
1107
  if ((negated && !same) || (!negated && same)) return;
1069
1108
  if (negated) {
1070
1109
  scr_assert_dyn_neq_fail(a, deep, msg, has_msg);
package/src/scr_async.c CHANGED
@@ -591,7 +591,8 @@ ScrArr *scr_active_resources(void) {
591
591
  * left queued on the uncaught paths) never run — the teardown releases
592
592
  * them, like Node dropping the queue at exit. */
593
593
  typedef struct ScrNtick {
594
- ScrClosure *cb; /* owned */
594
+ ScrClosure *cb; /* owned; NULL for a raw C-hook entry */
595
+ void (*raw)(void); /* the hook when cb == NULL (stream tick markers) */
595
596
  struct ScrNtick *next;
596
597
  } ScrNtick;
597
598
 
@@ -612,11 +613,25 @@ void scr_next_tick(ScrClosure *cb /*moves*/) {
612
613
  scr_nt_tail = t;
613
614
  }
614
615
 
616
+ /* A RAW C-hook tick: the stream unit enqueues one marker per deferred
617
+ * stream emission so those emissions interleave with user nextTicks in
618
+ * true FIFO order — in Node they ARE nextTicks (resume_, emitReadable_,
619
+ * endReadableNT, ...). The hook dispatches exactly one stream tick;
620
+ * teardown just drops markers (the stream queue owns its entries). */
621
+ void scr_next_tick_raw(void (*fn)(void)) {
622
+ ScrNtick *t = calloc(1, sizeof *t);
623
+ if (!t) scr_oom();
624
+ t->raw = fn;
625
+ if (scr_nt_tail) scr_nt_tail->next = t;
626
+ else scr_nt_head = t;
627
+ scr_nt_tail = t;
628
+ }
629
+
615
630
  void scr_nticks_teardown(void) {
616
631
  while (scr_nt_head != NULL) {
617
632
  ScrNtick *t = scr_nt_head;
618
633
  scr_nt_head = t->next;
619
- scr_closure_release(t->cb);
634
+ if (t->cb) scr_closure_release(t->cb);
620
635
  free(t);
621
636
  }
622
637
  scr_nt_tail = NULL;
@@ -1677,9 +1692,14 @@ void scr_loop_run(void) {
1677
1692
  scr_nt_head = t->next;
1678
1693
  if (scr_nt_head == NULL) scr_nt_tail = NULL;
1679
1694
  ScrClosure *cb = t->cb;
1695
+ void (*raw)(void) = t->raw;
1680
1696
  free(t);
1681
- ((void (*)(ScrClosure *))cb->fn)(cb);
1682
- scr_closure_release(cb);
1697
+ if (cb) {
1698
+ ((void (*)(ScrClosure *))cb->fn)(cb);
1699
+ scr_closure_release(cb);
1700
+ } else {
1701
+ raw(); /* one stream tick, FIFO with the user ticks around it */
1702
+ }
1683
1703
  if (scr_exc_pending()) return;
1684
1704
  }
1685
1705
  }
package/src/scr_bytes.c CHANGED
@@ -1,6 +1,8 @@
1
1
  /* Typed arrays / Buffer: ONE runtime representation (ScrBytes — see the
2
- * header contract). Element buffers own their storage outright: no views,
3
- * no byteOffset, slice/subarray both copy. Coercions are JS-exact
2
+ * header contract). An ScrBytes either OWNS its storage or is a VIEW into
3
+ * an owner's (backing set, chain depth exactly 1): DataView, subarray(),
4
+ * and Buffer's slice() all alias JS-exactly; only the plain typed arrays'
5
+ * slice() copies. Coercions are JS-exact
4
6
  * (ToUint8/ToUint32 modular truncation, double→float rounding); the
5
7
  * encoding conversions (utf8 with WHATWG replacement, hex, base64) match
6
8
  * Node byte-for-byte — the differential corpus holds them to it. */
@@ -164,7 +166,7 @@ void scr_bytes_set(ScrBytes *b, double i, double v) {
164
166
  }
165
167
  }
166
168
 
167
- /* ── slice / subarray (both COPY) ──────────────────────────────────────── */
169
+ /* ── slice (copy) / subarray (view) ────────────────────────────────────── */
168
170
 
169
171
  /* Relative index: ToIntegerOrInfinity, negatives from the end, clamped. */
170
172
  static size_t scr_bytes_rel_index(double i, size_t len) {
@@ -186,6 +188,41 @@ ScrBytes *scr_bytes_slice(const ScrBytes *b, double start, double end) {
186
188
  return out;
187
189
  }
188
190
 
191
+ /* TypedArray.prototype.fill on non-u8 receivers: per-ELEMENT fill with
192
+ * the element write's JS-exact coercion (ToUint32/ToInt32 wrap, f32
193
+ * rounding), slice-clamped relative indices; answers the receiver +1
194
+ * (chaining). Never throws — Buffer's throwing fill family is separate. */
195
+ ScrBytes *scr_bytes_fill_elem(ScrBytes *b, double v, double start, double end) {
196
+ size_t s = scr_bytes_rel_index(start, b->len);
197
+ size_t e = scr_bytes_rel_index(end, b->len);
198
+ for (size_t i = s; i < e; i++) scr_bytes_set(b, (double)i, v);
199
+ return scr_bytes_retain(b);
200
+ }
201
+
202
+ /* subarray(start, end): a same-elem VIEW over the receiver's storage —
203
+ * TypedArray.prototype.subarray and Buffer's slice()/subarray() all alias
204
+ * in JS (mutations are visible both ways; buffer-swap through a slice is
205
+ * the canonical Node use). Chain depth stays exactly 1: a subarray of a
206
+ * view retains the OWNER, offsets composed here (the DataView rule).
207
+ * Relative indices clamp like slice; never throws. */
208
+ ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end) {
209
+ size_t s = scr_bytes_rel_index(start, b->len);
210
+ size_t e = scr_bytes_rel_index(end, b->len);
211
+ size_t count = e > s ? e - s : 0;
212
+ ScrBytes *owner = b->backing ? b->backing : b;
213
+ ScrBytes *v = malloc(sizeof(ScrBytes));
214
+ if (!v) scr_bytes_oom();
215
+ v->rc = 1;
216
+ v->len = count;
217
+ v->elem = b->elem;
218
+ v->data = b->data + s * scr_bytes_elem_size(b->elem);
219
+ v->backing = scr_bytes_retain(owner);
220
+ #ifdef SCR_RC_AUDIT
221
+ scr_live_bytes++;
222
+ #endif
223
+ return v;
224
+ }
225
+
189
226
  void scr_bytes_set_from(ScrBytes *dst, const ScrBytes *src, double offset) {
190
227
  double t = (offset != offset) ? 0 : trunc(offset);
191
228
  if (!(t >= 0) || (double)src->len + t > (double)dst->len) {
@@ -927,7 +964,7 @@ ScrBytes *scr_bytes_from_arr(ScrBytesElem elem, const ScrArr *arr) {
927
964
  * the '>= 0 && <= max' render, ERR_OUT_OF_RANGE's Received rules — and
928
965
  * copy's C++-side ladder; pinned by corpus 1663) ─────────────────────── */
929
966
 
930
- static size_t scr_bytes_received(double v, char out[48]); /* the numeric section below */
967
+ size_t scr_num_received(double v, char out[48]); /* the numeric section below */
931
968
 
932
969
  /* validateOffset(name, 0, max): non-integers are 'an integer' (Number.
933
970
  * isInteger — ±Infinity render 'an integer' too), the rest '>= 0 && <=
@@ -938,7 +975,7 @@ static size_t scr_bytes_received(double v, char out[48]); /* the numeric section
938
975
  bool scr_bytes_validate_off(const char *name, double value, double max) {
939
976
  if (isfinite(value) && floor(value) == value && value >= 0 && (max < 0 || value <= max)) return true;
940
977
  char recv[48];
941
- scr_bytes_received(value, recv);
978
+ scr_num_received(value, recv);
942
979
  char msg[160];
943
980
  int mlen;
944
981
  if (floor(value) != value || !isfinite(value)) {
@@ -1250,8 +1287,9 @@ ScrBytes *scr_bytes_concat(const ScrArr *list) {
1250
1287
  /* ERR_OUT_OF_RANGE's "Received" rendering: integers with |v| > 2^32 get
1251
1288
  * Node's addNumericalSeparator underscores applied to the plain String()
1252
1289
  * form — INCLUDING its quirks on exponent renderings ("1e_+21",
1253
- * "1e+_300"); everything else is the shortest-roundtrip number. */
1254
- static size_t scr_bytes_received(double v, char out[48]) {
1290
+ * "1e+_300"); everything else is the shortest-roundtrip number. Shared
1291
+ * with the fs/net option-ladder validators (scr_num_received). */
1292
+ size_t scr_num_received(double v, char out[48]) {
1255
1293
  char plain[32];
1256
1294
  size_t n = scr_f64_to_str(v, plain);
1257
1295
  if (!(isfinite(v) && trunc(v) == v && fabs(v) > 4294967296.0)) {
@@ -1281,7 +1319,7 @@ static size_t scr_bytes_received(double v, char out[48]) {
1281
1319
  * "byteLength" renders min 1. All catchable RangeErrors. */
1282
1320
  static void scr_bytes_bounds_error(double value, double length, const char *type) {
1283
1321
  char recv[48];
1284
- scr_bytes_received(value, recv);
1322
+ scr_num_received(value, recv);
1285
1323
  char msg[160];
1286
1324
  int mlen;
1287
1325
  if (floor(value) != value) {
@@ -1322,7 +1360,7 @@ static bool scr_bytes_check_int(const ScrBytes *b, double value, double offset,
1322
1360
  double min = sign ? -exp2((double)(8 * width - 1)) : 0;
1323
1361
  if (value > max || value < min) {
1324
1362
  char recv[48];
1325
- scr_bytes_received(value, recv);
1363
+ scr_num_received(value, recv);
1326
1364
  char msg[160];
1327
1365
  int mlen;
1328
1366
  if (width > 4) {