add SetGlobal/SetGlobalLong opcodes
- main: split compile/runtime error messages
This commit is contained in:
parent
9f45dea2c0
commit
69dda36d16
4 changed files with 66 additions and 22 deletions
|
@ -33,6 +33,8 @@ const AllOpcodes = struct {
|
|||
pub DefineGlobalLong: u8 = 18,
|
||||
pub GetGlobal: u8 = 19,
|
||||
pub GetGlobalLong: u8 = 20,
|
||||
pub SetGlobal: u8 = 21,
|
||||
pub SetGlobalLong: u8 = 22,
|
||||
};
|
||||
|
||||
pub const OpCode = AllOpcodes{};
|
||||
|
@ -245,6 +247,10 @@ pub const Chunk = struct {
|
|||
return try constantInstruction(stdout, "OP_GETGLOBAL", self, index);
|
||||
} else if (instruction == OpCode.GetGlobalLong) {
|
||||
return try constantLongInstruction(stdout, "OP_GETGLOBAL_LONG", self, index);
|
||||
} else if (instruction == OpCode.SetGlobal) {
|
||||
return try constantInstruction(stdout, "OP_SETGLOBAL", self, index);
|
||||
} else if (instruction == OpCode.SetGlobalLong) {
|
||||
return try constantLongInstruction(stdout, "OP_SETGLOBAL_LONG", self, index);
|
||||
} else {
|
||||
try stdout.print("Unknown opcode: {}\n", instruction);
|
||||
return index + 1;
|
||||
|
|
|
@ -39,7 +39,7 @@ const Precedence = enum(u5) {
|
|||
Primary,
|
||||
};
|
||||
|
||||
const ParseFn = fn (*Compiler) anyerror!void;
|
||||
const ParseFn = fn (*Compiler, bool) anyerror!void;
|
||||
|
||||
const ParseRule = struct {
|
||||
prefix: ?ParseFn = null,
|
||||
|
@ -229,13 +229,13 @@ pub const Compiler = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn grouping(self: *Compiler) !void {
|
||||
fn grouping(self: *Compiler, canAssign: bool) !void {
|
||||
try self.expression();
|
||||
try self.consume(.RIGHT_PAREN, "Expect ')' after expression.");
|
||||
}
|
||||
|
||||
/// Emits bytecode for a number being loaded into the code.
|
||||
fn number(self: *Compiler) !void {
|
||||
fn number(self: *Compiler, canAssign: bool) !void {
|
||||
var value: f64 = try std.fmt.parseFloat(
|
||||
f64,
|
||||
self.parser.previous.lexeme,
|
||||
|
@ -243,7 +243,7 @@ pub const Compiler = struct {
|
|||
try self.emitConstant(values.NumberVal(value));
|
||||
}
|
||||
|
||||
fn string(self: *Compiler) !void {
|
||||
fn string(self: *Compiler, canAssign: bool) !void {
|
||||
const lexeme_len = self.parser.previous.lexeme.len;
|
||||
|
||||
try self.emitConstant(values.ObjVal(try objects.copyString(
|
||||
|
@ -252,7 +252,7 @@ pub const Compiler = struct {
|
|||
)));
|
||||
}
|
||||
|
||||
fn namedVariable(self: *Compiler, tok: *Token) !void {
|
||||
fn namedVariable(self: *Compiler, tok: *Token, canAssign: bool) !void {
|
||||
// writeConstant always writes OP_CODE which may be not
|
||||
// what we want, so.
|
||||
var idx = try self.currentChunk().writeConstantRaw(values.ObjVal(try objects.copyString(
|
||||
|
@ -260,19 +260,28 @@ pub const Compiler = struct {
|
|||
tok.lexeme,
|
||||
)), tok.line);
|
||||
|
||||
try self.emitConstWithIndex(
|
||||
chunks.OpCode.GetGlobal,
|
||||
chunks.OpCode.GetGlobalLong,
|
||||
idx,
|
||||
);
|
||||
if (canAssign and try self.match(.EQUAL)) {
|
||||
try self.expression();
|
||||
try self.emitConstWithIndex(
|
||||
chunks.OpCode.SetGlobal,
|
||||
chunks.OpCode.SetGlobalLong,
|
||||
idx,
|
||||
);
|
||||
} else {
|
||||
try self.emitConstWithIndex(
|
||||
chunks.OpCode.GetGlobal,
|
||||
chunks.OpCode.GetGlobalLong,
|
||||
idx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn variable(self: *Compiler) !void {
|
||||
try self.namedVariable(&self.parser.previous);
|
||||
fn variable(self: *Compiler, canAssign: bool) !void {
|
||||
try self.namedVariable(&self.parser.previous, canAssign);
|
||||
}
|
||||
|
||||
/// Emits bytecode for a given unary.
|
||||
fn unary(self: *Compiler) !void {
|
||||
fn unary(self: *Compiler, canAssign: bool) !void {
|
||||
var ttype = self.parser.previous.ttype;
|
||||
try self.parsePrecedence(.Unary);
|
||||
|
||||
|
@ -283,7 +292,7 @@ pub const Compiler = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn binary(self: *Compiler) !void {
|
||||
fn binary(self: *Compiler, canAssign: bool) !void {
|
||||
var op_type = self.parser.previous.ttype;
|
||||
var rule: *ParseRule = self.getRule(op_type);
|
||||
try self.parsePrecedence(@intToEnum(Precedence, @enumToInt(rule.precedence) + 1));
|
||||
|
@ -306,7 +315,7 @@ pub const Compiler = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn literal(self: *Compiler) !void {
|
||||
fn literal(self: *Compiler, canAssign: bool) !void {
|
||||
switch (self.parser.previous.ttype) {
|
||||
.FALSE => try self.emitByte(OpCode.False),
|
||||
.NIL => try self.emitByte(OpCode.Nil),
|
||||
|
@ -315,21 +324,27 @@ pub const Compiler = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn parsePrecedence(self: *Compiler, precedence: Precedence) !void {
|
||||
fn parsePrecedence(self: *Compiler, precedence: Precedence) anyerror!void {
|
||||
try self.advance();
|
||||
var as_int = @enumToInt(precedence);
|
||||
var prefix_rule_opt = self.getRule(self.parser.previous.ttype).prefix;
|
||||
|
||||
if (prefix_rule_opt) |prefix_rule| {
|
||||
try prefix_rule(self);
|
||||
var canAssign: bool = as_int <= @enumToInt(Precedence.Assignment);
|
||||
try prefix_rule(self, canAssign);
|
||||
|
||||
while (as_int <= @enumToInt(self.getRule(self.parser.current.ttype).precedence)) {
|
||||
try self.advance();
|
||||
var infix_rule_opt = self.getRule(self.parser.previous.ttype).infix;
|
||||
if (infix_rule_opt) |infix_rule| {
|
||||
try infix_rule(self);
|
||||
try infix_rule(self, canAssign);
|
||||
}
|
||||
}
|
||||
|
||||
if (canAssign and try self.match(.EQUAL)) {
|
||||
self.errorPrevious("Invalid assignment target.");
|
||||
try self.expression();
|
||||
}
|
||||
} else {
|
||||
self.errorPrevious("Expect expression.");
|
||||
return;
|
||||
|
@ -340,7 +355,7 @@ pub const Compiler = struct {
|
|||
return &rules[@enumToInt(ttype)];
|
||||
}
|
||||
|
||||
fn expression(self: *Compiler) !void {
|
||||
fn expression(self: *Compiler) anyerror!void {
|
||||
try self.parsePrecedence(.Assignment);
|
||||
}
|
||||
|
||||
|
@ -376,7 +391,7 @@ pub const Compiler = struct {
|
|||
self: *Compiler,
|
||||
token: *Token,
|
||||
) !chunks.ConstantIndex {
|
||||
return try self.currentChunk().writeConstant(values.ObjVal(try objects.copyString(
|
||||
return try self.currentChunk().writeConstantRaw(values.ObjVal(try objects.copyString(
|
||||
self.vmach,
|
||||
token.lexeme,
|
||||
)), token.line);
|
||||
|
|
|
@ -79,8 +79,11 @@ fn runPrompt(allocator: *Allocator) !void {
|
|||
runWithVM(&vmach, line) catch |err| {
|
||||
switch (err) {
|
||||
InterpretResult.Ok => {},
|
||||
InterpretResult.CompileError, InterpretResult.RuntimeError => blk: {
|
||||
try stdout.print("compile/runtime error.\n");
|
||||
InterpretResult.CompileError => blk: {
|
||||
try stdout.print("compile error.\n");
|
||||
},
|
||||
InterpretResult.RuntimeError => blk: {
|
||||
try stdout.print("runtime error.\n");
|
||||
},
|
||||
else => return err,
|
||||
}
|
||||
|
|
20
src/vm.zig
20
src/vm.zig
|
@ -245,6 +245,17 @@ pub const VM = struct {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
fn run(self: *VM) !void {
|
||||
while (true) {
|
||||
if (self.debug_flag) {
|
||||
|
@ -295,6 +306,15 @@ pub const VM = struct {
|
|||
break :blk;
|
||||
},
|
||||
|
||||
chunk.OpCode.SetGlobal => blk: {
|
||||
try self.doSetGlobal(self.readString());
|
||||
break :blk;
|
||||
},
|
||||
chunk.OpCode.SetGlobalLong => blk: {
|
||||
try self.doSetGlobal(self.readStringLong());
|
||||
break :blk;
|
||||
},
|
||||
|
||||
// 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.
|
||||
|
|
Loading…
Reference in a new issue