@scriptc/runtime 0.0.10 → 0.0.12

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/src/scr_stream.c CHANGED
@@ -13,11 +13,12 @@
13
13
  * nothing.
14
14
  *
15
15
  * EVENT TIMING. Node schedules most stream emissions on process.nextTick;
16
- * here those deferrals ride a FIFO tick queue drained at the top of every
17
- * loop turn (scr_loop_set_stream the station before events/net/timers,
18
- * the closest to nextTick; ticks scheduled from user code on the main
19
- * stack run when the loop starts, i.e. after all synchronous code — the
20
- * nextTick shape). The implemented orderings follow lib/internal/streams:
16
+ * here each deferral enqueues on the stream tick FIFO AND posts a raw
17
+ * marker on the USER nextTick queue (scr_next_tick_raw), so stream
18
+ * emissions and user nextTicks run in true FIFO enqueue order Node's,
19
+ * where they are the same queue. The scr_loop_set_stream station remains
20
+ * as the drain of anything a marker never reached (and the uncaught-throw
21
+ * cleanup). The implemented orderings follow lib/internal/streams:
21
22
  * - on('data') starts flowing on a TICK (resume_): synchronous code
22
23
  * after the registration runs before the first 'data'.
23
24
  * - push() while flowing with an empty buffer emits 'data'
