@scriptc/runtime 0.0.9 → 0.0.10

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_dgram.c CHANGED
@@ -64,6 +64,7 @@
64
64
 
65
65
  #include <errno.h>
66
66
  #include <fcntl.h>
67
+ #include <math.h>
67
68
  #include <stdio.h>
68
69
  #include <stdlib.h>
69
70
  #include <string.h>
@@ -466,7 +467,18 @@ ScrDgramSocket *scr_dgram_create(bool reuse_addr) {
466
467
  }
467
468
 
468
469
  static void scr_dgram_throw(const char *msg) {
469
- scr_throw_error_msg(0 /* Error */, msg, strlen(msg));
470
+ /* Node's state errors carry their ERR_SOCKET_* codes; the message IS
471
+ * the discriminant (each arm throws exactly one text). */
472
+ const char *code =
473
+ strcmp(msg, "Already connected") == 0 ? "ERR_SOCKET_DGRAM_IS_CONNECTED"
474
+ : strcmp(msg, "Not running") == 0 ? "ERR_SOCKET_DGRAM_NOT_RUNNING"
475
+ : strcmp(msg, "Socket is already bound") == 0 ? "ERR_SOCKET_ALREADY_BOUND"
476
+ : NULL;
477
+ if (code != NULL) {
478
+ scr_throw_error_msg_code(0 /* Error */, msg, strlen(msg), code);
479
+ } else {
480
+ scr_throw_error_msg(0 /* Error */, msg, strlen(msg));
481
+ }
470
482
  }
471
483
 
