nomen-lang 0.2.0 → 0.2.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
@@ -21164,7 +21164,8 @@ function build_return_node$1(node, status, nir_value) {
21164
21164
  else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
21165
21165
  }
21166
21166
  if (needs_borrow_strdup) {
21167
- emit_strdup_string(status);
21167
+ if (is_view_value(node.value, status) && (node.value.node_type === "value" || node.value.node_type === "access")) emit_view_materialize_owned(status);
21168
+ else emit_strdup_string(status);
21168
21169
  if (!status.code.endsWith("\n")) status.code += "\n";
21169
21170
  status.last_result_is_heap = true;
21170
21171
  }
@@ -24379,13 +24380,17 @@ function emit_struct_address(node, status) {
24379
24380
  /**
24380
24381
  * Whether an argument expression is a fat string VALUE: either its static
24381
24382
  * type names `string`, or it is a string literal (whose ValueNode.type may
24382
- * be unset). Literals ride the pair ABI like any string.
24383
+ * be unset). Literals ride the pair ABI like any string. A `view string`
24384
+ * counts too — it IS the same (ptr, len) pair at runtime, so a view
24385
+ * argument to a non-view `string` param (e.g. a `.slice` receiver) must
24386
+ * occupy two slots just like an owned string. (`view` DECLARED params are
24387
+ * unaffected: the view_arg_set check runs first.)
24383
24388
  */
24384
24389
  function arg_is_string$1(node) {
24385
24390
  const v = node;
24386
24391
  if (node.node_type === "value" && typeof v.value === "string" && v.value.startsWith("\"")) return true;
24387
24392
  const t = type_from_value_node$1(node);
24388
- return t?.name === "string" && !t.is_view && !t.is_array;
24393
+ return t?.name === "string" && !t.is_array;
24389
24394
  }
24390
24395
  function build_function_call_node$1(node, status) {
24391
24396
  if (node.is_enum_shorthand && node.params.length > 0) {
@@ -25992,6 +25997,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
25992
25997
  status.last_result_is_heap = false;
25993
25998
  emit_rhs_value$1(node.right_value, nir_rhs, status);
25994
25999
  if (!status.code.endsWith("\n")) status.code += "\n";
26000
+ const assign_lhs_type = status.scoped_declarations.find((d) => d.name === name)?.type ?? status.variable_types?.get(name);
26001
+ const rhs_is_view_into_owned = !node.swap && !node.operator && size === 16 && assign_lhs_type?.name === "string" && !assign_lhs_type.is_view && is_view_value(node.right_value, status);
26002
+ if (rhs_is_view_into_owned) {
26003
+ emit_view_materialize_owned(status);
26004
+ if (!status.code.endsWith("\n")) status.code += "\n";
26005
+ }
25995
26006
  const saves_fat_pair = size === 16;
25996
26007
  if (saves_fat_pair) status.code += `stp x0, x1, [sp, #-16]!\n`;
25997
26008
  else status.code += `str x0, [sp, #-16]!\n`;
@@ -26007,7 +26018,10 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
26007
26018
  const move_source = rhs_is_string_var && node.last_use_move && move_on_last_use_enabled() ? rhs_value.value : void 0;
26008
26019
  const explicit_move_source = !node.swap && !!rhs_value && rhs_value.is_moved && size === 16 && rhs_value.type?.name === "string" && !rhs_value.type?.is_view ? rhs_value.value : void 0;
26009
26020
  const borrow_needs_dup = !node.swap && size === 16 && is_string_borrow(node.right_value) && !!status.force_heap_strings?.has(name);
26010
- if (rhs_is_string_var && move_source === void 0 || borrow_needs_dup) emit_strdup_string(status);
26021
+ if (rhs_is_view_into_owned) {
26022
+ if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
26023
+ status.heap_strings.add(name);
26024
+ } else if (rhs_is_string_var && move_source === void 0 || borrow_needs_dup) emit_strdup_string(status);
26011
26025
  if (rhs_is_string_var || borrow_needs_dup) {
26012
26026
  if (move_source !== void 0) status.heap_strings?.delete(move_source);
26013
26027
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
@@ -29176,13 +29190,14 @@ function build_access_field(node, status) {
29176
29190
  /**
29177
29191
  * Whether an argument expression is a fat string VALUE: either its static
29178
29192
  * type names `string`, or it is a string literal (whose ValueNode.type may
29179
- * be unset). Literals ride the pair ABI like any string.
29193
+ * be unset). Literals ride the pair ABI like any string. A `view string`
29194
+ * counts too — same pair ABI (see build_function_call_node).
29180
29195
  */
29181
29196
  function arg_is_string(node) {
29182
29197
  const v = node;
29183
29198
  if (node.node_type === "value" && typeof v.value === "string" && v.value.startsWith("\"")) return true;
29184
29199
  const t = type_from_value_node$1(node);
29185
- return t?.name === "string" && !t.is_view && !t.is_array;
29200
+ return t?.name === "string" && !t.is_array;
29186
29201
  }
29187
29202
  function build_access_method(node, access_func, status) {
29188
29203
  let target_type = type_from_value_node$1(node.target);
@@ -29742,7 +29757,7 @@ function build_access_method(node, access_func, status) {
29742
29757
  status.stack_offsets.set(temp_addr, temp_offset);
29743
29758
  status.code += `add x8, x29, #${temp_offset}\n`;
29744
29759
  }
29745
- const receiver_is_string = !access_func.is_static && target_type?.name === "string" && !target_type.is_view && !target_type.is_array && !method_self_is_ref;
29760
+ const receiver_is_string = !access_func.is_static && target_type?.name === "string" && !target_type.is_array && !method_self_is_ref;
29746
29761
  let frees_string_receiver = false;
29747
29762
  if (receiver_is_string) {
29748
29763
  status.last_result_is_heap = false;
@@ -32535,8 +32550,9 @@ function build_struct_functions(node, status, skip_init = false) {
32535
32550
  } else if (return_type.startsWith("Array_")) status.code += `void* ${emit_label}(`;
32536
32551
  else if (func.return_type.is_view) status.code += `nomen_view ${emit_label}(`;
32537
32552
  else {
32538
- const return_struct = status.structs.find((s) => s.name === return_type && !s.is_simple_type);
32539
- const return_trait = status.traits.find((t) => t.name === return_type);
32553
+ const mono_return_type = mono_type_name(func.return_type);
32554
+ const return_struct = status.structs.find((s) => s.name === mono_return_type && !s.is_simple_type);
32555
+ const return_trait = status.traits.find((t) => t.name === mono_return_type);
32540
32556
  if (return_struct && !return_struct.is_class) {
32541
32557
  const swap = status.code;
32542
32558
  status.code = status.headers;
@@ -32544,7 +32560,7 @@ function build_struct_functions(node, status, skip_init = false) {
32544
32560
  status.headers = status.code;
32545
32561
  status.code = swap;
32546
32562
  }
32547
- if (return_struct || return_trait) status.code += `struct ${return_type}`;
32563
+ if (return_struct || return_trait) status.code += `struct ${mono_return_type}`;
32548
32564
  else status.code += `${c_type(return_type)}`;
32549
32565
  if (return_struct?.is_class || return_trait) status.code += `*`;
32550
32566
  status.code += ` ${emit_label}(`;
@@ -34102,6 +34118,7 @@ function build_return_node(node, status, nir_value) {
34102
34118
  if (!returned_value_decl) returns_borrow_var = true;
34103
34119
  else if (is_string_borrow(returned_value_decl.value) || !!status.string_borrow_vars?.has(var_name)) returns_borrow_var = true;
34104
34120
  }
34121
+ const returns_view_value = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_array && is_view_value(node.value, status) && (node.value.node_type === "value" || node.value.node_type === "access");
34105
34122
  if (returns_borrowed_string) {
34106
34123
  const access = node.value.access;
34107
34124
  if (access.node_type === "access_func") {
@@ -34128,16 +34145,17 @@ function build_return_node(node, status, nir_value) {
34128
34145
  }
34129
34146
  }
34130
34147
  const returns_string_zero = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_array && node.value.node_type === "value" && (node.value.value === "0" || node.value.value === "null");
34131
- if (!returns_string_zero && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `nomen_str_dup(`;
34148
+ if (!returns_string_zero && !returns_view_value && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `nomen_str_dup(`;
34132
34149
  const wrap_view_string_return = !!ret_type.is_view && ret_type.name === "string" && !is_view_value(node.value, status);
34133
34150
  const expr_type_name = node.type?.name || "";
34134
34151
  const expr_is_simple = !status.structs.find((s) => s.name === expr_type_name && !s.is_simple_type);
34135
34152
  if (is_struct && expr_is_simple && !returns_borrowed_string && !ret_type.is_view) status.code += return_is_class ? `(struct ${mono_ret_name}*)` : `(struct ${mono_ret_name})`;
34136
34153
  if (wrap_view_string_return) status.code += `({ nomen_string _p = `;
34137
34154
  if (returns_string_zero) status.code += `(nomen_string){0,0}`;
34155
+ else if (returns_view_value) c_materialize_view_string(node.value, status);
34138
34156
  else emit_return_value(node.value, nir_value, status);
34139
34157
  if (wrap_view_string_return) status.code += `; nomen_view _v = { (void*)_p.ptr, _p.len }; _v; })`;
34140
- if (!returns_string_zero && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `)`;
34158
+ if (!returns_string_zero && !returns_view_value && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `)`;
34141
34159
  status.code += `;\n`;
34142
34160
  if (node.value.node_type === "func_call" && node.value.field_overrides?.length) emit_field_overrides("_return_val", node.value, build_node, status, "", ";\n");
34143
34161
  reclaim_all_c_scopes(status);
@@ -35329,6 +35347,17 @@ function build_access_node(node, status) {
35329
35347
  status.code += `]`;
35330
35348
  return;
35331
35349
  }
35350
+ if (access_func.name === "slice" && target_type.name === "string" && access_func.params.length === 2) {
35351
+ const tmp = `_vsl_${status.label_counter = (status.label_counter ?? 0) + 1}`;
35352
+ status.code += `({ nomen_view ${tmp} = `;
35353
+ build_node(node.target, status);
35354
+ status.code += `; long _s = `;
35355
+ build_node(access_func.params[0], status);
35356
+ status.code += `; nomen_view _r; _r.ptr = (void*)((char*)${tmp}.ptr + _s); _r.len = (long)(`;
35357
+ build_node(access_func.params[1], status);
35358
+ status.code += ` - _s); _r; })`;
35359
+ return;
35360
+ }
35332
35361
  if (access_func.name === "to_string" && target_type.name === "string") {
35333
35362
  const tmp = `_vts_${status.label_counter = (status.label_counter ?? 0) + 1}`;
35334
35363
  status.code += `({ nomen_view ${tmp} = `;
@@ -35867,9 +35896,15 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
35867
35896
  } else {
35868
35897
  if (lhs_is_string && !rhs_is_bare_value) {
35869
35898
  const temp = `_reassign_${status.label_counter = (status.label_counter ?? 0) + 1}`;
35899
+ const rhs_is_view = is_view_value(node.right_value, status);
35870
35900
  status.code += `nomen_string ${temp} = `;
35871
- emit_rhs_value(node.right_value, nir_rhs, status);
35901
+ if (rhs_is_view) c_materialize_view_string(node.right_value, status);
35902
+ else emit_rhs_value(node.right_value, nir_rhs, status);
35872
35903
  status.code += `;\nfree(${lhs_name}.ptr);\n${lhs_name} = ${temp};\n`;
35904
+ if (rhs_is_view) {
35905
+ if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
35906
+ status.heap_strings.add(lhs_name);
35907
+ }
35873
35908
  return;
35874
35909
  }
35875
35910
  status.code += `free(${lhs_name}.ptr);\n`;
@@ -35883,6 +35918,13 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
35883
35918
  if (rhs_is_bare_value) {
35884
35919
  if (lhs_is_class) {
35885
35920
  if (!node.swap) splice_decl_from_c_scopes(status, rhs.value);
35921
+ } else if (lhs_is_string && !node.swap && rhs.node_type === "value" && is_view_value(rhs, status)) {
35922
+ status.code += `${lhs_name} = `;
35923
+ c_materialize_view_string(rhs, status);
35924
+ status.code += `;\n`;
35925
+ if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
35926
+ status.heap_strings.add(lhs_name);
35927
+ return;
35886
35928
  } else if (!node.swap && rhs.is_moved) {
35887
35929
  const src_name = rhs.value;
35888
35930
  if (string_var_owns_heap(status, src_name)) {
@@ -40205,6 +40247,15 @@ function evaluate_operation(op, status) {
40205
40247
  }
40206
40248
  if (op.op === "<=" && left_decl?.upper_bound_inclusive_exprs?.length && target_str) {
40207
40249
  if (left_decl.upper_bound_inclusive_exprs.includes(target_str)) return true;
40250
+ if (left_decl.upper_bound_inclusive_exprs.some((u) => upper_implies(u, target_str))) return true;
40251
+ for (const u of left_decl.upper_bound_inclusive_exprs) {
40252
+ const v = parse_offset_expr(u);
40253
+ if (v && v.offset === 0 && u !== target_str) {
40254
+ const vdecl = status.values.findLast((vv) => vv.name === v.base);
40255
+ if (vdecl?.upper_bound_exprs?.includes(target_str)) return true;
40256
+ if (vdecl?.upper_bound_inclusive_exprs?.includes(target_str)) return true;
40257
+ }
40258
+ }
40208
40259
  }
40209
40260
  if (op.op === "<" && left_decl?.upper_bound_inclusive_exprs?.length && target_str && left_decl.upper_bound_inclusive_exprs.includes(target_str)) return "unsafe";
40210
40261
  if (left_decl?.upper_bound_expr && target_str) {
@@ -40363,7 +40414,7 @@ function evaluate_operation(op, status) {
40363
40414
  const right_base_name = right_str.split(".")[0];
40364
40415
  const rdecl = status.values.findLast((v) => v.name === right_base_name);
40365
40416
  const ra = rdecl?.alias_of ? parse_offset_expr(rdecl.alias_of) : void 0;
40366
- const right_base = ra ? ra.base : right_str;
40417
+ const right_base = ra && !(right_str === ra.base || ra.offset === 0 && right_str.startsWith(ra.base + ".")) ? ra.base : right_str;
40367
40418
  const right_off = ra ? ra.offset : 0;
40368
40419
  if (left_base === right_base) {
40369
40420
  const d = left_off - right_off;
@@ -40514,6 +40565,7 @@ function is_nonnegative_access(node, status) {
40514
40565
  if (m) {
40515
40566
  const field_path = m[1];
40516
40567
  const offset_str = m[2];
40568
+ if (!field_path.includes(".")) return false;
40517
40569
  const last = field_path.split(".").at(-1);
40518
40570
  if (last && NON_NEGATIVE_FIELDS.has(last)) {
40519
40571
  if (!offset_str) return true;
@@ -40721,6 +40773,30 @@ function materialize_type(type, status) {
40721
40773
  //#endregion
40722
40774
  //#region ../src/check/utils/view_fields.ts
40723
40775
  /**
40776
+ * Whether the named struct (or any value struct embedded in its fields,
40777
+ * recursively) declares a `view T` field. A struct with view fields carries
40778
+ * non-owning borrows inside its bytes, which drives the borrow rules:
40779
+ * copies are sound (a pair copy aliases nothing owned), but escapes
40780
+ * (returning, outer-scope assignment) are checked against where those
40781
+ * borrows were taken.
40782
+ */
40783
+ function struct_has_view_fields(type_name, status) {
40784
+ if (!type_name) return false;
40785
+ const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type);
40786
+ return !!struct && has_view_fields(struct, status, /* @__PURE__ */ new Set());
40787
+ }
40788
+ function has_view_fields(struct, status, visited) {
40789
+ if (visited.has(struct.name)) return false;
40790
+ visited.add(struct.name);
40791
+ for (const field of struct.fields) {
40792
+ if (field.type.is_view) return true;
40793
+ if (field.type.is_ref || field.type.is_array) continue;
40794
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
40795
+ if (field_struct && has_view_fields(field_struct, status, visited)) return true;
40796
+ }
40797
+ return false;
40798
+ }
40799
+ /**
40724
40800
  * The root variable an access chain bottoms out at (`line.text` → "line",
40725
40801
  * `a.b.c` → "a", bare `x` → "x"). Returns undefined when the chain does not
40726
40802
  * bottom out in a plain name (e.g. a call receiver).
@@ -40842,6 +40918,23 @@ function view_borrows_root_at_self(sv) {
40842
40918
  return !!owners?.size && [...owners].every((o) => o === "self");
40843
40919
  }
40844
40920
  /**
40921
+ * Whether a value of this type can carry a non-owning view borrow: the type
40922
+ * itself is a `view`, a `ref` (a borrow by definition — keep the existing
40923
+ * taint), a struct declaring (transitively) `view T` fields, or a generic
40924
+ * instantiation with such an argument. Anything else (primitives, owned
40925
+ * strings, containers of owned values) owns its storage outright, so a call
40926
+ * producing it does not propagate its view arguments' borrows — tainting it
40927
+ * is a false positive (e.g. returning `slice_string(view)` from a function
40928
+ * returning owned `string`). Callers pass an unset/unknown type through as
40929
+ * carrying, preserving today's behavior where nothing is known.
40930
+ */
40931
+ function type_can_carry_view_borrow(type, status) {
40932
+ if (!type || !type.name) return true;
40933
+ if (type.is_view || type.is_ref) return true;
40934
+ if (struct_has_view_fields(type.name, status)) return true;
40935
+ return (type.type_args ?? []).some((t) => type_can_carry_view_borrow(t, status));
40936
+ }
40937
+ /**
40845
40938
  * Whether reading view fields of `root` is currently rejected because their
40846
40939
  * source was mutated (reassigned / ref-mutated).
40847
40940
  */
@@ -40966,7 +41059,7 @@ function check_declaration_node(decl, status) {
40966
41059
  if (type_from_value_node(decl.value, status)?.is_const_ref) decl.type.is_const_ref = true;
40967
41060
  }
40968
41061
  let view_borrows;
40969
- if (decl.value?.node_type === "func_call") {
41062
+ if (decl.value?.node_type === "func_call" && type_can_carry_view_borrow(decl.type, status)) {
40970
41063
  view_borrows = ctor_call_view_borrow(decl.value, status);
40971
41064
  if (view_borrows?.size) for (const [, info] of view_borrows) {
40972
41065
  const depth = info.depth ?? status.scope_depth;
@@ -43887,7 +43980,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
43887
43980
  lower_bound_inclusive_exprs = decl.lower_bound_inclusive_exprs;
43888
43981
  upper_bound_expr = decl.upper_bound_expr;
43889
43982
  lower_bound_expr = decl.lower_bound_expr;
43890
- alias_of = decl.alias_of;
43983
+ alias_of = decl.alias_of ?? expr_to_string(param, status);
43891
43984
  }
43892
43985
  } else if (param.node_type === "op") {
43893
43986
  const pop = param;
@@ -43981,12 +44074,24 @@ function check_function_call(node, status, func, target_type, self_value, self_p
43981
44074
  });
43982
44075
  if (self_value && self_value !== "?") {
43983
44076
  const self_type = target_type || func.params[0]?.type;
44077
+ const self_path_or_value = self_path ?? self_value;
44078
+ const self_base = status.values.findLast((v) => v.name === self_path_or_value.split(".")[0]);
43984
44079
  status.values.push({
43985
44080
  declaration: "const",
43986
44081
  name: "self",
43987
44082
  type: self_type,
43988
44083
  is_set: true,
43989
- alias_of: self_path ?? self_value
44084
+ alias_of: self_path_or_value,
44085
+ range_lower: self_base?.range_lower,
44086
+ range_upper: self_base?.range_upper,
44087
+ upper_bound_exprs: self_base?.upper_bound_exprs?.slice(),
44088
+ lower_bound_exprs: self_base?.lower_bound_exprs?.slice(),
44089
+ upper_bound_inclusive_exprs: self_base?.upper_bound_inclusive_exprs?.slice(),
44090
+ lower_bound_inclusive_exprs: self_base?.lower_bound_inclusive_exprs?.slice(),
44091
+ upper_bound_expr: self_base?.upper_bound_expr,
44092
+ lower_bound_expr: self_base?.lower_bound_expr,
44093
+ known_length: self_base?.known_length,
44094
+ path_bounds: self_base?.path_bounds
43990
44095
  });
43991
44096
  }
43992
44097
  const satisfied = evaluate_const_condition(func_param.constraint, status);
@@ -45160,7 +45265,7 @@ function check_assignment_node(assign, status) {
45160
45265
  const src_name = value_from_value_node(assign.right_value);
45161
45266
  const src_sv = assign.right_value.node_type === "value" ? status.values.findLast((v) => v.name === src_name) : void 0;
45162
45267
  if (src_sv?.has_view_borrows) propagate_view_borrows(left_value, src_sv);
45163
- } else if (assign.right_value.node_type === "func_call" && type_from_value_node(assign.right_value, status)?.name) {
45268
+ } else if (assign.right_value.node_type === "func_call" && type_from_value_node(assign.right_value, status)?.name && type_can_carry_view_borrow(type_from_value_node(assign.right_value, status), status)) {
45164
45269
  const infos = ctor_call_view_borrow(assign.right_value, status);
45165
45270
  if (infos?.size) {
45166
45271
  for (const [, info] of infos) {
@@ -46002,7 +46107,7 @@ function check_return_node(ret, status) {
46002
46107
  if (!safe_view_from_self && !explicit_mov) add_error(status, `cannot return a borrowed reference — use 'move' (with swap) to transfer ownership`, ret.value.start);
46003
46108
  }
46004
46109
  } else if (func && ret.value.node_type === "func_call") {
46005
- const infos = ctor_call_view_borrow(ret.value, status);
46110
+ const infos = type_can_carry_view_borrow(ret.type, status) ? ctor_call_view_borrow(ret.value, status) : void 0;
46006
46111
  if (infos?.size && ![...infos.keys()].every((o) => o === "self")) add_error(status, `cannot return '${ret.type.name}' — its 'view' field(s) borrow from this scope`, ret.value.start);
46007
46112
  }
46008
46113
  if (func) {
@@ -49276,6 +49381,14 @@ function project_root_for(input_path) {
49276
49381
  function build_dir_for(input_path, is_test) {
49277
49382
  return is_test ? path.join(project_root_for(input_path), "build", "test") : path.join(project_root_for(input_path), "build");
49278
49383
  }
49384
+ /**
49385
+ * The linked binary path for `input_path`: an explicit `--out` wins,
49386
+ * otherwise `<build dir>/<entry basename>`.
49387
+ */
49388
+ function outfile_for(input_path, is_test, out) {
49389
+ if (out) return path.resolve(out);
49390
+ return path.join(build_dir_for(input_path, is_test), path.basename(input_path, ".nm"));
49391
+ }
49279
49392
  //#endregion
49280
49393
  //#region src/test.ts
49281
49394
  const RECORD_PREFIX = "\\nomen|";
@@ -49746,6 +49859,7 @@ try {
49746
49859
  if (args.audit_runtime) config.audit_runtime = args.audit_runtime;
49747
49860
  if (args.release) config.release = args.release;
49748
49861
  if (args.fast_math) config.fast_math = args.fast_math;
49862
+ if (args.out) config.out = args.out;
49749
49863
  if (fs.lstatSync(args.in).isDirectory()) {
49750
49864
  if (args.watch) watchPath(args.in, config, mode, args.program_args);
49751
49865
  else processFolder(args.in, config, mode, args.program_args);
@@ -49872,7 +49986,8 @@ function processFile(filename, config, mode, program_args) {
49872
49986
  const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
49873
49987
  const headerfile = path.join(buildDir, "main.h");
49874
49988
  const codefile = path.join(buildDir, basename + ext);
49875
- const outfile = path.join(buildDir, basename);
49989
+ const outfile = outfile_for(resolved_path, filename.endsWith(".test.nm"), config.out);
49990
+ if (!fs.existsSync(path.dirname(outfile))) fs.mkdirSync(path.dirname(outfile), { recursive: true });
49876
49991
  fs.writeFileSync(headerfile, result.headers);
49877
49992
  fs.writeFileSync(codefile, result.code);
49878
49993
  let companionfile;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nomen-lang",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "The CLI for the Nomen programming language.",
5
5
  "keywords": [],
6
6
  "license": "ISC",
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@ import { parse_args, print_help, type Args } from "./args.ts";
14
14
  import { run_docs } from "./docs.ts";
15
15
  import render_errors, { render_warnings } from "./format_errors.ts";
16
16
  import { find_bundled, run_init } from "./init.ts";
17
- import { build_dir_for } from "./paths.ts";
17
+ import { build_dir_for, outfile_for } from "./paths.ts";
18
18
  import { runTests } from "./test.ts";
19
19
  import type Config from "./types/Config.ts";
20
20
 
@@ -170,6 +170,7 @@ try {
170
170
  if (args.audit_runtime) config.audit_runtime = args.audit_runtime;
171
171
  if (args.release) config.release = args.release;
172
172
  if (args.fast_math) config.fast_math = args.fast_math;
173
+ if (args.out) config.out = args.out;
173
174
 
174
175
  // Is the --in path a folder
175
176
  if (fs.lstatSync(args.in).isDirectory()) {
@@ -370,7 +371,8 @@ function processFile(filename: string, config: Config, mode: Mode, program_args:
370
371
  }
371
372
 
372
373
  const basename = path.basename(filename, ".nm");
373
- // Output lives in the project's `build/` — `build/test` for *.test.nm.
374
+ // Output lives in the project's `build/` — `build/test` for *.test.nm
375
+ // unless an explicit --out names the linked binary.
374
376
  const buildDir = build_dir_for(resolved_path, filename.endsWith(".test.nm"));
375
377
  if (!fs.existsSync(buildDir)) {
376
378
  fs.mkdirSync(buildDir, { recursive: true });
@@ -378,7 +380,10 @@ function processFile(filename: string, config: Config, mode: Mode, program_args:
378
380
  const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
379
381
  const headerfile = path.join(buildDir, "main.h");
380
382
  const codefile = path.join(buildDir, basename + ext);
381
- const outfile = path.join(buildDir, basename);
383
+ const outfile = outfile_for(resolved_path, filename.endsWith(".test.nm"), config.out);
384
+ if (!fs.existsSync(path.dirname(outfile))) {
385
+ fs.mkdirSync(path.dirname(outfile), { recursive: true });
386
+ }
382
387
  fs.writeFileSync(headerfile, result.headers);
383
388
  fs.writeFileSync(codefile, result.code);
384
389
 
package/src/paths.ts CHANGED
@@ -26,3 +26,12 @@ export function build_dir_for(input_path: string, is_test: boolean): string {
26
26
  ? path.join(project_root_for(input_path), "build", "test")
27
27
  : path.join(project_root_for(input_path), "build");
28
28
  }
29
+
30
+ /**
31
+ * The linked binary path for `input_path`: an explicit `--out` wins,
32
+ * otherwise `<build dir>/<entry basename>`.
33
+ */
34
+ export function outfile_for(input_path: string, is_test: boolean, out?: string): string {
35
+ if (out) return path.resolve(out);
36
+ return path.join(build_dir_for(input_path, is_test), path.basename(input_path, ".nm"));
37
+ }
@@ -6,4 +6,5 @@ export default interface Config {
6
6
  audit_runtime?: string;
7
7
  release?: boolean;
8
8
  fast_math?: boolean;
9
+ out?: string;
9
10
  }