@@ -103,6 +104,10 @@ struct ScrStreamState {
103
104
  bool encoded; /* string-chunk mode (decoder active) */
104
105
  ScrStr *enc; /* owned canonical encoding name, or NULL */
105
106
  double dec_pending; /* scr_strdec packed pending state */
107
+ /* The defaultEncoding option's push-side effect: how push(string)
108
+ * DECODES string chunks into bytes (Node's Buffer.from(chunk,
109
+ * state.defaultEncoding)). Owned canonical name; NULL = utf8. */
110
+ ScrStr *push_enc;
106
111
  /* Readable.from mode: entries are whole OBJECTS (each counts 1
107
112
  * toward length/hwm and delivers undivided — Node's objectMode
108
113
  * accounting for the from() surface; hwm is 1). */
@@ -223,6 +228,7 @@ static void scr_stream_state_drop(ScrStreamState *st, bool gc) {
223
228
  for (size_t i = 0; i < st->r.n; i++) scr_stream_entry_release(st, st->r.buf[i]);
224
229
  free(st->r.buf);
225
230
  if (st->r.enc) scr_str_release(st->r.enc);
231
+ if (st->r.push_enc) scr_str_release(st->r.push_enc);
226
232
  if (!gc && st->r.next_waiter) scr_promise_release(st->r.next_waiter);
227
233
  if (!gc) {
228
234
  if (st->r.read_cb) scr_closure_release(st->r.read_cb);
@@ -350,6 +356,8 @@ typedef struct ScrStreamTick {
350
356
  static ScrStreamTick *scr_st_head = NULL;
351
357
  static ScrStreamTick *scr_st_tail = NULL;
352
358
 
359
+ static void scr_stream_dispatch_one(void);
360
+
353
361
  static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*/,
354
362
  ScrClosure *cb /*moves*/) {
355
363
  ScrStreamTick *t = calloc(1, sizeof *t);
@@ -361,6 +369,13 @@ static void scr_st_tick(ScrStream *s, ScrStreamTickOp op, ScrError *err /*moves*
361
369
  if (scr_st_tail) scr_st_tail->next = t;
362
370
  else scr_st_head = t;
363
371
  scr_st_tail = t;
372
+ /* One marker per tick on the USER nextTick queue: stream emissions are
373
+ * process.nextTicks in Node (resume_, emitReadable_, endReadableNT,
374
+ * afterWrite, ...), so they must interleave with user nextTicks in
375
+ * enqueue order — a `push(); on('data'); process.nextTick(assert)`
376
+ * sequence sees its data before the assert runs. The station dispatch
377
+ * below stays as the drain of anything a marker never reached. */
378
+ scr_next_tick_raw(&scr_stream_dispatch_one);
364
379
  }
365
380
 
366
381
  static bool scr_stream_ticks_pending(void) { return scr_st_head != NULL; }
@@ -771,7 +786,7 @@ static void scr_stream_resume_kick(ScrStream *s) {
771
786
  * parameters implicitly-any: the compiler-emitted invoke thunks box the
772
787
  * chunk/encoding/error into dyn, and the COMPLETION callback arrives as
773
788
  * a callable dyn function whose glue lands here — arguments arrive as
774
- * borrowed DOM values: the error is null/undefined (none), an
789
+ * borrowed dyn values: the error is null/undefined (none), an
775
790
  * error-shaped object ({message}), or a string; transform/flush data is
776
791
  * bytes, a string, or absent. clo->caps[0] boxes the retained stream. */
777
792
 
@@ -855,7 +870,7 @@ ScrDyn *scr_stream_done_dyn_l(ScrClosure *clo, ScrDyn *const *args, size_t argc)
855
870
  }
856
871
 
857
872
  /* Checked-dynamic chunks (the JS lane's push/write of any-typed values):
858
- * dispatch by DOM tag — bytes copy out, strings convert utf8, null takes
873
+ * dispatch by dyn tag — bytes copy out, strings convert utf8, null takes
859
874
  * the null path (EOF for push; ERR_STREAM_NULL_VALUES for write), and
860
875
  * anything else throws the write TypeError (Node would throw
861
876
  * ERR_INVALID_ARG_TYPE — approximated by the same shape). Borrow d. */
@@ -1222,6 +1237,24 @@ static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r,
1222
1237
  s->reg = NULL;
1223
1238
  s->cls = cls;
1224
1239
  s->st = scr_stream_state_new(has_r, has_w, rhwm, whwm, auto_destroy, emit_close, allow_half_open);
1240
+ /* Node's stream constructors pre-create their known _events keys (a V8
1241
+ * shape optimization) — eventNames() lists these BEFORE user events
1242
+ * added earlier. Same names, same order: Readable close/error/data/end/
1243
+ * readable, Writable close/error/prefinish/finish/drain, Duplex the
1244
+ * union (writable trio first — Node's observed key order). */
1245
+ ScrEmitter *em = (ScrEmitter *)s;
1246
+ scr_emitter_reserve(em, "close");
1247
+ scr_emitter_reserve(em, "error");
1248
+ if (has_w) {
1249
+ scr_emitter_reserve(em, "prefinish");
1250
+ scr_emitter_reserve(em, "finish");
1251
+ scr_emitter_reserve(em, "drain");
1252
+ }
1253
+ if (has_r) {
1254
+ scr_emitter_reserve(em, "data");
1255
+ scr_emitter_reserve(em, "end");
1256
+ scr_emitter_reserve(em, "readable");
1257
+ }
1225
1258
  scr_obj_alloc_note();
1226
1259
  return s;
1227
1260
  }
@@ -1498,7 +1531,7 @@ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlain
1498
1531
  * keys are ignored exactly like Node), and callback slots holding
1499
1532
  * callable values (Node's `typeof === 'function'` guard — anything else
1500
1533
  * leaves the slot to the prototype/fallback) bind as closures whose one
1501
- * cap boxes the dyn callable; the invs below build the DOM arguments the
1534
+ * cap boxes the dyn callable; the invs below build the dyn arguments the
1502
1535
  * way the compiler-emitted dyn thunks do (chunk as Buffer-flavored bytes,
1503
1536
  * encoding 'buffer', the error via the boundary encoding, the completion
1504
1537
  * callback as a callable dyn over the *_done glue). Options that Node's
@@ -2193,6 +2226,133 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams) {
2193
2226
  return p;
2194
2227
  }
2195
2228
 
2229
+ /* ── node:stream/consumers ────────────────────────────────────────────
2230
+ * text/json/buffer over the readable machinery: a native 'data' listener
2231
+ * accumulates every chunk (Buffer chunks as-is; string chunks — an
2232
+ * encoded stream or Readable.from strings — as their utf8 bytes, the
2233
+ * Blob rule Node's consumers share for whole-stream accumulation), and
2234
+ * the finished watcher settles at the terminal point — the accumulated
2235
+ * result on a clean end (right after 'close', Node's own timing: the
2236
+ * consumer's async iterator completes through eos), the stream's error,
2237
+ * or ERR_STREAM_PREMATURE_CLOSE on an early close — and marks lifecycle
2238
+ * errors handled, exactly like the iterator's eos registration. Both
2239
+ * closures share the cap layout: caps[0] the pending promise, caps[1]
2240
+ * the chunk list (Buffer entries). */
2241
+
2242
+ enum { SCR_SC_TEXT = 0, SCR_SC_JSON = 1, SCR_SC_BUFFER = 2 };
2243
+
2244
+ static void scr_sc_settle_ok(ScrClosure *cb, int kind) {
2245
+ ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
2246
+ ScrArr *chunks = scr_box_get_ref(cb->caps[1]); /* +1 */
2247
+ ScrBytes *all = scr_bytes_concat(chunks);
2248
+ scr_arr_release(chunks);
2249
+ if (kind == SCR_SC_BUFFER) {
2250
+ scr_promise_fulfill_ref(p, all, scr_bytes_retain_v, scr_bytes_release_v, NULL);
2251
+ scr_promise_release(p);
2252
+ return;
2253
+ }
2254
+ ScrStr *enc = scr_str_new("utf8", 4);
2255
+ ScrStr *text = scr_bytes_to_str(all, enc); /* U+FFFD per invalid subpart */
2256
+ scr_str_release(enc);
2257
+ scr_bytes_release(all);
2258
+ if (kind == SCR_SC_TEXT) {
2259
+ scr_promise_fulfill_str(p, text); /* moves */
2260
+ } else {
2261
+ ScrDyn *doc = scr_json_parse(text);
2262
+ scr_str_release(text);
2263
+ if (doc == NULL) {
2264
+ /* the parse's SyntaxError rides the cell — the rejection, like
2265
+ * Node's json() rejecting with JSON.parse's throw */
2266
+ scr_promise_reject_pending(p);
2267
+ } else {
2268
+ scr_promise_fulfill_ref(p, doc, scr_dyn_retain_v, scr_dyn_release_v, NULL);
2269
+ }
2270
+ }
2271
+ scr_promise_release(p);
2272
+ }
2273
+
2274
+ /* The 'data' accumulation (the emit ABI's two payload slots — exactly
2275
+ * one non-NULL). */
2276
+ static void scr_sc_data(ScrClosure *cb, void *b, void *str) {
2277
+ ScrArr *chunks = scr_box_get_ref(cb->caps[1]); /* +1 */
2278
+ if (b != NULL) {
2279
+ scr_arr_push_ref(chunks, scr_bytes_retain((ScrBytes *)b));
2280
+ } else if (str != NULL) {
2281
+ ScrStr *enc = scr_str_new("utf8", 4);
2282
+ scr_arr_push_ref(chunks, scr_bytes_from_str((ScrStr *)str, enc));
2283
+ scr_str_release(enc);
2284
+ }
2285
+ scr_arr_release(chunks);
2286
+ }
2287
+
2288
+ static void scr_sc_fin(ScrClosure *cb, ScrError *err, int kind) {
2289
+ if (err != NULL) {
2290
+ ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
2291
+ scr_throw_obj(scr_error_retain(err), &scr_error_retain_v, &scr_error_release_v,
2292
+ scr_error_trace_arg());
2293
+ scr_promise_reject_pending(p);
2294
+ scr_promise_release(p);
2295
+ return;
2296
+ }
2297
+ scr_sc_settle_ok(cb, kind);
2298
+ }
2299
+
2300
+ static void scr_sc_fin_text(ScrClosure *cb, ScrStream *s, ScrError *err) {
2301
+ (void)s;
2302
+ scr_sc_fin(cb, err, SCR_SC_TEXT);
2303
+ }
2304
+ static void scr_sc_fin_json(ScrClosure *cb, ScrStream *s, ScrError *err) {
2305
+ (void)s;
2306
+ scr_sc_fin(cb, err, SCR_SC_JSON);
2307
+ }
2308
+ static void scr_sc_fin_buffer(ScrClosure *cb, ScrStream *s, ScrError *err) {
2309
+ (void)s;
2310
+ scr_sc_fin(cb, err, SCR_SC_BUFFER);
2311
+ }
2312
+
2313
+ static ScrClosure *scr_sc_closure(void *fn, ScrPromise *p, ScrArr *chunks) {
2314
+ ScrClosure *c = scr_closure_new(fn, 2);
2315
+ c->caps[0] = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v, scr_promise_trace_v);
2316
+ scr_box_set_ref(c->caps[0], scr_promise_retain(p));
2317
+ c->caps[1] = scr_box_new_obj(scr_arr_retain_v, scr_arr_release_v, NULL);
2318
+ scr_box_set_ref(c->caps[1], scr_arr_retain(chunks));
2319
+ return c;
2320
+ }
2321
+
2322
+ static ScrPromise *scr_sc_consume(ScrStream *s, int kind) {
2323
+ ScrPromise *p = scr_promise_new();
2324
+ if (s->st == NULL || !s->st->has_r) {
2325
+ /* Node's consumers for-await the argument; a stream with no readable
2326
+ * side has no async iterator — the TypeError rejects. */
2327
+ static const char msg[] = "stream is not async iterable";
2328
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
2329
+ scr_promise_reject_pending(p);
2330
+ return p;
2331
+ }
2332
+ ScrArr *chunks = scr_arr_new_ref(scr_bytes_retain_v, scr_bytes_release_v, NULL, 4);
2333
+ ScrStreamErrInv fin_inv = kind == SCR_SC_TEXT ? &scr_sc_fin_text
2334
+ : kind == SCR_SC_JSON ? &scr_sc_fin_json
2335
+ : &scr_sc_fin_buffer;
2336
+ /* The terminal watcher first (it marks lifecycle errors handled); the
2337
+ * promise form exposes no unhook — the cleanup closure drops. */
2338
+ ScrClosure *cleanup = scr_stream_finished(s, scr_sc_closure((void *)fin_inv, p, chunks), fin_inv);
2339
+ scr_closure_release(cleanup);
2340
+ ScrStr *dn = scr_str_new("data", 4);
2341
+ scr_emitter_release(scr_emitter_on((ScrEmitter *)s, dn,
2342
+ scr_sc_closure((void *)&scr_sc_data, p, chunks),
2343
+ scr_ee_inv_fixed2, false, false));
2344
+ scr_str_release(dn);
2345
+ /* resume() rather than the 'data' hook alone: Node's consumer pulls
2346
+ * through the iterator, which drains a PAUSED stream too. */
2347
+ scr_stream_release(scr_stream_resume(s));
2348
+ scr_arr_release(chunks);
2349
+ return p;
2350
+ }
2351
+
2352
+ ScrPromise *scr_sc_text(ScrStream *s) { return scr_sc_consume(s, SCR_SC_TEXT); }
2353
+ ScrPromise *scr_sc_json(ScrStream *s) { return scr_sc_consume(s, SCR_SC_JSON); }
2354
+ ScrPromise *scr_sc_buffer(ScrStream *s) { return scr_sc_consume(s, SCR_SC_BUFFER); }
2355
+
2196
2356
  /* ── the readable surface ─────────────────────────────────────────────── */
