@scriptc/runtime 0.0.23 → 0.0.25

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_util.c ADDED
@@ -0,0 +1,984 @@
1
+ /* node:util's engine-free static surface. parseArgs crosses the typed
2
+ * frontend through the checked-dynamic tree: the config is JSON-safe data,
3
+ * and the result is the ordinary Node-shaped { values, positionals,
4
+ * tokens? } tree. This keeps the parser independent of every emitted
5
+ * record/union layout while the frontend's dynCheck restores the precise
6
+ * ParsedResults<T> type at the call site.
7
+ *
8
+ * The grammar follows Node 24's util.parseArgs/tokenizeArgs: long options,
9
+ * grouped shorts, inline/separate string values, strict=false unknowns,
10
+ * -- termination, negative booleans, multiple/default accumulation, and
11
+ * token metadata. All input nodes are borrowed; the result owns fresh or
12
+ * retained children. Validation/grammar failures leave a coded TypeError
13
+ * pending and return NULL. */
14
+ #include "scr_runtime.h"
15
+
16
+ #include <math.h>
17
+ #include <stdio.h>
18
+ #include <stdlib.h>
19
+ #include <string.h>
20
+
21
+ static const ScrDyn *pa_own_member(const ScrDyn *obj, const char *key) {
22
+ if (!obj || obj->kind != SCR_DYN_OBJ) return NULL;
23
+ return scr_dyn_obj_get(obj, key, strlen(key));
24
+ }
25
+
26
+ /* The ordinary optional-member read: absent and explicit undefined both
27
+ * answer no value. Descriptor fields whose validation depends on own
28
+ * presence use pa_own_member directly. */
29
+ static const ScrDyn *pa_member(const ScrDyn *obj, const char *key) {
30
+ const ScrDyn *v = pa_own_member(obj, key);
31
+ return v && v->kind != SCR_DYN_UNDEF ? v : NULL;
32
+ }
33
+
34
+ static bool pa_str_eq_bytes(const ScrStr *s, const char *p, size_t n) {
35
+ return s && s->len == n && memcmp(s->data, p, n) == 0;
36
+ }
37
+
38
+ static bool pa_str_eq_c(const ScrStr *s, const char *p) {
39
+ return pa_str_eq_bytes(s, p, strlen(p));
40
+ }
41
+
42
+ static ScrStr *pa_str_bytes(const char *p, size_t n) {
43
+ return scr_str_new(p, n);
44
+ }
45
+
46
+ static ScrDyn *pa_dyn_str(const ScrStr *s) {
47
+ return scr_dyn_new_str((ScrStr *)s);
48
+ }
49
+
50
+ static ScrDyn *pa_dyn_str_bytes(const char *p, size_t n) {
51
+ ScrStr *s = pa_str_bytes(p, n);
52
+ ScrDyn *d = pa_dyn_str(s);
53
+ scr_str_release(s);
54
+ return d;
55
+ }
56
+
57
+ static char *pa_path(const char *prefix, const char *name, size_t name_len,
58
+ const char *suffix) {
59
+ size_t pn = strlen(prefix), sn = strlen(suffix);
60
+ char *out = malloc(pn + name_len + sn + 1);
61
+ if (!out) {
62
+ fputs("scriptc: out of memory\n", stderr);
63
+ abort();
64
+ }
65
+ memcpy(out, prefix, pn);
66
+ memcpy(out + pn, name, name_len);
67
+ memcpy(out + pn + name_len, suffix, sn + 1);
68
+ return out;
69
+ }
70
+
71
+ static void pa_throw_text(ScrJsonBuf *b, const char *code) {
72
+ ScrStr *msg = scr_jb_finish(b);
73
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, code);
74
+ scr_str_release(msg);
75
+ }
76
+
77
+ static void pa_unknown(const ScrStr *raw, bool allow_positionals) {
78
+ ScrJsonBuf b;
79
+ scr_jb_init(&b);
80
+ scr_jb_puts(&b, "Unknown option '");
81
+ scr_jb_put_str(&b, raw);
82
+ scr_jb_puts(&b, "'");
83
+ if (allow_positionals) {
84
+ scr_jb_puts(&b, ". To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- \"");
85
+ scr_jb_put_str(&b, raw);
86
+ scr_jb_putc(&b, '"');
87
+ }
88
+ pa_throw_text(&b, "ERR_PARSE_ARGS_UNKNOWN_OPTION");
89
+ }
90
+
91
+ static void pa_option_label(ScrJsonBuf *b, const ScrDyn *desc,
92
+ const ScrStr *name) {
93
+ const ScrDyn *shortv = pa_member(desc, "short");
94
+ if (shortv && shortv->kind == SCR_DYN_STR && shortv->v.str->len > 0) {
95
+ scr_jb_putc(b, '-');
96
+ scr_jb_put_str(b, shortv->v.str);
97
+ scr_jb_puts(b, ", ");
98
+ }
99
+ scr_jb_puts(b, "--");
100
+ scr_jb_put_str(b, name);
101
+ }
102
+
103
+ static void pa_missing_value(const ScrDyn *desc, const ScrStr *name) {
104
+ ScrJsonBuf b;
105
+ scr_jb_init(&b);
106
+ scr_jb_puts(&b, "Option '");
107
+ pa_option_label(&b, desc, name);
108
+ scr_jb_puts(&b, " <value>' argument missing");
109
+ pa_throw_text(&b, "ERR_PARSE_ARGS_INVALID_OPTION_VALUE");
110
+ }
111
+
112
+ static void pa_ambiguous(const ScrStr *raw, const ScrStr *name,
113
+ bool is_short) {
114
+ ScrJsonBuf b;
115
+ scr_jb_init(&b);
116
+ scr_jb_puts(&b, "Option '");
117
+ scr_jb_put_str(&b, raw);
118
+ scr_jb_puts(&b, "' argument is ambiguous.\nDid you forget to specify the option argument for '");
119
+ scr_jb_put_str(&b, raw);
120
+ scr_jb_puts(&b, "'?\nTo specify an option argument starting with a dash use '--");
121
+ scr_jb_put_str(&b, name);
122
+ scr_jb_puts(&b, "=-XYZ'");
123
+ if (is_short) {
124
+ scr_jb_puts(&b, " or '");
125
+ scr_jb_put_str(&b, raw);
126
+ scr_jb_puts(&b, "-XYZ'");
127
+ }
128
+ scr_jb_puts(&b, ".");
129
+ pa_throw_text(&b, "ERR_PARSE_ARGS_INVALID_OPTION_VALUE");
130
+ }
131
+
132
+ static void pa_takes_no_value(const ScrDyn *desc, const ScrStr *name) {
133
+ ScrJsonBuf b;
134
+ scr_jb_init(&b);
135
+ scr_jb_puts(&b, "Option '");
136
+ pa_option_label(&b, desc, name);
137
+ scr_jb_puts(&b, "' does not take an argument");
138
+ pa_throw_text(&b, "ERR_PARSE_ARGS_INVALID_OPTION_VALUE");
139
+ }
140
+
141
+ static void pa_unexpected(const ScrStr *arg) {
142
+ ScrJsonBuf b;
143
+ scr_jb_init(&b);
144
+ scr_jb_puts(&b, "Unexpected argument '");
145
+ scr_jb_put_str(&b, arg);
146
+ scr_jb_puts(&b, "'. This command does not take positional arguments");
147
+ pa_throw_text(&b, "ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL");
148
+ }
149
+
150
+ static void pa_unexpected_dyn(const ScrDyn *arg) {
151
+ if (arg->kind == SCR_DYN_STR) {
152
+ pa_unexpected(arg->v.str);
153
+ return;
154
+ }
155
+ ScrStr *shown = scr_dyn_format_j(arg);
156
+ if (!shown) return;
157
+ pa_unexpected(shown);
158
+ scr_str_release(shown);
159
+ }
160
+
161
+ static void pa_nullish_arg_length(const ScrDyn *arg) {
162
+ const char *kind = arg->kind == SCR_DYN_NULL ? "null" : "undefined";
163
+ ScrJsonBuf b;
164
+ scr_jb_init(&b);
165
+ scr_jb_puts(&b, "Cannot read properties of ");
166
+ scr_jb_puts(&b, kind);
167
+ scr_jb_puts(&b, " (reading 'length')");
168
+ ScrStr *msg = scr_jb_finish(&b);
169
+ scr_throw_error_msg(SCR_ERR_TYPE, msg->data, msg->len);
170
+ scr_str_release(msg);
171
+ }
172
+
173
+ /* A config boolean with Node's default/validation rule. */
174
+ static bool pa_config_bool(const ScrDyn *config, const char *name,
175
+ bool fallback, bool *ok) {
176
+ const ScrDyn *v = pa_member(config, name);
177
+ if (!v || v->kind == SCR_DYN_NULL) return fallback;
178
+ if (v->kind == SCR_DYN_BOOL) return v->v.b;
179
+ scr_dyn_arg_type_fail(name, "of type boolean", v);
180
+ *ok = false;
181
+ return fallback;
182
+ }
183
+
184
+ /* Descriptor type: 1 string, 0 boolean, -1 malformed. Descriptors were
185
+ * validated once before parsing, so parsing calls only see 0/1. */
186
+ static int pa_desc_type(const ScrDyn *desc) {
187
+ const ScrDyn *v = pa_member(desc, "type");
188
+ if (!v || v->kind != SCR_DYN_STR) return -1;
189
+ if (pa_str_eq_c(v->v.str, "string")) return 1;
190
+ if (pa_str_eq_c(v->v.str, "boolean")) return 0;
191
+ return -1;
192
+ }
193
+
194
+ static bool pa_desc_multiple(const ScrDyn *desc) {
195
+ const ScrDyn *v = pa_member(desc, "multiple");
196
+ return v && v->kind == SCR_DYN_BOOL && v->v.b;
197
+ }
198
+
199
+ static bool pa_default_value_ok(const ScrDyn *v, int type) {
200
+ return type == 1 ? v->kind == SCR_DYN_STR : v->kind == SCR_DYN_BOOL;
201
+ }
202
+
203
+ static ScrDyn *pa_materialize_js_array(const ScrDyn *value);
204
+
205
+ static bool pa_validate_option(const ScrStr *name, const ScrDyn *desc) {
206
+ char *base = pa_path("options.", name->data, name->len, "");
207
+ if (desc->kind != SCR_DYN_OBJ) {
208
+ scr_dyn_prop_type_fail(base, "of type object", desc);
209
+ free(base);
210
+ return false;
211
+ }
212
+ const ScrDyn *typev = pa_member(desc, "type");
213
+ int type = pa_desc_type(desc);
214
+ if (type < 0) {
215
+ char *path = pa_path(base, "", 0, ".type");
216
+ scr_dyn_prop_type_fail(path, "('string|boolean')",
217
+ typev ? typev : scr_dyn_undefined());
218
+ free(path);
219
+ free(base);
220
+ return false;
221
+ }
222
+ const ScrDyn *shortv = pa_own_member(desc, "short");
223
+ if (shortv) {
224
+ char *path = pa_path(base, "", 0, ".short");
225
+ if (shortv->kind != SCR_DYN_STR) {
226
+ scr_dyn_prop_type_fail(path, "of type string", shortv);
227
+ free(path);
228
+ free(base);
229
+ return false;
230
+ }
231
+ if (scr_str_utf16_len(shortv->v.str) != 1) {
232
+ scr_dyn_arg_value_fail(path, "must be a single character", shortv);
233
+ free(path);
234
+ free(base);
235
+ return false;
236
+ }
237
+ free(path);
238
+ }
239
+ const ScrDyn *multiplev = pa_own_member(desc, "multiple");
240
+ if (multiplev && multiplev->kind != SCR_DYN_BOOL) {
241
+ char *path = pa_path(base, "", 0, ".multiple");
242
+ scr_dyn_prop_type_fail(path, "of type boolean", multiplev);
243
+ free(path);
244
+ free(base);
245
+ return false;
246
+ }
247
+ const ScrDyn *def = pa_member(desc, "default");
248
+ if (def) {
249
+ char *path = pa_path(base, "", 0, ".default");
250
+ if (pa_desc_multiple(desc)) {
251
+ /* A live typed/island array stays in the descriptor so phase 3 can
252
+ * return that exact reference. Validate against a temporary snapshot. */
253
+ ScrDyn *items = pa_materialize_js_array(def);
254
+ if (!items) {
255
+ free(path);
256
+ free(base);
257
+ return false;
258
+ }
259
+ if (items->kind != SCR_DYN_ARR) {
260
+ scr_dyn_prop_type_fail(path, "an instance of Array", def);
261
+ scr_dyn_release(items);
262
+ free(path);
263
+ free(base);
264
+ return false;
265
+ }
266
+ for (size_t j = 0; j < items->v.arr.len; j++) {
267
+ if (!pa_default_value_ok(items->v.arr.items[j], type)) {
268
+ char suffix[48];
269
+ snprintf(suffix, sizeof suffix, "[%zu]", j);
270
+ char *item_path = pa_path(path, "", 0, suffix);
271
+ scr_dyn_prop_type_fail(item_path,
272
+ type ? "of type string" : "of type boolean",
273
+ items->v.arr.items[j]);
274
+ free(item_path);
275
+ scr_dyn_release(items);
276
+ free(path);
277
+ free(base);
278
+ return false;
279
+ }
280
+ }
281
+ scr_dyn_release(items);
282
+ } else if (!pa_default_value_ok(def, type)) {
283
+ scr_dyn_prop_type_fail(path,
284
+ type ? "of type string" : "of type boolean", def);
285
+ free(path);
286
+ free(base);
287
+ return false;
288
+ }
289
+ free(path);
290
+ }
291
+ free(base);
292
+ return true;
293
+ }
294
+
295
+ /* Validate the option schema up front, as Node does even when args is
296
+ * empty. Object.entries supplies JS own-key order (array-index names first),
297
+ * which controls both validation precedence and duplicate-short lookup. */
298
+ static bool pa_validate_options(const ScrDyn *options) {
299
+ if (!options) return true; /* null/undefined mean {} */
300
+ if (options->kind != SCR_DYN_OBJ) {
301
+ scr_dyn_arg_type_fail("options", "of type object", options);
302
+ return false;
303
+ }
304
+ ScrDyn *entries = scr_dyn_obj_entries(options);
305
+ for (size_t i = 0; i < entries->v.arr.len; i++) {
306
+ const ScrDyn *pair = entries->v.arr.items[i];
307
+ const ScrStr *name = pair->v.arr.items[0]->v.str;
308
+ const ScrDyn *desc = pair->v.arr.items[1];
309
+ if (!pa_validate_option(name, desc)) {
310
+ scr_dyn_release(entries);
311
+ return false;
312
+ }
313
+ }
314
+ scr_dyn_release(entries);
315
+ return true;
316
+ }
317
+
318
+ static const ScrDyn *pa_find_long(const ScrDyn *options, const ScrStr *name) {
319
+ if (!options) return NULL;
320
+ return scr_dyn_obj_get(options, name->data, name->len);
321
+ }
322
+
323
+ static bool pa_is_negative_name(const ScrStr *name) {
324
+ return name->len >= 3 && memcmp(name->data, "no-", 3) == 0;
325
+ }
326
+
327
+ /* Node recognizes an inline long-option value only when an '=' occurs
328
+ * after at least one name code unit, then splits at the first '='. */
329
+ static size_t pa_long_eq(const ScrStr *arg) {
330
+ bool has_inline_value = false;
331
+ for (size_t j = 3; j < arg->len; j++) {
332
+ if (arg->data[j] == '=') { has_inline_value = true; break; }
333
+ }
334
+ if (!has_inline_value) return arg->len;
335
+ for (size_t j = 2; j < arg->len; j++) {
336
+ if (arg->data[j] == '=') return j;
337
+ }
338
+ return arg->len;
339
+ }
340
+
341
+ static const ScrDyn *pa_find_short(const ScrDyn *options,
342
+ const ScrStr *short_name,
343
+ ScrStr **long_name) {
344
+ if (!options) {
345
+ *long_name = pa_str_bytes(short_name->data, short_name->len);
346
+ return NULL;
347
+ }
348
+ ScrDyn *entries = scr_dyn_obj_entries(options);
349
+ for (size_t i = 0; i < entries->v.arr.len; i++) {
350
+ const ScrDyn *pair = entries->v.arr.items[i];
351
+ const ScrStr *name = pair->v.arr.items[0]->v.str;
352
+ const ScrDyn *desc = pair->v.arr.items[1];
353
+ const ScrDyn *shortv = pa_member(desc, "short");
354
+ if (shortv && shortv->kind == SCR_DYN_STR &&
355
+ shortv->v.str->len == short_name->len &&
356
+ memcmp(shortv->v.str->data, short_name->data, short_name->len) == 0) {
357
+ *long_name = pa_str_bytes(name->data, name->len);
358
+ const ScrDyn *found = pa_find_long(options, name);
359
+ scr_dyn_release(entries);
360
+ return found;
361
+ }
362
+ }
363
+ scr_dyn_release(entries);
364
+ *long_name = pa_str_bytes(short_name->data, short_name->len);
365
+ /* Node falls back to the short spelling itself as the long option name.
366
+ * Thus `{ x: { type: "boolean" } }` accepts both `--x` and `-x` even
367
+ * without an explicit `short: "x"` descriptor. */
368
+ return pa_find_long(options, short_name);
369
+ }
370
+
371
+ static ScrDyn *pa_option_token(const ScrStr *name, const ScrStr *raw,
372
+ size_t index, const ScrDyn *value, int inline_value) {
373
+ ScrDyn *token = scr_dyn_new_obj();
374
+ scr_dyn_obj_set(token, "kind", 4, pa_dyn_str_bytes("option", 6));
375
+ scr_dyn_obj_set(token, "name", 4, pa_dyn_str(name));
376
+ scr_dyn_obj_set(token, "rawName", 7, pa_dyn_str(raw));
377
+ scr_dyn_obj_set(token, "index", 5, scr_dyn_new_num((double)index));
378
+ scr_dyn_obj_set(token, "value", 5,
379
+ value ? scr_dyn_retain((ScrDyn *)value)
380
+ : scr_dyn_retain(scr_dyn_undefined()));
381
+ scr_dyn_obj_set(token, "inlineValue", 11,
382
+ inline_value < 0 ? scr_dyn_retain(scr_dyn_undefined())
383
+ : scr_dyn_new_bool(inline_value != 0));
384
+ return token;
385
+ }
386
+
387
+ static void pa_store(ScrDyn *values, ScrDyn *tokens, const ScrDyn *desc,
388
+ const ScrStr *name, const ScrStr *raw, size_t index,
389
+ const ScrDyn *value, bool flag_value, int inline_value,
390
+ bool withhold_proto) {
391
+ /* Node deliberately withholds this key from the null-prototype values
392
+ * dictionary when it was the ORIGINAL option name. A normalized
393
+ * `--no-__proto__` passes this check before normalization in Node and is
394
+ * therefore stored safely on the null-prototype dictionary. */
395
+ if (withhold_proto && pa_str_eq_c(name, "__proto__")) {
396
+ if (tokens) {
397
+ scr_dyn_arr_push(tokens,
398
+ pa_option_token(name, raw, index, value, inline_value));
399
+ }
400
+ return;
401
+ }
402
+ ScrDyn *stored = value ? scr_dyn_retain((ScrDyn *)value)
403
+ : scr_dyn_new_bool(flag_value);
404
+ if (desc && pa_desc_multiple(desc)) {
405
+ ScrDyn *arr = scr_dyn_obj_get(values, name->data, name->len);
406
+ if (!arr) {
407
+ arr = scr_dyn_new_arr();
408
+ scr_dyn_obj_set(values, name->data, name->len, arr);
409
+ }
410
+ scr_dyn_arr_push(arr, stored);
411
+ } else {
412
+ scr_dyn_obj_set(values, name->data, name->len, stored);
413
+ }
414
+ if (tokens) {
415
+ scr_dyn_arr_push(tokens,
416
+ pa_option_token(name, raw, index, value, inline_value));
417
+ }
418
+ }
419
+
420
+ static void pa_store_flag(ScrDyn *values, ScrDyn *tokens,
421
+ const ScrDyn *options, const ScrDyn *desc,
422
+ const ScrStr *name, const ScrStr *raw, size_t index,
423
+ bool allow_negative) {
424
+ if (allow_negative && pa_is_negative_name(name)) {
425
+ ScrStr *positive = pa_str_bytes(name->data + 3, name->len - 3);
426
+ const ScrDyn *positive_desc = pa_find_long(options, positive);
427
+ pa_store(values, tokens, positive_desc, positive, raw, index,
428
+ NULL, false, -1, false);
429
+ scr_str_release(positive);
430
+ return;
431
+ }
432
+ pa_store(values, tokens, desc, name, raw, index, NULL, true, -1, true);
433
+ }
434
+
435
+ static void pa_positional(ScrDyn *positionals, ScrDyn *tokens,
436
+ const ScrDyn *value, size_t index) {
437
+ scr_dyn_arr_push(positionals, scr_dyn_retain((ScrDyn *)value));
438
+ if (!tokens) return;
439
+ ScrDyn *token = scr_dyn_new_obj();
440
+ scr_dyn_obj_set(token, "kind", 4, pa_dyn_str_bytes("positional", 10));
441
+ scr_dyn_obj_set(token, "index", 5, scr_dyn_new_num((double)index));
442
+ scr_dyn_obj_set(token, "value", 5, scr_dyn_retain((ScrDyn *)value));
443
+ scr_dyn_arr_push(tokens, token);
444
+ }
445
+
446
+ static ScrDyn *pa_default_args(void) {
447
+ ScrDyn *args = scr_dyn_new_arr();
448
+ ScrArr *argv = scr_process_argv();
449
+ /* Read the ONE live interned process.argv array: user pushes, pops, and
450
+ * replacements before parseArgs() are observable exactly as slice(2). */
451
+ for (size_t i = 2; i < argv->len; i++) {
452
+ ScrStr *arg = scr_arr_get_ref(argv, (double)i);
453
+ scr_dyn_arr_push(args, pa_dyn_str(arg));
454
+ scr_str_release(arg);
455
+ }
456
+ scr_arr_release(argv);
457
+ return args;
458
+ }
459
+
460
+ /* Copy an engine-backed array into the native dyn alphabet without using
461
+ * its iterator. Node's parseArgs takes args with Array.prototype.slice,
462
+ * so indexed Gets (including holes -> undefined) and the live length are
463
+ * the relevant semantics; an overridden Symbol.iterator is not. */
464
+ static ScrDyn *pa_materialize_js_array(const ScrDyn *value) {
465
+ if (value->kind == SCR_DYN_TYPED_REF) {
466
+ return scr_dyn_typed_ref_materialize(value);
467
+ }
468
+ if (value->kind != SCR_DYN_JSVAL ||
469
+ !scr_dyn_jsval_ops()->is_array(value->v.jsval.cell)) {
470
+ return scr_dyn_retain((ScrDyn *)value);
471
+ }
472
+ ScrStr *length_key = scr_str_new("length", 6);
473
+ ScrDyn *length = scr_dyn_jsval_ops()->key_get(value->v.jsval.cell,
474
+ length_key);
475
+ scr_str_release(length_key);
476
+ if (!length) return NULL;
477
+ if (length->kind != SCR_DYN_NUM || !isfinite(length->v.num) ||
478
+ length->v.num < 0) {
479
+ scr_dyn_release(length);
480
+ return scr_dyn_retain((ScrDyn *)value); /* defensive: real arrays cannot */
481
+ }
482
+ size_t len = (size_t)length->v.num;
483
+ scr_dyn_release(length);
484
+ ScrDyn *out = scr_dyn_new_arr();
485
+ for (size_t i = 0; i < len; i++) {
486
+ char index[32];
487
+ int n = snprintf(index, sizeof index, "%zu", i);
488
+ ScrStr *key = scr_str_new(index, (size_t)n);
489
+ ScrDyn *item = scr_dyn_jsval_ops()->key_get(value->v.jsval.cell, key);
490
+ scr_str_release(key);
491
+ if (!item) {
492
+ scr_dyn_release(out);
493
+ return NULL;
494
+ }
495
+ scr_dyn_arr_push(out, item);
496
+ }
497
+ return out;
498
+ }
499
+
500
+ /* Own-member read with an owned answer. Native config objects are already
501
+ * inert dyn data; engine-backed configs route Object.hasOwn + Get through
502
+ * the island so a normal `any` config can enter the native parser. */
503
+ static ScrDyn *pa_owned_member(const ScrDyn *obj, const char *key) {
504
+ if (obj->kind == SCR_DYN_OBJ) {
505
+ const ScrDyn *value = pa_own_member(obj, key);
506
+ return value ? scr_dyn_retain((ScrDyn *)value) : NULL;
507
+ }
508
+ if (obj->kind != SCR_DYN_JSVAL) return NULL;
509
+ ScrStr *name = scr_str_new(key, strlen(key));
510
+ int own = scr_dyn_jsval_ops()->has_own(obj->v.jsval.cell, name);
511
+ if (own != 1) {
512
+ scr_str_release(name);
513
+ return NULL;
514
+ }
515
+ ScrDyn *value = scr_dyn_jsval_ops()->key_get(obj->v.jsval.cell, name);
516
+ scr_str_release(name);
517
+ return value;
518
+ }
519
+
520
+ static ScrDyn *pa_materialize_descriptor(const ScrDyn *desc) {
521
+ if (desc->kind == SCR_DYN_TYPED_REF) {
522
+ ScrDyn *view = scr_dyn_typed_ref_materialize(desc);
523
+ ScrDyn *out = pa_materialize_descriptor(view);
524
+ scr_dyn_release(view);
525
+ return out;
526
+ }
527
+ if (desc->kind == SCR_DYN_JSVAL &&
528
+ scr_dyn_jsval_ops()->is_array(desc->v.jsval.cell)) {
529
+ return pa_materialize_js_array(desc);
530
+ }
531
+ if (desc->kind != SCR_DYN_OBJ && desc->kind != SCR_DYN_JSVAL) {
532
+ return scr_dyn_retain((ScrDyn *)desc);
533
+ }
534
+ if (desc->kind == SCR_DYN_JSVAL &&
535
+ !scr_dyn_isl_typeof_is(desc, "object")) {
536
+ return scr_dyn_retain((ScrDyn *)desc);
537
+ }
538
+ static const char *const fields[] = {"type", "short", "multiple", "default"};
539
+ ScrDyn *out = scr_dyn_new_obj();
540
+ for (size_t i = 0; i < sizeof fields / sizeof fields[0]; i++) {
541
+ ScrDyn *value = pa_owned_member(desc, fields[i]);
542
+ if (!value) {
543
+ if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
544
+ continue;
545
+ }
546
+ /* Node assigns a descriptor default directly into result.values. Keep
547
+ * typed array defaults as their original static reference; island arrays
548
+ * still need the ordinary native snapshot so dynCheck can consume them. */
549
+ ScrDyn *native = strcmp(fields[i], "default") == 0 &&
550
+ value->kind == SCR_DYN_TYPED_REF
551
+ ? scr_dyn_retain(value)
552
+ : pa_materialize_js_array(value);
553
+ scr_dyn_release(value);
554
+ if (!native) { scr_dyn_release(out); return NULL; }
555
+ scr_dyn_obj_set(out, fields[i], strlen(fields[i]), native);
556
+ }
557
+ return out;
558
+ }
559
+
560
+ static ScrDyn *pa_materialize_options(const ScrDyn *options) {
561
+ if (options->kind == SCR_DYN_TYPED_REF) {
562
+ ScrDyn *view = scr_dyn_typed_ref_materialize(options);
563
+ ScrDyn *out = pa_materialize_options(view);
564
+ scr_dyn_release(view);
565
+ return out;
566
+ }
567
+ if (options->kind == SCR_DYN_JSVAL &&
568
+ scr_dyn_jsval_ops()->is_array(options->v.jsval.cell)) {
569
+ return pa_materialize_js_array(options);
570
+ }
571
+ if (options->kind != SCR_DYN_OBJ && options->kind != SCR_DYN_JSVAL) {
572
+ return scr_dyn_retain((ScrDyn *)options);
573
+ }
574
+ if (options->kind == SCR_DYN_JSVAL &&
575
+ !scr_dyn_isl_typeof_is(options, "object")) {
576
+ return scr_dyn_retain((ScrDyn *)options);
577
+ }
578
+ ScrDyn *entries = scr_dyn_obj_entries(options);
579
+ if (!entries) return NULL;
580
+ ScrDyn *out = scr_dyn_new_obj();
581
+ for (size_t i = 0; i < entries->v.arr.len; i++) {
582
+ const ScrDyn *pair = entries->v.arr.items[i];
583
+ const ScrStr *name = pair->v.arr.items[0]->v.str;
584
+ ScrDyn *desc = pa_materialize_descriptor(pair->v.arr.items[1]);
585
+ if (!desc) {
586
+ scr_dyn_release(out);
587
+ scr_dyn_release(entries);
588
+ return NULL;
589
+ }
590
+ scr_dyn_obj_set(out, name->data, name->len, desc);
591
+ }
592
+ scr_dyn_release(entries);
593
+ return out;
594
+ }
595
+
596
+ /* Normalize the documented config members only. This is intentionally a
597
+ * snapshot, matching parseArgs's synchronous reads, while preserving own
598
+ * property presence (including explicit undefined) across the island. */
599
+ static ScrDyn *pa_materialize_config(const ScrDyn *config) {
600
+ if (config->kind == SCR_DYN_TYPED_REF) {
601
+ ScrDyn *view = scr_dyn_typed_ref_materialize(config);
602
+ ScrDyn *out = pa_materialize_config(view);
603
+ scr_dyn_release(view);
604
+ return out;
605
+ }
606
+ if (config->kind != SCR_DYN_OBJ && config->kind != SCR_DYN_JSVAL) {
607
+ return scr_dyn_retain((ScrDyn *)config);
608
+ }
609
+ static const char *const fields[] = {
610
+ "args", "strict", "allowPositionals", "tokens", "allowNegative", "options",
611
+ };
612
+ ScrDyn *out = scr_dyn_new_obj();
613
+ for (size_t i = 0; i < sizeof fields / sizeof fields[0]; i++) {
614
+ ScrDyn *value = pa_owned_member(config, fields[i]);
615
+ if (!value) {
616
+ if (scr_exc_pending()) { scr_dyn_release(out); return NULL; }
617
+ continue;
618
+ }
619
+ ScrDyn *native = strcmp(fields[i], "options") == 0
620
+ ? pa_materialize_options(value)
621
+ : pa_materialize_js_array(value);
622
+ scr_dyn_release(value);
623
+ if (!native) { scr_dyn_release(out); return NULL; }
624
+ scr_dyn_obj_set(out, fields[i], strlen(fields[i]), native);
625
+ }
626
+ return out;
627
+ }
628
+
629
+ /* Whether tokenizing this string argument consumes the next array item as
630
+ * a separate value. This mirrors the greedy string-option cases only; the
631
+ * processing pass below still owns usage validation and token storage. */
632
+ static bool pa_consumes_next(const ScrDyn *options, const ScrStr *arg) {
633
+ if (arg->len >= 2 && arg->data[0] == '-' && arg->data[1] == '-') {
634
+ size_t eq = pa_long_eq(arg);
635
+ if (eq < arg->len) return false;
636
+ ScrStr *name = pa_str_bytes(arg->data + 2, eq - 2);
637
+ const ScrDyn *desc = pa_find_long(options, name);
638
+ bool consumes = desc && pa_desc_type(desc) == 1;
639
+ scr_str_release(name);
640
+ return consumes;
641
+ }
642
+ if (arg->len <= 1 || arg->data[0] != '-') return false;
643
+ double units = scr_str_utf16_len((ScrStr *)arg);
644
+ for (double at = 1; at < units; at++) {
645
+ ScrStr *short_name = scr_str_char_at((ScrStr *)arg, at);
646
+ ScrStr *name = NULL;
647
+ const ScrDyn *desc = pa_find_short(options, short_name, &name);
648
+ scr_str_release(name);
649
+ scr_str_release(short_name);
650
+ if (desc && pa_desc_type(desc) == 1) return at + 1 >= units;
651
+ }
652
+ return false;
653
+ }
654
+
655
+ ScrDyn *scr_util_parse_args(const ScrDyn *config) {
656
+ /* Omitted config is lowered as {}, while an explicit undefined takes the
657
+ * default parameter just as Node does. Null keeps Object(null)'s useful
658
+ * failure; other primitive/array wrappers have no config members. */
659
+ if (!config || config->kind == SCR_DYN_NULL) {
660
+ static const char msg[] = "Cannot convert undefined or null to object";
661
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
662
+ return NULL;
663
+ }
664
+ ScrDyn *owned_config = pa_materialize_config(config);
665
+ if (!owned_config) return NULL;
666
+ const ScrDyn *cfg = owned_config->kind == SCR_DYN_OBJ ? owned_config : NULL;
667
+ /* Node validates the args container before every flag and before the
668
+ * option schema. Its element values are intentionally not prevalidated:
669
+ * non-string values can still become loose-mode positional tokens. */
670
+ ScrDyn *owned_args = NULL;
671
+ const ScrDyn *args = pa_member(cfg, "args");
672
+ if (!args || args->kind == SCR_DYN_NULL) {
673
+ owned_args = pa_default_args();
674
+ args = owned_args;
675
+ } else if (args->kind != SCR_DYN_ARR) {
676
+ scr_dyn_arg_type_fail("args", "an instance of Array", args);
677
+ scr_dyn_release(owned_config);
678
+ return NULL;
679
+ }
680
+
681
+ bool ok = true;
682
+ bool strict = pa_config_bool(cfg, "strict", true, &ok);
683
+ if (!ok) { scr_dyn_release(owned_args); scr_dyn_release(owned_config); return NULL; }
684
+ bool allow_positionals = pa_config_bool(cfg, "allowPositionals", !strict, &ok);
685
+ if (!ok) { scr_dyn_release(owned_args); scr_dyn_release(owned_config); return NULL; }
686
+ bool return_tokens = pa_config_bool(cfg, "tokens", false, &ok);
687
+ if (!ok) { scr_dyn_release(owned_args); scr_dyn_release(owned_config); return NULL; }
688
+ bool allow_negative = pa_config_bool(cfg, "allowNegative", false, &ok);
689
+ if (!ok) { scr_dyn_release(owned_args); scr_dyn_release(owned_config); return NULL; }
690
+
691
+ const ScrDyn *options = pa_member(cfg, "options");
692
+ if (options && options->kind == SCR_DYN_NULL) options = NULL;
693
+ if (!pa_validate_options(options)) {
694
+ scr_dyn_release(owned_args);
695
+ scr_dyn_release(owned_config);
696
+ return NULL;
697
+ }
698
+
699
+ /* Node tokenizes the complete input before applying strict usage checks.
700
+ * In the checked-dynamic alphabet, the only tokenizer-time exception is
701
+ * a nullish argument before `--` (other non-strings become positionals),
702
+ * so preflight that failure before the processing pass below. */
703
+ bool scan_after_terminator = false;
704
+ for (size_t i = 0; i < args->v.arr.len; i++) {
705
+ const ScrDyn *arg_value = args->v.arr.items[i];
706
+ if (scan_after_terminator) continue;
707
+ if (arg_value->kind == SCR_DYN_NULL ||
708
+ arg_value->kind == SCR_DYN_UNDEF) {
709
+ pa_nullish_arg_length(arg_value);
710
+ scr_dyn_release(owned_args);
711
+ scr_dyn_release(owned_config);
712
+ return NULL;
713
+ }
714
+ if (arg_value->kind != SCR_DYN_STR) continue;
715
+ if (pa_str_eq_c(arg_value->v.str, "--")) {
716
+ scan_after_terminator = true;
717
+ continue;
718
+ }
719
+ if (pa_consumes_next(options, arg_value->v.str) &&
720
+ i + 1 < args->v.arr.len &&
721
+ args->v.arr.items[i + 1]->kind != SCR_DYN_NULL &&
722
+ args->v.arr.items[i + 1]->kind != SCR_DYN_UNDEF) {
723
+ i++;
724
+ }
725
+ }
726
+
727
+ ScrDyn *result = scr_dyn_new_obj();
728
+ ScrDyn *values = scr_dyn_new_obj_null_proto();
729
+ ScrDyn *positionals = scr_dyn_new_arr();
730
+ ScrDyn *tokens = return_tokens ? scr_dyn_new_arr() : NULL;
731
+ bool after_terminator = false;
732
+
733
+ for (size_t i = 0; i < args->v.arr.len; i++) {
734
+ const ScrDyn *arg_value = args->v.arr.items[i];
735
+ if (after_terminator) {
736
+ if (!allow_positionals) {
737
+ pa_unexpected_dyn(arg_value);
738
+ goto fail;
739
+ }
740
+ pa_positional(positionals, tokens, arg_value, i);
741
+ continue;
742
+ }
743
+ if (arg_value->kind == SCR_DYN_NULL || arg_value->kind == SCR_DYN_UNDEF) {
744
+ pa_nullish_arg_length(arg_value);
745
+ goto fail;
746
+ }
747
+ if (arg_value->kind != SCR_DYN_STR) {
748
+ if (!allow_positionals) {
749
+ pa_unexpected_dyn(arg_value);
750
+ goto fail;
751
+ }
752
+ pa_positional(positionals, tokens, arg_value, i);
753
+ continue;
754
+ }
755
+ const ScrStr *arg = arg_value->v.str;
756
+ if (pa_str_eq_c(arg, "--")) {
757
+ after_terminator = true;
758
+ if (tokens) {
759
+ ScrDyn *token = scr_dyn_new_obj();
760
+ scr_dyn_obj_set(token, "kind", 4,
761
+ pa_dyn_str_bytes("option-terminator", 17));
762
+ scr_dyn_obj_set(token, "index", 5, scr_dyn_new_num((double)i));
763
+ scr_dyn_arr_push(tokens, token);
764
+ }
765
+ continue;
766
+ }
767
+
768
+ if (arg->len >= 2 && arg->data[0] == '-' && arg->data[1] == '-') {
769
+ size_t eq = pa_long_eq(arg);
770
+ /* The presence test starts after one name byte, so `--=x` stays the
771
+ * lone option "=x". Once present, Node splits at the FIRST equals:
772
+ * `--==x` therefore has the empty name and value "=x". */
773
+ ScrStr *name = pa_str_bytes(arg->data + 2, eq - 2);
774
+ ScrStr *raw = pa_str_bytes(arg->data, eq);
775
+ const ScrDyn *desc = pa_find_long(options, name);
776
+ if (eq < arg->len) {
777
+ ScrStr *checked_name = NULL;
778
+ const ScrDyn *checked_desc = desc;
779
+ if (strict && !checked_desc && allow_negative &&
780
+ pa_is_negative_name(name)) {
781
+ checked_name = pa_str_bytes(name->data + 3, name->len - 3);
782
+ const ScrDyn *positive_desc = pa_find_long(options, checked_name);
783
+ if (positive_desc && pa_desc_type(positive_desc) == 0) {
784
+ checked_desc = positive_desc;
785
+ }
786
+ }
787
+ if (strict && !checked_desc) {
788
+ pa_unknown(raw, allow_positionals);
789
+ scr_str_release(checked_name);
790
+ scr_str_release(raw);
791
+ scr_str_release(name);
792
+ goto fail;
793
+ }
794
+ if (strict && pa_desc_type(checked_desc) == 0) {
795
+ pa_takes_no_value(checked_desc,
796
+ checked_name ? checked_name : name);
797
+ scr_str_release(checked_name);
798
+ scr_str_release(raw);
799
+ scr_str_release(name);
800
+ goto fail;
801
+ }
802
+ scr_str_release(checked_name);
803
+ ScrStr *value = pa_str_bytes(arg->data + eq + 1, arg->len - eq - 1);
804
+ ScrDyn *dyn_value = pa_dyn_str(value);
805
+ pa_store(values, tokens, desc, name, raw, i, dyn_value, true, 1, true);
806
+ scr_dyn_release(dyn_value);
807
+ scr_str_release(value);
808
+ } else if (desc && pa_desc_type(desc) == 1) {
809
+ const ScrDyn *next = i + 1 < args->v.arr.len
810
+ ? args->v.arr.items[i + 1] : NULL;
811
+ if (next && next->kind != SCR_DYN_NULL && next->kind != SCR_DYN_UNDEF) {
812
+ i++;
813
+ if (strict && next->kind != SCR_DYN_STR) {
814
+ pa_missing_value(desc, name);
815
+ scr_str_release(raw);
816
+ scr_str_release(name);
817
+ goto fail;
818
+ }
819
+ if (strict && next->kind == SCR_DYN_STR &&
820
+ next->v.str->len > 1 && next->v.str->data[0] == '-') {
821
+ pa_ambiguous(raw, name, false);
822
+ scr_str_release(raw);
823
+ scr_str_release(name);
824
+ goto fail;
825
+ }
826
+ pa_store(values, tokens, desc, name, raw, i - 1, next, true, 0, true);
827
+ } else if (strict) {
828
+ pa_missing_value(desc, name);
829
+ scr_str_release(raw);
830
+ scr_str_release(name);
831
+ goto fail;
832
+ } else {
833
+ if (allow_negative && pa_is_negative_name(name)) {
834
+ ScrStr *positive = pa_str_bytes(name->data + 3, name->len - 3);
835
+ const ScrDyn *positive_desc = pa_find_long(options, positive);
836
+ pa_store(values, tokens, positive_desc, positive, raw, i,
837
+ NULL, false, -1, false);
838
+ scr_str_release(positive);
839
+ } else {
840
+ pa_store(values, tokens, desc, name, raw, i, NULL, true, -1, true);
841
+ }
842
+ }
843
+ } else {
844
+ ScrStr *positive = NULL;
845
+ const ScrDyn *positive_desc = NULL;
846
+ const bool negative = allow_negative && pa_is_negative_name(name);
847
+ if (negative) {
848
+ positive = pa_str_bytes(name->data + 3, name->len - 3);
849
+ positive_desc = pa_find_long(options, positive);
850
+ }
851
+ if (strict && !desc &&
852
+ (!negative || !positive_desc || pa_desc_type(positive_desc) != 0)) {
853
+ pa_unknown(raw, allow_positionals);
854
+ scr_str_release(positive);
855
+ scr_str_release(raw);
856
+ scr_str_release(name);
857
+ goto fail;
858
+ }
859
+ if (negative) {
860
+ pa_store(values, tokens, positive_desc, positive, raw, i,
861
+ NULL, false, -1, false);
862
+ scr_str_release(positive);
863
+ } else {
864
+ pa_store(values, tokens, desc, name, raw, i, NULL, true, -1, true);
865
+ }
866
+ }
867
+ scr_str_release(raw);
868
+ scr_str_release(name);
869
+ continue;
870
+ }
871
+
872
+ if (arg->len > 1 && arg->data[0] == '-') {
873
+ double units = scr_str_utf16_len((ScrStr *)arg);
874
+ for (double at = 1; at < units; at++) {
875
+ ScrStr *short_name = scr_str_char_at((ScrStr *)arg, at);
876
+ ScrStr *name = NULL;
877
+ const ScrDyn *desc = pa_find_short(options, short_name, &name);
878
+ char *raw_bytes = malloc(short_name->len + 1);
879
+ if (!raw_bytes) {
880
+ fputs("scriptc: out of memory\n", stderr);
881
+ abort();
882
+ }
883
+ raw_bytes[0] = '-';
884
+ memcpy(raw_bytes + 1, short_name->data, short_name->len);
885
+ ScrStr *raw = pa_str_bytes(raw_bytes, short_name->len + 1);
886
+ free(raw_bytes);
887
+ if (strict && !desc) {
888
+ pa_unknown(raw, allow_positionals);
889
+ scr_str_release(raw);
890
+ scr_str_release(name);
891
+ scr_str_release(short_name);
892
+ goto fail;
893
+ }
894
+ if (desc && pa_desc_type(desc) == 1) {
895
+ if (at + 1 < units) {
896
+ ScrStr *value = scr_str_slice((ScrStr *)arg, at + 1, INFINITY);
897
+ ScrDyn *dyn_value = pa_dyn_str(value);
898
+ pa_store(values, tokens, desc, name, raw, i, dyn_value, true, 1, true);
899
+ scr_dyn_release(dyn_value);
900
+ scr_str_release(value);
901
+ } else if (i + 1 < args->v.arr.len &&
902
+ args->v.arr.items[i + 1]->kind != SCR_DYN_NULL &&
903
+ args->v.arr.items[i + 1]->kind != SCR_DYN_UNDEF) {
904
+ const ScrDyn *value = args->v.arr.items[++i];
905
+ if (strict && value->kind != SCR_DYN_STR) {
906
+ pa_missing_value(desc, name);
907
+ scr_str_release(raw);
908
+ scr_str_release(name);
909
+ scr_str_release(short_name);
910
+ goto fail;
911
+ }
912
+ if (strict && value->kind == SCR_DYN_STR &&
913
+ value->v.str->len > 1 && value->v.str->data[0] == '-') {
914
+ pa_ambiguous(raw, name, true);
915
+ scr_str_release(raw);
916
+ scr_str_release(name);
917
+ scr_str_release(short_name);
918
+ goto fail;
919
+ }
920
+ pa_store(values, tokens, desc, name, raw, i - 1, value, true, 0, true);
921
+ } else if (strict) {
922
+ pa_missing_value(desc, name);
923
+ scr_str_release(raw);
924
+ scr_str_release(name);
925
+ scr_str_release(short_name);
926
+ goto fail;
927
+ } else {
928
+ pa_store_flag(values, tokens, options, desc, name, raw, i,
929
+ allow_negative);
930
+ }
931
+ scr_str_release(raw);
932
+ scr_str_release(name);
933
+ scr_str_release(short_name);
934
+ break;
935
+ }
936
+ pa_store_flag(values, tokens, options, desc, name, raw, i,
937
+ allow_negative);
938
+ scr_str_release(raw);
939
+ scr_str_release(name);
940
+ scr_str_release(short_name);
941
+ }
942
+ continue;
943
+ }
944
+
945
+ if (!allow_positionals) {
946
+ pa_unexpected(arg);
947
+ goto fail;
948
+ }
949
+ pa_positional(positionals, tokens, arg_value, i);
950
+ }
951
+
952
+ if (options) {
953
+ ScrDyn *entries = scr_dyn_obj_entries(options);
954
+ for (size_t i = 0; i < entries->v.arr.len; i++) {
955
+ const ScrDyn *pair = entries->v.arr.items[i];
956
+ const ScrStr *name = pair->v.arr.items[0]->v.str;
957
+ const ScrDyn *desc = pair->v.arr.items[1];
958
+ if (pa_str_eq_c(name, "__proto__")) continue;
959
+ if (scr_dyn_obj_get(values, name->data, name->len)) continue;
960
+ const ScrDyn *def = pa_member(desc, "default");
961
+ if (def) {
962
+ scr_dyn_obj_set(values, name->data, name->len,
963
+ scr_dyn_retain((ScrDyn *)def));
964
+ }
965
+ }
966
+ scr_dyn_release(entries);
967
+ }
968
+
969
+ scr_dyn_obj_set(result, "values", 6, values);
970
+ scr_dyn_obj_set(result, "positionals", 11, positionals);
971
+ if (tokens) scr_dyn_obj_set(result, "tokens", 6, tokens);
972
+ scr_dyn_release(owned_args);
973
+ scr_dyn_release(owned_config);
974
+ return result;
975
+
976
+ fail:
977
+ scr_dyn_release(tokens);
978
+ scr_dyn_release(positionals);
979
+ scr_dyn_release(values);
980
+ scr_dyn_release(result);
981
+ scr_dyn_release(owned_args);
982
+ scr_dyn_release(owned_config);
983
+ return NULL;
984
+ }