472
484
  /* Resolve a numeric host into a sockaddr_in; "" means any (bind's
@@ -1005,3 +1017,166 @@ void scr_dgram_install(void) {
1005
1017
  atexit(scr_dgram_cleanup_atexit);
1006
1018
  scr_loop_set_dgram(&scr_dgram_pending, &scr_dgram_dispatch, &scr_dgram_pollfd);
1007
1019
  }
1020
+
1021
+ /* ── the send argument-validation ladder (checked-dynamic lane) ─────────
1022
+ * Node's Socket.prototype.send signature shuffle and validation order
1023
+ * over DOM arguments, byte-for-byte: the connected/unconnected split
1024
+ * decides whether (a1, a2) are an offset/length slice or the port/address
1025
+ * pair; sliceBuffer validates the buffer's type and bounds
1026
+ * (ERR_BUFFER_OUT_OF_BOUNDS); list payloads validate per element with the
1027
+ * LIST as the Received tail; unconnected sends validate the port
1028
+ * (ERR_SOCKET_BAD_PORT, > 0 and < 65536 with the specific-type tail and
1029
+ * Node's trailing period) and the address's string contract; a send with
1030
+ * a port or address on a connected socket answers ERR_SOCKET_DGRAM_IS_
1031
+ * CONNECTED. A fully-validated unconnected single-payload send RUNS —
1032
+ * the callback form and the connected sends keep the compiler-rendered
1033
+ * fence. All dyn arguments borrowed. */
1034
+
1035
+ static bool scr_dgram_dyn_truthy(const ScrDyn *v) {
1036
+ switch (v->kind) {
1037
+ case SCR_DYN_UNDEF:
1038
+ case SCR_DYN_NULL: return false;
1039
+ case SCR_DYN_BOOL: return v->v.b;
1040
+ case SCR_DYN_NUM: return v->v.num == v->v.num && v->v.num != 0;
1041
+ case SCR_DYN_STR: return v->v.str->len > 0;
1042
+ default: return true;
1043
+ }
1044
+ }
1045
+
1046
+ static uint32_t scr_dgram_to_u32(const ScrDyn *v) {
1047
+ if (v->kind != SCR_DYN_NUM) return 0; /* >>> 0 over the ladder's shapes */
1048
+ double t = v->v.num;
1049
+ if (t != t || isinf(t)) return 0;
1050
+ t = trunc(t);
1051
+ t = fmod(t, 4294967296.0);
1052
+ if (t < 0) t += 4294967296.0;
1053
+ return (uint32_t)t;
1054
+ }
1055
+
1056
+ static const char SCR_DGRAM_BUF_EXPECTED[] =
1057
+ "of type string or an instance of Buffer, TypedArray, or DataView";
1058
+
1059
+ void scr_dgram_send_chk(ScrDgramSocket *s, const ScrDyn *buffer, const ScrDyn *a1,
1060
+ const ScrDyn *a2, const ScrDyn *a3, const ScrDyn *a4,
1061
+ const ScrStr *fence) {
1062
+ const ScrDyn *offset = a1, *length = a2, *port = a3, *address = a4;
1063
+ const ScrDyn *callback = scr_dyn_undefined();
1064
+ bool connected = s->connected;
1065
+ bool sliced = false;
1066
+ if (!connected) {
1067
+ if (scr_dgram_dyn_truthy(address) ||
1068
+ (scr_dgram_dyn_truthy(port) && port->kind != SCR_DYN_FUNC)) {
1069
+ sliced = true;
1070
+ } else {
1071
+ callback = port;
1072
+ port = offset;
1073
+ address = length;
1074
+ }
1075
+ } else {
1076
+ if (length->kind == SCR_DYN_NUM) {
1077
+ sliced = true;
1078
+ if (port->kind == SCR_DYN_FUNC) callback = port;
1079
+ } else {
1080
+ callback = offset;
1081
+ port = a3;
1082
+ address = a4;
1083
+ }
1084
+ }
1085
+ size_t slice_off = 0, slice_len = 0;
1086
+ if (sliced) {
1087
+ double bytelen;
1088
+ if (buffer->kind == SCR_DYN_STR) bytelen = (double)buffer->v.str->len;
1089
+ else if (buffer->kind == SCR_DYN_BYTES) bytelen = scr_bytes_byte_len(buffer->v.bytes);
1090
+ else {
1091
+ scr_dyn_arg_type_fail("buffer", SCR_DGRAM_BUF_EXPECTED, buffer);
1092
+ return;
1093
+ }
1094
+ uint32_t off = scr_dgram_to_u32(offset);
1095
+ uint32_t len = scr_dgram_to_u32(length);
1096
+ if ((double)off > bytelen) {
1097
+ static const char msg[] = "\"offset\" is outside of buffer bounds";
1098
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, sizeof msg - 1, "ERR_BUFFER_OUT_OF_BOUNDS");
1099
+ return;
1100
+ }
1101
+ if ((double)off + (double)len > bytelen) {
1102
+ static const char msg[] = "\"length\" is outside of buffer bounds";
1103
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, sizeof msg - 1, "ERR_BUFFER_OUT_OF_BOUNDS");
1104
+ return;
1105
+ }
1106
+ slice_off = off;
1107
+ slice_len = len;
1108
+ } else if (buffer->kind == SCR_DYN_ARR) {
1109
+ for (size_t i = 0; i < buffer->v.arr.len; i++) {
1110
+ const ScrDyn *e = buffer->v.arr.items[i];
1111
+ if (e->kind != SCR_DYN_STR && e->kind != SCR_DYN_BYTES) {
1112
+ scr_dyn_arg_type_fail("buffer list arguments", SCR_DGRAM_BUF_EXPECTED, buffer);
1113
+ return;
1114
+ }
1115
+ }
1116
+ } else if (buffer->kind != SCR_DYN_STR && buffer->kind != SCR_DYN_BYTES) {
1117
+ scr_dyn_arg_type_fail("buffer", SCR_DGRAM_BUF_EXPECTED, buffer);
1118
+ return;
1119
+ }
1120
+ if (connected) {
1121
+ if (scr_dgram_dyn_truthy(port) || scr_dgram_dyn_truthy(address)) {
1122
+ scr_dgram_throw("Already connected");
1123
+ return;
1124
+ }
1125
+ scr_throw_lowering_fence(fence); /* connected sends have no lowering yet */
1126
+ return;
1127
+ }
1128
+ /* validatePort(port, 'Port', false): integers (or numeric strings)
1129
+ * strictly between 0 and 65536; everything else renders the specific
1130
+ * type with Node's trailing period. */
1131
+ double portnum = -1;
1132
+ {
1133
+ bool ok = false;
1134
+ if (port->kind == SCR_DYN_NUM && trunc(port->v.num) == port->v.num &&
1135
+ port->v.num > 0 && port->v.num < 65536) {
1136
+ ok = true;
1137
+ portnum = port->v.num;
1138
+ } else if (port->kind == SCR_DYN_STR && port->v.str->len > 0) {
1139
+ ScrStr *ps = scr_str_retain(port->v.str);
1140
+ double n = scr_string_to_number(ps);
1141
+ scr_str_release(ps);
1142
+ if (n == n && trunc(n) == n && n > 0 && n < 65536) {
1143
+ ok = true;
1144
+ portnum = n;
1145
+ }
1146
+ }
1147
+ if (!ok) {
1148
+ char detail[64], msg[160];
1149
+ const char *d = scr_dyn_specific_type(port, detail, sizeof detail);
1150
+ int len = snprintf(msg, sizeof msg, "Port should be > 0 and < 65536. Received %s.", d);
1151
+ scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_SOCKET_BAD_PORT");
1152
+ return;
1153
+ }
1154
+ }
1155
+ if (address->kind == SCR_DYN_FUNC) {
1156
+ callback = address;
1157
+ address = scr_dyn_undefined();
1158
+ } else if (address->kind != SCR_DYN_UNDEF && address->kind != SCR_DYN_NULL &&
1159
+ address->kind != SCR_DYN_STR) {
1160
+ /* Node's gate is null/undefined-only: a falsy 0 still throws. */
1161
+ scr_dyn_arg_type_fail("address", "of type string", address);
1162
+ return;
1163
+ }
1164
+ if (callback->kind == SCR_DYN_FUNC || buffer->kind == SCR_DYN_ARR) {
1165
+ /* completion callbacks and list concatenation have no lowering yet —
1166
+ * refuse loudly after the full validation ladder, never drop */
1167
+ scr_throw_lowering_fence(fence);
1168
+ return;
1169
+ }
1170
+ const char *data = buffer->kind == SCR_DYN_STR ? buffer->v.str->data
1171
+ : (const char *)buffer->v.bytes->data;
1172
+ size_t datalen = buffer->kind == SCR_DYN_STR ? buffer->v.str->len
1173
+ : (size_t)scr_bytes_byte_len(buffer->v.bytes);
1174
+ if (sliced) {
1175
+ data += slice_off;
1176
+ datalen = slice_len;
1177
+ }
1178
+ ScrStr *host = address->kind == SCR_DYN_STR ? scr_str_retain(address->v.str)
1179
+ : scr_str_new("127.0.0.1", 9);
1180
+ scr_dgram_send_raw(s, data, datalen, portnum, host);
1181
+ scr_str_release(host);
1182
+ }
@@ -114,6 +114,11 @@ static void scr_dyn_display_buf(ScrJsonBuf *b, const ScrDyn *d) {
114
114
  * for a bare promise is "[object Promise]". */
115
115
  scr_jb_puts(b, "[object Promise]");
116
116
  return;
117
+ case SCR_DYN_JSVAL:
118
+ /* The engine's own ToString (a bridged failure leaves the exception
119
+ * pending and appends nothing — the loud path). */
120
+ scr_dyn_isl_tostr_buf(b, d);
121
+ return;
117
122
  }