2197
2357
 
2198
2358
  bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
@@ -2200,13 +2360,41 @@ bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
2200
2360
  }
2201
2361
 
2202
2362
  bool scr_stream_push_str(ScrStream *s, ScrStr *str) {
2203
- ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
2204
- memcpy(b->data, str->data, str->len);
2363
+ ScrStr *enc = s->st->has_r ? s->st->r.push_enc : NULL;
2364
+ ScrBytes *b;
2365
+ if (enc != NULL) {
2366
+ /* Node: Buffer.from(chunk, state.defaultEncoding). */
2367
+ b = scr_bytes_from_str(str, enc);
2368
+ } else {
2369
+ b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
2370
+ memcpy(b->data, str->data, str->len);
2371
+ }
2372
+ bool ret = scr_stream_add_chunk(s, b, false);
2373
+ scr_bytes_release(b);
2374
+ return ret;
2375
+ }
2376
+
2377
+ /* push(chunk, encoding) with an explicit non-utf8 literal: the per-call
2378
+ * encoding overrides the stream's default. Borrows both strings. */
2379
+ bool scr_stream_push_str_enc(ScrStream *s, ScrStr *str, ScrStr *enc) {
2380
+ ScrBytes *b = scr_bytes_from_str(str, enc);
2205
2381
  bool ret = scr_stream_add_chunk(s, b, false);
2206
2382
  scr_bytes_release(b);
2207
2383
  return ret;
2208
2384
  }
