Compare commits
4 commits
69dda36d16
...
d7b78e09e3
Author | SHA1 | Date | |
---|---|---|---|
d7b78e09e3 | |||
26d299cd23 | |||
5138410be4 | |||
25ee586acb |
3 changed files with 153 additions and 26 deletions
|
@ -35,6 +35,9 @@ const AllOpcodes = struct {
|
||||||
pub GetGlobalLong: u8 = 20,
|
pub GetGlobalLong: u8 = 20,
|
||||||
pub SetGlobal: u8 = 21,
|
pub SetGlobal: u8 = 21,
|
||||||
pub SetGlobalLong: u8 = 22,
|
pub SetGlobalLong: u8 = 22,
|
||||||
|
|
||||||
|
pub GetLocal: u8 = 23,
|
||||||
|
pub SetLocal: u8 = 24,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const OpCode = AllOpcodes{};
|
pub const OpCode = AllOpcodes{};
|
||||||
|
@ -85,6 +88,17 @@ fn constantLongInstruction(
|
||||||
return offset + 4;
|
return offset + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn byteInstruction(
|
||||||
|
stdout: var,
|
||||||
|
name: []const u8,
|
||||||
|
chunk: *Chunk,
|
||||||
|
index: usize,
|
||||||
|
) !usize {
|
||||||
|
var slot: u8 = chunk.code[index + 1];
|
||||||
|
try stdout.print("{} {}", name, slot);
|
||||||
|
return index + 2;
|
||||||
|
}
|
||||||
|
|
||||||
pub const ConstantIndexTag = enum {
|
pub const ConstantIndexTag = enum {
|
||||||
Small,
|
Small,
|
||||||
Long,
|
Long,
|
||||||
|
@ -251,6 +265,10 @@ pub const Chunk = struct {
|
||||||
return try constantInstruction(stdout, "OP_SETGLOBAL", self, index);
|
return try constantInstruction(stdout, "OP_SETGLOBAL", self, index);
|
||||||
} else if (instruction == OpCode.SetGlobalLong) {
|
} else if (instruction == OpCode.SetGlobalLong) {
|
||||||
return try constantLongInstruction(stdout, "OP_SETGLOBAL_LONG", self, index);
|
return try constantLongInstruction(stdout, "OP_SETGLOBAL_LONG", self, index);
|
||||||
|
} else if (instruction == OpCode.GetLocal) {
|
||||||
|
return try byteInstruction(stdout, "OP_GETLOCAL", self, index);
|
||||||
|
} else if (instruction == OpCode.SetLocal) {
|
||||||
|
return try byteInstruction(stdout, "OP_GETLOCAL", self, index);
|
||||||
} else {
|
} else {
|
||||||
try stdout.print("Unknown opcode: {}\n", instruction);
|
try stdout.print("Unknown opcode: {}\n", instruction);
|
||||||
return index + 1;
|
return index + 1;
|
||||||
|
|
141
src/compiler.zig
141
src/compiler.zig
|
@ -113,6 +113,11 @@ var rules = []ParseRule{
|
||||||
ParseRule{},
|
ParseRule{},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Local = struct {
|
||||||
|
name: tokens.Token,
|
||||||
|
depth: i32,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Compiler = struct {
|
pub const Compiler = struct {
|
||||||
src: []const u8,
|
src: []const u8,
|
||||||
stdout: vm.StdOut,
|
stdout: vm.StdOut,
|
||||||
|
@ -123,6 +128,10 @@ pub const Compiler = struct {
|
||||||
debug_flag: bool = false,
|
debug_flag: bool = false,
|
||||||
vmach: *vm.VM,
|
vmach: *vm.VM,
|
||||||
|
|
||||||
|
locals: [256]Local,
|
||||||
|
localCount: i32 = 0,
|
||||||
|
scopeDepth: i32 = 0,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: *Allocator,
|
allocator: *Allocator,
|
||||||
chunk: *chunks.Chunk,
|
chunk: *chunks.Chunk,
|
||||||
|
@ -139,6 +148,12 @@ pub const Compiler = struct {
|
||||||
.parser = Parser{},
|
.parser = Parser{},
|
||||||
.debug_flag = debug_flag,
|
.debug_flag = debug_flag,
|
||||||
.vmach = vmach,
|
.vmach = vmach,
|
||||||
|
|
||||||
|
// local variable resolution
|
||||||
|
.locals = []Local{Local{
|
||||||
|
.name = Token{},
|
||||||
|
.depth = -1,
|
||||||
|
}} ** 256,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +244,20 @@ pub const Compiler = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn beginScope(self: *Compiler) void {
|
||||||
|
self.scopeDepth += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endScope(self: *Compiler) !void {
|
||||||
|
self.scopeDepth -= 1;
|
||||||
|
|
||||||
|
// clear the current scope in the stack
|
||||||
|
while (self.localCount > 0 and self.locals[@intCast(usize, self.localCount - 1)].depth > self.scopeDepth) {
|
||||||
|
try self.emitByte(chunks.OpCode.Pop);
|
||||||
|
self.localCount -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn grouping(self: *Compiler, canAssign: bool) !void {
|
fn grouping(self: *Compiler, canAssign: bool) !void {
|
||||||
try self.expression();
|
try self.expression();
|
||||||
try self.consume(.RIGHT_PAREN, "Expect ')' after expression.");
|
try self.consume(.RIGHT_PAREN, "Expect ')' after expression.");
|
||||||
|
@ -252,27 +281,48 @@ pub const Compiler = struct {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolveLocal(self: *Compiler, name: *Token) i32 {
|
||||||
|
var i = self.localCount - 1;
|
||||||
|
while (i >= 0) : (i -= 1) {
|
||||||
|
var idx = @intCast(usize, i);
|
||||||
|
var local = &self.locals[idx];
|
||||||
|
if (std.mem.eql(u8, name.lexeme, local.name.lexeme)) {
|
||||||
|
if (local.depth == -1) {
|
||||||
|
self.errorCurrent("Cannot read local variable in its own initializer.");
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
fn namedVariable(self: *Compiler, tok: *Token, canAssign: bool) !void {
|
fn namedVariable(self: *Compiler, tok: *Token, canAssign: bool) !void {
|
||||||
// writeConstant always writes OP_CODE which may be not
|
// writeConstant always writes OP_CODE which may be not
|
||||||
// what we want, so.
|
// what we want, so.
|
||||||
var idx = try self.currentChunk().writeConstantRaw(values.ObjVal(try objects.copyString(
|
var getOp: u8 = undefined;
|
||||||
self.vmach,
|
var setOp: u8 = undefined;
|
||||||
tok.lexeme,
|
|
||||||
)), tok.line);
|
// we try to resolve the local. depending if it gets resolved
|
||||||
|
// or not, we select the necessary get/set op codes.
|
||||||
|
var arg: i32 = self.resolveLocal(tok);
|
||||||
|
|
||||||
|
if (arg != -1) {
|
||||||
|
getOp = chunks.OpCode.GetLocal;
|
||||||
|
setOp = chunks.OpCode.SetLocal;
|
||||||
|
} else {
|
||||||
|
arg = (try self.identifierConstant(tok)).Small;
|
||||||
|
getOp = chunks.OpCode.GetGlobal;
|
||||||
|
setOp = chunks.OpCode.SetGlobal;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx: u8 = @intCast(u8, arg);
|
||||||
|
|
||||||
if (canAssign and try self.match(.EQUAL)) {
|
if (canAssign and try self.match(.EQUAL)) {
|
||||||
try self.expression();
|
try self.expression();
|
||||||
try self.emitConstWithIndex(
|
try self.emitBytes(setOp, idx);
|
||||||
chunks.OpCode.SetGlobal,
|
|
||||||
chunks.OpCode.SetGlobalLong,
|
|
||||||
idx,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
try self.emitConstWithIndex(
|
try self.emitBytes(getOp, idx);
|
||||||
chunks.OpCode.GetGlobal,
|
|
||||||
chunks.OpCode.GetGlobalLong,
|
|
||||||
idx,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,8 +447,44 @@ pub const Compiler = struct {
|
||||||
)), token.line);
|
)), token.line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn addLocal(self: *Compiler, name: Token) void {
|
||||||
|
if (self.localCount == 256) {
|
||||||
|
self.errorCurrent("Too many variables in function.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.localCount += 1;
|
||||||
|
var local: *Local = &self.locals[@intCast(usize, self.localCount)];
|
||||||
|
local.name = name;
|
||||||
|
//local.depth = self.scopeDepth;
|
||||||
|
local.depth = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declareVariable(self: *Compiler) void {
|
||||||
|
if (self.scopeDepth == 0) return;
|
||||||
|
var name: *Token = &self.parser.previous;
|
||||||
|
|
||||||
|
// check if we're redeclaring an existing variable
|
||||||
|
// in the *CURRENT* scope.
|
||||||
|
|
||||||
|
// go from current down to global
|
||||||
|
var i = self.localCount;
|
||||||
|
while (i >= 0) : (i -= 1) {
|
||||||
|
var local = self.locals[@intCast(usize, i)];
|
||||||
|
if (local.depth == -1 and local.depth < self.scopeDepth) break;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, name.lexeme, local.name.lexeme)) {
|
||||||
|
self.errorCurrent("Variable with this name already declared in this scope.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addLocal(name.*);
|
||||||
|
}
|
||||||
|
|
||||||
fn parseVariable(self: *Compiler, msg: []const u8) !chunks.ConstantIndex {
|
fn parseVariable(self: *Compiler, msg: []const u8) !chunks.ConstantIndex {
|
||||||
try self.consume(.IDENTIFIER, msg);
|
try self.consume(.IDENTIFIER, msg);
|
||||||
|
self.declareVariable();
|
||||||
|
if (self.scopeDepth > 0) return chunks.ConstantIndex{ .Small = 0 };
|
||||||
return try self.identifierConstant(&self.parser.previous);
|
return try self.identifierConstant(&self.parser.previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +506,18 @@ pub const Compiler = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn markInitialized(self: *Compiler) void {
|
||||||
|
if (self.scopeDepth == 0) return;
|
||||||
|
var idx = @intCast(usize, self.localCount);
|
||||||
|
self.locals[idx].depth = self.scopeDepth;
|
||||||
|
}
|
||||||
|
|
||||||
fn defineVariable(self: *Compiler, global: chunks.ConstantIndex) !void {
|
fn defineVariable(self: *Compiler, global: chunks.ConstantIndex) !void {
|
||||||
|
if (self.scopeDepth > 0) {
|
||||||
|
self.markInitialized();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try self.emitConstWithIndex(
|
try self.emitConstWithIndex(
|
||||||
chunks.OpCode.DefineGlobal,
|
chunks.OpCode.DefineGlobal,
|
||||||
chunks.OpCode.DefineGlobalLong,
|
chunks.OpCode.DefineGlobalLong,
|
||||||
|
@ -437,11 +534,13 @@ pub const Compiler = struct {
|
||||||
try self.emitByte(chunks.OpCode.Nil);
|
try self.emitByte(chunks.OpCode.Nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check scopeDepth here
|
||||||
|
|
||||||
try self.consume(.SEMICOLON, "Expect ';' after variable declaration.");
|
try self.consume(.SEMICOLON, "Expect ';' after variable declaration.");
|
||||||
try self.defineVariable(global);
|
try self.defineVariable(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declaration(self: *Compiler) !void {
|
fn declaration(self: *Compiler) anyerror!void {
|
||||||
if (try self.match(.VAR)) {
|
if (try self.match(.VAR)) {
|
||||||
try self.varDecl();
|
try self.varDecl();
|
||||||
} else {
|
} else {
|
||||||
|
@ -450,9 +549,21 @@ pub const Compiler = struct {
|
||||||
if (self.parser.panicMode) try self.synchronize();
|
if (self.parser.panicMode) try self.synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn block(self: *Compiler) anyerror!void {
|
||||||
|
while (!self.check(.RIGHT_BRACE) and !self.check(.EOF)) {
|
||||||
|
try self.declaration();
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.consume(.RIGHT_BRACE, "Expect '}' after block.");
|
||||||
|
}
|
||||||
|
|
||||||
fn statement(self: *Compiler) !void {
|
fn statement(self: *Compiler) !void {
|
||||||
if (try self.match(.PRINT)) {
|
if (try self.match(.PRINT)) {
|
||||||
try self.printStmt();
|
try self.printStmt();
|
||||||
|
} else if (try self.match(.LEFT_BRACE)) {
|
||||||
|
self.beginScope();
|
||||||
|
try self.block();
|
||||||
|
try self.endScope();
|
||||||
} else {
|
} else {
|
||||||
try self.exprStmt();
|
try self.exprStmt();
|
||||||
}
|
}
|
||||||
|
|
20
src/vm.zig
20
src/vm.zig
|
@ -294,26 +294,24 @@ pub const VM = struct {
|
||||||
|
|
||||||
chunk.OpCode.Pop => blk: {
|
chunk.OpCode.Pop => blk: {
|
||||||
_ = self.pop();
|
_ = self.pop();
|
||||||
break :blk;
|
},
|
||||||
|
|
||||||
|
chunk.OpCode.GetLocal => blk: {
|
||||||
|
var slot = self.readByte();
|
||||||
|
try self.push(self.stack[slot]);
|
||||||
|
},
|
||||||
|
chunk.OpCode.SetLocal => blk: {
|
||||||
|
var slot = self.readByte();
|
||||||
|
self.stack[slot] = self.peek(0);
|
||||||
},
|
},
|
||||||
|
|
||||||
chunk.OpCode.GetGlobal => blk: {
|
chunk.OpCode.GetGlobal => blk: {
|
||||||
try self.doGetGlobal(self.readString());
|
try self.doGetGlobal(self.readString());
|
||||||
break :blk;
|
|
||||||
},
|
},
|
||||||
chunk.OpCode.GetGlobalLong => blk: {
|
|
||||||
try self.doGetGlobal(self.readStringLong());
|
|
||||||
break :blk;
|
|
||||||
},
|
|
||||||
|
|
||||||
chunk.OpCode.SetGlobal => blk: {
|
chunk.OpCode.SetGlobal => blk: {
|
||||||
try self.doSetGlobal(self.readString());
|
try self.doSetGlobal(self.readString());
|
||||||
break :blk;
|
break :blk;
|
||||||
},
|
},
|
||||||
chunk.OpCode.SetGlobalLong => blk: {
|
|
||||||
try self.doSetGlobal(self.readStringLong());
|
|
||||||
break :blk;
|
|
||||||
},
|
|
||||||
|
|
||||||
// extracting the name is different depending of the
|
// extracting the name is different depending of the
|
||||||
// op code since one just uses a single byte, the other
|
// op code since one just uses a single byte, the other
|
||||||
|
|
Loading…
Reference in a new issue