@nova-lang/cli 0.1.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.
@@ -0,0 +1,288 @@
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
+ };
package/src/lexer.zig ADDED
@@ -0,0 +1,400 @@
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
+ };