2209
2385
 
2386
+ /* The defaultEncoding option's push side (canonical literal, frontend-
2387
+ * folded; never "utf8" — that stays the NULL fast path). Answers the
2388
+ * receiver +1, the setEncoding chaining shape. */
2389
+ ScrStream *scr_stream_set_push_encoding(ScrStream *s, ScrStr *enc) {
2390
+ ScrStreamState *st = s->st;
2391
+ if (st->has_r) {
2392
+ if (st->r.push_enc) scr_str_release(st->r.push_enc);
2393
+ st->r.push_enc = scr_str_retain(enc);
2394
+ }
2395
+ return scr_stream_retain(s);
2396
+ }
2397
+
2210
2398
  bool scr_stream_push_null(ScrStream *s) {
2211
2399
  ScrStreamState *st = s->st;
2212
2400
  if (!st->has_r || st->destroyed) return false;
@@ -2850,7 +3038,7 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
2850
3038
  /* Fully closed: nothing calls the option callbacks again (Node's
2851
3039
  * contract), so drop them here — this breaks the reference cycle
2852
3040
  * a callback capturing its own stream forms, including the dyn-
2853
- * options closures whose stream edge rides through a DOM function
3041
+ * options closures whose stream edge rides through a dyn function
2854
3042
  * value the cycle collector cannot traverse. */
2855
3043
  if (st->r.read_cb) { scr_closure_release(st->r.read_cb); st->r.read_cb = NULL; }
2856
3044
  if (st->w.write_cb) { scr_closure_release(st->w.write_cb); st->w.write_cb = NULL; }
@@ -2891,6 +3079,25 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
2891
3079
  }
