Embedded programming language for Zig
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactor and fully implement the lexer/tokenizer

IamPyu f5e8bd4e 1731d287

+296 -166
+1 -1
.dir-locals.el
··· 1 - ((auto-mode-alist . (("\\.zexa\\'" . lisp-mode)))) 1 + ((auto-mode-alist . (("\\.zexa\\'" . scheme-mode))))
+14
LICENSE
··· 1 + BSD Zero Clause License 2 + 3 + Copyright (c) 2025 Pyu <pyucreates@gmail.com> 4 + 5 + Permission to use, copy, modify, and/or distribute this software for any 6 + purpose with or without fee is hereby granted. 7 + 8 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 + PERFORMANCE OF THIS SOFTWARE.
+3 -2
build.zig.zon
··· 3 3 .version = "0.1.0", 4 4 .dependencies = .{ 5 5 .mitochondria = .{ 6 - .url = "git+https://codeberg.org/IamPyu/mitochondria#22189e7af32d924691f3ebaf4719d441e3ec8ea5", 7 - .hash = "mitochondria-0.1.0-RHWHdxuBAAAOkGZKvncoVdrJctbjSaS2xt4iZ2XX6iDO", 6 + .url = "git+https://codeberg.org/IamPyu/mitochondria#ca71810849947cbdfc717db0a9de87011f34c19c", 7 + .hash = "mitochondria-0.1.0-RHWHd0ecAADYvF8d3fbTb7-Gga2LErrVmJ9YeQWdfQH9", 8 8 }, 9 9 }, 10 10 .minimum_zig_version = "0.15.2", ··· 12 12 "build.zig", 13 13 "build.zig.zon", 14 14 "src", 15 + "bin", 15 16 "README.md", 16 17 }, 17 18 .fingerprint = 0xb689341f9d4fe9ac,
+10 -12
src/env.zig
··· 104 104 const slice = try list.toSlice(scope); 105 105 106 106 for (slice) |v| { 107 - if (!v.isNil()) { 108 - std.debug.print("{any} ", .{v.atom.num}); 109 - } 107 + _ = try zexa.std.corelib.print(scope, &.{v}); 110 108 } 111 - std.debug.print("\n", .{}); 112 109 113 110 std.debug.print("{any}\n", .{scope.arena.queryCapacity()}); 114 111 } 115 112 116 - test "dbg" { 113 + test "print" { 117 114 var env = try Environment.init(std.testing.allocator); 118 115 defer env.deinit(); 119 116 120 117 const scope = &env.primary_scope; 121 118 122 119 const list = try Value.initList(scope, &.{ 123 - try Value.initNativeFunc(scope, zexa.std.corelib.dbgFormat), 120 + try Value.initNativeFunc(scope, zexa.std.corelib.print), 124 121 try Value.initFromAny(scope, 3), 125 122 try Value.initFromAny(scope, 8), 126 123 try Value.initFromAny(scope, 435), ··· 155 152 var current = list; 156 153 while (current.getCdr()) |cdr| : (current = cdr) { 157 154 if (current.getCar()) |car| { 158 - _ = try zexa.std.corelib.dbgFormat(scope, &.{car}); 155 + _ = try zexa.std.corelib.print(scope, &.{car}); 159 156 } 160 157 } 161 158 } ··· 168 165 169 166 const list = try Value.initList(scope, &.{ 170 167 try Value.initNativeFunc(scope, zexa.std.corelib.format), 171 - try Value.initString(scope, "hello %{}% %{}% %{}% %{}%"), 172 - try Value.initFromAny(scope, 3), 173 - try Value.initFromAny(scope, 87), 168 + try Value.initString(scope, "%{} hello %{} this is the format %{} string!"), 169 + try Value.initFromAny(scope, 3544), 174 170 try Value.initFromAny(scope, 435), 175 171 try Value.initString(scope, "heigjerof"), 176 172 }); 177 173 178 174 const v = try scope.evalExpr(list); 179 - _ = try zexa.std.corelib.dbgFormat(scope, &.{v}); 175 + _ = try zexa.std.corelib.print(scope, &.{v}); 180 176 181 177 const s = try (try Value.initString(scope, "hey ")).add( 182 178 scope, 183 179 try Value.initString(scope, "cool!"), 184 180 ); 185 - _ = try zexa.std.corelib.dbgFormat(scope, &.{s}); 181 + _ = try zexa.std.corelib.print(scope, &.{s}); 182 + 183 + std.debug.print("{d}\n", .{scope.arena.queryCapacity()}); 186 184 }
+197 -117
src/lexer.zig
··· 15 15 16 16 pub fn deinit(self: *@This(), allocator: Allocator) void { 17 17 switch (self.*) { 18 - .str => |str| { 19 - allocator.free(str); 20 - }, 18 + .str => allocator.free(self.str), 19 + .sym => allocator.free(self.sym), 20 + .num => allocator.free(self.num), 21 21 else => {}, 22 + } 23 + } 24 + 25 + pub fn format(self: *const @This(), writer: *std.Io.Writer) !void { 26 + switch (self.*) { 27 + .lparen => try writer.print("(", .{}), 28 + .rparen => try writer.print(")", .{}), 29 + .quote => try writer.print("'", .{}), 30 + .str => try writer.print("'{s}'", .{self.str}), 31 + .sym => try writer.print("{s}", .{self.sym}), 32 + .num => try writer.print("{s}", .{self.num}), 22 33 } 23 34 } 24 35 }; ··· 37 48 }; 38 49 39 50 test "line_info" { 40 - std.debug.print("{f}\n", .{LineInfo{ .file = "src/lexer.zig", .line = 3, .column = 6 }}); 51 + std.debug.print("{f}\n", .{ 52 + LineInfo{ .file = "src/lexer.zig", .line = 3, .column = 6 }, 53 + }); 41 54 } 42 55 43 56 pub const Token = struct { ··· 45 58 value: TokenValue, 46 59 }; 47 60 48 - pub const LexResult = union(enum) { 49 - pub const Exception = union(enum) { 50 - unexpected_token: struct { LineInfo, u8 }, 51 - extra_decimal_point: struct { LineInfo }, 52 - invalid_number: struct { LineInfo }, 53 - invalid_symbol_char: struct { LineInfo, u8 }, 54 - invalid_escape: struct { LineInfo, u8 }, 55 - string_break: struct { LineInfo }, 56 - 57 - pub fn format(self: *const @This(), writer: *std.Io.Writer) !void { 58 - _ = self; 59 - _ = writer; 60 - } 61 - }; 62 - 63 - exception: Exception, 64 - success: struct { allocator: Allocator, tokens: []Token }, 65 - 66 - pub fn deinit(self: *@This()) void { 67 - switch (self.*) { 68 - .success => |s| { 69 - for (s.tokens) |tok| { 70 - tok.value.deinit(s.allocator); 71 - } 72 - 73 - s.allocator.free(s.tokens); 74 - }, 75 - else => {}, 76 - } 77 - } 78 - }; 79 - 80 - fn exception(c: LexResult.Exception) LexResult { 81 - return LexResult{ .exception = c }; 82 - } 83 - 84 61 pub const CHARS = struct { 85 62 pub const LPAREN: u8 = '('; 86 63 pub const RPAREN: u8 = ')'; ··· 108 85 }; 109 86 } 110 87 111 - pub fn lex(allocator: Allocator, buf: []const u8) !LexResult { 112 - var tokens = try std.ArrayList(Token).initCapacity(allocator, 1000); 113 - defer tokens.deinit(allocator); 114 - var iter = SliceIterator(u8).init(buf); 115 - var line = LineInfo{ .line = 1, .column = 0 }; 88 + fn expr_ignore(c: u8) bool { 89 + return switch (c) { 90 + CHARS.LPAREN, CHARS.RPAREN, CHARS.QUOTE => true, 91 + else => if (std.ascii.isWhitespace(c)) true else false, 92 + }; 93 + } 94 + 95 + pub const Tokenizer = struct { 96 + const Self = @This(); 97 + 98 + allocator: Allocator, 99 + buf: []const u8, 100 + tokens: std.ArrayList(Token), 101 + line: LineInfo = LineInfo{ .line = 1, .column = 1 }, 102 + last_token: ?u8 = null, 103 + 104 + pub const LexError = error{ 105 + UnexpectedToken, 106 + ExtraDecimalPoint, 107 + InvalidNumber, 108 + InvalidSymbol, 109 + InvalidEscape, 110 + StringBreak, 111 + } || std.mem.Allocator.Error; 116 112 117 - while (iter.next()) |c| : (line.column += 1) { 118 - switch (c) { 119 - CHARS.LPAREN => try tokens.append(Token{ 120 - .line = line, 121 - .value = .lparen, 122 - }), 123 - CHARS.RPAREN => try tokens.append(Token{ 124 - .line = line, 125 - .value = .rparen, 126 - }), 127 - CHARS.QUOTE => try tokens.append(Token{ 128 - .line = line, 129 - .value = .quote, 130 - }), 131 - CHARS.COMMENT => { 132 - while (iter.next()) |nc| { 133 - if (nc == '\n') { 134 - line.line += 1; 135 - line.column = 0; 136 - break; 137 - } 138 - } 139 - }, 140 - CHARS.STRDELIM => { 141 - var sb = try StringBuilder.init(allocator, 15); 142 - defer sb.deinit(); 113 + pub fn init(allocator: Allocator, buf: []const u8) !Self { 114 + return .{ 115 + .allocator = allocator, 116 + .buf = buf, 117 + .tokens = try std.ArrayList(Token).initCapacity(allocator, 1000), 118 + }; 119 + } 143 120 144 - var in_escape = false; 121 + pub fn deinit(self: *Self) void { 122 + for (self.tokens.items) |*tok| { 123 + tok.value.deinit(self.allocator); 124 + } 145 125 146 - while (iter.peek()) |nc| : (line.column += 1) { 147 - if (nc == '\n') { 148 - return exception(.{ .string_break = struct { line } }); 149 - } 126 + self.tokens.deinit(self.allocator); 127 + } 128 + 129 + pub fn getTokens(self: *Self) ![]Token { 130 + self.tokens.items; 131 + } 132 + 133 + pub fn lex(self: *Self) LexError!void { 134 + const allocator = self.allocator; 135 + var iter = SliceIterator(u8).init(self.buf); 136 + var tokens = &self.tokens; 137 + var line = &self.line; 150 138 151 - if (nc == CHARS.STRDELIM and !in_escape) { 152 - _ = iter.next(); 153 - break; 139 + while (iter.next()) |c| : (line.column += 1) { 140 + self.last_token = c; 141 + switch (c) { 142 + CHARS.LPAREN => try tokens.append(allocator, Token{ 143 + .line = line.*, 144 + .value = .lparen, 145 + }), 146 + CHARS.RPAREN => try tokens.append(allocator, Token{ 147 + .line = line.*, 148 + .value = .rparen, 149 + }), 150 + CHARS.QUOTE => try tokens.append(allocator, Token{ 151 + .line = line.*, 152 + .value = .quote, 153 + }), 154 + CHARS.COMMENT => { 155 + while (iter.next()) |nc| { 156 + if (nc == '\n') { 157 + line.line += 1; 158 + line.column = 0; 159 + break; 160 + } 154 161 } 162 + }, 163 + CHARS.STRDELIM => { 164 + var sb = try StringBuilder.init(allocator, 15); 165 + defer sb.deinit(); 155 166 156 - if (in_escape) { 157 - switch (nc) { 158 - '\\' => try sb.append("\\"), 159 - 'n' => try sb.append("\n"), 160 - 'r' => try sb.append("\r"), 161 - 't' => try sb.append("\t"), 162 - 'a' => try sb.append("\x07"), 163 - 'b' => try sb.append("\x08"), 164 - CHARS.STRDELIM => try sb.append("\""), 165 - else => {}, 167 + var in_escape = false; 168 + 169 + while (iter.peek()) |nc| : (line.column += 1) { 170 + if (nc == '\n') { 171 + return error.StringBreak; 172 + } 173 + 174 + if (nc == CHARS.STRDELIM and !in_escape) { 175 + _ = iter.next(); 176 + break; 177 + } 178 + 179 + if (in_escape) { 180 + switch (nc) { 181 + '\\' => try sb.append("\\"), 182 + 'n' => try sb.append("\n"), 183 + 'r' => try sb.append("\r"), 184 + 't' => try sb.append("\t"), 185 + 'a' => try sb.append("\x07"), 186 + 'b' => try sb.append("\x08"), 187 + CHARS.STRDELIM => try sb.append("\""), 188 + else => {}, 189 + } 190 + 191 + _ = iter.next(); 192 + in_escape = false; 193 + } else if (nc == '\\') { 194 + in_escape = true; 195 + _ = iter.next(); 196 + } else { 197 + try sb.appendChar(iter.next().?); 166 198 } 199 + } 167 200 168 - _ = iter.next(); 169 - in_escape = false; 170 - } else if (nc == '\\') { 171 - in_escape = true; 172 - _ = iter.next(); 173 - } else { 174 - try sb.append(&.{nc}); 201 + try tokens.append(allocator, Token{ 202 + .line = line.*, 203 + .value = TokenValue{ 204 + .str = try sb.build(allocator), 205 + }, 206 + }); 207 + }, 208 + '0'...'9' => {}, 209 + 210 + '\n' => { 211 + line.line += 1; 212 + line.column = 0; 213 + }, 214 + else => if (sym_compat(c)) { 215 + var sb = try StringBuilder.init(allocator, 15); 216 + defer sb.deinit(); 217 + try sb.appendChar(c); 218 + 219 + while (iter.peek()) |nc| { 220 + if (sym_compat(nc) or num_compat(nc)) { 221 + try sb.appendChar(iter.next().?); 222 + } else if (expr_ignore(nc)) { 223 + break; 224 + } else { 225 + return error.InvalidSymbol; 226 + } 175 227 } 176 - } 228 + 229 + try tokens.append(allocator, Token{ 230 + .line = line.*, 231 + .value = TokenValue{ 232 + .sym = try sb.build(allocator), 233 + }, 234 + }); 235 + } else if (num_compat(c)) { 236 + var sb = try StringBuilder.init(allocator, 15); 237 + defer sb.deinit(); 238 + try sb.appendChar(c); 177 239 178 - try tokens.append(allocator, Token{ 179 - .line = line, 180 - .value = TokenValue{ 181 - .str = sb.build(allocator), 182 - }, 183 - }); 184 - }, 185 - '0'...'9' => { 186 - var sb = try StringBuilder.init(allocator, 15); 187 - defer sb.deinit(); 240 + var has_decimal = false; 188 241 189 - const has_decimal = false; 190 - _ = has_decimal; 191 - }, 192 - '\n' => { 193 - line.line += 1; 194 - line.column = 0; 195 - }, 196 - else => return exception(.{ .unexpected_token = struct { line, c } }), 242 + while (iter.peek()) |nc| : (line.column += 1) { 243 + if (std.ascii.isDigit(nc)) { 244 + try sb.appendChar(iter.next().?); 245 + } else if (nc == '.') { 246 + if (has_decimal) { 247 + return error.ExtraDecimalPoint; 248 + } 249 + has_decimal = true; 250 + try sb.appendChar(iter.next().?); 251 + } 252 + } 253 + 254 + try tokens.append(allocator, Token{ 255 + .line = line.*, 256 + .value = TokenValue{ 257 + .num = try sb.build(allocator), 258 + }, 259 + }); 260 + } else { 261 + if (!std.ascii.isWhitespace(c)) { 262 + return error.UnexpectedToken; 263 + } 264 + }, 265 + } 197 266 } 198 267 } 268 + }; 199 269 200 - return .{ 201 - .success = .{ .allocator = allocator, .tokens = tokens.toOwnedSlice(allocator) }, 270 + test "lex" { 271 + var tokenizer = try Tokenizer.init(std.testing.allocator, @embedFile("./test/test1.zexa")); 272 + defer tokenizer.deinit(); 273 + 274 + tokenizer.lex() catch |err| { 275 + std.debug.print("{any}: {f}: `{c}`\n", .{ err, tokenizer.line, tokenizer.last_token.? }); 276 + return; 202 277 }; 278 + 279 + for (tokenizer.tokens.items) |tok| { 280 + std.debug.print("{f} ", .{tok.value}); 281 + } 282 + std.debug.print("\n", .{}); 203 283 }
+1
src/parser.zig
··· 1 + const std = @import("std");
+8 -6
src/std.zig
··· 5 5 6 6 pub const corelib = @import("./std/core.zig"); 7 7 8 - pub fn loadStd(scope: *Scope) !void { 9 - try scope.insert("dbg", try Value.initFromAny(scope, corelib.dbg)); 10 - try scope.insert("format", try Value.initFromAny(scope, corelib.format)); 11 - try scope.insert("clone", try Value.initFromAny(scope, corelib.clone)); 12 - try scope.insert("append", try Value.initFromAny(scope, corelib.append)); 13 - try scope.insert("add", try Value.initFromAny(scope, corelib.add)); 8 + pub const StdConfiguration = struct { 9 + load_iolib: bool = true, 10 + }; 11 + 12 + pub fn loadStd(scope: *Scope, config: StdConfiguration) !void { 13 + corelib.load(scope); 14 + 15 + if (config.load_iolib) {} 14 16 }
+28 -21
src/std/core.zig
··· 8 8 const Scope = zexa.types.Scope; 9 9 const Value = zexa.types.Value; 10 10 11 - pub fn dbg(scope: *Scope, args: []const *Value) !*Value { 12 - for (args) |v| { 13 - std.debug.print("{any}\n", .{v.*}); 14 - } 15 - 16 - return Value.initNil(scope); 11 + pub fn load(scope: *Scope) !void { 12 + try scope.insert("print", try Value.initNativeFunc(scope, print)); 13 + try scope.insert("format", try Value.initNativeFunc(scope, format)); 14 + try scope.insert("clone", try Value.initNativeFunc(scope, clone)); 15 + try scope.insert("append", try Value.initNativeFunc(scope, append)); 16 + try scope.insert("add", try Value.initNativeFunc(scope, add)); 17 17 } 18 18 19 - pub fn dbgFormat(scope: *Scope, args: []const *Value) !*Value { 20 - for (args) |v| { 21 - std.debug.print("{f}\n", .{v}); 19 + pub fn print(scope: *Scope, args: []const *Value) !*Value { 20 + for (args) |value| { 21 + std.debug.print("{f}\n", .{value}); 22 22 } 23 23 24 24 return Value.initNil(scope); ··· 52 52 53 53 var writer = Io.Writer.Allocating.init(scope.allocator); 54 54 55 - const needle = "%{}%"; 55 + const needle = "%{}"; 56 56 57 57 if (std.mem.indexOf(u8, buf, needle)) |index| { 58 - var biter = SliceIterator(u8).init(buf); 59 - while (biter.peek()) |c| { 60 - if (biter.index == index) { 61 - try writer.writer.print("{s}", .{repstr}); 62 - biter.index += needle.len; 63 - } else { 64 - try writer.writer.print("{s}", .{&.{c}}); 65 - biter.index += 1; 58 + var not_escaped = index == 0; 59 + if (!not_escaped) { 60 + not_escaped = buf[index - 1] != '\\'; 61 + } 62 + 63 + if (not_escaped) { 64 + var biter = SliceIterator(u8).init(buf); 65 + while (biter.peek()) |c| { 66 + if (biter.index == index) { 67 + try writer.writer.print("{s}", .{repstr}); 68 + biter.index += needle.len; 69 + } else { 70 + try writer.writer.print("{s}", .{&.{c}}); 71 + biter.index += 1; 72 + } 66 73 } 67 - } 68 74 69 - scope.allocator.free(buf); 70 - buf = try writer.toOwnedSlice(); 75 + scope.allocator.free(buf); 76 + buf = try writer.toOwnedSlice(); 77 + } 71 78 } 72 79 } 73 80
+1
src/test/test1.zexa
··· 1 + (print (format "hello world"))
+33 -6
src/types.zig
··· 34 34 return s; 35 35 } 36 36 37 + pub fn passTo(self: *Self, other: *Self) void { 38 + other.arena = other.arena.state.promote(self.arena.child_allocator); 39 + } 40 + 37 41 pub fn arenaAllocator(self: *Self) Allocator { 38 42 return self.arena.allocator(); 39 43 } ··· 72 76 .sym => |s| return self.get(s) orelse try Value.initNil(self), 73 77 .quote => |v| return v, 74 78 .native_func => return expr, 79 + .userdata => return expr, 75 80 }, 76 81 .cons => { 77 - const func = try self.evalExpr(expr.getCar().?); 82 + var scope = self.branch(); 83 + defer scope.deinit(); 84 + const func = try scope.evalExpr(expr.getCar().?); 78 85 79 - var args = try std.ArrayList(*Value).initCapacity(self.allocator, 3); 80 - defer args.deinit(self.allocator); 86 + var args = try std.ArrayList(*Value).initCapacity(scope.allocator, 3); 87 + defer args.deinit(scope.allocator); 81 88 82 89 var current = expr.getCdr().?; 83 90 while (current.getCdr()) |cdr| : (current = cdr) { 84 91 if (current.getCar()) |car| { 85 - try args.append(self.allocator, try self.evalExpr(car)); 92 + try args.append(scope.allocator, try scope.evalExpr(car)); 86 93 } 87 94 } 88 95 89 96 switch (func.*) { 90 97 .atom => |atom| switch (atom) { 91 98 .native_func => |f| { 92 - return f(self, args.items); 99 + return f(&scope, args.items); 93 100 }, 94 101 else => return expr, 95 102 }, 96 103 else => return expr, 97 104 } 98 105 106 + scope.passTo(self); 99 107 return Value.initNil(self); 100 108 }, 101 109 } ··· 115 123 Str, 116 124 Quote, 117 125 NativeFunc, 126 + UserData, 127 + 118 128 Cons, 119 129 }; 120 130 ··· 132 142 str: []const u8, 133 143 quote: *Self, 134 144 native_func: NativeFunc, 145 + userdata: *anyopaque, 135 146 }; 136 147 pub const Cons = struct { car: *Value, cdr: *Value }; 137 148 138 149 atom: Atom, 139 150 cons: Cons, 151 + 152 + /// Create a `Value` independent of a `Scope` 153 + pub fn init(allocator: Allocator, raw: Self) !*Self { 154 + const value = try allocator.create(Self); 155 + value.* = raw; 156 + return value; 157 + } 140 158 141 159 pub fn initNil(scope: *Scope) !*Self { 142 160 const atom = try scope.arenaAllocator().create(Self); ··· 198 216 }); 199 217 } 200 218 219 + pub fn initUserData(scope: *Scope, userdata: *anyopaque) !*Self { 220 + return Self.initFrom(scope, .{ 221 + .atom = .{ .userdata = userdata }, 222 + }); 223 + } 224 + 201 225 pub fn initList(scope: *Scope, values: []const *Value) !*Self { 202 226 var cons = try Value.initCons(scope, try Value.initNil(scope), try Value.initNil(scope)); 203 227 ··· 234 258 }, 235 259 }), 236 260 .native_func => |f| return try Value.initNativeFunc(scope, f), 261 + .userdata => |u| return try Value.initUserData(scope, u), 237 262 }, 238 263 .cons => |cons| { 239 264 return try Value.initCons(scope, cons.car, cons.cdr); ··· 319 344 .sym => ValueType.Sym, 320 345 .quote => ValueType.Quote, 321 346 .native_func => ValueType.NativeFunc, 347 + .userdata => ValueType.UserData, 322 348 }, 323 349 .cons => ValueType.Cons, 324 350 }; ··· 327 353 pub fn format(self: *const Self, writer: *std.Io.Writer) !void { 328 354 switch (self.*) { 329 355 .atom => |atom| switch (atom) { 330 - .nil => try writer.print("nil", .{}), 356 + .nil => _ = try writer.write("nil"), 331 357 .bool => |b| try writer.print("{any}", .{b}), 332 358 .num => |n| try writer.print("{d}", .{n}), 333 359 .str => |s| try writer.print("\"{s}\"", .{s}), 334 360 .sym => |s| try writer.print("'{s}", .{s}), 335 361 .quote => |v| try writer.print("{f}", .{v}), 336 362 .native_func => try writer.print("{*}", .{self}), 363 + .userdata => try writer.print("{*}", .{self}), 337 364 }, 338 365 .cons => { 339 366 _ = try writer.write("(");
-1
test/test1.zexa
··· 1 -