const std = @import("std"); const Io = std.Io; const zexa = @import("./root.zig"); const Scope = zexa.lang.Scope; const Value = zexa.lang.Value; const AST = zexa.lang.AST; const RuntimeError = zexa.lang.RuntimeError; const util = @import("./util.zig"); const SliceIterator = util.SliceIterator; /// Load the standard library for `scope` pub fn loadCoreLibrary(scope: *Scope) !void { try scope.insertRegistry(&.{ .{ "#t", try Value.initFromAny(scope, true) }, .{ "#f", try Value.initFromAny(scope, false) }, .{ "nil", try Value.initNil(scope) }, .{ "progn", try Value.initNativeMacro(scope, progn) }, .{ "if", try Value.initNativeMacro(scope, @"if") }, .{ "while", try Value.initNativeMacro(scope, @"while") }, .{ "let", try Value.initNativeMacro(scope, let) }, .{ "lambda", try Value.initNativeMacro(scope, lambda) }, .{ "macro", try Value.initNativeMacro(scope, macro) }, .{ "dolist", try Value.initNativeMacro(scope, dolist) }, .{ "eval", try Value.initNativeFunc(scope, eval) }, .{ "define", try Value.initNativeFunc(scope, define) }, .{ "set", try Value.initNativeFunc(scope, set) }, .{ "print", try Value.initNativeFunc(scope, print) }, .{ "format", try Value.initNativeFunc(scope, format) }, .{ "clone", try Value.initNativeFunc(scope, clone) }, .{ "list", try Value.initNativeFunc(scope, list) }, .{ "append", try Value.initNativeFunc(scope, append) }, .{ "map", try Value.initNativeFunc(scope, map) }, .{ "loadlib", try Value.initNativeFunc(scope, loadlib) }, .{ "setexcept", try Value.initNativeFunc(scope, setexcept) }, .{ "throw", try Value.initNativeFunc(scope, throw) }, .{ "add", try Value.initNativeFunc(scope, add) }, .{ "sub", try Value.initNativeFunc(scope, sub) }, .{ "mul", try Value.initNativeFunc(scope, mul) }, .{ "div", try Value.initNativeFunc(scope, div) }, .{ "eql", try Value.initNativeFunc(scope, eql) }, .{ "not", try Value.initNativeFunc(scope, not) }, .{ "and", try Value.initNativeFunc(scope, @"and") }, .{ "or", try Value.initNativeFunc(scope, @"or") }, }); } // -- MACROS -- pub fn progn(scope: *Scope, args: []const *Value) RuntimeError!AST { return try scope.getRoot().createAst(args); } pub fn @"if"(scope: *Scope, args: []const *Value) RuntimeError!AST { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 3) { try util.unexpectedArguments(&sb, .{ .expected = 3, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const p = scope.parent.?; const b = try p.evalExpr(args[0]); if (b.isTruthy()) { return try p.createAst(&.{args[1]}); } else { return try p.createAst(&.{args[2]}); } } pub fn @"while"(scope: *Scope, args: []const *Value) RuntimeError!AST { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len == 0) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = 0, .mode = .at_least }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const p = scope.parent.?; const v = args[0]; const a = args[1..]; while ((try p.evalExpr(v)).isTruthy()) { _ = try p.evalAst(a); } return scope.createAst(&.{}); } pub fn let(scope: *Scope, args: []const *Value) RuntimeError!AST { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len == 0) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = 0, .mode = .at_least }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } if (args[0].getType() != .cons) { try util.invalidType(&sb, .cons, args[0].getType()); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const varlist = args[0]; const ast = args[1..]; var current = varlist; while (current.getCdr()) |cdr| : (current = cdr) { const def = current.getCar().?; if (def.getType() != .cons) { continue; } const sym = def.getCar().?; if (sym.getType() != .sym) { continue; } const symstr = sym.expr.sym; if (def.getCdr().?.getCar()) |val| { try scope.insert(symstr, val); } } const result = try scope.evalAst(ast); return scope.createAst(&.{result}); } pub fn lambda(scope: *Scope, args: []const *Value) RuntimeError!AST { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len == 0) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = 0, .mode = .at_least }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const root = scope.getRoot(); const arglist = args[0]; const body = args[1..]; if (arglist.getType() != .cons) { try util.invalidType(&sb, .cons, args[0].getType()); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } var arga = try std.ArrayList([]const u8).initCapacity(root.allocator, 5); var iter = Value.Iter.init(arglist); while (iter.next()) |current| { const sym = current; if (sym.getType() != .sym) { continue; } const symstr = sym.expr.sym; try arga.append(root.allocator, symstr); } const sl = try arga.toOwnedSlice(root.allocator); defer root.allocator.free(sl); const func = try Value.Function.create(root, sl, @constCast(body)); return root.createAst(&.{try Value.initFunction(root, func)}); } pub fn macro(scope: *Scope, args: []const *Value) RuntimeError!AST { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len == 0) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = 0, .mode = .at_least }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const root = scope.getRoot(); const arglist = args[0]; const body = args[1..]; if (arglist.getType() != .cons) { try util.invalidType(&sb, .cons, args[0].getType()); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } var arga = try std.ArrayList([]const u8).initCapacity(root.allocator, 5); var iter = Value.Iter.init(arglist); while (iter.next()) |current| { const sym = current; if (sym.getType() != .sym) { continue; } const symstr = sym.expr.sym; try arga.append(root.allocator, symstr); } const sl = try arga.toOwnedSlice(root.allocator); defer root.allocator.free(sl); const func = try Value.Macro.create(root, sl, @constCast(body)); return root.createAst(&.{try Value.initMacro(root, func)}); } pub fn dolist(scope: *Scope, args: []const *Value) RuntimeError!AST { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len < 2) { try util.unexpectedArguments(&sb, .{ .expected = 2, .got = args.len, .mode = .at_least }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const var_name = switch (args[0].expr) { .sym => |s| s, else => { try util.invalidType(&sb, .sym, args[0].getType()); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; }, }; var iter = Value.Iter.init(try scope.evalExpr(args[1])); const body = args[2..]; var eval_scope = try scope.branch(); defer eval_scope.deinit(); while (iter.next()) |val| { try eval_scope.insert(var_name, val); _ = try eval_scope.evalAst(body); } return scope.getRoot().createAst(&.{try Value.initNil(scope.getRoot())}); } // -- FUNCTIONS -- pub fn eval(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 1) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } return scope.parent.?.evalExpr(args[0]); } pub fn define(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 2) { try util.unexpectedArguments(&sb, .{ .expected = 2, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const sym = switch (args[0].expr) { .sym => |s| s, else => return RuntimeError.InvalidArguments, }; try scope.parent.?.insert(sym, args[1]); return try Value.initNil(scope); } pub fn set(scope: *Scope, args: []const *Value) RuntimeError!*Value { var iter = SliceIterator(*Value).init(args); var sb = try scope.stringBuilder(); defer sb.deinit(); while (iter.next()) |sym| { const val = iter.next() orelse break; switch (sym.expr) { .sym => |s| { const p = scope.parent.?; if (p.hasKey(s)) { p.get(s).?.* = (try val.clone(p.getRoot())).*; try p.insert(s, val); } else { sb.clear(); try util.undefinedVariable(&sb, s); try scope.setException(sb.written()); return RuntimeError.Exception; } }, else => { sb.clear(); try util.invalidType(&sb, Value.ExprTag.sym, sym.getType()); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; }, } } return Value.initNil(scope); } pub fn print(scope: *Scope, args: []const *Value) RuntimeError!*Value { for (args) |value| { std.debug.print("{f}\n", .{value}); } return Value.initNil(scope); } pub fn format(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len == 0) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = args.len, .mode = .at_least }); try scope.setException(sb.written()); return RuntimeError.Exception; } if (args[0].getType() != .str) { return Value.initNil(scope); } const fmt = args[0].*.expr.str; var buf: []u8 = try scope.allocator.alloc(u8, fmt.len); defer scope.allocator.free(buf); @memcpy(buf, fmt); var iter = SliceIterator(*Value).init(args[1..]); while (iter.next()) |val| { const repstr = blk: { var writer = Io.Writer.Allocating.init(scope.allocator); writer.writer.print("{f}", .{val}) catch return RuntimeError.Other; break :blk try writer.toOwnedSlice(); }; defer scope.allocator.free(repstr); var writer = Io.Writer.Allocating.init(scope.allocator); const needle = "%{}"; if (std.mem.indexOf(u8, buf, needle)) |index| { var not_escaped = index == 0; if (!not_escaped) { not_escaped = buf[index - 1] != '\\'; } if (not_escaped) { var biter = SliceIterator(u8).init(buf); while (biter.peek()) |c| { if (biter.index == index) { writer.writer.print("{s}", .{repstr}) catch return RuntimeError.Other; biter.index += needle.len; } else { writer.writer.print("{s}", .{&.{c}}) catch return RuntimeError.Other; biter.index += 1; } } scope.allocator.free(buf); buf = try writer.toOwnedSlice(); } } } return Value.initString(scope, buf); } pub fn clone(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 1) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = args.len }); try scope.setException(sb.written()); return Value.initNil(scope); } return args[0].clone(scope); } pub fn list(scope: *Scope, args: []const *Value) RuntimeError!*Value { const v = try Value.initCons( scope, try Value.initNil(scope), try Value.initNil(scope), ); var first = true; for (args) |i| { if (first) { v.expr.cons.car = i; first = false; } else { try v.append(scope, i); } } return v; } pub fn append(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len == 0) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = args.len, .mode = .at_least }); try scope.setException(sb.written()); return Value.initNil(scope); } const l = args[0]; const a = args[1..]; for (a) |v| { try l.append(scope, v); } return l; } pub fn map(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 2) { try util.unexpectedArguments(&sb, .{ .expected = 2, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } var func = args[0]; const iterable = try args[1].clone(scope); var iter = Value.Iter.init(iterable); while (iter.next()) |v| { scope.getCallStack().append(func) catch return RuntimeError.External; const out = try func.call(scope, &.{v}); v.* = out.*; } return iterable; } pub fn loadlib(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 1) { sb.clear(); try util.unexpectedArguments(&sb, .{ .expected = 1, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const path = switch (args[0].expr) { .str => |s| s, else => { sb.clear(); try util.invalidType(&sb, .str, args[0].getType()); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; }, }; const realpath = std.fs.cwd().realpathAlloc(scope.allocator, path) catch { try scope.setException("couldn't get realpath for library"); return RuntimeError.External; }; defer scope.allocator.free(realpath); const exists = if (std.fs.accessAbsolute(realpath, .{})) |_| true else |_| false; if (!exists) { std.debug.print("fuck you\n", .{}); return RuntimeError.Exception; } const sym: [:0]const u8 = "load"; var dynlib = std.DynLib.open(realpath) catch |e| { try scope.setException(@errorName(e)); return RuntimeError.External; }; defer dynlib.close(); const lib = dynlib.lookup(zexa.lang.Library, sym) orelse { sb.clear(); try sb.appendFormat("symbol not found: {s}", .{sym}); try scope.setException(sb.written()); return RuntimeError.Exception; }; try scope.getRoot().loadLibrary(lib); return try Value.initNil(scope); } pub fn setexcept(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); if (args.len != 1) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const msg = switch (args[0].expr) { .str => |s| s, else => { try util.invalidType(&sb, .str, args[0].getType()); return RuntimeError.InvalidType; }, }; try scope.setException(msg); return Value.initNil(scope); } pub fn throw(scope: *Scope, args: []const *Value) RuntimeError!*Value { _ = scope; _ = args; return RuntimeError.Exception; } // -- OPERATORS -- pub fn add(scope: *Scope, args: []const *Value) RuntimeError!*Value { if (args.len == 0) { return Value.initNil(scope); } var first = try args[0].clone(scope); for (args[1..]) |v| { first = try first.add(scope, v); } return first; } pub fn sub(scope: *Scope, args: []const *Value) RuntimeError!*Value { if (args.len == 0) { return Value.initNil(scope); } var first = try args[0].clone(scope); for (args[1..]) |v| { first = try first.sub(scope, v); } return first; } pub fn mul(scope: *Scope, args: []const *Value) RuntimeError!*Value { if (args.len == 0) { return Value.initNil(scope); } var first = try args[0].clone(scope); for (args[1..]) |v| { first = try first.mul(scope, v); } return first; } pub fn div(scope: *Scope, args: []const *Value) RuntimeError!*Value { if (args.len == 0) { return Value.initNil(scope); } var first = try args[0].clone(scope); for (args[1..]) |v| { first = try first.div(scope, v); } return first; } pub fn eql(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 2) { try util.unexpectedArguments(&sb, .{ .expected = 2, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const b = args[0].isEqual(args[1]); return Value.initFromAny(scope, b); } pub fn not(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 1) { try util.unexpectedArguments(&sb, .{ .expected = 1, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } return Value.initFromAny(scope, !args[0].isTruthy()); } pub fn @"and"(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 2) { try util.unexpectedArguments(&sb, .{ .expected = 2, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const b = args[0].isTruthy() and args[1].isTruthy(); return Value.initFromAny(scope, b); } pub fn @"or"(scope: *Scope, args: []const *Value) RuntimeError!*Value { var sb = try scope.stringBuilder(); defer sb.deinit(); if (args.len != 2) { try util.unexpectedArguments(&sb, .{ .expected = 2, .got = args.len }); try scope.setException(sb.written()); return RuntimeError.InvalidArguments; } const b = args[0].isTruthy() or args[1].isTruthy(); return Value.initFromAny(scope, b); }