2892
3080
  }
2893
3081
 
3082
+ static void scr_stream_dispatch(void);
3083
+
3084
+ /* One tick-marker's dispatch: the queue's FIFO head (markers and entries
3085
+ * are enqueued 1:1 in the same order). After an uncaught throw the
3086
+ * remaining entries drop through the station's exc branch below, keeping
3087
+ * the RC audit clean. */
3088
+ static void scr_stream_dispatch_one(void) {
3089
+ ScrStreamTick *t = scr_st_head;
3090
+ if (t == NULL || scr_exc_pending()) return;
3091
+ scr_st_head = t->next;
3092
+ if (scr_st_head == NULL) scr_st_tail = NULL;
3093
+ scr_stream_run_tick(t);
3094
+ scr_stream_release(t->s);
3095
+ if (t->err) scr_error_release(t->err);
3096
+ if (t->cb) scr_closure_release(t->cb);
3097
+ free(t);
3098
+ if (scr_exc_pending()) scr_stream_dispatch(); /* its exc branch drops the rest */
3099
+ }
3100
+
2894
3101
  static void scr_stream_dispatch(void) {
2895
3102
  while (scr_st_head != NULL && !scr_exc_pending() && !scr_loop_has_ready()) {
2896
3103
  ScrStreamTick *t = scr_st_head;
package/src/scr_tls.c CHANGED
@@ -984,7 +984,7 @@ ScrHttpClientReq *scr_https_request(ScrStr *host /*borrowed*/, double port,
984
984
  }
985
985
 
986
986
  /* ── RUNTIME options records (the divergence-66 stance) ────────────────
987
- * A non-literal options value — the checked-dynamic JS lane's DOM record
987
+ * A non-literal options value — the checked-dynamic JS lane's dyn record
988
988
  * — reads its members at RUNTIME. Members the literal path lowers take
989
989
  * the same lowering; members whose literal forms are compile fences
990
990
  * become runtime gates that THROW the catchable fence instead of
@@ -1002,7 +1002,7 @@ static void scr_tls_opt_fence(const char *api, const char *key, const char *hint
1002
1002
  scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b));
1003
1003
  }
1004
1004
 
1005
- /* JS truthiness over the DOM kinds — Node reads requestCert /
1005
+ /* JS truthiness over the dyn kinds — Node reads requestCert /
1006
1006
  * rejectUnauthorized through `if (options.x)`. */
1007
1007
  static bool scr_tls_dyn_truthy(const ScrDyn *v) {
1008
1008
  switch (v->kind) {
@@ -1219,7 +1219,7 @@ static bool scr_tls_opts_validate(const ScrDyn *opts) {
1219
1219
  }
1220
1220
 
1221
1221
  /* The server-options walk. Fills the cert/key out-params (+1 each) from a
1222
- * DOM options record; false = exception pending (partial results released). */
1222
+ * dyn options record; false = exception pending (partial results released). */
1223
1223
  static bool scr_tls_srv_opts_walk(const ScrDyn *opts, const char *api, ScrBytes **cert_out,
1224
1224
  ScrBytes **key_out) {
1225
1225
  *cert_out = NULL;
package/src/scr_web.c CHANGED
@@ -791,7 +791,7 @@ static const char web_prelude[] =
791
791
  " [Symbol.iterator]() { return this.entries(); }\n"
792
792
  " }\n"
793
793
  "\n"
794
- /* Event + EventTarget + CustomEvent — the DOM event plumbing Node
794
+ /* Event + EventTarget + CustomEvent — the dyn event plumbing Node
795
795
  * exposes as globals since v15. Synchronous dispatch on one target
796
796
  * (no tree, no phases — composedPath answers []), once/capture-shaped
797
797
  * options accepted, handleEvent objects honored, dispatchEvent