compiling expressions (ch17)
This commit is contained in:
parent
4d04208b2a
commit
f9c7a531dc
@ -15,19 +15,21 @@ pub const Chunk = struct {
|
||||
code: []u8,
|
||||
lines: []usize,
|
||||
constants: ValueArray,
|
||||
allocator: Allocator,
|
||||
|
||||
pub fn new() Chunk {
|
||||
pub fn new(allocator: Allocator) Chunk {
|
||||
return Chunk{
|
||||
.count = 0,
|
||||
.capacity = 0,
|
||||
.code = &.{},
|
||||
.lines = &.{},
|
||||
.constants = ValueArray.new(),
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(self: *Chunk, allocator: Allocator) !void {
|
||||
self.deinit(allocator);
|
||||
pub fn init(self: *Chunk) !void {
|
||||
self.deinit(self.allocator);
|
||||
|
||||
self.count = 0;
|
||||
self.capacity = 0;
|
||||
@ -36,12 +38,12 @@ pub const Chunk = struct {
|
||||
self.constants = ValueArray.new();
|
||||
}
|
||||
|
||||
pub fn write(self: *Chunk, allocator: Allocator, byte: u8, line: usize) !void {
|
||||
pub fn write(self: *Chunk, byte: u8, line: usize) !void {
|
||||
if (self.capacity < self.count + 1) {
|
||||
const old_capacity = self.capacity;
|
||||
self.capacity = grow_capacity(old_capacity);
|
||||
self.code = try allocator.realloc(self.code, self.capacity);
|
||||
self.lines = try allocator.realloc(self.lines, self.capacity);
|
||||
self.code = try self.allocator.realloc(self.code, self.capacity);
|
||||
self.lines = try self.allocator.realloc(self.lines, self.capacity);
|
||||
}
|
||||
|
||||
self.code[self.count] = byte;
|
||||
@ -59,7 +61,7 @@ pub const Chunk = struct {
|
||||
var offset: usize = 0;
|
||||
|
||||
while (offset < self.count) {
|
||||
offset += self.dissassemble_instruction(offset);
|
||||
offset = self.dissassemble_instruction(offset);
|
||||
}
|
||||
debug.print("== end of {s} ==\n\n", .{name});
|
||||
}
|
||||
@ -90,17 +92,17 @@ pub const Chunk = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Chunk, allocator: Allocator) void {
|
||||
self.constants.free(allocator);
|
||||
pub fn deinit(self: *Chunk) void {
|
||||
self.constants.free(self.allocator);
|
||||
|
||||
if (self.capacity > 0) {
|
||||
allocator.free(self.code);
|
||||
allocator.free(self.lines);
|
||||
self.allocator.free(self.code);
|
||||
self.allocator.free(self.lines);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_constant(self: *Chunk, allocator: Allocator, value: Value) !usize {
|
||||
try self.constants.write(allocator, value);
|
||||
pub fn add_constant(self: *Chunk, value: Value) !usize {
|
||||
try self.constants.write(self.allocator, value);
|
||||
return self.constants.count - 1;
|
||||
}
|
||||
};
|
||||
|
267
src/compile.zig
267
src/compile.zig
@ -2,28 +2,265 @@ const std = @import("std");
|
||||
const debug = std.debug;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const OpCode = @import("./opcode.zig").OpCode;
|
||||
const Scanner = @import("./scanner.zig").Scanner;
|
||||
const Token = @import("./scanner.zig").Token;
|
||||
const TokenType = @import("./scanner.zig").TokenType;
|
||||
const Chunk = @import("./chunk.zig").Chunk;
|
||||
const Value = @import("./values.zig").Value;
|
||||
|
||||
pub fn compile(allocator: Allocator, contents: []const u8) !void {
|
||||
var line: ?usize = null;
|
||||
const ParsingError = @import("./errors.zig").ParsingError;
|
||||
|
||||
const DEBUG_TRACE_EXECUTION = @import("./main.zig").DEBUG_TRACE_EXECUTION;
|
||||
|
||||
const Precedence = enum {
|
||||
None,
|
||||
Assignement,
|
||||
Or,
|
||||
And,
|
||||
Equality,
|
||||
Comparison,
|
||||
Term,
|
||||
Factor,
|
||||
Unary,
|
||||
Call,
|
||||
Primary,
|
||||
};
|
||||
|
||||
const ParserRule = struct {
|
||||
prefix: ?*const fn (*Parser) ParsingError!void,
|
||||
infix: ?*const fn (*Parser) ParsingError!void,
|
||||
precedence: Precedence,
|
||||
};
|
||||
|
||||
const Parser = struct {
|
||||
current: ?Token,
|
||||
previous: ?Token,
|
||||
scanner: *Scanner,
|
||||
had_error: bool,
|
||||
panic_mode: bool,
|
||||
chunk: *Chunk,
|
||||
|
||||
fn new(scanner: *Scanner, chunk: *Chunk) Parser {
|
||||
return Parser{
|
||||
.current = null,
|
||||
.previous = null,
|
||||
.scanner = scanner,
|
||||
.had_error = false,
|
||||
.panic_mode = false,
|
||||
.chunk = chunk,
|
||||
};
|
||||
}
|
||||
|
||||
fn advance(self: *Parser) void {
|
||||
self.previous = self.current;
|
||||
|
||||
while (true) {
|
||||
self.current = self.scanner.scan_token();
|
||||
if (self.current.?.token_type != TokenType.ERROR) {
|
||||
break;
|
||||
}
|
||||
|
||||
self.error_at_current(self.current.?.start);
|
||||
}
|
||||
}
|
||||
|
||||
fn expression(self: *Parser) ParsingError!void {
|
||||
try self.parse_precedence(Precedence.Assignement);
|
||||
}
|
||||
|
||||
fn consume(self: *Parser, token_type: TokenType, error_message: []const u8) void {
|
||||
if (self.current.?.token_type == token_type) {
|
||||
self.advance();
|
||||
return;
|
||||
}
|
||||
|
||||
self.error_at_current(error_message);
|
||||
}
|
||||
|
||||
fn error_at_current(self: *Parser, error_message: []const u8) void {
|
||||
self.error_at(self.current.?, error_message);
|
||||
}
|
||||
|
||||
fn error_at(self: *Parser, token: Token, error_message: []const u8) void {
|
||||
if (self.panic_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.panic_mode = true;
|
||||
|
||||
debug.print("[line {d}] Error", .{token.line});
|
||||
if (token.token_type == TokenType.EOF) {
|
||||
debug.print(" at end", .{});
|
||||
} else if (token.token_type == TokenType.ERROR) {
|
||||
// Nothing
|
||||
} else {
|
||||
const expr = std.mem.trimRight(u8, token.start[0..token.length], "\n");
|
||||
debug.print(" at '{s}'", .{expr});
|
||||
}
|
||||
debug.print(": {s}\n", .{error_message});
|
||||
self.had_error = true;
|
||||
}
|
||||
|
||||
fn error_msg(self: *Parser, error_message: []const u8) void {
|
||||
self.error_at(self.previous.?, error_message);
|
||||
}
|
||||
|
||||
fn emit_byte(self: *Parser, byte: u8) !void {
|
||||
try self.chunk.write(byte, self.previous.?.line);
|
||||
}
|
||||
|
||||
fn emit_bytes(self: *Parser, byte0: u8, byte1: u8) !void {
|
||||
try self.emit_byte(byte0);
|
||||
try self.emit_byte(byte1);
|
||||
}
|
||||
|
||||
fn emit_return(self: *Parser) !void {
|
||||
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
|
||||
}
|
||||
|
||||
fn end_parser(self: *Parser) !void {
|
||||
if (!self.had_error and DEBUG_TRACE_EXECUTION) {
|
||||
self.chunk.dissassemble("code");
|
||||
}
|
||||
try self.emit_return();
|
||||
}
|
||||
|
||||
fn number(self: *Parser) ParsingError!void {
|
||||
const value = std.fmt.parseFloat(f64, self.previous.?.start[0..self.previous.?.length]) catch {
|
||||
self.error_msg("Failed converting float.");
|
||||
return ParsingError.FloatConv;
|
||||
};
|
||||
self.emit_constant(value) catch {
|
||||
self.error_msg("Failed emiting constant.");
|
||||
return ParsingError.ChunkError;
|
||||
};
|
||||
}
|
||||
|
||||
fn emit_constant(self: *Parser, value: Value) !void {
|
||||
const constant = try self.make_constant(value);
|
||||
try self.emit_bytes(@intFromEnum(OpCode.OP_CONSTANT), constant);
|
||||
}
|
||||
|
||||
fn make_constant(self: *Parser, value: Value) !u8 {
|
||||
const constant = try self.chunk.add_constant(value);
|
||||
if (constant > 256) {
|
||||
self.error_msg("Too many constants in one chunk.");
|
||||
return 0;
|
||||
}
|
||||
return @intCast(constant);
|
||||
}
|
||||
|
||||
fn grouping(self: *Parser) ParsingError!void {
|
||||
try self.expression();
|
||||
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
|
||||
}
|
||||
|
||||
fn unary(self: *Parser) ParsingError!void {
|
||||
const operation_type = self.previous.?.token_type;
|
||||
|
||||
// Compile the operand
|
||||
try self.parse_precedence(Precedence.Unary);
|
||||
|
||||
// Emit the operator instruction
|
||||
switch (operation_type) {
|
||||
TokenType.MINUS => self.emit_byte(@intFromEnum(OpCode.OP_NEGATE)) catch {
|
||||
self.error_msg("Failed emiting NEGATE opcode.");
|
||||
return ParsingError.ChunkError;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn binary(self: *Parser) ParsingError!void {
|
||||
const operator_type = self.previous.?.token_type;
|
||||
const parser_rule = Parser.get_rule(operator_type);
|
||||
|
||||
try self.parse_precedence(@enumFromInt(1 + @intFromEnum(parser_rule.precedence)));
|
||||
|
||||
switch (operator_type) {
|
||||
TokenType.PLUS => self.emit_byte(@intFromEnum(OpCode.OP_ADD)) catch {},
|
||||
TokenType.MINUS => self.emit_byte(@intFromEnum(OpCode.OP_SUBSTRACT)) catch {},
|
||||
TokenType.STAR => self.emit_byte(@intFromEnum(OpCode.OP_MULTIPLY)) catch {},
|
||||
TokenType.SLASH => self.emit_byte(@intFromEnum(OpCode.OP_DIVIDE)) catch {},
|
||||
else => return,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rule(operator_type: TokenType) ParserRule {
|
||||
return switch (operator_type) {
|
||||
TokenType.LEFT_PAREN => ParserRule{ .prefix = grouping, .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.RIGHT_BRACE => 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.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term },
|
||||
TokenType.PLUS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Term },
|
||||
TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
|
||||
TokenType.STAR => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
|
||||
TokenType.BANG => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.BANG_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.EQUAL_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.GREATER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.GREATER_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.LESS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.LESS_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.IDENTIFIER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.STRING => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.NUMBER => ParserRule{ .prefix = number, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.AND => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.CLASS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.ELSE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.FALSE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.FOR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.FUN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.IF => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.NIL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.OR => 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.SUPER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.THIS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.TRUE => 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.ERROR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
TokenType.EOF => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_precedence(self: *Parser, precedence: Precedence) ParsingError!void {
|
||||
self.advance();
|
||||
|
||||
const prefix_rule = Parser.get_rule(self.previous.?.token_type).prefix;
|
||||
if (prefix_rule == null) {
|
||||
self.error_msg("Expect expression.");
|
||||
return;
|
||||
}
|
||||
|
||||
try prefix_rule.?(self);
|
||||
|
||||
while (@intFromEnum(precedence) <= @intFromEnum(Parser.get_rule(self.current.?.token_type).precedence)) {
|
||||
self.advance();
|
||||
const infix_rule = Parser.get_rule(self.previous.?.token_type).infix;
|
||||
try infix_rule.?(self);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn compile(allocator: Allocator, contents: []const u8, chunk: *Chunk) !bool {
|
||||
_ = allocator;
|
||||
|
||||
var scanner = Scanner.init(contents);
|
||||
var parser = Parser.new(&scanner, chunk);
|
||||
|
||||
while (true) {
|
||||
const token = scanner.scan_token();
|
||||
if (line == null or token.line != line.?) {
|
||||
debug.print("{d:4} ", .{token.line});
|
||||
line = token.line;
|
||||
} else {
|
||||
debug.print(" | ", .{});
|
||||
}
|
||||
debug.print("{s:12} len:{d:2} '{s}'\n", .{ token.token_type.string(), token.length, token.start[0..token.length] });
|
||||
parser.advance();
|
||||
try parser.expression();
|
||||
parser.consume(TokenType.EOF, "Expect end of expression.");
|
||||
try parser.end_parser();
|
||||
|
||||
if (token.token_type == TokenType.EOF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return !parser.had_error;
|
||||
}
|
||||
|
4
src/errors.zig
Normal file
4
src/errors.zig
Normal file
@ -0,0 +1,4 @@
|
||||
pub const ParsingError = error{
|
||||
FloatConv,
|
||||
ChunkError,
|
||||
};
|
54
src/main.zig
54
src/main.zig
@ -7,11 +7,9 @@ const OpCode = @import("./opcode.zig").OpCode;
|
||||
const VM = @import("./vm.zig").VM;
|
||||
const InterpretResult = @import("./vm.zig").InterpretResult;
|
||||
|
||||
const compile = @import("./compile.zig").compile;
|
||||
|
||||
pub const DEBUG_TRACE_EXECUTION = true;
|
||||
|
||||
pub fn repl(allocator: Allocator) !void {
|
||||
pub fn repl(allocator: Allocator, vm: *VM) !void {
|
||||
var line: [1024]u8 = undefined;
|
||||
|
||||
const stdin = std.io.getStdIn().reader();
|
||||
@ -29,18 +27,18 @@ pub fn repl(allocator: Allocator) !void {
|
||||
break;
|
||||
}
|
||||
|
||||
_ = try interpret(allocator, &line);
|
||||
_ = try vm.interpret(allocator, &line);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_file(allocator: Allocator, filepath: []const u8) !void {
|
||||
pub fn run_file(allocator: Allocator, vm: *VM, filepath: []const u8) !void {
|
||||
const file = try std.fs.cwd().openFile(filepath, .{});
|
||||
defer file.close();
|
||||
|
||||
const file_content = try file.readToEndAlloc(allocator, 1024 * 1024);
|
||||
defer allocator.free(file_content);
|
||||
|
||||
const result = try interpret(allocator, file_content);
|
||||
const result = try vm.interpret(allocator, file_content);
|
||||
|
||||
switch (result) {
|
||||
InterpretResult.COMPILE_ERROR => std.process.exit(65),
|
||||
@ -49,12 +47,6 @@ pub fn run_file(allocator: Allocator, filepath: []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interpret(allocator: Allocator, content: []const u8) !InterpretResult {
|
||||
// XXX catch and return InterpretResult.COMPILE_ERROR ?
|
||||
try compile(allocator, content);
|
||||
return InterpretResult.OK;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
|
||||
defer _ = debug.assert(gpa.deinit() == .ok);
|
||||
@ -63,44 +55,16 @@ pub fn main() !void {
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
|
||||
var vm = VM.new(allocator);
|
||||
defer vm.free();
|
||||
|
||||
if (args.len == 1) {
|
||||
try repl(allocator);
|
||||
try repl(allocator, &vm);
|
||||
} else if (args.len == 2) {
|
||||
try run_file(allocator, args[1]);
|
||||
try run_file(allocator, &vm, args[1]);
|
||||
} else {
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
try stdout.print("Usage: clox [path]\n", .{});
|
||||
std.process.exit(64);
|
||||
}
|
||||
|
||||
// var vm = VM.new(allocator);
|
||||
|
||||
// var chunk = Chunk.new();
|
||||
// try chunk.init(allocator);
|
||||
|
||||
// var constant = try chunk.add_constant(allocator, 1.2);
|
||||
// try chunk.write(allocator, @intFromEnum(OpCode.OP_CONSTANT), 123);
|
||||
// try chunk.write(allocator, @intCast(constant), 123);
|
||||
|
||||
// constant = try chunk.add_constant(allocator, 3.4);
|
||||
// try chunk.write(allocator, @intFromEnum(OpCode.OP_CONSTANT), 123);
|
||||
// try chunk.write(allocator, @intCast(constant), 123);
|
||||
|
||||
// try chunk.write(allocator, @intFromEnum(OpCode.OP_ADD), 123);
|
||||
|
||||
// constant = try chunk.add_constant(allocator, 5.6);
|
||||
// try chunk.write(allocator, @intFromEnum(OpCode.OP_CONSTANT), 123);
|
||||
// try chunk.write(allocator, @intCast(constant), 123);
|
||||
|
||||
// try chunk.write(allocator, @intFromEnum(OpCode.OP_DIVIDE), 123);
|
||||
|
||||
// try chunk.write(allocator, @intFromEnum(OpCode.OP_NEGATE), 123);
|
||||
// try chunk.write(allocator, @intFromEnum(OpCode.OP_RETURN), 123);
|
||||
|
||||
// chunk.dissassemble("test chunk");
|
||||
|
||||
// _ = try vm.interpret(&chunk);
|
||||
// vm.free();
|
||||
|
||||
// chunk.deinit(allocator);
|
||||
}
|
||||
|
@ -1 +1,9 @@
|
||||
pub const OpCode = enum(u8) { OP_CONSTANT, OP_ADD, OP_SUBSTRACT, OP_MULTIPLY, OP_DIVIDE, OP_NEGATE, OP_RETURN };
|
||||
pub const OpCode = enum(u8) {
|
||||
OP_CONSTANT,
|
||||
OP_ADD,
|
||||
OP_SUBSTRACT,
|
||||
OP_MULTIPLY,
|
||||
OP_DIVIDE,
|
||||
OP_NEGATE,
|
||||
OP_RETURN,
|
||||
};
|
||||
|
17
src/vm.zig
17
src/vm.zig
@ -6,6 +6,8 @@ const Chunk = @import("./chunk.zig").Chunk;
|
||||
const OpCode = @import("./opcode.zig").OpCode;
|
||||
const Value = @import("./values.zig").Value;
|
||||
|
||||
const compile = @import("./compile.zig").compile;
|
||||
|
||||
const DEBUG_TRACE_EXECUTION = @import("./main.zig").DEBUG_TRACE_EXECUTION;
|
||||
|
||||
const print_value = @import("./values.zig").print_value;
|
||||
@ -35,11 +37,19 @@ pub const VM = struct {
|
||||
self.stack.deinit();
|
||||
}
|
||||
|
||||
pub fn interpret(self: *VM, chunk: *Chunk) !InterpretResult {
|
||||
self.chunk = chunk;
|
||||
pub fn interpret(self: *VM, allocator: Allocator, content: []const u8) !InterpretResult {
|
||||
var chunk = Chunk.new(allocator);
|
||||
defer chunk.deinit();
|
||||
|
||||
const res = try compile(allocator, content, &chunk);
|
||||
if (!res) {
|
||||
return InterpretResult.COMPILE_ERROR;
|
||||
}
|
||||
|
||||
self.chunk = &chunk;
|
||||
self.ip = 0;
|
||||
|
||||
return self.run();
|
||||
return try self.run();
|
||||
}
|
||||
|
||||
pub fn run(self: *VM) !InterpretResult {
|
||||
@ -73,6 +83,7 @@ pub const VM = struct {
|
||||
},
|
||||
@intFromEnum(OpCode.OP_RETURN) => {
|
||||
print_value(self.pop());
|
||||
debug.print("\n", .{});
|
||||
return InterpretResult.OK;
|
||||
},
|
||||
else => {
|
||||
|
Loading…
Reference in New Issue
Block a user