@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/install.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
const { execSync, spawnSync } = require("child_process");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
const fs = require("fs");
|
|
7
|
-
|
|
8
|
-
const ROOT = path.resolve(__dirname);
|
|
9
|
-
const BIN_DIR = path.join(ROOT, "zig-out", "bin");
|
|
10
|
-
const BIN_NAME = process.platform === "win32" ? "nova.exe" : "nova";
|
|
11
|
-
const BIN_PATH = path.join(BIN_DIR, BIN_NAME);
|
|
12
|
-
|
|
13
|
-
function alreadyBuilt() {
|
|
14
|
-
try {
|
|
15
|
-
return fs.statSync(BIN_PATH).isFile();
|
|
16
|
-
} catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function findZig() {
|
|
22
|
-
try {
|
|
23
|
-
const r = spawnSync("which", ["zig"], { stdio: "pipe" });
|
|
24
|
-
if (r.status === 0) {
|
|
25
|
-
return r.stdout.toString().trim();
|
|
26
|
-
}
|
|
27
|
-
} catch {}
|
|
28
|
-
try {
|
|
29
|
-
const r = spawnSync("where", ["zig"], { stdio: "pipe", shell: true });
|
|
30
|
-
if (r.status === 0) {
|
|
31
|
-
return r.stdout.toString().trim().split("\n")[0];
|
|
32
|
-
}
|
|
33
|
-
} catch {}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function build() {
|
|
38
|
-
if (alreadyBuilt()) {
|
|
39
|
-
console.log("nova: binary already built at " + BIN_PATH);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const zig = findZig();
|
|
44
|
-
if (!zig) {
|
|
45
|
-
console.log(
|
|
46
|
-
"nova: Zig compiler not found. Skipping build.\n" +
|
|
47
|
-
" Install Zig: https://ziglang.org/download/\n" +
|
|
48
|
-
" Then run: npm run build"
|
|
49
|
-
);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.log("nova: building with " + zig + " ...");
|
|
54
|
-
const result = spawnSync(zig, ["build"], {
|
|
55
|
-
cwd: ROOT,
|
|
56
|
-
stdio: "inherit",
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
if (result.status === 0) {
|
|
60
|
-
console.log("nova: built successfully -> " + BIN_PATH);
|
|
61
|
-
} else {
|
|
62
|
-
console.error("nova: build failed (exit code " + result.status + ")");
|
|
63
|
-
process.exit(result.status ?? 1);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
build();
|
package/src/interpreter.zig
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const types = @import("types.zig");
|
|
3
|
-
const Node = types.Node;
|
|
4
|
-
|
|
5
|
-
pub const Env = struct {
|
|
6
|
-
allocator: std.mem.Allocator,
|
|
7
|
-
vars: std.StringHashMap([]const u8),
|
|
8
|
-
macros: std.StringHashMap(types.MacroDef),
|
|
9
|
-
parent: ?*Env,
|
|
10
|
-
|
|
11
|
-
pub fn init(allocator: std.mem.Allocator) Env {
|
|
12
|
-
return .{
|
|
13
|
-
.allocator = allocator,
|
|
14
|
-
.vars = std.StringHashMap([]const u8).init(allocator),
|
|
15
|
-
.macros = std.StringHashMap(types.MacroDef).init(allocator),
|
|
16
|
-
.parent = null,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
pub fn deinit(self: *Env) void {
|
|
21
|
-
self.vars.deinit();
|
|
22
|
-
self.macros.deinit();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
pub fn get(self: *Env, name: []const u8) ?[]const u8 {
|
|
26
|
-
if (self.vars.get(name)) |v| return v;
|
|
27
|
-
if (self.parent) |p| return p.get(name);
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
pub fn set(self: *Env, name: []const u8, value: []const u8) void {
|
|
32
|
-
self.vars.put(name, value) catch {};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
pub fn defMacro(self: *Env, name: []const u8, macro: types.MacroDef) void {
|
|
36
|
-
if (self.parent) |p| {
|
|
37
|
-
p.defMacro(name, macro);
|
|
38
|
-
} else {
|
|
39
|
-
self.macros.put(name, macro) catch {};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
pub fn lookupMacro(self: *Env, name: []const u8) ?types.MacroDef {
|
|
44
|
-
if (self.macros.get(name)) |m| return m;
|
|
45
|
-
if (self.parent) |p| return p.lookupMacro(name);
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
pub fn clone(self: *Env) !Env {
|
|
50
|
-
var e = Env.init(self.allocator);
|
|
51
|
-
e.parent = self;
|
|
52
|
-
return e;
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
pub const Interpreter = struct {
|
|
57
|
-
allocator: std.mem.Allocator,
|
|
58
|
-
global_env: Env,
|
|
59
|
-
|
|
60
|
-
pub fn init(allocator: std.mem.Allocator) Interpreter {
|
|
61
|
-
return .{
|
|
62
|
-
.allocator = allocator,
|
|
63
|
-
.global_env = Env.init(allocator),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
pub fn deinit(self: *Interpreter) void {
|
|
68
|
-
self.global_env.deinit();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
pub fn evalDocument(self: *Interpreter, doc: *Node) !Node {
|
|
72
|
-
const doc_data = &doc.data.document;
|
|
73
|
-
var new_children = std.ArrayList(Node).init(self.allocator);
|
|
74
|
-
for (doc_data.children.items) |child| {
|
|
75
|
-
const result = try self.evalNode(child, &self.global_env);
|
|
76
|
-
if (result) |n| try new_children.append(n);
|
|
77
|
-
}
|
|
78
|
-
doc_data.children = new_children;
|
|
79
|
-
return doc.*;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
fn evalNode(self: *Interpreter, node: Node, env: *Env) !?Node {
|
|
83
|
-
switch (node.tag) {
|
|
84
|
-
.document => return self.evalDocument(@constCast(&node)),
|
|
85
|
-
.block => return self.evalBlock(&node.data.block, env),
|
|
86
|
-
.inline_block => return self.evalInline(&node.data.inline_block, env),
|
|
87
|
-
.text => return self.evalText(&node.data.text, env),
|
|
88
|
-
.interpolation => return self.evalInterpolation(&node.data.interpolation, env),
|
|
89
|
-
.math_inline, .math_display => return node,
|
|
90
|
-
.list_block => return self.evalList(&node.data.list_block, env),
|
|
91
|
-
.table_block => return node,
|
|
92
|
-
.macro_def => {
|
|
93
|
-
env.defMacro(node.data.macro_def.name, node.data.macro_def);
|
|
94
|
-
return null;
|
|
95
|
-
},
|
|
96
|
-
.schema_def, .service_def => return node,
|
|
97
|
-
.if_block => return self.evalIf(&node.data.if_block, env),
|
|
98
|
-
.for_block => return self.evalFor(&node.data.for_block, env),
|
|
99
|
-
.use_block => {
|
|
100
|
-
std.debug.print("Warning: @use \"{s}\" is not yet supported (module system not implemented)\n", .{node.data.use_block.path});
|
|
101
|
-
return null;
|
|
102
|
-
},
|
|
103
|
-
.meta_block => return node,
|
|
104
|
-
else => return node,
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
fn evalBlock(self: *Interpreter, block: *types.Block, env: *Env) !?Node {
|
|
109
|
-
if (block.name.len == 0) {
|
|
110
|
-
block.children = try self.evalChildren(block.children, env);
|
|
111
|
-
return Node.init(.block, .{ .block = block.* });
|
|
112
|
-
}
|
|
113
|
-
const macro = env.lookupMacro(block.name);
|
|
114
|
-
if (macro) |m| {
|
|
115
|
-
return self.expandMacro(&m, block, env);
|
|
116
|
-
}
|
|
117
|
-
block.children = try self.evalChildren(block.children, env);
|
|
118
|
-
block.inline_content = try self.evalInlineList(block.inline_content, env);
|
|
119
|
-
return Node.init(.block, .{ .block = block.* });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
fn evalInline(self: *Interpreter, inl: *types.InlineBlock, env: *Env) !Node {
|
|
123
|
-
inl.content = try self.evalInlineList(inl.content, env);
|
|
124
|
-
return Node.init(.inline_block, .{ .inline_block = inl.* });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
fn evalText(_: *Interpreter, _text: *types.Text, _: *Env) !Node {
|
|
128
|
-
return Node.init(.text, .{ .text = _text.* });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
fn evalInterpolation(_: *Interpreter, interp: *types.Interpolation, _: *Env) !Node {
|
|
132
|
-
return Node.init(.text, .{ .text = .{ .value = interp.expression } });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
fn evalChildren(self: *Interpreter, children: std.ArrayList(Node), env: *Env) !std.ArrayList(Node) {
|
|
136
|
-
var result = std.ArrayList(Node).init(self.allocator);
|
|
137
|
-
for (children.items) |child| {
|
|
138
|
-
const evaled = try self.evalNode(child, env);
|
|
139
|
-
if (evaled) |n| try result.append(n);
|
|
140
|
-
}
|
|
141
|
-
return result;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
fn evalInlineList(self: *Interpreter, nodes: std.ArrayList(Node), env: *Env) !std.ArrayList(Node) {
|
|
145
|
-
var result = std.ArrayList(Node).init(self.allocator);
|
|
146
|
-
for (nodes.items) |node| {
|
|
147
|
-
const evaled = try self.evalNode(node, env);
|
|
148
|
-
if (evaled) |n| try result.append(n);
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
fn evalList(self: *Interpreter, list: *types.ListBlock, env: *Env) !Node {
|
|
154
|
-
var new_items = std.ArrayList(Node).init(self.allocator);
|
|
155
|
-
for (list.items.items) |item| {
|
|
156
|
-
if (item.tag == .block) {
|
|
157
|
-
var blk = item.data.block;
|
|
158
|
-
blk.children = try self.evalChildren(blk.children, env);
|
|
159
|
-
try new_items.append(Node.init(.block, .{ .block = blk }));
|
|
160
|
-
} else {
|
|
161
|
-
try new_items.append(item);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
list.items = new_items;
|
|
165
|
-
return Node.init(.list_block, .{ .list_block = list.* });
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
fn evalIf(self: *Interpreter, ifblk: *types.IfBlock, env: *Env) !?Node {
|
|
169
|
-
const cond = try self.evaluateCondition(ifblk.condition, env);
|
|
170
|
-
const result = if (cond)
|
|
171
|
-
try self.evalChildren(ifblk.then_body, env)
|
|
172
|
-
else
|
|
173
|
-
try self.evalChildren(ifblk.else_body, env);
|
|
174
|
-
if (result.items.len == 1) return result.items[0];
|
|
175
|
-
return Node.init(.document, .{
|
|
176
|
-
.document = .{
|
|
177
|
-
.children = result,
|
|
178
|
-
.meta = std.StringHashMap([]const u8).init(self.allocator),
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
fn evalFor(self: *Interpreter, forblk: *types.ForBlock, env: *Env) !?Node {
|
|
184
|
-
var child_env = try env.clone();
|
|
185
|
-
var result_nodes = std.ArrayList(Node).init(self.allocator);
|
|
186
|
-
|
|
187
|
-
const iterable_val = env.get(forblk.iterable) orelse forblk.iterable;
|
|
188
|
-
var items = std.ArrayList([]const u8).init(self.allocator);
|
|
189
|
-
defer items.deinit();
|
|
190
|
-
|
|
191
|
-
if (std.mem.eql(u8, forblk.iterable, "true") or std.mem.eql(u8, forblk.iterable, "false") or std.mem.eql(u8, forblk.iterable, "null")) {
|
|
192
|
-
} else if (std.ascii.isDigit(forblk.iterable[0]) or forblk.iterable[0] == '-') {
|
|
193
|
-
const count = std.fmt.parseInt(i32, forblk.iterable, 10) catch 1;
|
|
194
|
-
var i: i32 = 0;
|
|
195
|
-
while (i < count) : (i += 1) {
|
|
196
|
-
var buf: [16]u8 = undefined;
|
|
197
|
-
const val = try std.fmt.bufPrint(&buf, "{}", .{i});
|
|
198
|
-
try items.append(try self.allocator.dupe(u8, val));
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
var it = std.mem.splitScalar(u8, iterable_val, ',');
|
|
202
|
-
while (it.next()) |part| {
|
|
203
|
-
const trimmed = std.mem.trim(u8, part, " ");
|
|
204
|
-
if (trimmed.len > 0) try items.append(trimmed);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
for (items.items) |item_val| {
|
|
209
|
-
child_env.set(forblk.var_name, item_val);
|
|
210
|
-
const evaled = try self.evalChildren(forblk.body, &child_env);
|
|
211
|
-
for (evaled.items) |n| try result_nodes.append(n);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return Node.init(.document, .{
|
|
215
|
-
.document = .{
|
|
216
|
-
.children = result_nodes,
|
|
217
|
-
.meta = std.StringHashMap([]const u8).init(self.allocator),
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
fn evaluateCondition(self: *Interpreter, condition: []const u8, env: *Env) !bool {
|
|
223
|
-
if (condition.len == 0) return true;
|
|
224
|
-
const trimmed = std.mem.trim(u8, condition, " ");
|
|
225
|
-
if (std.mem.eql(u8, trimmed, "true")) return true;
|
|
226
|
-
if (std.mem.eql(u8, trimmed, "false")) return false;
|
|
227
|
-
if (std.mem.eql(u8, trimmed, "null")) return false;
|
|
228
|
-
if (std.mem.startsWith(u8, trimmed, "@defined(")) {
|
|
229
|
-
const inner = std.mem.trim(u8, trimmed["@defined(".len..trimmed.len - 1], " ");
|
|
230
|
-
return env.get(inner) != null;
|
|
231
|
-
}
|
|
232
|
-
if (std.mem.startsWith(u8, trimmed, "!")) {
|
|
233
|
-
const rest = std.mem.trim(u8, trimmed[1..], " ");
|
|
234
|
-
return !try self.evaluateCondition(rest, env);
|
|
235
|
-
}
|
|
236
|
-
if (env.get(trimmed)) |val| {
|
|
237
|
-
return !(val.len == 0 or std.mem.eql(u8, val, "false") or std.mem.eql(u8, val, "null"));
|
|
238
|
-
}
|
|
239
|
-
const num = std.fmt.parseInt(i32, trimmed, 10) catch return condition.len > 0;
|
|
240
|
-
return num != 0;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
fn expandMacro(self: *Interpreter, macro: *types.MacroDef, call: *types.Block, env: *Env) !Node {
|
|
244
|
-
var child_env = try env.clone();
|
|
245
|
-
|
|
246
|
-
var arg_idx: usize = 0;
|
|
247
|
-
for (macro.params.items) |param_name| {
|
|
248
|
-
var param_val: []const u8 = "";
|
|
249
|
-
if (arg_idx < call.inline_content.items.len) {
|
|
250
|
-
const arg_node = call.inline_content.items[arg_idx];
|
|
251
|
-
if (arg_node.tag == .text) {
|
|
252
|
-
param_val = arg_node.data.text.value;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
child_env.set(param_name, param_val);
|
|
256
|
-
arg_idx += 1;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
var kw_iter = macro.kwargs.iterator();
|
|
260
|
-
while (kw_iter.next()) |entry| {
|
|
261
|
-
const kw_name = entry.key_ptr.*;
|
|
262
|
-
const call_val = call.attrs.get(kw_name) orelse entry.value_ptr.*;
|
|
263
|
-
child_env.set(kw_name, call_val);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
var evaled_body = std.ArrayList(Node).init(self.allocator);
|
|
267
|
-
for (macro.body.items) |body_node| {
|
|
268
|
-
if (body_node.tag == .block and std.mem.eql(u8, body_node.data.block.name, macro.block_param_name) and macro.has_block_param) {
|
|
269
|
-
for (call.children.items) |call_child| {
|
|
270
|
-
const evaled = try self.evalNode(call_child, &child_env);
|
|
271
|
-
if (evaled) |n| try evaled_body.append(n);
|
|
272
|
-
}
|
|
273
|
-
} else {
|
|
274
|
-
const evaled = try self.evalNode(body_node, &child_env);
|
|
275
|
-
if (evaled) |n| try evaled_body.append(n);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return Node.init(.block, .{
|
|
280
|
-
.block = .{
|
|
281
|
-
.name = macro.name,
|
|
282
|
-
.attrs = std.StringHashMap([]const u8).init(self.allocator),
|
|
283
|
-
.inline_content = std.ArrayList(Node).init(self.allocator),
|
|
284
|
-
.children = evaled_body,
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
};
|