@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/src/lexer.zig DELETED
@@ -1,400 +0,0 @@
1
- const std = @import("std");
2
- const types = @import("types.zig");
3
- const TokenType = types.TokenType;
4
- const Token = types.Token;
5
-
6
- const ESCAPE_MAP = std.ComptimeStringMap(u8, .{
7
- .{ "n", '\n' }, .{ "t", '\t' }, .{ "r", '\r' },
8
- .{ "\"", '"' }, .{ "'", '\'' }, .{ "\\", '\\' },
9
- .{ "#", '#' }, .{ "$", '$' },
10
- });
11
-
12
- const INDENT_WIDTH: u32 = 2;
13
-
14
- pub const Lexer = struct {
15
- allocator: std.mem.Allocator,
16
- source: []const u8,
17
- pos: u32,
18
- line: u32,
19
- col: u32,
20
- tokens: std.ArrayList(Token),
21
- indent_stack: std.ArrayList(u32),
22
- line_start: bool,
23
-
24
- pub fn init(allocator: std.mem.Allocator, source: []const u8) Lexer {
25
- var stack = std.ArrayList(u32).init(allocator);
26
- stack.append(0) catch {};
27
- return .{
28
- .allocator = allocator,
29
- .source = source,
30
- .pos = 0,
31
- .line = 1,
32
- .col = 1,
33
- .tokens = std.ArrayList(Token).init(allocator),
34
- .indent_stack = stack,
35
- .line_start = true,
36
- };
37
- }
38
-
39
- pub fn deinit(self: *Lexer) void {
40
- self.tokens.deinit();
41
- self.indent_stack.deinit();
42
- }
43
-
44
- pub fn getTokens(self: *Lexer) !std.ArrayList(Token) {
45
- try self.tokenize();
46
- return self.tokens;
47
- }
48
-
49
- fn peek(self: *Lexer, offset: u32) u8 {
50
- const idx = self.pos + offset;
51
- return if (idx < self.source.len) self.source[idx] else 0;
52
- }
53
-
54
- fn advance(self: *Lexer) u8 {
55
- const ch = self.source[self.pos];
56
- self.pos += 1;
57
- if (ch == '\n') {
58
- self.line += 1;
59
- self.col = 1;
60
- } else {
61
- self.col += 1;
62
- }
63
- return ch;
64
- }
65
-
66
- fn syntaxError(self: *Lexer, msg: []const u8) !void {
67
- std.debug.print("SyntaxError: {s} at line {}, col {}\n", .{ msg, self.line, self.col });
68
- return error.SyntaxError;
69
- }
70
-
71
- fn emit(self: *Lexer, token_type: TokenType, value: []const u8) !void {
72
- try self.tokens.append(.{
73
- .type = token_type,
74
- .value = value,
75
- .line = self.line,
76
- .col = self.col,
77
- });
78
- }
79
-
80
- fn skipComment(self: *Lexer) !void {
81
- if (self.peek(0) != '/') return;
82
- self.pos += 1;
83
- if (self.peek(0) == '/') {
84
- self.pos += 1;
85
- while (self.pos < self.source.len and self.source[self.pos] != '\n') {
86
- self.pos += 1;
87
- }
88
- } else if (self.peek(0) == '*') {
89
- self.pos += 1;
90
- var depth: u32 = 1;
91
- while (depth > 0 and self.pos < self.source.len) {
92
- if (self.peek(0) == '/' and self.peek(1) == '*') {
93
- self.pos += 2;
94
- depth += 1;
95
- } else if (self.peek(0) == '*' and self.peek(1) == '/') {
96
- self.pos += 2;
97
- depth -= 1;
98
- } else {
99
- self.pos += 1;
100
- }
101
- }
102
- } else {
103
- self.pos -= 1;
104
- }
105
- }
106
-
107
- fn readString(self: *Lexer, quote: u8) ![]const u8 {
108
- var buf = std.ArrayList(u8).init(self.allocator);
109
- defer buf.deinit();
110
- while (self.pos < self.source.len) {
111
- const ch = self.advance();
112
- if (ch == '\\') {
113
- if (self.pos < self.source.len) {
114
- const next = self.advance();
115
- const escaped = ESCAPE_MAP.get(&.{next});
116
- try buf.append('\\');
117
- if (escaped) |e| try buf.append(e) else try buf.append(next);
118
- }
119
- } else if (ch == '#' and self.peek(0) == '{') {
120
- return buf.toOwnedSlice();
121
- } else if (ch == quote) {
122
- return buf.toOwnedSlice();
123
- } else {
124
- try buf.append(ch);
125
- }
126
- }
127
- return error.UnterminatedString;
128
- }
129
-
130
- fn readStringInterp(self: *Lexer, quote: u8) !void {
131
- while (true) {
132
- var buf = std.ArrayList(u8).init(self.allocator);
133
- defer buf.deinit();
134
- var has_content = false;
135
- while (self.pos < self.source.len) {
136
- const ch = self.advance();
137
- if (ch == '\\') {
138
- if (self.pos < self.source.len) {
139
- const next = self.advance();
140
- const escaped = ESCAPE_MAP.get(&.{next});
141
- try buf.append('\\');
142
- if (escaped) |e| try buf.append(e) else try buf.append(next);
143
- has_content = true;
144
- }
145
- } else if (ch == '#' and self.peek(0) == '{') {
146
- self.pos += 1;
147
- if (has_content or buf.items.len > 0) {
148
- try self.emit(.string, try buf.toOwnedSlice());
149
- }
150
- var depth: u32 = 1;
151
- const start = self.pos;
152
- while (self.pos < self.source.len and depth > 0) {
153
- const c = self.source[self.pos];
154
- self.pos += 1;
155
- if (c == '{') {
156
- depth += 1;
157
- } else if (c == '}') {
158
- depth -= 1;
159
- }
160
- }
161
- try self.emit(.interp_start, self.source[start .. self.pos - 1]);
162
- break;
163
- } else if (ch == quote) {
164
- if (has_content or buf.items.len > 0) {
165
- try self.emit(.string, buf.items);
166
- }
167
- return;
168
- } else {
169
- try buf.append(ch);
170
- has_content = true;
171
- }
172
- } else {
173
- return;
174
- }
175
- }
176
- }
177
-
178
- fn readNumber(self: *Lexer) ![]const u8 {
179
- const start = self.pos;
180
- if (self.peek(0) == '+' or self.peek(0) == '-') {
181
- self.pos += 1;
182
- }
183
- if (self.peek(0) == '0' and (self.peek(1) == 'x' or self.peek(1) == 'X')) {
184
- self.pos += 2;
185
- while (self.pos < self.source.len and std.ascii.isHexDigit(self.source[self.pos])) {
186
- self.pos += 1;
187
- }
188
- return self.source[start..self.pos];
189
- }
190
- while (self.pos < self.source.len and (std.ascii.isDigit(self.source[self.pos]) or self.source[self.pos] == '.')) {
191
- self.pos += 1;
192
- }
193
- if (self.pos < self.source.len and (self.source[self.pos] == 'e' or self.source[self.pos] == 'E')) {
194
- self.pos += 1;
195
- if (self.pos < self.source.len and (self.source[self.pos] == '+' or self.source[self.pos] == '-')) {
196
- self.pos += 1;
197
- }
198
- while (self.pos < self.source.len and std.ascii.isDigit(self.source[self.pos])) {
199
- self.pos += 1;
200
- }
201
- }
202
- return self.source[start..self.pos];
203
- }
204
-
205
- fn readIdent(self: *Lexer) []const u8 {
206
- const start = self.pos;
207
- while (self.pos < self.source.len) {
208
- const ch = self.source[self.pos];
209
- if (std.ascii.isAlphanumeric(ch) or ch == '_' or ch == '-') {
210
- self.pos += 1;
211
- } else {
212
- break;
213
- }
214
- }
215
- return self.source[start..self.pos];
216
- }
217
-
218
- fn readText(self: *Lexer) []const u8 {
219
- const start = self.pos;
220
- while (self.pos < self.source.len) {
221
- const ch = self.source[self.pos];
222
- const next = if (self.pos + 1 < self.source.len) self.source[self.pos + 1] else 0;
223
- if (ch == '@' or ch == '{' or ch == '}' or ch == '$' or ch == '\n' or ch == '"' or ch == '\'') break;
224
- if (ch == '/' and (next == '/' or next == '*')) break;
225
- if (ch == '#' and next == '{') break;
226
- self.pos += 1;
227
- }
228
- return self.source[start..self.pos];
229
- }
230
-
231
- fn handleIndent(self: *Lexer) !void {
232
- var count: u32 = 0;
233
- while (self.pos < self.source.len and self.source[self.pos] == ' ') {
234
- self.pos += 1;
235
- count += 1;
236
- }
237
- while (self.pos < self.source.len and self.source[self.pos] == '\t') {
238
- self.pos += 1;
239
- count += INDENT_WIDTH;
240
- }
241
- if (self.pos >= self.source.len) return;
242
- if (self.source[self.pos] == '\n') return;
243
- if (self.source[self.pos] == '#') return;
244
-
245
- const top = self.indent_stack.getLast();
246
- if (count > top) {
247
- try self.indent_stack.append(count);
248
- try self.emit(.indent, "");
249
- } else if (count < top) {
250
- while (self.indent_stack.items.len > 0 and count < self.indent_stack.getLast()) {
251
- _ = self.indent_stack.pop();
252
- try self.emit(.dedent, "");
253
- }
254
- if (self.indent_stack.items.len > 0 and count != self.indent_stack.getLast()) {
255
- return self.syntaxError("Inconsistent indentation");
256
- }
257
- }
258
- }
259
-
260
- fn tokenize(self: *Lexer) !void {
261
- while (self.pos < self.source.len) {
262
- if (self.line_start) {
263
- self.line_start = false;
264
- try self.handleIndent();
265
- if (self.pos >= self.source.len) break;
266
- }
267
-
268
- const ch = self.peek(0);
269
-
270
- if (ch == '\n') {
271
- self.pos += 1;
272
- self.line += 1;
273
- self.col = 1;
274
- self.line_start = true;
275
- try self.emit(.newline, "");
276
- continue;
277
- }
278
-
279
- if (ch == '/' and (self.peek(1) == '/' or self.peek(1) == '*')) {
280
- try self.skipComment();
281
- continue;
282
- }
283
-
284
- if (ch == ' ' or ch == '\t') {
285
- self.pos += 1;
286
- self.col += 1;
287
- continue;
288
- }
289
-
290
- if (ch == '$') {
291
- if (self.peek(1) == '$') {
292
- self.pos += 2;
293
- const start = self.pos;
294
- while (self.pos < self.source.len) {
295
- if (self.peek(0) == '$' and self.peek(1) == '$') {
296
- try self.emit(.math_display, self.source[start..self.pos]);
297
- self.pos += 2;
298
- break;
299
- }
300
- self.pos += 1;
301
- } else {
302
- return self.syntaxError("Unterminated display math");
303
- }
304
- } else {
305
- self.pos += 1;
306
- const start = self.pos;
307
- while (self.pos < self.source.len and self.source[self.pos] != '$') {
308
- self.pos += 1;
309
- }
310
- if (self.pos >= self.source.len) return self.syntaxError("Unterminated inline math");
311
- try self.emit(.math_inline, self.source[start..self.pos]);
312
- self.pos += 1;
313
- }
314
- continue;
315
- }
316
-
317
- if (ch == '@') {
318
- self.pos += 1;
319
- try self.emit(.at, "");
320
- continue;
321
- }
322
-
323
- if (ch == '#') {
324
- if (self.peek(1) == '{') {
325
- self.pos += 2;
326
- var depth: u32 = 1;
327
- const start = self.pos;
328
- while (self.pos < self.source.len and depth > 0) {
329
- const c = self.source[self.pos];
330
- self.pos += 1;
331
- if (c == '{') {
332
- depth += 1;
333
- } else if (c == '}') {
334
- depth -= 1;
335
- }
336
- }
337
- try self.emit(.interp_start, self.source[start .. self.pos - 1]);
338
- continue;
339
- }
340
- }
341
-
342
- if (ch == '"' or ch == '\'') {
343
- self.pos += 1;
344
- try self.readStringInterp(ch);
345
- continue;
346
- }
347
-
348
- if (ch == '(') { self.pos += 1; try self.emit(.lparen, ""); continue; }
349
- if (ch == ')') { self.pos += 1; try self.emit(.rparen, ""); continue; }
350
- if (ch == '{') { self.pos += 1; try self.emit(.lbrace, ""); continue; }
351
- if (ch == '}') { self.pos += 1; try self.emit(.rbrace, ""); continue; }
352
- if (ch == '[') { self.pos += 1; try self.emit(.lbracket, ""); continue; }
353
- if (ch == ']') { self.pos += 1; try self.emit(.rbracket, ""); continue; }
354
- if (ch == ':') { self.pos += 1; try self.emit(.colon, ":"); continue; }
355
- if (ch == ',') { self.pos += 1; try self.emit(.comma, ""); continue; }
356
- if (ch == '-') { self.pos += 1; try self.emit(.dash, ""); continue; }
357
- if (ch == '*') { self.pos += 1; try self.emit(.star, ""); continue; }
358
- if (ch == '+') { self.pos += 1; try self.emit(.plus, ""); continue; }
359
- if (ch == '|') { self.pos += 1; try self.emit(.pipe, ""); continue; }
360
- if (ch == '=') { self.pos += 1; try self.emit(.equals, ""); continue; }
361
- if (ch == '.') { self.pos += 1; try self.emit(.dot, ""); continue; }
362
- if (ch == '?') { self.pos += 1; try self.emit(.question, ""); continue; }
363
- if (ch == '<') { self.pos += 1; try self.emit(.lt, ""); continue; }
364
- if (ch == '>') { self.pos += 1; try self.emit(.gt, ""); continue; }
365
-
366
- if (std.ascii.isDigit(ch) or ((ch == '+' or ch == '-') and self.peek(1) != 0 and std.ascii.isDigit(self.peek(1)))) {
367
- const num = try self.readNumber();
368
- try self.emit(.number, num);
369
- continue;
370
- }
371
-
372
- if (std.ascii.isAlpha(ch) or ch == '_') {
373
- const ident = self.readIdent();
374
- if (std.mem.eql(u8, ident, "true") or std.mem.eql(u8, ident, "false")) {
375
- try self.emit(.bool, ident);
376
- } else if (std.mem.eql(u8, ident, "null")) {
377
- try self.emit(.null, ident);
378
- } else {
379
- try self.emit(.ident, ident);
380
- }
381
- continue;
382
- }
383
-
384
- const text = self.readText();
385
- if (text.len > 0) {
386
- try self.emit(.text, text);
387
- continue;
388
- }
389
-
390
- if (ch == '\r') { self.pos += 1; continue; }
391
- self.pos += 1;
392
- }
393
-
394
- while (self.indent_stack.items.len > 1) {
395
- _ = self.indent_stack.pop();
396
- try self.emit(.dedent, "");
397
- }
398
- try self.emit(.eof, "");
399
- }
400
- };
package/src/main.zig DELETED
@@ -1,147 +0,0 @@
1
- const std = @import("std");
2
- const types = @import("types.zig");
3
- const lexer_mod = @import("lexer.zig");
4
- const parser_mod = @import("parser.zig");
5
- const interpreter_mod = @import("interpreter.zig");
6
- const renderer_mod = @import("renderer.zig");
7
-
8
- pub fn main() !void {
9
- var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
10
- defer arena.deinit();
11
- const allocator = arena.allocator();
12
-
13
- var args_list = std.ArrayList([]const u8).init(allocator);
14
- {
15
- var args_iter = try std.process.Args.Iterator.initAllocator(std.process.args, allocator);
16
- defer args_iter.deinit();
17
- while (try args_iter.next()) |arg| {
18
- try args_list.append(try allocator.dupe(u8, arg));
19
- }
20
- }
21
-
22
- if (args_list.items.len < 2) {
23
- std.debug.print("Usage: nova <input.nv> [output.html]\n", .{});
24
- std.debug.print(" nova lex <input.nv>\n", .{});
25
- std.debug.print(" nova ast <input.nv>\n", .{});
26
- return;
27
- }
28
-
29
- const command = args_list.items[1];
30
- if (std.mem.eql(u8, command, "lex") or std.mem.eql(u8, command, "ast")) {
31
- if (args_list.items.len < 3) {
32
- std.debug.print("Usage: nova {s} <input.nv>\n", .{command});
33
- return;
34
- }
35
- const input_file = args_list.items[2];
36
- const source = try readFile(allocator, input_file);
37
- var lexer = lexer_mod.Lexer.init(allocator, source);
38
- defer lexer.deinit();
39
- const tokens = try lexer.getTokens();
40
- if (std.mem.eql(u8, command, "lex")) {
41
- for (tokens.items) |tok| {
42
- const padding = " "[@tagName(tok.type).len..];
43
- std.debug.print("{s}{s} '{s}' L:{} C:{}\n", .{
44
- @tagName(tok.type), padding, tok.value, tok.line, tok.col,
45
- });
46
- }
47
- } else {
48
- var pool = types.AstPool.init(allocator);
49
- var parser = parser_mod.Parser.init(allocator, tokens, &pool);
50
- const doc = parser.parse() catch |err| {
51
- std.debug.print("Parse error: {}\n", .{err});
52
- return;
53
- };
54
- printAST(doc, 0);
55
- }
56
- return;
57
- }
58
-
59
- const input_file = args_list.items[1];
60
- const output_file = if (args_list.items.len > 2) args_list.items[2] else "";
61
- const source = readFile(allocator, input_file) catch {
62
- std.debug.print("Error: file not found: {s}\n", .{input_file});
63
- return;
64
- };
65
-
66
- var lexer = lexer_mod.Lexer.init(allocator, source);
67
- defer lexer.deinit();
68
- const tokens = lexer.getTokens() catch |err| {
69
- std.debug.print("Lex error: {}\n", .{err});
70
- return;
71
- };
72
-
73
- var pool = types.AstPool.init(allocator);
74
- var parser = parser_mod.Parser.init(allocator, tokens, &pool);
75
- const doc = parser.parse() catch |err| {
76
- std.debug.print("Parse error: {}\n", .{err});
77
- return;
78
- };
79
-
80
- var interp = interpreter_mod.Interpreter.init(allocator);
81
- defer interp.deinit();
82
- const evaled_doc = interp.evalDocument(@constCast(&doc)) catch |err| {
83
- std.debug.print("Interpretation error: {}\n", .{err});
84
- return;
85
- };
86
-
87
- var renderer = renderer_mod.Renderer.init(allocator);
88
- defer renderer.deinit();
89
- const html = renderer.render(evaled_doc) catch |err| {
90
- std.debug.print("Render error: {}\n", .{err});
91
- return;
92
- };
93
-
94
- if (output_file.len > 0) {
95
- const file = std.fs.cwd().createFile(output_file, .{}) catch |err| {
96
- std.debug.print("Error creating output file: {}\n", .{err});
97
- return;
98
- };
99
- defer file.close();
100
- _ = file.write(html) catch {};
101
- std.debug.print("Written to {s}\n", .{output_file});
102
- } else {
103
- std.debug.print("{s}", .{html});
104
- }
105
- }
106
-
107
- fn readFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
108
- const file = std.fs.cwd().openFile(path, .{}) catch return error.FileNotFound;
109
- defer file.close();
110
- return file.readToEndAlloc(allocator, 1_000_000);
111
- }
112
-
113
- fn printAST(node: types.Node, indent: u32) void {
114
- var i: u32 = 0;
115
- while (i < indent) : (i += 1) std.debug.print(" ", .{});
116
- switch (node.tag) {
117
- .document => {
118
- std.debug.print("Document\n", .{});
119
- for (node.data.document.children.items) |child| printAST(child, indent + 1);
120
- },
121
- .text => std.debug.print("Text(value='{s}')\n", .{node.data.text.value}),
122
- .block => {
123
- const b = &node.data.block;
124
- std.debug.print("Block(name='{s}', attrs={any}, inline_content=[], position=null)\n", .{ b.name, b.attrs });
125
- for (b.children.items) |child| printAST(child, indent + 1);
126
- },
127
- .inline_block => {
128
- const ib = &node.data.inline_block;
129
- std.debug.print("InlineBlock(name='{s}', attrs={any}, content=[])\n", .{ ib.name, ib.attrs });
130
- },
131
- .math_inline => std.debug.print("MathInline(source='{s}')\n", .{node.data.math_inline.source}),
132
- .math_display => std.debug.print("MathDisplay(source='{s}')\n", .{node.data.math_display.source}),
133
- .interpolation => std.debug.print("Interpolation(expression='{s}')\n", .{node.data.interpolation.expression}),
134
- .list_block => std.debug.print("ListBlock(ordered={})\n", .{node.data.list_block.ordered}),
135
- .table_block => std.debug.print("TableBlock\n", .{}),
136
- .schema_def => std.debug.print("SchemaDef(name='{s}')\n", .{node.data.schema_def.name}),
137
- .service_def => std.debug.print("ServiceDef(name='{s}')\n", .{node.data.service_def.name}),
138
- .macro_def => std.debug.print("MacroDef(name='{s}')\n", .{node.data.macro_def.name}),
139
- .if_block => std.debug.print("IfBlock(condition='{s}')\n", .{node.data.if_block.condition}),
140
- .for_block => std.debug.print("ForBlock(var='{s}', iterable='{s}')\n", .{ node.data.for_block.var_name, node.data.for_block.iterable }),
141
- .use_block => std.debug.print("UseBlock(path='{s}')\n", .{node.data.use_block.path}),
142
- .meta_block => std.debug.print("MetaBlock\n", .{}),
143
- .biblio_entry => std.debug.print("BiblioEntry(key='{s}')\n", .{node.data.biblio_entry.key}),
144
- .field_def => std.debug.print("FieldDef\n", .{}),
145
- .method_def => std.debug.print("MethodDef\n", .{}),
146
- }
147
- }