2019-06-01 17:55:11 +00:00
|
|
|
const std = @import("std");
|
|
|
|
const chunk = @import("chunk.zig");
|
|
|
|
const value = @import("value.zig");
|
2019-06-02 02:33:53 +00:00
|
|
|
const values = value;
|
2019-06-01 19:12:39 +00:00
|
|
|
const compiler = @import("compiler.zig");
|
2019-06-02 17:18:01 +00:00
|
|
|
const objects = @import("object.zig");
|
2019-06-01 17:55:11 +00:00
|
|
|
|
|
|
|
const Chunk = chunk.Chunk;
|
2019-06-01 18:45:30 +00:00
|
|
|
const Value = value.Value;
|
2019-06-01 19:12:39 +00:00
|
|
|
const Compiler = compiler.Compiler;
|
2019-06-01 17:55:11 +00:00
|
|
|
|
2019-06-01 19:37:24 +00:00
|
|
|
pub const StdOut = *std.io.OutStream(std.fs.File.WriteError);
|
2019-06-01 18:23:23 +00:00
|
|
|
|
2019-06-02 02:33:53 +00:00
|
|
|
pub const InterpretResult = error{
|
2019-06-01 17:55:11 +00:00
|
|
|
Ok,
|
|
|
|
CompileError,
|
|
|
|
RuntimeError,
|
|
|
|
};
|
2019-06-01 04:20:06 +00:00
|
|
|
|
2019-06-02 03:02:37 +00:00
|
|
|
fn isFalsey(val: value.Value) bool {
|
|
|
|
return val.vtype == .Nil or (val.vtype == .Bool and !val.as.Bool);
|
|
|
|
}
|
|
|
|
|
2019-06-02 03:16:33 +00:00
|
|
|
fn valuesEqual(a: value.Value, b: value.Value) bool {
|
|
|
|
if (a.vtype != b.vtype) return false;
|
|
|
|
|
|
|
|
switch (a.vtype) {
|
|
|
|
.Nil => return true,
|
|
|
|
.Bool => return a.as.Bool == b.as.Bool,
|
|
|
|
.Number => return a.as.Number == b.as.Number,
|
2019-06-02 17:01:54 +00:00
|
|
|
.Object => blk: {
|
|
|
|
var aStr = a.as.Object.value.String;
|
|
|
|
var bStr = b.as.Object.value.String;
|
|
|
|
return std.mem.compare(u8, aStr, bStr) == .Equal;
|
|
|
|
},
|
2019-06-02 03:16:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-03 01:52:19 +00:00
|
|
|
pub const ValueMap = std.AutoHashMap([]const u8, values.Value);
|
|
|
|
|
2019-06-01 04:20:06 +00:00
|
|
|
pub const VM = struct {
|
2019-06-01 19:12:39 +00:00
|
|
|
chk: *Chunk = undefined,
|
2019-06-01 18:23:23 +00:00
|
|
|
ip: usize = 0,
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
stack: []Value,
|
2019-06-01 18:23:23 +00:00
|
|
|
stackTop: usize = 0,
|
|
|
|
|
2019-06-01 17:55:11 +00:00
|
|
|
stdout: StdOut,
|
2019-06-01 18:01:39 +00:00
|
|
|
debug_flag: bool,
|
2019-06-02 17:52:19 +00:00
|
|
|
pub allocator: *std.mem.Allocator,
|
|
|
|
|
|
|
|
objs: ?*objects.Object = null,
|
2019-06-03 01:52:19 +00:00
|
|
|
globals: ValueMap,
|
2019-06-01 17:55:11 +00:00
|
|
|
|
2019-06-01 18:23:23 +00:00
|
|
|
fn resetStack(self: *VM) void {
|
|
|
|
self.stackTop = 0;
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
pub fn init(
|
|
|
|
allocator: *std.mem.Allocator,
|
|
|
|
stdout: StdOut,
|
|
|
|
debug_flag: bool,
|
|
|
|
) !VM {
|
2019-06-01 18:23:23 +00:00
|
|
|
var self = VM{
|
2019-06-01 18:45:30 +00:00
|
|
|
.stack = try allocator.alloc(Value, 256),
|
2019-06-01 18:23:23 +00:00
|
|
|
.stdout = stdout,
|
2019-06-01 18:01:39 +00:00
|
|
|
.debug_flag = debug_flag,
|
2019-06-01 18:45:30 +00:00
|
|
|
.allocator = allocator,
|
2019-06-03 01:52:19 +00:00
|
|
|
|
|
|
|
.globals = ValueMap.init(allocator),
|
2019-06-01 17:55:11 +00:00
|
|
|
};
|
2019-06-01 18:23:23 +00:00
|
|
|
|
|
|
|
self.resetStack();
|
|
|
|
|
|
|
|
return self;
|
2019-06-01 17:55:11 +00:00
|
|
|
}
|
|
|
|
|
2019-06-02 18:39:04 +00:00
|
|
|
fn deinitObject(self: *VM, obj: *objects.Object) void {
|
|
|
|
switch (obj.otype) {
|
|
|
|
.String => blk: {
|
|
|
|
self.allocator.free(obj.value.String);
|
|
|
|
self.allocator.destroy(obj);
|
|
|
|
break :blk;
|
|
|
|
},
|
|
|
|
else => unreachable,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn deinitObjects(self: *VM) void {
|
|
|
|
var obj_opt: ?*objects.Object = self.objs;
|
|
|
|
|
|
|
|
// doing a while(obj != null) but with optionals
|
|
|
|
while (true) {
|
|
|
|
if (obj_opt) |obj| {
|
|
|
|
var next = obj.next;
|
|
|
|
self.deinitObject(obj);
|
|
|
|
obj_opt = next;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deinit(self: *VM) void {
|
2019-06-03 02:13:42 +00:00
|
|
|
self.globals.deinit();
|
2019-06-02 18:39:04 +00:00
|
|
|
self.deinitObjects();
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:01:39 +00:00
|
|
|
pub fn debug(self: *VM, comptime fmt: []const u8, args: ...) void {
|
|
|
|
if (self.debug_flag) {
|
|
|
|
std.debug.warn(fmt, args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-01 17:55:11 +00:00
|
|
|
fn readByte(self: *VM) u8 {
|
|
|
|
var byte: u8 = self.chk.code[self.ip];
|
|
|
|
self.ip += 1;
|
|
|
|
return byte;
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
fn readConst(self: *VM) Value {
|
2019-06-01 17:55:11 +00:00
|
|
|
return self.chk.constants.values[self.readByte()];
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
fn readConstLong(self: *VM) Value {
|
2019-06-01 17:55:11 +00:00
|
|
|
const v3 = self.readByte();
|
|
|
|
const v2 = self.readByte();
|
|
|
|
const v1 = self.readByte();
|
|
|
|
const const_idx = (@intCast(u24, v3) << 16) |
|
|
|
|
(@intCast(u24, v2) << 8) |
|
|
|
|
v1;
|
|
|
|
|
|
|
|
return self.chk.constants.values[const_idx];
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:23:23 +00:00
|
|
|
fn debugStack(self: *VM) !void {
|
|
|
|
try self.stdout.print(" ");
|
|
|
|
for (self.stack) |val, idx| {
|
|
|
|
if (idx >= self.stackTop) break;
|
|
|
|
|
|
|
|
try self.stdout.print("[ ");
|
|
|
|
try value.printValue(self.stdout, val);
|
|
|
|
try self.stdout.print(" ]");
|
|
|
|
}
|
|
|
|
try self.stdout.print("\n");
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:40:18 +00:00
|
|
|
/// gets a f64 out of a value on the top of the stack.
|
2019-06-02 02:33:53 +00:00
|
|
|
fn popNum(self: *VM) !f64 {
|
|
|
|
var val: Value = self.pop();
|
|
|
|
|
|
|
|
switch (val.vtype) {
|
|
|
|
.Number => return val.as.Number,
|
|
|
|
|
|
|
|
else => |vtype| blk: {
|
|
|
|
self.runtimeError("Expected number, got {x}", vtype);
|
|
|
|
return InterpretResult.RuntimeError;
|
|
|
|
},
|
|
|
|
}
|
2019-06-01 18:40:18 +00:00
|
|
|
}
|
|
|
|
|
2019-06-02 17:18:01 +00:00
|
|
|
fn concatenateStrings(self: *VM) !void {
|
|
|
|
var b = self.pop().as.Object.value.String;
|
|
|
|
var a = self.pop().as.Object.value.String;
|
|
|
|
|
|
|
|
var res_str = try std.mem.join(
|
|
|
|
self.allocator,
|
|
|
|
"",
|
|
|
|
[][]u8{ a, b },
|
|
|
|
);
|
|
|
|
|
2019-06-02 17:52:19 +00:00
|
|
|
var val = values.ObjVal(try objects.takeString(self, res_str));
|
2019-06-02 17:18:01 +00:00
|
|
|
try self.push(val);
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
fn doAdd(self: *VM) !void {
|
2019-06-02 17:18:01 +00:00
|
|
|
if (values.isObjType(self.peek(0), .String) and
|
|
|
|
values.isObjType(self.peek(1), .String))
|
|
|
|
{
|
|
|
|
return try self.concatenateStrings();
|
|
|
|
}
|
|
|
|
|
2019-06-02 02:33:53 +00:00
|
|
|
var b = try self.popNum();
|
|
|
|
var a = try self.popNum();
|
|
|
|
try self.push(values.NumberVal(a + b));
|
2019-06-01 18:40:18 +00:00
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
fn doSub(self: *VM) !void {
|
2019-06-02 02:33:53 +00:00
|
|
|
var b = try self.popNum();
|
|
|
|
var a = try self.popNum();
|
|
|
|
try self.push(values.NumberVal(a - b));
|
2019-06-01 18:40:18 +00:00
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
fn doMul(self: *VM) !void {
|
2019-06-02 02:33:53 +00:00
|
|
|
var b = try self.popNum();
|
|
|
|
var a = try self.popNum();
|
|
|
|
try self.push(values.NumberVal(a * b));
|
2019-06-01 18:40:18 +00:00
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
fn doDiv(self: *VM) !void {
|
2019-06-02 02:33:53 +00:00
|
|
|
var b = try self.popNum();
|
|
|
|
var a = try self.popNum();
|
|
|
|
try self.push(values.NumberVal(a / b));
|
|
|
|
}
|
|
|
|
|
2019-06-02 03:23:50 +00:00
|
|
|
fn doGreater(self: *VM) !void {
|
|
|
|
var b = try self.popNum();
|
|
|
|
var a = try self.popNum();
|
|
|
|
try self.push(values.BoolVal(a > b));
|
|
|
|
}
|
|
|
|
|
|
|
|
fn doLess(self: *VM) !void {
|
|
|
|
var b = try self.popNum();
|
|
|
|
var a = try self.popNum();
|
|
|
|
try self.push(values.BoolVal(a < b));
|
|
|
|
}
|
|
|
|
|
2019-06-02 02:33:53 +00:00
|
|
|
fn runtimeError(self: *VM, comptime fmt: []const u8, args: ...) void {
|
|
|
|
std.debug.warn(fmt, args);
|
|
|
|
std.debug.warn("\n[line {}] in script\n", self.chk.lines[self.ip]);
|
|
|
|
self.resetStack();
|
2019-06-01 18:40:18 +00:00
|
|
|
}
|
|
|
|
|
2019-06-03 01:52:19 +00:00
|
|
|
fn defGlobal(self: *VM, name: []const u8) !void {
|
|
|
|
_ = try self.globals.put(name, self.peek(0));
|
|
|
|
_ = self.pop();
|
|
|
|
}
|
|
|
|
|
2019-06-03 02:57:28 +00:00
|
|
|
fn readString(self: *VM) []u8 {
|
|
|
|
return self.readConst().as.Object.value.String;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn readStringLong(self: *VM) []u8 {
|
|
|
|
return self.readConstLong().as.Object.value.String;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn doGetGlobal(self: *VM, name: []u8) !void {
|
|
|
|
var kv_opt = self.globals.get(name);
|
|
|
|
|
|
|
|
if (kv_opt) |kv| {
|
|
|
|
try self.push(kv.value);
|
|
|
|
} else {
|
|
|
|
self.runtimeError("Undefined variable '{}'.", name);
|
|
|
|
return InterpretResult.RuntimeError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-03 04:41:22 +00:00
|
|
|
fn doSetGlobal(self: *VM, name: []u8) !void {
|
|
|
|
var res = try self.globals.getOrPut(name);
|
|
|
|
|
|
|
|
if (res.found_existing) {
|
|
|
|
res.kv.value = self.peek(0);
|
|
|
|
} else {
|
|
|
|
self.runtimeError("Undefined variable '{}'.", name);
|
|
|
|
return InterpretResult.RuntimeError;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-02 02:33:53 +00:00
|
|
|
fn run(self: *VM) !void {
|
2019-06-01 17:55:11 +00:00
|
|
|
while (true) {
|
2019-06-01 18:01:39 +00:00
|
|
|
if (self.debug_flag) {
|
2019-06-01 18:23:23 +00:00
|
|
|
try self.debugStack();
|
2019-06-01 18:01:39 +00:00
|
|
|
_ = try self.chk.disassembleInstruction(self.stdout, self.ip);
|
|
|
|
}
|
|
|
|
|
2019-06-01 17:55:11 +00:00
|
|
|
var instruction = self.readByte();
|
|
|
|
|
|
|
|
switch (instruction) {
|
|
|
|
chunk.OpCode.Constant => blk: {
|
|
|
|
var constant = self.readConst();
|
2019-06-01 18:45:30 +00:00
|
|
|
try self.push(constant);
|
2019-06-01 17:55:11 +00:00
|
|
|
break :blk;
|
|
|
|
},
|
|
|
|
chunk.OpCode.ConstantLong => blk: {
|
|
|
|
var constant = self.readConstLong();
|
2019-06-01 18:45:30 +00:00
|
|
|
try self.push(constant);
|
2019-06-01 17:55:11 +00:00
|
|
|
break :blk;
|
|
|
|
},
|
2019-06-01 18:23:23 +00:00
|
|
|
|
2019-06-02 20:28:54 +00:00
|
|
|
chunk.OpCode.Print => blk: {
|
2019-06-01 18:23:23 +00:00
|
|
|
try value.printValue(self.stdout, self.pop());
|
|
|
|
try self.stdout.print("\n");
|
2019-06-02 20:28:54 +00:00
|
|
|
break :blk;
|
|
|
|
},
|
|
|
|
|
|
|
|
chunk.OpCode.Return => blk: {
|
|
|
|
// Exit VM
|
2019-06-01 17:55:11 +00:00
|
|
|
return InterpretResult.Ok;
|
|
|
|
},
|
2019-06-01 18:27:19 +00:00
|
|
|
|
2019-06-02 02:44:59 +00:00
|
|
|
chunk.OpCode.Nil => try self.push(values.NilVal()),
|
|
|
|
chunk.OpCode.True => try self.push(values.BoolVal(true)),
|
|
|
|
chunk.OpCode.False => try self.push(values.BoolVal(false)),
|
|
|
|
|
2019-06-02 21:11:23 +00:00
|
|
|
chunk.OpCode.Pop => blk: {
|
|
|
|
_ = self.pop();
|
|
|
|
},
|
2019-06-02 21:04:36 +00:00
|
|
|
|
2019-06-03 19:24:54 +00:00
|
|
|
chunk.OpCode.GetLocal => blk: {
|
|
|
|
var slot = self.readByte();
|
|
|
|
try self.push(self.stack[slot]);
|
2019-06-03 02:57:28 +00:00
|
|
|
},
|
2019-06-03 19:24:54 +00:00
|
|
|
chunk.OpCode.SetLocal => blk: {
|
|
|
|
var slot = self.readByte();
|
|
|
|
self.stack[slot] = self.peek(0);
|
2019-06-03 02:57:28 +00:00
|
|
|
},
|
|
|
|
|
2019-06-03 19:24:54 +00:00
|
|
|
chunk.OpCode.GetGlobal => blk: {
|
|
|
|
try self.doGetGlobal(self.readString());
|
|
|
|
},
|
2019-06-03 04:41:22 +00:00
|
|
|
chunk.OpCode.SetGlobal => blk: {
|
|
|
|
try self.doSetGlobal(self.readString());
|
|
|
|
break :blk;
|
|
|
|
},
|
|
|
|
|
2019-06-03 01:52:19 +00:00
|
|
|
// extracting the name is different depending of the
|
|
|
|
// op code since one just uses a single byte, the other
|
|
|
|
// uses three bytes since its a u24.
|
|
|
|
chunk.OpCode.DefineGlobal => blk: {
|
2019-06-03 02:57:28 +00:00
|
|
|
try self.defGlobal(self.readString());
|
2019-06-03 01:52:19 +00:00
|
|
|
break :blk;
|
|
|
|
},
|
|
|
|
chunk.OpCode.DefineGlobalLong => blk: {
|
2019-06-03 02:57:28 +00:00
|
|
|
try self.defGlobal(self.readStringLong());
|
2019-06-03 01:52:19 +00:00
|
|
|
break :blk;
|
|
|
|
},
|
|
|
|
|
2019-06-02 03:16:33 +00:00
|
|
|
chunk.OpCode.Equal => blk: {
|
|
|
|
var a = self.pop();
|
|
|
|
var b = self.pop();
|
|
|
|
try self.push(values.BoolVal(valuesEqual(a, b)));
|
|
|
|
},
|
|
|
|
|
2019-06-02 03:23:50 +00:00
|
|
|
chunk.OpCode.Greater => try self.doGreater(),
|
|
|
|
chunk.OpCode.Less => try self.doLess(),
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
chunk.OpCode.Add => try self.doAdd(),
|
|
|
|
chunk.OpCode.Subtract => try self.doSub(),
|
|
|
|
chunk.OpCode.Multiply => try self.doMul(),
|
|
|
|
chunk.OpCode.Divide => try self.doDiv(),
|
2019-06-02 03:02:37 +00:00
|
|
|
chunk.OpCode.Not => blk: {
|
|
|
|
try self.push(values.BoolVal(isFalsey(self.pop())));
|
|
|
|
},
|
2019-06-02 03:16:33 +00:00
|
|
|
|
2019-06-02 02:33:53 +00:00
|
|
|
chunk.OpCode.Negate => blk: {
|
|
|
|
var val = self.peek(0);
|
|
|
|
if (val.vtype != .Bool) {
|
|
|
|
self.runtimeError("Operand must be a number.");
|
|
|
|
return InterpretResult.RuntimeError;
|
|
|
|
}
|
|
|
|
|
|
|
|
val = self.pop();
|
|
|
|
switch (val.as) {
|
|
|
|
.Number => |num| {
|
|
|
|
try self.push(values.NumberVal(-num));
|
|
|
|
},
|
|
|
|
else => unreachable,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-06-01 17:55:11 +00:00
|
|
|
else => blk: {
|
|
|
|
std.debug.warn("Unknown instruction: {x}\n", instruction);
|
|
|
|
return InterpretResult.RuntimeError;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-01 04:20:06 +00:00
|
|
|
|
2019-06-03 03:02:07 +00:00
|
|
|
pub fn interpret(self: *VM, src: []const u8) !void {
|
2019-06-01 19:12:39 +00:00
|
|
|
//self.ip = 0;
|
|
|
|
//self.debug("VM start\n");
|
|
|
|
//var res = try self.run();
|
|
|
|
//self.debug("VM end\n");
|
|
|
|
//return res;
|
2019-06-01 23:33:43 +00:00
|
|
|
var chk = try Chunk.init(self.allocator);
|
|
|
|
|
2019-06-02 00:32:25 +00:00
|
|
|
var cmpr = Compiler.init(
|
|
|
|
self.allocator,
|
|
|
|
&chk,
|
|
|
|
self.stdout,
|
2019-06-03 03:02:07 +00:00
|
|
|
src,
|
2019-06-02 00:32:25 +00:00
|
|
|
self.debug_flag,
|
2019-06-02 17:52:19 +00:00
|
|
|
self,
|
2019-06-02 00:32:25 +00:00
|
|
|
);
|
2019-06-01 23:33:43 +00:00
|
|
|
if (!try cmpr.compile(&chk)) {
|
|
|
|
return InterpretResult.CompileError;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.chk = &chk;
|
|
|
|
self.ip = 0;
|
|
|
|
return try self.run();
|
2019-06-01 17:55:11 +00:00
|
|
|
}
|
2019-06-01 18:23:23 +00:00
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
pub fn push(self: *VM, val: Value) !void {
|
|
|
|
if (self.stackTop > 0 and self.stackTop - 1 > self.stack.len) {
|
|
|
|
self.stack = try self.allocator.realloc(self.stack, self.stack.len + 1);
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:23:23 +00:00
|
|
|
self.stack[self.stackTop] = val;
|
|
|
|
self.stackTop += 1;
|
|
|
|
}
|
|
|
|
|
2019-06-01 18:45:30 +00:00
|
|
|
pub fn pop(self: *VM) Value {
|
2019-06-01 18:23:23 +00:00
|
|
|
self.stackTop -= 1;
|
|
|
|
return self.stack[self.stackTop];
|
|
|
|
}
|
2019-06-02 02:33:53 +00:00
|
|
|
|
|
|
|
pub fn peek(self: *VM, distance: usize) Value {
|
|
|
|
return self.stack[self.stackTop - 1 - distance];
|
|
|
|
}
|
2019-06-01 04:20:06 +00:00
|
|
|
};
|