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/dist/index.mjs +14237 -5656
- package/package.json +4 -7
- package/src/docs.ts +267 -0
- package/src/format_errors.ts +23 -9
- package/src/index.ts +146 -25
- package/vite.config.ts +7 -0
- package/dist/index.d.mts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nomen-lang",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
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
|
-
"
|
|
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
|
+
}
|
package/src/format_errors.ts
CHANGED
|
@@ -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
|
|
55
|
-
const line_text = lines[
|
|
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 =
|
|
73
|
+
let file_line = message.line;
|
|
60
74
|
for (let m = markers.length - 1; m >= 0; m--) {
|
|
61
|
-
if (markers[m].joined_line <
|
|
62
|
-
file_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,
|
|
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
|
-
|
|
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 =
|
|
85
|
-
return `\n${blocks.join("\n\n")}\n\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
|
|
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
|
|
27
|
-
.usage(
|
|
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
|
-
.
|
|
73
|
-
|
|
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
|
-
|
|
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 (
|
|
382
|
+
if (errors.length) {
|
|
278
383
|
console.log(render_errors(input, errors));
|
|
279
384
|
return;
|
|
280
385
|
}
|
|
281
386
|
|
|
282
|
-
//
|
|
283
|
-
|
|
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
package/dist/index.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|