nomen-lang 0.0.9 → 0.0.11
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 +258 -46
- package/package.json +1 -1
- package/src/index.ts +3 -13
- package/src/init.ts +1 -1
- package/src/paths.ts +28 -0
- package/src/test.ts +5 -4
package/dist/index.mjs
CHANGED
|
@@ -3742,6 +3742,9 @@ var ForLoopNode = class extends BaseNode {
|
|
|
3742
3742
|
index;
|
|
3743
3743
|
update;
|
|
3744
3744
|
statements;
|
|
3745
|
+
/** `for ref x of arr` — the user wants mutable element access. For arrays
|
|
3746
|
+
* this desugars to copy-out / mutate / copy-back via .at()/.set(). */
|
|
3747
|
+
item_is_ref;
|
|
3745
3748
|
constructor(start, item, list, statements, update) {
|
|
3746
3749
|
super("for", start);
|
|
3747
3750
|
this.item = item;
|
|
@@ -5094,6 +5097,7 @@ function build_break_node$1(status) {
|
|
|
5094
5097
|
const loop = status.loop_labels?.[status.loop_labels.length - 1];
|
|
5095
5098
|
if (loop) {
|
|
5096
5099
|
emit_cleanup_to_loop_depth(status);
|
|
5100
|
+
status.loop_writebacks?.[status.loop_writebacks.length - 1]?.();
|
|
5097
5101
|
status.code += `b ${loop.end}\n`;
|
|
5098
5102
|
}
|
|
5099
5103
|
}
|
|
@@ -5172,6 +5176,7 @@ function build_continue_node$1(status) {
|
|
|
5172
5176
|
const loop = status.loop_labels?.[status.loop_labels.length - 1];
|
|
5173
5177
|
if (loop) {
|
|
5174
5178
|
emit_cleanup_to_loop_depth(status);
|
|
5179
|
+
status.loop_writebacks?.[status.loop_writebacks.length - 1]?.();
|
|
5175
5180
|
status.code += `b ${loop.start}\n`;
|
|
5176
5181
|
}
|
|
5177
5182
|
}
|
|
@@ -6604,6 +6609,8 @@ function build_for_loop_node$1(node, status) {
|
|
|
6604
6609
|
end: end_label,
|
|
6605
6610
|
cleanup_depth
|
|
6606
6611
|
});
|
|
6612
|
+
if (!status.loop_writebacks) status.loop_writebacks = [];
|
|
6613
|
+
status.loop_writebacks.push(void 0);
|
|
6607
6614
|
if (status.function_return_label) {
|
|
6608
6615
|
const item_offset = allocate_stack_space(status, 8);
|
|
6609
6616
|
status.stack_offsets.set(item_name, item_offset);
|
|
@@ -6817,7 +6824,49 @@ function build_for_loop_node$1(node, status) {
|
|
|
6817
6824
|
else status.code += `ldr x0, [x0]\n`;
|
|
6818
6825
|
emit_var_store(status, "x0", item_name, element_size);
|
|
6819
6826
|
}
|
|
6827
|
+
if (node.item_is_ref) {
|
|
6828
|
+
const wb_struct_type = struct_type;
|
|
6829
|
+
const wb_element_size = element_size;
|
|
6830
|
+
const wb_item_name = item_name;
|
|
6831
|
+
const wb_idx_name = idx_name;
|
|
6832
|
+
const wb_list_name = list_name;
|
|
6833
|
+
const wb_list_is_pointer = list_is_pointer;
|
|
6834
|
+
const wb_shift = shift;
|
|
6835
|
+
status.loop_writebacks[status.loop_writebacks.length - 1] = () => {
|
|
6836
|
+
if (wb_list_is_pointer) {
|
|
6837
|
+
emit_var_load(status, "x9", wb_list_name, 8);
|
|
6838
|
+
if (status.heap_array_vars?.has(wb_list_name)) status.code += `add x9, x9, #8\n`;
|
|
6839
|
+
} else emit_var_address(status, "x9", wb_list_name);
|
|
6840
|
+
emit_var_load(status, "x10", wb_idx_name, 8);
|
|
6841
|
+
if (Number.isInteger(wb_shift) && wb_shift > 0) status.code += `add x9, x9, x10, lsl #${wb_shift}\n`;
|
|
6842
|
+
else {
|
|
6843
|
+
status.code += `mov x11, #${wb_element_size}\n`;
|
|
6844
|
+
status.code += `mul x10, x10, x11\n`;
|
|
6845
|
+
status.code += `add x9, x9, x10\n`;
|
|
6846
|
+
}
|
|
6847
|
+
if (wb_struct_type) {
|
|
6848
|
+
const item_offset = status.stack_offsets.get(wb_item_name);
|
|
6849
|
+
if (item_offset !== void 0) {
|
|
6850
|
+
const words = Math.ceil(wb_element_size / 8);
|
|
6851
|
+
for (let w = 0; w < words; w++) {
|
|
6852
|
+
status.code += `ldr x10, [x29, #${item_offset + w * 8}]\n`;
|
|
6853
|
+
status.code += `str x10, [x9, #${w * 8}]\n`;
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
} else if (wb_element_size === 1) {
|
|
6857
|
+
emit_var_load(status, "x10", wb_item_name, wb_element_size);
|
|
6858
|
+
status.code += `strb w10, [x9]\n`;
|
|
6859
|
+
} else if (wb_element_size === 4) {
|
|
6860
|
+
emit_var_load(status, "x10", wb_item_name, wb_element_size);
|
|
6861
|
+
status.code += `str w10, [x9]\n`;
|
|
6862
|
+
} else {
|
|
6863
|
+
emit_var_load(status, "x10", wb_item_name, wb_element_size);
|
|
6864
|
+
status.code += `str x10, [x9]\n`;
|
|
6865
|
+
}
|
|
6866
|
+
};
|
|
6867
|
+
}
|
|
6820
6868
|
build_block_node$1(node, status);
|
|
6869
|
+
status.loop_writebacks[status.loop_writebacks.length - 1]?.();
|
|
6821
6870
|
if (node.update) {
|
|
6822
6871
|
status.code += `${continue_label}:\n`;
|
|
6823
6872
|
build_node$1(node.update, status);
|
|
@@ -6835,6 +6884,7 @@ function build_for_loop_node$1(node, status) {
|
|
|
6835
6884
|
else status.register_allocations = void 0;
|
|
6836
6885
|
status.buffer_data_cache = saved_buffer_cache;
|
|
6837
6886
|
status.loop_labels.pop();
|
|
6887
|
+
status.loop_writebacks?.pop();
|
|
6838
6888
|
status.scoped_declarations = old_scoped_declarations;
|
|
6839
6889
|
}
|
|
6840
6890
|
function is_enumerable_type$1(node, status) {
|
|
@@ -14064,6 +14114,7 @@ function build_async_block_node(node, status) {
|
|
|
14064
14114
|
//#region ../src/build_c/build_break_node.ts
|
|
14065
14115
|
function build_break_node(node, status) {
|
|
14066
14116
|
reclaim_to_loop_body(status);
|
|
14117
|
+
status.loop_writebacks?.[status.loop_writebacks.length - 1]?.();
|
|
14067
14118
|
status.code += `break;\n`;
|
|
14068
14119
|
}
|
|
14069
14120
|
//#endregion
|
|
@@ -14088,6 +14139,7 @@ function build_cast_node(node, status) {
|
|
|
14088
14139
|
//#region ../src/build_c/build_continue_node.ts
|
|
14089
14140
|
function build_continue_node(node, status) {
|
|
14090
14141
|
reclaim_to_loop_body(status);
|
|
14142
|
+
status.loop_writebacks?.[status.loop_writebacks.length - 1]?.();
|
|
14091
14143
|
status.code += `continue;\n`;
|
|
14092
14144
|
}
|
|
14093
14145
|
//#endregion
|
|
@@ -14373,6 +14425,7 @@ function build_for_loop_node(node, status) {
|
|
|
14373
14425
|
const old_deferred_frees = status.deferred_frees;
|
|
14374
14426
|
status.deferred_frees = [];
|
|
14375
14427
|
push_c_loop_frame(status);
|
|
14428
|
+
let ref_writeback;
|
|
14376
14429
|
if (node.item && node.list) if (node.list.node_type == "range") {
|
|
14377
14430
|
status.code += "{\n";
|
|
14378
14431
|
status.code += `${c_type("int")} `;
|
|
@@ -14439,14 +14492,27 @@ function build_for_loop_node(node, status) {
|
|
|
14439
14492
|
build_node(node.list, status);
|
|
14440
14493
|
status.code += `[${idx_var}];\n`;
|
|
14441
14494
|
}
|
|
14495
|
+
if (node.item_is_ref) {
|
|
14496
|
+
let wb_target;
|
|
14497
|
+
if (is_heap) wb_target = `((${elem_is_class ? `struct ${element_type} **` : `${elem_struct ? `struct ${element_type}` : c_type(element_type)} *`})((char *)${list_name} + sizeof(struct Array_${element_type})))[${idx_var}]`;
|
|
14498
|
+
else wb_target = `${list_name}[${idx_var}]`;
|
|
14499
|
+
const wb_code = `${wb_target} = ${node.item.value};\n`;
|
|
14500
|
+
ref_writeback = () => {
|
|
14501
|
+
status.code += wb_code;
|
|
14502
|
+
};
|
|
14503
|
+
}
|
|
14442
14504
|
}
|
|
14505
|
+
if (!status.loop_writebacks) status.loop_writebacks = [];
|
|
14506
|
+
status.loop_writebacks.push(ref_writeback);
|
|
14443
14507
|
build_block_node(node, status);
|
|
14444
14508
|
if (node.update) {
|
|
14445
14509
|
build_node(node.update, status);
|
|
14446
14510
|
status.code += ";\n";
|
|
14447
14511
|
}
|
|
14512
|
+
if (ref_writeback) ref_writeback();
|
|
14448
14513
|
build_auto_free(status);
|
|
14449
14514
|
status.code += `}\n`;
|
|
14515
|
+
status.loop_writebacks.pop();
|
|
14450
14516
|
if (node.list && (node.list.node_type == "range" || is_enumerable_type(node.list, status))) status.code += `}\n`;
|
|
14451
14517
|
pop_c_loop_frame(status);
|
|
14452
14518
|
leave_c_scope(status);
|
|
@@ -15410,8 +15476,7 @@ const CALL_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
15410
15476
|
"trait",
|
|
15411
15477
|
"enum",
|
|
15412
15478
|
"bitset",
|
|
15413
|
-
"cast"
|
|
15414
|
-
"view"
|
|
15479
|
+
"cast"
|
|
15415
15480
|
]);
|
|
15416
15481
|
const NO_SPACE_BEFORE = /* @__PURE__ */ new Set([
|
|
15417
15482
|
",",
|
|
@@ -16021,9 +16086,10 @@ const PAIRS = {
|
|
|
16021
16086
|
* formatted source doesn't tokenize to exactly the same stream, the original is
|
|
16022
16087
|
* returned untouched. Nomen's tokenizer folds a sign into the number that
|
|
16023
16088
|
* follows it (`a -1` is not `a - 1`), so a spacing slip really can change the
|
|
16024
|
-
* meaning of a program — never let one through.
|
|
16089
|
+
* meaning of a program — never let one through. Pass `force` to apply the
|
|
16090
|
+
* layout anyway (e.g. for an explicit "force format" command).
|
|
16025
16091
|
*/
|
|
16026
|
-
function format_source(source, options) {
|
|
16092
|
+
function format_source(source, options, force = false) {
|
|
16027
16093
|
const config = {
|
|
16028
16094
|
...default_format_options,
|
|
16029
16095
|
...options
|
|
@@ -16034,7 +16100,7 @@ function format_source(source, options) {
|
|
|
16034
16100
|
strip_redundant_types: false
|
|
16035
16101
|
});
|
|
16036
16102
|
const difference = token_difference(source, layout);
|
|
16037
|
-
if (difference) return {
|
|
16103
|
+
if (difference && !force) return {
|
|
16038
16104
|
code: source,
|
|
16039
16105
|
changed: false,
|
|
16040
16106
|
unsafe: difference
|
|
@@ -16308,14 +16374,22 @@ function describe(token) {
|
|
|
16308
16374
|
}
|
|
16309
16375
|
//#endregion
|
|
16310
16376
|
//#region ../src/join.ts
|
|
16311
|
-
const
|
|
16312
|
-
|
|
16377
|
+
const DEFAULT_LIB_REL = "../../core/src";
|
|
16378
|
+
let default_lib_path;
|
|
16379
|
+
function fallback_lib_path() {
|
|
16380
|
+
if (default_lib_path === void 0) try {
|
|
16381
|
+
default_lib_path = path.resolve(path.dirname(fileURLToPath(import.meta.url)), DEFAULT_LIB_REL);
|
|
16382
|
+
} catch {
|
|
16383
|
+
default_lib_path = "";
|
|
16384
|
+
}
|
|
16385
|
+
return default_lib_path;
|
|
16386
|
+
}
|
|
16313
16387
|
let test_src_dir;
|
|
16314
16388
|
function join$1(entry_file_path, lib_path, options) {
|
|
16315
16389
|
const folder_path = path.dirname(entry_file_path);
|
|
16316
16390
|
const file_path = path.basename(entry_file_path);
|
|
16317
16391
|
const inputs = /* @__PURE__ */ new Map();
|
|
16318
|
-
const resolved_lib_path = lib_path ? path.resolve(lib_path, "src") :
|
|
16392
|
+
const resolved_lib_path = lib_path ? path.resolve(lib_path, "src") : fallback_lib_path();
|
|
16319
16393
|
const for_test = options?.for_test ?? file_path.endsWith(".test.nm");
|
|
16320
16394
|
const prev_test_src_dir = test_src_dir;
|
|
16321
16395
|
test_src_dir = for_test ? resolve_src_module(folder_path) : void 0;
|
|
@@ -16759,14 +16833,20 @@ function struct_owns_heap(s, status, visited) {
|
|
|
16759
16833
|
//#endregion
|
|
16760
16834
|
//#region ../src/check/utils/type_name.ts
|
|
16761
16835
|
function type_name(type) {
|
|
16762
|
-
|
|
16836
|
+
const prefix = type_modifier(type);
|
|
16837
|
+
if (type.tuple_types?.length && (type.name === "tuple" || type.name.startsWith("_Tuple_"))) return `${prefix}[${type.tuple_types.map((t) => type_name(t)).join(", ")}]${type.is_nullable ? "?" : ""}`;
|
|
16763
16838
|
if (type.is_array) return `Array<${type_name_without_array(type)}>${type.is_nullable ? "?" : ""}`;
|
|
16764
16839
|
const args = type.type_args?.length ? `<${type.type_args.map((t) => type_name(t)).join(", ")}>` : "";
|
|
16765
|
-
return `${
|
|
16840
|
+
return `${prefix}${type.name}${args}${type.is_nullable ? "?" : ""}`;
|
|
16841
|
+
}
|
|
16842
|
+
function type_modifier(type) {
|
|
16843
|
+
if (type.is_view) return "view ";
|
|
16844
|
+
if (type.is_ref) return "ref ";
|
|
16845
|
+
return "";
|
|
16766
16846
|
}
|
|
16767
16847
|
function type_name_without_array(type) {
|
|
16768
16848
|
const args = type.type_args?.length ? `<${type.type_args.map((t) => type_name(t)).join(", ")}>` : "";
|
|
16769
|
-
return `${type
|
|
16849
|
+
return `${type_modifier(type)}${type.name}${args}`;
|
|
16770
16850
|
}
|
|
16771
16851
|
//#endregion
|
|
16772
16852
|
//#region ../src/check/utils/check_type_and_value_match.ts
|
|
@@ -17685,6 +17765,7 @@ function evaluate_operation(op, status) {
|
|
|
17685
17765
|
if (op.op === "<" || op.op === "<=" || op.op === ">" || op.op === ">=") {
|
|
17686
17766
|
const num = evaluate_numeric_comparison(op, status);
|
|
17687
17767
|
if (num !== void 0) return num;
|
|
17768
|
+
if (op.op === "<=" && is_zero(op.left_value, status) && is_nonnegative_access(op.right_value) || op.op === ">=" && is_zero(op.right_value, status) && is_nonnegative_access(op.left_value)) return true;
|
|
17688
17769
|
let left_var;
|
|
17689
17770
|
let left_offset = 0;
|
|
17690
17771
|
if (op.left_value.node_type === "value") left_var = op.left_value.value;
|
|
@@ -17981,6 +18062,25 @@ function evaluate_numeric_or_bool(node, status) {
|
|
|
17981
18062
|
return evaluate_const_condition(op_node, status);
|
|
17982
18063
|
}
|
|
17983
18064
|
}
|
|
18065
|
+
const NON_NEGATIVE_FIELDS = /* @__PURE__ */ new Set([
|
|
18066
|
+
"length",
|
|
18067
|
+
"cap",
|
|
18068
|
+
"count",
|
|
18069
|
+
"size"
|
|
18070
|
+
]);
|
|
18071
|
+
/** Does `node` read a container's length/capacity, which is always >= 0? */
|
|
18072
|
+
function is_nonnegative_access(node) {
|
|
18073
|
+
if (node.node_type !== "access") return false;
|
|
18074
|
+
const access = node;
|
|
18075
|
+
if (access.access.node_type === "access_field") return NON_NEGATIVE_FIELDS.has(access.access.name);
|
|
18076
|
+
if (access.access.node_type === "access_func") return NON_NEGATIVE_FIELDS.has(access.access.name);
|
|
18077
|
+
return false;
|
|
18078
|
+
}
|
|
18079
|
+
/** Does `node` evaluate to the compile-time integer 0 (literal or const)? */
|
|
18080
|
+
function is_zero(node, status) {
|
|
18081
|
+
const value = evaluate_numeric_or_bool(node, status);
|
|
18082
|
+
return typeof value === "number" && value === 0;
|
|
18083
|
+
}
|
|
17984
18084
|
//#endregion
|
|
17985
18085
|
//#region ../src/check/utils/is_visible.ts
|
|
17986
18086
|
/**
|
|
@@ -18352,10 +18452,12 @@ function check_function_call(node, status, func, target_type, self_value, self_p
|
|
|
18352
18452
|
}
|
|
18353
18453
|
const satisfied = evaluate_const_condition(func_param.constraint, status);
|
|
18354
18454
|
status.values.length = saved_values_length;
|
|
18355
|
-
|
|
18455
|
+
const constraint_source = expression_to_source(func_param.constraint);
|
|
18456
|
+
const suffix = constraint_source ? `\n ${constraint_source}` : "";
|
|
18457
|
+
if (satisfied === false) add_error(status, `Parameter constraint not satisfied: ${func_param.name}${suffix}`, param.start);
|
|
18356
18458
|
else if (satisfied === void 0) {
|
|
18357
|
-
if (!is_inside_core_method(status)) add_error(status, `Parameter constraint cannot be verified: ${func_param.name}`, param.start);
|
|
18358
|
-
} else if (satisfied === "unsafe") add_error(status, `Parameter constraint not satisfied: ${func_param.name}`, param.start);
|
|
18459
|
+
if (!is_inside_core_method(status)) add_error(status, `Parameter constraint cannot be verified: ${func_param.name}${suffix}`, param.start);
|
|
18460
|
+
} else if (satisfied === "unsafe") add_error(status, `Parameter constraint not satisfied: ${func_param.name}${suffix}`, param.start);
|
|
18359
18461
|
}
|
|
18360
18462
|
if (param.node_type !== "value" && !has_ref_keyword && !node.swap_params?.has(i) && !(i === node.variadic_param_index && param.node_type === "array")) {
|
|
18361
18463
|
const declaration_name = `_param_${status.var_name_counter.value++}`;
|
|
@@ -18444,6 +18546,29 @@ function find_lhs_var_name(status) {
|
|
|
18444
18546
|
if (node.node_type === "func") return void 0;
|
|
18445
18547
|
}
|
|
18446
18548
|
}
|
|
18549
|
+
/** Render a constraint expression AST back to a source-like string. */
|
|
18550
|
+
function expression_to_source(node) {
|
|
18551
|
+
if (!node) return "";
|
|
18552
|
+
if (node.node_type === "value") return node.value;
|
|
18553
|
+
if (node.node_type === "access") {
|
|
18554
|
+
const access = node;
|
|
18555
|
+
const target = expression_to_source(access.target);
|
|
18556
|
+
if (access.access.node_type === "access_field") {
|
|
18557
|
+
const name = access.access.name;
|
|
18558
|
+
return target ? `${target}.${name}` : name;
|
|
18559
|
+
}
|
|
18560
|
+
if (access.access.node_type === "access_func") {
|
|
18561
|
+
const name = access.access.name;
|
|
18562
|
+
return target ? `${target}.${name}()` : `${name}()`;
|
|
18563
|
+
}
|
|
18564
|
+
return target;
|
|
18565
|
+
}
|
|
18566
|
+
if (node.node_type === "op") {
|
|
18567
|
+
const op = node;
|
|
18568
|
+
return `${expression_to_source(op.left_value)} ${op.op} ${expression_to_source(op.right_value)}`;
|
|
18569
|
+
}
|
|
18570
|
+
return "";
|
|
18571
|
+
}
|
|
18447
18572
|
//#endregion
|
|
18448
18573
|
//#region ../src/nodes/clone_node.ts
|
|
18449
18574
|
function clone_type$2(type) {
|
|
@@ -19024,7 +19149,12 @@ function materialize_tuple_type(type, status) {
|
|
|
19024
19149
|
//#endregion
|
|
19025
19150
|
//#region ../src/check/check_declaration_node.ts
|
|
19026
19151
|
function check_declaration_node(decl, status) {
|
|
19152
|
+
const is_view_keyword = decl.declaration === "view";
|
|
19153
|
+
const declaration = decl.declaration === "view" ? "const" : decl.declaration;
|
|
19154
|
+
decl.declaration = declaration;
|
|
19155
|
+
const declared_type_is_view = !!decl.type.is_view;
|
|
19027
19156
|
if (decl.func_params) {
|
|
19157
|
+
if (is_view_keyword) add_error(status, `'view' cannot declare a function-typed binding`, decl.start);
|
|
19028
19158
|
if (decl.func_return_type) {
|
|
19029
19159
|
check_type_exists(decl.func_return_type, status, -1);
|
|
19030
19160
|
if (decl.func_return_type.name === "tuple" && decl.func_return_type.tuple_types?.length) decl.func_return_type = materialize_tuple_type(decl.func_return_type, status);
|
|
@@ -19062,13 +19192,13 @@ function check_declaration_node(decl, status) {
|
|
|
19062
19192
|
status.stack.pop();
|
|
19063
19193
|
}
|
|
19064
19194
|
status.values.push({
|
|
19065
|
-
declaration
|
|
19195
|
+
declaration,
|
|
19066
19196
|
name: decl.name,
|
|
19067
19197
|
type: decl.func_return_type || decl.type,
|
|
19068
19198
|
is_set: !!decl.value,
|
|
19069
19199
|
start: decl.start,
|
|
19070
19200
|
is_null: decl.value?.node_type === "value" && decl.value.value === "null",
|
|
19071
|
-
const_value:
|
|
19201
|
+
const_value: declaration === "const" ? extract_const_value(decl.value) : void 0,
|
|
19072
19202
|
constraint: decl.constraint,
|
|
19073
19203
|
func_params: decl.func_params?.map((p) => ({
|
|
19074
19204
|
name: p.name,
|
|
@@ -19095,6 +19225,8 @@ function check_declaration_node(decl, status) {
|
|
|
19095
19225
|
}
|
|
19096
19226
|
status.stack.pop();
|
|
19097
19227
|
}
|
|
19228
|
+
if (is_view_keyword && decl.value && !decl.type.is_view) add_error(status, `'view' binding requires a view value (e.g. a .slice() result), got ${decl.type.name}`, decl.value.start);
|
|
19229
|
+
if (!is_view_keyword && !declared_type_is_view && decl.type.is_view && decl.value) add_error(status, `binding a view requires the 'view' keyword (e.g. 'view name = ...' or 'var view name = ...')`, decl.value.start);
|
|
19098
19230
|
if (decl.type.name === "string" && decl.value && decl.value.node_type === "value" && decl.value.value.startsWith("\"") && decl.value.value.endsWith("\"")) {
|
|
19099
19231
|
const len = decl.value.value.length - 2;
|
|
19100
19232
|
decl.type.length = new ValueNode(decl.value.start, len.toString(), new Type("int"));
|
|
@@ -19119,13 +19251,13 @@ function check_declaration_node(decl, status) {
|
|
|
19119
19251
|
check_constraint(decl, status);
|
|
19120
19252
|
const class_alias_src = decl.value?.node_type === "value" && !decl.value.is_moved && decl.value.value !== "null" && decl.type.name && is_class_type$1(decl.type.name, status) ? decl.value.value : void 0;
|
|
19121
19253
|
status.values.push({
|
|
19122
|
-
declaration
|
|
19254
|
+
declaration,
|
|
19123
19255
|
name: decl.name,
|
|
19124
19256
|
type: decl.type,
|
|
19125
19257
|
is_set: !!decl.value,
|
|
19126
19258
|
start: decl.start,
|
|
19127
19259
|
is_null: decl.value?.node_type === "value" && decl.value.value === "null",
|
|
19128
|
-
const_value:
|
|
19260
|
+
const_value: declaration === "const" ? extract_const_value(decl.value) : void 0,
|
|
19129
19261
|
constraint: decl.constraint,
|
|
19130
19262
|
decl_depth: status.scope_depth,
|
|
19131
19263
|
borrow_depth: decl.value ? borrow_depth_of(decl.value, status) : void 0,
|
|
@@ -19249,8 +19381,8 @@ function check_struct_node(struct, status) {
|
|
|
19249
19381
|
const values_length_before_fields = status.values.length;
|
|
19250
19382
|
for (let decl of struct.fields) {
|
|
19251
19383
|
decl.scope = struct;
|
|
19252
|
-
if (decl.type.is_ref || decl.type.is_view) {
|
|
19253
|
-
add_error(status, `${struct.is_class ? "class" : "struct"} fields cannot be '${decl.type.is_view ? "view" : "ref"}'`, decl.start);
|
|
19384
|
+
if (decl.type.is_ref || decl.type.is_view || decl.declaration === "view") {
|
|
19385
|
+
add_error(status, `${struct.is_class ? "class" : "struct"} fields cannot be '${decl.type.is_view || decl.declaration === "view" ? "view" : "ref"}'`, decl.start);
|
|
19254
19386
|
continue;
|
|
19255
19387
|
}
|
|
19256
19388
|
if (!struct.is_class && is_class_type$1(decl.type.name, status)) add_error(status, `struct fields cannot be class types, use a class instead`, decl.start);
|
|
@@ -21715,6 +21847,7 @@ function check_for_loop_node(for_loop, status) {
|
|
|
21715
21847
|
const has_at = list_type.name !== void 0 && !!status.structs.find((s) => s.name === "Array_" + list_type.name && s.functions.some((f) => f.name === "at"));
|
|
21716
21848
|
if (list_type.is_array && !enumerable && has_at) {
|
|
21717
21849
|
desugar_array_for_loop(for_loop, list_type);
|
|
21850
|
+
for_loop.item_is_ref = false;
|
|
21718
21851
|
check_for_loop_node(for_loop, status);
|
|
21719
21852
|
return;
|
|
21720
21853
|
}
|
|
@@ -21725,7 +21858,13 @@ function check_for_loop_node(for_loop, status) {
|
|
|
21725
21858
|
const list_type = type_from_value_node(for_loop.list, for_status);
|
|
21726
21859
|
const is_enumerable = list_type.name ? has_trait(list_type.name, "Enumerable", for_status) : false;
|
|
21727
21860
|
if (!list_type.is_array && !is_enumerable && list_type.name) add_error(for_status, `For loop list must be an array or Enumerable, not ${list_type.name}`, for_loop.list.start);
|
|
21861
|
+
if (for_loop.item_is_ref && list_type.is_array && for_loop.list.node_type === "value") {
|
|
21862
|
+
const list_name = for_loop.list.value;
|
|
21863
|
+
const list_value = for_status.values.find((v) => v.name === list_name);
|
|
21864
|
+
if (list_value && list_value.declaration !== "var") add_error(for_status, `'ref' iteration requires a 'var' array, but '${list_name}' is const`, for_loop.list.start);
|
|
21865
|
+
}
|
|
21728
21866
|
if (for_loop.item) {
|
|
21867
|
+
if (for_loop.item_is_ref && (for_loop.list instanceof RangeNode || is_enumerable)) add_error(for_status, `'ref' is only valid for array element iteration (for ref x of arr), not ranges or Enumerable types`, for_loop.item.start);
|
|
21729
21868
|
if (is_enumerable) for_loop.item.type = new Type("int", true);
|
|
21730
21869
|
else for_loop.item.type = new Type(list_type.name);
|
|
21731
21870
|
let range_lower;
|
|
@@ -21746,7 +21885,7 @@ function check_for_loop_node(for_loop, status) {
|
|
|
21746
21885
|
if (upper_bound_expr) upper_bound_expr = upper_bound_expr + ".length";
|
|
21747
21886
|
}
|
|
21748
21887
|
for_status.values.push({
|
|
21749
|
-
declaration: "var",
|
|
21888
|
+
declaration: for_loop.item_is_ref ? "var" : "const",
|
|
21750
21889
|
name: for_loop.item.value,
|
|
21751
21890
|
type: for_loop.item.type,
|
|
21752
21891
|
is_set: true,
|
|
@@ -21761,19 +21900,56 @@ function check_for_loop_node(for_loop, status) {
|
|
|
21761
21900
|
if (for_loop.update) check_node(for_loop.update, for_status);
|
|
21762
21901
|
persist_invalidated(status, for_status);
|
|
21763
21902
|
}
|
|
21764
|
-
/**
|
|
21903
|
+
/**
|
|
21904
|
+
* Rewrite `for x of arr` in place into `for __idx of 0..arr.length { x = arr.at(__idx) }`.
|
|
21905
|
+
*
|
|
21906
|
+
* By default `x` is const (read-only). For `for ref x of arr` the desugaring is
|
|
21907
|
+
* copy-out / mutate / copy-back: `var x = arr.at(__idx)` is prepended and
|
|
21908
|
+
* `arr.set(__idx, x)` is appended, with the write-back duplicated before every
|
|
21909
|
+
* break/continue so mutations persist on all exit paths. (The write-back is
|
|
21910
|
+
* skipped on `return`, matching Rust's copy semantics.)
|
|
21911
|
+
*/
|
|
21765
21912
|
function desugar_array_for_loop(for_loop, array_type) {
|
|
21766
21913
|
const list = for_loop.list;
|
|
21767
21914
|
const start = list.start;
|
|
21768
21915
|
const original_item = for_loop.item;
|
|
21769
21916
|
const idx_name = `__for_idx_${original_item.value}`;
|
|
21917
|
+
const is_ref = !!for_loop.item_is_ref;
|
|
21770
21918
|
for_loop.item = new ValueNode(original_item.start, idx_name);
|
|
21771
21919
|
for_loop.list = new RangeNode(start, new ValueNode(start, "0"), array_type.length ? clone_node(array_type.length) : new AccessNode(start, clone_node(list), new AccessFieldNode(start, "length")));
|
|
21772
21920
|
const at_call = new AccessFunctionCallNode(start, "at", new Type(""), [new ValueNode(start, idx_name)]);
|
|
21773
21921
|
const at_access = new AccessNode(start, clone_node(list), at_call);
|
|
21774
|
-
const decl = new DeclarationNode(original_item.start, "private", "var", original_item.value, new Type(array_type.name), at_access);
|
|
21922
|
+
const decl = new DeclarationNode(original_item.start, "private", is_ref ? "var" : "const", original_item.value, new Type(array_type.name), at_access);
|
|
21775
21923
|
decl.is_loop_iterator = true;
|
|
21776
21924
|
for_loop.statements.unshift(decl);
|
|
21925
|
+
if (is_ref) {
|
|
21926
|
+
const make_writeback = () => {
|
|
21927
|
+
const set_call = new AccessFunctionCallNode(start, "set", new Type(""), [new ValueNode(start, idx_name), new ValueNode(start, original_item.value)]);
|
|
21928
|
+
return new AccessNode(start, clone_node(list), set_call);
|
|
21929
|
+
};
|
|
21930
|
+
insert_writebacks(for_loop.statements, make_writeback);
|
|
21931
|
+
for_loop.statements.push(make_writeback());
|
|
21932
|
+
}
|
|
21933
|
+
}
|
|
21934
|
+
/**
|
|
21935
|
+
* Walk a statement array and insert a write-back node before every break /
|
|
21936
|
+
* continue that belongs to the current loop. Nested for/while loops and nested
|
|
21937
|
+
* function bodies are not descended into (their exits belong to them).
|
|
21938
|
+
*/
|
|
21939
|
+
function insert_writebacks(statements, make_writeback) {
|
|
21940
|
+
for (let i = statements.length - 1; i >= 0; i--) {
|
|
21941
|
+
const stmt = statements[i];
|
|
21942
|
+
if (stmt.node_type === "break" || stmt.node_type === "continue") {
|
|
21943
|
+
statements.splice(i, 0, make_writeback());
|
|
21944
|
+
continue;
|
|
21945
|
+
}
|
|
21946
|
+
if (stmt.node_type === "for" || stmt.node_type === "while" || stmt.node_type === "func") continue;
|
|
21947
|
+
for (const key of Object.keys(stmt)) {
|
|
21948
|
+
if (key === "parent" || key === "scope") continue;
|
|
21949
|
+
const val = stmt[key];
|
|
21950
|
+
if (Array.isArray(val)) insert_writebacks(val, make_writeback);
|
|
21951
|
+
}
|
|
21952
|
+
}
|
|
21777
21953
|
}
|
|
21778
21954
|
function evaluate_range_bound_value(node, status) {
|
|
21779
21955
|
const val = evaluate_numeric_or_bool(node, status);
|
|
@@ -22688,6 +22864,10 @@ function warn_var_not_changed(decl, locals, status) {
|
|
|
22688
22864
|
if (is_discard(decl.name)) return;
|
|
22689
22865
|
if (locals.assigned.has(decl.name)) return;
|
|
22690
22866
|
if (status.mutated_local_names?.has(decl.name)) return;
|
|
22867
|
+
if (decl.type?.is_view) {
|
|
22868
|
+
add_warning(status, `View '${decl.name}' is never re-pointed, consider dropping 'var' (use 'view ${decl.name} = ...')`, decl.name_start ?? decl.start);
|
|
22869
|
+
return;
|
|
22870
|
+
}
|
|
22691
22871
|
add_warning(status, `Variable '${decl.name}' is never changed, consider using const`, decl.name_start ?? decl.start);
|
|
22692
22872
|
}
|
|
22693
22873
|
/** Names that count as a "use": call targets, method names, and value reads. */
|
|
@@ -23040,12 +23220,23 @@ function parse_declaration(visibility, declaration, status) {
|
|
|
23040
23220
|
decl.type_start = get_index(status);
|
|
23041
23221
|
decl.type = parse_type(status);
|
|
23042
23222
|
if (peek_current(status) === "=" || status.i >= status.tokens.length) {
|
|
23043
|
-
|
|
23044
|
-
|
|
23045
|
-
|
|
23046
|
-
|
|
23047
|
-
|
|
23048
|
-
|
|
23223
|
+
const modifier = status.tokens[saved_i]?.value;
|
|
23224
|
+
if ((modifier === "view" || modifier === "ref") && decl.type.name) {
|
|
23225
|
+
decl.name = decl.type.name;
|
|
23226
|
+
decl.name_start = saved_i + 1;
|
|
23227
|
+
const inferred = new Type("");
|
|
23228
|
+
if (modifier === "view") inferred.is_view = true;
|
|
23229
|
+
else inferred.is_ref = true;
|
|
23230
|
+
decl.type = inferred;
|
|
23231
|
+
decl.type_start = void 0;
|
|
23232
|
+
} else {
|
|
23233
|
+
status.i = saved_i;
|
|
23234
|
+
status.errors.length = saved_errors_length;
|
|
23235
|
+
decl.type = new Type("");
|
|
23236
|
+
decl.type_start = void 0;
|
|
23237
|
+
decl.name_start = get_index(status);
|
|
23238
|
+
decl.name = consume(status);
|
|
23239
|
+
}
|
|
23049
23240
|
} else {
|
|
23050
23241
|
decl.name_start = get_index(status);
|
|
23051
23242
|
decl.name = consume(status);
|
|
@@ -24270,13 +24461,16 @@ function parse_extend(visibility, status) {
|
|
|
24270
24461
|
function parse_for_loop(status) {
|
|
24271
24462
|
const for_start = get_index(status);
|
|
24272
24463
|
accept("for", status);
|
|
24273
|
-
const
|
|
24464
|
+
const start = get_index(status);
|
|
24465
|
+
const item_is_ref = accept("ref", status);
|
|
24466
|
+
const item = new ValueNode(start, consume(status));
|
|
24274
24467
|
if (expect("of", status)) {
|
|
24275
24468
|
const list = parse_expression(status);
|
|
24276
24469
|
let update;
|
|
24277
24470
|
if (accept(";", status)) update = parse_expression(status);
|
|
24278
24471
|
if (expect("{", status)) {
|
|
24279
24472
|
const for_loop = new ForLoopNode(for_start, item, list, void 0, update);
|
|
24473
|
+
for_loop.item_is_ref = item_is_ref;
|
|
24280
24474
|
status.stack.push(for_loop);
|
|
24281
24475
|
parse_statement(status);
|
|
24282
24476
|
expect("}", status);
|
|
@@ -24374,6 +24568,7 @@ function parse_visibility(visibility, status) {
|
|
|
24374
24568
|
case "const":
|
|
24375
24569
|
case "var":
|
|
24376
24570
|
case "mov":
|
|
24571
|
+
case "view":
|
|
24377
24572
|
if (visibility === "private" && status.stack.at(-1)?.node_type === "trait") {
|
|
24378
24573
|
add_error(status, `Trait fields cannot be private`, get_index(status));
|
|
24379
24574
|
consume(status);
|
|
@@ -24465,6 +24660,7 @@ function parse_statement(status) {
|
|
|
24465
24660
|
case "const":
|
|
24466
24661
|
case "var":
|
|
24467
24662
|
case "mov":
|
|
24663
|
+
case "view":
|
|
24468
24664
|
parse_declaration(default_visibility(status), value, status);
|
|
24469
24665
|
break;
|
|
24470
24666
|
case "struct":
|
|
@@ -25223,7 +25419,7 @@ const MAIN_NM = `import System
|
|
|
25223
25419
|
pub func add = (int a, int b) => a + b
|
|
25224
25420
|
|
|
25225
25421
|
pub func main = (Init init) {
|
|
25226
|
-
Console.write("Hello world, 2 + 2 is \\{add(
|
|
25422
|
+
Console.write("Hello world, 2 + 2 is \\{add(2, 2)}!\\n")
|
|
25227
25423
|
}
|
|
25228
25424
|
`;
|
|
25229
25425
|
const TEST_NM = `import System
|
|
@@ -25307,6 +25503,30 @@ function run_init(name) {
|
|
|
25307
25503
|
console.log(`\nNext: cd ${name} && nomen run`);
|
|
25308
25504
|
}
|
|
25309
25505
|
//#endregion
|
|
25506
|
+
//#region src/paths.ts
|
|
25507
|
+
/**
|
|
25508
|
+
* The project root for `input_path`: the nearest ancestor folder (or the input
|
|
25509
|
+
* folder itself) containing a `package.jsonc`. Falls back to the input's own
|
|
25510
|
+
* folder for standalone files, so builds stay next to their source.
|
|
25511
|
+
*/
|
|
25512
|
+
function project_root_for(input_path) {
|
|
25513
|
+
let dir = fs.lstatSync(input_path).isDirectory() ? input_path : path.dirname(input_path);
|
|
25514
|
+
for (let i = 0; i < 20; i++) {
|
|
25515
|
+
if (fs.existsSync(path.join(dir, "package.jsonc"))) return dir;
|
|
25516
|
+
const parent = path.dirname(dir);
|
|
25517
|
+
if (parent === dir) break;
|
|
25518
|
+
dir = parent;
|
|
25519
|
+
}
|
|
25520
|
+
return path.dirname(input_path);
|
|
25521
|
+
}
|
|
25522
|
+
/**
|
|
25523
|
+
* The folder that receives compiler output for `input_path`: the project's
|
|
25524
|
+
* `build/`, or `build/test` for `*.test.nm` files.
|
|
25525
|
+
*/
|
|
25526
|
+
function build_dir_for(input_path, is_test) {
|
|
25527
|
+
return is_test ? path.join(project_root_for(input_path), "build", "test") : path.join(project_root_for(input_path), "build");
|
|
25528
|
+
}
|
|
25529
|
+
//#endregion
|
|
25310
25530
|
//#region src/test.ts
|
|
25311
25531
|
const RECORD_PREFIX = "\\nomen|";
|
|
25312
25532
|
/** Recursively collect `*.test.nm` files under `root`, skipping build output. */
|
|
@@ -25487,7 +25707,7 @@ function run_test_file(entry_path, lib_path, arch) {
|
|
|
25487
25707
|
result.ms = performance.now() - start;
|
|
25488
25708
|
return result;
|
|
25489
25709
|
}
|
|
25490
|
-
const buildDir =
|
|
25710
|
+
const buildDir = build_dir_for(resolved, true);
|
|
25491
25711
|
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir, { recursive: true });
|
|
25492
25712
|
const ext = arch === "aarch64" ? ".s" : ".c";
|
|
25493
25713
|
const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
|
|
@@ -25573,8 +25793,8 @@ function report_file(result) {
|
|
|
25573
25793
|
const mark = result.ok ? C.green("✓") : C.red("✗");
|
|
25574
25794
|
console.log(` ${mark} ${rel} ${C.dim(`(${totalTests} tests)`)} ${C.dim(format_duration(result.ms))}`);
|
|
25575
25795
|
if (result.crashed) console.log(C.red(` ${result.crashed}`));
|
|
25576
|
-
for (const f of result.fails) console.log(` ${C.red("✗")} ${
|
|
25577
|
-
for (const b of result.benches) console.log(` ${C.cyan("⏱")} ${
|
|
25796
|
+
for (const f of result.fails) console.log(` ${C.red("✗")} ${f.test} ${C.dim(">")} ${f.message}`);
|
|
25797
|
+
for (const b of result.benches) console.log(` ${C.cyan("⏱")} ${b.label} ${C.dim(`(n=${b.n})`)} ${C.dim("min")} ${fmt_ns(b.min)} ${C.dim("median")} ${fmt_ns(b.median)} ${C.dim("mean")} ${fmt_ns(Math.round(b.mean))} ${C.dim("max")} ${fmt_ns(b.max)} ${C.dim("±")} ${fmt_ns(Math.round(b.stddev))}`);
|
|
25578
25798
|
if (result.other.length) {
|
|
25579
25799
|
console.log(C.dim(" --- test stdout ---"));
|
|
25580
25800
|
for (const line of result.other) console.log(C.dim(` ${line}`));
|
|
@@ -25595,7 +25815,7 @@ function runTests(root, options = {}) {
|
|
|
25595
25815
|
}
|
|
25596
25816
|
const elapsed = performance.now() - startTime;
|
|
25597
25817
|
const totalFiles = results.length;
|
|
25598
|
-
const totalFilesFailed = results.reduce((a, r) => a + (r.
|
|
25818
|
+
const totalFilesFailed = results.reduce((a, r) => a + (r.ok ? 0 : 1), 0);
|
|
25599
25819
|
const totalFilesPassed = totalFiles - totalFilesFailed;
|
|
25600
25820
|
const totalTests = results.reduce((a, r) => a + r.tests.length, 0);
|
|
25601
25821
|
const totalFailed = results.reduce((a, r) => a + r.tests.reduce((x, t) => x + t.failed, 0), 0);
|
|
@@ -25667,7 +25887,6 @@ function collect_nm_files(folder) {
|
|
|
25667
25887
|
}
|
|
25668
25888
|
return out;
|
|
25669
25889
|
}
|
|
25670
|
-
let build_root;
|
|
25671
25890
|
console.error("\n~ NOMEN ~\n");
|
|
25672
25891
|
const args = parse_args();
|
|
25673
25892
|
const command = args.command;
|
|
@@ -25728,7 +25947,6 @@ try {
|
|
|
25728
25947
|
}
|
|
25729
25948
|
args.in = args.in ?? resolve_input();
|
|
25730
25949
|
if (!args.in) process.exit(0);
|
|
25731
|
-
if (!build_root) build_root = fs.lstatSync(args.in).isDirectory() ? args.in : path.dirname(args.in);
|
|
25732
25950
|
if (fs.existsSync(args.in)) {
|
|
25733
25951
|
let config = {
|
|
25734
25952
|
arch: "aarch64",
|
|
@@ -25758,19 +25976,13 @@ function resolve_input() {
|
|
|
25758
25976
|
if (fs.existsSync(config_path)) try {
|
|
25759
25977
|
const json = fs.readFileSync(config_path, "utf8").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
25760
25978
|
const parsed = JSON.parse(json);
|
|
25761
|
-
if (parsed.entry)
|
|
25762
|
-
build_root = cwd;
|
|
25763
|
-
return path.resolve(cwd, parsed.entry);
|
|
25764
|
-
}
|
|
25979
|
+
if (parsed.entry) return path.resolve(cwd, parsed.entry);
|
|
25765
25980
|
} catch {}
|
|
25766
25981
|
let nm_files = [];
|
|
25767
25982
|
try {
|
|
25768
25983
|
nm_files = fs.readdirSync(cwd).filter((f) => f.endsWith(".nm"));
|
|
25769
25984
|
} catch {}
|
|
25770
|
-
if (nm_files.length === 1)
|
|
25771
|
-
build_root = cwd;
|
|
25772
|
-
return path.resolve(cwd, nm_files[0]);
|
|
25773
|
-
}
|
|
25985
|
+
if (nm_files.length === 1) return path.resolve(cwd, nm_files[0]);
|
|
25774
25986
|
if (nm_files.length > 1) {
|
|
25775
25987
|
console.log(`Found ${nm_files.length} .nm files in ${cwd}. Specify which to run with --in:\n ` + nm_files.join("\n "));
|
|
25776
25988
|
return;
|
|
@@ -25863,7 +26075,7 @@ function processFile(filename, config, mode) {
|
|
|
25863
26075
|
return;
|
|
25864
26076
|
}
|
|
25865
26077
|
const basename = path.basename(filename, ".nm");
|
|
25866
|
-
const buildDir =
|
|
26078
|
+
const buildDir = build_dir_for(resolved_path, filename.endsWith(".test.nm"));
|
|
25867
26079
|
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir, { recursive: true });
|
|
25868
26080
|
const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
25869
26081
|
const headerfile = path.join(buildDir, "main.h");
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,6 +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
18
|
import { runTests } from "./test.ts";
|
|
18
19
|
import type Config from "./types/Config.ts";
|
|
19
20
|
|
|
@@ -66,11 +67,6 @@ function collect_nm_files(folder: string): string[] {
|
|
|
66
67
|
return out;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
// The folder whose `build/` subdirectory receives compiler output. Set during
|
|
70
|
-
// input resolution: the --in folder, the .nm file's folder, or — for
|
|
71
|
-
// package.jsonc discovery — the package folder (cwd), not the entry's folder.
|
|
72
|
-
let build_root: string | undefined;
|
|
73
|
-
|
|
74
70
|
console.error("\n~ NOMEN ~\n");
|
|
75
71
|
|
|
76
72
|
const args: Args = parse_args();
|
|
@@ -154,11 +150,6 @@ try {
|
|
|
154
150
|
if (!args.in) {
|
|
155
151
|
process.exit(0);
|
|
156
152
|
}
|
|
157
|
-
// For an explicit --in, build next to whatever was passed (the folder
|
|
158
|
-
// itself, or the file's folder). Discovery cases set build_root themselves.
|
|
159
|
-
if (!build_root) {
|
|
160
|
-
build_root = fs.lstatSync(args.in).isDirectory() ? args.in : path.dirname(args.in);
|
|
161
|
-
}
|
|
162
153
|
|
|
163
154
|
if (fs.existsSync(args.in)) {
|
|
164
155
|
let config: Config = { arch: "aarch64", platform: default_platform() };
|
|
@@ -213,7 +204,6 @@ function resolve_input(): string | undefined {
|
|
|
213
204
|
const json = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
214
205
|
const parsed = JSON.parse(json);
|
|
215
206
|
if (parsed.entry) {
|
|
216
|
-
build_root = cwd;
|
|
217
207
|
return path.resolve(cwd, parsed.entry);
|
|
218
208
|
}
|
|
219
209
|
} catch {
|
|
@@ -229,7 +219,6 @@ function resolve_input(): string | undefined {
|
|
|
229
219
|
// unreadable working folder
|
|
230
220
|
}
|
|
231
221
|
if (nm_files.length === 1) {
|
|
232
|
-
build_root = cwd;
|
|
233
222
|
return path.resolve(cwd, nm_files[0]);
|
|
234
223
|
}
|
|
235
224
|
if (nm_files.length > 1) {
|
|
@@ -368,7 +357,8 @@ function processFile(filename: string, config: Config, mode: Mode) {
|
|
|
368
357
|
}
|
|
369
358
|
|
|
370
359
|
const basename = path.basename(filename, ".nm");
|
|
371
|
-
|
|
360
|
+
// Output lives in the project's `build/` — `build/test` for *.test.nm.
|
|
361
|
+
const buildDir = build_dir_for(resolved_path, filename.endsWith(".test.nm"));
|
|
372
362
|
if (!fs.existsSync(buildDir)) {
|
|
373
363
|
fs.mkdirSync(buildDir, { recursive: true });
|
|
374
364
|
}
|
package/src/init.ts
CHANGED
package/src/paths.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The project root for `input_path`: the nearest ancestor folder (or the input
|
|
6
|
+
* folder itself) containing a `package.jsonc`. Falls back to the input's own
|
|
7
|
+
* folder for standalone files, so builds stay next to their source.
|
|
8
|
+
*/
|
|
9
|
+
export function project_root_for(input_path: string): string {
|
|
10
|
+
let dir = fs.lstatSync(input_path).isDirectory() ? input_path : path.dirname(input_path);
|
|
11
|
+
for (let i = 0; i < 20; i++) {
|
|
12
|
+
if (fs.existsSync(path.join(dir, "package.jsonc"))) return dir;
|
|
13
|
+
const parent = path.dirname(dir);
|
|
14
|
+
if (parent === dir) break;
|
|
15
|
+
dir = parent;
|
|
16
|
+
}
|
|
17
|
+
return path.dirname(input_path);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The folder that receives compiler output for `input_path`: the project's
|
|
22
|
+
* `build/`, or `build/test` for `*.test.nm` files.
|
|
23
|
+
*/
|
|
24
|
+
export function build_dir_for(input_path: string, is_test: boolean): string {
|
|
25
|
+
return is_test
|
|
26
|
+
? path.join(project_root_for(input_path), "build", "test")
|
|
27
|
+
: path.join(project_root_for(input_path), "build");
|
|
28
|
+
}
|
package/src/test.ts
CHANGED
|
@@ -8,6 +8,7 @@ import join from "../../src/join.ts";
|
|
|
8
8
|
import { get_library } from "../../src/lib.ts";
|
|
9
9
|
import parse from "../../src/parse.ts";
|
|
10
10
|
import { find_bundled } from "./init.ts";
|
|
11
|
+
import { build_dir_for } from "./paths.ts";
|
|
11
12
|
|
|
12
13
|
const RECORD_PREFIX = "\\nomen|";
|
|
13
14
|
|
|
@@ -303,7 +304,7 @@ export function run_test_file(
|
|
|
303
304
|
return result;
|
|
304
305
|
}
|
|
305
306
|
|
|
306
|
-
const buildDir =
|
|
307
|
+
const buildDir = build_dir_for(resolved, true);
|
|
307
308
|
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir, { recursive: true });
|
|
308
309
|
const ext = arch === "aarch64" ? ".s" : ".c";
|
|
309
310
|
const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
|
|
@@ -407,11 +408,11 @@ export function report_file(result: TestFileResult): void {
|
|
|
407
408
|
// short-circuiting a test has at most one failure, so the fail lines are
|
|
408
409
|
// the per-test detail and a separate count line would just repeat names.
|
|
409
410
|
for (const f of result.fails) {
|
|
410
|
-
console.log(` ${C.red("✗")} ${
|
|
411
|
+
console.log(` ${C.red("✗")} ${f.test} ${C.dim(">")} ${f.message}`);
|
|
411
412
|
}
|
|
412
413
|
for (const b of result.benches) {
|
|
413
414
|
console.log(
|
|
414
|
-
` ${C.cyan("⏱")} ${
|
|
415
|
+
` ${C.cyan("⏱")} ${b.label} ${C.dim(`(n=${b.n})`)} ` +
|
|
415
416
|
`${C.dim("min")} ${fmt_ns(b.min)} ${C.dim("median")} ${fmt_ns(b.median)} ` +
|
|
416
417
|
`${C.dim("mean")} ${fmt_ns(Math.round(b.mean))} ${C.dim("max")} ${fmt_ns(b.max)} ` +
|
|
417
418
|
`${C.dim("±")} ${fmt_ns(Math.round(b.stddev))}`,
|
|
@@ -447,7 +448,7 @@ export function runTests(root: string, options: RunTestsOptions = {}): boolean {
|
|
|
447
448
|
|
|
448
449
|
const elapsed = performance.now() - startTime;
|
|
449
450
|
const totalFiles = results.length;
|
|
450
|
-
const totalFilesFailed = results.reduce((a, r) => a + (r.
|
|
451
|
+
const totalFilesFailed = results.reduce((a, r) => a + (r.ok ? 0 : 1), 0);
|
|
451
452
|
const totalFilesPassed = totalFiles - totalFilesFailed;
|
|
452
453
|
// `tests[].failed` (from each test's `done` record) is the authoritative
|
|
453
454
|
// failure count; `result.fails` holds the same failures' messages for
|