118
123
  }
119
124
 
@@ -186,6 +191,12 @@ static ScrDyn *dyn_call_cb(ScrDyn *cb, ScrDyn *item, size_t i, ScrDyn *recv) {
186
191
  static bool dyn_cb_check(ScrDyn *const *args, size_t argc) {
187
192
  ScrDyn *cb = argc > 0 ? args[0] : scr_dyn_undefined();
188
193
  if (cb->kind == SCR_DYN_FUNC) return true;
194
+ /* An ENGINE function is callable — scr_dyn_call's JSVAL arm routes it
195
+ * (the loops below call through scr_dyn_call, which converts the DOM
196
+ * element arguments per the uniform crossing). A wrapped NON-function
197
+ * falls through to the display path: String(cb) renders through the
198
+ * engine, exactly Node's message. */
199
+ if (cb->kind == SCR_DYN_JSVAL && scr_dyn_isl_typeof_is(cb, "function")) return true;
189
200
  ScrJsonBuf b;
190
201
  scr_jb_init(&b);
191
202
  scr_dyn_display_buf(&b, cb);
@@ -289,7 +300,7 @@ static bool dyn_arr_sort(ScrDyn *recv, ScrDyn *cmp) {
289
300
  /* Names each prototype declares BEYOND what's implemented here — these
290
301
  * fence loudly instead of mis-answering "is not a function". */
291
302
  static bool dyn_arr_proto_unimpl(const char *m) {
292
- static const char *names[] = { "splice", "reduce", "reduceRight", "flat", "flatMap",
303
+ static const char *names[] = { "splice", "reduce", "reduceRight", "flat",
293
304
  "fill", "copyWithin", "keys", "values", "entries", "toReversed", "toSorted", "toSpliced",
294
305
  "with", "toString", "toLocaleString", NULL };
295
306
  for (size_t i = 0; names[i]; i++) if (dyn_name_is(m, names[i])) return true;
@@ -325,11 +336,25 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
325
336
  return scr_dynh_dispatch(recv, method, args, argc, what);
326
337
  }
327
338
 
339
+ /* Island-held receivers: the ENGINE runs its own prototypes (JS-exact
340
+ * flatMap/map/forEach/filter — Array.prototype is the engine's) through
341
+ * scr_jsval_call_method; arguments cross per the uniform conversion
342
+ * (wrapped cells by reference, DOM data deep-copied, FUNC boxes through
343
+ * the host shim), a missing member throws the engine's own TypeError,
344
+ * and the result wraps back scalar-normalized. `what` is unused — the
345
+ * engine's message names the failure. */
346
+ if (recv->kind == SCR_DYN_JSVAL) {
347
+ return scr_dyn_jsval_ops()->invoke(recv->v.jsval.cell, method, args, argc, what);
348
+ }
349
+
328
350
  /* OBJ: the own member calls (own properties shadow prototypes in JS
329
351
  * too); anything else is Node's is-not-a-function. */
330
352
  if (recv->kind == SCR_DYN_OBJ) {
331
353
  ScrDyn *m = scr_dyn_obj_get(recv, method, strlen(method));
332
- if (m && m->kind == SCR_DYN_FUNC) {
354
+ if (m && (m->kind == SCR_DYN_FUNC ||
355
+ /* a WRAPPED engine function stored as a DOM member: the
356
+ * routed call (scr_dyn_call's JSVAL arm) runs it. */
357
+ (m->kind == SCR_DYN_JSVAL && scr_dyn_isl_typeof_is(m, "function")))) {
333
358
  /* JS binds the receiver for the call (`obj.method()` — this === obj):
334
359
  * the ambient-receiver window (scr_runtime.h). */
335
360
  scr_dyn_this_push_dyn(recv);
@@ -374,6 +399,23 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
374
399
  dyn_throw_unsupported("Function", method);
375
400
  return NULL;
376
401
  }
402
+ /* An OWN property on the FUNC box (defineProperties writes — the
403
+ * mustCall-wrapper expando family): a callable member runs with the
404
+ * box bound (JS's o.m() receiver), everything else keeps Node's
405
+ * is-not-a-function. */
406
+ {
407
+ ScrDyn *own = scr_dyn_fn_get(recv, method, strlen(method));
408
+ if (own) {
409
+ if (own->kind == SCR_DYN_FUNC || own->kind == SCR_DYN_JSVAL) {
410
+ scr_dyn_this_push_dyn(recv);
411
+ ScrDyn *r = scr_dyn_call(own, args, argc, what);
412
+ scr_dyn_this_pop();
413
+ scr_dyn_release(own);
414
+ return r;
415
+ }
416
+ scr_dyn_release(own);
417
+ }
418
+ }
377
419
  dyn_throw_not_fn(what);
378
420
  return NULL;
379
421
  }
@@ -541,6 +583,53 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
541
583
  if (dyn_name_is(method, "findIndex")) return scr_dyn_new_num(-1);
542
584
  return scr_dyn_retain(scr_dyn_undefined()); /* forEach */
543
585
  }
586
+ if (dyn_name_is(method, "flatMap")) {
587
+ /* JS Array.prototype.flatMap over a DOM array: map + a depth-1
588
+ * flatten. Native DOM-array results flatten element-by-element; a
589
+ * WRAPPED engine array flattens through the routed keyed reads
590
+ * (elements wrap back scalar-normalized); everything else pushes
591
+ * as a single element (JS keeps non-array callback results whole).
592
+ * JS's spec snapshots the length up front (elements appended by
593
+ * the callback are not visited). */
594
+ if (!dyn_cb_check(args, argc)) return NULL;
595
+ ScrDyn *cb = args[0];
596
+ ScrDyn *out = scr_dyn_new_arr();
597
+ size_t n = recv->v.arr.len;
598
+ for (size_t i = 0; i < n && i < recv->v.arr.len; i++) {
599
+ ScrDyn *item = scr_dyn_retain(recv->v.arr.items[i]);
600
+ ScrDyn *r = dyn_call_cb(cb, item, i, recv);
601
+ scr_dyn_release(item);
602
+ if (!r) { scr_dyn_release(out); return NULL; }
603
+ if (r->kind == SCR_DYN_ARR) {
604
+ for (size_t j = 0; j < r->v.arr.len; j++) {
605
+ scr_dyn_arr_push(out, scr_dyn_retain(r->v.arr.items[j]));
606
+ }
607
+ scr_dyn_release(r);
608
+ } else if (scr_dyn_isl_is_array(r)) {
609
+ /* An engine-array result: length + element reads through the
610
+ * routed engine ops (a bridged surprise unwinds). */
611
+ ScrStr *lk = scr_str_new("length", 6);
612
+ ScrDyn *lenv = scr_dyn_isl_key_get(r, lk);
613
+ scr_str_release(lk);
614
+ if (!lenv) { scr_dyn_release(r); scr_dyn_release(out); return NULL; }
615
+ size_t rn = lenv->kind == SCR_DYN_NUM ? (size_t)lenv->v.num : 0;
616
+ scr_dyn_release(lenv);
617
+ for (size_t j = 0; j < rn; j++) {
618
+ char idx[24];
619
+ int ilen = snprintf(idx, sizeof idx, "%zu", j);
620
+ ScrStr *jk = scr_str_new(idx, (size_t)ilen);
621
+ ScrDyn *el = scr_dyn_isl_key_get(r, jk);
622
+ scr_str_release(jk);
623
+ if (!el) { scr_dyn_release(r); scr_dyn_release(out); return NULL; }
624
+ scr_dyn_arr_push(out, el); /* ownership moves in */
625
+ }
626
+ scr_dyn_release(r);
627
+ } else {
628
+ scr_dyn_arr_push(out, r); /* ownership moves in */
629
+ }
630
+ }
631
+ return out;
632
+ }
544
633
  if (dyn_name_is(method, "sort")) {
545
634
  ScrDyn *cmp = argc > 0 ? args[0] : scr_dyn_undefined();
546
635
  if (cmp->kind != SCR_DYN_UNDEF && cmp->kind != SCR_DYN_FUNC) {
@@ -598,6 +687,11 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
598
687
  * (DOM properties are plain data properties — SEMANTICS.md); get/set
599
688
  * throw the loud unsupported Error, never a silent drop. */
600
689
  ScrDyn *scr_dyn_define_props(ScrDyn *target, ScrDyn *descs) {
690
+ /* Island-held operands ARE objects to Node — the non-object TypeError
691
+ * below would be a wrong claim. Loud fence (lane dyn-routing-ops). */
692
+ scr_dyn_isl_fence(target, "Object.defineProperties");
693
+ if (!scr_exc_pending()) scr_dyn_isl_fence(descs, "Object.defineProperties");
694
+ if (scr_exc_pending()) return NULL;
601
695
  if (target->kind != SCR_DYN_OBJ && target->kind != SCR_DYN_FUNC) {
602
696
  scr_throw_error_msg(SCR_ERR_TYPE, "Object.defineProperties called on non-object",
603
697
  strlen("Object.defineProperties called on non-object"));
package/src/scr_inspect.c CHANGED
@@ -775,8 +775,18 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
775
775
  return out;
776
776
  }
777
777
  case SCR_DYN_OBJ: {
778
- if (d->v.obj.len == 0) return scr_str_new("{}", 2);
779
- if (recurse > depth) return scr_str_new("[Object]", 8);
778
+ /* Object.create(null)'s dictionary: Node prefixes the rendering
779
+ * with "[Object: null prototype]" (formatValue's constructor-less
780
+ * base), the empty form included, and the beyond-depth answer IS
781
+ * the bare marker (where a plain object says [Object]). */
782
+ if (d->v.obj.len == 0) {
783
+ return d->null_proto ? scr_str_new("[Object: null prototype] {}", 27)
784
+ : scr_str_new("{}", 2);
785
+ }
786
+ if (recurse > depth) {
787
+ return d->null_proto ? scr_str_new("[Object: null prototype]", 24)
788
+ : scr_str_new("[Object]", 8);
789
+ }
780
790
  scr_insp_begin(recurse + 1);
781
791
  for (size_t i = 0; i < d->v.obj.len; i++) {
782
792
  ScrDynEntry *ent = &d->v.obj.entries[i];
@@ -790,7 +800,8 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
790
800
  scr_insp_entry(entry, false);
791
801
  scr_str_release(entry);
792
802
  }
793
- ScrStr *base = scr_str_new("", 0);
803
+ ScrStr *base = d->null_proto ? scr_str_new("[Object: null prototype]", 24)
804
+ : scr_str_new("", 0);
794
805
  ScrStr *b0 = scr_str_new("{", 1);
795
806
  ScrStr *b1 = scr_str_new("}", 1);
796
807
  ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, false, false);
@@ -825,6 +836,19 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
825
836
  scr_throw_error(SCR_ERR_ERROR, ib_take(&out));
826
837
  return scr_str_new("", 0);
827
838
  }
839
+ case SCR_DYN_JSVAL: {
840
+ /* An island value inside the DOM: Node prints the engine object's
841
+ * property dump — fence loudly, naming the engine typeof (the
842
+ * scr_insp_jsval wording for bare 'any' composites). */
843
+ ScrStr *t = scr_dyn_typeof(d); /* routes to the engine; +1 */
844
+ InspBuf out = {0};
845
+ ib_cstr(&out, "util.inspect of a composite 'any' value (typeof '");
846
+ ib_bytes(&out, t->data, t->len);
847
+ ib_cstr(&out, "') is not supported yet");
848
+ scr_str_release(t);
849
+ scr_throw_error(SCR_ERR_ERROR, ib_take(&out));
850
+ return scr_str_new("", 0);
851
+ }
828
852
  case SCR_DYN_PROMISE: {
829
853
  /* Node renders Promise { <pending> } / Promise { value } — the
830
854
  * settled-value rendering pulls in the whole inspector; fence