nomen-lang 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nomen-lang",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
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
  }
@@ -0,0 +1,113 @@
1
+ // Template for one benchmark's timing loop. The CLI substitutes __NAME__,
2
+ // __TARGET__ and __N__ and concatenates this after the test file's source
3
+ // (so it is user code and can call the test file's functions). Kept as a
4
+ // separate .nm file (not a TS template literal) because Nomen's generic syntax
5
+ // (List<int>) is not valid TypeScript and would break the CLI's own build.
6
+ //
7
+ // Placeholders use the leading-AND-trailing `__NAME__` convention so they don't
8
+ // collide with the `__w` / `__i` / `__samples` locals (leading-only).
9
+ //
10
+ // min / max / sum / sum-of-squares are tracked as running aggregates during
11
+ // sampling, so mean and stddev need no `List.at` calls at all. Only the median
12
+ // needs sorted data, so the samples are insertion-sorted afterwards. Every
13
+ // `List.at` / `List.set` is guarded by `idx >= 0 && idx < __samples.length` —
14
+ // the exact bound `List.at`'s constraint requires — because the constraint
15
+ // checker treats an unverifiable index as an out-of-bounds risk (the backends
16
+ // emit unchecked strided loads).
17
+ func bench_loop___NAME__ = (ref Tester t) {
18
+ // Warm up so the first samples don't pay for cold caches / lazy binding.
19
+ var int __w = 0
20
+ while __w < 8 {
21
+ __TARGET__()
22
+ __w += 1
23
+ }
24
+ // Collect __N__ samples, tracking running aggregates for every statistic
25
+ // except the median (which needs sorted data).
26
+ var List<int> __samples = List<int>()
27
+ var int __sum = 0
28
+ var float __sum_sq = 0.0
29
+ var int __min = 0
30
+ var int __max = 0
31
+ var int __i = 0
32
+ while __i < __N__ {
33
+ const uint64 __t0 = Time.now_ns()
34
+ __TARGET__()
35
+ const uint64 __t1 = Time.now_ns()
36
+ const int __dt = (__t1 - __t0) as int
37
+ __samples.push(__dt)
38
+ __sum += __dt
39
+ __sum_sq = __sum_sq + (__dt as float) * (__dt as float)
40
+ if __i == 0 {
41
+ __min = __dt
42
+ __max = __dt
43
+ }
44
+ if __i > 0 {
45
+ if __dt < __min {
46
+ __min = __dt
47
+ }
48
+ if __dt > __max {
49
+ __max = __dt
50
+ }
51
+ }
52
+ __i += 1
53
+ }
54
+ const int __n = __samples.length
55
+ if __n > 0 {
56
+ const float __fn = __n as float
57
+ const float __mean = (__sum as float) / __fn
58
+ var float __variance = __sum_sq / __fn - __mean * __mean
59
+ if __variance < 0.0 {
60
+ __variance = 0.0
61
+ }
62
+ const float __stddev = Math.sqrt(__variance)
63
+ // Insertion sort the samples so we can pick a median. `length` is
64
+ // stable throughout (only `set` mutates), so every access is guarded
65
+ // against `__samples.length` directly.
66
+ var int __s = 1
67
+ while __s < __n {
68
+ if __s >= 0 && __s < __samples.length {
69
+ const int __v = __samples.at(__s)
70
+ var int __j = __s
71
+ var bool __more = true
72
+ while __more {
73
+ var int __pm1 = __j - 1
74
+ if __pm1 >= 0 && __pm1 < __samples.length {
75
+ const int __prev = __samples.at(__pm1)
76
+ if __prev > __v {
77
+ if __j >= 0 && __j < __samples.length {
78
+ __samples.set(__j, __prev)
79
+ }
80
+ __j -= 1
81
+ } else {
82
+ __more = false
83
+ }
84
+ } else {
85
+ __more = false
86
+ }
87
+ }
88
+ if __j >= 0 && __j < __samples.length {
89
+ __samples.set(__j, __v)
90
+ }
91
+ }
92
+ __s += 1
93
+ }
94
+ // Median of the sorted samples. Indices are precomputed into locals so
95
+ // the guards are simple variable bounds.
96
+ var int __median = __min
97
+ var int __hi = __n / 2
98
+ if __hi >= 0 && __hi < __samples.length {
99
+ __median = __samples.at(__hi)
100
+ }
101
+ if __n % 2 == 0 {
102
+ var int __lo = __n / 2 - 1
103
+ if __lo >= 0 && __lo < __samples.length {
104
+ if __hi >= 0 && __hi < __samples.length {
105
+ var int __a = __samples.at(__lo)
106
+ var int __b = __samples.at(__hi)
107
+ __median = (__a + __b) / 2
108
+ }
109
+ }
110
+ }
111
+ t.record_bench(t.bench_label, __n, __min, __median, __max, __mean, __stddev)
112
+ }
113
+ }
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
  }