nomen-lang 0.0.5 → 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.5",
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,14 +8,63 @@ 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
+
19
68
  // The folder whose `build/` subdirectory receives compiler output. Set during
20
69
  // input resolution: the --in folder, the .nm file's folder, or — for
21
70
  // package.jsonc discovery — the package folder (cwd), not the entry's folder.
@@ -23,8 +72,20 @@ let build_root: string | undefined;
23
72
 
24
73
  console.log("\n~ NOMEN ~\n");
25
74
 
26
- const options = yargs(hideBin(process.argv))
27
- .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")
28
89
  .option("in", {
29
90
  alias: "i",
30
91
  describe: "Input file or folder",
@@ -69,10 +130,59 @@ const options = yargs(hideBin(process.argv))
69
130
  describe: "Path to audit_runtime.c, linked in when --audit is set",
70
131
  type: "string",
71
132
  })
72
- .help(true)
73
- .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];
74
142
 
75
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
+
76
186
  // An explicit --in wins; otherwise discover what to compile from the
77
187
  // working folder — a package.jsonc `entry`, or a lone .nm file.
78
188
  options.in = options.in ?? resolve_input();
@@ -100,24 +210,21 @@ try {
100
210
 
101
211
  // Is the --in path a folder
102
212
  if (fs.lstatSync(options.in).isDirectory()) {
103
- // Loop through files in the folder
104
- //processFolder(options.in);
105
213
  if (options.watch) {
106
- watchPath(options.in, config);
214
+ watchPath(options.in, config, mode);
107
215
  } else {
108
- processFolder(options.in, config);
216
+ processFolder(options.in, config, mode);
109
217
  }
110
218
  } else {
111
219
  // Process the supplied file
112
220
  const extname = path.extname(options.in);
113
221
  if (shouldProcessFile(options.in)) {
114
- //processFile(options.in);
115
222
  // NOTE: We get add notifications for all watched files immediately
116
223
  // TODO: Is this the case on Windows etc too?
117
224
  if (options.watch) {
118
- watchPath(options.in, config);
225
+ watchPath(options.in, config, mode);
119
226
  } else {
120
- processFile(options.in, config);
227
+ processFile(options.in, config, mode);
121
228
  }
122
229
  } else {
123
230
  console.log("Unsupported file type: " + extname);
@@ -227,20 +334,20 @@ function compile_audit_runtime(config: Config, input_path: string, buildDir: str
227
334
  return audit_obj;
228
335
  }
229
336
 
230
- function watchPath(p: string, config: Config) {
337
+ function watchPath(p: string, config: Config, mode: Mode) {
231
338
  chokidar.watch(p).on("all", (event, filePath) => {
232
339
  if (shouldProcessFile(filePath)) {
233
- processFile(filePath, config);
340
+ processFile(filePath, config, mode);
234
341
  }
235
342
  });
236
343
  }
237
344
 
238
- function processFolder(folder: string, config: Config) {
345
+ function processFolder(folder: string, config: Config, mode: Mode) {
239
346
  const dir = fs.opendirSync(folder);
240
347
  let dirent;
241
348
  while ((dirent = dir.readSync()) !== null) {
242
349
  if (shouldProcessFile(dirent.name)) {
243
- processFile(path.join(folder, dirent.name), config);
350
+ processFile(path.join(folder, dirent.name), config, mode);
244
351
  // @ts-ignore
245
352
  let _ = fs.watch;
246
353
  }
@@ -252,7 +359,7 @@ function shouldProcessFile(filename: string) {
252
359
  return path.extname(filename) === SUPPORTED_EXTENSION;
253
360
  }
254
361
 
255
- function processFile(filename: string, config: Config) {
362
+ function processFile(filename: string, config: Config, mode: Mode) {
256
363
  console.log("Processing", filename);
257
364
 
258
365
  const arch = config.arch || "aarch64";
@@ -265,22 +372,28 @@ function processFile(filename: string, config: Config) {
265
372
 
266
373
  let startTime = performance.now();
267
374
 
268
- let input = join(path.resolve(filename), config.lib);
375
+ const resolved_path = path.resolve(filename);
376
+ const input = join(resolved_path, config.lib);
269
377
  const library = config.lib ? get_library(config.lib) : undefined;
270
- const parsed = parse(input, library);
271
- // TODO: If verbose flag
272
- // console.log("Parsed");
378
+ const parsed = parse(input, library, resolved_path);
273
379
 
274
380
  let errors = parsed.errors;
275
- const ok = !errors.length;
276
381
 
277
- if (!ok) {
382
+ if (errors.length) {
278
383
  console.log(render_errors(input, errors));
279
384
  return;
280
385
  }
281
386
 
282
- // TODO: If verbose flag
283
- // 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
+
284
397
  const result = build(parsed.root, { arch, platform, audit: config.audit });
285
398
 
286
399
  if (result.errors && result.errors.length > 0) {
@@ -311,6 +424,7 @@ function processFile(filename: string, config: Config) {
311
424
  console.log(`Created ${codefile} in ${(compileTime - startTime).toFixed(2)}ms`);
312
425
  console.log("");
313
426
 
427
+ // `build` links the executable but does not run it; `run` links and runs.
314
428
  startTime = performance.now();
315
429
 
316
430
  const audit_obj = config.audit ? compile_audit_runtime(config, resolved, buildDir) : undefined;
@@ -322,6 +436,13 @@ function processFile(filename: string, config: Config) {
322
436
  ? " -framework CoreGraphics -framework Foundation -framework AppKit -lobjc"
323
437
  : "";
324
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
+
325
446
  execSync(outfile, { stdio: "inherit" });
326
447
 
327
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 { };