DzLox

:)
git clone https://git.sr.ht/~ashymad/DzLox
Log | Files | Refs | Submodules | LICENSE

commit 74b66998cf648e3c5b9edc44d8b22a4699ddee91
parent 50d0fdf0163453fc3bdbc44236bf7cdf7be97f99
Author: Ashymad <szymon.mikulicz@posteo.net>
Date:   Sat, 16 Mar 2024 10:47:43 +0100

Hello strings

Diffstat:
Mzlox/src/chunk.zig | 12++++++------
Mzlox/src/compiler.zig | 19+++++++++++++++----
Azlox/src/obj.zig | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mzlox/src/value.zig | 32++++++++++++++++++++++----------
Mzlox/src/vm.zig | 37++++++++++++++++++++++---------------
Azlox/src/vm_callbacks.zig | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dzlox/src/wrap.zig | 18------------------
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; -}