nomen-lang 0.0.4 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nomen-lang",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "The CLI for the Nomen programming language.",
5
5
  "keywords": [],
6
6
  "license": "ISC",
@@ -19,16 +19,13 @@
19
19
  "go": "tsx src/index.ts",
20
20
  "register": "pnpm build && pnpm add -g ."
21
21
  },
22
- "dependencies": {
23
- "chokidar": "^5.0.0",
24
- "yargs": "^18.0.0"
25
- },
26
22
  "devDependencies": {
27
- "@types/node": "^26.1.1",
23
+ "@types/node": "^26.1.2",
28
24
  "@types/yargs": "^17.0.35",
25
+ "chokidar": "^5.0.0",
29
26
  "tsx": "^4.23.1",
30
27
  "typescript": "^7.0.2",
31
28
  "vite-plus": "catalog:",
32
- "vitest": "catalog:"
29
+ "yargs": "^18.1.0"
33
30
  }
34
31
  }
package/src/docs.ts ADDED
@@ -0,0 +1,267 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { get_library, resolve_export_files, type Library } from "../../src/lib.ts";
5
+ import type BaseNode from "../../src/nodes/BaseNode.ts";
6
+ import type BitsetNode from "../../src/nodes/BitsetNode.ts";
7
+ import type EnumNode from "../../src/nodes/EnumNode.ts";
8
+ import type FunctionNode from "../../src/nodes/FunctionNode.ts";
9
+ import type StructNode from "../../src/nodes/StructNode.ts";
10
+ import type TraitNode from "../../src/nodes/TraitNode.ts";
11
+ import type Type from "../../src/nodes/Type.ts";
12
+ import parse from "../../src/parse.ts";
13
+
14
+ interface DocItem {
15
+ kind: string;
16
+ name: string;
17
+ signature?: string;
18
+ doc?: string;
19
+ node: BaseNode;
20
+ }
21
+
22
+ /**
23
+ * `nomen docs`: parse every `.nm` file in scope, then write one markdown file per
24
+ * source file into `<root>/docs/`, mirroring the source tree. Emits a warning
25
+ * for each top-level `pub` item that has no documentation comment.
26
+ */
27
+ export function run_docs(explicit_in?: string): void {
28
+ const root = path.resolve(explicit_in || process.cwd());
29
+ const { files, library, docs_root } = gather_doc_files(root);
30
+ if (!files.length) {
31
+ console.log(`No .nm files found under ${root}`);
32
+ return;
33
+ }
34
+
35
+ let warnings = 0;
36
+ let written = 0;
37
+ for (const file of files) {
38
+ const text = fs.readFileSync(file, "utf8");
39
+ let parsed;
40
+ try {
41
+ parsed = parse(text, library, file);
42
+ } catch (err) {
43
+ const msg = err instanceof Error ? err.message : String(err);
44
+ console.log(` warning: could not parse ${path.relative(root, file)} (${msg})`);
45
+ continue;
46
+ }
47
+ const items = collect_items(parsed.root, text.length);
48
+ if (!items.length) continue;
49
+
50
+ const rel = path.relative(docs_root, file).replace(/\.nm$/, ".md");
51
+ const out_path = path.join(docs_root, "docs", rel);
52
+ const { markdown, warns } = render_file(file, items, text);
53
+ warnings += warns;
54
+ fs.mkdirSync(path.dirname(out_path), { recursive: true });
55
+ fs.writeFileSync(out_path, markdown);
56
+ written++;
57
+ }
58
+
59
+ console.log(
60
+ `Wrote ${written} doc file(s) to ${path.join(docs_root, "docs")}` +
61
+ (warnings ? ` with ${warnings} warning(s)` : ""),
62
+ );
63
+ }
64
+
65
+ // Decide which files to document. A package.jsonc with `exports` is a library
66
+ // (document every exported file); otherwise document the resolved input file
67
+ // alongside its module siblings.
68
+ function gather_doc_files(root: string): {
69
+ files: string[];
70
+ library: Library | undefined;
71
+ docs_root: string;
72
+ } {
73
+ const config_path = path.join(root, "package.jsonc");
74
+ if (fs.existsSync(config_path)) {
75
+ try {
76
+ const raw = fs.readFileSync(config_path, "utf8");
77
+ const json = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
78
+ const parsed = JSON.parse(json);
79
+ if (parsed.exports) {
80
+ let files: string[] = [];
81
+ for (const pattern of Object.values(parsed.exports) as string[]) {
82
+ files = files.concat(resolve_export_files(root, pattern));
83
+ }
84
+ return { files, library: get_library(root), docs_root: root };
85
+ }
86
+ } catch {
87
+ // fall through
88
+ }
89
+ }
90
+
91
+ // App / single-file mode: one module folder of siblings.
92
+ const dir = fs.existsSync(root) && fs.lstatSync(root).isDirectory() ? root : path.dirname(root);
93
+ let files: string[] = [];
94
+ try {
95
+ files = fs
96
+ .readdirSync(dir)
97
+ .filter((f) => f.endsWith(".nm"))
98
+ .map((f) => path.join(dir, f));
99
+ } catch {
100
+ // unreadable
101
+ }
102
+ return { files, library: resolve_library_for(dir), docs_root: dir };
103
+ }
104
+
105
+ function resolve_library_for(dir: string): Library | undefined {
106
+ let d = dir;
107
+ for (let i = 0; i < 20; i++) {
108
+ const config_path = path.join(d, "package.jsonc");
109
+ if (fs.existsSync(config_path)) {
110
+ try {
111
+ const raw = fs.readFileSync(config_path, "utf8");
112
+ const json = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
113
+ const parsed = JSON.parse(json);
114
+ if (parsed.exports) return get_library(d);
115
+ if (parsed.imports?.System) return get_library(path.resolve(d, parsed.imports.System));
116
+ } catch {
117
+ // ignore
118
+ }
119
+ }
120
+ const lib_config = path.join(d, "core", "package.jsonc");
121
+ if (fs.existsSync(lib_config)) return get_library(path.join(d, "core"));
122
+ const parent = path.dirname(d);
123
+ if (parent === d) break;
124
+ d = parent;
125
+ }
126
+ return undefined;
127
+ }
128
+
129
+ // Collect this file's own top-level pub declarations (those starting before the
130
+ // appended library source — i.e. defined in `text`, not pulled in for linking).
131
+ function collect_items(root_node: BaseNode, user_length: number): DocItem[] {
132
+ const items: DocItem[] = [];
133
+ const statements = (root_node as unknown as { statements: BaseNode[] }).statements;
134
+ for (const stmt of statements) {
135
+ if (stmt.start >= user_length) continue;
136
+ switch (stmt.node_type) {
137
+ case "struct": {
138
+ const s = stmt as unknown as StructNode;
139
+ if (s.visibility !== "pub") break;
140
+ items.push({
141
+ kind: s.is_class ? "class" : "struct",
142
+ name: s.name,
143
+ doc: s.doc,
144
+ node: s,
145
+ });
146
+ break;
147
+ }
148
+ case "trait": {
149
+ const t = stmt as unknown as TraitNode;
150
+ if (t.visibility !== "pub") break;
151
+ items.push({ kind: "trait", name: t.name, doc: t.doc, node: t });
152
+ break;
153
+ }
154
+ case "enum": {
155
+ const e = stmt as unknown as EnumNode;
156
+ if (e.visibility !== "pub") break;
157
+ items.push({ kind: "enum", name: e.name, doc: e.doc, node: e });
158
+ break;
159
+ }
160
+ case "bitset": {
161
+ const b = stmt as unknown as BitsetNode;
162
+ if (b.visibility !== "pub") break;
163
+ items.push({ kind: "bitset", name: b.name, doc: b.doc, node: b });
164
+ break;
165
+ }
166
+ case "func": {
167
+ const f = stmt as unknown as FunctionNode;
168
+ if (f.visibility !== "pub") break;
169
+ items.push({
170
+ kind: "func",
171
+ name: f.name,
172
+ signature: signature_of(f),
173
+ doc: f.doc,
174
+ node: f,
175
+ });
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ return items;
181
+ }
182
+
183
+ function render_file(
184
+ file: string,
185
+ items: DocItem[],
186
+ source: string,
187
+ ): { markdown: string; warns: number } {
188
+ const lines: string[] = [];
189
+ const title = path.basename(file, ".nm");
190
+ lines.push(`# ${title}`);
191
+ lines.push("");
192
+
193
+ let warns = 0;
194
+ for (const item of items) {
195
+ const where = `${path.basename(file)}:${line_of(source, item.node.start)}`;
196
+ lines.push(`## \`${heading_of(item)}\``);
197
+ lines.push("");
198
+ if (item.doc) {
199
+ lines.push(item.doc);
200
+ } else {
201
+ lines.push(`_No documentation._`);
202
+ console.log(` warning: ${item.kind} \`${item.name}\` has no doc comment (${where})`);
203
+ warns++;
204
+ }
205
+ lines.push("");
206
+
207
+ // Struct/trait: list pub members.
208
+ const members = members_of(item.node);
209
+ if (members.length) {
210
+ lines.push("**Members:**");
211
+ lines.push("");
212
+ for (const m of members) {
213
+ const sig = signature_of(m);
214
+ const doc = m.doc ? ` — ${m.doc.split("\n")[0]}` : "";
215
+ lines.push(`- \`${sig}\`${doc}`);
216
+ }
217
+ lines.push("");
218
+ }
219
+ }
220
+ return { markdown: lines.join("\n"), warns };
221
+ }
222
+
223
+ function heading_of(item: DocItem): string {
224
+ if (item.kind === "func") return `func ${item.name}${item.signature ?? ""}`;
225
+ const generic = generic_params_of(item.node);
226
+ return `${item.kind} ${item.name}${generic}`;
227
+ }
228
+
229
+ // Render a function signature like `name(type name, …) -> Ret`, omitting the
230
+ // implicit `self` parameter of methods.
231
+ function signature_of(fn: FunctionNode): string {
232
+ const params = fn.params
233
+ .filter((p) => !p.is_self_param)
234
+ .map((p) => `${render_type(p.type)}${p.name ? " " + p.name : ""}`);
235
+ let sig = `${fn.name}(${params.join(", ")})`;
236
+ if (fn.return_type?.name) sig += ` -> ${render_type(fn.return_type)}`;
237
+ return sig;
238
+ }
239
+
240
+ function members_of(node: BaseNode): FunctionNode[] {
241
+ if (node.node_type === "struct" || node.node_type === "trait") {
242
+ const n = node as unknown as { functions: FunctionNode[] };
243
+ return n.functions.filter((f) => f.visibility === "pub" && !f.name.startsWith("#"));
244
+ }
245
+ return [];
246
+ }
247
+
248
+ function generic_params_of(node: BaseNode): string {
249
+ const n = node as unknown as { type_params?: string[] };
250
+ if (n.type_params && n.type_params.length) return `<${n.type_params.join(", ")}>`;
251
+ return "";
252
+ }
253
+
254
+ function render_type(t: Type): string {
255
+ let s = t.name;
256
+ if (t.type_args?.length) s += `<${t.type_args.map(render_type).join(", ")}>`;
257
+ if (t.is_ref) s = `ref ${s}`;
258
+ if (t.is_view) s = `view ${s}`;
259
+ if (t.is_array) s += "[]";
260
+ return s;
261
+ }
262
+
263
+ function line_of(source: string, start: number): number {
264
+ let line = 1;
265
+ for (let i = 0; i < start && i < source.length; i++) if (source[i] === "\n") line++;
266
+ return line;
267
+ }
@@ -47,32 +47,46 @@ function visual_start(line_text: string, col: number): number {
47
47
  }
48
48
 
49
49
  export default function render_errors(source: string, errors: CompileError[]): string {
50
+ return render_messages(source, errors, "Error");
51
+ }
52
+
53
+ /** Render `messages` with a `Warning:`/`Error:` label and matching summary. */
54
+ export function render_warnings(source: string, warnings: CompileError[]): string {
55
+ return render_messages(source, warnings, "Warning");
56
+ }
57
+
58
+ function render_messages(
59
+ source: string,
60
+ messages: CompileError[],
61
+ severity: "Error" | "Warning",
62
+ ): string {
63
+ if (!messages.length) return "";
50
64
  const lines = source.split("\n");
51
65
  const markers = find_file_markers(source);
52
66
 
53
67
  const blocks: string[] = [];
54
- for (const error of errors) {
55
- const line_text = lines[error.line - 1] ?? "";
68
+ for (const message of messages) {
69
+ const line_text = lines[message.line - 1] ?? "";
56
70
 
57
71
  // Map the joined-source line back to the originating file + in-file line.
58
72
  let rel_path = "";
59
- let file_line = error.line;
73
+ let file_line = message.line;
60
74
  for (let m = markers.length - 1; m >= 0; m--) {
61
- if (markers[m].joined_line < error.line) {
62
- file_line = error.line - markers[m].joined_line;
75
+ if (markers[m].joined_line < message.line) {
76
+ file_line = message.line - markers[m].joined_line;
63
77
  rel_path = path.relative(process.cwd(), markers[m].abs_path) || markers[m].abs_path;
64
78
  break;
65
79
  }
66
80
  }
67
81
 
68
82
  const gutter = " ".repeat(String(file_line).length);
69
- const col = Math.max(1, error.column);
83
+ const col = Math.max(1, message.column);
70
84
  const display_line = line_text.replace(/\t/g, " ".repeat(TAB_WIDTH));
71
85
  const squiggle = "~".repeat(token_width(line_text, col - 1));
72
86
 
73
87
  blocks.push(
74
88
  [
75
- `Error: ${error.message}`,
89
+ `${severity}: ${message.message}`,
76
90
  ` File: ${rel_path}:${file_line}:${col}`,
77
91
  `${gutter} |`,
78
92
  `${file_line} | ${display_line}`,
@@ -81,6 +95,6 @@ export default function render_errors(source: string, errors: CompileError[]): s
81
95
  );
82
96
  }
83
97
 
84
- const label = errors.length === 1 ? "error" : "errors";
85
- return `\n${blocks.join("\n\n")}\n\n${errors.length} ${label} found.\n`;
98
+ const label = messages.length === 1 ? severity.toLowerCase() : `${severity.toLowerCase()}s`;
99
+ return `\n${blocks.join("\n\n")}\n\n${messages.length} ${label} found.\n`;
86
100
  }
package/src/index.ts CHANGED
@@ -8,18 +8,84 @@ 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 render_errors from "./format_errors.ts";
15
+ import { run_docs } from "./docs.ts";
16
+ import render_errors, { render_warnings } from "./format_errors.ts";
15
17
  import type Config from "./types/Config.ts";
16
18
 
17
19
  const SUPPORTED_EXTENSION = ".nm";
18
20
 
21
+ type Mode = "check" | "build" | "run";
22
+
23
+ // Strip `//` line and `/* */` block comments so a .jsonc file parses as JSON.
24
+ function parse_jsonc(text: string): any {
25
+ return JSON.parse(text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""));
26
+ }
27
+
28
+ /** Read the `format` options from the nearest package.jsonc above `start`. */
29
+ function load_format_options(start: string): Partial<FormatOptions> {
30
+ let dir = fs.lstatSync(start).isDirectory() ? start : path.dirname(start);
31
+ for (let i = 0; i < 20; i++) {
32
+ const config_path = path.join(dir, "package.jsonc");
33
+ if (fs.existsSync(config_path)) {
34
+ try {
35
+ const parsed = parse_jsonc(fs.readFileSync(config_path, "utf8"));
36
+ if (parsed.format) return parsed.format as Partial<FormatOptions>;
37
+ } catch {
38
+ // ignore malformed package.jsonc and keep searching
39
+ }
40
+ }
41
+ const lib_config = path.join(dir, "core", "package.jsonc");
42
+ if (fs.existsSync(lib_config)) {
43
+ try {
44
+ const parsed = parse_jsonc(fs.readFileSync(lib_config, "utf8"));
45
+ if (parsed.format) return parsed.format as Partial<FormatOptions>;
46
+ } catch {
47
+ // ignore
48
+ }
49
+ }
50
+ const parent = path.dirname(dir);
51
+ if (parent === dir) break;
52
+ dir = parent;
53
+ }
54
+ return {};
55
+ }
56
+
57
+ /** Recursively collect every `.nm` file under `folder`. */
58
+ function collect_nm_files(folder: string): string[] {
59
+ const out: string[] = [];
60
+ for (const entry of fs.readdirSync(folder, { withFileTypes: true })) {
61
+ const full = path.join(folder, entry.name);
62
+ if (entry.isDirectory()) out.push(...collect_nm_files(full));
63
+ else if (shouldProcessFile(entry.name)) out.push(full);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ // The folder whose `build/` subdirectory receives compiler output. Set during
69
+ // input resolution: the --in folder, the .nm file's folder, or — for
70
+ // package.jsonc discovery — the package folder (cwd), not the entry's folder.
71
+ let build_root: string | undefined;
72
+
19
73
  console.log("\n~ NOMEN ~\n");
20
74
 
21
- const options = yargs(hideBin(process.argv))
22
- .usage("Usage: nomen --in [file/folder]")
75
+ const parser = yargs(hideBin(process.argv))
76
+ .usage(
77
+ "Usage:\n" +
78
+ " nomen run --in [file/folder] Parse, check, build and run a program\n" +
79
+ " nomen build --in [file/folder] Parse, check and build (no run)\n" +
80
+ " nomen check --in [file/folder] Parse and check only\n" +
81
+ " nomen format [--in folder] Reformat every .nm file\n" +
82
+ " nomen docs [--in file] Generate markdown documentation",
83
+ )
84
+ .command("run", "Parse, check, build and run a program")
85
+ .command("build", "Parse, check and build (compile and link, but do not run)")
86
+ .command("check", "Parse and check only")
87
+ .command("format", "Reformat every .nm file")
88
+ .command("docs", "Generate markdown documentation")
23
89
  .option("in", {
24
90
  alias: "i",
25
91
  describe: "Input file or folder",
@@ -64,16 +130,70 @@ const options = yargs(hideBin(process.argv))
64
130
  describe: "Path to audit_runtime.c, linked in when --audit is set",
65
131
  type: "string",
66
132
  })
67
- .help(true)
68
- .parseSync();
133
+ .option("check", {
134
+ describe: "For `nomen format`: report files that would change without writing them",
135
+ type: "boolean",
136
+ })
137
+ .help(true);
138
+
139
+ const options = parser.parseSync();
140
+
141
+ const command = options._[0];
69
142
 
70
143
  try {
144
+ // `nomen docs` generates markdown documentation instead of compiling.
145
+ if (command === "docs") {
146
+ run_docs(typeof options.in === "string" ? options.in : undefined);
147
+ process.exit(0);
148
+ }
149
+
150
+ // `nomen format` re-indents and tidies every .nm file under a folder.
151
+ if (command === "format") {
152
+ const root = options.in ?? process.cwd();
153
+ const format_options = load_format_options(root);
154
+ const files = collect_nm_files(root);
155
+ let changed = 0;
156
+ for (const file of files) {
157
+ const source = fs.readFileSync(file, "utf8");
158
+ const result = format_source(source, format_options);
159
+ if (result.unsafe) {
160
+ console.log(`Skipped ${file}: ${result.unsafe}`);
161
+ continue;
162
+ }
163
+ if (result.changed) {
164
+ if (!options.check) fs.writeFileSync(file, result.code);
165
+ changed += 1;
166
+ console.log(`Formatted ${file}`);
167
+ }
168
+ }
169
+ console.log(`\nFormatted ${changed} of ${files.length} file(s).`);
170
+ process.exit(0);
171
+ }
172
+
173
+ // `run`, `build` and `check` all start from parsed + checked source; `run`
174
+ // also links and executes, `build` stops after linking, `check` stops after
175
+ // checking. An unknown (or missing) command prints the help instead.
176
+ let mode: Mode | undefined;
177
+ if (command === "run") mode = "run";
178
+ else if (command === "build") mode = "build";
179
+ else if (command === "check") mode = "check";
180
+
181
+ if (!mode) {
182
+ parser.showHelp("log");
183
+ process.exit(1);
184
+ }
185
+
71
186
  // An explicit --in wins; otherwise discover what to compile from the
72
187
  // working folder — a package.jsonc `entry`, or a lone .nm file.
73
188
  options.in = options.in ?? resolve_input();
74
189
  if (!options.in) {
75
190
  process.exit(0);
76
191
  }
192
+ // For an explicit --in, build next to whatever was passed (the folder
193
+ // itself, or the file's folder). Discovery cases set build_root themselves.
194
+ if (!build_root) {
195
+ build_root = fs.lstatSync(options.in).isDirectory() ? options.in : path.dirname(options.in);
196
+ }
77
197
 
78
198
  if (fs.existsSync(options.in)) {
79
199
  let config: Config = { arch: "aarch64", platform: default_platform() };
@@ -90,24 +210,21 @@ try {
90
210
 
91
211
  // Is the --in path a folder
92
212
  if (fs.lstatSync(options.in).isDirectory()) {
93
- // Loop through files in the folder
94
- //processFolder(options.in);
95
213
  if (options.watch) {
96
- watchPath(options.in, config);
214
+ watchPath(options.in, config, mode);
97
215
  } else {
98
- processFolder(options.in, config);
216
+ processFolder(options.in, config, mode);
99
217
  }
100
218
  } else {
101
219
  // Process the supplied file
102
220
  const extname = path.extname(options.in);
103
221
  if (shouldProcessFile(options.in)) {
104
- //processFile(options.in);
105
222
  // NOTE: We get add notifications for all watched files immediately
106
223
  // TODO: Is this the case on Windows etc too?
107
224
  if (options.watch) {
108
- watchPath(options.in, config);
225
+ watchPath(options.in, config, mode);
109
226
  } else {
110
- processFile(options.in, config);
227
+ processFile(options.in, config, mode);
111
228
  }
112
229
  } else {
113
230
  console.log("Unsupported file type: " + extname);
@@ -131,6 +248,7 @@ function resolve_input(): string | undefined {
131
248
  const json = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
132
249
  const parsed = JSON.parse(json);
133
250
  if (parsed.entry) {
251
+ build_root = cwd;
134
252
  return path.resolve(cwd, parsed.entry);
135
253
  }
136
254
  } catch {
@@ -146,6 +264,7 @@ function resolve_input(): string | undefined {
146
264
  // unreadable working folder
147
265
  }
148
266
  if (nm_files.length === 1) {
267
+ build_root = cwd;
149
268
  return path.resolve(cwd, nm_files[0]);
150
269
  }
151
270
  if (nm_files.length > 1) {
@@ -215,20 +334,20 @@ function compile_audit_runtime(config: Config, input_path: string, buildDir: str
215
334
  return audit_obj;
216
335
  }
217
336
 
218
- function watchPath(p: string, config: Config) {
337
+ function watchPath(p: string, config: Config, mode: Mode) {
219
338
  chokidar.watch(p).on("all", (event, filePath) => {
220
339
  if (shouldProcessFile(filePath)) {
221
- processFile(filePath, config);
340
+ processFile(filePath, config, mode);
222
341
  }
223
342
  });
224
343
  }
225
344
 
226
- function processFolder(folder: string, config: Config) {
345
+ function processFolder(folder: string, config: Config, mode: Mode) {
227
346
  const dir = fs.opendirSync(folder);
228
347
  let dirent;
229
348
  while ((dirent = dir.readSync()) !== null) {
230
349
  if (shouldProcessFile(dirent.name)) {
231
- processFile(path.join(folder, dirent.name), config);
350
+ processFile(path.join(folder, dirent.name), config, mode);
232
351
  // @ts-ignore
233
352
  let _ = fs.watch;
234
353
  }
@@ -240,7 +359,7 @@ function shouldProcessFile(filename: string) {
240
359
  return path.extname(filename) === SUPPORTED_EXTENSION;
241
360
  }
242
361
 
243
- function processFile(filename: string, config: Config) {
362
+ function processFile(filename: string, config: Config, mode: Mode) {
244
363
  console.log("Processing", filename);
245
364
 
246
365
  const arch = config.arch || "aarch64";
@@ -253,22 +372,28 @@ function processFile(filename: string, config: Config) {
253
372
 
254
373
  let startTime = performance.now();
255
374
 
256
- let input = join(path.resolve(filename), config.lib);
375
+ const resolved_path = path.resolve(filename);
376
+ const input = join(resolved_path, config.lib);
257
377
  const library = config.lib ? get_library(config.lib) : undefined;
258
- const parsed = parse(input, library);
259
- // TODO: If verbose flag
260
- // console.log("Parsed");
378
+ const parsed = parse(input, library, resolved_path);
261
379
 
262
380
  let errors = parsed.errors;
263
- const ok = !errors.length;
264
381
 
265
- if (!ok) {
382
+ if (errors.length) {
266
383
  console.log(render_errors(input, errors));
267
384
  return;
268
385
  }
269
386
 
270
- // TODO: If verbose flag
271
- // console.log("Built");
387
+ // Warnings come out of the parse/check phase, so every mode reports them.
388
+ if (parsed.warnings.length) console.log(render_warnings(input, parsed.warnings));
389
+
390
+ // `check` stops after parsing and checking — no building, linking or running.
391
+ if (mode === "check") {
392
+ const checkTime = performance.now();
393
+ console.log(`Checked in ${(checkTime - startTime).toFixed(2)}ms`);
394
+ return;
395
+ }
396
+
272
397
  const result = build(parsed.root, { arch, platform, audit: config.audit });
273
398
 
274
399
  if (result.errors && result.errors.length > 0) {
@@ -276,9 +401,8 @@ function processFile(filename: string, config: Config) {
276
401
  return;
277
402
  }
278
403
 
279
- const dir = path.dirname(filename);
280
404
  const basename = path.basename(filename, ".nm");
281
- const buildDir = path.join(dir, "build");
405
+ const buildDir = path.join(build_root ?? path.dirname(filename), "build");
282
406
  if (!fs.existsSync(buildDir)) {
283
407
  fs.mkdirSync(buildDir, { recursive: true });
284
408
  }
@@ -300,6 +424,7 @@ function processFile(filename: string, config: Config) {
300
424
  console.log(`Created ${codefile} in ${(compileTime - startTime).toFixed(2)}ms`);
301
425
  console.log("");
302
426
 
427
+ // `build` links the executable but does not run it; `run` links and runs.
303
428
  startTime = performance.now();
304
429
 
305
430
  const audit_obj = config.audit ? compile_audit_runtime(config, resolved, buildDir) : undefined;
@@ -311,6 +436,13 @@ function processFile(filename: string, config: Config) {
311
436
  ? " -framework CoreGraphics -framework Foundation -framework AppKit -lobjc"
312
437
  : "";
313
438
  execSync(`clang -o ${outfile} ${link_inputs}${framework_flags}`);
439
+
440
+ if (mode === "build") {
441
+ const buildTime = performance.now();
442
+ console.log(`Built ${outfile} in ${(buildTime - startTime).toFixed(2)}ms`);
443
+ return;
444
+ }
445
+
314
446
  execSync(outfile, { stdio: "inherit" });
315
447
 
316
448
  const runTime = performance.now();
package/vite.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite-plus";
2
+
3
+ export default defineConfig({
4
+ pack: {
5
+ dts: false,
6
+ },
7
+ });
package/dist/index.d.mts DELETED
@@ -1 +0,0 @@
1
- export { };