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/dist/index.mjs +16024 -7436
- package/package.json +4 -7
- package/src/docs.ts +267 -0
- package/src/format_errors.ts +23 -9
- package/src/index.ts +159 -27
- 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,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
|
|
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
|
|
22
|
-
.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")
|
|
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
|
-
.
|
|
68
|
-
|
|
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
|
-
|
|
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 (
|
|
382
|
+
if (errors.length) {
|
|
266
383
|
console.log(render_errors(input, errors));
|
|
267
384
|
return;
|
|
268
385
|
}
|
|
269
386
|
|
|
270
|
-
//
|
|
271
|
-
|
|
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(
|
|
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
package/dist/index.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|