implementing calls and functions (ch24)

This commit is contained in:
Patrick MARIE 2024-08-28 16:25:01 +02:00
parent 732e0861b5
commit 3daa675f8d
11 changed files with 252 additions and 46 deletions

View File

@ -0,0 +1,4 @@
fun areWeHavingItYet() {
print "Yes we are!";
}
print areWeHavingItYet;

View File

@ -0,0 +1,9 @@
fun helloWorld(var_str, var_int) {
var b = 1;
print var_str + " blah";
return b + var_int;
}
var c = helloWorld("a", 42);
print c;

View File

@ -0,0 +1,7 @@
fun a() { b(); }
fun b() { c(); }
fun c() {
c("too", "many");
}
a();

View File

@ -0,0 +1,3 @@
fun a(a, b) { var c = a + b; return c; }
print a(2, 4);
print a(4, 6);

View File

@ -10,22 +10,25 @@ const grow_capacity = @import("./utils.zig").grow_capacity;
const utils = @import("./utils.zig"); const utils = @import("./utils.zig");
pub const Chunk = struct { pub const Chunk = struct {
allocator: Allocator,
count: usize, count: usize,
capacity: usize, capacity: usize,
code: []u8, code: []u8,
lines: []usize, lines: []usize,
constants: ValueArray, constants: ValueArray,
allocator: Allocator,
pub fn new(allocator: Allocator) Chunk { pub fn new(allocator: Allocator) *Chunk {
return Chunk{ var chunk: *Chunk = allocator.create(Chunk) catch unreachable;
.count = 0,
.capacity = 0, chunk.allocator = allocator;
.code = &.{}, chunk.count = 0;
.lines = &.{}, chunk.capacity = 0;
.constants = ValueArray.new(allocator), chunk.code = &.{};
.allocator = allocator, chunk.lines = &.{};
}; chunk.constants = ValueArray.new(allocator);
return chunk;
} }
pub fn destroy(self: *Chunk) void { pub fn destroy(self: *Chunk) void {
@ -35,6 +38,8 @@ pub const Chunk = struct {
self.allocator.free(self.code); self.allocator.free(self.code);
self.allocator.free(self.lines); self.allocator.free(self.lines);
} }
self.allocator.destroy(self);
} }
pub fn write(self: *Chunk, byte: u8, line: usize) !void { pub fn write(self: *Chunk, byte: u8, line: usize) !void {
@ -50,8 +55,17 @@ pub const Chunk = struct {
self.count += 1; self.count += 1;
} }
pub fn dump(self: Chunk) void { pub fn dump(self: *Chunk) void {
debug.print("== chunk dump of {*} ==\n", .{self});
debug.print("{any}\n", .{self}); debug.print("{any}\n", .{self});
for (0..self.constants.count) |idx| {
debug.print("constant {d}: {*} ", .{ idx, &self.constants.values[idx] });
self.constants.values[idx].print();
debug.print("\n", .{});
}
debug.print("== end of chunk dump \n\n", .{});
} }
pub fn dissassemble(self: Chunk, name: []const u8) void { pub fn dissassemble(self: Chunk, name: []const u8) void {
@ -101,6 +115,7 @@ pub const Chunk = struct {
@intFromEnum(OpCode.OP_JUMP) => return utils.jump_instruction("OP_JUMP", 1, self, offset), @intFromEnum(OpCode.OP_JUMP) => return utils.jump_instruction("OP_JUMP", 1, self, offset),
@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),
else => { else => {
debug.print("unknown opcode {d}\n", .{instruction}); debug.print("unknown opcode {d}\n", .{instruction});
return offset + 1; return offset + 1;

View File

@ -61,7 +61,7 @@ const Parser = struct {
} }
inline fn current_chunk(self: *Parser) *Chunk { inline fn current_chunk(self: *Parser) *Chunk {
return &self.compiler.function.chunk; return self.compiler.function.chunk;
} }
fn advance(self: *Parser) void { fn advance(self: *Parser) void {
@ -132,17 +132,24 @@ const Parser = struct {
} }
fn emit_return(self: *Parser) ParsingError!void { fn emit_return(self: *Parser) ParsingError!void {
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();
if (!self.had_error and constants.DEBUG_PRINT_CODE) { if (!self.had_error and constants.DEBUG_PRINT_CODE) {
self.current_chunk().dissassemble("code"); self.current_chunk().dissassemble("code");
} }
try self.emit_return(); const function_obj = self.compiler.function;
return self.compiler.function; if (self.compiler.enclosing != null) {
self.compiler = self.compiler.enclosing.?;
}
return function_obj;
} }
fn number(self: *Parser, can_assign: bool) ParsingError!void { fn number(self: *Parser, can_assign: bool) ParsingError!void {
@ -235,7 +242,7 @@ const Parser = struct {
fn get_rule(operator_type: TokenType) ParserRule { fn get_rule(operator_type: TokenType) ParserRule {
return switch (operator_type) { return switch (operator_type) {
TokenType.LEFT_PAREN => ParserRule{ .prefix = grouping, .infix = null, .precedence = Precedence.None }, TokenType.LEFT_PAREN => ParserRule{ .prefix = grouping, .infix = call, .precedence = Precedence.Call },
TokenType.RIGHT_PAREN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.RIGHT_PAREN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
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 },
@ -353,7 +360,9 @@ const Parser = struct {
} }
fn declaration(self: *Parser) ParsingError!void { fn declaration(self: *Parser) ParsingError!void {
if (self.match(TokenType.VAR)) { if (self.match(TokenType.FUN)) {
try self.fun_declaration();
} else if (self.match(TokenType.VAR)) {
try self.var_declaration(); try self.var_declaration();
} else { } else {
try self.statement(); try self.statement();
@ -371,6 +380,8 @@ const Parser = struct {
try self.for_statement(); try self.for_statement();
} else if (self.match(TokenType.IF)) { } else if (self.match(TokenType.IF)) {
try self.if_statement(); try self.if_statement();
} else if (self.match(TokenType.RETURN)) {
try self.return_statement();
} else if (self.match(TokenType.WHILE)) { } else if (self.match(TokenType.WHILE)) {
try self.while_statement(); try self.while_statement();
} else if (self.match(TokenType.LEFT_BRACE)) { } else if (self.match(TokenType.LEFT_BRACE)) {
@ -470,6 +481,9 @@ const Parser = struct {
} }
fn mark_initialized(self: *Parser) void { fn mark_initialized(self: *Parser) void {
if (self.compiler.scope_depth == 0) {
return;
}
self.compiler.locals[self.compiler.local_count - 1].depth = self.compiler.scope_depth; self.compiler.locals[self.compiler.local_count - 1].depth = self.compiler.scope_depth;
} }
@ -701,6 +715,93 @@ const Parser = struct {
try self.end_scope(); try self.end_scope();
} }
fn fun_declaration(self: *Parser) ParsingError!void {
const global: u8 = try self.parse_variable("Expect function name.");
self.mark_initialized();
try self.function(FunctionType.Function);
try self.define_variable(global);
}
fn function(self: *Parser, function_type: FunctionType) ParsingError!void {
var compiler = Compiler.new(self.vm.allocator, self.compiler, function_type);
self.compiler = &compiler;
if (function_type != FunctionType.Script) {
self.compiler.function.name = self.vm.copy_string(self.previous.?.start[0..self.previous.?.length]);
}
self.begin_scope();
self.consume(TokenType.LEFT_PAREN, "Expect '(' after function name.");
if (!self.check(TokenType.RIGHT_PAREN)) {
while (true) {
self.compiler.function.arity += 1;
if (self.compiler.function.arity > 255) {
self.error_at_current("Can't have more than 255 parameters.");
}
const constant = try self.parse_variable("Expect parameter name.");
try self.define_variable(constant);
if (!self.match(TokenType.COMMA)) {
break;
}
}
}
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after parameters.");
self.consume(TokenType.LEFT_BRACE, "Expect '{' before function body.");
try self.block();
const obj_function = try self.end_parser();
const constant = try self.make_constant(Value.obj_val(&obj_function.obj));
try self.emit_bytes(@intFromEnum(OpCode.OP_CONSTANT), constant);
}
fn call(self: *Parser, can_assign: bool) ParsingError!void {
_ = can_assign;
const arg_count = try self.argument_list();
try self.emit_bytes(@intFromEnum(OpCode.OP_CALL), @intCast(arg_count));
}
fn argument_list(self: *Parser) ParsingError!usize {
var arg_count: usize = 0;
if (!self.check(TokenType.RIGHT_PAREN)) {
while (true) {
try self.expression();
if (arg_count == 16) {
self.error_msg("Can't have more than 16 arguments.");
}
arg_count += 1;
if (!self.match(TokenType.COMMA)) {
break;
}
}
}
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after arguments.");
return arg_count;
}
fn return_statement(self: *Parser) ParsingError!void {
if (self.compiler.function_type == FunctionType.Script) {
self.error_msg("Can't return from top-level code.");
}
if (self.match(TokenType.SEMICOLON)) {
try self.emit_return();
} else {
try self.expression();
self.consume(TokenType.SEMICOLON, "Expect ';' after return value.");
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
}
}
}; };
const FunctionType = enum { const FunctionType = enum {
@ -709,6 +810,8 @@ const FunctionType = enum {
}; };
const Compiler = struct { const Compiler = struct {
enclosing: ?*Compiler,
function: *Obj.Function, function: *Obj.Function,
function_type: FunctionType, function_type: FunctionType,
@ -716,7 +819,7 @@ const Compiler = struct {
local_count: usize, local_count: usize,
scope_depth: usize, scope_depth: usize,
fn new(allocator: std.mem.Allocator, function_type: FunctionType) Compiler { fn new(allocator: std.mem.Allocator, enclosing: ?*Compiler, function_type: FunctionType) Compiler {
const obj_function = Obj.Function.new(allocator); const obj_function = Obj.Function.new(allocator);
var compiler = Compiler{ var compiler = Compiler{
@ -725,6 +828,7 @@ const Compiler = struct {
.scope_depth = 0, .scope_depth = 0,
.function = obj_function, .function = obj_function,
.function_type = function_type, .function_type = function_type,
.enclosing = enclosing,
}; };
compiler.locals[0].depth = 0; compiler.locals[0].depth = 0;
@ -739,6 +843,11 @@ const Compiler = struct {
return compiler; return compiler;
} }
fn destroy(self: *Compiler) void {
// do not destroy function here! it is used after compiler life.
_ = self;
}
}; };
const Local = struct { const Local = struct {
@ -747,8 +856,7 @@ const Local = struct {
}; };
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, FunctionType.Script); var compiler = Compiler.new(vm.allocator, 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);

View File

@ -10,7 +10,7 @@ pub const UINT8_COUNT = UINT8_MAX + 1;
pub const FRAMES_MAX = 64; pub const FRAMES_MAX = 64;
pub const STACK_MAX = (FRAMES_MAX * UINT8_MAX); pub const STACK_MAX = (FRAMES_MAX * UINT8_MAX);
pub const DEBUG_PRINT_CODE = true; pub const DEBUG_PRINT_CODE = false;
pub const DEBUG_TRACE_EXECUTION = true; 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;

View File

@ -12,7 +12,7 @@ const InterpretResult = @import("./vm.zig").InterpretResult;
// XXX imported to run tests. // XXX imported to run tests.
const Table = @import("./table.zig"); const Table = @import("./table.zig");
pub fn repl(allocator: Allocator, vm: *VM) !void { pub fn repl(vm: *VM) !void {
var line: [1024]u8 = undefined; var line: [1024]u8 = undefined;
const stdin = std.io.getStdIn().reader(); const stdin = std.io.getStdIn().reader();
@ -34,7 +34,7 @@ pub fn repl(allocator: Allocator, vm: *VM) !void {
break; break;
} }
_ = try vm.interpret(allocator, &line); _ = try vm.interpret(&line);
} }
} }
@ -45,7 +45,7 @@ pub fn run_file(allocator: Allocator, vm: *VM, filepath: []const u8) !void {
const file_content = try file.readToEndAlloc(allocator, 1024 * 1024); const file_content = try file.readToEndAlloc(allocator, 1024 * 1024);
defer allocator.free(file_content); defer allocator.free(file_content);
const result = try vm.interpret(allocator, file_content); const result = try vm.interpret(file_content);
switch (result) { switch (result) {
InterpretResult.COMPILE_ERROR => std.process.exit(65), InterpretResult.COMPILE_ERROR => std.process.exit(65),
@ -55,7 +55,7 @@ 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 = true }){}; var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = false }){};
defer _ = debug.assert(gpa.deinit() == .ok); defer _ = debug.assert(gpa.deinit() == .ok);
const allocator = gpa.allocator(); const allocator = gpa.allocator();
@ -66,7 +66,7 @@ pub fn main() !void {
defer vm.destroy(); defer vm.destroy();
if (args.len == 1) { if (args.len == 1) {
try repl(allocator, &vm); try repl(&vm);
} else if (args.len == 2) { } else if (args.len == 2) {
try run_file(allocator, &vm, args[1]); try run_file(allocator, &vm, args[1]);
} else { } else {

View File

@ -44,7 +44,7 @@ pub const Obj = struct {
pub const Function = struct { pub const Function = struct {
obj: Obj, obj: Obj,
arity: usize, arity: usize,
chunk: Chunk, chunk: *Chunk,
name: ?*Obj.String, name: ?*Obj.String,
pub fn new(allocator: std.mem.Allocator) *Function { pub fn new(allocator: std.mem.Allocator) *Function {

View File

@ -22,5 +22,6 @@ pub const OpCode = enum(u8) {
OP_JUMP, OP_JUMP,
OP_JUMP_IF_FALSE, OP_JUMP_IF_FALSE,
OP_LOOP, OP_LOOP,
OP_CALL,
OP_RETURN, OP_RETURN,
}; };

View File

@ -8,6 +8,7 @@ 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 Table = @import("./table.zig").Table; const Table = @import("./table.zig").Table;
const compile = @import("./compile.zig").compile; const compile = @import("./compile.zig").compile;
@ -65,30 +66,22 @@ pub const VM = struct {
} }
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].function.chunk;
} }
inline fn current_frame(self: *VM) *CallFrame { inline fn current_frame(self: *VM) *CallFrame {
return &self.frames[self.frame_count - 1]; return &self.frames[self.frame_count - 1];
} }
pub fn interpret(self: *VM, allocator: Allocator, content: []const u8) !InterpretResult { pub fn interpret(self: *VM, content: []const u8) !InterpretResult {
var chunk = Chunk.new(allocator); var function = try compile(self, content);
defer chunk.destroy();
const function = try compile(self, content);
if (function == null) { if (function == null) {
return InterpretResult.COMPILE_ERROR; return InterpretResult.COMPILE_ERROR;
} }
defer function.?.destroy(); defer function.?.destroy();
_ = try self.push(Value.obj_val(&function.?.obj)); _ = try self.push(Value.obj_val(&function.?.obj));
_ = self.call(function.?, 0);
const frame = &self.frames[self.frame_count];
self.frame_count += 1;
frame.function = function.?;
frame.ip = 0;
frame.slots_idx = self.stack_top;
return try self.run(); return try self.run();
} }
@ -146,7 +139,15 @@ pub const VM = struct {
debug.print("\n", .{}); debug.print("\n", .{});
}, },
@intFromEnum(OpCode.OP_RETURN) => { @intFromEnum(OpCode.OP_RETURN) => {
return InterpretResult.OK; const result = self.pop();
self.frame_count -= 1;
if (self.frame_count == 0) {
_ = self.pop();
return InterpretResult.OK;
}
self.stack_top = self.frames[self.frame_count].slots_idx;
try self.push(result);
}, },
@intFromEnum(OpCode.OP_EQUAL) => { @intFromEnum(OpCode.OP_EQUAL) => {
try self.push(Value.bool_val(self.pop().equals(self.pop()))); try self.push(Value.bool_val(self.pop().equals(self.pop())));
@ -185,11 +186,11 @@ pub const VM = struct {
}, },
@intFromEnum(OpCode.OP_GET_LOCAL) => { @intFromEnum(OpCode.OP_GET_LOCAL) => {
const slot = self.read_byte(); const slot = self.read_byte();
try self.push(self.stack[self.current_frame().slots_idx + slot - 1]); try self.push(self.stack[self.current_frame().slots_idx + slot]);
}, },
@intFromEnum(OpCode.OP_SET_LOCAL) => { @intFromEnum(OpCode.OP_SET_LOCAL) => {
const slot = self.read_byte(); const slot = self.read_byte();
self.stack[self.current_frame().slots_idx + slot - 1] = self.peek(0); self.stack[self.current_frame().slots_idx + slot] = self.peek(0);
}, },
@intFromEnum(OpCode.OP_JUMP) => { @intFromEnum(OpCode.OP_JUMP) => {
const offset = self.read_short(); const offset = self.read_short();
@ -205,6 +206,12 @@ pub const VM = struct {
const offset = self.read_short(); const offset = self.read_short();
self.current_frame().ip -= offset; self.current_frame().ip -= offset;
}, },
@intFromEnum(OpCode.OP_CALL) => {
const arg_count = self.read_byte();
if (!self.call_value(self.peek(arg_count), 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;
@ -292,11 +299,28 @@ pub const VM = struct {
} }
pub fn runtime_error(self: *VM, err_msg: []const u8) void { pub fn runtime_error(self: *VM, err_msg: []const u8) void {
const instruction = self.current_frame().ip;
const line = self.current_chunk().lines[instruction];
debug.print("err: {s}\n", .{err_msg}); debug.print("err: {s}\n", .{err_msg});
debug.print("[line {d}] in script\n", .{line});
var frame_idx = self.frame_count - 1;
while (true) {
const frame = self.frames[frame_idx];
const function = frame.function;
const instruction = frame.ip;
debug.print("[line {d}] in ", .{function.chunk.lines[instruction]});
if (function.name == null) {
debug.print("script\n", .{});
} else {
debug.print("{s}()\n", .{function.name.?.chars});
}
if (frame_idx == 0) {
break;
}
frame_idx -= 1;
}
} }
pub fn add_reference(self: *VM, obj: *Obj) void { pub fn add_reference(self: *VM, obj: *Obj) void {
@ -347,4 +371,39 @@ pub const VM = struct {
return obj_string; return obj_string;
} }
pub fn call_value(self: *VM, callee: Value, arg_count: usize) bool {
if (callee.is_obj()) {
switch (callee.as_obj().kind) {
ObjType.Function => {
return self.call(callee.as_obj().as_function(), arg_count);
},
else => {},
}
}
self.runtime_error("Can only call functions and classes.");
return false;
}
pub fn call(self: *VM, function: *Obj.Function, arg_count: usize) bool {
if (arg_count != function.arity) {
self.runtime_error("Invalid argument count.");
// runtimeError("Expected %d arguments but got %d.", function->arity, argCount);
return false;
}
if (self.frame_count == constants.FRAMES_MAX) {
self.runtime_error("Stack overflow.");
return false;
}
const frame = &self.frames[self.frame_count];
self.frame_count += 1;
frame.function = function;
frame.ip = 0;
frame.slots_idx = self.stack_top - arg_count - 1;
return true;
}
}; };