nomen-lang 0.0.5 → 0.0.7
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 +18650 -9439
- package/package.json +4 -7
- package/src/bench_loop.nm +113 -0
- package/src/docs.ts +267 -0
- package/src/format_errors.ts +23 -9
- package/src/index.ts +163 -25
- package/src/test.ts +518 -0
- package/test/fixtures/build/calc.test +0 -0
- package/test/fixtures/build/calc.test.c +969 -0
- package/test/fixtures/build/main.h +170 -0
- package/test/fixtures/calc.test.nm +32 -0
- package/vite.config.ts +7 -0
- package/dist/index.d.mts +0 -1
package/src/index.ts
CHANGED
|
@@ -8,14 +8,64 @@ import { hideBin } from "yargs/helpers";
|
|
|
8
8
|
import yargs from "yargs/yargs";
|
|
9
9
|
|
|
10
10
|
import build, { default_platform } from "../../src/build.ts";
|
|
11
|
+
import { format_source, type FormatOptions } from "../../src/format.ts";
|
|
11
12
|
import join from "../../src/join.ts";
|
|
12
13
|
import { get_library } from "../../src/lib.ts";
|
|
13
14
|
import parse from "../../src/parse.ts";
|
|
14
|
-
import
|
|
15
|
+
import { run_docs } from "./docs.ts";
|
|
16
|
+
import render_errors, { render_warnings } from "./format_errors.ts";
|
|
17
|
+
import { runTests } from "./test.ts";
|
|
15
18
|
import type Config from "./types/Config.ts";
|
|
16
19
|
|
|
17
20
|
const SUPPORTED_EXTENSION = ".nm";
|
|
18
21
|
|
|
22
|
+
type Mode = "check" | "build" | "run";
|
|
23
|
+
|
|
24
|
+
// Strip `//` line and `/* */` block comments so a .jsonc file parses as JSON.
|
|
25
|
+
function parse_jsonc(text: string): any {
|
|
26
|
+
return JSON.parse(text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Read the `format` options from the nearest package.jsonc above `start`. */
|
|
30
|
+
function load_format_options(start: string): Partial<FormatOptions> {
|
|
31
|
+
let dir = fs.lstatSync(start).isDirectory() ? start : path.dirname(start);
|
|
32
|
+
for (let i = 0; i < 20; i++) {
|
|
33
|
+
const config_path = path.join(dir, "package.jsonc");
|
|
34
|
+
if (fs.existsSync(config_path)) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = parse_jsonc(fs.readFileSync(config_path, "utf8"));
|
|
37
|
+
if (parsed.format) return parsed.format as Partial<FormatOptions>;
|
|
38
|
+
} catch {
|
|
39
|
+
// ignore malformed package.jsonc and keep searching
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const lib_config = path.join(dir, "core", "package.jsonc");
|
|
43
|
+
if (fs.existsSync(lib_config)) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = parse_jsonc(fs.readFileSync(lib_config, "utf8"));
|
|
46
|
+
if (parsed.format) return parsed.format as Partial<FormatOptions>;
|
|
47
|
+
} catch {
|
|
48
|
+
// ignore
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const parent = path.dirname(dir);
|
|
52
|
+
if (parent === dir) break;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Recursively collect every `.nm` file under `folder`. */
|
|
59
|
+
function collect_nm_files(folder: string): string[] {
|
|
60
|
+
const out: string[] = [];
|
|
61
|
+
for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
|
|
62
|
+
const full = path.join(folder, entry.name);
|
|
63
|
+
if (entry.isDirectory()) out.push(...collect_nm_files(full));
|
|
64
|
+
else if (shouldProcessFile(entry.name)) out.push(full);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
19
69
|
// The folder whose `build/` subdirectory receives compiler output. Set during
|
|
20
70
|
// input resolution: the --in folder, the .nm file's folder, or — for
|
|
21
71
|
// package.jsonc discovery — the package folder (cwd), not the entry's folder.
|
|
@@ -23,8 +73,21 @@ let build_root: string | undefined;
|
|
|
23
73
|
|
|
24
74
|
console.log("\n~ NOMEN ~\n");
|
|
25
75
|
|
|
26
|
-
const
|
|
27
|
-
.usage(
|
|
76
|
+
const parser = yargs(hideBin(process.argv))
|
|
77
|
+
.usage(
|
|
78
|
+
"Usage:\n" +
|
|
79
|
+
" nomen run --in [file/folder] Parse, check, build and run a program\n" +
|
|
80
|
+
" nomen build --in [file/folder] Parse, check and build (no run)\n" +
|
|
81
|
+
" nomen check --in [file/folder] Parse and check only\n" +
|
|
82
|
+
" nomen format [--in folder] Reformat every .nm file\n" +
|
|
83
|
+
" nomen docs [--in file] Generate markdown documentation",
|
|
84
|
+
)
|
|
85
|
+
.command("run", "Parse, check, build and run a program")
|
|
86
|
+
.command("build", "Parse, check and build (compile and link, but do not run)")
|
|
87
|
+
.command("check", "Parse and check only")
|
|
88
|
+
.command("format", "Reformat every .nm file")
|
|
89
|
+
.command("docs", "Generate markdown documentation")
|
|
90
|
+
.command("test", "Discover and run *.test.nm files with the Tester harness")
|
|
28
91
|
.option("in", {
|
|
29
92
|
alias: "i",
|
|
30
93
|
describe: "Input file or folder",
|
|
@@ -45,6 +108,11 @@ const options = yargs(hideBin(process.argv))
|
|
|
45
108
|
describe: "Whether to watch for file changes",
|
|
46
109
|
type: "boolean",
|
|
47
110
|
})
|
|
111
|
+
.option("filter", {
|
|
112
|
+
alias: "f",
|
|
113
|
+
describe: "Only run test files whose path matches this regex",
|
|
114
|
+
type: "string",
|
|
115
|
+
})
|
|
48
116
|
.option("arch", {
|
|
49
117
|
alias: "a",
|
|
50
118
|
describe: "Target architecture (aarch64 or c)",
|
|
@@ -69,10 +137,69 @@ const options = yargs(hideBin(process.argv))
|
|
|
69
137
|
describe: "Path to audit_runtime.c, linked in when --audit is set",
|
|
70
138
|
type: "string",
|
|
71
139
|
})
|
|
72
|
-
.
|
|
73
|
-
|
|
140
|
+
.option("check", {
|
|
141
|
+
describe: "For `nomen format`: report files that would change without writing them",
|
|
142
|
+
type: "boolean",
|
|
143
|
+
})
|
|
144
|
+
.help(true);
|
|
145
|
+
|
|
146
|
+
const options = parser.parseSync();
|
|
147
|
+
|
|
148
|
+
const command = options._[0];
|
|
74
149
|
|
|
75
150
|
try {
|
|
151
|
+
// `nomen docs` generates markdown documentation instead of compiling.
|
|
152
|
+
if (command === "docs") {
|
|
153
|
+
run_docs(typeof options.in === "string" ? options.in : undefined);
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// `nomen test` discovers and runs every `*.test.nm` file under --in (or
|
|
158
|
+
// the cwd), compiling each into a Tester harness and reporting results.
|
|
159
|
+
if (command === "test") {
|
|
160
|
+
const root = options.in ?? process.cwd();
|
|
161
|
+
const filter = typeof options.filter === "string" ? new RegExp(options.filter) : undefined;
|
|
162
|
+
const arch = (options.arch as string | undefined) ?? "aarch64";
|
|
163
|
+
const ok = runTests(root, { arch, filter });
|
|
164
|
+
process.exit(ok ? 0 : 1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// `nomen format` re-indents and tidies every .nm file under a folder.
|
|
168
|
+
if (command === "format") {
|
|
169
|
+
const root = options.in ?? process.cwd();
|
|
170
|
+
const format_options = load_format_options(root);
|
|
171
|
+
const files = collect_nm_files(root);
|
|
172
|
+
let changed = 0;
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const source = fs.readFileSync(file, "utf8");
|
|
175
|
+
const result = format_source(source, format_options);
|
|
176
|
+
if (result.unsafe) {
|
|
177
|
+
console.log(`Skipped ${file}: ${result.unsafe}`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (result.changed) {
|
|
181
|
+
if (!options.check) fs.writeFileSync(file, result.code);
|
|
182
|
+
changed += 1;
|
|
183
|
+
console.log(`Formatted ${file}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log(`\nFormatted ${changed} of ${files.length} file(s).`);
|
|
187
|
+
process.exit(0);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// `run`, `build` and `check` all start from parsed + checked source; `run`
|
|
191
|
+
// also links and executes, `build` stops after linking, `check` stops after
|
|
192
|
+
// checking. An unknown (or missing) command prints the help instead.
|
|
193
|
+
let mode: Mode | undefined;
|
|
194
|
+
if (command === "run") mode = "run";
|
|
195
|
+
else if (command === "build") mode = "build";
|
|
196
|
+
else if (command === "check") mode = "check";
|
|
197
|
+
|
|
198
|
+
if (!mode) {
|
|
199
|
+
parser.showHelp("log");
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
76
203
|
// An explicit --in wins; otherwise discover what to compile from the
|
|
77
204
|
// working folder — a package.jsonc `entry`, or a lone .nm file.
|
|
78
205
|
options.in = options.in ?? resolve_input();
|
|
@@ -100,24 +227,21 @@ try {
|
|
|
100
227
|
|
|
101
228
|
// Is the --in path a folder
|
|
102
229
|
if (fs.lstatSync(options.in).isDirectory()) {
|
|
103
|
-
// Loop through files in the folder
|
|
104
|
-
//processFolder(options.in);
|
|
105
230
|
if (options.watch) {
|
|
106
|
-
watchPath(options.in, config);
|
|
231
|
+
watchPath(options.in, config, mode);
|
|
107
232
|
} else {
|
|
108
|
-
processFolder(options.in, config);
|
|
233
|
+
processFolder(options.in, config, mode);
|
|
109
234
|
}
|
|
110
235
|
} else {
|
|
111
236
|
// Process the supplied file
|
|
112
237
|
const extname = path.extname(options.in);
|
|
113
238
|
if (shouldProcessFile(options.in)) {
|
|
114
|
-
//processFile(options.in);
|
|
115
239
|
// NOTE: We get add notifications for all watched files immediately
|
|
116
240
|
// TODO: Is this the case on Windows etc too?
|
|
117
241
|
if (options.watch) {
|
|
118
|
-
watchPath(options.in, config);
|
|
242
|
+
watchPath(options.in, config, mode);
|
|
119
243
|
} else {
|
|
120
|
-
processFile(options.in, config);
|
|
244
|
+
processFile(options.in, config, mode);
|
|
121
245
|
}
|
|
122
246
|
} else {
|
|
123
247
|
console.log("Unsupported file type: " + extname);
|
|
@@ -227,20 +351,20 @@ function compile_audit_runtime(config: Config, input_path: string, buildDir: str
|
|
|
227
351
|
return audit_obj;
|
|
228
352
|
}
|
|
229
353
|
|
|
230
|
-
function watchPath(p: string, config: Config) {
|
|
354
|
+
function watchPath(p: string, config: Config, mode: Mode) {
|
|
231
355
|
chokidar.watch(p).on("all", (event, filePath) => {
|
|
232
356
|
if (shouldProcessFile(filePath)) {
|
|
233
|
-
processFile(filePath, config);
|
|
357
|
+
processFile(filePath, config, mode);
|
|
234
358
|
}
|
|
235
359
|
});
|
|
236
360
|
}
|
|
237
361
|
|
|
238
|
-
function processFolder(folder: string, config: Config) {
|
|
362
|
+
function processFolder(folder: string, config: Config, mode: Mode) {
|
|
239
363
|
const dir = fs.opendirSync(folder);
|
|
240
364
|
let dirent;
|
|
241
365
|
while ((dirent = dir.readSync()) !== null) {
|
|
242
366
|
if (shouldProcessFile(dirent.name)) {
|
|
243
|
-
processFile(path.join(folder, dirent.name), config);
|
|
367
|
+
processFile(path.join(folder, dirent.name), config, mode);
|
|
244
368
|
// @ts-ignore
|
|
245
369
|
let _ = fs.watch;
|
|
246
370
|
}
|
|
@@ -252,7 +376,7 @@ function shouldProcessFile(filename: string) {
|
|
|
252
376
|
return path.extname(filename) === SUPPORTED_EXTENSION;
|
|
253
377
|
}
|
|
254
378
|
|
|
255
|
-
function processFile(filename: string, config: Config) {
|
|
379
|
+
function processFile(filename: string, config: Config, mode: Mode) {
|
|
256
380
|
console.log("Processing", filename);
|
|
257
381
|
|
|
258
382
|
const arch = config.arch || "aarch64";
|
|
@@ -265,22 +389,28 @@ function processFile(filename: string, config: Config) {
|
|
|
265
389
|
|
|
266
390
|
let startTime = performance.now();
|
|
267
391
|
|
|
268
|
-
|
|
392
|
+
const resolved_path = path.resolve(filename);
|
|
393
|
+
const input = join(resolved_path, config.lib);
|
|
269
394
|
const library = config.lib ? get_library(config.lib) : undefined;
|
|
270
|
-
const parsed = parse(input, library);
|
|
271
|
-
// TODO: If verbose flag
|
|
272
|
-
// console.log("Parsed");
|
|
395
|
+
const parsed = parse(input, library, resolved_path);
|
|
273
396
|
|
|
274
397
|
let errors = parsed.errors;
|
|
275
|
-
const ok = !errors.length;
|
|
276
398
|
|
|
277
|
-
if (
|
|
399
|
+
if (errors.length) {
|
|
278
400
|
console.log(render_errors(input, errors));
|
|
279
401
|
return;
|
|
280
402
|
}
|
|
281
403
|
|
|
282
|
-
//
|
|
283
|
-
|
|
404
|
+
// Warnings come out of the parse/check phase, so every mode reports them.
|
|
405
|
+
if (parsed.warnings.length) console.log(render_warnings(input, parsed.warnings));
|
|
406
|
+
|
|
407
|
+
// `check` stops after parsing and checking — no building, linking or running.
|
|
408
|
+
if (mode === "check") {
|
|
409
|
+
const checkTime = performance.now();
|
|
410
|
+
console.log(`Checked in ${(checkTime - startTime).toFixed(2)}ms`);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
284
414
|
const result = build(parsed.root, { arch, platform, audit: config.audit });
|
|
285
415
|
|
|
286
416
|
if (result.errors && result.errors.length > 0) {
|
|
@@ -311,6 +441,7 @@ function processFile(filename: string, config: Config) {
|
|
|
311
441
|
console.log(`Created ${codefile} in ${(compileTime - startTime).toFixed(2)}ms`);
|
|
312
442
|
console.log("");
|
|
313
443
|
|
|
444
|
+
// `build` links the executable but does not run it; `run` links and runs.
|
|
314
445
|
startTime = performance.now();
|
|
315
446
|
|
|
316
447
|
const audit_obj = config.audit ? compile_audit_runtime(config, resolved, buildDir) : undefined;
|
|
@@ -322,6 +453,13 @@ function processFile(filename: string, config: Config) {
|
|
|
322
453
|
? " -framework CoreGraphics -framework Foundation -framework AppKit -lobjc"
|
|
323
454
|
: "";
|
|
324
455
|
execSync(`clang -o ${outfile} ${link_inputs}${framework_flags}`);
|
|
456
|
+
|
|
457
|
+
if (mode === "build") {
|
|
458
|
+
const buildTime = performance.now();
|
|
459
|
+
console.log(`Built ${outfile} in ${(buildTime - startTime).toFixed(2)}ms`);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
325
463
|
execSync(outfile, { stdio: "inherit" });
|
|
326
464
|
|
|
327
465
|
const runTime = performance.now();
|