@nova-lang/cli 0.1.2 → 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/install.js DELETED
@@ -1,184 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const { execSync, spawnSync } = require("child_process");
5
- const fs = require("fs");
6
- const path = require("path");
7
- const https = require("https");
8
- const { createHash } = require("crypto");
9
-
10
- const ROOT = path.resolve(__dirname);
11
- const BIN_DIR = path.join(ROOT, "bin");
12
- const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf-8"));
13
- const VERSION = PKG.version;
14
-
15
- function platform() {
16
- const os = process.platform;
17
- if (os === "darwin") return "darwin";
18
- if (os === "linux") return "linux";
19
- if (os === "win32") return "win32";
20
- throw new Error("unsupported platform: " + os);
21
- }
22
-
23
- function arch() {
24
- const a = process.arch;
25
- if (a === "x64") return "x64";
26
- if (a === "arm64") return "arm64";
27
- throw new Error("unsupported arch: " + a);
28
- }
29
-
30
- function binaryName() {
31
- const base = ["nova", platform(), arch()].join("-");
32
- return process.platform === "win32" ? base + ".exe" : base;
33
- }
34
-
35
- function binaryPath() {
36
- return path.join(BIN_DIR, binaryName());
37
- }
38
-
39
- function binaryExists() {
40
- try {
41
- return fs.statSync(binaryPath()).isFile();
42
- } catch {
43
- return false;
44
- }
45
- }
46
-
47
- function downloadUrl() {
48
- return [
49
- "https://github.com/nova-lang/nova/releases/download",
50
- "v" + VERSION,
51
- binaryName(),
52
- ].join("/");
53
- }
54
-
55
- function sha256Url() {
56
- return downloadUrl() + ".sha256";
57
- }
58
-
59
- function download(url, dest) {
60
- return new Promise((resolve, reject) => {
61
- const file = fs.createWriteStream(dest);
62
- https
63
- .get(url, { timeout: 30000 }, (res) => {
64
- if (res.statusCode === 302 || res.statusCode === 301) {
65
- file.close();
66
- fs.unlinkSync(dest);
67
- return download(res.headers.location, dest).then(resolve).catch(reject);
68
- }
69
- if (res.statusCode !== 200) {
70
- file.close();
71
- fs.unlinkSync(dest);
72
- return reject(new Error("HTTP " + res.statusCode + " for " + url));
73
- }
74
- res.pipe(file);
75
- file.on("finish", () => {
76
- file.close();
77
- resolve();
78
- });
79
- })
80
- .on("error", (err) => {
81
- file.close();
82
- try { fs.unlinkSync(dest); } catch {}
83
- reject(err);
84
- });
85
- });
86
- }
87
-
88
- function verifyChecksum(filePath, expectedSha256) {
89
- const hash = createHash("sha256");
90
- const data = fs.readFileSync(filePath);
91
- hash.update(data);
92
- return hash.digest("hex") === expectedSha256.trim();
93
- }
94
-
95
- async function downloadBinary() {
96
- const url = downloadUrl();
97
- const dest = binaryPath();
98
- const shaUrl = sha256Url();
99
- const shaDest = dest + ".sha256";
100
-
101
- console.log("nova: downloading " + url);
102
-
103
- try {
104
- await download(shaUrl, shaDest);
105
- const expected = fs.readFileSync(shaDest, "utf-8").split(/\s+/)[0];
106
- fs.unlinkSync(shaDest);
107
-
108
- await download(url, dest);
109
- fs.chmodSync(dest, 0o755);
110
-
111
- if (!verifyChecksum(dest, expected)) {
112
- fs.unlinkSync(dest);
113
- throw new Error("sha256 mismatch");
114
- }
115
-
116
- console.log("nova: downloaded -> " + dest);
117
- return true;
118
- } catch (err) {
119
- try { fs.unlinkSync(dest); } catch {}
120
- try { fs.unlinkSync(shaDest); } catch {}
121
- return false;
122
- }
123
- }
124
-
125
- function findZig() {
126
- try {
127
- const r = spawnSync("which", ["zig"], { stdio: "pipe" });
128
- if (r.status === 0) return r.stdout.toString().trim();
129
- } catch {}
130
- try {
131
- const r = spawnSync("where", ["zig"], { stdio: "pipe", shell: true });
132
- if (r.status === 0) return r.stdout.toString().trim().split("\n")[0];
133
- } catch {}
134
- return null;
135
- }
136
-
137
- function buildFromSource() {
138
- const zig = findZig();
139
- if (!zig) {
140
- console.log(
141
- "nova: no pre-built binary for " + platform() + "-" + arch() + ".\n" +
142
- " Build from source: npm run build (requires Zig compiler)\n" +
143
- " Or download: https://github.com/nova-lang/nova/releases"
144
- );
145
- return false;
146
- }
147
-
148
- console.log("nova: building from source with " + zig + " ...");
149
- const result = spawnSync(zig, ["build", "-Doptimize=ReleaseSafe"], {
150
- cwd: ROOT,
151
- stdio: "inherit",
152
- });
153
- if (result.status !== 0) {
154
- console.error("nova: build failed");
155
- return false;
156
- }
157
-
158
- const built = path.join(ROOT, "zig-out", "bin", process.platform === "win32" ? "nova.exe" : "nova");
159
- const dest = binaryPath();
160
- if (fs.existsSync(built)) {
161
- fs.mkdirSync(path.dirname(dest), { recursive: true });
162
- fs.copyFileSync(built, dest);
163
- fs.chmodSync(dest, 0o755);
164
- console.log("nova: built -> " + dest);
165
- return true;
166
- }
167
- return false;
168
- }
169
-
170
- async function main() {
171
- if (binaryExists()) {
172
- return;
173
- }
174
-
175
- fs.mkdirSync(BIN_DIR, { recursive: true });
176
-
177
- if (await downloadBinary()) return;
178
- buildFromSource();
179
- }
180
-
181
- main().catch((err) => {
182
- console.error("nova: install error:", err.message);
183
- process.exit(1);
184
- });
@@ -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
- };