zahl

Log | Files | Refs | README

commit 74a99fe819ad724c164709d45493935ca1ce2c27
parent 1068dd9d4b352cba1f2db33bee4969e00aaba2d0
Author: Thomas Vigouroux <thomas.vigouroux@univ-grenoble-alpes.fr>
Date:   Wed, 12 Jun 2024 14:38:28 +0200

feat: function calls

Diffstat:
Abenchmakrs/fib.scm | 2++
Msrc/chunk.zig | 21+++++++++++++++++----
Msrc/compile.zig | 366+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/main.zig | 58+++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/scan.zig | 40++++++++++++++++++++++++++++------------
Msrc/value.zig | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/vm.zig | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
7 files changed, 438 insertions(+), 221 deletions(-)

diff --git a/benchmakrs/fib.scm b/benchmakrs/fib.scm @@ -0,0 +1,2 @@ +(def! fib (fn* (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))) +(print! (fib 20)) diff --git a/src/chunk.zig b/src/chunk.zig @@ -32,12 +32,21 @@ pub const Op = union(enum) { // ..[a] -> ..[-a] negate, + // ..[args..] -> ..[ret] + call: usize, + // ..[r][l] -> ..[l op r] add, substract, multiply, pow, + // ..[r][l] -> ..[l cmp r] + compare: std.math.CompareOperator, + + // ..[v] -> ..[v] + print, + // ..[r][l] -> ..[l/r][l%r] div }; @@ -86,11 +95,15 @@ pub const Chunk = struct { std.debug.print("{s} {}\n", .{@tagName(tag), num.formatter()}); }, + inline .call, .compare => |cval, tag| { + std.debug.print("{s} {}\n", .{@tagName(tag), cval}); + }, + inline .@"local", .popscope, .jumpfalse, .jump => |vindex, tag| { std.debug.print("{s} {}\n", .{@tagName(tag), vindex}); }, - inline .@"return", .negate, .add, .substract, .multiply, .pow, .div, .pop => |_, tag| { + inline .@"return", .negate, .add, .substract, .multiply, .pow, .div, .pop, .print => |_, tag| { std.debug.print("{s}\n", .{@tagName(tag)}); }, } @@ -98,13 +111,13 @@ pub const Chunk = struct { pub fn disassemble(self: Self) void { for (self.instrs.items, 0..) |instr, pc| { - std.debug.print("{} |\t", .{pc}); + std.debug.print("{:04} |\t", .{pc}); self.disassembleInstr(instr); } } - pub fn lastInstrRef(self: *Self) *Op { - return &self.instrs.items[self.instrs.items.len - 1]; + pub fn lastInstrRef(self: *Self) usize { + return self.instrs.items.len - 1; } pub fn nextPc(self: *const Self) u32 { diff --git a/src/compile.zig b/src/compile.zig @@ -82,172 +82,17 @@ pub const Compiler = struct { }, .special => @panic("Cannot compile special"), .list => |lst| { - const fstsym = lst[0].val.special; - // TODO: maybe this can be something else - switch (fstsym) { - inline .@"+", .@"*" => |tag| { - try self.compileInner(lst[1]); - for (lst[2..]) |inner| { - try self.compileInner(inner); - try self.getChunk().write(if (tag == .@"+") .add else .multiply); - } - }, - .@"-" => { - if (lst.len == 2) { - try self.compileInner(lst[1]); - try self.getChunk().write(.negate); - } else { - try self.compileInner(lst[2]); - try self.compileInner(lst[1]); - try self.getChunk().write(.substract); - } - }, - .@"^" => { - try self.compileInner(lst[2]); - try self.compileInner(lst[1]); - try self.getChunk().write(.pow); - }, - .@"/" => { - @panic("TODO: multiple return values"); - // try self.compileInner(lst[2]); - // try self.compileInner(lst[1]); - // try self.chunk.write(.div); - // try self.chunk.write(.pop); - }, - // (let* (bindings...) expr) - .@"let*" => { - if (lst.len != 3) { - return CompileError.InvalidNumberOfArguments; - } - if (!lst[1].isA(.list)) { - return CompileError.InvalidArgType; - } - - const blist = lst[1].val.list; - if (blist.len % 2 != 0) { - return CompileError.UnbalancedBindings; - } - - for (0..blist.len / 2) |idx| { - const sym = blist[2 * idx]; - const sval = blist[2 * idx + 1]; - - if (!sym.isA(.symbol)) { - return CompileError.InvalidArgType; - } - - // Perform constant folding !! - const storage: Storage = if (self.isConstant(sval)) |cval| - .{ .value = cval } - else blk: { - try self.compileInner(sval); - complog.debug("{s} is at depth {}", .{ sym.val.symbol, self.scope_depth }); - const loc = .{ .local = self.scope_depth }; - self.scope_depth += 1; - break :blk loc; - }; - - try self.sym_map.put(sym.val.symbol, storage); - } - - try self.compileInner(lst[2]); - - // Now, remove the bindings of the variables - var to_pop: usize = 0; - for (0..blist.len / 2) |idx| { - const got = self.sym_map.fetchRemove(blist[2 * idx].val.symbol).?; - switch (got.value) { - .local => { - to_pop += 1; - self.scope_depth -= 1; - }, - .value => {}, - } - } - if (to_pop != 0) { - try self.getChunk().write(.{ .popscope = to_pop }); - } - }, - - // (if cond then else) - .@"if" => { - if (lst.len != 4) { - return CompileError.InvalidNumberOfArguments; - } - - // Generate cond - try self.compileInner(lst[1]); - - // If falsy then jump to else - try self.getChunk().write(.{ .jumpfalse = undefined }); - const jmpthen = self.getChunk().lastInstrRef(); - - try self.compileInner(lst[2]); - - try self.getChunk().write(.{ .jump = undefined }); - const jmpelse = self.getChunk().lastInstrRef(); - - jmpthen.* = .{ .jumpfalse = self.getChunk().nextPc() }; - - try self.compileInner(lst[3]); - jmpelse.* = .{ .jump = self.getChunk().nextPc() }; - }, - - // (def! name value) - .@"def!" => { - if (lst.len != 3) { - return CompileError.InvalidNumberOfArguments; - } - - if (!lst[1].isA(.symbol)) { - return CompileError.InvalidArgType; - } - - const sym = try self.getChunk().addConstant(lst[1].ref()); - - try self.compileInner(lst[2]); - - try self.getChunk().write(.{ .define_global = sym }); - }, - - // (fn* (args...) expr) - .@"fn*" => { - if (lst.len != 3) { - return CompileError.InvalidNumberOfArguments; - } - - if (!lst[1].isA(.list)) { - return CompileError.InvalidArgType; - } - - const args = lst[1].val.list; - - for (args) |arg| { - if (!arg.isA(.symbol)) { - return CompileError.InvalidArgType; - } - } - - const arity = args.len; - - var inner = try Compiler.init(self.all, lst[2]); - defer inner.deinit(); - - // Declare the parameters - for (args) |arg| { - const loc: Storage = .{.local = inner.scope_depth}; - inner.scope_depth+=1; - try inner.sym_map.put(arg.val.symbol, loc); - } - - // Now finish compilation - var res = try inner.compile(); - res.arity = arity; - const valres = try v.Value.newFunction(self.all, res); - - const vindex = try self.getChunk().addConstant(valres); - try self.getChunk().write(.{.constant = vindex}); + if (lst[0].isA(.symbol)) { + // First, push the arguments one by one + for (lst[1..]) |param| { + try self.compileInner(param); } + + const symc = try self.getChunk().addConstant(lst[0].ref()); + try self.getChunk().write(.{ .get_global = symc }); + try self.getChunk().write(.{ .call = lst.len - 1 }); + } else { + try self.compileSpecial(lst[0].val.special, lst); } }, .number, .nil, .true, .false => { @@ -257,10 +102,201 @@ pub const Compiler = struct { } } + fn compileSpecial(self: *Self, name: v.Special, lst: []*v.Value) !void { + switch (name) { + inline .@"+", .@"*" => |tag| { + try self.compileInner(lst[1]); + for (lst[2..]) |inner| { + try self.compileInner(inner); + try self.getChunk().write(if (tag == .@"+") .add else .multiply); + } + }, + .@"-" => { + if (lst.len == 2) { + try self.compileInner(lst[1]); + try self.getChunk().write(.negate); + } else { + try self.compileInner(lst[2]); + try self.compileInner(lst[1]); + try self.getChunk().write(.substract); + } + }, + + inline .@"/", .@"%" => |tag| { + try self.compileInner(lst[2]); + try self.compileInner(lst[1]); + try self.getChunk().write(.div); + try self.getChunk().write(switch (tag) { + .@"/" => .pop, + .@"%" => .{ .popscope = 1 }, + else => @compileError("Unreachable") + }); + }, + + inline .@"=", .@"<", .@"^" => |val| { + try self.compileInner(lst[2]); + try self.compileInner(lst[1]); + try self.getChunk().write(switch (val) { + .@"^" => .pow, + .@"=" => .{ .compare = .eq }, + .@"<" => .{ .compare = .lt }, + else => @compileError("unreachable"), + }); + }, + // (let* (bindings...) expr) + .@"let*" => { + if (lst.len != 3) { + return CompileError.InvalidNumberOfArguments; + } + if (!lst[1].isA(.list)) { + return CompileError.InvalidArgType; + } + + const blist = lst[1].val.list; + if (blist.len % 2 != 0) { + return CompileError.UnbalancedBindings; + } + + for (0..blist.len / 2) |idx| { + const sym = blist[2 * idx]; + const sval = blist[2 * idx + 1]; + + if (!sym.isA(.symbol)) { + return CompileError.InvalidArgType; + } + + // Perform constant folding !! + const storage: Storage = if (self.isConstant(sval)) |cval| + .{ .value = cval } + else blk: { + try self.compileInner(sval); + complog.debug("{s} is at depth {}", .{ sym.val.symbol, self.scope_depth }); + const loc = .{ .local = self.scope_depth }; + self.scope_depth += 1; + break :blk loc; + }; + + try self.sym_map.put(sym.val.symbol, storage); + } + + try self.compileInner(lst[2]); + + // Now, remove the bindings of the variables + var to_pop: usize = 0; + for (0..blist.len / 2) |idx| { + const got = self.sym_map.fetchRemove(blist[2 * idx].val.symbol).?; + switch (got.value) { + .local => { + to_pop += 1; + self.scope_depth -= 1; + }, + .value => {}, + } + } + if (to_pop != 0) { + try self.getChunk().write(.{ .popscope = to_pop }); + } + }, + + // (if cond then else) + .@"if" => { + if (lst.len != 4) { + return CompileError.InvalidNumberOfArguments; + } + + // Generate cond + try self.compileInner(lst[1]); + + // If falsy then jump to else + try self.getChunk().write(.{ .jumpfalse = undefined }); + const jmpthen = self.getChunk().lastInstrRef(); + + try self.compileInner(lst[2]); + + try self.getChunk().write(.{ .jump = undefined }); + const jmpelse = self.getChunk().lastInstrRef(); + + self.getChunk().instrs.items[jmpthen] = .{ .jumpfalse = self.getChunk().nextPc() }; + complog.debug("Backpatched jumpfalse: {}", .{jmpthen}); + + try self.compileInner(lst[3]); + self.getChunk().instrs.items[jmpelse] = .{ .jump = self.getChunk().nextPc() }; + complog.debug("Backpatched jump: {}", .{jmpelse}); + }, + + // (def! name value) + .@"def!" => { + if (lst.len != 3) { + return CompileError.InvalidNumberOfArguments; + } + + if (!lst[1].isA(.symbol)) { + return CompileError.InvalidArgType; + } + + const sym = try self.getChunk().addConstant(lst[1].ref()); + + try self.compileInner(lst[2]); + + try self.getChunk().write(.{ .define_global = sym }); + }, + + // (fn* (args...) expr) + .@"fn*" => { + if (lst.len != 3) { + return CompileError.InvalidNumberOfArguments; + } + + if (!lst[1].isA(.list)) { + return CompileError.InvalidArgType; + } + + const args = lst[1].val.list; + + for (args) |arg| { + if (!arg.isA(.symbol)) { + return CompileError.InvalidArgType; + } + } + + const arity = args.len; + + var inner = try Compiler.init(self.all, lst[2]); + defer inner.deinit(); + + // Declare the parameters + for (args) |arg| { + const loc: Storage = .{.local = inner.scope_depth}; + inner.scope_depth+=1; + try inner.sym_map.put(arg.val.symbol, loc); + } + + // Now finish compilation + var res = try inner.compile(); + res.arity = arity; + const valres = try v.Value.newFunction(self.all, res); + + const vindex = try self.getChunk().addConstant(valres); + try self.getChunk().write(.{.constant = vindex}); + }, + + // (print expr) + .@"print!" => { + try self.compileInner(lst[1]); + try self.getChunk().write(.print); + } + } + } + pub fn compile(self: *Self) CompileError!v.Function { try self.compileInner(self.val); try self.getChunk().write(.@"return"); + if (std.log.logEnabled(.debug, .compile)) { + complog.debug("Compiled version of {}", .{self.val.formatter()}); + self.func.code.disassemble(); + } + return self.func; } }; diff --git a/src/main.zig b/src/main.zig @@ -7,11 +7,12 @@ const cl = @cImport(@cInclude("crossline.h")); const s = @import("scan.zig"); const comp = @import("compile.zig"); const symintern = @import("symintern.zig"); +const builtin = @import("builtin"); const GPAll = std.heap.GeneralPurposeAllocator(.{}); pub const std_options = .{ - .log_level = std.log.Level.debug, + .log_level = if (builtin.mode == .Debug) std.log.Level.debug else .info, .log_scope_levels = &.{.{ .scope = .read, .level = .err }}, .logFn = myLogFn, }; @@ -22,11 +23,8 @@ pub fn myLogFn( comptime format: []const u8, args: anytype, ) void { - // Ignore all non-error logging from sources other than - // .my_project, .nice_library and the default const scope_prefix = "(" ++ @tagName(scope) ++ "): "; - // Print the message to stderr, silently ignoring any errors std.debug.lockStdErr(); defer std.debug.unlockStdErr(); const stderrf = std.io.getStdErr(); @@ -47,34 +45,65 @@ pub fn myLogFn( pub fn main() !void { var gp = GPAll{}; - const all = gp.allocator(); + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer std.debug.assert(gp.deinit() == .ok); + const all = if (builtin.mode == .Debug) gp.allocator() else arena.allocator(); + + defer if (builtin.mode == .Debug) std.debug.assert(gp.deinit() == .ok); symintern.init(all); defer symintern.deinit(); + try value.init(all); + defer value.deinit(); + var v = try vm.VM.init(all); defer v.deinit(); var args = std.process.args(); _ = args.next(); - if (args.next()) |file| { + var offset: ?usize = null; + + if (args.next()) |path| { // TODO: file parsing stuff - _ = file; + var file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); + + const content = try file.readToEndAlloc(all, 10 * 1024 * 1024); + defer all.free(content); + + var reader = try s.Reader.init(content, all, &offset); + + while (reader.readValue(&offset)) |val| { + defer val.unref(); + + var compiler = try comp.Compiler.init(all, val); + defer compiler.deinit(); + + var c = try compiler.compile(); + defer c.deinit(); + + const result = try v.interpret(&c); + defer result.unref(); + std.debug.assert(v.stack.items.len == 0); + } else |err| { + if (err != error.Eof) { + std.log.err("Parse error {} at {}, char '{c}' ({})", .{err, offset.?, content[offset.?], content[offset.?]}); + return err; + } + } } else { var buf: [1024:0]u8 = undefined; - const stdout = std.io.getStdOut(); while (cl.crossline_readline("> ", &buf, buf.len)) |obuf| { const input = obuf[0..std.mem.len(obuf)]; - var reader = try s.Reader.init(input, all); + var reader = try s.Reader.init(input, all, &offset); - var val = reader.readValue() catch { - std.log.err("Parsing error", .{}); + var val = reader.readValue(&offset) catch |err| { + std.log.err("Parsing error {} at {}, char '{c}'", .{err, offset.?, obuf[offset.?]}); continue; }; defer val.unref(); @@ -85,9 +114,6 @@ pub fn main() !void { var c = try compiler.compile(); defer c.deinit(); - std.log.debug("Compiled version of {}", .{val.formatter()}); - c.code.disassemble(); - const result = try v.interpret(&c); defer result.unref(); std.debug.assert(v.stack.items.len == 0); @@ -95,6 +121,4 @@ pub fn main() !void { try std.fmt.format(stdout.writer(), "Result: {}\n", .{result.formatter()}); } } - - // try v.interpret(&c); } diff --git a/src/scan.zig b/src/scan.zig @@ -26,7 +26,7 @@ pub const Reader = struct { curtok: Token, all: std.mem.Allocator, - pub fn init(input: []const u8, all: std.mem.Allocator) !Self { + pub fn init(input: []const u8, all: std.mem.Allocator, err_offset: *?usize) !Self { var self: Self = .{ .input = input, .curpos = 0, @@ -34,7 +34,7 @@ pub const Reader = struct { .all = all, }; - try self.nextTok(); + try self.nextTok(err_offset); return self; } @@ -44,7 +44,8 @@ pub const Reader = struct { (ascii.isWhitespace(self.input[self.curpos]) or self.input[self.curpos] == ';')) : (self.curpos += 1) { - // skip whitespace and ',' + readlog.debug("Skip over offset {} char '{c}'", .{self.curpos, self.input[self.curpos]}); + // skip comments if (self.input[self.curpos] == ';') { const endcomment = std.mem.indexOfScalar(u8, self.input[self.curpos..], '\n') orelse { self.curpos = self.input.len; @@ -66,23 +67,27 @@ pub const Reader = struct { return self.curtok; } - pub fn nextTok(self: *Reader) !void { + pub fn nextTok(self: *Reader, err_offset: *?usize) !void { self.skipWhitespaceComment(); if (self.curpos >= self.input.len) { self.curtok = .eof; return; } + errdefer if (err_offset.* == null) { + err_offset.* = self.curpos; + }; + self.curtok = switch (self.input[self.curpos]) { inline '(', ')' => |val| onechar: { self.curpos += 1; break :onechar switch (val) { '(' => .oparen, ')' => .cparen, - else => @panic("Unreachable") + else => @compileError("Unreachable") }; }, - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => .{ .number = try self.tokInt() }, + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => .{ .number = try self.tokInt(err_offset) }, else => sym: { var offset = self.curpos; while (offset < self.input.len and isSymChar(self.input[offset])) : (offset += 1) {} @@ -97,8 +102,9 @@ pub const Reader = struct { readlog.debug("Curtok: {}", .{self.curtok}); } - fn tokInt(self: *Self) !v.Int { + fn tokInt(self: *Self, err_offset: *?usize) !v.Int { var offset = self.curpos; + errdefer err_offset.* = offset; // TODO: hex/binary... while (offset < self.input.len and ascii.isDigit(self.input[offset])) : (offset += 1) {} @@ -112,9 +118,14 @@ pub const Reader = struct { } // Reader functions - pub fn readValue(self: *Self) !*v.Value { + pub fn readValue(self: *Self, err_offset: *?usize) !*v.Value { + errdefer if (err_offset.* == null) { + err_offset.* = self.curpos; + }; + switch (self.peekTok()) { .oparen => { + readlog.debug("Start parse list", .{}); var acc = std.ArrayList(*v.Value).init(self.all); defer acc.deinit(); errdefer { @@ -123,21 +134,26 @@ pub const Reader = struct { } } - try self.nextTok(); - while (self.peekTok() != .cparen) : (try self.nextTok()) { + try self.nextTok(err_offset); + while (self.peekTok() != .cparen) { if (self.peekTok() == .eof) { return error.Eof; } - try acc.append(try self.readValue()); + try acc.append(try self.readValue(err_offset)); } + // Consume the closing parenthesis + try self.nextTok(err_offset); return v.Value.newList(self.all, try acc.toOwnedSlice()); }, .number => |num| { + try self.nextTok(err_offset); return v.Value.newInt(self.all, num); }, .symbol => |sym| { + try self.nextTok(err_offset); + inline for (comptime std.enums.values(v.Special)) |tag| { if (ueql(sym, @tagName(tag))) { return v.Value.newSpecial(self.all, tag); @@ -153,7 +169,7 @@ pub const Reader = struct { else v.Value.newSymbol(self.all, try symintern.intern(sym)); }, - .cparen, .eof => return error.Unexpected + .cparen, .eof => return error.Eof } } }; diff --git a/src/value.zig b/src/value.zig @@ -9,12 +9,18 @@ pub const Special = enum { @"*", @"^", @"-", + @"%", @"let*", @"if", @"def!", + @"print!", @"fn*", + + // Comparisons + @"=", + @"<" }; pub const Function = struct { @@ -69,8 +75,16 @@ pub const Value = struct { } pub fn newInt(all: std.mem.Allocator, val: anytype) !*Self { - const payload = if (@TypeOf(val) == Int) val else (try Int.initSet(all, val)); - return try Self.init(.{ .number = payload }, all); + var payload = if (@TypeOf(val) == Int) val else (try Int.initSet(all, val)); + + if (intcache.get(payload)) |cached| { + payload.deinit(); + return cached.ref(); + } else { + var new = try Self.init(.{ .number = payload }, all); + try intcache.put(payload, new); + return new.ref(); + } } // orig has to be allocated with all @@ -92,7 +106,8 @@ pub const Value = struct { } pub fn newBool(all: std.mem.Allocator, val: bool) !*Self { - return try Self.init(if (val) .true else .false, all); + _ = all; + return if (val) trueval.ref() else falseval.ref(); } pub fn newFunction(all: std.mem.Allocator, val: Function) !*Self { @@ -120,8 +135,8 @@ pub const Value = struct { pub fn deinit(self: *Self) void { vallog.debug("Destroy {}", .{self.formatter()}); switch (self.val) { - .number => |*num| { - num.deinit(); + .number => |_| { + // XXX: numbers are freed when the cache is cleared }, .list => |lst| { for (lst) |val| { @@ -184,3 +199,45 @@ pub const Value = struct { } } }; + +var trueval: *Value = undefined; +var falseval: *Value = undefined; + +const IntHashContext = struct { + pub fn hash(_: @This(), v: Int) u64 { + var wh = std.hash.Wyhash.init(0xDEADBEEF); + for (v.limbs[0..v.len()]) |limb| { + wh.update(std.mem.asBytes(&limb)); + } + return wh.final(); + } + + pub fn eql(_: @This(), left: Int, right: Int) bool { + return left.eql(right); + } +}; + +const IntHashMap = std.HashMap(Int, *Value, IntHashContext, 80); + +var intcache: IntHashMap = undefined; + +pub fn init(all: std.mem.Allocator) !void { + trueval = try Value.init(.true, all); + falseval = try Value.init(.false, all); + intcache = IntHashMap.init(all); +} + +pub fn deinit() void { + trueval.unref(); + falseval.unref(); + + var iter = intcache.iterator(); + + while (iter.next()) |entry| { + // First unref to avoid invalid memory accesses + entry.value_ptr.*.unref(); + entry.key_ptr.deinit(); + } + + intcache.deinit(); +} diff --git a/src/vm.zig b/src/vm.zig @@ -13,7 +13,7 @@ const VMError = error { OutOfMemory, NegativeIntoUnsigned, TargetTooSmall -}; +} || std.fs.File.WriteError; const CallFrame = struct { ip: []const c.Op, @@ -69,13 +69,27 @@ pub const VM = struct { const start_time = std.time.microTimestamp(); const res = self.run(); const end_time = std.time.microTimestamp(); - vmlog.debug("Eval in {} us", .{end_time - start_time}); + vmlog.info("Eval in {} us", .{end_time - start_time}); return res; } + fn advanceIp(self: *Self) void { + self.currentFrame().ip = self.currentFrame().ip[1..]; + } + + fn doPopScope(self: *Self, amount: usize) !void { + const topval = self.pop(); + + for (0..amount) |_| { + self.pop().unref(); + } + + try self.push(topval); + } + fn run(self: *Self) !*v.Value { - while (true) : (self.currentFrame().ip = self.currentFrame().ip[1..]) { + while (true) { const cframe = self.currentFrame(); const instr = cframe.ip[0]; const cchunk = cframe.func.code; @@ -93,11 +107,13 @@ pub const VM = struct { .constant => |vindex| { const val = cchunk.values.items[vindex]; try self.push(val.ref()); + self.advanceIp(); }, .local => |vindex| { const val = self.stack.items[vindex + cframe.offset]; try self.push(val.ref()); + self.advanceIp(); }, .get_global => |vindex| { @@ -113,6 +129,17 @@ pub const VM = struct { } else { return VMError.UndefinedSymbol; } + self.advanceIp(); + }, + + .print => { + const val = self.stack.getLast(); + + var stdout = std.io.getStdOut().writer(); + + try stdout.print("{}\n", .{val.formatter()}); + + self.advanceIp(); }, .define_global => |index| { @@ -123,6 +150,7 @@ pub const VM = struct { vmlog.debug("Defining {s}", .{sym.val.symbol}); try self.globals.put(sym.val.symbol, val); + self.advanceIp(); }, .jumpfalse => |offset| { @@ -130,33 +158,57 @@ pub const VM = struct { defer val.unref(); if (val.isFalsy()) { - cframe.ip = cchunk.instrs.items[offset - 1 ..]; + cframe.ip = cchunk.instrs.items[offset..]; + } else { + self.advanceIp(); } }, .jump => |offset| { - cframe.ip = cchunk.instrs.items[offset - 1 ..]; + cframe.ip = cchunk.instrs.items[offset..]; + }, + + .call => |arity| { + const funcval = self.pop(); + defer funcval.unref(); + if (!funcval.isA(.function)) { + return VMError.InvalidType; + } + + const func = funcval.val.function; + + std.debug.assert(arity == func.arity); + + const offset = self.stack.items.len - arity; + + const frame: CallFrame = .{.ip = func.code.instrs.items, .offset = offset, .func = &funcval.val.function}; + + self.advanceIp(); + try self.call_stack.append(frame); }, .@"return" => { - _ = self.call_stack.pop(); - // XXX: no unref because we return - if (self.stack.items.len > 0) { - return self.pop(); + const frame = self.call_stack.pop(); + + if (self.call_stack.items.len == 0) { + // XXX: no unref because we return + if (self.stack.items.len > 0) { + return self.pop(); + } else { + return try v.Value.newNil(self.all); + } } else { - return try v.Value.newNil(self.all); + try self.doPopScope(frame.func.arity); } }, - .pop => self.pop().unref(), + .pop => pop: { + self.advanceIp(); + break :pop self.pop().unref(); + }, .popscope => |amount| { - const topval = self.pop(); - - for (0..amount) |_| { - self.pop().unref(); - } - - try self.push(topval); + try self.doPopScope(amount); + self.advanceIp(); }, .negate => { @@ -172,6 +224,7 @@ pub const VM = struct { errdefer new.unref(); try self.push(new); + self.advanceIp(); }, .div => { @@ -188,6 +241,7 @@ pub const VM = struct { try self.push(try v.Value.newInt(self.all, div)); try self.push(try v.Value.newInt(self.all, rem)); + self.advanceIp(); }, inline .add, .multiply, .substract, .pow => |_, tag| { @@ -214,7 +268,22 @@ pub const VM = struct { } try self.push(try v.Value.newInt(self.all, new)); + self.advanceIp(); }, + + .compare => |op| { + var left = self.pop(); + defer left.unref(); + + var right = self.pop(); + defer right.unref(); + + const lnum = left.val.number; + const rnum = right.val.number; + + try self.push(try v.Value.newBool(self.all, lnum.order(rnum).compare(op))); + self.advanceIp(); + } } } }