commit 74b66998cf648e3c5b9edc44d8b22a4699ddee91
parent 50d0fdf0163453fc3bdbc44236bf7cdf7be97f99
Author: Ashymad <szymon.mikulicz@posteo.net>
Date: Sat, 16 Mar 2024 10:47:43 +0100
Hello strings
Diffstat:
7 files changed, 209 insertions(+), 53 deletions(-)
diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig
@@ -20,10 +20,10 @@ pub const OP = enum(u8) {
NOT,
};
-pub const ChunkError = error{OutOfMemory};
-
pub const Chunk = struct {
- pub fn init(allocator: std.mem.Allocator) ChunkError!@This() {
+ pub const Error = error{OutOfMemory};
+
+ pub fn init(allocator: std.mem.Allocator) Error!@This() {
return @This(){
.code = try array.Array(u8, usize, 8).init(allocator),
.constants = try ValueArray.init(allocator),
@@ -31,16 +31,16 @@ pub const Chunk = struct {
};
}
- pub fn write(self: *@This(), byte: u8, line: i32) ChunkError!void {
+ pub fn write(self: *@This(), byte: u8, line: i32) Error!void {
try self.code.add(byte);
try self.lines.add(line);
}
- pub fn writeOP(self: *@This(), op: OP, line: i32) ChunkError!void {
+ pub fn writeOP(self: *@This(), op: OP, line: i32) Error!void {
try self.write(@intFromEnum(op), line);
}
- pub fn addConstant(self: *@This(), val: value.Value) ChunkError!u8 {
+ pub fn addConstant(self: *@This(), val: value.Value) Error!u8 {
try self.constants.add(val);
return self.constants.len - 1;
}
diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig
@@ -2,9 +2,10 @@ const std = @import("std");
const scanner = @import("scanner.zig");
const chunk = @import("chunk.zig");
const Value = @import("value.zig").Value;
+const Obj = @import("obj.zig").Obj;
const debug = @import("debug.zig");
-pub const CompilerError = scanner.ScannerError || chunk.ChunkError || Value.ParseNumberError || error{ UnexpectedToken, NotAnExpression };
+pub const CompilerError = scanner.ScannerError || chunk.Chunk.Error || Value.ParseNumberError || error{ UnexpectedToken, NotAnExpression };
const Precedence = enum {
NONE,
@@ -37,6 +38,7 @@ pub const Compiler = struct {
hadError: bool,
panicMode: bool,
compilingChunk: *chunk.Chunk,
+ allocator: std.mem.Allocator,
const ParseFn = *const fn (*@This()) void;
@@ -81,7 +83,7 @@ pub const Compiler = struct {
T.LESS => R(null, S.binary, P.COMPARISON ),
T.LESS_EQUAL => R(null, S.binary, P.COMPARISON ),
T.IDENTIFIER => R(null, null, P.NONE ),
- T.STRING => R(null, null, P.NONE ),
+ T.STRING => R(S.string, null, P.NONE ),
T.NUMBER => R(S.number, null, P.NONE ),
T.AND => R(null, null, P.NONE ),
T.CLASS => R(null, null, P.NONE ),
@@ -234,6 +236,15 @@ pub const Compiler = struct {
});
}
+ fn string(self: *@This()) void {
+ const ret = Obj.String.init(self.previous.lexeme[1 .. self.previous.lexeme.len - 1], self.allocator) catch |err| {
+ self.lastError = err;
+ self.errorAtPrevious("Out of memory");
+ return;
+ };
+ self.emitConstant(Value{ .obj = ret.cast() });
+ }
+
fn emitConstant(self: *@This(), val: Value) void {
self.emit(chunk.OP.CONSTANT, self.makeConstant(val));
}
@@ -304,8 +315,8 @@ pub const Compiler = struct {
// emit bytecode
}
- pub fn compile(source: []const u8, ch: *chunk.Chunk) CompilerError!void {
- var self = @This(){ .scanner = try scanner.Scanner.init(source), .current = scanner.Token.Empty, .previous = scanner.Token.Empty, .panicMode = false, .hadError = false, .lastError = scanner.ScannerError.EmptyToken, .compilingChunk = ch };
+ pub fn compile(source: []const u8, ch: *chunk.Chunk, allocator: std.mem.Allocator) CompilerError!void {
+ var self = @This(){ .scanner = try scanner.Scanner.init(source), .current = scanner.Token.Empty, .previous = scanner.Token.Empty, .panicMode = false, .hadError = false, .lastError = scanner.ScannerError.EmptyToken, .compilingChunk = ch, .allocator = allocator };
self.advance();
self.expression();
self.consume(scanner.TokenType.EOF, "Expected end of expression.");
diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig
@@ -0,0 +1,79 @@
+const std = @import("std");
+
+pub const Obj = packed struct {
+ const Self = @This();
+
+ pub const String = struct {
+ obj: Self,
+ len: usize,
+ fn data(self: *@This()) [*]u8 {
+ const ptr: [*]u8 = @ptrCast(self);
+ return ptr + @sizeOf(@This());
+ }
+ fn cdata(self: *const @This()) [*]const u8 {
+ const ptr: [*]const u8 = @ptrCast(self);
+ return ptr + @sizeOf(@This());
+ }
+ fn new(len: usize, allocator: std.mem.Allocator) !*@This() {
+ const ret: *@This() = @ptrCast(try allocator.alignedAlloc(u8, @alignOf(@This()), @sizeOf(@This()) + len));
+ ret.* = @This(){ .obj = Self{
+ .type = Self.Type.String,
+ }, .len = len };
+ return ret;
+ }
+ pub fn slice(self: *const @This()) []const u8 {
+ return self.cdata()[0..self.len];
+ }
+ pub fn init(string: []const u8, allocator: std.mem.Allocator) !*@This() {
+ const ret = try new(string.len, allocator);
+ @memcpy(ret.data(), string);
+ return ret;
+ }
+ pub fn cat(self: *const @This(), other: *const @This(), allocator: std.mem.Allocator) !*@This() {
+ const ret = try new(self.len + other.len, allocator);
+ @memcpy(ret.data(), self.slice());
+ @memcpy(ret.data() + self.len, other.slice());
+ return ret;
+ }
+ pub fn cast(self: *@This()) *Self {
+ return @ptrCast(self);
+ }
+ };
+
+ pub const Type = enum {
+ String,
+
+ pub fn get(comptime self: @This()) type {
+ return @field(Obj, @tagName(self));
+ }
+ };
+
+ const Error = error{IllegalCastError};
+ type: Type,
+
+ pub fn is(self: *const Self, tp: Type) bool {
+ return self.type == tp;
+ }
+
+ pub fn print(self: *const Self) void {
+ switch (self.type) {
+ .String => std.debug.print("\"{s}\"", .{self._cast(.String).slice()}),
+ }
+ }
+
+ pub fn equal(self: *const Self, other: *const Self) bool {
+ if (!self.is(other.type)) return false;
+ return switch (self.type) {
+ .String => std.mem.eql(u8, self._cast(.String).slice(), other._cast(.String).slice()),
+ };
+ }
+
+ fn _cast(self: *const Self, comptime tp: Type) *const tp.get() {
+ return @ptrCast(@alignCast(self));
+ }
+
+ pub fn cast(self: *const Self, comptime tp: Type) Error!*const tp.get() {
+ if (!self.is(tp)) return Error.IllegalCastError;
+ return self._cast(tp);
+ }
+};
diff --git a/zlox/src/value.zig b/zlox/src/value.zig
@@ -1,10 +1,12 @@
const std = @import("std");
const array = @import("array.zig");
+const Obj = @import("obj.zig").Obj;
pub const Value = union(enum) {
number: f64,
bool: bool,
nil: void,
+ obj: *Obj,
pub const Tag = std.meta.Tag(@This());
@@ -13,30 +15,39 @@ pub const Value = union(enum) {
.number => |val| std.debug.print("{d}", .{val}),
.bool => |val| std.debug.print("{s}", .{if (val) "true" else "false"}),
.nil => std.debug.print("nil", .{}),
+ .obj => |o| o.print(),
}
}
- pub fn is(self: @This(), comptime tag: Tag) bool {
+ fn toTag(comptime from: anytype) Tag {
+ if (@TypeOf(from) == Obj.Type) {
+ return .obj;
+ } else {
+ return from;
+ }
+ }
+
+ pub fn is(self: @This(), comptime tag: anytype) bool {
return switch (self) {
- tag => true,
+ toTag(tag) => @TypeOf(tag) == Tag or self.obj.is(tag),
else => false,
};
}
- pub fn new(comptime tag: Tag, value: tagType(tag)) @This() {
- return @unionInit(@This(), @tagName(tag), value);
+ pub fn new(comptime tag: anytype, value: tagType(tag)) @This() {
+ return @unionInit(@This(), @tagName(toTag(tag)), value);
}
- pub fn get(self: @This(), comptime tag: Tag) tagType(tag) {
- return @field(self, @tagName(tag));
+ pub fn get(self: @This(), comptime tag: anytype) tagType(tag) {
+ return @field(self, @tagName(toTag(tag)));
}
- pub fn set(self: *@This(), comptime tag: Tag, value: tagType(tag)) void {
- @field(self, @tagName(tag)) = value;
+ pub fn set(self: *@This(), comptime tag: anytype, value: tagType(tag)) void {
+ @field(self, @tagName(toTag(tag))) = value;
}
- pub fn tagType(comptime tag: Tag) type {
- return @TypeOf(@field(@unionInit(@This(), @tagName(tag), undefined), @tagName(tag)));
+ pub fn tagType(comptime tag: anytype) type {
+ return @TypeOf(@field(@unionInit(@This(), @tagName(toTag(tag)), undefined), @tagName(toTag(tag))));
}
pub const ParseNumberError = std.fmt.ParseFloatError;
@@ -59,6 +70,7 @@ pub const Value = union(enum) {
.number => |x| x == other.number,
.bool => |x| x == other.bool,
.nil => true,
+ .obj => |x| x.equal(other.obj),
};
}
};
diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig
@@ -3,10 +3,11 @@ const OP = @import("chunk.zig").OP;
const Value = @import("value.zig").Value;
const std = @import("std");
const debug = @import("debug.zig");
-const wrp = @import("wrap.zig");
const compiler = @import("compiler.zig");
+const Obj = @import("obj.zig").Obj;
+const Callback = @import("vm_callbacks.zig");
-pub const InterpreterError = compiler.CompilerError || error{ OutOfMemory, CompileError, RuntimeError, IndexOutOfBounds, Overflow, DivisionByZero };
+pub const InterpreterError = compiler.CompilerError || Callback.Error || Chunk.Error || error{ CompileError, RuntimeError, IndexOutOfBounds, Overflow, DivisionByZero };
pub const VM = struct {
ip: [*]const u8,
@@ -28,20 +29,20 @@ pub const VM = struct {
return ret;
}
- pub fn interpretChunk(self: *@This(), chunk: *const Chunk) InterpreterError!void {
+ pub fn interpretChunk(self: *@This(), chunk: *const Chunk, allocator: std.mem.Allocator) InterpreterError!void {
self.resetStack();
self.chunk = chunk;
self.ip = chunk.code.data.ptr;
- try self.run(true);
+ try self.run(true, allocator);
}
pub fn interpret(self: *@This(), source: []const u8, allocator: std.mem.Allocator) InterpreterError!void {
var chunk = try Chunk.init(allocator);
defer chunk.deinit();
- try compiler.Compiler.compile(source, &chunk);
+ try compiler.Compiler.compile(source, &chunk, allocator);
- try self.interpretChunk(&chunk);
+ try self.interpretChunk(&chunk, allocator);
self.resetStack();
}
@@ -73,11 +74,11 @@ pub const VM = struct {
return (self.stackTop - (1 + distance))[0];
}
- fn binary_op(self: *@This(), comptime in_tag: Value.Tag, comptime out_tag: Value.Tag, op: fn (type, Value.tagType(in_tag), Value.tagType(in_tag)) Value.tagType(out_tag)) !void {
+ fn binary_op(self: *@This(), comptime in_tag: anytype, comptime out_tag: anytype, op: Callback.Type(in_tag, out_tag)) InterpreterError!void {
const b = self.pop();
const a = self.pop();
if (a.is(in_tag) and b.is(in_tag)) {
- self.push(Value.new(out_tag, op(Value.tagType(in_tag), a.get(in_tag), b.get(in_tag))));
+ self.push(Value.new(out_tag, try op.call(a.get(in_tag), b.get(in_tag))));
} else {
self.runtimeError("Operands have invalid types, expected: {s}", .{@tagName(in_tag)});
return InterpreterError.RuntimeError;
@@ -88,7 +89,7 @@ pub const VM = struct {
return @intFromPtr(self.ip) - @intFromPtr(self.chunk.code.data.ptr);
}
- fn run(self: *@This(), comptime dbg: bool) !void {
+ fn run(self: *@This(), comptime dbg: bool, allocator: std.mem.Allocator) !void {
while (true) {
if (dbg) {
std.debug.print(" ", .{});
@@ -119,15 +120,21 @@ pub const VM = struct {
}
self.push(Value{ .number = -self.pop().number });
},
- @intFromEnum(OP.ADD) => try self.binary_op(Value.number, Value.number, wrp.add),
- @intFromEnum(OP.SUBTRACT) => try self.binary_op(Value.number, Value.number, wrp.sub),
- @intFromEnum(OP.MULTIPLY) => try self.binary_op(Value.number, Value.number, wrp.mul),
- @intFromEnum(OP.DIVIDE) => try self.binary_op(Value.number, Value.number, wrp.div),
+ @intFromEnum(OP.ADD) => {
+ if (self.peek(0).is(Obj.Type.String)) {
+ try self.binary_op(Obj.Type.String, Obj.Type.String, Callback.concatenate(allocator));
+ } else {
+ try self.binary_op(Value.number, Value.number, Callback.add);
+ }
+ },
+ @intFromEnum(OP.SUBTRACT) => try self.binary_op(Value.number, Value.number, Callback.sub),
+ @intFromEnum(OP.MULTIPLY) => try self.binary_op(Value.number, Value.number, Callback.mul),
+ @intFromEnum(OP.DIVIDE) => try self.binary_op(Value.number, Value.number, Callback.div),
@intFromEnum(OP.TRUE) => self.push(Value{ .bool = true }),
@intFromEnum(OP.FALSE) => self.push(Value{ .bool = false }),
@intFromEnum(OP.EQUAL) => self.push(Value{ .bool = self.pop().equal(self.pop()) }),
- @intFromEnum(OP.LESS) => try self.binary_op(Value.number, Value.bool, wrp.less),
- @intFromEnum(OP.GREATER) => try self.binary_op(Value.number, Value.bool, wrp.more),
+ @intFromEnum(OP.LESS) => try self.binary_op(Value.number, Value.bool, Callback.less),
+ @intFromEnum(OP.GREATER) => try self.binary_op(Value.number, Value.bool, Callback.more),
@intFromEnum(OP.NIL) => self.push(Value{ .nil = undefined }),
@intFromEnum(OP.NOT) => self.push(Value{ .bool = !self.pop().isTruthy() }),
else => return InterpreterError.CompileError,
diff --git a/zlox/src/vm_callbacks.zig b/zlox/src/vm_callbacks.zig
@@ -0,0 +1,65 @@
+const Value = @import("value.zig").Value;
+const std = @import("std");
+const Obj = @import("obj.zig").Obj;
+
+const Number = Value.tagType(.number);
+const Bool = Value.tagType(.bool);
+
+pub const Error = error{OutOfMemory};
+
+pub fn Type(comptime in_tag: anytype, comptime out_tag: anytype) type {
+ if (@TypeOf(in_tag) == Obj.Type) {
+ return struct {
+ allocator: std.mem.Allocator,
+ _call: *const fn (self: *const @This(), Value.tagType(in_tag), Value.tagType(in_tag)) Error!Value.tagType(out_tag),
+ pub fn call(self: *const @This(), a: Value.tagType(in_tag), b: Value.tagType(in_tag)) Error!Value.tagType(out_tag) {
+ return self._call(self, a, b);
+ }
+ };
+ } else {
+ return struct {
+ call: fn (Value.tagType(in_tag), Value.tagType(in_tag)) callconv(.Inline) Error!Value.tagType(out_tag),
+ };
+ }
+}
+
+pub fn concatenate(allocator: std.mem.Allocator) Type(Obj.Type.String, Obj.Type.String) {
+ const Ret = Type(Obj.Type.String, Obj.Type.String);
+ const ret = Ret{ .allocator = allocator, ._call = struct {
+ pub fn concatenate(self: *const Ret, lhs: *Obj, rhs: *Obj) Error!*Obj {
+ return (try (lhs.cast(.String) catch unreachable).cat(rhs.cast(.String) catch unreachable, self.allocator)).cast();
+ }
+ }.concatenate };
+ return ret;
+}
+
+pub const add = Type(Value.number, Value.number){ .call = struct {
+ pub inline fn add(a: Number, b: Number) Error!Number {
+ return a + b;
+ }
+}.add };
+pub const mul = Type(Value.number, Value.number){ .call = struct {
+ pub inline fn mul(a: Number, b: Number) Error!Number {
+ return a * b;
+ }
+}.mul };
+pub const sub = Type(Value.number, Value.number){ .call = struct {
+ pub inline fn sub(a: Number, b: Number) Error!Number {
+ return a - b;
+ }
+}.sub };
+pub const div = Type(Value.number, Value.number){ .call = struct {
+ pub inline fn div(a: Number, b: Number) Error!Number {
+ return a / b;
+ }
+}.div };
+pub const less = Type(Value.number, Value.bool){ .call = struct {
+ pub inline fn less(a: Number, b: Number) Error!Bool {
+ return a < b;
+ }
+}.less };
+pub const more = Type(Value.number, Value.bool){ .call = struct {
+ pub inline fn more(a: Number, b: Number) Error!Bool {
+ return a > b;
+ }
+}.more };
diff --git a/zlox/src/wrap.zig b/zlox/src/wrap.zig
@@ -1,18 +0,0 @@
-pub fn add(comptime T: type, a: T, b: T) T {
- return a + b;
-}
-pub fn mul(comptime T: type, a: T, b: T) T {
- return a * b;
-}
-pub fn sub(comptime T: type, a: T, b: T) T {
- return a - b;
-}
-pub fn div(comptime T: type, a: T, b: T) T {
- return a / b;
-}
-pub fn less(comptime T: type, a: T, b: T) bool {
- return a < b;
-}
-pub fn more(comptime T: type, a: T, b: T) bool {
- return a > b;
-}