nomen-lang 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOMEN_AGENTS.md +5 -5
- package/dist/index.mjs +191 -35
- package/package.json +1 -1
- package/src/index.ts +4 -14
- package/src/init.ts +7 -7
- package/src/paths.ts +28 -0
- package/src/test.ts +18 -9
package/NOMEN_AGENTS.md
CHANGED
|
@@ -68,11 +68,11 @@ func safe = (string[] s, int i: i >= 0 && i < s.length, out string) {
|
|
|
68
68
|
|
|
69
69
|
### Memory modifiers
|
|
70
70
|
|
|
71
|
-
| Modifier | Meaning
|
|
72
|
-
|
|
73
|
-
| `ref T` | mutable borrow; caller must also write `ref`
|
|
74
|
-
| `view T` | read-only borrow
|
|
75
|
-
| `mov T` | transfer ownership; caller writes `mov`
|
|
71
|
+
| Modifier | Meaning |
|
|
72
|
+
| -------- | ---------------------------------------------- |
|
|
73
|
+
| `ref T` | mutable borrow; caller must also write `ref` |
|
|
74
|
+
| `view T` | read-only borrow |
|
|
75
|
+
| `mov T` | transfer ownership; caller writes `mov` |
|
|
76
76
|
| `out T` | output parameter, assigned inside the function |
|
|
77
77
|
|
|
78
78
|
`const` values cannot be passed to `ref` — rebind the caller as `var` first.
|
package/dist/index.mjs
CHANGED
|
@@ -16308,16 +16308,33 @@ function describe(token) {
|
|
|
16308
16308
|
}
|
|
16309
16309
|
//#endregion
|
|
16310
16310
|
//#region ../src/join.ts
|
|
16311
|
-
const
|
|
16312
|
-
|
|
16313
|
-
function
|
|
16311
|
+
const DEFAULT_LIB_REL = "../../core/src";
|
|
16312
|
+
let default_lib_path;
|
|
16313
|
+
function fallback_lib_path() {
|
|
16314
|
+
if (default_lib_path === void 0) try {
|
|
16315
|
+
default_lib_path = path.resolve(path.dirname(fileURLToPath(import.meta.url)), DEFAULT_LIB_REL);
|
|
16316
|
+
} catch {
|
|
16317
|
+
default_lib_path = "";
|
|
16318
|
+
}
|
|
16319
|
+
return default_lib_path;
|
|
16320
|
+
}
|
|
16321
|
+
let test_src_dir;
|
|
16322
|
+
function join$1(entry_file_path, lib_path, options) {
|
|
16314
16323
|
const folder_path = path.dirname(entry_file_path);
|
|
16315
16324
|
const file_path = path.basename(entry_file_path);
|
|
16316
16325
|
const inputs = /* @__PURE__ */ new Map();
|
|
16317
|
-
const resolved_lib_path = lib_path ? path.resolve(lib_path, "src") :
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16326
|
+
const resolved_lib_path = lib_path ? path.resolve(lib_path, "src") : fallback_lib_path();
|
|
16327
|
+
const for_test = options?.for_test ?? file_path.endsWith(".test.nm");
|
|
16328
|
+
const prev_test_src_dir = test_src_dir;
|
|
16329
|
+
test_src_dir = for_test ? resolve_src_module(folder_path) : void 0;
|
|
16330
|
+
try {
|
|
16331
|
+
add_source(folder_path, `./${file_path}`, inputs, resolved_lib_path);
|
|
16332
|
+
gather_module_siblings(folder_path, file_path, inputs, resolved_lib_path);
|
|
16333
|
+
gather_module_parent(folder_path, file_path, inputs, resolved_lib_path);
|
|
16334
|
+
if (test_src_dir) gather_src_module(test_src_dir, inputs, resolved_lib_path);
|
|
16335
|
+
} finally {
|
|
16336
|
+
test_src_dir = prev_test_src_dir;
|
|
16337
|
+
}
|
|
16321
16338
|
return Array.from(inputs.values()).join("\n\n") + "\n";
|
|
16322
16339
|
}
|
|
16323
16340
|
function gather_module_siblings(folder_path, entry_file, inputs, lib_path) {
|
|
@@ -16361,6 +16378,26 @@ function gather_module_parent(folder_path, entry_file, inputs, lib_path) {
|
|
|
16361
16378
|
add_source(parent_path, `./${name}`, inputs, lib_path);
|
|
16362
16379
|
}
|
|
16363
16380
|
}
|
|
16381
|
+
/** The program's `src/` module dir for a file in `entry_dir`, if one exists. */
|
|
16382
|
+
function resolve_src_module(entry_dir) {
|
|
16383
|
+
for (const dir of [path.join(entry_dir, "src"), path.join(path.dirname(entry_dir), "src")]) try {
|
|
16384
|
+
if (fs.statSync(dir).isDirectory()) return dir;
|
|
16385
|
+
} catch {}
|
|
16386
|
+
}
|
|
16387
|
+
function gather_src_module(src_dir, inputs, lib_path) {
|
|
16388
|
+
let names;
|
|
16389
|
+
try {
|
|
16390
|
+
names = fs.readdirSync(src_dir);
|
|
16391
|
+
} catch {
|
|
16392
|
+
return;
|
|
16393
|
+
}
|
|
16394
|
+
for (const name of names.sort()) {
|
|
16395
|
+
if (!name.endsWith(".nm")) continue;
|
|
16396
|
+
const import_file_path = `./${name}`;
|
|
16397
|
+
if (inputs.has(import_file_path)) continue;
|
|
16398
|
+
add_source(src_dir, import_file_path, inputs, lib_path);
|
|
16399
|
+
}
|
|
16400
|
+
}
|
|
16364
16401
|
function has_main(source) {
|
|
16365
16402
|
return /\bfunc\s+main\b/.test(source);
|
|
16366
16403
|
}
|
|
@@ -16370,8 +16407,9 @@ function add_source(folder_path, file_path, inputs, lib_path) {
|
|
|
16370
16407
|
const lib_source_path = path.resolve(lib_path, file_path);
|
|
16371
16408
|
if (fs.existsSync(lib_source_path)) source_path = lib_source_path;
|
|
16372
16409
|
}
|
|
16373
|
-
let source =
|
|
16374
|
-
|
|
16410
|
+
let source = fs.readFileSync(source_path, "utf8");
|
|
16411
|
+
if (test_src_dir && is_within$1(source_path, test_src_dir)) source = strip_main_functions(source);
|
|
16412
|
+
source = `// file://${source_path}\n` + source;
|
|
16375
16413
|
source = source.replaceAll(/^import(.*)$/gm, (match, name) => {
|
|
16376
16414
|
const trimmed = name.trim();
|
|
16377
16415
|
if (trimmed === "System" || trimmed.startsWith("System/")) return match;
|
|
@@ -16381,6 +16419,105 @@ function add_source(folder_path, file_path, inputs, lib_path) {
|
|
|
16381
16419
|
});
|
|
16382
16420
|
inputs.set(file_path, source);
|
|
16383
16421
|
}
|
|
16422
|
+
function is_within$1(child, parent) {
|
|
16423
|
+
const rel = path.relative(parent, child);
|
|
16424
|
+
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
16425
|
+
}
|
|
16426
|
+
/**
|
|
16427
|
+
* Remove every top-level `func main` (optionally `pub`) declaration, body and
|
|
16428
|
+
* all. Used when compiling a test: the generated harness provides `main`, so
|
|
16429
|
+
* the program's own `main` would collide as a duplicate declaration.
|
|
16430
|
+
*/
|
|
16431
|
+
function strip_main_functions(source) {
|
|
16432
|
+
let result = "";
|
|
16433
|
+
let i = 0;
|
|
16434
|
+
const n = source.length;
|
|
16435
|
+
let depth = 0;
|
|
16436
|
+
while (i < n) {
|
|
16437
|
+
if (depth === 0 && is_main_declaration(source, i)) {
|
|
16438
|
+
i = skip_main_function(source, i);
|
|
16439
|
+
continue;
|
|
16440
|
+
}
|
|
16441
|
+
const ch = source[i];
|
|
16442
|
+
result += ch;
|
|
16443
|
+
if (ch === "\"" || ch === "'") {
|
|
16444
|
+
i++;
|
|
16445
|
+
let escaped = false;
|
|
16446
|
+
while (i < n) {
|
|
16447
|
+
const c = source[i];
|
|
16448
|
+
result += c;
|
|
16449
|
+
i++;
|
|
16450
|
+
if (escaped) escaped = false;
|
|
16451
|
+
else if (c === "\\") escaped = true;
|
|
16452
|
+
else if (c === ch) break;
|
|
16453
|
+
}
|
|
16454
|
+
continue;
|
|
16455
|
+
}
|
|
16456
|
+
if (ch === "{") depth++;
|
|
16457
|
+
if (ch === "}") depth--;
|
|
16458
|
+
i++;
|
|
16459
|
+
}
|
|
16460
|
+
return result;
|
|
16461
|
+
}
|
|
16462
|
+
function is_main_declaration(source, i) {
|
|
16463
|
+
if (i > 0 && source[i - 1] !== "\n") return false;
|
|
16464
|
+
const line_end = source.indexOf("\n", i);
|
|
16465
|
+
const line = line_end === -1 ? source.slice(i) : source.slice(i, line_end);
|
|
16466
|
+
return /^\s*(pub\s+)?func\s+main\b/.test(line);
|
|
16467
|
+
}
|
|
16468
|
+
function skip_main_function(source, i) {
|
|
16469
|
+
const n = source.length;
|
|
16470
|
+
let line_end = i;
|
|
16471
|
+
while (line_end < n && source[line_end] !== "\n") line_end++;
|
|
16472
|
+
const decl_line = source.slice(i, line_end);
|
|
16473
|
+
const brace = decl_line.indexOf("{");
|
|
16474
|
+
if (brace === -1) {
|
|
16475
|
+
if (decl_line.includes("=>")) return line_end < n ? line_end + 1 : n;
|
|
16476
|
+
let j = line_end;
|
|
16477
|
+
while (j < n && source[j] !== "{") {
|
|
16478
|
+
if (source[j] === "\"" || source[j] === "'") {
|
|
16479
|
+
const quote = source[j];
|
|
16480
|
+
j++;
|
|
16481
|
+
while (j < n && source[j] !== quote) j++;
|
|
16482
|
+
}
|
|
16483
|
+
j++;
|
|
16484
|
+
}
|
|
16485
|
+
if (j >= n) return n;
|
|
16486
|
+
return skip_balanced_block(source, j);
|
|
16487
|
+
}
|
|
16488
|
+
return skip_balanced_block(source, i + brace);
|
|
16489
|
+
}
|
|
16490
|
+
function skip_balanced_block(source, brace) {
|
|
16491
|
+
const n = source.length;
|
|
16492
|
+
let depth = 0;
|
|
16493
|
+
let j = brace;
|
|
16494
|
+
while (j < n) {
|
|
16495
|
+
const ch = source[j];
|
|
16496
|
+
if (ch === "\"" || ch === "'") {
|
|
16497
|
+
const quote = ch;
|
|
16498
|
+
j++;
|
|
16499
|
+
let escaped = false;
|
|
16500
|
+
while (j < n) {
|
|
16501
|
+
const c = source[j++];
|
|
16502
|
+
if (escaped) escaped = false;
|
|
16503
|
+
else if (c === "\\") escaped = true;
|
|
16504
|
+
else if (c === quote) break;
|
|
16505
|
+
}
|
|
16506
|
+
continue;
|
|
16507
|
+
}
|
|
16508
|
+
if (ch === "{") depth++;
|
|
16509
|
+
if (ch === "}") {
|
|
16510
|
+
depth--;
|
|
16511
|
+
if (depth === 0) {
|
|
16512
|
+
j++;
|
|
16513
|
+
while (j < n && (source[j] === "\r" || source[j] === "\n")) j++;
|
|
16514
|
+
return j;
|
|
16515
|
+
}
|
|
16516
|
+
}
|
|
16517
|
+
j++;
|
|
16518
|
+
}
|
|
16519
|
+
return n;
|
|
16520
|
+
}
|
|
16384
16521
|
//#endregion
|
|
16385
16522
|
//#region ../src/lib.ts
|
|
16386
16523
|
function read_library_config(lib_dir) {
|
|
@@ -22513,6 +22650,7 @@ function dedupe_warnings(warnings) {
|
|
|
22513
22650
|
function warn_unused_function(func, referenced, status, struct) {
|
|
22514
22651
|
if (func.is_library || struct && struct.is_library) return;
|
|
22515
22652
|
if (func.name === "main" || is_discard(func.name) || func.name.startsWith("#")) return;
|
|
22653
|
+
if (func.visibility === "pub") return;
|
|
22516
22654
|
if (func.is_generic) return;
|
|
22517
22655
|
if (struct && struct.traits.length > 0) return;
|
|
22518
22656
|
if (referenced.has(func.name)) return;
|
|
@@ -25084,22 +25222,22 @@ function package_jsonc(name) {
|
|
|
25084
25222
|
"entry": "src/main.nm"
|
|
25085
25223
|
// The System library is resolved automatically from your nomen-lang
|
|
25086
25224
|
// install. To pin a local checkout instead, uncomment:
|
|
25087
|
-
// "imports": { "System": "
|
|
25225
|
+
// "imports": { "System": "~/Source/nomen/core" }
|
|
25088
25226
|
}
|
|
25089
25227
|
`;
|
|
25090
25228
|
}
|
|
25091
25229
|
const MAIN_NM = `import System
|
|
25092
25230
|
|
|
25231
|
+
pub func add = (int a, int b) => a + b
|
|
25232
|
+
|
|
25093
25233
|
pub func main = (Init init) {
|
|
25094
|
-
Console.write("Hello world!\\n")
|
|
25234
|
+
Console.write("Hello world, 2 + 2 is \\{add(2, 2)}!\\n")
|
|
25095
25235
|
}
|
|
25096
25236
|
`;
|
|
25097
25237
|
const TEST_NM = `import System
|
|
25098
25238
|
import System/Test
|
|
25099
25239
|
|
|
25100
|
-
func
|
|
25101
|
-
|
|
25102
|
-
pub func test_smoke = (ref Tester t) {
|
|
25240
|
+
pub func test_add = (ref Tester t) {
|
|
25103
25241
|
t.expect(add(2, 2) == 4, "2 + 2 should equal 4")
|
|
25104
25242
|
}
|
|
25105
25243
|
`;
|
|
@@ -25164,19 +25302,43 @@ function run_init(name) {
|
|
|
25164
25302
|
fs.writeFileSync(path.join(target, ".gitignore"), GITIGNORE);
|
|
25165
25303
|
fs.writeFileSync(path.join(target, "package.jsonc"), package_jsonc(name));
|
|
25166
25304
|
fs.writeFileSync(path.join(src, "main.nm"), MAIN_NM);
|
|
25167
|
-
fs.writeFileSync(path.join(test, "
|
|
25305
|
+
fs.writeFileSync(path.join(test, "main.test.nm"), TEST_NM);
|
|
25168
25306
|
fs.writeFileSync(path.join(target, "README.md"), readme(name));
|
|
25169
25307
|
fs.writeFileSync(path.join(target, "AGENTS.md"), fs.readFileSync(agents_template));
|
|
25170
25308
|
console.log(`Created ${name}/`);
|
|
25171
25309
|
console.log(` ${name}/.gitignore`);
|
|
25172
25310
|
console.log(` ${name}/package.jsonc`);
|
|
25173
25311
|
console.log(` ${name}/src/main.nm`);
|
|
25174
|
-
console.log(` ${name}/test/
|
|
25312
|
+
console.log(` ${name}/test/main.test.nm`);
|
|
25175
25313
|
console.log(` ${name}/README.md`);
|
|
25176
25314
|
console.log(` ${name}/AGENTS.md`);
|
|
25177
25315
|
console.log(`\nNext: cd ${name} && nomen run`);
|
|
25178
25316
|
}
|
|
25179
25317
|
//#endregion
|
|
25318
|
+
//#region src/paths.ts
|
|
25319
|
+
/**
|
|
25320
|
+
* The project root for `input_path`: the nearest ancestor folder (or the input
|
|
25321
|
+
* folder itself) containing a `package.jsonc`. Falls back to the input's own
|
|
25322
|
+
* folder for standalone files, so builds stay next to their source.
|
|
25323
|
+
*/
|
|
25324
|
+
function project_root_for(input_path) {
|
|
25325
|
+
let dir = fs.lstatSync(input_path).isDirectory() ? input_path : path.dirname(input_path);
|
|
25326
|
+
for (let i = 0; i < 20; i++) {
|
|
25327
|
+
if (fs.existsSync(path.join(dir, "package.jsonc"))) return dir;
|
|
25328
|
+
const parent = path.dirname(dir);
|
|
25329
|
+
if (parent === dir) break;
|
|
25330
|
+
dir = parent;
|
|
25331
|
+
}
|
|
25332
|
+
return path.dirname(input_path);
|
|
25333
|
+
}
|
|
25334
|
+
/**
|
|
25335
|
+
* The folder that receives compiler output for `input_path`: the project's
|
|
25336
|
+
* `build/`, or `build/test` for `*.test.nm` files.
|
|
25337
|
+
*/
|
|
25338
|
+
function build_dir_for(input_path, is_test) {
|
|
25339
|
+
return is_test ? path.join(project_root_for(input_path), "build", "test") : path.join(project_root_for(input_path), "build");
|
|
25340
|
+
}
|
|
25341
|
+
//#endregion
|
|
25180
25342
|
//#region src/test.ts
|
|
25181
25343
|
const RECORD_PREFIX = "\\nomen|";
|
|
25182
25344
|
/** Recursively collect `*.test.nm` files under `root`, skipping build output. */
|
|
@@ -25357,7 +25519,7 @@ function run_test_file(entry_path, lib_path, arch) {
|
|
|
25357
25519
|
result.ms = performance.now() - start;
|
|
25358
25520
|
return result;
|
|
25359
25521
|
}
|
|
25360
|
-
const buildDir =
|
|
25522
|
+
const buildDir = build_dir_for(resolved, true);
|
|
25361
25523
|
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir, { recursive: true });
|
|
25362
25524
|
const ext = arch === "aarch64" ? ".s" : ".c";
|
|
25363
25525
|
const codefile = path.join(buildDir, path.basename(entry_path, ".nm") + ext);
|
|
@@ -25443,8 +25605,8 @@ function report_file(result) {
|
|
|
25443
25605
|
const mark = result.ok ? C.green("✓") : C.red("✗");
|
|
25444
25606
|
console.log(` ${mark} ${rel} ${C.dim(`(${totalTests} tests)`)} ${C.dim(format_duration(result.ms))}`);
|
|
25445
25607
|
if (result.crashed) console.log(C.red(` ${result.crashed}`));
|
|
25446
|
-
for (const f of result.fails) console.log(` ${C.red("✗")} ${
|
|
25447
|
-
for (const b of result.benches) console.log(` ${C.cyan("⏱")} ${
|
|
25608
|
+
for (const f of result.fails) console.log(` ${C.red("✗")} ${f.test} ${C.dim(">")} ${f.message}`);
|
|
25609
|
+
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))}`);
|
|
25448
25610
|
if (result.other.length) {
|
|
25449
25611
|
console.log(C.dim(" --- test stdout ---"));
|
|
25450
25612
|
for (const line of result.other) console.log(C.dim(` ${line}`));
|
|
@@ -25455,7 +25617,7 @@ function runTests(root, options = {}) {
|
|
|
25455
25617
|
const arch = options.arch ?? "aarch64";
|
|
25456
25618
|
const lib = resolve_lib_for(root);
|
|
25457
25619
|
const files = collect_test_files(root).filter((f) => !options.filter || options.filter.test(f));
|
|
25458
|
-
console.log(
|
|
25620
|
+
console.log(`Found ${files.length} test file(s)\n`);
|
|
25459
25621
|
const startTime = performance.now();
|
|
25460
25622
|
const results = [];
|
|
25461
25623
|
for (const file of files) {
|
|
@@ -25465,14 +25627,16 @@ function runTests(root, options = {}) {
|
|
|
25465
25627
|
}
|
|
25466
25628
|
const elapsed = performance.now() - startTime;
|
|
25467
25629
|
const totalFiles = results.length;
|
|
25630
|
+
const totalFilesFailed = results.reduce((a, r) => a + (r.ok ? 0 : 1), 0);
|
|
25631
|
+
const totalFilesPassed = totalFiles - totalFilesFailed;
|
|
25468
25632
|
const totalTests = results.reduce((a, r) => a + r.tests.length, 0);
|
|
25469
25633
|
const totalFailed = results.reduce((a, r) => a + r.tests.reduce((x, t) => x + t.failed, 0), 0);
|
|
25470
25634
|
const totalPassed = totalTests - totalFailed;
|
|
25471
25635
|
const anyFailed = results.some((r) => !r.ok);
|
|
25472
25636
|
console.log("");
|
|
25473
|
-
console.log(` ${C.
|
|
25474
|
-
console.log(` ${C.
|
|
25475
|
-
console.log(` ${C.
|
|
25637
|
+
console.log(` ${C.dim("Files ")} ${C.green(`${totalFilesPassed} passed`)}` + (totalFilesFailed ? ` ${C.dim("|")} ${C.red(`${totalFilesFailed} failed`)}` : "") + C.dim(` (${totalFiles})`));
|
|
25638
|
+
console.log(` ${C.dim("Tests ")} ${C.green(`${totalPassed} passed`)}` + (totalFailed ? ` ${C.dim("|")} ${C.red(`${totalFailed} failed`)}` : "") + C.dim(` (${totalTests})`));
|
|
25639
|
+
console.log(` ${C.dim(" Time ")} ${format_duration(elapsed)}`);
|
|
25476
25640
|
console.log("");
|
|
25477
25641
|
return !anyFailed;
|
|
25478
25642
|
}
|
|
@@ -25535,8 +25699,7 @@ function collect_nm_files(folder) {
|
|
|
25535
25699
|
}
|
|
25536
25700
|
return out;
|
|
25537
25701
|
}
|
|
25538
|
-
|
|
25539
|
-
console.log("\n~ NOMEN ~\n");
|
|
25702
|
+
console.error("\n~ NOMEN ~\n");
|
|
25540
25703
|
const args = parse_args();
|
|
25541
25704
|
const command = args.command;
|
|
25542
25705
|
try {
|
|
@@ -25596,7 +25759,6 @@ try {
|
|
|
25596
25759
|
}
|
|
25597
25760
|
args.in = args.in ?? resolve_input();
|
|
25598
25761
|
if (!args.in) process.exit(0);
|
|
25599
|
-
if (!build_root) build_root = fs.lstatSync(args.in).isDirectory() ? args.in : path.dirname(args.in);
|
|
25600
25762
|
if (fs.existsSync(args.in)) {
|
|
25601
25763
|
let config = {
|
|
25602
25764
|
arch: "aarch64",
|
|
@@ -25626,19 +25788,13 @@ function resolve_input() {
|
|
|
25626
25788
|
if (fs.existsSync(config_path)) try {
|
|
25627
25789
|
const json = fs.readFileSync(config_path, "utf8").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
25628
25790
|
const parsed = JSON.parse(json);
|
|
25629
|
-
if (parsed.entry)
|
|
25630
|
-
build_root = cwd;
|
|
25631
|
-
return path.resolve(cwd, parsed.entry);
|
|
25632
|
-
}
|
|
25791
|
+
if (parsed.entry) return path.resolve(cwd, parsed.entry);
|
|
25633
25792
|
} catch {}
|
|
25634
25793
|
let nm_files = [];
|
|
25635
25794
|
try {
|
|
25636
25795
|
nm_files = fs.readdirSync(cwd).filter((f) => f.endsWith(".nm"));
|
|
25637
25796
|
} catch {}
|
|
25638
|
-
if (nm_files.length === 1)
|
|
25639
|
-
build_root = cwd;
|
|
25640
|
-
return path.resolve(cwd, nm_files[0]);
|
|
25641
|
-
}
|
|
25797
|
+
if (nm_files.length === 1) return path.resolve(cwd, nm_files[0]);
|
|
25642
25798
|
if (nm_files.length > 1) {
|
|
25643
25799
|
console.log(`Found ${nm_files.length} .nm files in ${cwd}. Specify which to run with --in:\n ` + nm_files.join("\n "));
|
|
25644
25800
|
return;
|
|
@@ -25731,7 +25887,7 @@ function processFile(filename, config, mode) {
|
|
|
25731
25887
|
return;
|
|
25732
25888
|
}
|
|
25733
25889
|
const basename = path.basename(filename, ".nm");
|
|
25734
|
-
const buildDir =
|
|
25890
|
+
const buildDir = build_dir_for(resolved_path, filename.endsWith(".test.nm"));
|
|
25735
25891
|
if (!fs.existsSync(buildDir)) fs.mkdirSync(buildDir, { recursive: true });
|
|
25736
25892
|
const ext = arch === "aarch64" ? ".s" : platform === "macos" || platform === "ios" ? ".m" : ".c";
|
|
25737
25893
|
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,12 +67,7 @@ function collect_nm_files(folder: string): string[] {
|
|
|
66
67
|
return out;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
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
|
-
console.log("\n~ NOMEN ~\n");
|
|
70
|
+
console.error("\n~ NOMEN ~\n");
|
|
75
71
|
|
|
76
72
|
const args: Args = parse_args();
|
|
77
73
|
const command = args.command;
|
|
@@ -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
|
@@ -16,24 +16,24 @@ function package_jsonc(name: string): string {
|
|
|
16
16
|
"entry": "src/main.nm"
|
|
17
17
|
// The System library is resolved automatically from your nomen-lang
|
|
18
18
|
// install. To pin a local checkout instead, uncomment:
|
|
19
|
-
// "imports": { "System": "
|
|
19
|
+
// "imports": { "System": "~/Source/nomen/core" }
|
|
20
20
|
}
|
|
21
21
|
`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const MAIN_NM = `import System
|
|
25
25
|
|
|
26
|
+
pub func add = (int a, int b) => a + b
|
|
27
|
+
|
|
26
28
|
pub func main = (Init init) {
|
|
27
|
-
Console.write("Hello world!\\n")
|
|
29
|
+
Console.write("Hello world, 2 + 2 is \\{add(2, 2)}!\\n")
|
|
28
30
|
}
|
|
29
31
|
`;
|
|
30
32
|
|
|
31
33
|
const TEST_NM = `import System
|
|
32
34
|
import System/Test
|
|
33
35
|
|
|
34
|
-
func
|
|
35
|
-
|
|
36
|
-
pub func test_smoke = (ref Tester t) {
|
|
36
|
+
pub func test_add = (ref Tester t) {
|
|
37
37
|
t.expect(add(2, 2) == 4, "2 + 2 should equal 4")
|
|
38
38
|
}
|
|
39
39
|
`;
|
|
@@ -113,7 +113,7 @@ export function run_init(name: string | undefined): void {
|
|
|
113
113
|
fs.writeFileSync(path.join(target, ".gitignore"), GITIGNORE);
|
|
114
114
|
fs.writeFileSync(path.join(target, "package.jsonc"), package_jsonc(name));
|
|
115
115
|
fs.writeFileSync(path.join(src, "main.nm"), MAIN_NM);
|
|
116
|
-
fs.writeFileSync(path.join(test, "
|
|
116
|
+
fs.writeFileSync(path.join(test, "main.test.nm"), TEST_NM);
|
|
117
117
|
fs.writeFileSync(path.join(target, "README.md"), readme(name));
|
|
118
118
|
fs.writeFileSync(path.join(target, "AGENTS.md"), fs.readFileSync(agents_template));
|
|
119
119
|
|
|
@@ -121,7 +121,7 @@ export function run_init(name: string | undefined): void {
|
|
|
121
121
|
console.log(` ${name}/.gitignore`);
|
|
122
122
|
console.log(` ${name}/package.jsonc`);
|
|
123
123
|
console.log(` ${name}/src/main.nm`);
|
|
124
|
-
console.log(` ${name}/test/
|
|
124
|
+
console.log(` ${name}/test/main.test.nm`);
|
|
125
125
|
console.log(` ${name}/README.md`);
|
|
126
126
|
console.log(` ${name}/AGENTS.md`);
|
|
127
127
|
console.log(`\nNext: cd ${name} && nomen run`);
|
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))}`,
|
|
@@ -434,7 +435,8 @@ export function runTests(root: string, options: RunTestsOptions = {}): boolean {
|
|
|
434
435
|
const lib = resolve_lib_for(root);
|
|
435
436
|
const files = collect_test_files(root).filter((f) => !options.filter || options.filter.test(f));
|
|
436
437
|
|
|
437
|
-
console.log(
|
|
438
|
+
console.log(`Found ${files.length} test file(s)\n`);
|
|
439
|
+
|
|
438
440
|
const startTime = performance.now();
|
|
439
441
|
|
|
440
442
|
const results: TestFileResult[] = [];
|
|
@@ -446,6 +448,8 @@ export function runTests(root: string, options: RunTestsOptions = {}): boolean {
|
|
|
446
448
|
|
|
447
449
|
const elapsed = performance.now() - startTime;
|
|
448
450
|
const totalFiles = results.length;
|
|
451
|
+
const totalFilesFailed = results.reduce((a, r) => a + (r.ok ? 0 : 1), 0);
|
|
452
|
+
const totalFilesPassed = totalFiles - totalFilesFailed;
|
|
449
453
|
// `tests[].failed` (from each test's `done` record) is the authoritative
|
|
450
454
|
// failure count; `result.fails` holds the same failures' messages for
|
|
451
455
|
// display, so don't sum both or failures are double-counted.
|
|
@@ -455,15 +459,20 @@ export function runTests(root: string, options: RunTestsOptions = {}): boolean {
|
|
|
455
459
|
const anyFailed = results.some((r) => !r.ok);
|
|
456
460
|
|
|
457
461
|
console.log("");
|
|
462
|
+
//console.log(
|
|
463
|
+
// ` ${C.dim("Files ")} ${totalFiles} ${anyFailed ? C.red("failed") : C.green("passed")} (${totalFiles})`,
|
|
464
|
+
//);
|
|
458
465
|
console.log(
|
|
459
|
-
` ${C.
|
|
466
|
+
` ${C.dim("Files ")} ${C.green(`${totalFilesPassed} passed`)}` +
|
|
467
|
+
(totalFilesFailed ? ` ${C.dim("|")} ${C.red(`${totalFilesFailed} failed`)}` : "") +
|
|
468
|
+
C.dim(` (${totalFiles})`),
|
|
460
469
|
);
|
|
461
470
|
console.log(
|
|
462
|
-
` ${C.
|
|
463
|
-
(totalFailed ? ` | ${C.red(`${totalFailed} failed`)}` : "") +
|
|
464
|
-
` (${totalTests})
|
|
471
|
+
` ${C.dim("Tests ")} ${C.green(`${totalPassed} passed`)}` +
|
|
472
|
+
(totalFailed ? ` ${C.dim("|")} ${C.red(`${totalFailed} failed`)}` : "") +
|
|
473
|
+
C.dim(` (${totalTests})`),
|
|
465
474
|
);
|
|
466
|
-
console.log(` ${C.
|
|
475
|
+
console.log(` ${C.dim(" Time ")} ${format_duration(elapsed)}`);
|
|
467
476
|
console.log("");
|
|
468
477
|
|
|
469
478
|
return !anyFailed;
|