add SetGlobal/SetGlobalLong opcodes

- main: split compile/runtime error messages
This commit is contained in:
Luna 2019-06-03 01:41:22 -03:00
parent 9f45dea2c0
commit 69dda36d16
4 changed files with 66 additions and 22 deletions

View file

@ -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;

View file

@ -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);
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);

View file

@ -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,
}

View file

@ -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.