nomen-lang 0.6.0 → 0.6.2

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/dist/index.mjs CHANGED
@@ -19242,6 +19242,7 @@ function build_function_node$1(node, status) {
19242
19242
  const old_struct_param_slots = status.function_struct_param_slots;
19243
19243
  const old_function_param_types = status.function_param_types;
19244
19244
  const old_function_local_types = status.function_local_types;
19245
+ const old_variable_types = status.variable_types;
19245
19246
  status.function_param_regs = /* @__PURE__ */ new Map();
19246
19247
  status.function_param_vars = /* @__PURE__ */ new Set();
19247
19248
  status.function_array_params = /* @__PURE__ */ new Set();
@@ -19250,6 +19251,7 @@ function build_function_node$1(node, status) {
19250
19251
  status.function_struct_param_slots = /* @__PURE__ */ new Set();
19251
19252
  status.function_param_types = /* @__PURE__ */ new Map();
19252
19253
  status.function_local_types = /* @__PURE__ */ new Map();
19254
+ status.variable_types = /* @__PURE__ */ new Map();
19253
19255
  const old_variadic_params_aarch64 = status.function_variadic_params;
19254
19256
  const old_view_params = status.function_view_params;
19255
19257
  status.function_variadic_params = /* @__PURE__ */ new Set();
@@ -19658,6 +19660,7 @@ function build_function_node$1(node, status) {
19658
19660
  status.function_struct_param_slots = old_struct_param_slots;
19659
19661
  status.function_param_types = old_function_param_types;
19660
19662
  status.function_local_types = old_function_local_types;
19663
+ status.variable_types = old_variable_types;
19661
19664
  status.function_variadic_params = old_variadic_params_aarch64;
19662
19665
  status.function_view_params = old_view_params;
19663
19666
  status.function_return_label = old_return_label;
@@ -21418,6 +21421,67 @@ function call_in_set(set, call) {
21418
21421
  return set.has(call.name.replace(/#/g, ""));
21419
21422
  }
21420
21423
  //#endregion
21424
+ //#region ../src/build_common/fold_string_const.ts
21425
+ /**
21426
+ * Fold a top-level `const`'s string `+` chain into a single string literal.
21427
+ *
21428
+ * Module-level `const` declarations are inlined at every use site (see
21429
+ * `top_level_consts` in both backends' BuildStatus): the build replays the
21430
+ * const's initializer expression wherever the name appears. For a composed
21431
+ * pattern like `pub const HTML_TAG_REGEX = "^(" + TAG_NAME + "|…)"` that
21432
+ * replay lowers to a malloc/copy/free `string_add` chain PER USE — the
21433
+ * allmark port's inline-token loop rebuilt its ~1 KB pattern on every
21434
+ * attempt, and the chain's calls were also the trigger for the aarch64
21435
+ * ref-argument clobber (PORT.md).
21436
+ *
21437
+ * When the initializer is a pure `+` chain of plain string literals and
21438
+ * references to other foldable consts, this returns one merged literal
21439
+ * ValueNode — semantically the string the source spells — that the use site
21440
+ * emits as rodata instead. Anything else (a runtime operand, a struct ctor,
21441
+ * an operator overload) returns null and the tree builds as before.
21442
+ *
21443
+ * Raw token merging is byte-preserving: escape sequences are self-contained
21444
+ * within a part's content (a content ending in a lone `\` would have
21445
+ * escaped that part's closing quote, so it cannot occur), and both
21446
+ * emitters' `\x`-as-octal re-encoding plus the 2/3-digit caps keep merged
21447
+ * escapes from absorbing a following part's text.
21448
+ */
21449
+ const fold_cache = /* @__PURE__ */ new WeakMap();
21450
+ function fold_string_const(decl, consts) {
21451
+ if (fold_cache.has(decl)) return fold_cache.get(decl);
21452
+ const folded = fold_value(decl.value, consts, /* @__PURE__ */ new Set());
21453
+ fold_cache.set(decl, folded);
21454
+ return folded;
21455
+ }
21456
+ function fold_value(node, consts, active) {
21457
+ if (!node) return null;
21458
+ if (node.node_type === "value") {
21459
+ const raw = node.value;
21460
+ if (typeof raw !== "string") return null;
21461
+ if (raw.startsWith("\"")) return literal(raw, node.start);
21462
+ if (active.has(raw)) return null;
21463
+ const ref = consts.get(raw);
21464
+ if (!ref) return null;
21465
+ active.add(raw);
21466
+ const folded = fold_value(ref.value, consts, active);
21467
+ active.delete(raw);
21468
+ return folded;
21469
+ }
21470
+ if (node.node_type === "op") {
21471
+ const op = node;
21472
+ if (!(op.op === "+" && (!op.operator_func || op.operator_func.struct_name === "string"))) return null;
21473
+ const left = fold_value(op.left_value, consts, active);
21474
+ if (!left) return null;
21475
+ const right = fold_value(op.right_value, consts, active);
21476
+ if (!right) return null;
21477
+ return literal(left.value.slice(0, -1) + right.value.slice(1), node.start);
21478
+ }
21479
+ return null;
21480
+ }
21481
+ function literal(raw, start) {
21482
+ return new ValueNode(start, raw, new Type("string", true));
21483
+ }
21484
+ //#endregion
21421
21485
  //#region ../src/build_common/string_escapes.ts
21422
21486
  /**
21423
21487
  * Source string-literal escape handling shared by both backends.
@@ -21452,8 +21516,8 @@ function reencode_hex_escapes(raw) {
21452
21516
  let j = i + 2;
21453
21517
  let value = 0;
21454
21518
  let digits = 0;
21455
- while (j < raw.length && digits < 2 && is_hex_digit(raw[j])) {
21456
- value = value * 16 + hex_value(raw[j]);
21519
+ while (j < raw.length && digits < 2 && is_hex_digit$1(raw[j])) {
21520
+ value = value * 16 + hex_value$1(raw[j]);
21457
21521
  j += 1;
21458
21522
  digits += 1;
21459
21523
  }
@@ -21468,10 +21532,10 @@ function reencode_hex_escapes(raw) {
21468
21532
  }
21469
21533
  return out;
21470
21534
  }
21471
- function is_hex_digit(c) {
21535
+ function is_hex_digit$1(c) {
21472
21536
  return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
21473
21537
  }
21474
- function hex_value(c) {
21538
+ function hex_value$1(c) {
21475
21539
  if (c <= "9") return c.charCodeAt(0) - 48;
21476
21540
  if (c <= "F") return c.charCodeAt(0) - 55;
21477
21541
  return c.charCodeAt(0) - 87;
@@ -21501,7 +21565,7 @@ function scan_string_escapes(raw) {
21501
21565
  if (next === "x") {
21502
21566
  let j = i + 2;
21503
21567
  let digits = 0;
21504
- while (j < end && digits < 2 && is_hex_digit(raw[j])) {
21568
+ while (j < end && digits < 2 && is_hex_digit$1(raw[j])) {
21505
21569
  j += 1;
21506
21570
  digits += 1;
21507
21571
  }
@@ -21536,6 +21600,16 @@ function scan_string_escapes(raw) {
21536
21600
  }
21537
21601
  return issues;
21538
21602
  }
21603
+ /** Escape one raw string-literal token (quotes included) for a GAS
21604
+ * `.asciz` directive: source `\xHH` hex escapes re-encode as 3-digit octal
21605
+ * (GAS consumes `\x` greedily — see reencode_hex_escapes) and raw newlines
21606
+ * (multi-line strings) become `\n` so the directive stays one line. */
21607
+ function escape_asciz(value) {
21608
+ const reencoded = reencode_hex_escapes(value);
21609
+ if (!reencoded.includes("\n")) return reencoded;
21610
+ const quote = reencoded[0];
21611
+ return quote + reencoded.slice(1, reencoded.endsWith(quote) ? -1 : void 0).replace(/\n/g, "\\n") + (reencoded.endsWith(quote) ? quote : "");
21612
+ }
21539
21613
  //#endregion
21540
21614
  //#region ../src/build_aarch64/build_range_node.ts
21541
21615
  function build_range_node$1(node, status) {
@@ -21749,12 +21823,6 @@ function is_value_struct_type_node(node, status) {
21749
21823
  const t = type_from_value_node$1(node);
21750
21824
  return !!t?.name && !!status.structs.find((s) => s.name === t.name && !s.is_simple_type && !s.is_class);
21751
21825
  }
21752
- function escape_asciz(value) {
21753
- const reencoded = reencode_hex_escapes(value);
21754
- if (!reencoded.includes("\n")) return reencoded;
21755
- const quote = reencoded[0];
21756
- return quote + reencoded.slice(1, reencoded.endsWith(quote) ? -1 : void 0).replace(/\n/g, "\\n") + (reencoded.endsWith(quote) ? quote : "");
21757
- }
21758
21826
  /** Allocate an array on the stack with an 8-byte length prefix.
21759
21827
  * Writes the length at the start and returns the offset of the first element. */
21760
21828
  function alloc_array_with_prefix(status, length, element_size) {
@@ -22224,6 +22292,93 @@ function resolve_string_op(op, status) {
22224
22292
  }
22225
22293
  return null;
22226
22294
  }
22295
+ const FLOAT_LITERAL_RE = /^(\+|-)?\d+\.\d+([eE](\+|-)?\d+)?$/;
22296
+ /**
22297
+ * Fold a module-level primitive `const`/`var` initializer to a literal string
22298
+ * when it is a compile-time constant: literal operands, references to other
22299
+ * foldable module-level consts, and arithmetic/bitwise operators over them.
22300
+ * Returns null when the value can only be computed at runtime (a call, a
22301
+ * field read, …), which a file-scope `.quad` initializer cannot express.
22302
+ *
22303
+ * Without this, `pub const N = 3 + 4` at module scope reserved
22304
+ * `N: .space 8` and emitted its store as dead code between functions, so
22305
+ * every read observed 0 (FOLLOWUP.md). Integer math is exact via BigInt so
22306
+ * 64-bit constants are not rounded through JS doubles; float arithmetic
22307
+ * falls back to JS doubles (which match Nomen's `float`).
22308
+ */
22309
+ function fold_const_scalar(node, status, seen) {
22310
+ if (!node) return null;
22311
+ if (node.node_type === "grouped") return fold_const_scalar(node.value, status, seen);
22312
+ if (node.node_type === "value") {
22313
+ const raw = get_raw_value$1(node, status);
22314
+ if (is_int_literal(raw)) return to_decimal_string(raw);
22315
+ if (FLOAT_LITERAL_RE.test(raw)) return raw;
22316
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(raw) && !seen.has(raw)) {
22317
+ const decl = find_global_const(raw, status);
22318
+ if (decl?.value) {
22319
+ seen.add(raw);
22320
+ const folded = fold_const_scalar(decl.value, status, seen);
22321
+ seen.delete(raw);
22322
+ return folded;
22323
+ }
22324
+ }
22325
+ return null;
22326
+ }
22327
+ if (node.node_type === "op") {
22328
+ const op = node;
22329
+ if (op.operator_func) return null;
22330
+ const left = fold_const_scalar(op.left_value, status, seen);
22331
+ if (left === null) return null;
22332
+ const right = fold_const_scalar(op.right_value, status, seen);
22333
+ if (right === null) return null;
22334
+ return apply_const_op(op.op, left, right);
22335
+ }
22336
+ return null;
22337
+ }
22338
+ /** Apply one arithmetic/bitwise operator to two folded literal strings.
22339
+ * Float operands use JS doubles; integral operands stay exact via BigInt. */
22340
+ function apply_const_op(op, left, right) {
22341
+ if (FLOAT_LITERAL_RE.test(left) || FLOAT_LITERAL_RE.test(right)) {
22342
+ const a = Number(left);
22343
+ const b = Number(right);
22344
+ if (!isFinite(a) || !isFinite(b)) return null;
22345
+ switch (op) {
22346
+ case "+": return String(a + b);
22347
+ case "-": return String(a - b);
22348
+ case "*": return String(a * b);
22349
+ case "/": return b === 0 ? null : String(a / b);
22350
+ default: return null;
22351
+ }
22352
+ }
22353
+ let a;
22354
+ let b;
22355
+ try {
22356
+ a = BigInt(left);
22357
+ b = BigInt(right);
22358
+ } catch {
22359
+ return null;
22360
+ }
22361
+ switch (op) {
22362
+ case "+": return (a + b).toString();
22363
+ case "-": return (a - b).toString();
22364
+ case "*": return (a * b).toString();
22365
+ case "/": return b === 0n ? null : (a / b).toString();
22366
+ case "%": return b === 0n ? null : (a % b).toString();
22367
+ case "<<": return (a << b).toString();
22368
+ case ">>": return (a >> b).toString();
22369
+ case "&": return (a & b).toString();
22370
+ case "|": return (a | b).toString();
22371
+ case "^": return (a ^ b).toString();
22372
+ default: return null;
22373
+ }
22374
+ }
22375
+ /** The root-scope `const` declaration named `name` (used to fold a const
22376
+ * reference through to its initializer). Mutable `var`s are deliberately
22377
+ * excluded: their value is established by module init, so baking in the
22378
+ * declaration's initializer could differ from the runtime value. */
22379
+ function find_global_const(name, status) {
22380
+ return (status.root.statements ?? []).find((s) => s.node_type === "declare" && s.declaration === "const" && s.name === name);
22381
+ }
22227
22382
  /**
22228
22383
  * Emit a declaration INITIALIZER expression. Under NIR-driven emission the
22229
22384
  * lowered `NirExpr` rides in and descends through `emit_expr_from_nir` (the
@@ -22255,7 +22410,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
22255
22410
  const prev_heap = status.last_result_is_heap;
22256
22411
  if (node.value?.node_type === "value") {
22257
22412
  const inlined = status.top_level_consts?.get(node.value.value);
22258
- if (inlined?.value) node.value = inlined.value;
22413
+ if (inlined?.value) node.value = fold_string_const(inlined, status.top_level_consts) ?? inlined.value;
22259
22414
  }
22260
22415
  function check_heap() {
22261
22416
  if (status.last_result_is_heap) {
@@ -23197,7 +23352,15 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23197
23352
  if (status.function_return_label) {
23198
23353
  const offset = declare_slot_offset(status, node.name, size);
23199
23354
  status.stack_offsets.set(node.name, offset);
23200
- } else emit_data(status, `${node.name}: .space ${size}\n`);
23355
+ } else {
23356
+ const folded = fold_const_scalar(node.value, status, /* @__PURE__ */ new Set());
23357
+ if (folded !== null) {
23358
+ emit_data(status, `${node.name}: ${directive} ${folded}\n`);
23359
+ if (size % 4 !== 0) emit_data(status, `.p2align 2\n`);
23360
+ return;
23361
+ }
23362
+ emit_data(status, `${node.name}: .space ${size}\n`);
23363
+ }
23201
23364
  if (node.type.name === "string" && !node.type.is_view && is_view_value(node.value, status)) {
23202
23365
  emit_view_string_arg(node.value, status);
23203
23366
  emit_view_materialize_owned(status);
@@ -35126,6 +35289,8 @@ function build_function_node(node, status) {
35126
35289
  status.function_view_params = /* @__PURE__ */ new Set();
35127
35290
  const old_return_type = status.function_return_type;
35128
35291
  status.function_return_type = node.return_type;
35292
+ const old_variable_types = status.variable_types;
35293
+ status.variable_types = /* @__PURE__ */ new Map();
35129
35294
  const old_nullable_ret_has = status.nullable_ret_has_param;
35130
35295
  if (is_nullable_struct_type(node.return_type, status)) status.nullable_ret_has_param = "_ret_has";
35131
35296
  else status.nullable_ret_has_param = void 0;
@@ -35181,6 +35346,7 @@ function build_function_node(node, status) {
35181
35346
  status.function_variadic_params = old_variadic_params;
35182
35347
  status.function_view_params = old_view_params;
35183
35348
  status.function_return_type = old_return_type;
35349
+ status.variable_types = old_variable_types;
35184
35350
  status.nullable_ret_has_param = old_nullable_ret_has;
35185
35351
  status.current_function_name = old_function_name;
35186
35352
  status.current_function = old_current_function;
@@ -36790,7 +36956,7 @@ function build_value_node$1(node, status) {
36790
36956
  }
36791
36957
  const inlined = status.top_level_consts?.get(value);
36792
36958
  if (inlined?.value) {
36793
- build_node$1(inlined.value, status);
36959
+ build_node$1(fold_string_const(inlined, status.top_level_consts) ?? inlined.value, status);
36794
36960
  return;
36795
36961
  }
36796
36962
  if (node.is_enum_shorthand) {
@@ -37731,7 +37897,7 @@ function build_value_node(node, status) {
37731
37897
  }
37732
37898
  const inlined = status.top_level_consts?.get(original_value);
37733
37899
  if (inlined?.value) {
37734
- build_node(inlined.value, status);
37900
+ build_node(fold_string_const(inlined, status.top_level_consts) ?? inlined.value, status);
37735
37901
  return;
37736
37902
  }
37737
37903
  if (node.is_enum_shorthand) {
@@ -42974,8 +43140,6 @@ function build_access_method(node, access_func, status) {
42974
43140
  return true;
42975
43141
  };
42976
43142
  const overflow_count = Math.max(0, total_arg_slots - (8 - start_reg));
42977
- let overflow_base = 0;
42978
- if (overflow_count > 0) overflow_base = allocate_stack_space(status, overflow_count * 8, 16);
42979
43143
  const lambda_arg_indices = /* @__PURE__ */ new Set();
42980
43144
  for (let i = 0; i < access_func.params.length; i++) {
42981
43145
  const p = access_func.params[i];
@@ -42986,21 +43150,17 @@ function build_access_method(node, access_func, status) {
42986
43150
  lambda_arg_indices.add(i);
42987
43151
  }
42988
43152
  const lambda_arg_slots = [];
42989
- const has_pair_args = view_arg_set.size > 0 || string_arg_set.size > 0;
42990
- let view_spill_base = 0;
42991
- if (has_pair_args) view_spill_base = allocate_stack_space(status, total_arg_slots * 8, 16);
42992
- const view_half_store = (j, half) => {
42993
- const half_slot = start_reg + arg_slot[j] + half;
42994
- return half_slot >= 8 ? overflow_base + (half_slot - 8) * 8 : view_spill_base + (arg_slot[j] + half) * 8;
43153
+ const arg_builds_in_loop = (i) => {
43154
+ if (view_arg_set.has(i) || string_arg_set.has(i)) return true;
43155
+ if ((access_func.ref_param_indices ?? []).includes(i)) return true;
43156
+ const param_type = access_func.params[i].type?.name || "";
43157
+ if (is_struct_type(param_type, status) || is_enum_with_data_type(param_type, status)) return true;
43158
+ return !arg_deferrable(i);
42995
43159
  };
42996
- const param0 = access_func.params[0];
42997
- const param0_is_pair = view_arg_set.has(0) || string_arg_set.has(0);
42998
- const param0_is_ref = (access_func.ref_param_indices ?? []).includes(0);
42999
- const param0_type_name = param0?.type?.name || "";
43000
- const param0_builds_in_loop = access_func.params.length > 0 && (param0_is_ref || is_struct_type(param0_type_name, status) || is_enum_with_data_type(param0_type_name, status) || !arg_deferrable(0));
43001
- const static_arg0_needs_spill = start_reg === 0 && access_func.params.length > 0 && !param0_is_pair && param0_builds_in_loop;
43002
- let static_arg0_spill = 0;
43003
- if (static_arg0_needs_spill) static_arg0_spill = allocate_stack_space(status, 8, 8);
43160
+ const any_in_loop_arg = access_func.params.some((_, i) => arg_builds_in_loop(i));
43161
+ let arg_spill_base = 0;
43162
+ if (any_in_loop_arg) arg_spill_base = allocate_stack_space(status, total_arg_slots * 8, 16);
43163
+ const view_half_store = (j, half) => arg_spill_base + (arg_slot[j] + half) * 8;
43004
43164
  const ref_class_param_reload = [];
43005
43165
  const ref_class_sync_names = [];
43006
43166
  for (let i = access_func.params.length - 1; i >= 0; i--) {
@@ -43070,31 +43230,20 @@ function build_access_method(node, access_func, status) {
43070
43230
  lambda_arg_slots.push(dispose_slot);
43071
43231
  }
43072
43232
  }
43073
- const slot = start_reg + arg_slot[i];
43074
43233
  ensure_newline(status);
43075
- if (slot >= 8) emit_asm(status, `str x0, [x29, #${overflow_base + (slot - 8) * 8}]\n`);
43076
- else {
43077
- const reg = `x${slot}`;
43078
- if (reg !== "x0") emit_asm(status, `mov ${reg}, x0\n`);
43079
- else if (static_arg0_needs_spill) emit_asm(status, `str x0, [x29, #${static_arg0_spill}]\n`);
43080
- }
43234
+ emit_asm(status, `str x0, [x29, #${arg_spill_base + arg_slot[i] * 8}]\n`);
43081
43235
  }
43082
43236
  deferred_args.sort((a, b) => b.slot - a.slot);
43083
43237
  for (const d of deferred_args) {
43084
43238
  build_operand(d.param, `x${start_reg + d.slot}`, status);
43085
43239
  ensure_newline(status);
43086
43240
  }
43087
- if (has_pair_args) for (let j = 0; j < access_func.params.length; j++) {
43088
- if (!view_arg_set.has(j) && !string_arg_set.has(j)) continue;
43089
- for (const half of [0, 1]) {
43090
- const half_slot = start_reg + arg_slot[j] + half;
43091
- if (half_slot >= 8) continue;
43092
- emit_asm(status, `ldr x${half_slot}, [x29, #${view_spill_base + (arg_slot[j] + half) * 8}]\n`);
43093
- }
43094
- }
43095
- if (static_arg0_needs_spill) {
43096
- ensure_newline(status);
43097
- emit_asm(status, `ldr x0, [x29, #${static_arg0_spill}]\n`);
43241
+ const deferred_slots = new Set(deferred_args.map((d) => start_reg + d.slot));
43242
+ for (let s = 0; s < total_arg_slots; s++) {
43243
+ const slot = start_reg + s;
43244
+ if (slot >= 8) continue;
43245
+ if (deferred_slots.has(slot)) continue;
43246
+ emit_asm(status, `ldr x${slot}, [x29, #${arg_spill_base + s * 8}]\n`);
43098
43247
  }
43099
43248
  ensure_newline(status);
43100
43249
  if (receiver_is_string) emit_asm(status, frees_string_receiver ? `ldp x0, x1, [sp]\n` : `ldp x0, x1, [sp], #16\n`);
@@ -43104,8 +43253,9 @@ function build_access_method(node, access_func, status) {
43104
43253
  if (overflow_count > 0) {
43105
43254
  outgoing_size = Math.ceil(overflow_count * 8 / 16) * 16;
43106
43255
  emit_asm(status, `sub sp, sp, #${outgoing_size}\n`);
43256
+ const overflow_first = 8 - start_reg;
43107
43257
  for (let k = 0; k < overflow_count; k++) {
43108
- emit_asm(status, `ldr x9, [x29, #${overflow_base + k * 8}]\n`);
43258
+ emit_asm(status, `ldr x9, [x29, #${arg_spill_base + (overflow_first + k) * 8}]\n`);
43109
43259
  emit_asm(status, `str x9, [sp, #${k * 8}]\n`);
43110
43260
  }
43111
43261
  }
@@ -43171,7 +43321,7 @@ function build_access_method(node, access_func, status) {
43171
43321
  emit_asm(status, `str x0, [sp, #-16]!\n`);
43172
43322
  emit_asm(status, `str x1, [sp, #-16]!\n`);
43173
43323
  emit_asm(status, `mov x0, x9\n`);
43174
- emit_asm(status, `bl _nomen_free_wrap\n`);
43324
+ emit_free(status);
43175
43325
  emit_asm(status, `ldr x1, [sp], #16\n`);
43176
43326
  emit_asm(status, `ldr x0, [sp], #16\n`);
43177
43327
  emit_asm(status, `add sp, sp, #16\n`);
@@ -44184,10 +44334,7 @@ function build(root, options = {}) {
44184
44334
  }
44185
44335
  if (status.strings && status.strings.size > 0) {
44186
44336
  status.code += "\n";
44187
- for (const [label, value] of status.strings) {
44188
- const escaped = value.replace(/\n/g, "\\n");
44189
- status.code += `${label}: .asciz ${escaped}\n`;
44190
- }
44337
+ for (const [label, value] of status.strings) status.code += `${label}: .asciz ${escape_asciz(value)}\n`;
44191
44338
  }
44192
44339
  if (status.float_literals && status.float_literals.size > 0) {
44193
44340
  status.code += "\n.p2align 2\n";
@@ -50012,16 +50159,22 @@ function must_use_type_display_name(en, type) {
50012
50159
  */
50013
50160
  function gather_top_level_consts(block, status) {
50014
50161
  const seen = /* @__PURE__ */ new Set();
50162
+ const candidates = [];
50015
50163
  for (const child of block.statements) {
50016
50164
  if (child.node_type !== "declare") continue;
50017
50165
  const decl = child;
50018
50166
  if (decl.declaration !== "const") continue;
50019
- if (!decl.name || !decl.type?.name) continue;
50020
- if (decl.type.is_array) continue;
50167
+ if (!decl.name) continue;
50021
50168
  if (seen.has(decl.name)) continue;
50022
50169
  seen.add(decl.name);
50170
+ candidates.push(decl);
50171
+ }
50172
+ infer_const_decl_types(candidates, status);
50173
+ for (const decl of candidates) {
50174
+ if (!decl.type?.name) continue;
50175
+ if (decl.type.is_array) continue;
50023
50176
  status.values.push({
50024
- declaration: decl.declaration,
50177
+ declaration: "const",
50025
50178
  name: decl.name,
50026
50179
  type: decl.type,
50027
50180
  is_set: !!decl.value,
@@ -50031,6 +50184,60 @@ function gather_top_level_consts(block, status) {
50031
50184
  }
50032
50185
  }
50033
50186
  /**
50187
+ * Give every root-level `const` a declared type BEFORE the statement walk,
50188
+ * so a const declared BELOW its first use resolves like a free function
50189
+ * (which hoists across the whole file). Parse can only infer the type when
50190
+ * the initializer is a bare literal; a composed chain (`const TAG = "<" +
50191
+ * TAG_NAME + ">"`, the allmark htmlPatterns shape) stayed typeless and its
50192
+ * use-above-declaration failed with "Unknown value".
50193
+ *
50194
+ * The inference iterates until nothing new is derivable, so a chain may
50195
+ * reference consts declared BELOW itself too. Recognized initializers:
50196
+ * literals, references to other (typed) consts, and arithmetic/concat
50197
+ * operations (the result type is the left operand's, mirroring
50198
+ * check_operation_node). Anything else (constructor calls, function calls)
50199
+ * keeps today's behavior: annotate the type or declare before use.
50200
+ */
50201
+ function infer_const_decl_types(decls, status) {
50202
+ const known = /* @__PURE__ */ new Map();
50203
+ for (const decl of decls) if (decl.type?.name) known.set(decl.name, decl.type);
50204
+ let progress = true;
50205
+ while (progress) {
50206
+ progress = false;
50207
+ for (const decl of decls) {
50208
+ if (decl.type?.name) continue;
50209
+ const inferred = infer_const_expr_type(decl.value, known, status);
50210
+ if (!inferred) continue;
50211
+ decl.type = inferred;
50212
+ known.set(decl.name, inferred);
50213
+ progress = true;
50214
+ }
50215
+ }
50216
+ }
50217
+ function infer_const_expr_type(node, known, status) {
50218
+ if (!node) return void 0;
50219
+ if (node.node_type === "grouped") return infer_const_expr_type(node.value, known, status);
50220
+ if (node.node_type === "value") {
50221
+ const raw = node.value;
50222
+ if (typeof raw !== "string") return void 0;
50223
+ if (raw.startsWith("\"")) return new Type("string", true);
50224
+ if (raw === "true" || raw === "false") return new Type("bool", true);
50225
+ if (raw.startsWith("'") && raw.endsWith("'")) return new Type("char", true);
50226
+ if (is_int_literal(raw)) return new Type("int", true);
50227
+ if (/^(\+|-)*\d+.\d+([eE](\+|-)?\d+)?$/.test(raw)) return new Type("float", true);
50228
+ const declared = known.get(raw);
50229
+ if (declared) return declared;
50230
+ return;
50231
+ }
50232
+ if (node.node_type === "op") return infer_const_expr_type(node.left_value, known, status);
50233
+ if (node.node_type === "func_call") {
50234
+ const call = node;
50235
+ const target = status.structs.find((s) => s.name === call.name && !s.is_simple_type);
50236
+ if (target && target.type_params.length === 0) return new Type(target.name);
50237
+ return;
50238
+ }
50239
+ }
50240
+ /**
50034
50241
  * A bare statement that constructs (or obtains) a class instance without
50035
50242
  * binding it to a variable would leak: classes are heap-allocated and freed
50036
50243
  * by the owning variable's scope-exit cleanup, so an unbound instance is
@@ -50368,6 +50575,7 @@ function clone_status(status) {
50368
50575
  var_name_counter: status.var_name_counter,
50369
50576
  type_params: status.type_params,
50370
50577
  errors: status.errors,
50578
+ warnings: status.warnings,
50371
50579
  buffer_caps: status.buffer_caps,
50372
50580
  equal_lengths: status.equal_lengths?.slice(),
50373
50581
  mutated_local_names: status.mutated_local_names,
@@ -52468,6 +52676,143 @@ function is_visible(declaring_scope, visibility, access_scope, stack, declaring_
52468
52676
  return access_scope === declaring_scope;
52469
52677
  }
52470
52678
  //#endregion
52679
+ //#region ../src/check/utils/regex_pattern_lint.ts
52680
+ /**
52681
+ * Compile-time lint for Regex pattern literals (FOLLOWUP "Regex pattern
52682
+ * escapes").
52683
+ *
52684
+ * The Regex engine (core/System/Text/Regex.nm) recognizes only the
52685
+ * shorthands (`\d \D \w \W \s \S`), backreferences (`\1`-`\9`), the control
52686
+ * escapes (`\n \r \t`), and escaped metacharacters. Any OTHER engine escape
52687
+ — most notably a typo like `\z` — silently matches a literal letter. When a
52688
+ * Regex entry point's pattern argument is a static string literal, the check
52689
+ * can flag those as probable typos; dynamic runtime patterns can't be
52690
+ * validated this way.
52691
+ *
52692
+ * The literal arrives RAW (quotes included, Nomen escapes unresolved), so it
52693
+ * must first be decoded to the engine-visible bytes: a Nomen `"\n"` is a real
52694
+ * LF byte the engine treats as a literal, while `"\n"` reaches the engine as
52695
+ * a backslash + 'n' escape. Only the second spelling can carry a typo.
52696
+ */
52697
+ /** Decode a raw string-literal token (quotes included) to its engine-visible
52698
+ * bytes, resolving Nomen's own string escapes. Mirrors the pair-counting of
52699
+ * string_literal_length and the decode rules of decode_char_literal. */
52700
+ function decode_string_literal_bytes(raw) {
52701
+ const bytes = [];
52702
+ let i = 1;
52703
+ const end = raw.endsWith("\"") ? raw.length - 1 : raw.length;
52704
+ while (i < end) {
52705
+ if (raw[i] !== "\\") {
52706
+ const cp = raw.codePointAt(i) ?? 0;
52707
+ if (cp < 128) {
52708
+ bytes.push(cp);
52709
+ i += 1;
52710
+ } else if (cp < 2048) {
52711
+ bytes.push(192 | cp >> 6, 128 | cp & 63);
52712
+ i += 1;
52713
+ } else if (cp < 65536) {
52714
+ bytes.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
52715
+ i += 1;
52716
+ } else {
52717
+ bytes.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
52718
+ i += 2;
52719
+ }
52720
+ continue;
52721
+ }
52722
+ const next = raw[i + 1];
52723
+ if (next === void 0) break;
52724
+ if (next === "x") {
52725
+ let j = i + 2;
52726
+ let value = 0;
52727
+ let digits = 0;
52728
+ while (j < end && digits < 2 && is_hex_digit(raw[j])) {
52729
+ value = value * 16 + hex_value(raw[j]);
52730
+ j += 1;
52731
+ digits += 1;
52732
+ }
52733
+ if (digits > 0) {
52734
+ bytes.push(value & 255);
52735
+ i = j;
52736
+ continue;
52737
+ }
52738
+ i += 2;
52739
+ continue;
52740
+ }
52741
+ if (next >= "0" && next <= "7") {
52742
+ let j = i + 1;
52743
+ let value = 0;
52744
+ let digits = 0;
52745
+ while (j < end && digits < 3 && raw[j] >= "0" && raw[j] <= "7") {
52746
+ value = value * 8 + (raw[j].charCodeAt(0) - 48);
52747
+ j += 1;
52748
+ digits += 1;
52749
+ }
52750
+ bytes.push(value & 255);
52751
+ i = j;
52752
+ continue;
52753
+ }
52754
+ switch (next) {
52755
+ case "\\":
52756
+ bytes.push(92);
52757
+ break;
52758
+ case "n":
52759
+ bytes.push(10);
52760
+ break;
52761
+ case "t":
52762
+ bytes.push(9);
52763
+ break;
52764
+ case "r":
52765
+ bytes.push(13);
52766
+ break;
52767
+ case "0":
52768
+ bytes.push(0);
52769
+ break;
52770
+ case "\"":
52771
+ bytes.push(34);
52772
+ break;
52773
+ case "'":
52774
+ bytes.push(39);
52775
+ break;
52776
+ default: bytes.push(next.charCodeAt(0));
52777
+ }
52778
+ i += 2;
52779
+ }
52780
+ return bytes;
52781
+ }
52782
+ function is_hex_digit(c) {
52783
+ return c >= "0" && c <= "9" || c >= "a" && c <= "f" || c >= "A" && c <= "F";
52784
+ }
52785
+ function hex_value(c) {
52786
+ if (c <= "9") return c.charCodeAt(0) - 48;
52787
+ if (c <= "F") return c.charCodeAt(0) - 55;
52788
+ return c.charCodeAt(0) - 87;
52789
+ }
52790
+ function is_ascii_letter(code) {
52791
+ return code >= 65 && code <= 90 || code >= 97 && code <= 122;
52792
+ }
52793
+ /** Scan the decoded pattern bytes for escapes outside the engine's known set
52794
+ * (`\d \D \w \W \s \S`, `\1`-`\9`, `\n \r \t`, escaped punctuation) and
52795
+ * return one warning message per probable typo. */
52796
+ function lint_regex_pattern(raw) {
52797
+ const bytes = decode_string_literal_bytes(raw);
52798
+ const messages = [];
52799
+ let i = 0;
52800
+ while (i < bytes.length) {
52801
+ if (bytes[i] !== 92) {
52802
+ i += 1;
52803
+ continue;
52804
+ }
52805
+ const escaped = bytes[i + 1];
52806
+ if (escaped === void 0) break;
52807
+ if (!(escaped === 100 || escaped === 68 || escaped === 119 || escaped === 87 || escaped === 115 || escaped === 83 || escaped === 110 || escaped === 114 || escaped === 116 || escaped >= 49 && escaped <= 57) && is_ascii_letter(escaped)) {
52808
+ const letter = String.fromCharCode(escaped);
52809
+ messages.push(`Regex pattern: unsupported escape '\\${letter}' matches a literal '${letter}' — supported escapes are \\d \\D \\w \\W \\s \\S \\1-\\9 \\n \\r \\t and escaped punctuation`);
52810
+ }
52811
+ i += 2;
52812
+ }
52813
+ return messages;
52814
+ }
52815
+ //#endregion
52471
52816
  //#region ../src/check/utils/string_mutation_scan.ts
52472
52817
  /**
52473
52818
  * Interprocedural no-mutation scan for borrow-position `to_string()` elision
@@ -52861,6 +53206,7 @@ function is_inside_core_method(status) {
52861
53206
  }
52862
53207
  function check_function_call(node, status, func, target_type, self_value, self_path) {
52863
53208
  set_resolved_function(node, func);
53209
+ lint_regex_entry_pattern(node, func, status);
52864
53210
  const access_scope = status.stack.at(-1);
52865
53211
  if (func.name === "#init") {
52866
53212
  const struct_name = target_type?.name || func.return_type.name;
@@ -53372,6 +53718,59 @@ function receiver_is_const(target_type, self_value, status) {
53372
53718
  const binding = status.values.findLast((v) => v.name === self_value);
53373
53719
  return !!binding && binding.declaration === "const" && !binding.type.is_ref;
53374
53720
  }
53721
+ /**
53722
+ * Regex entry points whose FIRST parameter is the pattern. When that argument
53723
+ * is a static string literal, flag engine escapes outside the known set as
53724
+ * probable typos (see regex_pattern_lint.ts) — a dynamic runtime pattern
53725
+ * can't be validated this way.
53726
+ */
53727
+ const REGEX_PATTERN_ENTRY_POINTS = /* @__PURE__ */ new Set([
53728
+ "test",
53729
+ "match",
53730
+ "count",
53731
+ "find",
53732
+ "replace_all",
53733
+ "captures",
53734
+ "test_ci",
53735
+ "match_ci",
53736
+ "find_ci",
53737
+ "captures_ci",
53738
+ "replace_all_ci"
53739
+ ]);
53740
+ /**
53741
+ * Lint a static string-literal pattern passed to a Regex entry point. Only
53742
+ * the library's own `Regex` struct qualifies (a user struct of the same name
53743
+ * with a same-named method is left alone), and only when the first argument
53744
+ * is a literal — anything else is a runtime value.
53745
+ */
53746
+ function lint_regex_entry_pattern(node, func, status) {
53747
+ if (!is_library_regex_func(func, status)) return;
53748
+ if (!REGEX_PATTERN_ENTRY_POINTS.has(func.name)) return;
53749
+ const pattern = node.params[0];
53750
+ if (!pattern || pattern.node_type !== "value") return;
53751
+ const raw = pattern.value;
53752
+ if (!raw.startsWith("\"")) return;
53753
+ if (!status.warnings) status.warnings = [];
53754
+ for (const message of lint_regex_pattern(raw)) status.warnings.push({
53755
+ message,
53756
+ start: pattern.start,
53757
+ line: 0,
53758
+ column: 0
53759
+ });
53760
+ }
53761
+ /**
53762
+ * Whether `func` is a method of the library's `Regex` struct. Two paths: the
53763
+ * method's enclosing struct is stamped on it for calls checked after the
53764
+ * struct gather (library bodies, later files), and for user call sites —
53765
+ * which are checked BEFORE the gather stamps `func.scope` — the callee is
53766
+ * matched against the gathered library struct's own methods.
53767
+ */
53768
+ function is_library_regex_func(func, status) {
53769
+ const scope = func.scope;
53770
+ if (scope && scope.node_type === "struct" && scope.is_library && scope.name === "Regex") return true;
53771
+ const regex_struct = status.structs.find((s) => s.is_library && s.name === "Regex");
53772
+ return !!regex_struct && regex_struct.functions.includes(func);
53773
+ }
53375
53774
  //#endregion
53376
53775
  //#region ../src/check/check_access_node.ts
53377
53776
  function check_access_node(node, status) {
@@ -55547,7 +55946,13 @@ function check_return_node(ret, status) {
55547
55946
  } else if (!func.is_arrow_body) {
55548
55947
  add_error(status, `Function returns a value but has no 'out' return type`, ret.start);
55549
55948
  func.return_type = ret.type;
55550
- } else func.return_type = ret.type;
55949
+ } else {
55950
+ func.return_type = ret.type;
55951
+ if (!ret.type?.name) {
55952
+ func.return_type = new Type("void");
55953
+ ret.type = new Type("void");
55954
+ }
55955
+ }
55551
55956
  }
55552
55957
  }
55553
55958
  //#endregion
@@ -58515,7 +58920,24 @@ function resolve_linked_types(source, library, file_path) {
58515
58920
  }
58516
58921
  const user_declared_funcs = /* @__PURE__ */ new Set();
58517
58922
  for (let i = 0; i < tokens.length - 1; i++) if (tokens[i].value === "func" && /^\w+$/.test(tokens[i + 1].value)) user_declared_funcs.add(tokens[i + 1].value);
58518
- for (const token of tokens) {
58923
+ const import_tokens = /* @__PURE__ */ new Set();
58924
+ for (let i = 0; i < tokens.length; i++) {
58925
+ if (tokens[i].value !== "import") continue;
58926
+ import_tokens.add(i);
58927
+ let j = i + 1;
58928
+ if (j < tokens.length && /^\w+$/.test(tokens[j].value)) {
58929
+ import_tokens.add(j);
58930
+ j += 1;
58931
+ while (j + 1 < tokens.length && tokens[j].value === "::" && /^\w+$/.test(tokens[j + 1].value)) {
58932
+ import_tokens.add(j);
58933
+ import_tokens.add(j + 1);
58934
+ j += 2;
58935
+ }
58936
+ }
58937
+ }
58938
+ for (let i = 0; i < tokens.length; i++) {
58939
+ if (import_tokens.has(i)) continue;
58940
+ const token = tokens[i];
58519
58941
  if (user_defined.has(token.value)) continue;
58520
58942
  if (library.types.has(token.value) && !BASE_TYPES.includes(token.value)) needed.add(token.value);
58521
58943
  else if (library.functions.has(token.value) && !user_declared_funcs.has(token.value)) needed.add(token.value);
@@ -59323,6 +59745,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59323
59745
  const parsed = parse(input + "\n" + harness, library, resolved);
59324
59746
  if (parsed.errors.length) {
59325
59747
  result.ok = false;
59748
+ result.phase = "parse";
59326
59749
  result.crashed = parsed.errors.map((e) => `${e.message} (${e.line ?? "?"}:${e.column ?? "?"})`).join("\n");
59327
59750
  result.ms = performance.now() - start;
59328
59751
  return result;
@@ -59333,6 +59756,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59333
59756
  });
59334
59757
  if (buildResult.errors && buildResult.errors.length) {
59335
59758
  result.ok = false;
59759
+ result.phase = "build";
59336
59760
  result.crashed = buildResult.errors.map((e) => `${e.message} (${e.line ?? "?"}:${e.column ?? "?"})`).join("\n");
59337
59761
  result.ms = performance.now() - start;
59338
59762
  return result;
@@ -59344,6 +59768,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59344
59768
  const runtime_src = audit_runtime ? path.resolve(audit_runtime) : find_audit_runtime(path.dirname(resolved));
59345
59769
  if (!runtime_src || !fs.existsSync(runtime_src)) {
59346
59770
  result.ok = false;
59771
+ result.phase = "setup";
59347
59772
  result.crashed = "Audit enabled but audit_runtime.c was not found. Pass --audit-runtime <path>.";
59348
59773
  result.ms = performance.now() - start;
59349
59774
  return result;
@@ -59356,7 +59781,8 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59356
59781
  audit_obj
59357
59782
  ]);
59358
59783
  }
59359
- const ext = arch === "aarch64" ? ".s" : ".c";
59784
+ const platform = default_platform();
59785
+ const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
59360
59786
  const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
59361
59787
  const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
59362
59788
  fs.writeFileSync(path.join(buildDir, "main.h"), buildResult.headers ?? "");
@@ -59367,6 +59793,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59367
59793
  codefile
59368
59794
  ];
59369
59795
  if (audit_obj) link_args.push(audit_obj);
59796
+ if (platform === "macos" || platform === "ios") link_args.push("-framework", "CoreGraphics", "-framework", "Foundation", "-framework", "AppKit", "-lobjc");
59370
59797
  try {
59371
59798
  execFileSync("clang", link_args, {
59372
59799
  encoding: "utf8",
@@ -59379,6 +59806,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59379
59806
  } catch (err) {
59380
59807
  const stderr = err.stderr ? err.stderr.toString() : err.message ?? "";
59381
59808
  result.ok = false;
59809
+ result.phase = "link";
59382
59810
  result.crashed = `link failed: ${stderr.trim() || "clang error"}`;
59383
59811
  result.ms = performance.now() - start;
59384
59812
  return result;
@@ -59407,6 +59835,7 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
59407
59835
  result.benches = records.benches;
59408
59836
  result.other = records.other;
59409
59837
  result.crashed = crashed;
59838
+ if (crashed) result.phase = "run";
59410
59839
  result.ok = !(result.fails.length > 0 || result.tests.some((t) => t.failed > 0)) && crashed === void 0;
59411
59840
  result.ms = performance.now() - start;
59412
59841
  return result;
@@ -59434,7 +59863,8 @@ const C = {
59434
59863
  function report_file(result) {
59435
59864
  const rel = path.relative(process.cwd(), result.file);
59436
59865
  if (result.crashed && result.tests.length === 0 && result.fails.length === 0) {
59437
- console.log(` ${C.red("✗")} ${rel} ${C.red("(failed to build)")}`);
59866
+ const label = result.phase === "run" ? "(crashed before records)" : result.phase === "link" ? "(link failed)" : result.phase === "setup" ? "(setup failed)" : "(failed to build)";
59867
+ console.log(` ${C.red("✗")} ${rel} ${C.red(label)}`);
59438
59868
  console.log(C.red(result.crashed));
59439
59869
  return;
59440
59870
  }