Embedded programming language for Zig
1
fork

Configure Feed

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

Add exceptions, improve call stack, add iterator for `Value`, and fix lambdas with no arguments

IamPyu f0f0f0b2 30c9d929

+231 -81
+1
build.zig.zon
··· 13 13 "build.zig.zon", 14 14 "src", 15 15 "bin", 16 + "test", 16 17 "README.md", 17 18 }, 18 19 .fingerprint = 0xb689341f9d4fe9ac,
+21
src/env.zig
··· 168 168 169 169 std.debug.print("{d}\n", .{scope.arena.queryCapacity()}); 170 170 } 171 + 172 + test "iter" { 173 + std.debug.print("iter\n", .{}); 174 + var env = try Environment.init(std.testing.allocator); 175 + defer env.deinit(); 176 + 177 + const scope = &env.primary_scope; 178 + 179 + const list = try Value.initList(scope, &.{ 180 + try Value.initString(scope, "hello world"), 181 + try Value.initFromAny(scope, 12345), 182 + try Value.initSymbol(scope, "print"), 183 + try Value.initUserData(scope, scope), 184 + }); 185 + 186 + var iter = Value.Iter.init(list); 187 + 188 + while (iter.next()) |v| { 189 + std.debug.print("{f}\n", .{v}); 190 + } 191 + }
+97 -27
src/lang.zig
··· 11 11 InvalidArguments, 12 12 External, 13 13 Other, 14 + Exception, 14 15 } || std.mem.Allocator.Error; 15 16 16 17 pub const Symbol = []const u8; ··· 74 75 cons: Cons, 75 76 }; 76 77 pub const Cons = struct { car: *Value, cdr: *Value }; 78 + 79 + pub const Iter = struct { 80 + current: ?*Value, 81 + 82 + pub fn init(value: ?*Value) Iter { 83 + return Iter{ .current = value }; 84 + } 85 + 86 + pub fn next(self: *Iter) ?*Value { 87 + const cur = self.current orelse return null; 88 + self.current = cur.getCdr(); 89 + return cur.getCar(); 90 + } 91 + }; 77 92 78 93 atom: Atom, 79 94 ··· 240 255 pub fn getCar(self: *const Self) ?*Value { 241 256 return switch (self.atom) { 242 257 .cons => |cons| cons.car, 258 + // .nil => @constCast(self), 243 259 else => null, 244 260 }; 245 261 } ··· 248 264 pub fn getCdr(self: *const Self) ?*Value { 249 265 return switch (self.atom) { 250 266 .cons => |cons| cons.cdr, 267 + // .nil => @constCast(self), 251 268 else => null, 252 269 }; 253 270 } ··· 255 272 pub fn isNil(self: *const Self) bool { 256 273 return switch (self.atom) { 257 274 .nil => true, 258 - .cons => |cons| cons.car.isNil() and cons.cdr.isNil(), 275 + // .cons => |cons| cons.car.isNil() and cons.cdr.isNil(), 276 + else => false, 277 + }; 278 + } 279 + 280 + pub fn isConsOrNil(self: *const Self) bool { 281 + return switch (self.atom) { 282 + .nil => true, 283 + .cons => true, 259 284 else => false, 260 285 }; 261 286 } ··· 306 331 const slice = try list.toOwnedSlice(scope.allocator); 307 332 defer scope.allocator.free(slice); 308 333 309 - const nslice = try scope.arenaAllocator().alloc(*Value, slice.len); 334 + const nslice = try scope.arena.allocator().alloc(*Value, slice.len); 310 335 @memcpy(nslice, slice); 311 336 312 337 return nslice; ··· 452 477 453 478 switch (self.getType()) { 454 479 .function, .native_func => { 455 - for (arglist.items, 0..) |v, i| { 456 - arglist.items[i] = scope.evalExpr(v) catch |e| return scope.dump_stack_and_error(e); 480 + for (arglist.items) |*v| { 481 + v.* = try scope.evalExpr(v.*); 457 482 } 458 483 }, 459 484 .native_macro => {}, ··· 464 489 switch (self.getType()) { 465 490 .function => { 466 491 const f = self.atom.function; 467 - for (f.arglist, 0..) |s, i| { 468 - try scope.insert(s, arglist.items[i]); 492 + 493 + if (f.arglist.len != arglist.items.len) { 494 + try scope.setException("Function called with incorrect number of arguments"); 495 + 496 + return RuntimeError.InvalidArguments; 469 497 } 498 + 499 + if (f.arglist.len != 0) { 500 + for (f.arglist, 0..) |s, i| { 501 + try scope.insert(s, arglist.items[i]); 502 + } 503 + } 504 + 470 505 break :blk scope.evalAst(f.body); 471 506 }, 472 507 .native_func => { ··· 480 515 const ast = self.atom.native_macro( 481 516 scope, 482 517 arglist.items, 483 - ) catch return error.FunctionFail; 518 + ) catch |e| return e; 484 519 break :blk scope.evalAst(ast); 485 520 }, 486 521 else => unreachable, 487 522 } 488 523 }; 489 - return value catch |e| return scope.dump_stack_and_error(e); 524 + return try value; 490 525 } 491 526 }; 492 527 ··· 515 550 516 551 pub fn deinit(self: *CallStack) void { 517 552 self.stack.deinit(self.allocator); 553 + self.string_arena.deinit(); 518 554 } 519 555 520 556 pub fn append(self: *CallStack, value: *Value) !void { ··· 524 560 try self.stack.append(self.allocator, s); 525 561 } 526 562 527 - pub fn dump(self: *CallStack) void { 563 + pub fn pop(self: *CallStack) void { 564 + _ = self.stack.pop(); 565 + } 566 + 567 + pub fn dump(self: *CallStack, scope: *Scope) void { 568 + if (scope.getException()) |e| { 569 + std.debug.print("Exception message: {s}\n", .{e}); 570 + scope.clearException(); 571 + } 572 + 528 573 while (self.stack.pop()) |v| { 529 574 std.debug.print("{s}\n", .{v}); 530 575 } 531 - 532 - // if (self.parent) |p| { 533 - // p.dump(); 534 - // } 535 576 } 536 577 }; 537 578 ··· 541 582 parent: ?*Scope = null, 542 583 tracked_values: ValMap, 543 584 call_stack: CallStack, 585 + exception: ?[]u8 = null, 544 586 545 587 pub fn init(allocator: Allocator) !Self { 546 588 return Self{ ··· 559 601 } 560 602 self.tracked_values.deinit(self.allocator); 561 603 self.call_stack.deinit(); 562 - 604 + self.clearException(); 563 605 self.arena.deinit(); 564 606 } 565 607 ··· 598 640 return out; 599 641 } 600 642 643 + pub fn setException(self: *Self, msg: []const u8) !void { 644 + const s = self.getRoot(); 645 + s.exception = try s.allocator.alloc(u8, msg.len); 646 + @memcpy(s.exception.?, msg); 647 + } 648 + 649 + pub fn clearException(self: *Self) void { 650 + const s = self.getRoot(); 651 + if (s.exception) |p| { 652 + s.allocator.free(p); 653 + s.exception = null; 654 + } 655 + } 656 + 657 + pub fn getException(self: *Self) ?[]const u8 { 658 + return self.getRoot().exception; 659 + } 660 + 661 + pub fn getCallStack(self: *Self) *CallStack { 662 + return &self.getRoot().call_stack; 663 + } 664 + 601 665 pub fn insert(self: *Self, key: []const u8, value: *Value) !void { 602 666 try self.variables.put(key, value); 603 667 } ··· 612 676 return null; 613 677 } 614 678 679 + pub fn hasKey(self: *Self, key: []const u8) bool { 680 + return self.get(key) != null; 681 + } 682 + 615 683 pub fn createAst(self: *Self, ast: []const *Value) RuntimeError!AST { 616 684 const root = self.getRoot(); 617 685 const out = try root.arena.allocator().alloc(*Value, ast.len); ··· 632 700 .quote => |v| return v, 633 701 .function, .native_func, .native_macro, .userdata => return expr, 634 702 .cons => { 635 - _ = self.call_stack.append(expr.getCar().?) catch return error.External; 703 + const uneval_func = expr.getCar().?; 704 + _ = self.getCallStack().append(uneval_func) catch return error.External; 636 705 637 706 var scope = try self.branch(); 638 707 defer scope.deinit(); 639 - const func = scope.evalExpr(expr.getCar().?) catch |e| return scope.dump_stack_and_error(e); 708 + const func = try scope.evalExpr(uneval_func); 640 709 641 710 var args = try std.ArrayList(*Value).initCapacity(scope.allocator, 3); 642 711 defer args.deinit(scope.allocator); 643 712 644 - var current = expr.getCdr().?; 645 - while (current.getCdr()) |cdr| : (current = cdr) { 646 - if (current.getCar()) |car| { 647 - try args.append(scope.allocator, car); 648 - } 713 + var iter = Value.Iter.init(expr.getCdr()); 714 + while (iter.next()) |arg| { 715 + try args.append(scope.allocator, arg); 649 716 } 650 717 651 - const result = func.call(&scope, args.items) catch |e| return scope.dump_stack_and_error(e); 652 - return try result.clone(self); 718 + const result = func.call(&scope, args.items) catch |e| return self.dump_stack_and_error(e); 719 + 720 + const c = try result.clone(self); 721 + self.getCallStack().pop(); 722 + return c; 653 723 }, 654 724 } 655 725 } ··· 657 727 pub fn evalAst(self: *Self, ast: []const *Value) RuntimeError!*Value { 658 728 var iter = SliceIterator(*Value).init(ast); 659 729 var value: ?*Value = null; 660 - while (iter.next()) |expr| : (value = self.evalExpr(expr) catch |e| return self.dump_stack_and_error(e)) {} 730 + while (iter.next()) |expr| : (value = try self.evalExpr(expr)) {} 661 731 return value orelse try Value.initNil(self); 662 732 } 663 733 664 734 pub fn dump_stack_and_error(self: *Self, e: RuntimeError) RuntimeError { 665 - std.debug.print("\nerror occured in scope {*}: {}\n", .{ self, e }); 666 - std.debug.print("unwinding callstack {*}:\n", .{&self.call_stack}); 667 - self.call_stack.dump(); 735 + std.debug.print("error occured in scope {*}: {}\n", .{ self, e }); 736 + std.debug.print("unwinding callstack {*}:\n", .{&self.getCallStack()}); 737 + self.getCallStack().dump(self); 668 738 return e; 669 739 } 670 740 };
+32 -3
src/parser.zig
··· 65 65 .sym => |sym| return try Value.initSymbol(scope, sym), 66 66 .str => |str| return try Value.initString(scope, str), 67 67 .quote => { 68 - const ntok = tokens.next() orelse return error.UnexpectedEOF; 68 + const ntok = tokens.next() orelse return ParseError.UnexpectedEOF; 69 69 70 70 return try Value.initFromAny( 71 71 scope, ··· 81 81 82 82 var first = true; 83 83 84 + if (tokens.peek()) |ptok| { 85 + switch (ptok.value) { 86 + .rparen => { 87 + _ = tokens.next(); 88 + return list; 89 + }, 90 + else => {}, 91 + } 92 + } 93 + 84 94 while (tokens.next()) |ntok| { 85 95 switch (ntok.value) { 86 - .rparen => break, 96 + .rparen => { 97 + break; 98 + }, 87 99 else => { 88 100 const val = try self.parseExpr(ntok, tokens); 89 101 if (first) { ··· 98 110 99 111 return list; 100 112 }, 101 - .rparen => return error.UnclosedList, 113 + .rparen => return ParseError.UnclosedList, 102 114 } 103 115 } 104 116 }; ··· 119 131 120 132 _ = try env.primary_scope.evalAst(parser.ast.items); 121 133 } 134 + 135 + test "test" { 136 + var tokenizer = try Tokenizer.init(std.testing.allocator, "(lambda () (print hey))"); 137 + defer tokenizer.deinit(); 138 + try tokenizer.lex(); 139 + 140 + var env = try Environment.init(std.testing.allocator); 141 + defer env.deinit(); 142 + 143 + var parser = try Parser.init(std.testing.allocator, &tokenizer, &env); 144 + defer parser.deinit(); 145 + try parser.parse(); 146 + 147 + for (parser.ast.items) |v| { 148 + std.debug.print("value: {f}\n", .{v}); 149 + } 150 + }
+47 -25
src/std/core.zig
··· 3 3 const Io = std.Io; 4 4 5 5 const SliceIterator = @import("mitochondria").SliceIterator; 6 + const StringBuilder = @import("mitochondria").StringBuilder; 6 7 7 8 const zexa = @import("../root.zig"); 8 9 const Scope = zexa.types.Scope; ··· 31 32 try scope.insert("or", try Value.initNativeFunc(scope, @"or")); 32 33 33 34 try scope.insert("eval", try Value.initNativeFunc(scope, eval)); 35 + try scope.insert("define", try Value.initNativeFunc(scope, define)); 34 36 try scope.insert("set", try Value.initNativeFunc(scope, set)); 35 37 try scope.insert("print", try Value.initNativeFunc(scope, print)); 36 - try scope.insert("floop", try Value.initNativeFunc(scope, floop)); 37 38 try scope.insert("format", try Value.initNativeFunc(scope, format)); 38 39 try scope.insert("clone", try Value.initNativeFunc(scope, clone)); 39 40 try scope.insert("list", try Value.initNativeFunc(scope, list)); ··· 48 49 49 50 pub fn @"if"(scope: *Scope, args: []const *Value) RuntimeError!AST { 50 51 if (args.len != 3) { 51 - return error.InvalidArguments; 52 + return RuntimeError.InvalidArguments; 52 53 } 53 54 54 55 const p = scope.parent.?; ··· 63 64 64 65 pub fn @"while"(scope: *Scope, args: []const *Value) RuntimeError!AST { 65 66 if (args.len == 0) { 66 - return error.InvalidArguments; 67 + return RuntimeError.InvalidArguments; 67 68 } 68 69 69 70 const p = scope.parent.?; ··· 80 81 81 82 pub fn let(scope: *Scope, args: []const *Value) RuntimeError!AST { 82 83 if (args.len == 0) { 83 - return error.InvalidArguments; 84 + return RuntimeError.InvalidArguments; 84 85 } 85 86 86 87 if (args[0].getType() != .cons) { 87 - return error.InvalidArguments; 88 + return RuntimeError.InvalidArguments; 88 89 } 89 90 90 91 const varlist = args[0]; ··· 115 116 116 117 pub fn lambda(scope: *Scope, args: []const *Value) RuntimeError!AST { 117 118 if (args.len < 1) { 118 - return error.InvalidArguments; 119 + return RuntimeError.InvalidArguments; 119 120 } 120 121 const root = scope.getRoot(); 121 122 122 123 const arglist = args[0]; 123 124 const body = args[1..]; 124 125 125 - if (arglist.getType() != .cons) 126 - return error.InvalidArguments; 126 + if (!arglist.isConsOrNil()) { 127 + return RuntimeError.InvalidArguments; 128 + } 127 129 128 130 var arga = try std.ArrayList([]const u8).initCapacity(root.allocator, 5); 129 - var current = arglist; 130 - while (current.getCdr()) |cdr| : (current = cdr) { 131 - const sym = current.getCar().?; 131 + var iter = Value.Iter.init(arglist); 132 + 133 + while (iter.next()) |current| { 134 + const sym = current.getCar() orelse break; 132 135 if (sym.getType() != .sym) { 133 136 continue; 134 137 } ··· 144 147 145 148 pub fn dolist(scope: *Scope, args: []const *Value) RuntimeError!AST { 146 149 if (args.len != 3) { 147 - return error.InvalidArguments; 150 + return RuntimeError.InvalidArguments; 148 151 } 149 152 _ = scope; 150 153 // TODO: implement dolist ··· 210 213 211 214 pub fn eql(scope: *Scope, args: []const *Value) RuntimeError!*Value { 212 215 if (args.len != 2) { 213 - return error.InvalidArguments; 216 + return RuntimeError.InvalidArguments; 214 217 } 215 218 216 219 const b = args[0].isEqual(args[1]); ··· 219 222 220 223 pub fn not(scope: *Scope, args: []const *Value) RuntimeError!*Value { 221 224 if (args.len != 1) { 222 - return error.InvalidArguments; 225 + return RuntimeError.InvalidArguments; 223 226 } 224 227 225 228 return Value.initFromAny(scope, !args[0].isTruthy()); ··· 227 230 228 231 pub fn @"and"(scope: *Scope, args: []const *Value) RuntimeError!*Value { 229 232 if (args.len != 2) { 230 - return error.InvalidArguments; 233 + return RuntimeError.InvalidArguments; 231 234 } 232 235 233 236 const b = args[0].isTruthy() and args[1].isTruthy(); ··· 236 239 237 240 pub fn @"or"(scope: *Scope, args: []const *Value) RuntimeError!*Value { 238 241 if (args.len != 2) { 239 - return error.InvalidArguments; 242 + return RuntimeError.InvalidArguments; 240 243 } 241 244 242 245 const b = args[0].isTruthy() or args[1].isTruthy(); ··· 247 250 248 251 pub fn eval(scope: *Scope, args: []const *Value) RuntimeError!*Value { 249 252 if (args.len != 1) { 250 - return error.InvalidArguments; 253 + return RuntimeError.InvalidArguments; 251 254 } 252 255 253 256 return scope.evalExpr(args[0]); 254 257 } 255 258 259 + pub fn define(scope: *Scope, args: []const *Value) RuntimeError!*Value { 260 + if (args.len != 2) { 261 + return RuntimeError.InvalidArguments; 262 + } 263 + 264 + const sym = switch (args[0].atom) { 265 + .sym => |s| s, 266 + else => return RuntimeError.InvalidArguments, 267 + }; 268 + 269 + try scope.parent.?.insert(sym, args[1]); 270 + return try Value.initNil(scope); 271 + } 272 + 256 273 pub fn set(scope: *Scope, args: []const *Value) RuntimeError!*Value { 257 274 var iter = SliceIterator(*Value).init(args); 258 275 ··· 261 278 262 279 switch (sym.atom) { 263 280 .sym => |s| { 264 - try scope.parent.?.insert(s, val); 281 + const p = scope.parent.?; 282 + if (p.hasKey(s)) { 283 + p.get(s).?.* = (try val.clone(p.getRoot())).*; 284 + try p.insert(s, val); 285 + } else { 286 + var sb = try StringBuilder.init(scope.allocator, 30); 287 + try sb.append("Undefined variable: "); 288 + try sb.append(s); 289 + const sbo = try sb.build(scope.allocator); 290 + try scope.setException(sbo); 291 + return RuntimeError.Exception; 292 + } 265 293 }, 266 - else => {}, 294 + else => return RuntimeError.InvalidArguments, 267 295 } 268 296 } 269 297 ··· 275 303 std.debug.print("{f}\n", .{value}); 276 304 } 277 305 278 - return Value.initNil(scope); 279 - } 280 - 281 - pub fn floop(scope: *Scope, args: []const *Value) RuntimeError!*Value { 282 - _ = args; 283 - while (true) {} 284 306 return Value.initNil(scope); 285 307 } 286 308
+16
src/test/test6.zexa
··· 1 + (define 'square (lambda (x) 2 + (mul x x))) 3 + 4 + (define 'cube (lambda (x) 5 + (mul x x x))) 6 + 7 + (print (format "%{} %{}" (square 3) (cube 3))) 8 + 9 + (define 'adder (lambda (x) 10 + (lambda (y) 11 + (add x y)))) 12 + 13 + ;; TODO: implement closures 14 + (print ((adder 3) 4)) 15 + 16 + (print "Hello World!")
+9
src/test/test8.zexa
··· 1 + (define 'a (lambda () 2 + (print "hello"))) 3 + (a) 4 + 5 + (define 'exception-throwing (lambda () 6 + (set 'efuerifreijferjofeoio 6))) 7 + (exception-throwing) 8 + 9 + (print "Hello?")
+7 -8
test/test1.zexa src/test/test1.zexa
··· 1 - (set 'hello "Hello World") 1 + (define 'hello "Hello World") 2 2 (print hello) 3 3 4 - (set 'mylist (list 1 24.3 #t 9 #f 3 nil)) 4 + (define 'mylist (list 1 24.3 #t 9 #f 3 nil)) 5 5 (print mylist) 6 6 7 7 (if #f ··· 17 17 (print (div (add 3 3 4) 2 (mul 3 2))) 18 18 19 19 (print (format "negative: %{}" -5)) 20 - (set 'count 5) 20 + (define 'count 5) 21 21 (while (not (eql count 0)) 22 22 (print count) 23 23 (set 'count (sub count 1))) ··· 27 27 (print (or #f #t)) 28 28 (print (not #t)) 29 29 30 - ;; FIXME: this breaks at 60 for some reason 31 - ;; (set 'nc 0) 32 - ;; (while (not (eql nc 100)) 33 - ;; (set 'nc (\+ nc 1)) 34 - ;; (print (format "I have counted to: %{}" nc))) 30 + (define 'nc 0) 31 + (while (not (eql nc 100)) 32 + (set 'nc (add nc 1)) 33 + (print (format "I have counted to: %{}" nc)))
+1 -1
test/test2.zexa src/test/test2.zexa
··· 1 - (set 'count 5000) 1 + (define 'count 5000) 2 2 3 3 (while (not (eql count 0)) 4 4 (print count)
test/test3.zexa src/test/test3.zexa
test/test4.zexa src/test/test4.zexa
test/test5.zexa src/test/test5.zexa
-17
test/test6.zexa
··· 1 - (set 'square (lambda (x) 2 - (mul x x))) 3 - 4 - (set 'cube (lambda (x) 5 - (mul x x x))) 6 - 7 - (print (format "%{} %{}" (square 3) (cube 3))) 8 - 9 - (set 'adder (lambda (x) 10 - (lambda (y) 11 - (add x y)))) 12 - 13 - ;; TODO: closures currying has to work1 14 - (print ((adder 3) 4)) 15 - 16 - (print "Hello World!") 17 - (print "Thufreufjeivjeiojferijofoej")
test/test7.zexa src/test/test7.zexa