@nova-lang/cli 0.1.1 → 0.2.0

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/bin/nova.js CHANGED
@@ -1,49 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
- const { spawnSync } = require("child_process");
5
- const path = require("path");
6
- const fs = require("fs");
7
-
8
- function findBinary() {
9
- const binName = process.platform === "win32" ? "nova.exe" : "nova";
10
-
11
- const candidates = [
12
- path.join(__dirname, "..", "zig-out", "bin", binName),
13
- path.join(__dirname, "..", "bin", binName),
14
- ];
15
-
16
- if (process.env.NOVA_BINARY) {
17
- candidates.unshift(process.env.NOVA_BINARY);
18
- }
19
-
20
- for (const p of candidates) {
21
- try {
22
- if (fs.statSync(p).isFile()) {
23
- return p;
24
- }
25
- } catch {}
26
- }
27
-
28
- return null;
29
- }
30
-
31
- function main() {
32
- const binary = findBinary();
33
- if (!binary) {
34
- console.error(
35
- "Nova Zig binary not found.\n" +
36
- " Build with: npm run build (requires Zig compiler)\n" +
37
- " Or set: NOVA_BINARY=/path/to/nova\n"
38
- );
39
- process.exit(1);
40
- }
41
-
42
- const result = spawnSync(binary, process.argv.slice(2), {
43
- stdio: "inherit",
44
- });
45
-
46
- process.exit(result.status ?? 1);
47
- }
48
-
49
- main();
4
+ const cli = require("../lib/cli");
5
+ cli.run(process.argv);
package/lib/cli.js ADDED
@@ -0,0 +1,203 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { tokenize } = require("./lexer");
4
+ const { parse } = require("./parser");
5
+ const { interpret } = require("./interpreter");
6
+ const { render } = require("./renderer");
7
+
8
+ function formatTokens(tokens) {
9
+ const lines = [];
10
+ for (const tok of tokens) {
11
+ lines.push(`${tok.type}${tok.value ? " " + JSON.stringify(tok.value) : ""}`);
12
+ }
13
+ return lines.join("\n") + "\n";
14
+ }
15
+
16
+ function formatAST(node, indent = 0) {
17
+ const pad = " ".repeat(indent);
18
+ const tag = node.tag;
19
+ const data = node.data;
20
+ let result = pad + tag;
21
+
22
+ switch (tag) {
23
+ case "document":
24
+ result += ` (meta: ${JSON.stringify(data.meta)})`;
25
+ result += "\n";
26
+ for (const child of data.children) {
27
+ result += formatAST(child, indent + 1);
28
+ }
29
+ break;
30
+ case "text":
31
+ result += ` ${JSON.stringify(data.value)}`;
32
+ result += "\n";
33
+ break;
34
+ case "math_inline":
35
+ result += ` ${JSON.stringify(data.source)}`;
36
+ result += "\n";
37
+ break;
38
+ case "math_display":
39
+ result += ` ${JSON.stringify(data.source)}`;
40
+ result += "\n";
41
+ break;
42
+ case "interpolation":
43
+ result += ` ${JSON.stringify(data.expression)}`;
44
+ result += "\n";
45
+ break;
46
+ case "block":
47
+ result += ` @${data.name}`;
48
+ if (Object.keys(data.attrs).length > 0) {
49
+ result += ` attrs=${JSON.stringify(data.attrs)}`;
50
+ }
51
+ if (data.inline_content && data.inline_content.length > 0) {
52
+ result += "\n" + pad + " inline:";
53
+ for (const c of data.inline_content) {
54
+ result += "\n" + formatAST(c, indent + 2).trimEnd();
55
+ }
56
+ }
57
+ result += "\n";
58
+ for (const child of data.children) {
59
+ result += formatAST(child, indent + 1);
60
+ }
61
+ break;
62
+ case "inline_block":
63
+ result += ` @${data.name}`;
64
+ if (Object.keys(data.attrs).length > 0) {
65
+ result += ` attrs=${JSON.stringify(data.attrs)}`;
66
+ }
67
+ if (data.content && data.content.length > 0) {
68
+ result += "\n" + pad + " content:";
69
+ for (const c of data.content) {
70
+ result += "\n" + formatAST(c, indent + 2).trimEnd();
71
+ }
72
+ }
73
+ result += "\n";
74
+ break;
75
+ case "list_block":
76
+ result += ` ordered=${data.ordered}`;
77
+ result += "\n";
78
+ for (const item of data.items) {
79
+ result += pad + " - item:\n";
80
+ result += formatAST(item, indent + 3);
81
+ }
82
+ break;
83
+ case "table_block":
84
+ result += "\n";
85
+ if (data.caption) result += pad + " caption: " + JSON.stringify(data.caption) + "\n";
86
+ if (data.headers) result += pad + " headers: " + JSON.stringify(data.headers) + "\n";
87
+ if (data.rows && data.rows.length > 0) {
88
+ for (const row of data.rows) {
89
+ result += pad + " row: " + JSON.stringify(row) + "\n";
90
+ }
91
+ }
92
+ if (data.data && data.data.length > 0) {
93
+ result += pad + " data: " + JSON.stringify(data.data) + "\n";
94
+ }
95
+ break;
96
+ case "macro_def":
97
+ result += ` name=${data.name}`;
98
+ result += ` params=${JSON.stringify(data.params)}`;
99
+ result += ` kwargs=${JSON.stringify(data.kwargs)}`;
100
+ result += ` hasBlockParam=${data.hasBlockParam}`;
101
+ result += ` blockParamName=${data.blockParamName}`;
102
+ result += ` body_len=${data.body.length}`;
103
+ result += "\n";
104
+ break;
105
+ case "schema_def":
106
+ result += ` name=${data.name}`;
107
+ result += "\n";
108
+ for (const f of data.fields) {
109
+ result += pad + " field: " + JSON.stringify(f) + "\n";
110
+ }
111
+ break;
112
+ case "service_def":
113
+ result += ` name=${data.name}`;
114
+ result += "\n";
115
+ for (const m of data.methods) {
116
+ result += pad + " method: " + JSON.stringify(m) + "\n";
117
+ }
118
+ break;
119
+ case "biblio_entry":
120
+ result += ` key=${data.key}`;
121
+ result += "\n";
122
+ break;
123
+ case "if_block":
124
+ result += ` condition=${JSON.stringify(data.condition)}`;
125
+ result += "\n";
126
+ result += pad + " then:\n";
127
+ for (const c of data.then_body) {
128
+ result += formatAST(c, indent + 2);
129
+ }
130
+ if (data.else_body && data.else_body.length > 0) {
131
+ result += pad + " else:\n";
132
+ for (const c of data.else_body) {
133
+ result += formatAST(c, indent + 2);
134
+ }
135
+ }
136
+ break;
137
+ case "for_block":
138
+ result += ` var=${data.var_name} iterable=${data.iterable}`;
139
+ result += "\n";
140
+ result += pad + " body:\n";
141
+ for (const c of data.body) {
142
+ result += formatAST(c, indent + 2);
143
+ }
144
+ break;
145
+ case "meta_block":
146
+ result += ` ${JSON.stringify(data.pairs)}`;
147
+ result += "\n";
148
+ break;
149
+ case "use_block":
150
+ result += ` path=${data.path}`;
151
+ result += "\n";
152
+ break;
153
+ default:
154
+ result += ` ${JSON.stringify(data)}`;
155
+ result += "\n";
156
+ }
157
+
158
+ return result;
159
+ }
160
+
161
+ function run(argv) {
162
+ const args = argv.slice(2);
163
+ const command = args[0];
164
+
165
+ if (command === "lex") {
166
+ const filePath = args[1];
167
+ if (!filePath) {
168
+ console.error("Usage: nova lex <file>");
169
+ process.exit(1);
170
+ }
171
+ const source = fs.readFileSync(path.resolve(filePath), "utf-8");
172
+ const tokens = tokenize(source);
173
+ process.stdout.write(formatTokens(tokens));
174
+ return;
175
+ }
176
+
177
+ if (command === "ast") {
178
+ const filePath = args[1];
179
+ if (!filePath) {
180
+ console.error("Usage: nova ast <file>");
181
+ process.exit(1);
182
+ }
183
+ const source = fs.readFileSync(path.resolve(filePath), "utf-8");
184
+ const tokens = tokenize(source);
185
+ const ast = parse(tokens);
186
+ process.stdout.write(formatAST(ast));
187
+ return;
188
+ }
189
+
190
+ const filePath = command;
191
+ if (!filePath) {
192
+ console.error("Usage: nova [lex|ast] <file>");
193
+ process.exit(1);
194
+ }
195
+ const source = fs.readFileSync(path.resolve(filePath), "utf-8");
196
+ const tokens = tokenize(source);
197
+ const ast = parse(tokens);
198
+ const doc = interpret(ast);
199
+ const html = render(doc);
200
+ process.stdout.write(html);
201
+ }
202
+
203
+ module.exports = { run, formatAST, formatTokens };
@@ -0,0 +1,274 @@
1
+ const { NodeTag, makeNode } = require("./types");
2
+
3
+ class Env {
4
+ constructor(parent) {
5
+ this.vars = {};
6
+ this.macros = {};
7
+ this.parent = parent || null;
8
+ }
9
+
10
+ get(name) {
11
+ if (name in this.vars) return this.vars[name];
12
+ if (this.parent) return this.parent.get(name);
13
+ return null;
14
+ }
15
+
16
+ set(name, value) {
17
+ this.vars[name] = value;
18
+ }
19
+
20
+ defMacro(name, macro) {
21
+ if (this.parent) {
22
+ this.parent.defMacro(name, macro);
23
+ } else {
24
+ this.macros[name] = macro;
25
+ }
26
+ }
27
+
28
+ lookupMacro(name) {
29
+ if (name in this.macros) return this.macros[name];
30
+ if (this.parent) return this.parent.lookupMacro(name);
31
+ return null;
32
+ }
33
+
34
+ clone() {
35
+ const e = new Env(this);
36
+ return e;
37
+ }
38
+ }
39
+
40
+ class Interpreter {
41
+ constructor() {
42
+ this.globalEnv = new Env();
43
+ }
44
+
45
+ evalDocument(doc) {
46
+ const children = [];
47
+ for (const child of doc.data.children) {
48
+ const result = this.evalNode(child, this.globalEnv);
49
+ if (result != null) children.push(result);
50
+ }
51
+ doc.data.children = children;
52
+ return doc;
53
+ }
54
+
55
+ evalNode(node, env) {
56
+ switch (node.tag) {
57
+ case NodeTag.document:
58
+ return this.evalDocument(node);
59
+ case NodeTag.block:
60
+ return this.evalBlock(node.data, env);
61
+ case NodeTag.inline_block:
62
+ return this.evalInline(node.data, env);
63
+ case NodeTag.text:
64
+ return this.evalText(node.data, env);
65
+ case NodeTag.interpolation:
66
+ return this.evalInterpolation(node.data, env);
67
+ case NodeTag.math_inline:
68
+ case NodeTag.math_display:
69
+ return node;
70
+ case NodeTag.list_block:
71
+ return this.evalList(node.data, env);
72
+ case NodeTag.table_block:
73
+ return node;
74
+ case NodeTag.macro_def:
75
+ env.defMacro(node.data.name, node.data);
76
+ return null;
77
+ case NodeTag.schema_def:
78
+ case NodeTag.service_def:
79
+ return node;
80
+ case NodeTag.if_block:
81
+ return this.evalIf(node.data, env);
82
+ case NodeTag.for_block:
83
+ return this.evalFor(node.data, env);
84
+ case NodeTag.use_block:
85
+ console.warn(`Warning: @use "${node.data.path}" is not yet supported (module system not implemented)`);
86
+ return null;
87
+ case NodeTag.meta_block:
88
+ return node;
89
+ default:
90
+ return node;
91
+ }
92
+ }
93
+
94
+ evalBlock(block, env) {
95
+ if (!block.name || block.name.length === 0) {
96
+ block.children = this.evalChildren(block.children, env);
97
+ return makeNode(NodeTag.block, { ...block });
98
+ }
99
+ const macro = env.lookupMacro(block.name);
100
+ if (macro) {
101
+ return this.expandMacro(macro, block, env);
102
+ }
103
+ block.children = this.evalChildren(block.children, env);
104
+ block.inline_content = this.evalInlineList(block.inline_content, env);
105
+ return makeNode(NodeTag.block, { ...block });
106
+ }
107
+
108
+ evalInline(inl, env) {
109
+ inl.content = this.evalInlineList(inl.content, env);
110
+ return makeNode(NodeTag.inline_block, { ...inl });
111
+ }
112
+
113
+ evalText(_text, _env) {
114
+ return makeNode(NodeTag.text, { ..._text });
115
+ }
116
+
117
+ evalInterpolation(interp, _env) {
118
+ return makeNode(NodeTag.text, { value: interp.expression });
119
+ }
120
+
121
+ evalChildren(children, env) {
122
+ const result = [];
123
+ for (const child of children) {
124
+ const evaled = this.evalNode(child, env);
125
+ if (evaled != null) result.push(evaled);
126
+ }
127
+ return result;
128
+ }
129
+
130
+ evalInlineList(nodes, env) {
131
+ const result = [];
132
+ for (const node of nodes) {
133
+ const evaled = this.evalNode(node, env);
134
+ if (evaled != null) result.push(evaled);
135
+ }
136
+ return result;
137
+ }
138
+
139
+ evalList(list, env) {
140
+ const newItems = [];
141
+ for (const item of list.items) {
142
+ if (item.tag === NodeTag.block) {
143
+ const blk = { ...item.data };
144
+ blk.children = this.evalChildren(blk.children, env);
145
+ newItems.push(makeNode(NodeTag.block, blk));
146
+ } else {
147
+ newItems.push(item);
148
+ }
149
+ }
150
+ list.items = newItems;
151
+ return makeNode(NodeTag.list_block, { ...list });
152
+ }
153
+
154
+ evalIf(ifblk, env) {
155
+ const cond = this.evaluateCondition(ifblk.condition, env);
156
+ const result = cond
157
+ ? this.evalChildren(ifblk.then_body, env)
158
+ : this.evalChildren(ifblk.else_body, env);
159
+ if (result.length === 1) return result[0];
160
+ return makeNode(NodeTag.document, {
161
+ children: result,
162
+ meta: {},
163
+ });
164
+ }
165
+
166
+ evalFor(forblk, env) {
167
+ const childEnv = env.clone();
168
+ const resultNodes = [];
169
+
170
+ const iterableVal = env.get(forblk.iterable) || forblk.iterable;
171
+ const items = [];
172
+
173
+ if (iterableVal === "true" || iterableVal === "false" || iterableVal === "null") {
174
+ } else if (/^-?\d/.test(iterableVal)) {
175
+ const count = parseInt(iterableVal, 10) || 1;
176
+ for (let i = 0; i < count; i++) {
177
+ items.push(String(i));
178
+ }
179
+ } else {
180
+ const parts = iterableVal.split(",");
181
+ for (const part of parts) {
182
+ const trimmed = part.trim();
183
+ if (trimmed.length > 0) items.push(trimmed);
184
+ }
185
+ }
186
+
187
+ for (const itemVal of items) {
188
+ childEnv.set(forblk.var_name, itemVal);
189
+ const evaled = this.evalChildren(forblk.body, childEnv);
190
+ for (const n of evaled) resultNodes.push(n);
191
+ }
192
+
193
+ return makeNode(NodeTag.document, {
194
+ children: resultNodes,
195
+ meta: {},
196
+ });
197
+ }
198
+
199
+ evaluateCondition(condition, env) {
200
+ if (!condition || condition.length === 0) return true;
201
+ const trimmed = condition.trim();
202
+ if (trimmed === "true") return true;
203
+ if (trimmed === "false") return false;
204
+ if (trimmed === "null") return false;
205
+ if (trimmed.startsWith("@defined(")) {
206
+ const inner = trimmed.slice("@defined(".length, -1).trim();
207
+ return env.get(inner) != null;
208
+ }
209
+ if (trimmed.startsWith("!")) {
210
+ const rest = trimmed.slice(1).trim();
211
+ return !this.evaluateCondition(rest, env);
212
+ }
213
+ const val = env.get(trimmed);
214
+ if (val != null) {
215
+ return !(val.length === 0 || val === "false" || val === "null");
216
+ }
217
+ const num = parseInt(trimmed, 10);
218
+ if (!isNaN(num)) return num !== 0;
219
+ return condition.length > 0;
220
+ }
221
+
222
+ expandMacro(macro, call, env) {
223
+ const childEnv = env.clone();
224
+
225
+ let argIdx = 0;
226
+ for (const paramName of macro.params) {
227
+ let paramVal = "";
228
+ if (argIdx < call.inline_content.length) {
229
+ const argNode = call.inline_content[argIdx];
230
+ if (argNode.tag === NodeTag.text) {
231
+ paramVal = argNode.data.value;
232
+ }
233
+ }
234
+ childEnv.set(paramName, paramVal);
235
+ argIdx++;
236
+ }
237
+
238
+ for (const [kwName, defaultVal] of Object.entries(macro.kwargs)) {
239
+ const callVal = call.attrs[kwName] || defaultVal;
240
+ childEnv.set(kwName, callVal);
241
+ }
242
+
243
+ const evaledBody = [];
244
+ for (const bodyNode of macro.body) {
245
+ if (
246
+ bodyNode.tag === NodeTag.block &&
247
+ bodyNode.data.name === macro.blockParamName &&
248
+ macro.hasBlockParam
249
+ ) {
250
+ for (const callChild of call.children) {
251
+ const evaled = this.evalNode(callChild, childEnv);
252
+ if (evaled != null) evaledBody.push(evaled);
253
+ }
254
+ } else {
255
+ const evaled = this.evalNode(bodyNode, childEnv);
256
+ if (evaled != null) evaledBody.push(evaled);
257
+ }
258
+ }
259
+
260
+ return makeNode(NodeTag.block, {
261
+ name: macro.name,
262
+ attrs: {},
263
+ inline_content: [],
264
+ children: evaledBody,
265
+ });
266
+ }
267
+ }
268
+
269
+ function interpret(doc) {
270
+ const interp = new Interpreter();
271
+ return interp.evalDocument(doc);
272
+ }
273
+
274
+ module.exports = { Interpreter, Env, interpret };