@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 +2 -46
- package/lib/cli.js +203 -0
- package/lib/interpreter.js +274 -0
- package/lib/lexer.js +315 -0
- package/lib/parser.js +1037 -0
- package/lib/renderer.js +564 -0
- package/lib/types.js +73 -0
- package/package.json +4 -12
- package/build.zig +0 -25
- package/install.js +0 -67
- package/src/interpreter.zig +0 -288
- package/src/lexer.zig +0 -400
- package/src/main.zig +0 -147
- package/src/parser.zig +0 -1011
- package/src/renderer.zig +0 -678
- package/src/types.zig +0 -235
package/lib/renderer.js
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
const { NodeTag } = require("./types");
|
|
2
|
+
|
|
3
|
+
const DEFAULT_STYLES = `
|
|
4
|
+
body { font-family: 'Georgia', 'Noto Serif CJK', serif; line-height: 1.7; color: #333; max-width: 900px; margin: 0 auto; padding: 2rem; background: #fafafa; }
|
|
5
|
+
.nova-document { background: #fff; padding: 2.5rem; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,0.1); }
|
|
6
|
+
h1, h2, h3, h4 { color: #1a1a1a; margin-top: 1.5em; margin-bottom: 0.5em; font-weight: 600; }
|
|
7
|
+
h1 { font-size: 2em; border-bottom: 2px solid #eee; padding-bottom: 0.3em; }
|
|
8
|
+
h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.2em; }
|
|
9
|
+
p { margin: 0.8em 0; text-align: justify; }
|
|
10
|
+
em { font-style: italic; }
|
|
11
|
+
strong { font-weight: bold; }
|
|
12
|
+
code { font-family: 'Fira Code', 'Consolas', monospace; background: #f0f0f0; padding: 0.15em 0.3em; border-radius: 3px; font-size: 0.9em; }
|
|
13
|
+
pre { background: #f5f5f5; padding: 1em; border-radius: 4px; overflow-x: auto; }
|
|
14
|
+
a { color: #0366d6; text-decoration: none; }
|
|
15
|
+
a:hover { text-decoration: underline; }
|
|
16
|
+
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
|
17
|
+
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
|
18
|
+
th { background: #f5f5f5; font-weight: 600; }
|
|
19
|
+
tr:nth-child(even) { background: #fafafa; }
|
|
20
|
+
ul, ol { padding-left: 2em; margin: 0.5em 0; }
|
|
21
|
+
li { margin: 0.3em 0; }
|
|
22
|
+
blockquote { border-left: 4px solid #ddd; margin: 1em 0; padding: 0.5em 1em; color: #666; }
|
|
23
|
+
.math { font-family: 'STIX', 'Latin Modern Math', serif; }
|
|
24
|
+
.math-display { display: block; text-align: center; margin: 1em 0; overflow-x: auto; }
|
|
25
|
+
.nova-meta { font-size: 0.9em; color: #888; margin: 1em 0; padding: 0.5em; background: #f9f9f9; border-radius: 4px; }
|
|
26
|
+
.nova-meta-key { font-weight: bold; }
|
|
27
|
+
.nova-schema, .nova-service { background: #f8f8ff; border: 1px solid #dde; padding: 1em; margin: 1em 0; border-radius: 4px; font-family: 'Fira Code', monospace; font-size: 0.9em; }
|
|
28
|
+
.nova-field { margin: 0.3em 0; }
|
|
29
|
+
.nova-tag { color: #888; }
|
|
30
|
+
.nova-def { color: #444; }
|
|
31
|
+
.nova-type { color: #b44; }
|
|
32
|
+
.nova-ref { color: #0366d6; cursor: pointer; border-bottom: 1px dotted #0366d6; }
|
|
33
|
+
.footnotes { margin-top: 2em; padding-top: 1em; border-top: 1px solid #ddd; font-size: 0.9em; color: #666; }
|
|
34
|
+
.nova-config dt { font-weight: bold; margin-top: 0.5em; }
|
|
35
|
+
.nova-config dd { margin-left: 1.5em; color: #555; }
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
function escape(s) {
|
|
39
|
+
return s
|
|
40
|
+
.replace(/&/g, "&")
|
|
41
|
+
.replace(/</g, "<")
|
|
42
|
+
.replace(/>/g, ">")
|
|
43
|
+
.replace(/"/g, """)
|
|
44
|
+
.replace(/'/g, "'");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const MATHJAX_SCRIPT = `
|
|
48
|
+
<script>
|
|
49
|
+
MathJax = {tex: {inlineMath: [['$','$'],['\\\\\\(','\\\\\\)']],displayMath:[['$$','$$'],['\\\\[','\\\\]']]},svg:{fontCache:'global'}};
|
|
50
|
+
</script>
|
|
51
|
+
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" async></script>
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
function render(doc) {
|
|
55
|
+
const out = [];
|
|
56
|
+
let indentLevel = 0;
|
|
57
|
+
|
|
58
|
+
function emit(s) {
|
|
59
|
+
out.push(s, "\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function emitIndent(n) {
|
|
63
|
+
out.push(" ".repeat(n));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function emitLine(s) {
|
|
67
|
+
out.push(s, "\n");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function renderNode(node, indent) {
|
|
71
|
+
switch (node.tag) {
|
|
72
|
+
case NodeTag.block:
|
|
73
|
+
renderBlock(node.data, indent);
|
|
74
|
+
break;
|
|
75
|
+
case NodeTag.inline_block: {
|
|
76
|
+
const html = renderInline(node.data);
|
|
77
|
+
out.push(html);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case NodeTag.text:
|
|
81
|
+
out.push(escape(node.data.value));
|
|
82
|
+
break;
|
|
83
|
+
case NodeTag.math_inline:
|
|
84
|
+
emitIndent(indent);
|
|
85
|
+
out.push(`<span class="math">\\(${escape(node.data.source)}\\)</span>`);
|
|
86
|
+
break;
|
|
87
|
+
case NodeTag.math_display:
|
|
88
|
+
emitIndent(indent);
|
|
89
|
+
out.push(`<div class="math-display">\\[${escape(node.data.source)}\\]</div>`);
|
|
90
|
+
break;
|
|
91
|
+
case NodeTag.interpolation:
|
|
92
|
+
out.push(`<code>#{${escape(node.data.expression)}}</code>`);
|
|
93
|
+
break;
|
|
94
|
+
case NodeTag.list_block:
|
|
95
|
+
renderList(node.data, indent);
|
|
96
|
+
break;
|
|
97
|
+
case NodeTag.table_block:
|
|
98
|
+
renderTable(node.data, indent);
|
|
99
|
+
break;
|
|
100
|
+
case NodeTag.schema_def:
|
|
101
|
+
renderSchema(node.data);
|
|
102
|
+
break;
|
|
103
|
+
case NodeTag.service_def:
|
|
104
|
+
renderService(node.data);
|
|
105
|
+
break;
|
|
106
|
+
case NodeTag.biblio_entry:
|
|
107
|
+
renderBiblioEntry(node.data);
|
|
108
|
+
break;
|
|
109
|
+
case NodeTag.if_block:
|
|
110
|
+
for (const c of node.data.then_body) renderNode(c, indent);
|
|
111
|
+
break;
|
|
112
|
+
case NodeTag.for_block:
|
|
113
|
+
for (const c of node.data.body) renderNode(c, indent);
|
|
114
|
+
break;
|
|
115
|
+
case NodeTag.document:
|
|
116
|
+
for (const c of node.data.children) renderNode(c, indent);
|
|
117
|
+
break;
|
|
118
|
+
case NodeTag.macro_def:
|
|
119
|
+
case NodeTag.meta_block:
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderBlock(block, indent) {
|
|
125
|
+
const name = block.name;
|
|
126
|
+
|
|
127
|
+
if (name === "page" || name === "header" || name === "body") {
|
|
128
|
+
for (const child of block.children) renderNode(child, indent);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (name === "else") {
|
|
133
|
+
for (const child of block.children) renderNode(child, indent);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (name === "title") {
|
|
138
|
+
emitIndent(indent);
|
|
139
|
+
out.push('<h1 class="nova-title">');
|
|
140
|
+
renderInlineList(block.inline_content);
|
|
141
|
+
emitLine("</h1>");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (name === "abstract") {
|
|
146
|
+
emitIndent(indent);
|
|
147
|
+
emitLine('<blockquote class="abstract">');
|
|
148
|
+
emitIndent(indent + 1);
|
|
149
|
+
emitLine("<strong>Abstract</strong>");
|
|
150
|
+
for (const child of block.children) renderNode(child, indent + 1);
|
|
151
|
+
emitIndent(indent);
|
|
152
|
+
emitLine("</blockquote>");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (name === "chapter" || name === "section") {
|
|
157
|
+
const level = name === "chapter" ? 2 : 3;
|
|
158
|
+
const tag = `h${level}`;
|
|
159
|
+
|
|
160
|
+
let titleText = "";
|
|
161
|
+
for (const child of block.children) {
|
|
162
|
+
if (child.tag === NodeTag.block && child.data.name === "title") {
|
|
163
|
+
titleText = collectText(child.data.inline_content);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
emitIndent(indent);
|
|
168
|
+
out.push(`<${tag}`);
|
|
169
|
+
if (block.attrs.id) {
|
|
170
|
+
out.push(` id="${escape(block.attrs.id)}"`);
|
|
171
|
+
}
|
|
172
|
+
out.push(">");
|
|
173
|
+
emitLine("");
|
|
174
|
+
if (titleText) {
|
|
175
|
+
emitIndent(indent + 1);
|
|
176
|
+
out.push(escape(titleText));
|
|
177
|
+
emitLine("");
|
|
178
|
+
}
|
|
179
|
+
emitIndent(indent);
|
|
180
|
+
out.push(`</${tag}>`);
|
|
181
|
+
emitLine("");
|
|
182
|
+
|
|
183
|
+
for (const child of block.children) {
|
|
184
|
+
if (child.tag === NodeTag.block && child.data.name === "title") continue;
|
|
185
|
+
renderNode(child, indent);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (name === "p" || name === "para") {
|
|
191
|
+
emitIndent(indent);
|
|
192
|
+
out.push("<p>");
|
|
193
|
+
renderInlineList(block.inline_content);
|
|
194
|
+
renderChildrenInline(block.children);
|
|
195
|
+
emitLine("</p>");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (name === "div") {
|
|
200
|
+
emitIndent(indent);
|
|
201
|
+
emitLine("<div>");
|
|
202
|
+
for (const child of block.children) renderNode(child, indent + 1);
|
|
203
|
+
emitIndent(indent);
|
|
204
|
+
emitLine("</div>");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (name === "meta") {
|
|
209
|
+
emitIndent(indent);
|
|
210
|
+
emitLine('<div class="nova-meta">');
|
|
211
|
+
for (const [k, v] of Object.entries(block.attrs)) {
|
|
212
|
+
emitIndent(indent + 1);
|
|
213
|
+
out.push(`<span class="nova-meta-key">${escape(k)}</span>: <span class="nova-meta-val">${escape(v)}</span>`);
|
|
214
|
+
emitLine("");
|
|
215
|
+
}
|
|
216
|
+
emitIndent(indent);
|
|
217
|
+
emitLine("</div>");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (name === "config") {
|
|
222
|
+
emitIndent(indent);
|
|
223
|
+
emitLine('<dl class="nova-config">');
|
|
224
|
+
for (const [k, v] of Object.entries(block.attrs)) {
|
|
225
|
+
emitIndent(indent + 1);
|
|
226
|
+
out.push(`<dt>${escape(k)}</dt>`);
|
|
227
|
+
emitLine("");
|
|
228
|
+
emitIndent(indent + 1);
|
|
229
|
+
out.push(`<dd>${escape(v)}</dd>`);
|
|
230
|
+
emitLine("");
|
|
231
|
+
}
|
|
232
|
+
emitIndent(indent);
|
|
233
|
+
emitLine("</dl>");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (name === "note" || name === "warn") {
|
|
238
|
+
emitIndent(indent);
|
|
239
|
+
out.push(`<blockquote class="${name}">`);
|
|
240
|
+
emitLine("");
|
|
241
|
+
for (const child of block.children) renderNode(child, indent + 1);
|
|
242
|
+
emitIndent(indent);
|
|
243
|
+
emitLine("</blockquote>");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (name === "bibliography") {
|
|
248
|
+
emitIndent(indent);
|
|
249
|
+
emitLine("<hr>");
|
|
250
|
+
emitIndent(indent);
|
|
251
|
+
emitLine("<h2>References</h2>");
|
|
252
|
+
for (const child of block.children) {
|
|
253
|
+
if (child.tag === NodeTag.biblio_entry) renderBiblioEntry(child.data);
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (name === "equation") {
|
|
259
|
+
let mathText = "";
|
|
260
|
+
for (const c of block.inline_content) {
|
|
261
|
+
if (c.tag === NodeTag.text) mathText += c.data.value;
|
|
262
|
+
}
|
|
263
|
+
for (const c of block.children) {
|
|
264
|
+
if (c.tag === NodeTag.text) mathText += c.data.value;
|
|
265
|
+
}
|
|
266
|
+
emitIndent(indent);
|
|
267
|
+
out.push(`<div class="math-display">\\[${escape(mathText)}\\]</div>`);
|
|
268
|
+
emitLine("");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (name === "img" || name === "image") {
|
|
273
|
+
const src = block.attrs.src || "";
|
|
274
|
+
const alt = block.attrs.alt || "image";
|
|
275
|
+
emitIndent(indent);
|
|
276
|
+
out.push(`<img src="${escape(src)}" alt="${escape(alt)}">`);
|
|
277
|
+
emitLine("");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// fallback: generic block
|
|
282
|
+
emitIndent(indent);
|
|
283
|
+
out.push(`<div class="nova-${name}">`);
|
|
284
|
+
emitLine("");
|
|
285
|
+
for (const child of block.children) renderNode(child, indent + 1);
|
|
286
|
+
emitIndent(indent);
|
|
287
|
+
emitLine("</div>");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function renderInline(inl) {
|
|
291
|
+
const name = inl.name;
|
|
292
|
+
const content = renderInlineListText(inl.content);
|
|
293
|
+
const attrs = inl.attrs;
|
|
294
|
+
|
|
295
|
+
switch (name) {
|
|
296
|
+
case "em":
|
|
297
|
+
return `<em>${content}</em>`;
|
|
298
|
+
case "strong":
|
|
299
|
+
return `<strong>${content}</strong>`;
|
|
300
|
+
case "code":
|
|
301
|
+
return `<code>${content}</code>`;
|
|
302
|
+
case "a": {
|
|
303
|
+
const href = attrs.href || "";
|
|
304
|
+
return `<a href="${escape(href)}">${content}</a>`;
|
|
305
|
+
}
|
|
306
|
+
case "ref": {
|
|
307
|
+
const key = attrs.key || attrs.id || "";
|
|
308
|
+
return `<span class="nova-ref">[${escape(key)}]</span>`;
|
|
309
|
+
}
|
|
310
|
+
case "cite": {
|
|
311
|
+
const key = attrs.key || "";
|
|
312
|
+
return `<span class="nova-cite">[${escape(key)}]</span>`;
|
|
313
|
+
}
|
|
314
|
+
case "label": {
|
|
315
|
+
const key = attrs.id || attrs.key || "";
|
|
316
|
+
return `<span id="${escape(key)}" class="nova-label"></span>`;
|
|
317
|
+
}
|
|
318
|
+
default:
|
|
319
|
+
if (content) {
|
|
320
|
+
return `<span class="${escape(name)}">${content}</span>`;
|
|
321
|
+
}
|
|
322
|
+
return "";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function renderInlineList(nodes) {
|
|
327
|
+
for (const node of nodes) {
|
|
328
|
+
switch (node.tag) {
|
|
329
|
+
case NodeTag.text:
|
|
330
|
+
out.push(escape(node.data.value));
|
|
331
|
+
break;
|
|
332
|
+
case NodeTag.inline_block:
|
|
333
|
+
out.push(renderInline(node.data));
|
|
334
|
+
break;
|
|
335
|
+
case NodeTag.math_inline:
|
|
336
|
+
out.push(`<span class="math">\\(${escape(node.data.source)}\\)</span>`);
|
|
337
|
+
break;
|
|
338
|
+
case NodeTag.math_display:
|
|
339
|
+
out.push(`<div class="math-display">\\[${escape(node.data.source)}\\]</div>`);
|
|
340
|
+
break;
|
|
341
|
+
case NodeTag.interpolation:
|
|
342
|
+
out.push(escape(node.data.expression));
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function renderInlineListText(nodes) {
|
|
349
|
+
let buf = "";
|
|
350
|
+
for (const node of nodes) {
|
|
351
|
+
switch (node.tag) {
|
|
352
|
+
case NodeTag.text:
|
|
353
|
+
buf += escape(node.data.value);
|
|
354
|
+
break;
|
|
355
|
+
case NodeTag.inline_block:
|
|
356
|
+
buf += renderInline(node.data);
|
|
357
|
+
break;
|
|
358
|
+
case NodeTag.math_inline:
|
|
359
|
+
buf += `<span class="math">\\(${escape(node.data.source)}\\)</span>`;
|
|
360
|
+
break;
|
|
361
|
+
case NodeTag.interpolation:
|
|
362
|
+
buf += `<code>#{${escape(node.data.expression)}}</code>`;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return buf;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function renderChildrenInline(children) {
|
|
370
|
+
for (const child of children) {
|
|
371
|
+
if (child.tag === NodeTag.text) out.push(escape(child.data.value));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function collectText(nodes) {
|
|
376
|
+
let buf = "";
|
|
377
|
+
for (const node of nodes) {
|
|
378
|
+
switch (node.tag) {
|
|
379
|
+
case NodeTag.text:
|
|
380
|
+
buf += node.data.value;
|
|
381
|
+
break;
|
|
382
|
+
case NodeTag.inline_block:
|
|
383
|
+
buf += collectText(node.data.content);
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return buf;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function renderList(list, indent) {
|
|
391
|
+
const tag = list.ordered ? "ol" : "ul";
|
|
392
|
+
emitIndent(indent);
|
|
393
|
+
out.push(`<${tag}>`);
|
|
394
|
+
emitLine("");
|
|
395
|
+
for (const item of list.items) {
|
|
396
|
+
emitIndent(indent + 1);
|
|
397
|
+
emitLine("<li>");
|
|
398
|
+
if (item.tag === NodeTag.block) {
|
|
399
|
+
for (const child of item.data.children) renderNode(child, indent + 2);
|
|
400
|
+
renderInlineList(item.data.inline_content);
|
|
401
|
+
} else {
|
|
402
|
+
renderNode(item, indent + 2);
|
|
403
|
+
}
|
|
404
|
+
emitIndent(indent + 1);
|
|
405
|
+
emitLine("</li>");
|
|
406
|
+
}
|
|
407
|
+
emitIndent(indent);
|
|
408
|
+
out.push(`</${tag}>`);
|
|
409
|
+
emitLine("");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function renderTable(table, indent) {
|
|
413
|
+
if (table.caption) {
|
|
414
|
+
emitIndent(indent);
|
|
415
|
+
out.push(`<p><strong>${escape(table.caption)}</strong></p>`);
|
|
416
|
+
emitLine("");
|
|
417
|
+
}
|
|
418
|
+
emitIndent(indent);
|
|
419
|
+
emitLine("<table>");
|
|
420
|
+
|
|
421
|
+
if (table.headers) {
|
|
422
|
+
emitIndent(indent + 1);
|
|
423
|
+
emitLine("<thead>");
|
|
424
|
+
emitIndent(indent + 2);
|
|
425
|
+
emitLine("<tr>");
|
|
426
|
+
for (const h of table.headers) {
|
|
427
|
+
out.push(`<th>${escape(h)}</th>`);
|
|
428
|
+
}
|
|
429
|
+
emitLine("");
|
|
430
|
+
emitIndent(indent + 2);
|
|
431
|
+
emitLine("</tr>");
|
|
432
|
+
emitIndent(indent + 1);
|
|
433
|
+
emitLine("</thead>");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (table.rows && table.rows.length > 0) {
|
|
437
|
+
emitIndent(indent + 1);
|
|
438
|
+
emitLine("<tbody>");
|
|
439
|
+
for (const row of table.rows) {
|
|
440
|
+
emitIndent(indent + 2);
|
|
441
|
+
emitLine("<tr>");
|
|
442
|
+
for (const cell of row) {
|
|
443
|
+
out.push(`<td>${escape(cell)}</td>`);
|
|
444
|
+
}
|
|
445
|
+
emitLine("");
|
|
446
|
+
emitIndent(indent + 2);
|
|
447
|
+
emitLine("</tr>");
|
|
448
|
+
}
|
|
449
|
+
emitIndent(indent + 1);
|
|
450
|
+
emitLine("</tbody>");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (table.data) {
|
|
454
|
+
emitIndent(indent + 1);
|
|
455
|
+
emitLine("<tbody>");
|
|
456
|
+
for (const row of table.data) {
|
|
457
|
+
emitIndent(indent + 2);
|
|
458
|
+
emitLine("<tr>");
|
|
459
|
+
for (const cell of row) {
|
|
460
|
+
out.push(`<td>${escape(cell)}</td>`);
|
|
461
|
+
}
|
|
462
|
+
emitLine("");
|
|
463
|
+
emitIndent(indent + 2);
|
|
464
|
+
emitLine("</tr>");
|
|
465
|
+
}
|
|
466
|
+
emitIndent(indent + 1);
|
|
467
|
+
emitLine("</tbody>");
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
emitIndent(indent);
|
|
471
|
+
emitLine("</table>");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function renderSchema(schema) {
|
|
475
|
+
emitLine('<div class="nova-schema">');
|
|
476
|
+
out.push(`<strong>schema</strong> ${escape(schema.name)} {`);
|
|
477
|
+
emitLine("");
|
|
478
|
+
for (const field of schema.fields) {
|
|
479
|
+
out.push(` <span class="nova-field">${escape(field.name)}: <span class="nova-type">${escape(field.type_name)}`);
|
|
480
|
+
if (field.optional) out.push("?");
|
|
481
|
+
out.push("</span>");
|
|
482
|
+
if (field.tag > 0) out.push(` <span class="nova-tag">@${field.tag}</span>`);
|
|
483
|
+
if (field.default != null) out.push(` = ${escape(field.default)}`);
|
|
484
|
+
emitLine("</span>");
|
|
485
|
+
}
|
|
486
|
+
emitLine("}");
|
|
487
|
+
emitLine("</div>");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function renderService(service) {
|
|
491
|
+
emitLine('<div class="nova-service">');
|
|
492
|
+
out.push(`<strong>service</strong> ${escape(service.name)} {`);
|
|
493
|
+
emitLine("");
|
|
494
|
+
for (const method of service.methods) {
|
|
495
|
+
out.push(` <span class="nova-method">${escape(method.name)}(`);
|
|
496
|
+
for (let i = 0; i < method.params.length; i++) {
|
|
497
|
+
if (i > 0) out.push(", ");
|
|
498
|
+
out.push(`${escape(method.params[i].name)}: ${escape(method.params[i].type_name)}`);
|
|
499
|
+
}
|
|
500
|
+
out.push(")");
|
|
501
|
+
if (method.returns) out.push(` \u2192 ${escape(method.returns)}`);
|
|
502
|
+
emitLine("</span>");
|
|
503
|
+
}
|
|
504
|
+
emitLine("}");
|
|
505
|
+
emitLine("</div>");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function renderBiblioEntry(entry) {
|
|
509
|
+
const author = entry.attrs.author || "";
|
|
510
|
+
const title = entry.attrs.title || "";
|
|
511
|
+
const year = entry.attrs.year || "";
|
|
512
|
+
out.push(`<p id="bib-${escape(entry.key)}">[${escape(entry.key)}] ${escape(author)}, <em>${escape(title)}</em>, ${escape(year)}.</p>`);
|
|
513
|
+
emitLine("");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Main render function body
|
|
517
|
+
emit('<!DOCTYPE html>');
|
|
518
|
+
emit('<html lang="en">');
|
|
519
|
+
emit("<head>");
|
|
520
|
+
emit('<meta charset="UTF-8">');
|
|
521
|
+
emit('<meta name="viewport" content="width=device-width, initial-scale=1.0">');
|
|
522
|
+
|
|
523
|
+
let title = "Nova Document";
|
|
524
|
+
if (doc.tag === NodeTag.document) {
|
|
525
|
+
if (doc.data.meta.title) title = doc.data.meta.title;
|
|
526
|
+
}
|
|
527
|
+
emitLine("<title>");
|
|
528
|
+
out.push(escape(title));
|
|
529
|
+
emitLine("</title>");
|
|
530
|
+
|
|
531
|
+
emit("<style>");
|
|
532
|
+
out.push(DEFAULT_STYLES);
|
|
533
|
+
emit("</style>");
|
|
534
|
+
out.push(MATHJAX_SCRIPT);
|
|
535
|
+
emit("</head>");
|
|
536
|
+
emit("<body>");
|
|
537
|
+
emitLine('<div class="nova-document">');
|
|
538
|
+
|
|
539
|
+
if (doc.tag === NodeTag.document) {
|
|
540
|
+
const meta = doc.data.meta;
|
|
541
|
+
if (Object.keys(meta).length > 0) {
|
|
542
|
+
emitIndent(1);
|
|
543
|
+
emitLine('<div class="nova-meta">');
|
|
544
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
545
|
+
emitIndent(2);
|
|
546
|
+
out.push(`<span class="nova-meta-key">${escape(k)}</span>: <span class="nova-meta-val">${escape(v)}</span>`);
|
|
547
|
+
emitLine("");
|
|
548
|
+
}
|
|
549
|
+
emitIndent(1);
|
|
550
|
+
emitLine("</div>");
|
|
551
|
+
}
|
|
552
|
+
for (const child of doc.data.children) {
|
|
553
|
+
renderNode(child, 1);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
emitLine("</div>");
|
|
558
|
+
emit("</body>");
|
|
559
|
+
emit("</html>");
|
|
560
|
+
|
|
561
|
+
return out.join("");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
module.exports = { render };
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const TokenType = Object.freeze({
|
|
2
|
+
command: "command",
|
|
3
|
+
ident: "ident",
|
|
4
|
+
lparen: "lparen",
|
|
5
|
+
rparen: "rparen",
|
|
6
|
+
lbrace: "lbrace",
|
|
7
|
+
rbrace: "rbrace",
|
|
8
|
+
lbracket: "lbracket",
|
|
9
|
+
rbracket: "rbracket",
|
|
10
|
+
colon: "colon",
|
|
11
|
+
comma: "comma",
|
|
12
|
+
dash: "dash",
|
|
13
|
+
star: "star",
|
|
14
|
+
plus: "plus",
|
|
15
|
+
pipe: "pipe",
|
|
16
|
+
string: "string",
|
|
17
|
+
number: "number",
|
|
18
|
+
bool: "bool",
|
|
19
|
+
null: "null",
|
|
20
|
+
text: "text",
|
|
21
|
+
indent: "indent",
|
|
22
|
+
dedent: "dedent",
|
|
23
|
+
newline: "newline",
|
|
24
|
+
math_inline: "math_inline",
|
|
25
|
+
math_display: "math_display",
|
|
26
|
+
interp_start: "interp_start",
|
|
27
|
+
interp_end: "interp_end",
|
|
28
|
+
at: "at",
|
|
29
|
+
equals: "equals",
|
|
30
|
+
dot: "dot",
|
|
31
|
+
hash: "hash",
|
|
32
|
+
question: "question",
|
|
33
|
+
lt: "lt",
|
|
34
|
+
gt: "gt",
|
|
35
|
+
eof: "eof",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
class Token {
|
|
39
|
+
constructor(type, value = "", line = 0, col = 0) {
|
|
40
|
+
this.type = type;
|
|
41
|
+
this.value = value;
|
|
42
|
+
this.line = line;
|
|
43
|
+
this.col = col;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const NodeTag = Object.freeze({
|
|
48
|
+
document: "document",
|
|
49
|
+
text: "text",
|
|
50
|
+
math_inline: "math_inline",
|
|
51
|
+
math_display: "math_display",
|
|
52
|
+
interpolation: "interpolation",
|
|
53
|
+
block: "block",
|
|
54
|
+
inline_block: "inline_block",
|
|
55
|
+
list_block: "list_block",
|
|
56
|
+
table_block: "table_block",
|
|
57
|
+
macro_def: "macro_def",
|
|
58
|
+
field_def: "field_def",
|
|
59
|
+
schema_def: "schema_def",
|
|
60
|
+
method_def: "method_def",
|
|
61
|
+
service_def: "service_def",
|
|
62
|
+
biblio_entry: "biblio_entry",
|
|
63
|
+
if_block: "if_block",
|
|
64
|
+
for_block: "for_block",
|
|
65
|
+
use_block: "use_block",
|
|
66
|
+
meta_block: "meta_block",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function makeNode(tag, data) {
|
|
70
|
+
return { tag, data };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { TokenType, Token, NodeTag, makeNode };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nova-lang/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Nova: Nested Ordered Versatile Architecture — a programmable markup language CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -15,21 +15,13 @@
|
|
|
15
15
|
"nova": "bin/nova.js"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"build": "zig build",
|
|
20
|
-
"build-release": "zig build -Doptimize=ReleaseSafe",
|
|
21
|
-
"clean": "rm -rf zig-out zig-cache .zig-cache",
|
|
22
|
-
"test": "nova lex examples/basics.nv && nova ast examples/basics.nv"
|
|
18
|
+
"test": "node bin/nova.js lex examples/math.nv && node bin/nova.js ast examples/math.nv && node bin/nova.js examples/math.nv > /dev/null"
|
|
23
19
|
},
|
|
24
20
|
"files": [
|
|
25
21
|
"bin/",
|
|
26
|
-
"
|
|
27
|
-
"build.zig",
|
|
28
|
-
"src/"
|
|
22
|
+
"lib/"
|
|
29
23
|
],
|
|
30
24
|
"engines": {
|
|
31
25
|
"node": ">=16"
|
|
32
|
-
}
|
|
33
|
-
"os": ["darwin", "linux", "win32"],
|
|
34
|
-
"cpu": ["x64", "arm64"]
|
|
26
|
+
}
|
|
35
27
|
}
|
package/build.zig
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
|
|
3
|
-
pub fn build(b: *std.Build) void {
|
|
4
|
-
const target = b.standardTargetOptions(.{});
|
|
5
|
-
const optimize = b.standardOptimizeOption(.{});
|
|
6
|
-
|
|
7
|
-
const exe_mod = b.createModule(.{
|
|
8
|
-
.root_source_file = b.path("src/main.zig"),
|
|
9
|
-
.target = target,
|
|
10
|
-
.optimize = optimize,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const exe = b.addExecutable(.{
|
|
14
|
-
.name = "nova",
|
|
15
|
-
.root_module = exe_mod,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
b.installArtifact(exe);
|
|
19
|
-
|
|
20
|
-
const run_cmd = b.addRunArtifact(exe);
|
|
21
|
-
run_cmd.step.dependOn(b.getInstallStep());
|
|
22
|
-
|
|
23
|
-
const run_step = b.step("run", "Run Nova");
|
|
24
|
-
run_step.dependOn(&run_cmd.step);
|
|
25
|
-
}
|