Compare commits
16 Commits
a2200debb4
...
trunk
Author | SHA1 | Date | |
---|---|---|---|
7e216dd382 | |||
031ca59a07 | |||
3ecbe5ca9d | |||
980312bd62 | |||
1300c42a09 | |||
22ed27a931 | |||
0bc5f495b9 | |||
b839d67cea | |||
b522f05c8c | |||
3c1c37799c | |||
c8cd24afbf | |||
2e6f977b34 | |||
96e8b1742c | |||
ab00c0f785 | |||
53d5cca124 | |||
7ed6cf6dcc |
12
README.md
12
README.md
@ -15,12 +15,12 @@ While reading [Crafting Interpreters](https://craftinginterpreters.com/), after
|
|||||||
- [x] 22 - Local Variables
|
- [x] 22 - Local Variables
|
||||||
- [x] 23 - Jumping Back and Forth
|
- [x] 23 - Jumping Back and Forth
|
||||||
- [x] 24 - Calls and Functions
|
- [x] 24 - Calls and Functions
|
||||||
- [ ] 25 - Closures
|
- [x] 25 - Closures
|
||||||
- [ ] 26 - Garbage Collection
|
- [x] 26 - Garbage Collection
|
||||||
- [ ] 27 - Classes and Instances
|
- [x] 27 - Classes and Instances
|
||||||
- [ ] 28 - Method and Initializers
|
- [x] 28 - Method and Initializers
|
||||||
- [ ] 29 - Superclasses
|
- [x] 29 - Superclasses
|
||||||
- [ ] 30 - Optimization
|
- [x] 30 - Optimization (without NaN boxing/NaN tagging)
|
||||||
|
|
||||||
## Build & run
|
## Build & run
|
||||||
|
|
||||||
|
13
samples/ch25_closures1.lox
Normal file
13
samples/ch25_closures1.lox
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// this program should print "outer"; without proper closure support, it shows "global".
|
||||||
|
|
||||||
|
var x = "global";
|
||||||
|
|
||||||
|
fun outer() {
|
||||||
|
var x = "outer";
|
||||||
|
fun inner() {
|
||||||
|
print x;
|
||||||
|
}
|
||||||
|
inner();
|
||||||
|
}
|
||||||
|
|
||||||
|
outer();
|
22
samples/ch25_closures2.lox
Normal file
22
samples/ch25_closures2.lox
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
fun makeClosure() {
|
||||||
|
var local = "local";
|
||||||
|
fun closure() {
|
||||||
|
print local;
|
||||||
|
}
|
||||||
|
return closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
var closure = makeClosure();
|
||||||
|
closure();
|
||||||
|
|
||||||
|
fun makeClosure2(value) {
|
||||||
|
fun closure() {
|
||||||
|
print value;
|
||||||
|
}
|
||||||
|
return closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
var doughnut = makeClosure("doughnut");
|
||||||
|
var bagel = makeClosure("bagel");
|
||||||
|
doughnut();
|
||||||
|
bagel();
|
11
samples/ch25_closures3.lox
Normal file
11
samples/ch25_closures3.lox
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fun outer() {
|
||||||
|
var a = 1;
|
||||||
|
var b = 2;
|
||||||
|
fun middle() {
|
||||||
|
var c = 3;
|
||||||
|
var d = 4;
|
||||||
|
fun inner() {
|
||||||
|
print a + c + b + d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
samples/ch25_closures4.lox
Normal file
8
samples/ch25_closures4.lox
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fun outer() {
|
||||||
|
var x = "outside";
|
||||||
|
fun inner() {
|
||||||
|
print x;
|
||||||
|
}
|
||||||
|
inner();
|
||||||
|
}
|
||||||
|
outer();
|
10
samples/ch25_closures5.lox
Normal file
10
samples/ch25_closures5.lox
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
fun outer() {
|
||||||
|
var x = "outside";
|
||||||
|
fun inner() {
|
||||||
|
print x;
|
||||||
|
}
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
var closure = outer();
|
||||||
|
closure();
|
22
samples/ch25_closures6.lox
Normal file
22
samples/ch25_closures6.lox
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
var globalSet;
|
||||||
|
var globalGet;
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
var a = "initial";
|
||||||
|
|
||||||
|
fun set() {
|
||||||
|
a = "updated";
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get() {
|
||||||
|
print a;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalSet = set;
|
||||||
|
globalGet = get;
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
||||||
|
globalSet();
|
||||||
|
globalGet();
|
21
samples/ch26_gc.lox
Normal file
21
samples/ch26_gc.lox
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
fun do_stuff(v) {
|
||||||
|
var s = v + v;
|
||||||
|
print s;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun blah() {
|
||||||
|
var c = "ha" + "ha";
|
||||||
|
|
||||||
|
for(var i = 0; i < 100; i = i + 1) {
|
||||||
|
var d = "ha" + num2str(i);
|
||||||
|
do_stuff(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "hiii";
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = blah();
|
||||||
|
|
||||||
|
a = "updated";
|
||||||
|
// GC here.
|
||||||
|
print a;
|
16
samples/ch27_classes_instances.lox
Normal file
16
samples/ch27_classes_instances.lox
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
class Brioche {}
|
||||||
|
print Brioche();
|
||||||
|
|
||||||
|
class Toast {}
|
||||||
|
var toast = Toast();
|
||||||
|
print toast;
|
||||||
|
print toast.jam = "grape";
|
||||||
|
print toast.jam;
|
||||||
|
|
||||||
|
class Pair {}
|
||||||
|
var pair = Pair();
|
||||||
|
|
||||||
|
pair.first = 1;
|
||||||
|
pair.second = 2;
|
||||||
|
|
||||||
|
print pair.first + pair.second;
|
9
samples/ch28_calling_methods.lox
Normal file
9
samples/ch28_calling_methods.lox
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class Scone {
|
||||||
|
topping(first, second) {
|
||||||
|
return "scone with " + first + " and " + second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var scone = Scone();
|
||||||
|
var result = scone.topping("berries", "cream");
|
||||||
|
print result;
|
14
samples/ch28_initializers.lox
Normal file
14
samples/ch28_initializers.lox
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class CoffeeMaker {
|
||||||
|
init(coffee) {
|
||||||
|
this.coffee = coffee;
|
||||||
|
}
|
||||||
|
brew() {
|
||||||
|
print "Enjoy your cup of " + this.coffee;
|
||||||
|
// No reusing the grounds!
|
||||||
|
this.coffee = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maker = CoffeeMaker("coffee and chicory");
|
||||||
|
print maker;
|
||||||
|
maker.brew();
|
12
samples/ch28_methods_again.lox
Normal file
12
samples/ch28_methods_again.lox
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
class Brunch {
|
||||||
|
bacon() {
|
||||||
|
print "calling bacon()";
|
||||||
|
}
|
||||||
|
eggs() {
|
||||||
|
print "calling eggs()";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var brunch = Brunch();
|
||||||
|
print brunch;
|
||||||
|
brunch.bacon();
|
4
samples/ch28_methods_initializers.lox
Normal file
4
samples/ch28_methods_initializers.lox
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class Brunch {
|
||||||
|
init(food, drink) {}
|
||||||
|
}
|
||||||
|
Brunch("eggs", "coffee");
|
17
samples/ch28_optimized_invocation.lox
Normal file
17
samples/ch28_optimized_invocation.lox
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class Oops {
|
||||||
|
init() {
|
||||||
|
fun f() {
|
||||||
|
print "not a method";
|
||||||
|
return "returned value from f";
|
||||||
|
}
|
||||||
|
this.field = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
blah() {
|
||||||
|
return "returned value from blah";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oops = Oops();
|
||||||
|
print oops.field();
|
||||||
|
print oops.blah();
|
11
samples/ch28_say_name.lox
Normal file
11
samples/ch28_say_name.lox
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class Person {
|
||||||
|
sayName() {
|
||||||
|
print this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var jane = Person();
|
||||||
|
jane.name = "Jane";
|
||||||
|
|
||||||
|
var method = jane.sayName;
|
||||||
|
method();
|
5
samples/ch28_this_outside.lox
Normal file
5
samples/ch28_this_outside.lox
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
print this;
|
||||||
|
|
||||||
|
fun invalid() {
|
||||||
|
print this;
|
||||||
|
}
|
21
samples/ch29_superclass.lox
Normal file
21
samples/ch29_superclass.lox
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
class Doughnut {
|
||||||
|
cook() {
|
||||||
|
print "Dunk in the fryer.";
|
||||||
|
}
|
||||||
|
|
||||||
|
zzz() {
|
||||||
|
print "Bla";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cruller < Doughnut {
|
||||||
|
finish() {
|
||||||
|
print "Glaze with icing.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var o2 = Cruller();
|
||||||
|
o2.cook();
|
||||||
|
o2.finish();
|
||||||
|
|
||||||
|
|
19
samples/ch29_superclass2.lox
Normal file
19
samples/ch29_superclass2.lox
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
class A {
|
||||||
|
method() {
|
||||||
|
print "A method";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B < A {
|
||||||
|
method() {
|
||||||
|
print "B method";
|
||||||
|
}
|
||||||
|
|
||||||
|
test() {
|
||||||
|
this.method();
|
||||||
|
super.method();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class C < B {}
|
||||||
|
C().test();
|
2
samples/ch29_superclass_error.lox
Normal file
2
samples/ch29_superclass_error.lox
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class Cake < Cake {
|
||||||
|
}
|
2
samples/ch29_superclass_error2.lox
Normal file
2
samples/ch29_superclass_error2.lox
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
var NotClass = "So not a class";
|
||||||
|
class OhNo < NotClass {}
|
17
samples/ch8_initalizers.lox
Normal file
17
samples/ch8_initalizers.lox
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class Brunch {
|
||||||
|
var food;
|
||||||
|
var drink;
|
||||||
|
|
||||||
|
init(food, drink) {
|
||||||
|
this.food = food;
|
||||||
|
this.drink = drink;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get_recipe() {
|
||||||
|
return this.food + " " + this.drink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance = Brunch("eggs", "coffee");
|
||||||
|
print instance;
|
||||||
|
print instance.get_recipe();
|
1
samples/hello_world.lox
Normal file
1
samples/hello_world.lox
Normal file
@ -0,0 +1 @@
|
|||||||
|
print "hello world!";
|
3
samples/indices.lox
Normal file
3
samples/indices.lox
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
var alphabet = "abcdefghijklmnopqrstuvwxyz"; var X = alphabet; print "hello " + X[3] + X[0] + X[10];
|
||||||
|
|
||||||
|
var s = " "; s[0] = "d"; s[1] = "a"; s[2] = "k"; var hey = "hello " + s + "!"; print hey;
|
3
samples/native_power.lox
Normal file
3
samples/native_power.lox
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
print power(str2num("3"),str2num("3"));
|
||||||
|
|
||||||
|
print str2num("3") + str2num("4");
|
@ -5,12 +5,15 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Value = @import("./values.zig").Value;
|
const Value = @import("./values.zig").Value;
|
||||||
const ValueArray = @import("./values.zig").ValueArray;
|
const ValueArray = @import("./values.zig").ValueArray;
|
||||||
const OpCode = @import("./opcode.zig").OpCode;
|
const OpCode = @import("./opcode.zig").OpCode;
|
||||||
|
const VM = @import("./vm.zig").VM;
|
||||||
|
const ZloxAllocator = @import("./memory.zig").ZloxAllocator;
|
||||||
|
|
||||||
const grow_capacity = @import("./utils.zig").grow_capacity;
|
const constants = @import("./constant.zig");
|
||||||
const utils = @import("./utils.zig");
|
const utils = @import("./utils.zig");
|
||||||
|
|
||||||
pub const Chunk = struct {
|
pub const Chunk = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
vm: *VM,
|
||||||
|
|
||||||
count: usize,
|
count: usize,
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
@ -18,10 +21,11 @@ pub const Chunk = struct {
|
|||||||
lines: []usize,
|
lines: []usize,
|
||||||
constants: ValueArray,
|
constants: ValueArray,
|
||||||
|
|
||||||
pub fn new(allocator: Allocator) *Chunk {
|
pub fn new(allocator: Allocator, vm: *VM) *Chunk {
|
||||||
var chunk: *Chunk = allocator.create(Chunk) catch unreachable;
|
var chunk: *Chunk = allocator.create(Chunk) catch unreachable;
|
||||||
|
|
||||||
chunk.allocator = allocator;
|
chunk.allocator = allocator;
|
||||||
|
chunk.vm = vm;
|
||||||
chunk.count = 0;
|
chunk.count = 0;
|
||||||
chunk.capacity = 0;
|
chunk.capacity = 0;
|
||||||
chunk.code = &.{};
|
chunk.code = &.{};
|
||||||
@ -45,7 +49,8 @@ pub const Chunk = struct {
|
|||||||
pub fn write(self: *Chunk, byte: u8, line: usize) !void {
|
pub fn write(self: *Chunk, byte: u8, line: usize) !void {
|
||||||
if (self.capacity < self.count + 1) {
|
if (self.capacity < self.count + 1) {
|
||||||
const old_capacity = self.capacity;
|
const old_capacity = self.capacity;
|
||||||
self.capacity = grow_capacity(old_capacity);
|
self.capacity = ZloxAllocator.grow_capacity(old_capacity);
|
||||||
|
|
||||||
self.code = try self.allocator.realloc(self.code, self.capacity);
|
self.code = try self.allocator.realloc(self.code, self.capacity);
|
||||||
self.lines = try self.allocator.realloc(self.lines, self.capacity);
|
self.lines = try self.allocator.realloc(self.lines, self.capacity);
|
||||||
}
|
}
|
||||||
@ -68,18 +73,19 @@ pub const Chunk = struct {
|
|||||||
debug.print("== end of chunk dump \n\n", .{});
|
debug.print("== end of chunk dump \n\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dissassemble(self: Chunk, name: []const u8) void {
|
pub fn disassemble(self: Chunk, name: []const u8) void {
|
||||||
debug.print("== {s} ==\n", .{name});
|
debug.print("== {s} ==\n", .{name});
|
||||||
|
|
||||||
var offset: usize = 0;
|
var offset: usize = 0;
|
||||||
|
|
||||||
while (offset < self.count) {
|
while (offset < self.count) {
|
||||||
offset = self.dissassemble_instruction(offset);
|
offset = self.disassemble_instruction(offset);
|
||||||
}
|
}
|
||||||
debug.print("== end of {s} ==\n\n", .{name});
|
debug.print("== end of {s} ==\n\n", .{name});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dissassemble_instruction(self: Chunk, offset: usize) usize {
|
pub fn disassemble_instruction(self: Chunk, offset: usize) usize {
|
||||||
|
var current_offset = offset;
|
||||||
debug.print("{d:0>4} ", .{offset});
|
debug.print("{d:0>4} ", .{offset});
|
||||||
|
|
||||||
if (offset > 0 and self.lines[offset] == self.lines[offset - 1]) {
|
if (offset > 0 and self.lines[offset] == self.lines[offset - 1]) {
|
||||||
@ -116,6 +122,42 @@ pub const Chunk = struct {
|
|||||||
@intFromEnum(OpCode.OP_JUMP_IF_FALSE) => return utils.jump_instruction("OP_JUMP_IF_FALSE", 1, self, offset),
|
@intFromEnum(OpCode.OP_JUMP_IF_FALSE) => return utils.jump_instruction("OP_JUMP_IF_FALSE", 1, self, offset),
|
||||||
@intFromEnum(OpCode.OP_LOOP) => return utils.jump_instruction("OP_LOOP", -1, self, offset),
|
@intFromEnum(OpCode.OP_LOOP) => return utils.jump_instruction("OP_LOOP", -1, self, offset),
|
||||||
@intFromEnum(OpCode.OP_CALL) => return utils.byte_instruction("OP_CALL", self, offset),
|
@intFromEnum(OpCode.OP_CALL) => return utils.byte_instruction("OP_CALL", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_CLOSURE) => {
|
||||||
|
current_offset += 1;
|
||||||
|
const constant = self.code[current_offset];
|
||||||
|
current_offset += 1;
|
||||||
|
debug.print("{s:<16} {d:0>4} ", .{ "OP_CLOSURE", constant });
|
||||||
|
self.constants.values[constant].print();
|
||||||
|
debug.print("\n", .{});
|
||||||
|
|
||||||
|
const function = self.constants.values[constant].as_obj().as_function();
|
||||||
|
for (0..function.upvalue_count) |j| {
|
||||||
|
_ = j;
|
||||||
|
const is_local_str = switch (self.code[current_offset]) {
|
||||||
|
1 => "local",
|
||||||
|
else => "upvalue",
|
||||||
|
};
|
||||||
|
current_offset += 1;
|
||||||
|
const index = self.code[current_offset];
|
||||||
|
current_offset += 1;
|
||||||
|
|
||||||
|
debug.print("{d:0>4} | {s:<19} {s} {d}\n", .{ current_offset - 2, "", is_local_str, index });
|
||||||
|
}
|
||||||
|
return current_offset;
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_GET_UPVALUE) => return utils.byte_instruction("OP_GET_UPVALUE", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_SET_UPVALUE) => return utils.byte_instruction("OP_SET_UPVALUE", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_CLOSE_UPVALUE) => return utils.simple_instruction("OP_CLOSE_UPVALUE", offset),
|
||||||
|
@intFromEnum(OpCode.OP_CLASS) => return utils.constant_instruction("OP_CLASS", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_GET_PROPERTY) => return utils.constant_instruction("OP_GET_PROPERTY", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_SET_PROPERTY) => return utils.constant_instruction("OP_SET_PROPERTY", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_METHOD) => return utils.constant_instruction("OP_METHOD", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_INDEX_GET) => return utils.simple_instruction("OP_INDEX_GET", offset),
|
||||||
|
@intFromEnum(OpCode.OP_INDEX_SET) => return utils.simple_instruction("OP_INDEX_SET", offset),
|
||||||
|
@intFromEnum(OpCode.OP_INVOKE) => return utils.invoke_instruction("OP_INVOKE", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_INHERIT) => return utils.simple_instruction("OP_INHERIT", offset),
|
||||||
|
@intFromEnum(OpCode.OP_GET_SUPER) => return utils.constant_instruction("OP_GET_SUPER", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_SUPER_INVOKE) => return utils.invoke_instruction("OP_SUPER_INVOKE", self, offset),
|
||||||
else => {
|
else => {
|
||||||
debug.print("unknown opcode {d}\n", .{instruction});
|
debug.print("unknown opcode {d}\n", .{instruction});
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
@ -124,7 +166,10 @@ pub const Chunk = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_constant(self: *Chunk, value: Value) !usize {
|
pub fn add_constant(self: *Chunk, value: Value) !usize {
|
||||||
|
_ = try self.vm.push(value);
|
||||||
try self.constants.write(value);
|
try self.constants.write(value);
|
||||||
|
_ = self.vm.pop();
|
||||||
|
|
||||||
return self.constants.count - 1;
|
return self.constants.count - 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
317
src/compile.zig
317
src/compile.zig
@ -29,19 +29,25 @@ const Precedence = enum {
|
|||||||
Term,
|
Term,
|
||||||
Factor,
|
Factor,
|
||||||
Unary,
|
Unary,
|
||||||
|
Index,
|
||||||
Call,
|
Call,
|
||||||
Primary,
|
Primary,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ParserFn = *const fn (*Parser, bool) ParsingError!void;
|
const ParserFn = *const fn (*Parser, bool) ParsingError!void;
|
||||||
|
|
||||||
|
const ClassCompiler = struct {
|
||||||
|
enclosing: ?*ClassCompiler,
|
||||||
|
has_super_class: bool,
|
||||||
|
};
|
||||||
|
|
||||||
const ParserRule = struct {
|
const ParserRule = struct {
|
||||||
prefix: ?ParserFn,
|
prefix: ?ParserFn,
|
||||||
infix: ?ParserFn,
|
infix: ?ParserFn,
|
||||||
precedence: Precedence,
|
precedence: Precedence,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Parser = struct {
|
pub const Parser = struct {
|
||||||
compiler: *Compiler,
|
compiler: *Compiler,
|
||||||
current: ?Token,
|
current: ?Token,
|
||||||
previous: ?Token,
|
previous: ?Token,
|
||||||
@ -49,6 +55,7 @@ const Parser = struct {
|
|||||||
had_error: bool,
|
had_error: bool,
|
||||||
panic_mode: bool,
|
panic_mode: bool,
|
||||||
vm: *VM,
|
vm: *VM,
|
||||||
|
class_compiler: ?*ClassCompiler,
|
||||||
|
|
||||||
fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner) Parser {
|
fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner) Parser {
|
||||||
return Parser{
|
return Parser{
|
||||||
@ -59,6 +66,7 @@ const Parser = struct {
|
|||||||
.had_error = false,
|
.had_error = false,
|
||||||
.panic_mode = false,
|
.panic_mode = false,
|
||||||
.vm = vm,
|
.vm = vm,
|
||||||
|
.class_compiler = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +74,10 @@ const Parser = struct {
|
|||||||
return self.compiler.function.chunk;
|
return self.compiler.function.chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fn current_class_compiler(self: *Parser) ?*ClassCompiler {
|
||||||
|
return self.class_compiler;
|
||||||
|
}
|
||||||
|
|
||||||
fn advance(self: *Parser) void {
|
fn advance(self: *Parser) void {
|
||||||
self.previous = self.current;
|
self.previous = self.current;
|
||||||
|
|
||||||
@ -134,15 +146,26 @@ const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn emit_return(self: *Parser) ParsingError!void {
|
fn emit_return(self: *Parser) ParsingError!void {
|
||||||
|
if (self.compiler.function_type == FunctionType.Initializer) {
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_LOCAL), 0);
|
||||||
|
} else {
|
||||||
try self.emit_byte(@intFromEnum(OpCode.OP_NIL));
|
try self.emit_byte(@intFromEnum(OpCode.OP_NIL));
|
||||||
|
}
|
||||||
|
|
||||||
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
|
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_parser(self: *Parser) !*Obj.Function {
|
fn end_parser(self: *Parser) !*Obj.Function {
|
||||||
try self.emit_return();
|
try self.emit_return();
|
||||||
|
|
||||||
|
const compiler_function = self.compiler.function;
|
||||||
|
|
||||||
if (!self.had_error and constants.DEBUG_PRINT_CODE) {
|
if (!self.had_error and constants.DEBUG_PRINT_CODE) {
|
||||||
self.current_chunk().dissassemble("code");
|
var label: []const u8 = "<script>";
|
||||||
|
if (compiler_function.name != null) {
|
||||||
|
label = compiler_function.name.?.chars;
|
||||||
|
}
|
||||||
|
self.current_chunk().disassemble(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
const function_obj = self.compiler.function;
|
const function_obj = self.compiler.function;
|
||||||
@ -191,6 +214,18 @@ const Parser = struct {
|
|||||||
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
|
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn index_(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
|
try self.expression();
|
||||||
|
self.consume(TokenType.RIGHT_BRACKET, "Expecting ']");
|
||||||
|
|
||||||
|
if (can_assign and self.match(TokenType.EQUAL)) {
|
||||||
|
try self.expression();
|
||||||
|
try self.emit_byte(@intFromEnum(OpCode.OP_INDEX_SET));
|
||||||
|
} else {
|
||||||
|
try self.emit_byte(@intFromEnum(OpCode.OP_INDEX_GET));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn unary(self: *Parser, can_assign: bool) ParsingError!void {
|
fn unary(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
_ = can_assign;
|
_ = can_assign;
|
||||||
|
|
||||||
@ -249,9 +284,11 @@ const Parser = struct {
|
|||||||
TokenType.LEFT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.LEFT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.RIGHT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.RIGHT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.COMMA => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.COMMA => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.DOT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.DOT => ParserRule{ .prefix = null, .infix = dot, .precedence = Precedence.Call },
|
||||||
TokenType.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term },
|
TokenType.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term },
|
||||||
TokenType.PLUS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Term },
|
TokenType.PLUS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Term },
|
||||||
|
TokenType.LEFT_BRACKET => ParserRule{ .prefix = null, .infix = index_, .precedence = Precedence.Unary },
|
||||||
|
TokenType.RIGHT_BRACKET => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
|
TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
|
||||||
TokenType.STAR => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
|
TokenType.STAR => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
|
||||||
@ -277,8 +314,8 @@ const Parser = struct {
|
|||||||
TokenType.OR => ParserRule{ .prefix = null, .infix = or_, .precedence = Precedence.Or },
|
TokenType.OR => ParserRule{ .prefix = null, .infix = or_, .precedence = Precedence.Or },
|
||||||
TokenType.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.RETURN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.RETURN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.SUPER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.SUPER => ParserRule{ .prefix = super_, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.THIS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.THIS => ParserRule{ .prefix = this_, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.TRUE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None },
|
TokenType.TRUE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.WHILE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.WHILE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
@ -326,9 +363,7 @@ const Parser = struct {
|
|||||||
|
|
||||||
const str = self.previous.?.start[1 .. self.previous.?.length - 1];
|
const str = self.previous.?.start[1 .. self.previous.?.length - 1];
|
||||||
|
|
||||||
var string_obj = self.vm.copy_string(str);
|
const string_obj = self.vm.copy_string(str);
|
||||||
|
|
||||||
self.vm.add_reference(&string_obj.obj);
|
|
||||||
|
|
||||||
try self.emit_constant(Value.obj_val(&string_obj.obj));
|
try self.emit_constant(Value.obj_val(&string_obj.obj));
|
||||||
}
|
}
|
||||||
@ -340,29 +375,45 @@ const Parser = struct {
|
|||||||
fn named_variable(self: *Parser, token: Token, can_assign: bool) ParsingError!void {
|
fn named_variable(self: *Parser, token: Token, can_assign: bool) ParsingError!void {
|
||||||
var get_op: OpCode = OpCode.OP_GET_LOCAL;
|
var get_op: OpCode = OpCode.OP_GET_LOCAL;
|
||||||
var set_op: OpCode = OpCode.OP_SET_LOCAL;
|
var set_op: OpCode = OpCode.OP_SET_LOCAL;
|
||||||
var has_local = true;
|
|
||||||
|
|
||||||
var constant = self.resolve_local(token) catch blk: {
|
var arg = self.resolve_local(self.compiler, token);
|
||||||
has_local = false;
|
const upvalue_arg = self.resolve_upvalue(self.compiler, token);
|
||||||
break :blk 0;
|
if (arg != -1) {
|
||||||
};
|
get_op = OpCode.OP_GET_LOCAL;
|
||||||
|
set_op = OpCode.OP_SET_LOCAL;
|
||||||
if (!has_local) {
|
} else if (upvalue_arg != -1) {
|
||||||
constant = try self.identifier_constant(token);
|
get_op = OpCode.OP_GET_UPVALUE;
|
||||||
|
set_op = OpCode.OP_SET_UPVALUE;
|
||||||
|
arg = upvalue_arg;
|
||||||
|
} else {
|
||||||
|
arg = try self.identifier_constant(token);
|
||||||
get_op = OpCode.OP_GET_GLOBAL;
|
get_op = OpCode.OP_GET_GLOBAL;
|
||||||
set_op = OpCode.OP_SET_GLOBAL;
|
set_op = OpCode.OP_SET_GLOBAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle assignments by index:
|
||||||
|
// - push value to the stack
|
||||||
|
// - modify it (through a OP_INDEX_SET)
|
||||||
|
// - update the variable
|
||||||
|
if (can_assign and self.match(TokenType.LEFT_BRACKET)) {
|
||||||
|
try self.emit_bytes(@intFromEnum(get_op), @intCast(arg));
|
||||||
|
try self.index_(can_assign);
|
||||||
|
try self.emit_bytes(@intFromEnum(set_op), @intCast(arg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (can_assign and self.match(TokenType.EQUAL)) {
|
if (can_assign and self.match(TokenType.EQUAL)) {
|
||||||
try self.expression();
|
try self.expression();
|
||||||
try self.emit_bytes(@intFromEnum(set_op), constant);
|
try self.emit_bytes(@intFromEnum(set_op), @intCast(arg));
|
||||||
} else {
|
} else {
|
||||||
try self.emit_bytes(@intFromEnum(get_op), constant);
|
try self.emit_bytes(@intFromEnum(get_op), @intCast(arg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn declaration(self: *Parser) ParsingError!void {
|
fn declaration(self: *Parser) ParsingError!void {
|
||||||
if (self.match(TokenType.FUN)) {
|
if (self.match(TokenType.CLASS)) {
|
||||||
|
try self.class_declaration();
|
||||||
|
} else if (self.match(TokenType.FUN)) {
|
||||||
try self.fun_declaration();
|
try self.fun_declaration();
|
||||||
} else if (self.match(TokenType.VAR)) {
|
} else if (self.match(TokenType.VAR)) {
|
||||||
try self.var_declaration();
|
try self.var_declaration();
|
||||||
@ -469,7 +520,6 @@ const Parser = struct {
|
|||||||
|
|
||||||
fn identifier_constant(self: *Parser, token: Token) ParsingError!u8 {
|
fn identifier_constant(self: *Parser, token: Token) ParsingError!u8 {
|
||||||
const copy = &self.vm.copy_string(token.start[0..token.length]).obj;
|
const copy = &self.vm.copy_string(token.start[0..token.length]).obj;
|
||||||
self.vm.add_reference(copy);
|
|
||||||
return self.make_constant(Value.obj_val(copy));
|
return self.make_constant(Value.obj_val(copy));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +588,11 @@ const Parser = struct {
|
|||||||
self.compiler.scope_depth -= 1;
|
self.compiler.scope_depth -= 1;
|
||||||
|
|
||||||
while (self.compiler.local_count > 0 and self.compiler.locals[self.compiler.local_count - 1].depth.? > self.compiler.scope_depth) {
|
while (self.compiler.local_count > 0 and self.compiler.locals[self.compiler.local_count - 1].depth.? > self.compiler.scope_depth) {
|
||||||
|
if (self.compiler.locals[self.compiler.local_count - 1].is_captured) {
|
||||||
|
try self.emit_byte(@intFromEnum(OpCode.OP_CLOSE_UPVALUE));
|
||||||
|
} else {
|
||||||
try self.emit_byte(@intFromEnum(OpCode.OP_POP));
|
try self.emit_byte(@intFromEnum(OpCode.OP_POP));
|
||||||
|
}
|
||||||
self.compiler.local_count -= 1;
|
self.compiler.local_count -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -554,17 +608,18 @@ const Parser = struct {
|
|||||||
|
|
||||||
local.name = token;
|
local.name = token;
|
||||||
local.depth = null;
|
local.depth = null;
|
||||||
|
local.is_captured = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_local(self: *Parser, name: Token) !u8 {
|
fn resolve_local(self: *Parser, compiler: *Compiler, name: Token) isize {
|
||||||
if (self.compiler.local_count == 0) {
|
if (compiler.local_count == 0) {
|
||||||
return ParsingError.NotFound;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var idx: u8 = @intCast(self.compiler.local_count - 1);
|
var idx: u8 = @intCast(compiler.local_count - 1);
|
||||||
|
|
||||||
while (idx >= 0) {
|
while (idx >= 0) {
|
||||||
const local = &self.compiler.locals[idx];
|
const local = &compiler.locals[idx];
|
||||||
|
|
||||||
if (identifiers_equals(local.name, name)) {
|
if (identifiers_equals(local.name, name)) {
|
||||||
if (local.depth == null) {
|
if (local.depth == null) {
|
||||||
@ -579,7 +634,47 @@ const Parser = struct {
|
|||||||
idx -= 1;
|
idx -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParsingError.NotFound;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_upvalue(self: *Parser, compiler: *Compiler, name: Token) isize {
|
||||||
|
if (compiler.enclosing == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const local = self.resolve_local(compiler.enclosing.?, name);
|
||||||
|
if (local != -1) {
|
||||||
|
compiler.enclosing.?.locals[@intCast(local)].is_captured = true;
|
||||||
|
return @intCast(self.add_upvalue(compiler, @intCast(local), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
const upvalue = self.resolve_upvalue(compiler.enclosing.?, name);
|
||||||
|
if (upvalue != -1) {
|
||||||
|
return @intCast(self.add_upvalue(compiler, @intCast(upvalue), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_upvalue(self: *Parser, compiler: *Compiler, index: u8, is_local: bool) usize {
|
||||||
|
const upvalue_count = compiler.function.upvalue_count;
|
||||||
|
|
||||||
|
for (0..upvalue_count) |i| {
|
||||||
|
const upvalue: *Upvalue = &compiler.upvalues[i];
|
||||||
|
if (upvalue.index == index and upvalue.is_local == is_local) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upvalue_count == constants.UINT8_COUNT) {
|
||||||
|
self.error_msg("Too many closure variables in function.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
compiler.upvalues[upvalue_count].is_local = is_local;
|
||||||
|
compiler.upvalues[upvalue_count].index = index;
|
||||||
|
compiler.function.upvalue_count += 1;
|
||||||
|
return compiler.function.upvalue_count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn if_statement(self: *Parser) !void {
|
fn if_statement(self: *Parser) !void {
|
||||||
@ -726,7 +821,7 @@ const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn function(self: *Parser, function_type: FunctionType) ParsingError!void {
|
fn function(self: *Parser, function_type: FunctionType) ParsingError!void {
|
||||||
var compiler = Compiler.new(self.vm.allocator, self.compiler, function_type);
|
var compiler = Compiler.new(self.vm, self.compiler, function_type);
|
||||||
|
|
||||||
self.compiler = &compiler;
|
self.compiler = &compiler;
|
||||||
if (function_type != FunctionType.Script) {
|
if (function_type != FunctionType.Script) {
|
||||||
@ -759,7 +854,16 @@ const Parser = struct {
|
|||||||
const obj_function = try self.end_parser();
|
const obj_function = try self.end_parser();
|
||||||
|
|
||||||
const constant = try self.make_constant(Value.obj_val(&obj_function.obj));
|
const constant = try self.make_constant(Value.obj_val(&obj_function.obj));
|
||||||
try self.emit_bytes(@intFromEnum(OpCode.OP_CONSTANT), constant);
|
try self.emit_bytes(@intFromEnum(OpCode.OP_CLOSURE), constant);
|
||||||
|
|
||||||
|
for (0..obj_function.upvalue_count) |i| {
|
||||||
|
if (compiler.upvalues[i].is_local) {
|
||||||
|
try self.emit_byte(1);
|
||||||
|
} else {
|
||||||
|
try self.emit_byte(0);
|
||||||
|
}
|
||||||
|
try self.emit_byte(@intCast(compiler.upvalues[i].index));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(self: *Parser, can_assign: bool) ParsingError!void {
|
fn call(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
@ -799,19 +903,149 @@ const Parser = struct {
|
|||||||
if (self.match(TokenType.SEMICOLON)) {
|
if (self.match(TokenType.SEMICOLON)) {
|
||||||
try self.emit_return();
|
try self.emit_return();
|
||||||
} else {
|
} else {
|
||||||
|
if (self.compiler.function_type == FunctionType.Initializer) {
|
||||||
|
self.error_msg("Can't return a value from an initialiaer");
|
||||||
|
}
|
||||||
try self.expression();
|
try self.expression();
|
||||||
self.consume(TokenType.SEMICOLON, "Expect ';' after return value.");
|
self.consume(TokenType.SEMICOLON, "Expect ';' after return value.");
|
||||||
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
|
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn class_declaration(self: *Parser) ParsingError!void {
|
||||||
|
self.consume(TokenType.IDENTIFIER, "Expect class name.");
|
||||||
|
const class_name = self.previous.?;
|
||||||
|
|
||||||
|
const name_constant = try self.identifier_constant(self.previous.?);
|
||||||
|
self.declare_variable();
|
||||||
|
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_CLASS), name_constant);
|
||||||
|
try self.define_variable(name_constant);
|
||||||
|
|
||||||
|
var class_compiler = ClassCompiler{
|
||||||
|
.enclosing = self.current_class_compiler(),
|
||||||
|
.has_super_class = false,
|
||||||
|
};
|
||||||
|
self.class_compiler = &class_compiler;
|
||||||
|
|
||||||
|
if (self.match(TokenType.LESS)) {
|
||||||
|
self.consume(TokenType.IDENTIFIER, "Expect superclass name.");
|
||||||
|
try self.variable(false);
|
||||||
|
|
||||||
|
if (identifiers_equals(class_name, self.previous.?)) {
|
||||||
|
self.error_msg("A class can't inherit from itself.");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.begin_scope();
|
||||||
|
self.add_local(self.synthetic_token("super"));
|
||||||
|
try self.define_variable(0);
|
||||||
|
|
||||||
|
try self.named_variable(class_name, false);
|
||||||
|
try self.emit_byte(@intFromEnum(OpCode.OP_INHERIT));
|
||||||
|
|
||||||
|
self.current_class_compiler().?.has_super_class = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.named_variable(class_name, false);
|
||||||
|
|
||||||
|
self.consume(TokenType.LEFT_BRACE, "Expect '{' before class body.");
|
||||||
|
while (!self.check(TokenType.RIGHT_BRACE) and !self.check(TokenType.EOF)) {
|
||||||
|
try self.method();
|
||||||
|
}
|
||||||
|
self.consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
|
||||||
|
try self.emit_byte(@intFromEnum(OpCode.OP_POP));
|
||||||
|
|
||||||
|
if (self.current_class_compiler().?.has_super_class) {
|
||||||
|
try self.end_scope();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.class_compiler = self.current_class_compiler().?.enclosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthetic_token(self: *Parser, text: []const u8) Token {
|
||||||
|
_ = self;
|
||||||
|
return Token{
|
||||||
|
.token_type = TokenType.EOF,
|
||||||
|
.start = text,
|
||||||
|
.length = text.len,
|
||||||
|
.line = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dot(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
|
self.consume(TokenType.IDENTIFIER, "Expect property name after '.'.");
|
||||||
|
const name = try self.identifier_constant(self.previous.?);
|
||||||
|
|
||||||
|
if (can_assign and self.match(TokenType.EQUAL)) {
|
||||||
|
try self.expression();
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_SET_PROPERTY), name);
|
||||||
|
} else if (self.match(TokenType.LEFT_PAREN)) {
|
||||||
|
const arg_count = try self.argument_list();
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_INVOKE), name);
|
||||||
|
try self.emit_byte(@intCast(arg_count));
|
||||||
|
} else {
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_PROPERTY), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method(self: *Parser) ParsingError!void {
|
||||||
|
self.consume(TokenType.IDENTIFIER, "Expect method name.");
|
||||||
|
const constant = try self.identifier_constant(self.previous.?);
|
||||||
|
|
||||||
|
var function_type: FunctionType = FunctionType.Method;
|
||||||
|
// std.debug.print("len: {d} {s}\n", .{self.previous.?.length, });
|
||||||
|
if (self.previous.?.length == 4 and std.mem.eql(u8, self.previous.?.start[0..4], "init")) {
|
||||||
|
function_type = FunctionType.Initializer;
|
||||||
|
}
|
||||||
|
try self.function(function_type);
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_METHOD), constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn this_(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
|
if (self.current_class_compiler() == null) {
|
||||||
|
self.error_msg("Can't use 'this' outside of a class.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = can_assign;
|
||||||
|
try self.variable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn super_(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
|
_ = can_assign;
|
||||||
|
|
||||||
|
if (self.current_class_compiler() == null) {
|
||||||
|
self.error_msg("Can't use 'super' outside of a class.");
|
||||||
|
} else if (!self.current_class_compiler().?.has_super_class) {
|
||||||
|
self.error_msg("Can't use 'super' in a class with no superclass.");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.consume(TokenType.DOT, "Expect '.' after 'super'.");
|
||||||
|
self.consume(TokenType.IDENTIFIER, "Expect superclass method name.");
|
||||||
|
const name = try self.identifier_constant(self.previous.?);
|
||||||
|
|
||||||
|
try self.named_variable(self.synthetic_token("this"), false);
|
||||||
|
|
||||||
|
if (self.match(TokenType.LEFT_PAREN)) {
|
||||||
|
const arg_count = try self.argument_list();
|
||||||
|
try self.named_variable(self.synthetic_token("super"), false);
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_SUPER_INVOKE), name);
|
||||||
|
try self.emit_byte(@intCast(arg_count));
|
||||||
|
} else {
|
||||||
|
try self.named_variable(self.synthetic_token("super"), false);
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_SUPER), name);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FunctionType = enum {
|
const FunctionType = enum {
|
||||||
Function,
|
Function,
|
||||||
Script,
|
Script,
|
||||||
|
Method,
|
||||||
|
Initializer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Compiler = struct {
|
pub const Compiler = struct {
|
||||||
enclosing: ?*Compiler,
|
enclosing: ?*Compiler,
|
||||||
|
|
||||||
function: *Obj.Function,
|
function: *Obj.Function,
|
||||||
@ -819,20 +1053,24 @@ const Compiler = struct {
|
|||||||
|
|
||||||
locals: [constants.UINT8_COUNT]Local,
|
locals: [constants.UINT8_COUNT]Local,
|
||||||
local_count: usize,
|
local_count: usize,
|
||||||
|
upvalues: [constants.UINT8_COUNT]Upvalue,
|
||||||
scope_depth: usize,
|
scope_depth: usize,
|
||||||
|
|
||||||
fn new(allocator: std.mem.Allocator, enclosing: ?*Compiler, function_type: FunctionType) Compiler {
|
fn new(vm: *VM, enclosing: ?*Compiler, function_type: FunctionType) Compiler {
|
||||||
const obj_function = Obj.Function.new(allocator);
|
const obj_function = Obj.Function.new(vm);
|
||||||
|
|
||||||
var compiler = Compiler{
|
var compiler = Compiler{
|
||||||
.locals = undefined,
|
.locals = undefined,
|
||||||
.local_count = 0,
|
.local_count = 0,
|
||||||
|
.upvalues = undefined,
|
||||||
.scope_depth = 0,
|
.scope_depth = 0,
|
||||||
.function = obj_function,
|
.function = obj_function,
|
||||||
.function_type = function_type,
|
.function_type = function_type,
|
||||||
.enclosing = enclosing,
|
.enclosing = enclosing,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
compiler.local_count += 1;
|
||||||
|
|
||||||
compiler.locals[0].depth = 0;
|
compiler.locals[0].depth = 0;
|
||||||
compiler.locals[0].name = Token{
|
compiler.locals[0].name = Token{
|
||||||
.token_type = TokenType.EOF,
|
.token_type = TokenType.EOF,
|
||||||
@ -840,8 +1078,12 @@ const Compiler = struct {
|
|||||||
.length = 0,
|
.length = 0,
|
||||||
.line = 0,
|
.line = 0,
|
||||||
};
|
};
|
||||||
|
compiler.locals[0].is_captured = false;
|
||||||
|
|
||||||
compiler.local_count += 1;
|
if (function_type != FunctionType.Function) {
|
||||||
|
compiler.locals[0].name.start = "this";
|
||||||
|
compiler.locals[0].name.length = 4;
|
||||||
|
}
|
||||||
|
|
||||||
return compiler;
|
return compiler;
|
||||||
}
|
}
|
||||||
@ -855,13 +1097,21 @@ const Compiler = struct {
|
|||||||
const Local = struct {
|
const Local = struct {
|
||||||
name: Token,
|
name: Token,
|
||||||
depth: ?usize,
|
depth: ?usize,
|
||||||
|
is_captured: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Upvalue = struct {
|
||||||
|
index: usize,
|
||||||
|
is_local: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function {
|
pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function {
|
||||||
var compiler = Compiler.new(vm.allocator, null, FunctionType.Script);
|
var compiler = Compiler.new(vm, null, FunctionType.Script);
|
||||||
var scanner = Scanner.init(contents);
|
var scanner = Scanner.init(contents);
|
||||||
var parser = Parser.new(vm, &compiler, &scanner);
|
var parser = Parser.new(vm, &compiler, &scanner);
|
||||||
|
|
||||||
|
vm.parser = &parser;
|
||||||
|
|
||||||
parser.advance();
|
parser.advance();
|
||||||
|
|
||||||
while (!parser.match(TokenType.EOF)) {
|
while (!parser.match(TokenType.EOF)) {
|
||||||
@ -870,6 +1120,7 @@ pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function {
|
|||||||
|
|
||||||
const function = try parser.end_parser();
|
const function = try parser.end_parser();
|
||||||
|
|
||||||
|
vm.parser = null;
|
||||||
if (!parser.had_error) {
|
if (!parser.had_error) {
|
||||||
return function;
|
return function;
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,3 +14,10 @@ pub const DEBUG_PRINT_CODE = false;
|
|||||||
pub const DEBUG_TRACE_EXECUTION = false;
|
pub const DEBUG_TRACE_EXECUTION = false;
|
||||||
pub const DEBUG_PRINT_INTERNAL_STRINGS = false;
|
pub const DEBUG_PRINT_INTERNAL_STRINGS = false;
|
||||||
pub const DEBUG_PRINT_GLOBALS = false;
|
pub const DEBUG_PRINT_GLOBALS = false;
|
||||||
|
|
||||||
|
pub const DEBUG_STRESS_GC = true;
|
||||||
|
pub const DEBUG_LOG_GC = false;
|
||||||
|
|
||||||
|
pub const USE_CUSTON_ALLOCATOR = true;
|
||||||
|
|
||||||
|
pub const GC_HEAP_GROW_FACTOR = 2;
|
||||||
|
14
src/main.zig
14
src/main.zig
@ -9,6 +9,8 @@ const OpCode = @import("./opcode.zig").OpCode;
|
|||||||
const VM = @import("./vm.zig").VM;
|
const VM = @import("./vm.zig").VM;
|
||||||
const InterpretResult = @import("./vm.zig").InterpretResult;
|
const InterpretResult = @import("./vm.zig").InterpretResult;
|
||||||
|
|
||||||
|
const ZloxAllocator = @import("./memory.zig").ZloxAllocator;
|
||||||
|
|
||||||
// XXX imported to run tests.
|
// XXX imported to run tests.
|
||||||
const Table = @import("./table.zig");
|
const Table = @import("./table.zig");
|
||||||
|
|
||||||
@ -55,15 +57,21 @@ pub fn run_file(allocator: Allocator, vm: *VM, filepath: []const u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = false }){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
|
||||||
defer _ = debug.assert(gpa.deinit() == .ok);
|
defer _ = debug.assert(gpa.deinit() == .ok);
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
defer std.process.argsFree(allocator, args);
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
var vm = VM.new(allocator);
|
var vm = VM.new();
|
||||||
vm.init_vm();
|
if (constants.USE_CUSTON_ALLOCATOR) {
|
||||||
|
var zlox_allocator_generator = ZloxAllocator.init(allocator, &vm);
|
||||||
|
const z_allocator = zlox_allocator_generator.allocator();
|
||||||
|
vm.init_vm(z_allocator);
|
||||||
|
} else {
|
||||||
|
vm.init_vm(allocator);
|
||||||
|
}
|
||||||
defer vm.destroy();
|
defer vm.destroy();
|
||||||
|
|
||||||
if (args.len == 1) {
|
if (args.len == 1) {
|
||||||
|
306
src/memory.zig
Normal file
306
src/memory.zig
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const VM = @import("./vm.zig").VM;
|
||||||
|
const Obj = @import("./object.zig").Obj;
|
||||||
|
const ObjType = @import("./object.zig").ObjType;
|
||||||
|
const Table = @import("./table.zig").Table;
|
||||||
|
const Entry = @import("./table.zig").Entry;
|
||||||
|
const Value = @import("./values.zig").Value;
|
||||||
|
const Compiler = @import("./compile.zig").Compiler;
|
||||||
|
const ValueArray = @import("./values.zig").ValueArray;
|
||||||
|
|
||||||
|
const constants = @import("./constant.zig");
|
||||||
|
|
||||||
|
pub const ZloxAllocator = struct {
|
||||||
|
parent_allocator: std.mem.Allocator,
|
||||||
|
vm: *VM,
|
||||||
|
bytes_allocated: usize,
|
||||||
|
next_gc: usize,
|
||||||
|
current_gc: bool,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(parent_allocator: std.mem.Allocator, vm: *VM) Self {
|
||||||
|
return .{
|
||||||
|
.parent_allocator = parent_allocator,
|
||||||
|
.vm = vm,
|
||||||
|
.bytes_allocated = 0,
|
||||||
|
.next_gc = 4096,
|
||||||
|
.current_gc = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocator(self: *Self) std.mem.Allocator {
|
||||||
|
return .{
|
||||||
|
.ptr = self,
|
||||||
|
.vtable = &.{
|
||||||
|
.alloc = alloc,
|
||||||
|
.resize = resize,
|
||||||
|
.free = free,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
|
||||||
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
|
|
||||||
|
const res = self.parent_allocator.rawAlloc(len, ptr_align, ret_addr);
|
||||||
|
|
||||||
|
if (self.bytes_allocated > self.next_gc) {
|
||||||
|
self.collect_garbage();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bytes_allocated += len;
|
||||||
|
|
||||||
|
if (constants.DEBUG_LOG_GC) {
|
||||||
|
if (res == null) {
|
||||||
|
std.debug.print("GC: failed allocing buffer of size {d}\n", .{len});
|
||||||
|
} else {
|
||||||
|
std.debug.print("GC: allocing buffer {*} of size {d}\n", .{ res.?, len });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, new_len: usize, ret_addr: usize) bool {
|
||||||
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
|
|
||||||
|
if (constants.DEBUG_LOG_GC) {
|
||||||
|
std.debug.print("GC: resizing buffer {*} from size {d} to size {d}\n", .{ buf, buf.len, new_len });
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bytes_allocated += new_len - buf.len;
|
||||||
|
|
||||||
|
if (self.bytes_allocated > self.next_gc or constants.DEBUG_STRESS_GC) {
|
||||||
|
self.collect_garbage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.parent_allocator.rawResize(buf, log2_buf_align, new_len, ret_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
|
||||||
|
const self: *Self = @ptrCast(@alignCast(ctx));
|
||||||
|
|
||||||
|
if (constants.DEBUG_LOG_GC) {
|
||||||
|
std.debug.print("GC: freeing buffer {*} of size {d} ({d}/{d})\n", .{ &buf, buf.len, self.bytes_allocated, self.next_gc });
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.parent_allocator.rawFree(buf, log2_buf_align, ret_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_vm(self: *Self, vm: *VM) void {
|
||||||
|
self.vm = vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grow_capacity(capacity: usize) usize {
|
||||||
|
if (capacity < 8) {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
return capacity * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_garbage(self: *Self) void {
|
||||||
|
if (self.current_gc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (comptime constants.DEBUG_LOG_GC == true) {
|
||||||
|
std.debug.print("\nGC: collect_garbage(): begin\n", .{});
|
||||||
|
}
|
||||||
|
self.current_gc = true;
|
||||||
|
|
||||||
|
self.mark_roots();
|
||||||
|
self.trace_references();
|
||||||
|
self.table_remove_white(&self.vm.strings);
|
||||||
|
|
||||||
|
self.sweep();
|
||||||
|
|
||||||
|
self.next_gc = self.bytes_allocated * constants.GC_HEAP_GROW_FACTOR;
|
||||||
|
|
||||||
|
if (comptime constants.DEBUG_LOG_GC == true) {
|
||||||
|
std.debug.print("GC: collect_garbage(): end\n\n", .{});
|
||||||
|
}
|
||||||
|
self.current_gc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_roots(self: *Self) void {
|
||||||
|
for (0..self.vm.stack_top) |stack_idx| {
|
||||||
|
self.mark_value(&self.vm.stack[stack_idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0..self.vm.frame_count) |frame_idx| {
|
||||||
|
self.mark_object(&self.vm.frames[frame_idx].closure.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
var upvalue = self.vm.open_upvalues;
|
||||||
|
while (upvalue != null) {
|
||||||
|
self.mark_object(&upvalue.?.obj);
|
||||||
|
upvalue = upvalue.?.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mark_table(&self.vm.globals);
|
||||||
|
|
||||||
|
self.mark_compiler_roots();
|
||||||
|
|
||||||
|
self.mark_object(&self.vm.init_string.?.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_value(self: *Self, value: *Value) void {
|
||||||
|
if (value.is_obj()) {
|
||||||
|
self.mark_object(value.as_obj());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_object(self: *Self, obj: *Obj) void {
|
||||||
|
if (obj.is_marked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constants.DEBUG_LOG_GC) {
|
||||||
|
std.debug.print("GC: mark {*} ", .{obj});
|
||||||
|
obj.print();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
obj.is_marked = true;
|
||||||
|
|
||||||
|
if (self.vm.gray_capacity < self.vm.gray_count + 1) {
|
||||||
|
self.vm.gray_capacity = grow_capacity(self.vm.gray_capacity);
|
||||||
|
self.vm.gray_stack = self.allocator().realloc(self.vm.gray_stack.?, self.vm.gray_capacity) catch {
|
||||||
|
@panic("failed to realloc gray stack");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// doing a realloc here will likely recall mark_roots and so on.
|
||||||
|
self.vm.gray_stack.?[self.vm.gray_count] = obj;
|
||||||
|
self.vm.gray_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_table(self: *Self, table: *Table) void {
|
||||||
|
for (0..table.capacity) |idx| {
|
||||||
|
const entry = &table.entries[idx];
|
||||||
|
if (entry.key != null) {
|
||||||
|
self.mark_object(&entry.key.?.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mark_value(&entry.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_compiler_roots(self: *Self) void {
|
||||||
|
if (self.vm.parser == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var compiler: ?*Compiler = self.vm.parser.?.compiler;
|
||||||
|
|
||||||
|
while (compiler != null) {
|
||||||
|
self.mark_object(&compiler.?.function.obj);
|
||||||
|
compiler = compiler.?.enclosing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trace_references(self: *Self) void {
|
||||||
|
while (self.vm.gray_count > 0) {
|
||||||
|
self.vm.gray_count -= 1;
|
||||||
|
const obj: *Obj = self.vm.gray_stack.?[self.vm.gray_count];
|
||||||
|
self.blacken_object(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blacken_object(self: *Self, obj: *Obj) void {
|
||||||
|
if (constants.DEBUG_LOG_GC) {
|
||||||
|
std.debug.print("GC: {*} blacken ", .{obj});
|
||||||
|
obj.print();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
switch (obj.kind) {
|
||||||
|
ObjType.Native, ObjType.String => {},
|
||||||
|
ObjType.Upvalue => self.mark_value(&obj.as_upvalue().closed),
|
||||||
|
ObjType.Function => {
|
||||||
|
const function: *Obj.Function = obj.as_function();
|
||||||
|
if (function.name != null) {
|
||||||
|
self.mark_object(&function.name.?.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mark_array(&function.chunk.constants);
|
||||||
|
},
|
||||||
|
ObjType.Closure => {
|
||||||
|
const closure: *Obj.Closure = obj.as_closure();
|
||||||
|
self.mark_object(&closure.function.obj);
|
||||||
|
for (0..closure.upvalue_count) |i| {
|
||||||
|
if (closure.upvalues[i] != null) {
|
||||||
|
self.mark_object(&closure.upvalues[i].?.obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ObjType.Class => {
|
||||||
|
const class: *Obj.Class = obj.as_class();
|
||||||
|
self.mark_object(&class.name.obj);
|
||||||
|
self.mark_table(&class.methods);
|
||||||
|
},
|
||||||
|
ObjType.Instance => {
|
||||||
|
const instance: *Obj.Instance = obj.as_instance();
|
||||||
|
self.mark_object(&instance.class.obj);
|
||||||
|
self.mark_table(&instance.fields);
|
||||||
|
},
|
||||||
|
ObjType.BoundMethod => {
|
||||||
|
const bound_method: *Obj.BoundMethod = obj.as_bound_method();
|
||||||
|
self.mark_value(&bound_method.receiver);
|
||||||
|
self.mark_object(&bound_method.method.obj);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mark_array(self: *Self, value_array: *ValueArray) void {
|
||||||
|
for (0..value_array.count) |i| {
|
||||||
|
self.mark_value(&value_array.values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn table_remove_white(self: *Self, table: *Table) void {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
for (0..table.capacity) |idx| {
|
||||||
|
const entry: *Entry = &table.entries[idx];
|
||||||
|
if (entry.key != null and !entry.key.?.obj.is_marked) {
|
||||||
|
if (comptime constants.DEBUG_LOG_GC) {
|
||||||
|
std.debug.print("GC: table_remove_white: deleting {s}\n", .{entry.key.?.chars});
|
||||||
|
}
|
||||||
|
_ = table.del(entry.key.?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sweep(self: *Self) void {
|
||||||
|
var previous: ?*Obj = null;
|
||||||
|
var object: ?*Obj = self.vm.objects;
|
||||||
|
|
||||||
|
while (object != null) {
|
||||||
|
if (object.?.is_marked) {
|
||||||
|
object.?.is_marked = false;
|
||||||
|
previous = object;
|
||||||
|
object = object.?.next;
|
||||||
|
} else {
|
||||||
|
const unreached: *Obj = object.?;
|
||||||
|
object = object.?.next;
|
||||||
|
|
||||||
|
if (previous != null) {
|
||||||
|
previous.?.next = object;
|
||||||
|
} else {
|
||||||
|
self.vm.objects = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime constants.DEBUG_LOG_GC) {
|
||||||
|
std.debug.print("GC: sweeping {*}: ", .{unreached});
|
||||||
|
unreached.print();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
unreached.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
64
src/native.zig
Normal file
64
src/native.zig
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Obj = @import("./object.zig").Obj;
|
||||||
|
const Value = @import("./values.zig").Value;
|
||||||
|
const VM = @import("./vm.zig").VM;
|
||||||
|
|
||||||
|
pub fn clock(vm: *VM, arg_count: usize, args: []Value) Value {
|
||||||
|
_ = vm;
|
||||||
|
_ = arg_count;
|
||||||
|
_ = args;
|
||||||
|
|
||||||
|
const ts = std.time.milliTimestamp();
|
||||||
|
return Value.number_val(@floatFromInt(ts));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn power(vm: *VM, arg_count: usize, args: []Value) Value {
|
||||||
|
_ = vm;
|
||||||
|
|
||||||
|
if (arg_count != 2) {
|
||||||
|
std.debug.print("power() is expecting 2 arguments.\n", .{});
|
||||||
|
return Value.nil_val();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args[0].is_number() or !args[0].is_number()) {
|
||||||
|
std.debug.print("args must be numbers.\n", .{});
|
||||||
|
return Value.nil_val();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result_f64: f64 = std.math.pow(f64, args[0].as_number(), args[1].as_number());
|
||||||
|
|
||||||
|
return Value.number_val(result_f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn str2num(vm: *VM, arg_count: usize, args: []Value) Value {
|
||||||
|
_ = vm;
|
||||||
|
|
||||||
|
if (arg_count != 1 or !args[0].is_string()) {
|
||||||
|
std.debug.print("str2num() is expecting 1 string argument.\n", .{});
|
||||||
|
return Value.nil_val();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = std.fmt.parseFloat(f64, args[0].as_cstring()) catch {
|
||||||
|
std.debug.print("invalid string for number.\n", .{});
|
||||||
|
return Value.nil_val();
|
||||||
|
};
|
||||||
|
|
||||||
|
return Value.number_val(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num2str(vm: *VM, arg_count: usize, args: []Value) Value {
|
||||||
|
if (arg_count != 1 or !args[0].is_number()) {
|
||||||
|
std.debug.print("num2str() is expecting 1 number argument.\n", .{});
|
||||||
|
return Value.nil_val();
|
||||||
|
}
|
||||||
|
|
||||||
|
const str = std.fmt.allocPrint(vm.allocator, "{d}", .{args[0].as_number()}) catch {
|
||||||
|
std.debug.print("unable to convert number to string.\n", .{});
|
||||||
|
return Value.nil_val();
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = Obj.String.new(vm, str);
|
||||||
|
|
||||||
|
return Value.obj_val(&result.obj);
|
||||||
|
}
|
256
src/object.zig
256
src/object.zig
@ -3,7 +3,9 @@ const debug = std.debug;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Chunk = @import("./chunk.zig").Chunk;
|
const Chunk = @import("./chunk.zig").Chunk;
|
||||||
|
const Table = @import("./table.zig").Table;
|
||||||
const Value = @import("./values.zig").Value;
|
const Value = @import("./values.zig").Value;
|
||||||
|
const VM = @import("./vm.zig").VM;
|
||||||
|
|
||||||
const compute_hash = @import("./utils.zig").compute_hash;
|
const compute_hash = @import("./utils.zig").compute_hash;
|
||||||
|
|
||||||
@ -11,27 +13,56 @@ pub const ObjType = enum {
|
|||||||
String,
|
String,
|
||||||
Function,
|
Function,
|
||||||
Native,
|
Native,
|
||||||
|
Closure,
|
||||||
|
Upvalue,
|
||||||
|
Class,
|
||||||
|
Instance,
|
||||||
|
BoundMethod,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const NativeFn = *const fn (arg_count: usize, args: []Value) Value;
|
pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value;
|
||||||
|
|
||||||
pub const Obj = struct {
|
pub const Obj = struct {
|
||||||
kind: ObjType,
|
kind: ObjType,
|
||||||
allocator: std.mem.Allocator,
|
allocator: Allocator,
|
||||||
|
next: ?*Obj,
|
||||||
|
is_marked: bool,
|
||||||
|
|
||||||
|
fn new(comptime T: type, vm: *VM, kind: ObjType) *T {
|
||||||
|
const created_obj = vm.allocator.create(T) catch unreachable;
|
||||||
|
|
||||||
|
created_obj.obj = Obj{
|
||||||
|
.kind = kind,
|
||||||
|
.allocator = vm.allocator,
|
||||||
|
.next = vm.objects,
|
||||||
|
.is_marked = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.objects = &created_obj.obj;
|
||||||
|
|
||||||
|
return created_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Obj) void {
|
||||||
|
switch (self.kind) {
|
||||||
|
ObjType.String => self.as_string().destroy(),
|
||||||
|
ObjType.Function => self.as_function().destroy(),
|
||||||
|
ObjType.Native => self.as_native().destroy(),
|
||||||
|
ObjType.Closure => self.as_closure().destroy(),
|
||||||
|
ObjType.Upvalue => self.as_upvalue().destroy(),
|
||||||
|
ObjType.Class => self.as_class().destroy(),
|
||||||
|
ObjType.Instance => self.as_instance().destroy(),
|
||||||
|
ObjType.BoundMethod => self.as_bound_method().destroy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const String = struct {
|
pub const String = struct {
|
||||||
obj: Obj,
|
obj: Obj,
|
||||||
chars: []const u8,
|
chars: []const u8,
|
||||||
hash: u32,
|
hash: u32,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator, chars: []const u8) *String {
|
pub fn new(vm: *VM, chars: []const u8) *String {
|
||||||
const obj = Obj{
|
const str_obj = Obj.new(String, vm, ObjType.String);
|
||||||
.kind = ObjType.String,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const str_obj = allocator.create(String) catch unreachable;
|
|
||||||
str_obj.obj = obj;
|
|
||||||
|
|
||||||
str_obj.chars = chars;
|
str_obj.chars = chars;
|
||||||
str_obj.hash = compute_hash(str_obj.chars);
|
str_obj.hash = compute_hash(str_obj.chars);
|
||||||
@ -48,19 +79,16 @@ pub const Obj = struct {
|
|||||||
pub const Function = struct {
|
pub const Function = struct {
|
||||||
obj: Obj,
|
obj: Obj,
|
||||||
arity: usize,
|
arity: usize,
|
||||||
|
upvalue_count: usize,
|
||||||
chunk: *Chunk,
|
chunk: *Chunk,
|
||||||
name: ?*Obj.String,
|
name: ?*Obj.String,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator) *Function {
|
pub fn new(vm: *VM) *Function {
|
||||||
const obj = Obj{
|
const function_obj = Obj.new(Function, vm, ObjType.Function);
|
||||||
.kind = ObjType.Function,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const function_obj = allocator.create(Function) catch unreachable;
|
|
||||||
function_obj.obj = obj;
|
|
||||||
function_obj.arity = 0;
|
function_obj.arity = 0;
|
||||||
function_obj.chunk = Chunk.new(allocator);
|
function_obj.upvalue_count = 0;
|
||||||
|
function_obj.chunk = Chunk.new(vm.allocator, vm);
|
||||||
function_obj.name = null;
|
function_obj.name = null;
|
||||||
|
|
||||||
return function_obj;
|
return function_obj;
|
||||||
@ -76,14 +104,9 @@ pub const Obj = struct {
|
|||||||
obj: Obj,
|
obj: Obj,
|
||||||
native: NativeFn,
|
native: NativeFn,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator, native: NativeFn) *Native {
|
pub fn new(vm: *VM, native: NativeFn) *Native {
|
||||||
const obj = Obj{
|
const native_obj = Obj.new(Native, vm, ObjType.Native);
|
||||||
.kind = ObjType.Native,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const native_obj = allocator.create(Native) catch unreachable;
|
|
||||||
native_obj.obj = obj;
|
|
||||||
native_obj.native = native;
|
native_obj.native = native;
|
||||||
|
|
||||||
return native_obj;
|
return native_obj;
|
||||||
@ -94,6 +117,113 @@ pub const Obj = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Closure = struct {
|
||||||
|
obj: Obj,
|
||||||
|
function: *Obj.Function,
|
||||||
|
upvalues: []?*Obj.Upvalue,
|
||||||
|
upvalue_count: usize,
|
||||||
|
|
||||||
|
pub fn new(vm: *VM, function: *Obj.Function) *Closure {
|
||||||
|
const closure_obj = Obj.new(Closure, vm, ObjType.Closure);
|
||||||
|
|
||||||
|
closure_obj.function = function;
|
||||||
|
closure_obj.upvalue_count = function.upvalue_count;
|
||||||
|
|
||||||
|
closure_obj.upvalues = vm.allocator.alloc(?*Obj.Upvalue, function.upvalue_count) catch unreachable;
|
||||||
|
|
||||||
|
for (0..function.upvalue_count) |i| {
|
||||||
|
closure_obj.upvalues[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return closure_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Closure) void {
|
||||||
|
self.obj.allocator.free(self.upvalues);
|
||||||
|
self.obj.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Upvalue = struct {
|
||||||
|
obj: Obj,
|
||||||
|
location: *Value,
|
||||||
|
next: ?*Obj.Upvalue,
|
||||||
|
closed: Value,
|
||||||
|
|
||||||
|
pub fn new(vm: *VM, slot: *Value) *Upvalue {
|
||||||
|
const upvalue_obj = Obj.new(Upvalue, vm, ObjType.Upvalue);
|
||||||
|
|
||||||
|
upvalue_obj.location = slot;
|
||||||
|
upvalue_obj.next = null;
|
||||||
|
upvalue_obj.closed = Value.nil_val();
|
||||||
|
|
||||||
|
return upvalue_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Upvalue) void {
|
||||||
|
self.obj.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Class = struct {
|
||||||
|
obj: Obj,
|
||||||
|
name: *Obj.String,
|
||||||
|
methods: Table,
|
||||||
|
|
||||||
|
pub fn new(vm: *VM, name: *Obj.String) *Class {
|
||||||
|
const class_obj = Obj.new(Class, vm, ObjType.Class);
|
||||||
|
|
||||||
|
class_obj.name = name;
|
||||||
|
class_obj.methods = Table.new(vm.allocator);
|
||||||
|
|
||||||
|
return class_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Class) void {
|
||||||
|
self.methods.destroy();
|
||||||
|
self.obj.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Instance = struct {
|
||||||
|
obj: Obj,
|
||||||
|
class: *Obj.Class,
|
||||||
|
fields: Table,
|
||||||
|
|
||||||
|
pub fn new(vm: *VM, class: *Obj.Class) *Instance {
|
||||||
|
const instance_obj = Obj.new(Instance, vm, ObjType.Instance);
|
||||||
|
|
||||||
|
instance_obj.class = class;
|
||||||
|
instance_obj.fields = Table.new(vm.allocator);
|
||||||
|
|
||||||
|
return instance_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Instance) void {
|
||||||
|
self.fields.destroy();
|
||||||
|
self.obj.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const BoundMethod = struct {
|
||||||
|
obj: Obj,
|
||||||
|
receiver: Value,
|
||||||
|
method: *Obj.Closure,
|
||||||
|
|
||||||
|
pub fn new(vm: *VM, receiver: Value, method: *Obj.Closure) *BoundMethod {
|
||||||
|
const bound_method = Obj.new(BoundMethod, vm, ObjType.BoundMethod);
|
||||||
|
|
||||||
|
bound_method.receiver = receiver;
|
||||||
|
bound_method.method = method;
|
||||||
|
|
||||||
|
return bound_method;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *BoundMethod) void {
|
||||||
|
self.obj.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn is_type(self: *Obj, kind: ObjType) bool {
|
pub fn is_type(self: *Obj, kind: ObjType) bool {
|
||||||
return self.kind == kind;
|
return self.kind == kind;
|
||||||
}
|
}
|
||||||
@ -110,6 +240,26 @@ pub const Obj = struct {
|
|||||||
return self.is_type(ObjType.Native);
|
return self.is_type(ObjType.Native);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_closure(self: *Obj) bool {
|
||||||
|
return self.is_type(ObjType.Closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_upvalue(self: *Obj) bool {
|
||||||
|
return self.is_type(ObjType.Upvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_class(self: *Obj) bool {
|
||||||
|
return self.is_type(ObjType.Class);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_instance(self: *Obj) bool {
|
||||||
|
return self.is_type(ObjType.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_bound_method(self: *Obj) bool {
|
||||||
|
return self.is_type(ObjType.BoundMethod);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print(self: *Obj) void {
|
pub fn print(self: *Obj) void {
|
||||||
switch (self.kind) {
|
switch (self.kind) {
|
||||||
ObjType.String => {
|
ObjType.String => {
|
||||||
@ -125,25 +275,26 @@ pub const Obj = struct {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ObjType.Native => {
|
ObjType.Native => {
|
||||||
// const obj = self.as_native();
|
|
||||||
debug.print("<native fn>", .{});
|
debug.print("<native fn>", .{});
|
||||||
},
|
},
|
||||||
}
|
ObjType.Closure => {
|
||||||
}
|
const obj = self.as_closure();
|
||||||
|
obj.function.obj.print();
|
||||||
pub fn destroy(self: *Obj) void {
|
|
||||||
switch (self.kind) {
|
|
||||||
ObjType.String => {
|
|
||||||
const obj: *String = @fieldParentPtr("obj", self);
|
|
||||||
obj.destroy();
|
|
||||||
},
|
},
|
||||||
ObjType.Function => {
|
ObjType.Upvalue => {
|
||||||
const obj: *Function = @fieldParentPtr("obj", self);
|
debug.print("upvalue", .{});
|
||||||
obj.destroy();
|
|
||||||
},
|
},
|
||||||
ObjType.Native => {
|
ObjType.Class => {
|
||||||
const obj: *Native = @fieldParentPtr("obj", self);
|
const obj = self.as_class();
|
||||||
obj.destroy();
|
debug.print("{s}", .{obj.name.chars});
|
||||||
|
},
|
||||||
|
ObjType.Instance => {
|
||||||
|
const obj = self.as_instance();
|
||||||
|
debug.print("{s} instance", .{obj.class.name.chars});
|
||||||
|
},
|
||||||
|
ObjType.BoundMethod => {
|
||||||
|
const obj = self.as_bound_method();
|
||||||
|
obj.method.function.obj.print();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,4 +313,29 @@ pub const Obj = struct {
|
|||||||
std.debug.assert(self.kind == ObjType.Native);
|
std.debug.assert(self.kind == ObjType.Native);
|
||||||
return @fieldParentPtr("obj", self);
|
return @fieldParentPtr("obj", self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_closure(self: *Obj) *Closure {
|
||||||
|
std.debug.assert(self.kind == ObjType.Closure);
|
||||||
|
return @fieldParentPtr("obj", self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_upvalue(self: *Obj) *Upvalue {
|
||||||
|
std.debug.assert(self.kind == ObjType.Upvalue);
|
||||||
|
return @fieldParentPtr("obj", self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_class(self: *Obj) *Class {
|
||||||
|
std.debug.assert(self.kind == ObjType.Class);
|
||||||
|
return @fieldParentPtr("obj", self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_instance(self: *Obj) *Instance {
|
||||||
|
std.debug.assert(self.kind == ObjType.Instance);
|
||||||
|
return @fieldParentPtr("obj", self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bound_method(self: *Obj) *BoundMethod {
|
||||||
|
std.debug.assert(self.kind == ObjType.BoundMethod);
|
||||||
|
return @fieldParentPtr("obj", self);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,15 @@ pub const OpCode = enum(u8) {
|
|||||||
OP_TRUE,
|
OP_TRUE,
|
||||||
OP_FALSE,
|
OP_FALSE,
|
||||||
OP_POP,
|
OP_POP,
|
||||||
OP_GET_GLOBAL,
|
|
||||||
OP_DEFINE_GLOBAL,
|
OP_DEFINE_GLOBAL,
|
||||||
|
OP_GET_GLOBAL,
|
||||||
OP_SET_GLOBAL,
|
OP_SET_GLOBAL,
|
||||||
OP_GET_LOCAL,
|
OP_GET_LOCAL,
|
||||||
OP_SET_LOCAL,
|
OP_SET_LOCAL,
|
||||||
|
OP_GET_UPVALUE,
|
||||||
|
OP_SET_UPVALUE,
|
||||||
|
OP_GET_PROPERTY,
|
||||||
|
OP_SET_PROPERTY,
|
||||||
OP_EQUAL,
|
OP_EQUAL,
|
||||||
OP_GREATER,
|
OP_GREATER,
|
||||||
OP_LESS,
|
OP_LESS,
|
||||||
@ -23,5 +27,15 @@ pub const OpCode = enum(u8) {
|
|||||||
OP_JUMP_IF_FALSE,
|
OP_JUMP_IF_FALSE,
|
||||||
OP_LOOP,
|
OP_LOOP,
|
||||||
OP_CALL,
|
OP_CALL,
|
||||||
|
OP_CLOSURE,
|
||||||
|
OP_CLOSE_UPVALUE,
|
||||||
OP_RETURN,
|
OP_RETURN,
|
||||||
|
OP_CLASS,
|
||||||
|
OP_METHOD,
|
||||||
|
OP_INDEX_SET,
|
||||||
|
OP_INDEX_GET,
|
||||||
|
OP_INVOKE,
|
||||||
|
OP_INHERIT,
|
||||||
|
OP_GET_SUPER,
|
||||||
|
OP_SUPER_INVOKE,
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,8 @@ pub const TokenType = enum {
|
|||||||
RIGHT_PAREN,
|
RIGHT_PAREN,
|
||||||
LEFT_BRACE,
|
LEFT_BRACE,
|
||||||
RIGHT_BRACE,
|
RIGHT_BRACE,
|
||||||
|
LEFT_BRACKET,
|
||||||
|
RIGHT_BRACKET,
|
||||||
COMMA,
|
COMMA,
|
||||||
DOT,
|
DOT,
|
||||||
MINUS,
|
MINUS,
|
||||||
@ -55,6 +57,8 @@ pub const TokenType = enum {
|
|||||||
TokenType.RIGHT_PAREN => "RIGHT_PAREN",
|
TokenType.RIGHT_PAREN => "RIGHT_PAREN",
|
||||||
TokenType.LEFT_BRACE => "LEFT_BRACE",
|
TokenType.LEFT_BRACE => "LEFT_BRACE",
|
||||||
TokenType.RIGHT_BRACE => "RIGHT_BRACE",
|
TokenType.RIGHT_BRACE => "RIGHT_BRACE",
|
||||||
|
TokenType.LEFT_BRACKET => "LEFT_BRACKET",
|
||||||
|
TokenType.RIGHT_BRACKET => "RIGHT_BRACKET",
|
||||||
TokenType.COMMA => "COMMA",
|
TokenType.COMMA => "COMMA",
|
||||||
TokenType.DOT => "DOT",
|
TokenType.DOT => "DOT",
|
||||||
TokenType.MINUS => "MINUS",
|
TokenType.MINUS => "MINUS",
|
||||||
@ -140,6 +144,8 @@ pub const Scanner = struct {
|
|||||||
')' => self.make_token(TokenType.RIGHT_PAREN),
|
')' => self.make_token(TokenType.RIGHT_PAREN),
|
||||||
'{' => self.make_token(TokenType.LEFT_BRACE),
|
'{' => self.make_token(TokenType.LEFT_BRACE),
|
||||||
'}' => self.make_token(TokenType.RIGHT_BRACE),
|
'}' => self.make_token(TokenType.RIGHT_BRACE),
|
||||||
|
'[' => self.make_token(TokenType.LEFT_BRACKET),
|
||||||
|
']' => self.make_token(TokenType.RIGHT_BRACKET),
|
||||||
';' => self.make_token(TokenType.SEMICOLON),
|
';' => self.make_token(TokenType.SEMICOLON),
|
||||||
',' => self.make_token(TokenType.COMMA),
|
',' => self.make_token(TokenType.COMMA),
|
||||||
'.' => self.make_token(TokenType.DOT),
|
'.' => self.make_token(TokenType.DOT),
|
||||||
@ -316,7 +322,7 @@ pub const Scanner = struct {
|
|||||||
pub fn identifier_type(self: *Scanner) TokenType {
|
pub fn identifier_type(self: *Scanner) TokenType {
|
||||||
return switch (self.source[self.start]) {
|
return switch (self.source[self.start]) {
|
||||||
'a' => self.check_keyword(1, 2, "nd", TokenType.AND),
|
'a' => self.check_keyword(1, 2, "nd", TokenType.AND),
|
||||||
'c' => self.check_keyword(1, 4, "class", TokenType.CLASS),
|
'c' => self.check_keyword(1, 4, "lass", TokenType.CLASS),
|
||||||
'e' => self.check_keyword(1, 3, "lse", TokenType.ELSE),
|
'e' => self.check_keyword(1, 3, "lse", TokenType.ELSE),
|
||||||
'f' => if (self.current - self.start > 1) {
|
'f' => if (self.current - self.start > 1) {
|
||||||
return switch (self.source[self.start + 1]) {
|
return switch (self.source[self.start + 1]) {
|
||||||
|
@ -6,13 +6,13 @@ const constants = @import("./constant.zig");
|
|||||||
|
|
||||||
const Obj = @import("./object.zig").Obj;
|
const Obj = @import("./object.zig").Obj;
|
||||||
const Value = @import("./values.zig").Value;
|
const Value = @import("./values.zig").Value;
|
||||||
|
const ZloxAllocator = @import("./memory.zig").ZloxAllocator;
|
||||||
|
|
||||||
const grow_capacity = @import("./utils.zig").grow_capacity;
|
|
||||||
const compute_hash = @import("./utils.zig").compute_hash;
|
const compute_hash = @import("./utils.zig").compute_hash;
|
||||||
|
|
||||||
const Entry = struct {
|
pub const Entry = struct {
|
||||||
key: ?*Obj.String,
|
key: ?*Obj.String = null,
|
||||||
value: Value,
|
value: Value = Value.nil_val(),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Table = struct {
|
pub const Table = struct {
|
||||||
@ -43,7 +43,7 @@ pub const Table = struct {
|
|||||||
const current_capacity: f32 = @floatFromInt(self.capacity);
|
const current_capacity: f32 = @floatFromInt(self.capacity);
|
||||||
|
|
||||||
if (current_count > current_capacity * constants.TABLE_MAX_LOAD) {
|
if (current_count > current_capacity * constants.TABLE_MAX_LOAD) {
|
||||||
const capacity = grow_capacity(self.capacity);
|
const capacity = ZloxAllocator.grow_capacity(self.capacity);
|
||||||
self.adjust_capacity(capacity);
|
self.adjust_capacity(capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,11 +61,11 @@ pub const Table = struct {
|
|||||||
|
|
||||||
pub fn find_entry(entries: []Entry, key: *Obj.String) ?*Entry {
|
pub fn find_entry(entries: []Entry, key: *Obj.String) ?*Entry {
|
||||||
var tombstone: ?*Entry = null;
|
var tombstone: ?*Entry = null;
|
||||||
var index = key.hash % entries.len;
|
// var index = key.hash % entries.len;
|
||||||
|
var index = key.hash & (entries.len - 1);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const entry = &entries[index];
|
const entry = &entries[index];
|
||||||
|
|
||||||
if (entry.key == null) {
|
if (entry.key == null) {
|
||||||
if (entry.value.is_nil()) {
|
if (entry.value.is_nil()) {
|
||||||
// Empty entry.
|
// Empty entry.
|
||||||
@ -85,16 +85,16 @@ pub const Table = struct {
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
index = (index + 1) % entries.len;
|
// index = (index + 1) % entries.len;
|
||||||
|
index = (index + 1) & (entries.len - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjust_capacity(self: *Table, capacity: usize) void {
|
pub fn adjust_capacity(self: *Table, capacity: usize) void {
|
||||||
var entries = self.allocator.alloc(Entry, capacity) catch unreachable;
|
const entries = self.allocator.alloc(Entry, capacity) catch unreachable;
|
||||||
|
|
||||||
for (0..entries.len) |idx| {
|
for (entries) |*e| {
|
||||||
entries[idx].key = null;
|
e.* = Entry{};
|
||||||
entries[idx].value = Value.nil_val();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.count = 0;
|
self.count = 0;
|
||||||
@ -122,7 +122,7 @@ pub const Table = struct {
|
|||||||
std.debug.print("== Hash table count:{} capacity:{} ==\n", .{ self.count, self.capacity });
|
std.debug.print("== Hash table count:{} capacity:{} ==\n", .{ self.count, self.capacity });
|
||||||
for (self.entries, 0..) |entry, idx| {
|
for (self.entries, 0..) |entry, idx| {
|
||||||
if (entry.key != null) {
|
if (entry.key != null) {
|
||||||
std.debug.print("{d} ({d}) - {s}: ", .{ idx, entry.key.?.hash, entry.key.?.chars });
|
std.debug.print("{d} {*} (size: {d} hash:{d}) - {s}: ", .{ idx, entry.key, entry.key.?.chars.len, entry.key.?.hash, entry.key.?.chars });
|
||||||
entry.value.type_print();
|
entry.value.type_print();
|
||||||
std.debug.print("\n", .{});
|
std.debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
@ -134,12 +134,12 @@ pub const Table = struct {
|
|||||||
std.debug.print("== End of hash table ==\n\n", .{});
|
std.debug.print("== End of hash table ==\n\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_all(self: *Table, from: Table) void {
|
pub fn add_all(self: *Table, to: *Table) void {
|
||||||
for (from.entries) |entry| {
|
for (self.entries) |entry| {
|
||||||
if (entry.key == null) {
|
if (entry.key == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ = self.set(entry.key.?, entry.value);
|
_ = to.set(entry.key.?, entry.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +181,8 @@ pub const Table = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = hash % self.capacity;
|
// var index = hash % self.capacity;
|
||||||
|
var index = hash & (self.capacity - 1);
|
||||||
while (true) {
|
while (true) {
|
||||||
const entry = &self.entries[index];
|
const entry = &self.entries[index];
|
||||||
if (entry.key == null) {
|
if (entry.key == null) {
|
||||||
@ -193,7 +194,8 @@ pub const Table = struct {
|
|||||||
return entry.key;
|
return entry.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
index = (index + 1) % self.capacity;
|
// index = (index + 1) % self.capacity;
|
||||||
|
index = (index + 1) & (self.capacity - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,13 +6,6 @@ const Token = @import("./scanner.zig").Token;
|
|||||||
|
|
||||||
const print_value = @import("./values.zig").print_value;
|
const print_value = @import("./values.zig").print_value;
|
||||||
|
|
||||||
pub fn grow_capacity(capacity: usize) usize {
|
|
||||||
if (capacity < 8) {
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
return capacity * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn simple_instruction(opcode_name: []const u8, offset: usize) usize {
|
pub fn simple_instruction(opcode_name: []const u8, offset: usize) usize {
|
||||||
debug.print("{s:<16}\n", .{opcode_name});
|
debug.print("{s:<16}\n", .{opcode_name});
|
||||||
|
|
||||||
@ -62,3 +55,14 @@ pub fn identifiers_equals(a: Token, b: Token) bool {
|
|||||||
|
|
||||||
return std.mem.eql(u8, a.start[0..a.length], b.start[0..b.length]);
|
return std.mem.eql(u8, a.start[0..a.length], b.start[0..b.length]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn invoke_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize) usize {
|
||||||
|
const constant = chunk.code[offset + 1];
|
||||||
|
const arg_count = chunk.code[offset + 2];
|
||||||
|
|
||||||
|
std.debug.print("{s:<16} ({d} args) {d:4} '", .{ opcode_name, arg_count, constant });
|
||||||
|
chunk.constants.values[constant].print();
|
||||||
|
std.debug.print("'\n", .{});
|
||||||
|
|
||||||
|
return offset + 3;
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ const debug = std.debug;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Obj = @import("./object.zig").Obj;
|
const Obj = @import("./object.zig").Obj;
|
||||||
const utils = @import("./utils.zig");
|
const ZloxAllocator = @import("./memory.zig").ZloxAllocator;
|
||||||
|
|
||||||
pub const ValueType = enum {
|
pub const ValueType = enum {
|
||||||
Bool,
|
Bool,
|
||||||
@ -158,7 +158,7 @@ pub const ValueArray = struct {
|
|||||||
pub fn write(self: *ValueArray, value: Value) !void {
|
pub fn write(self: *ValueArray, value: Value) !void {
|
||||||
if (self.capacity < self.count + 1) {
|
if (self.capacity < self.count + 1) {
|
||||||
const old_capacity = self.capacity;
|
const old_capacity = self.capacity;
|
||||||
self.capacity = utils.grow_capacity(old_capacity);
|
self.capacity = ZloxAllocator.grow_capacity(old_capacity);
|
||||||
self.values = try self.allocator.realloc(self.values, self.capacity);
|
self.values = try self.allocator.realloc(self.values, self.capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
397
src/vm.zig
397
src/vm.zig
@ -4,14 +4,19 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const constants = @import("./constant.zig");
|
const constants = @import("./constant.zig");
|
||||||
|
|
||||||
|
const ZloxAllocator = @import("./memory.zig").ZloxAllocator;
|
||||||
|
|
||||||
const Chunk = @import("./chunk.zig").Chunk;
|
const Chunk = @import("./chunk.zig").Chunk;
|
||||||
const OpCode = @import("./opcode.zig").OpCode;
|
const OpCode = @import("./opcode.zig").OpCode;
|
||||||
const Value = @import("./values.zig").Value;
|
const Value = @import("./values.zig").Value;
|
||||||
const Obj = @import("./object.zig").Obj;
|
const Obj = @import("./object.zig").Obj;
|
||||||
const ObjType = @import("./object.zig").ObjType;
|
const ObjType = @import("./object.zig").ObjType;
|
||||||
|
const Parser = @import("./compile.zig").Parser;
|
||||||
const NativeFn = @import("./object.zig").NativeFn;
|
const NativeFn = @import("./object.zig").NativeFn;
|
||||||
const Table = @import("./table.zig").Table;
|
const Table = @import("./table.zig").Table;
|
||||||
|
|
||||||
|
const natives = @import("./native.zig");
|
||||||
|
|
||||||
const compile = @import("./compile.zig").compile;
|
const compile = @import("./compile.zig").compile;
|
||||||
const compute_hash = @import("./utils.zig").compute_hash;
|
const compute_hash = @import("./utils.zig").compute_hash;
|
||||||
|
|
||||||
@ -24,7 +29,7 @@ pub const InterpretResult = enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const CallFrame = struct {
|
pub const CallFrame = struct {
|
||||||
function: *Obj.Function,
|
closure: *Obj.Closure,
|
||||||
ip: usize,
|
ip: usize,
|
||||||
// pointer to stack index provided to this frame
|
// pointer to stack index provided to this frame
|
||||||
slots_idx: usize,
|
slots_idx: usize,
|
||||||
@ -34,29 +39,51 @@ pub const VM = struct {
|
|||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
stack: [constants.STACK_MAX]Value,
|
stack: [constants.STACK_MAX]Value,
|
||||||
stack_top: usize,
|
stack_top: usize,
|
||||||
// Keeping creating objects in references to destroy objects on cleaning.
|
|
||||||
// In the book, a linked list between objects is used to handle this.
|
|
||||||
references: std.ArrayList(*Obj),
|
|
||||||
strings: Table,
|
strings: Table,
|
||||||
globals: Table,
|
globals: Table,
|
||||||
frames: [constants.FRAMES_MAX]CallFrame,
|
frames: [constants.FRAMES_MAX]CallFrame,
|
||||||
frame_count: usize,
|
frame_count: usize,
|
||||||
|
open_upvalues: ?*Obj.Upvalue,
|
||||||
|
objects: ?*Obj,
|
||||||
|
parser: ?*Parser,
|
||||||
|
gray_count: usize,
|
||||||
|
gray_capacity: usize,
|
||||||
|
gray_stack: ?[]*Obj,
|
||||||
|
init_string: ?*Obj.String,
|
||||||
|
|
||||||
pub fn new(allocator: Allocator) VM {
|
pub fn new() VM {
|
||||||
return VM{
|
const vm = VM{
|
||||||
.allocator = allocator,
|
.allocator = undefined,
|
||||||
.stack = undefined,
|
.stack = undefined,
|
||||||
.stack_top = 0,
|
.stack_top = 0,
|
||||||
.references = std.ArrayList(*Obj).init(allocator),
|
.strings = undefined,
|
||||||
.strings = Table.new(allocator),
|
.globals = undefined,
|
||||||
.globals = Table.new(allocator),
|
|
||||||
.frames = undefined,
|
.frames = undefined,
|
||||||
.frame_count = 0,
|
.frame_count = 0,
|
||||||
|
.open_upvalues = null,
|
||||||
|
.objects = null,
|
||||||
|
.parser = null,
|
||||||
|
.gray_capacity = 0,
|
||||||
|
.gray_count = 0,
|
||||||
|
.gray_stack = &.{},
|
||||||
|
.init_string = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return vm;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_vm(self: *VM) void {
|
pub fn init_vm(self: *VM, allocator: Allocator) void {
|
||||||
self.define_native("clock", clock_native);
|
self.allocator = allocator;
|
||||||
|
self.globals = Table.new(self.allocator);
|
||||||
|
self.strings = Table.new(self.allocator);
|
||||||
|
|
||||||
|
_ = try self.push(Value.obj_val(&self.copy_string("init").obj));
|
||||||
|
self.init_string = self.pop().as_string();
|
||||||
|
|
||||||
|
self.define_native("clock", natives.clock);
|
||||||
|
self.define_native("power", natives.power);
|
||||||
|
self.define_native("str2num", natives.str2num);
|
||||||
|
self.define_native("num2str", natives.num2str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *VM) void {
|
pub fn destroy(self: *VM) void {
|
||||||
@ -66,12 +93,36 @@ pub const VM = struct {
|
|||||||
|
|
||||||
self.strings.destroy();
|
self.strings.destroy();
|
||||||
self.globals.destroy();
|
self.globals.destroy();
|
||||||
self.clean_references();
|
self.init_string = null;
|
||||||
self.references.deinit();
|
self.destroy_objects();
|
||||||
|
|
||||||
|
if (self.gray_stack != null) {
|
||||||
|
self.allocator.free(self.gray_stack.?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy_objects(self: *VM) void {
|
||||||
|
var obj = self.objects;
|
||||||
|
while (obj != null) {
|
||||||
|
const obj_next = obj.?.next;
|
||||||
|
obj.?.destroy();
|
||||||
|
obj = obj_next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_objects(self: *VM) void {
|
||||||
|
var obj = self.objects;
|
||||||
|
while (obj != null) {
|
||||||
|
const obj_next = obj.?.next;
|
||||||
|
std.debug.print("OBJ: {*} {any}", .{ obj.?, obj.?.kind });
|
||||||
|
// obj.?.print();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
obj = obj_next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn current_chunk(self: *VM) *Chunk {
|
inline fn current_chunk(self: *VM) *Chunk {
|
||||||
return self.frames[self.frame_count - 1].function.chunk;
|
return self.frames[self.frame_count - 1].closure.function.chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn current_frame(self: *VM) *CallFrame {
|
inline fn current_frame(self: *VM) *CallFrame {
|
||||||
@ -83,10 +134,12 @@ pub const VM = struct {
|
|||||||
if (function == null) {
|
if (function == null) {
|
||||||
return InterpretResult.COMPILE_ERROR;
|
return InterpretResult.COMPILE_ERROR;
|
||||||
}
|
}
|
||||||
defer function.?.destroy();
|
|
||||||
|
|
||||||
_ = try self.push(Value.obj_val(&function.?.obj));
|
_ = try self.push(Value.obj_val(&function.?.obj));
|
||||||
_ = self.call(function.?, 0);
|
const closure: *Obj.Closure = Obj.Closure.new(self, function.?);
|
||||||
|
_ = self.pop();
|
||||||
|
_ = try self.push(Value.obj_val(&closure.obj));
|
||||||
|
_ = self.call(closure, 0);
|
||||||
|
|
||||||
return try self.run();
|
return try self.run();
|
||||||
}
|
}
|
||||||
@ -103,7 +156,7 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
debug.print("\n", .{});
|
debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
_ = self.current_chunk().dissassemble_instruction(self.current_frame().ip);
|
_ = self.current_chunk().disassemble_instruction(self.current_frame().ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
const instruction = self.read_byte();
|
const instruction = self.read_byte();
|
||||||
@ -145,6 +198,7 @@ pub const VM = struct {
|
|||||||
},
|
},
|
||||||
@intFromEnum(OpCode.OP_RETURN) => {
|
@intFromEnum(OpCode.OP_RETURN) => {
|
||||||
const result = self.pop();
|
const result = self.pop();
|
||||||
|
self.close_upvalues(&self.stack[self.current_frame().slots_idx]);
|
||||||
self.frame_count -= 1;
|
self.frame_count -= 1;
|
||||||
if (self.frame_count == 0) {
|
if (self.frame_count == 0) {
|
||||||
_ = self.pop();
|
_ = self.pop();
|
||||||
@ -217,6 +271,143 @@ pub const VM = struct {
|
|||||||
return InterpretResult.RUNTIME_ERROR;
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@intFromEnum(OpCode.OP_CLOSURE) => {
|
||||||
|
const function = self.read_constant().as_obj().as_function();
|
||||||
|
const closure = Obj.Closure.new(self, function);
|
||||||
|
_ = try self.push(Value.obj_val(&closure.obj));
|
||||||
|
for (0..closure.upvalue_count) |i| {
|
||||||
|
const is_local = self.read_byte();
|
||||||
|
const index = self.read_byte();
|
||||||
|
if (is_local == 1) {
|
||||||
|
const value_idx = self.current_frame().slots_idx + index;
|
||||||
|
closure.upvalues[i] = self.capture_upvalue(&self.stack[value_idx]);
|
||||||
|
} else {
|
||||||
|
closure.upvalues[i] = self.current_frame().closure.upvalues[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_GET_UPVALUE) => {
|
||||||
|
const slot = self.read_byte();
|
||||||
|
try self.push(self.current_frame().closure.upvalues[slot].?.location.*);
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_SET_UPVALUE) => {
|
||||||
|
const slot = self.read_byte();
|
||||||
|
self.current_frame().closure.upvalues[slot].?.location = @constCast(&self.peek(0));
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_CLOSE_UPVALUE) => {
|
||||||
|
self.close_upvalues(&self.stack[self.stack_top - 1]);
|
||||||
|
_ = self.pop();
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_CLASS) => {
|
||||||
|
const name: *Obj.String = self.read_constant().as_string();
|
||||||
|
_ = try self.push(Value.obj_val(&Obj.Class.new(self, name).obj));
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_GET_PROPERTY) => {
|
||||||
|
if (!self.peek(0).is_obj() or !self.peek(0).as_obj().is_instance()) {
|
||||||
|
self.runtime_error("Only instances have properties.");
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = self.peek(0).as_obj().as_instance();
|
||||||
|
const name = self.read_constant().as_string();
|
||||||
|
|
||||||
|
var value = Value.nil_val();
|
||||||
|
|
||||||
|
if (instance.fields.get(name, &value)) {
|
||||||
|
_ = self.pop();
|
||||||
|
_ = try self.push(value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self.bind_method(instance.class, name)) {
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_SET_PROPERTY) => {
|
||||||
|
if (!self.peek(1).is_obj() or !self.peek(1).as_obj().is_instance()) {
|
||||||
|
self.runtime_error("Only instances have fields.");
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
const instance = self.peek(1).as_obj().as_instance();
|
||||||
|
_ = instance.fields.set(self.read_constant().as_string(), self.peek(0));
|
||||||
|
|
||||||
|
const value = self.pop();
|
||||||
|
_ = self.pop();
|
||||||
|
_ = try self.push(value);
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_METHOD) => {
|
||||||
|
self.define_method(self.read_constant().as_string());
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_INDEX_GET) => {
|
||||||
|
if (!self.peek(0).is_number() or !self.peek(1).is_string()) {
|
||||||
|
self.runtime_error("A number and a string are required for indexes.");
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
const index_val = self.pop();
|
||||||
|
const value = self.pop();
|
||||||
|
|
||||||
|
const index: usize = @as(usize, @intFromFloat(index_val.as_number()));
|
||||||
|
if (index >= value.as_cstring().len) {
|
||||||
|
self.runtime_error("The index must be set between 0 and string len.");
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
const c = value.as_cstring()[index .. index + 1];
|
||||||
|
|
||||||
|
_ = try self.push(Value.obj_val(&self.copy_string(c).obj));
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_INDEX_SET) => {
|
||||||
|
const value = self.pop();
|
||||||
|
const index_val = self.pop();
|
||||||
|
const origin = self.pop();
|
||||||
|
|
||||||
|
if (!value.is_string() or value.as_cstring().len != 1) {
|
||||||
|
self.runtime_error("Value to assign must be one byte.");
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index: usize = @as(usize, @intFromFloat(index_val.as_number()));
|
||||||
|
|
||||||
|
var str = self.allocator.dupe(u8, origin.as_cstring()) catch unreachable;
|
||||||
|
str[index] = value.as_cstring()[0];
|
||||||
|
|
||||||
|
_ = try self.push(Value.obj_val(&self.take_string(str).obj));
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_INVOKE) => {
|
||||||
|
const method = self.read_constant().as_string();
|
||||||
|
const arg_count = self.read_byte();
|
||||||
|
if (!self.invoke(method, arg_count)) {
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_INHERIT) => {
|
||||||
|
const superclass = self.peek(1);
|
||||||
|
const subclass = self.peek(0).as_obj().as_class();
|
||||||
|
|
||||||
|
if (!superclass.is_obj() or !superclass.as_obj().is_class()) {
|
||||||
|
self.runtime_error("Superclass must be a class.");
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
superclass.as_obj().as_class().methods.add_all(&subclass.methods);
|
||||||
|
_ = self.pop(); // subclass
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_GET_SUPER) => {
|
||||||
|
const name: *Obj.String = self.read_constant().as_string();
|
||||||
|
const superclass = self.pop().as_obj().as_class();
|
||||||
|
|
||||||
|
if (!self.bind_method(superclass, name)) {
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@intFromEnum(OpCode.OP_SUPER_INVOKE) => {
|
||||||
|
const method = self.read_constant().as_string();
|
||||||
|
const arg_count = self.read_byte();
|
||||||
|
const superclass = self.pop().as_obj().as_class();
|
||||||
|
|
||||||
|
if (!self.invoke_from_class(superclass, method, arg_count)) {
|
||||||
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
},
|
||||||
else => {
|
else => {
|
||||||
debug.print("Invalid instruction: {d}\n", .{instruction});
|
debug.print("Invalid instruction: {d}\n", .{instruction});
|
||||||
return InterpretResult.RUNTIME_ERROR;
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
@ -287,14 +478,14 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn concatenate(self: *VM) !void {
|
pub fn concatenate(self: *VM) !void {
|
||||||
const b = self.pop().as_cstring();
|
const b = self.peek(0).as_cstring();
|
||||||
const a = self.pop().as_cstring();
|
const a = self.peek(1).as_cstring();
|
||||||
|
|
||||||
const concat_str = try std.mem.concat(self.current_chunk().allocator, u8, &.{ a, b });
|
const concat_str = try std.mem.concat(self.current_chunk().allocator, u8, &.{ a, b });
|
||||||
|
const string_obj = self.take_string(concat_str);
|
||||||
|
|
||||||
var string_obj = self.take_string(concat_str);
|
_ = self.pop();
|
||||||
|
_ = self.pop();
|
||||||
self.add_reference(&string_obj.obj);
|
|
||||||
|
|
||||||
try self.push(Value.obj_val(&string_obj.obj));
|
try self.push(Value.obj_val(&string_obj.obj));
|
||||||
}
|
}
|
||||||
@ -310,15 +501,15 @@ pub const VM = struct {
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const frame = self.frames[frame_idx];
|
const frame = self.frames[frame_idx];
|
||||||
const function = frame.function;
|
const closure = frame.closure;
|
||||||
const instruction = frame.ip;
|
const instruction = frame.ip;
|
||||||
|
|
||||||
debug.print("[line {d}] in ", .{function.chunk.lines[instruction]});
|
debug.print("[line {d}] in ", .{closure.function.chunk.lines[instruction]});
|
||||||
|
|
||||||
if (function.name == null) {
|
if (closure.function.name == null) {
|
||||||
debug.print("script\n", .{});
|
debug.print("script\n", .{});
|
||||||
} else {
|
} else {
|
||||||
debug.print("{s}()\n", .{function.name.?.chars});
|
debug.print("{s}()\n", .{closure.function.name.?.chars});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame_idx == 0) {
|
if (frame_idx == 0) {
|
||||||
@ -328,23 +519,6 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_reference(self: *VM, obj: *Obj) void {
|
|
||||||
// do not add duplicate references
|
|
||||||
for (self.references.items) |item| {
|
|
||||||
if (item == obj) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// XXX TODO catch unreachable to prevents
|
|
||||||
self.references.append(obj) catch unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clean_references(self: *VM) void {
|
|
||||||
for (self.references.items) |item| {
|
|
||||||
item.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn copy_string(self: *VM, source: []const u8) *Obj.String {
|
pub fn copy_string(self: *VM, source: []const u8) *Obj.String {
|
||||||
const hash = compute_hash(source);
|
const hash = compute_hash(source);
|
||||||
const obj_string = self.strings.find_string(source, hash);
|
const obj_string = self.strings.find_string(source, hash);
|
||||||
@ -371,8 +545,11 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocate_string(self: *VM, source: []const u8) *Obj.String {
|
pub fn allocate_string(self: *VM, source: []const u8) *Obj.String {
|
||||||
const obj_string = Obj.String.new(self.allocator, source);
|
const obj_string = Obj.String.new(self, source);
|
||||||
|
|
||||||
|
_ = try self.push(Value.obj_val(&obj_string.obj));
|
||||||
_ = self.strings.set(obj_string, Value.nil_val());
|
_ = self.strings.set(obj_string, Value.nil_val());
|
||||||
|
_ = self.pop();
|
||||||
|
|
||||||
return obj_string;
|
return obj_string;
|
||||||
}
|
}
|
||||||
@ -381,18 +558,41 @@ pub const VM = struct {
|
|||||||
if (callee.is_obj()) {
|
if (callee.is_obj()) {
|
||||||
switch (callee.as_obj().kind) {
|
switch (callee.as_obj().kind) {
|
||||||
ObjType.Function => {
|
ObjType.Function => {
|
||||||
return self.call(callee.as_obj().as_function(), arg_count);
|
return self.call(callee.as_obj().as_closure(), arg_count);
|
||||||
},
|
},
|
||||||
ObjType.Native => {
|
ObjType.Native => {
|
||||||
const native_obj: *Obj.Native = callee.as_obj().as_native();
|
const native_obj: *Obj.Native = callee.as_obj().as_native();
|
||||||
const value = native_obj.native(
|
const value = native_obj.native(
|
||||||
|
self,
|
||||||
arg_count,
|
arg_count,
|
||||||
self.stack[self.current_frame().slots_idx - arg_count .. self.current_frame().slots_idx],
|
self.stack[self.stack_top - arg_count .. self.stack_top],
|
||||||
);
|
);
|
||||||
self.stack_top -= arg_count + 1;
|
self.stack_top -= arg_count + 1;
|
||||||
_ = try self.push(value);
|
_ = try self.push(value);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
ObjType.Closure => {
|
||||||
|
return self.call(callee.as_obj().as_closure(), arg_count);
|
||||||
|
},
|
||||||
|
ObjType.Class => {
|
||||||
|
const class = callee.as_obj().as_class();
|
||||||
|
self.stack[self.stack_top - arg_count - 1] = Value.obj_val(&Obj.Instance.new(self, class).obj);
|
||||||
|
|
||||||
|
var initializer = Value.nil_val();
|
||||||
|
|
||||||
|
if (class.methods.get(self.init_string.?, &initializer) == true) {
|
||||||
|
return self.call(initializer.as_obj().as_closure(), arg_count);
|
||||||
|
} else if (arg_count != 0) {
|
||||||
|
self.runtime_error("Expected 0 arguments."); // XXX show number of arguments.
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
ObjType.BoundMethod => {
|
||||||
|
const bound_method = callee.as_obj().as_bound_method();
|
||||||
|
self.stack[self.stack_top - arg_count - 1] = bound_method.receiver;
|
||||||
|
return self.call(bound_method.method, arg_count);
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -400,10 +600,29 @@ pub const VM = struct {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call(self: *VM, function: *Obj.Function, arg_count: usize) bool {
|
pub fn bind_method(self: *VM, class: *Obj.Class, name: *Obj.String) bool {
|
||||||
if (arg_count != function.arity) {
|
var method: Value = Value.nil_val();
|
||||||
self.runtime_error("Invalid argument count.");
|
|
||||||
// runtimeError("Expected %d arguments but got %d.", function->arity, argCount);
|
if (!class.methods.get(name, &method)) {
|
||||||
|
const err_msg = std.fmt.allocPrint(self.allocator, "Undefined property '{s}'.", .{name.chars}) catch unreachable;
|
||||||
|
defer self.allocator.free(err_msg);
|
||||||
|
self.runtime_error(err_msg);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bound: *Obj.BoundMethod = Obj.BoundMethod.new(self, self.peek(0), method.as_obj().as_closure());
|
||||||
|
_ = self.pop();
|
||||||
|
try self.push(Value.obj_val(&bound.obj));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool {
|
||||||
|
if (arg_count != closure.function.arity) {
|
||||||
|
const err_msg = std.fmt.allocPrint(self.allocator, "Expected {d} arguments but got {d}.", .{ closure.function.arity, arg_count }) catch unreachable;
|
||||||
|
defer self.allocator.free(err_msg);
|
||||||
|
self.runtime_error(err_msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,7 +634,7 @@ pub const VM = struct {
|
|||||||
const frame = &self.frames[self.frame_count];
|
const frame = &self.frames[self.frame_count];
|
||||||
self.frame_count += 1;
|
self.frame_count += 1;
|
||||||
|
|
||||||
frame.function = function;
|
frame.closure = closure;
|
||||||
frame.ip = 0;
|
frame.ip = 0;
|
||||||
frame.slots_idx = self.stack_top - arg_count - 1;
|
frame.slots_idx = self.stack_top - arg_count - 1;
|
||||||
|
|
||||||
@ -424,7 +643,7 @@ pub const VM = struct {
|
|||||||
|
|
||||||
pub fn define_native(self: *VM, name: []const u8, native_fn: NativeFn) void {
|
pub fn define_native(self: *VM, name: []const u8, native_fn: NativeFn) void {
|
||||||
_ = try self.push(Value.obj_val(&self.copy_string(name).obj));
|
_ = try self.push(Value.obj_val(&self.copy_string(name).obj));
|
||||||
_ = try self.push(Value.obj_val(&Obj.Native.new(self.allocator, native_fn).obj));
|
_ = try self.push(Value.obj_val(&Obj.Native.new(self, native_fn).obj));
|
||||||
|
|
||||||
_ = self.globals.set(self.stack[0].as_string(), self.stack[1]);
|
_ = self.globals.set(self.stack[0].as_string(), self.stack[1]);
|
||||||
|
|
||||||
@ -432,10 +651,72 @@ pub const VM = struct {
|
|||||||
_ = self.pop();
|
_ = self.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clock_native(arg_count: usize, args: []Value) Value {
|
fn capture_upvalue(self: *VM, local: *Value) *Obj.Upvalue {
|
||||||
const ts = std.time.milliTimestamp();
|
var prev_upvalue: ?*Obj.Upvalue = null;
|
||||||
_ = arg_count;
|
var upvalue: ?*Obj.Upvalue = self.open_upvalues;
|
||||||
_ = args;
|
|
||||||
return Value.number_val(@floatFromInt(ts));
|
while (upvalue != null and @intFromPtr(upvalue.?.location) > @intFromPtr(local)) {
|
||||||
|
prev_upvalue = upvalue;
|
||||||
|
upvalue = upvalue.?.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upvalue != null and upvalue.?.location == local) {
|
||||||
|
return upvalue.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
const created_upvalue = Obj.Upvalue.new(self, local);
|
||||||
|
created_upvalue.next = upvalue;
|
||||||
|
|
||||||
|
if (prev_upvalue == null) {
|
||||||
|
self.open_upvalues = created_upvalue;
|
||||||
|
} else {
|
||||||
|
prev_upvalue.?.next = created_upvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return created_upvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_upvalues(self: *VM, last: *Value) void {
|
||||||
|
while (self.open_upvalues != null and @intFromPtr(self.open_upvalues.?.location) >= @intFromPtr(last)) {
|
||||||
|
const upvalue = self.open_upvalues.?;
|
||||||
|
|
||||||
|
upvalue.closed = upvalue.location.*;
|
||||||
|
upvalue.location = &upvalue.closed;
|
||||||
|
self.open_upvalues = upvalue.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn define_method(self: *VM, name: *Obj.String) void {
|
||||||
|
const method = self.peek(0);
|
||||||
|
const class = self.peek(1).as_obj().as_class();
|
||||||
|
|
||||||
|
_ = class.methods.set(name, method);
|
||||||
|
_ = self.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke(self: *VM, name: *Obj.String, arg_count: usize) bool {
|
||||||
|
const receiver = self.peek(arg_count);
|
||||||
|
const instance = receiver.as_obj().as_instance();
|
||||||
|
|
||||||
|
var value = Value.nil_val();
|
||||||
|
if (instance.fields.get(name, &value)) {
|
||||||
|
self.stack[self.stack_top - arg_count - 1] = value;
|
||||||
|
|
||||||
|
return self.call_value(value, arg_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.invoke_from_class(instance.class, name, arg_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke_from_class(self: *VM, class: *Obj.Class, name: *Obj.String, arg_count: usize) bool {
|
||||||
|
var method = Value.nil_val();
|
||||||
|
if (!class.methods.get(name, &method)) {
|
||||||
|
const err_msg = std.fmt.allocPrint(self.allocator, "Undefined property '{s}'.", .{name.chars}) catch unreachable;
|
||||||
|
defer self.allocator.free(err_msg);
|
||||||
|
self.runtime_error(err_msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.call(method.as_obj().as_closure(), arg_count);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user