Last active
December 10, 2023 16:59
-
-
Save marler8997/8c3f0e5a48159eb16a321323dbfc25e7 to your computer and use it in GitHub Desktop.
interp.zig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const Vm = @import("vm.zig").Vm; | |
const tokenizer = @import("tokenizer.zig"); | |
pub fn lex(src: [:0]const u8, off: usize) tokenizer.Token { | |
var t = @import("tokenizer.zig").Tokenizer{ .code = src, .idx = off }; | |
return t.next(); | |
} | |
// Expr | |
// <- PrimaryExpr (SuffixOp / FnCallArgs)* | |
// | |
pub fn Expr(src: [:0]const u8, start: usize, vm_opt: ?*Vm) error{Vm}!?usize { | |
const after_primary = try PrimaryExpr(src, start, vm_opt) orelse return null; | |
var off = after_primary; | |
while (true) { | |
const next_token = lex(src, off); | |
if (next_token.tag == .eof) return off; | |
if (true) @panic("todo"); | |
off = next_token.tag.end; | |
} | |
} | |
// PrimaryExpr | |
// <- STRING | |
// / NUMBER | |
// / '$' IDENTIFIER | |
// | |
fn PrimaryExpr(src: [:0]const u8, start: usize, vm_opt: ?*Vm) error{Vm}!?usize { | |
const first_token = lex(src, start); | |
switch (first_token.tag) { | |
.dollar => { | |
const id_token = lex(src, first_token.loc.end); | |
if (id_token.tag != .identifier) return null; | |
if (vm_opt) |vm| { | |
try vm.pushID(id_token.loc); | |
} | |
return id_token.loc.end; | |
}, | |
else => std.debug.panic("todo: handle token '{s}'", .{@tagName(first_token.tag)}), | |
} | |
} | |
// SuffixOp | |
// <- DOT IDENTIFIER | |
fn SuffixOp(src: [:0]const u8, start: usize, vm_opt: ?*Vm) error{Vm}!?usize { | |
_ = src; | |
_ = start; | |
_ = vm_opt; | |
@panic("todo"); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const Vm = @import("vm.zig").Vm; | |
const interp = @import("interp.zig"); | |
pub fn main() !void { | |
try testError("$badVariable", "undeclared identifier"); | |
try testExpr("$version", .{ .string = "v0" }); | |
} | |
fn testExpr(src: [:0]const u8, expected: Vm.ValueHack) !void { | |
var gpa = std.heap.GeneralPurposeAllocator(.{}){ }; | |
defer switch (gpa.deinit()) { .ok => {}, .leak => @panic("leak!") }; | |
var vm = Vm{ | |
.src = src, | |
.allocator = gpa.allocator() | |
}; | |
defer vm.deinit(); | |
vm.blockStart(); | |
defer vm.blockEnd(); | |
vm.pushString("v0"); | |
try vm.declareAndAssignStackTop2(.@"const", "version"); | |
if (interp.Expr(src, 0, &vm)) |end| { | |
if (end != src.len) { | |
std.log.err("src '{s}' is not an Expr (end={?})", .{src, end}); | |
} | |
try std.testing.expectEqual(@as(usize, 1), vm.scope_stack.items.len); | |
try std.testing.expectEqual(@as(usize, 1), vm.stack.items.len); | |
const value = vm.stack.items[0].resolve(vm); | |
switch (value) { | |
.bool => @panic("todo"), | |
.number => @panic("todo"), | |
.string => |actual_str| switch (expected) { | |
.bool => @panic("todo"), | |
.number => @panic("todo"), | |
.string => |expected_str| try std.testing.expectEqualSlices(u8, expected_str, actual_str), | |
.function => @panic("todo"), | |
}, | |
.function => @panic("todo"), | |
} | |
} else |vm_err| switch (vm_err) { | |
error.Vm => { | |
const err = vm.err orelse @panic("vm reported error but has none?"); | |
const error_msg = err.getTestMsg(); | |
std.log.err("src '{s}' had unexpected error: {s}", .{src, error_msg}); | |
return error.TestUnexpectedResult; | |
}, | |
} | |
} | |
fn testError(src: [:0]const u8, expected_error: []const u8) !void { | |
var gpa = std.heap.GeneralPurposeAllocator(.{}){ }; | |
defer switch (gpa.deinit()) { .ok => {}, .leak => @panic("leak!") }; | |
var vm = Vm{ | |
.src = src, | |
.allocator = gpa.allocator() | |
}; | |
defer vm.deinit(); | |
if (interp.Expr(src, 0, &vm)) |_| { | |
std.log.err("src '{s}' unexpectedly didn't have an error", .{src}); | |
return error.TestUnexpectedResult; | |
} else |vm_err| switch (vm_err) { | |
error.Vm => { | |
const err = vm.err orelse @panic("vm reported error but has none?"); | |
const actual_msg = err.getTestMsg(); | |
return std.testing.expectEqualSlices(u8, expected_error, actual_msg); | |
}, | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const builtin = @import("builtin"); | |
const std = @import("std"); | |
const interp = @import("interp.zig"); | |
const tokenizer = @import("tokenizer.zig"); | |
const Token = tokenizer.Token; | |
pub fn oom(e: error{OutOfMemory}) noreturn { | |
@panic(@errorName(e)); | |
} | |
const TokenError = enum { | |
undeclared_identifier, | |
redeclaration, | |
not_lvalue, | |
expected_eof, | |
not_implemented, | |
assert_failed, | |
unknown_builtin, | |
invalid_string_literal, | |
}; | |
const VmError = struct { | |
pos: usize, | |
kind: union(enum) { | |
token: TokenError, | |
general: []const u8, | |
}, | |
pub fn deinit(self: VmError, allocator: std.mem.Allocator) void { | |
switch (self.kind) { | |
.token => {}, | |
.general => |msg| allocator.free(msg), | |
} | |
} | |
pub fn getTestMsg(self: VmError) []const u8 { | |
switch (self.kind) { | |
.token => |token_error| switch (token_error) { | |
.undeclared_identifier => return "undeclared identifier", | |
.redeclaration => return "redeclaration", | |
.not_lvalue => return "invalid left-hand side to assignment", | |
.expected_eof => return "expected eof", | |
.not_implemented => return "not implemented", | |
.assert_failed => return "assert failed", | |
.unknown_builtin => return "unknown builtin", | |
.invalid_string_literal => return "invalid string literal", | |
}, | |
.general => |msg| return msg, | |
} | |
} | |
}; | |
pub const Mutability = enum { mutable, @"const" }; | |
const ScopeEntry = struct { | |
mutability: Mutability, | |
value: Value, | |
}; | |
const Scope = struct { | |
map: std.StringHashMapUnmanaged(ScopeEntry) = .{}, | |
pub fn deinit(self: *Scope, allocator: std.mem.Allocator) void { | |
var it = self.map.iterator(); | |
while (it.next()) |pair| { | |
pair.value_ptr.value.deinit(allocator); | |
} | |
self.map.deinit(allocator); | |
} | |
}; | |
pub const Vm = struct { | |
src: [:0]const u8, | |
allocator: std.mem.Allocator, | |
stack: std.ArrayListUnmanaged(IndirectValue) = .{}, | |
err: ?VmError = null, | |
current_function: ?Function = null, | |
scope_stack: std.ArrayListUnmanaged(Scope) = .{}, | |
// HACK | |
pub const ValueHack = Value; | |
const Function = struct { | |
id: ?Token.Loc, | |
params: std.ArrayListUnmanaged(Token.Loc) = .{}, | |
params_done: bool = false, | |
}; | |
pub fn deinit(self: *Vm) void { | |
if (self.err) |err| { | |
err.deinit(self.allocator); | |
} | |
for (self.stack.items) |item| { | |
item.deinit(self.allocator); | |
} | |
self.stack.deinit(self.allocator); | |
if (self.current_function) |*f| { | |
f.params.deinit(self.allocator); | |
} | |
for (self.scope_stack.items) |*item| { | |
item.deinit(self.allocator); | |
} | |
self.scope_stack.deinit(self.allocator); | |
} | |
pub fn tokenError(self: *Vm, token_pos: usize, token_error: TokenError) error{Vm} { | |
std.debug.assert(self.err == null); | |
self.err = .{ .pos = token_pos, .kind = .{ .token = token_error } }; | |
return error.Vm; | |
} | |
fn generalError(self: *Vm, pos: usize, comptime fmt: []const u8, args: anytype) error{Vm} { | |
std.debug.assert(self.err == null); | |
self.err = .{ | |
.pos = pos, | |
.kind = .{ | |
.general = std.fmt.allocPrint(self.allocator, fmt, args) catch |e| oom(e), | |
}, | |
}; | |
return error.Vm; | |
} | |
pub fn blockStart(self: *Vm) void { | |
self.scope_stack.append(self.allocator, .{}) catch |e| oom(e); | |
} | |
pub fn blockEnd(self: *Vm) void { | |
if (self.scope_stack.items.len == 0) @panic("codebug"); | |
var scope = self.scope_stack.pop(); | |
scope.deinit(self.allocator); | |
} | |
pub fn push_bool(self: *Vm, val: bool) void { | |
self.stack.append(self.allocator, .{ .direct = .{ .bool = val } }) catch |e| oom(e); | |
} | |
pub fn pushString(self: *Vm, str: []const u8) void { | |
const copy = self.allocator.dupe(u8, str) catch |e| oom(e); | |
errdefer self.allocator.free(copy); | |
self.stack.append(self.allocator, .{ .direct = .{ .string = copy } }) catch |e| oom(e); | |
} | |
pub fn push_number_literal(self: *Vm, loc: Token.Loc) error{Vm}!void { | |
var num = std.math.big.int.Managed.init(self.allocator) catch |e| oom(e); | |
errdefer num.deinit(); | |
const literal = self.src[loc.start..loc.end]; | |
const args: struct { base: u8, str: []const u8 } = blk: { | |
if (std.mem.startsWith(u8, literal, "0x")) | |
break :blk .{ .base = 16, .str = literal[2..] }; | |
if (std.mem.startsWith(u8, literal, "0o")) | |
break :blk .{ .base = 8, .str = literal[2..] }; | |
if (std.mem.startsWith(u8, literal, "0b")) | |
break :blk .{ .base = 2, .str = literal[2..] }; | |
break :blk .{ .base = 10, .str = literal }; | |
}; | |
num.setString(args.base, args.str) catch |err| switch (err) { | |
error.OutOfMemory => |e| oom(e), | |
error.InvalidBase, error.InvalidCharacter => |e| return self.generalError( | |
loc.start, "unable to convert integer literal '{s}' to bigint with {s}", .{literal, @errorName(e)} | |
), | |
}; | |
self.stack.append(self.allocator, .{ .direct = .{ | |
.number = num.toMutable(), | |
}}) catch |e| oom(e); | |
} | |
pub fn pushCharLiteral(self: *Vm, token: Token) error{Vm}!void { | |
return self.tokenError(token.loc.start, .not_implemented); | |
} | |
pub fn pushDotIdentifier(self: *Vm, token: Token) error{Vm}!void { | |
return self.tokenError(token.loc.start, .not_implemented); | |
} | |
const ScopeLookup = struct { | |
scope_index: usize, | |
entry: *const ScopeEntry, | |
}; | |
fn scopeLookup(self: *Vm, slice: []const u8) ?ScopeLookup { | |
var i: usize = self.scope_stack.items.len; | |
while (i > 0) { | |
i -= 1; | |
const scope = &self.scope_stack.items[i]; | |
if (scope.map.get(slice)) |*value_ptr| | |
return .{ .scope_index = i, .entry = value_ptr }; | |
} | |
return null; | |
} | |
pub fn getStackTopLValue(self: *Vm, token_pos: usize) error{Vm}!ScopeRef { | |
if (self.stack.items.len == 0) @panic("codebug"); | |
const top = self.stack.pop(); | |
return switch (top) { | |
.direct => self.tokenError(token_pos, .not_lvalue), | |
.scope_ref => |s| s, | |
}; | |
} | |
pub fn assignStackTop(self: *Vm, op_pos: usize, op: AssignOp, l_value: ScopeRef) error{Vm}!void { | |
const entry = l_value.getEntry(self.*); | |
var rhs_indirect = self.stack.popOrNull() orelse @panic("codebug"); | |
errdefer rhs_indirect.deinit(self.allocator); | |
switch (op) { | |
.normal => rhs_indirect.moveInto(self.allocator, &entry.value) catch return self.generalError( | |
op_pos, | |
"expected type '{s}', found '{s}'", | |
.{ entry.value.error_desc(), rhs_indirect.resolve(self.*).error_desc() }, | |
), | |
} | |
} | |
pub fn declareAndAssignStackTop(self: *Vm, mutability: Mutability, id_loc: Token.Loc) error{Vm}!void { | |
const slice = self.src[id_loc.start .. id_loc.end]; | |
if (self.scopeLookup(slice)) |_| return self.tokenError(id_loc.start, .redeclaration); | |
return self.declareAndAssignStackTop2(mutability, slice); | |
} | |
pub fn declareAndAssignStackTop2(self: *Vm, mutability: Mutability, slice: []const u8) error{Vm}!void { | |
if (self.scopeLookup(slice)) |_| @panic("todo"); | |
if (self.scope_stack.items.len == 0) @panic("todo: assign var in global scope"); | |
const scope = &self.scope_stack.items[self.scope_stack.items.len-1]; | |
const value = (self.stack.popOrNull() orelse @panic("codebug")).resolve(self.*); | |
scope.map.putNoClobber(self.allocator, slice, .{ | |
.mutability = mutability, | |
.value = value, | |
}) catch |e| oom(e); | |
} | |
pub fn pushID(self: *Vm, loc: Token.Loc) error{Vm}!void { | |
const slice = self.src[loc.start..loc.end]; | |
const lookup = self.scopeLookup(slice) orelse return self.tokenError(loc.start, .undeclared_identifier); | |
self.stack.append(self.allocator, .{ .scope_ref = .{ | |
.scope_index = lookup.scope_index, | |
.id = loc, | |
}}) catch |e| oom(e); | |
} | |
pub fn functionProtoStart(self: *Vm, id: ?Token.Loc) void { | |
if (self.current_function) |_| | |
@panic("function proto inside function proto not implemented"); | |
self.current_function = .{ .id = id }; | |
} | |
pub fn functionProtoEndParams(self: *Vm) void { | |
const f = &(self.current_function orelse @panic("codebug")); | |
if (f.params_done) @panic("codebug"); | |
f.params_done = true; | |
} | |
pub fn functionProtoParam(self: *Vm, id: Token.Loc) void { | |
const f = &(self.current_function orelse @panic("codebug")); | |
if (f.params_done) @panic("codebug"); | |
f.params.append(self.allocator, id) catch |e| oom(e); | |
} | |
pub fn functionProtoNoBody(self: *Vm) void { | |
const f = &(self.current_function orelse @panic("codebug")); | |
if (!f.params_done) @panic("codebug"); | |
self.stack.append(self.allocator, .{ .direct = .{ .function = .{ | |
.id = f.id, | |
.params = f.params.toOwnedSlice(self.allocator) catch |e| oom(e), | |
.body = false, | |
}}}) catch |e| oom(e); | |
self.current_function = null; | |
} | |
pub fn functionProtoBodyStart(self: *Vm) void { | |
const f = &(self.current_function orelse @panic("codebug")); | |
if (!f.params_done) @panic("codebug"); | |
self.stack.append(self.allocator, .{ .direct = .{ .function = .{ | |
.id = f.id, | |
.params = f.params.toOwnedSlice(self.allocator) catch |e| oom(e), | |
.body = true, | |
}}}) catch |e| oom(e); | |
self.current_function = null; | |
} | |
pub fn functionProtoBodyFailedParse(self: *Vm) void { | |
if (self.stack.items.len == 0) @panic("codebug"); | |
const f = self.stack.pop().resolve(self.*); | |
std.debug.assert(f == .function); | |
} | |
pub fn functionProtoBodyEnd(self: *Vm) void { | |
// TODO: set the last block parsed to the function | |
_ = self; | |
} | |
fn enforceArgCount(self: *Vm, loc: usize, count: usize) error{Vm}!void { | |
if (self.stack.items.len != count) return self.generalError( | |
loc, | |
"expected {} argument(s), found {}", | |
.{count, self.stack.items.len}, | |
); | |
} | |
fn enforceType(self: *Vm, comptime expected_type: ValueType, loc: usize, value: Value) error{Vm}!expected_type.T() { | |
if (value != expected_type) return self.generalError( | |
loc, | |
"expected type '{s}', found '{s}'", | |
.{ expected_type.error_desc(), value.error_desc() }, | |
); | |
return switch (expected_type) { | |
.bool => value.bool, | |
.number => value.number, | |
.string => value.string, | |
.function => value.function, | |
}; | |
} | |
pub fn callBuiltin(self: *Vm, loc: Token.Loc) error{Vm}!void { | |
const name = self.src[loc.start..loc.end]; | |
if (std.mem.eql(u8, name, "@out")) { | |
try self.enforceArgCount(loc.start, 1); | |
const msg_indirect = self.stack.pop(); | |
defer msg_indirect.deinit(self.allocator); | |
const msg = try self.enforceType(.string, loc.start, msg_indirect.resolve(self.*)); | |
std.io.getStdOut().writer().writeAll(msg) | |
catch |err| std.debug.panic("@out failed with {s}", .{@errorName(err)}); | |
} else if (std.mem.eql(u8, name, "@assert")) { | |
try self.enforceArgCount(loc.start, 1); | |
const val_indirect = self.stack.pop(); | |
defer val_indirect.deinit(self.allocator); | |
const val = try self.enforceType(.bool, loc.start, val_indirect.resolve(self.*)); | |
if (!val) | |
return self.tokenError(loc.start, .assert_failed); | |
} else { | |
return self.tokenError(loc.start, .unknown_builtin); | |
} | |
} | |
const BinaryBoolOp = enum { | |
@"or", | |
@"and", | |
}; | |
pub fn binaryBoolOp(self: *Vm, op: BinaryBoolOp, op_loc: usize) error{Vm}!void { | |
std.debug.assert(self.stack.items.len >= 2); // should be guaranteed | |
const first_indirect = self.stack.pop(); | |
defer first_indirect.deinit(self.allocator); | |
const second_indirect = self.stack.pop(); | |
defer second_indirect.deinit(self.allocator); | |
const first = try self.enforceType(.bool, op_loc, first_indirect.resolve(self.*)); | |
const second = try self.enforceType(.bool, op_loc, second_indirect.resolve(self.*)); | |
// we know we have capacity because we just popped off the old values | |
self.stack.appendAssumeCapacity(.{ .direct = .{ | |
.bool = switch (op) { | |
.@"or" => first or second, | |
.@"and" => first and second, | |
}, | |
}}); | |
} | |
pub fn binaryCompareOp(self: *Vm, op: std.math.CompareOperator, op_loc: usize) error{Vm}!void { | |
std.debug.assert(self.stack.items.len >= 2); // should be guaranteed | |
const rhs_indirect = self.stack.pop(); | |
defer rhs_indirect.deinit(self.allocator); | |
const lhs_indirect = self.stack.pop(); | |
defer lhs_indirect.deinit(self.allocator); | |
const rhs = rhs_indirect.resolve(self.*); | |
const lhs = lhs_indirect.resolve(self.*); | |
if (lhs == .string or rhs == .string) | |
return self.generalError(op_loc, "cannot compare strings with {s}", .{compareOpStr(op)}); | |
// we know we have capacity because we just popped off the old values | |
self.stack.appendAssumeCapacity(.{ .direct = .{ | |
.bool = switch (lhs) { | |
.bool => |v| try self.compareBool(op, op_loc, v, rhs), | |
.number => |v| try self.compareNumber(op, op_loc, v, rhs), | |
.string => unreachable, | |
.function => @panic("todo"), | |
} | |
}}); | |
} | |
pub fn compareBool(self: *Vm, op: std.math.CompareOperator, op_loc: usize, lhs: bool, rhs_val: Value) error{Vm}!bool { | |
const rhs = switch (rhs_val) { | |
.bool => |rhs| rhs, | |
.string => unreachable, | |
else => |rhs_type| return self.generalError( | |
op_loc, "incompatible types: 'bool' and '{s}'", .{rhs_type.error_desc()}, | |
), | |
}; | |
return switch (op) { | |
.eq => lhs == rhs, | |
.neq => lhs != rhs, | |
else => self.generalError( | |
op_loc, "operator {s} not allowed for type 'bool'", .{compareOpStr(op)}, | |
), | |
}; | |
} | |
pub fn compareNumber(self: *Vm, op: std.math.CompareOperator, op_loc: usize, lhs: std.math.big.int.Mutable, rhs_val: Value) error{Vm}!bool { | |
const rhs = switch (rhs_val) { | |
.number => |rhs| rhs, | |
.string => unreachable, | |
else => |rhs_type| return self.generalError( | |
op_loc, "incompatible types: 'number' and '{s}'", .{rhs_type.error_desc()}, | |
), | |
}; | |
// workaround bug in std.math.big.int treating positive/negative 0 as different | |
switch (op) { | |
.lt, .gt, .neq => {}, | |
.lte, .gte, .eq => { | |
if (lhs.eqlZero() and rhs.eqlZero()) return true; | |
}, | |
} | |
return lhs.toConst().order(rhs.toConst()).compare(op); | |
} | |
pub fn additionOp(self: *Vm, op: AdditionOp, op_loc: usize) error{Vm}!void { | |
std.debug.assert(self.stack.items.len >= 2); // should be guaranteed | |
const rhs_indirect = self.stack.pop(); | |
defer rhs_indirect.deinit(self.allocator); | |
const lhs_indirect = self.stack.pop(); | |
defer lhs_indirect.deinit(self.allocator); | |
const rhs = rhs_indirect.resolve(self.*); | |
const lhs = lhs_indirect.resolve(self.*); | |
if (op == .concat) { | |
if (lhs != .string) | |
return self.generalError(op_loc, "expected indexable; found '{s}'", .{lhs.error_desc()}); | |
if (rhs != .string) | |
return self.generalError(op_loc, "expected indexable; found '{s}'", .{rhs.error_desc()}); | |
self.stack.appendAssumeCapacity(.{ .direct = .{ | |
.string = std.mem.concat(self.allocator, u8, &.{ lhs.string, rhs.string }) catch |e| oom(e), | |
}}); | |
return; | |
} | |
switch (lhs) { | |
.bool => {}, | |
.number => |lhs_num| switch (rhs) { | |
.bool => {}, | |
.number => |rhs_num| { | |
var result = std.math.big.int.Managed.init(self.allocator) catch |e| oom(e); | |
errdefer result.deinit(); | |
switch (op) { | |
.concat => unreachable, | |
.add => { | |
result.ensureAddCapacity(lhs_num.toConst(), rhs_num.toConst()) catch |e| oom(e); | |
var m = result.toMutable(); | |
m.add(lhs_num.toConst(), rhs_num.toConst()); | |
result.setMetadata(m.positive, m.len); | |
}, | |
.sub => { | |
result.ensureCapacity(@max(lhs_num.len, rhs_num.len) + 1) catch |e| oom(e); | |
var m = result.toMutable(); | |
m.sub(lhs_num.toConst(), rhs_num.toConst()); | |
result.setMetadata(m.positive, m.len); | |
}, | |
.add_wrap, .sub_wrap, | |
.add_sat, .sub_sat, | |
=> return self.generalError( | |
op_loc, | |
"zigscript doesn't support the {s} operator", | |
.{ op.str() }, | |
), | |
} | |
self.stack.appendAssumeCapacity(.{ .direct = .{ .number = result.toMutable() } }); | |
return; | |
}, | |
.string => {}, | |
.function => {}, | |
}, | |
.string => {}, | |
.function => {}, | |
} | |
return self.generalError( | |
op_loc, | |
"invalid operands to binary expression '{s}' and '{s}'", | |
.{ lhs.error_desc(), rhs.error_desc() } | |
); | |
} | |
pub fn multiplyOp(self: *Vm, op: MultiplyOp, op_loc: usize) error{Vm}!void { | |
std.debug.assert(self.stack.items.len >= 2); // should be guaranteed | |
const rhs_indirect = self.stack.pop(); | |
defer rhs_indirect.deinit(self.allocator); | |
const lhs_indirect = self.stack.pop(); | |
defer lhs_indirect.deinit(self.allocator); | |
const rhs = rhs_indirect.resolve(self.*); | |
const lhs = lhs_indirect.resolve(self.*); | |
switch (op) { | |
.double_pipe => return self.tokenError(op_loc, .not_implemented), | |
.mul => { | |
if (lhs != .number or rhs != .number) return self.generalError( | |
op_loc, | |
"incompatible types: '{s}' and '{s}'", | |
.{lhs.error_desc(), rhs.error_desc()}, | |
); | |
var result = std.math.big.int.Managed.init(self.allocator) catch |e| oom(e); | |
errdefer result.deinit(); | |
result.ensureMulCapacity(lhs.number.toConst(), rhs.number.toConst()) catch |e| oom(e); | |
var m = result.toMutable(); | |
m.mulNoAlias(lhs.number.toConst(), rhs.number.toConst(), self.allocator); | |
result.setMetadata(m.positive, m.len); | |
self.stack.appendAssumeCapacity(.{ .direct = .{ .number = result.toMutable() } }); | |
}, | |
} | |
} | |
pub fn applyPrefixOp(self: *Vm, op: PrefixOp, op_loc: usize) error{Vm}!void { | |
std.debug.assert(self.stack.items.len >= 1); // should be guaranteed | |
var value_indirect = self.stack.pop(); | |
var deinit_value = true; | |
defer if (deinit_value) value_indirect.deinit(self.allocator); | |
var value = value_indirect.resolve(self.*); | |
switch (op) { | |
.not => { | |
if (value != .bool) return self.generalError( | |
op_loc, "expected type 'bool' found '{s}'", .{@tagName(value)} | |
); | |
self.stack.appendAssumeCapacity(.{ .direct = .{ .bool = !value.bool } }); | |
}, | |
.negate => { | |
if (value != .number) return self.generalError( | |
op_loc, "negation of type '{s}'", .{@tagName(value)} | |
); | |
deinit_value = false; | |
value.number.negate(); | |
self.stack.appendAssumeCapacity(.{ .direct = .{ .number = value.number } }); | |
}, | |
} | |
} | |
pub fn applyOptional(self: *Vm, op_loc: usize) error{Vm}!void { | |
return self.tokenError(op_loc, .not_implemented); | |
} | |
pub fn applyPtrType(self: *Vm, op_loc: usize) error{Vm}!void { | |
_ = self; | |
_ = op_loc; | |
@panic("todo"); | |
} | |
pub fn applyErrorUnion(self: *Vm, op_loc: usize) error{Vm}!void { | |
return self.tokenError(op_loc, .not_implemented); | |
} | |
}; | |
pub const ScopeRef = struct { | |
scope_index: usize, | |
id: Token.Loc, | |
pub fn deinit(self: ScopeRef, allocator: std.mem.Allocator) void { | |
_ = self; | |
_ = allocator; | |
} | |
pub fn getEntry(self: ScopeRef, vm: Vm) *ScopeEntry { | |
std.debug.assert(self.scope_index < vm.scope_stack.items.len); | |
const slice = vm.src[self.id.start .. self.id.end]; | |
const scope = &vm.scope_stack.items[self.scope_index]; | |
return scope.map.getPtr(slice) orelse @panic("codebug"); | |
} | |
}; | |
const IndirectValue = union(enum) { | |
direct: Value, | |
scope_ref: ScopeRef, | |
pub fn deinit(self: IndirectValue, allocator: std.mem.Allocator) void { | |
switch (self) { | |
.direct => |d| d.deinit(allocator), | |
.scope_ref => |s| s.deinit(allocator), | |
} | |
} | |
pub fn resolve(self: IndirectValue, vm: Vm) Value { | |
return switch (self) { | |
.direct => |d| d, | |
.scope_ref => |s| s.getEntry(vm).value, | |
}; | |
} | |
pub fn moveInto(self: *IndirectValue, allocator: std.mem.Allocator, dst: *Value) error{TypeMismatch}!void { | |
switch (self.*) { | |
.direct => |*d| try dst.move(allocator, d), | |
.scope_ref => { | |
@panic("todo"); | |
}, | |
} | |
} | |
}; | |
const ValueType = enum { | |
bool, | |
number, | |
string, | |
function, | |
pub fn T(comptime self: ValueType) type { | |
return switch (self) { | |
.bool => bool, | |
.number => std.math.big.int.Mutable, | |
.string => []const u8, | |
.function => Value.Function, | |
}; | |
} | |
pub fn error_desc(self: ValueType) []const u8 { | |
return switch (self) { | |
.bool => "bool", | |
.number => "number", | |
.string => "string", | |
.function => "function", | |
}; | |
} | |
}; | |
const Value = union(ValueType) { | |
bool: bool, | |
number: std.math.big.int.Mutable, | |
string: []const u8, | |
function: Function, | |
pub fn deinitAndInvalidate(self: *Value) void { | |
self.deinit(); | |
self.* = undefined; | |
} | |
pub fn deinit(self: Value, allocator: std.mem.Allocator) void { | |
switch (self) { | |
.bool => {}, | |
.number => |n| allocator.free(n.limbs), | |
.string => |s| allocator.free(s), | |
.function => |f| f.deinit(allocator), | |
} | |
} | |
pub fn error_desc(self: Value) []const u8 { | |
return @as(ValueType, self).error_desc(); | |
} | |
pub fn move(self: *Value, allocator: std.mem.Allocator, from: *Value) error{TypeMismatch}!void { | |
switch (self.*) { | |
.bool => { | |
const from_bool = switch (from.*) { | |
.bool => |b2| b2, | |
else => return error.TypeMismatch, | |
}; | |
self.* = .{ .bool = from_bool }; | |
}, | |
.number => |n| { | |
const from_num = switch (from.*) { | |
.number => |n2| n2, | |
else => return error.TypeMismatch, | |
}; | |
allocator.free(n.limbs); | |
self.* = .{ .number = from_num }; | |
}, | |
.string => |s| { | |
const from_str = switch (from.*) { | |
.string => |s2| s2, | |
else => return error.TypeMismatch, | |
}; | |
allocator.free(s); | |
self.* = .{ .string = from_str }; | |
}, | |
.function => @panic("todo"), | |
} | |
from.* = undefined; | |
} | |
const Function = struct { | |
id: ?Token.Loc, | |
params: []Token.Loc, | |
body: bool, | |
pub fn deinit(self: Function, allocator: std.mem.Allocator) void { | |
allocator.free(self.params); | |
} | |
}; | |
}; | |
fn ErrorOr(comptime T: type) type { | |
return union(enum) { | |
ok: T, | |
err: []u8, | |
}; | |
} | |
pub const AssignOp = enum { | |
normal, | |
}; | |
fn compareOpStr(op: std.math.CompareOperator) []const u8 { | |
return switch (op) { | |
.lt => "<", | |
.lte => "<=", | |
.eq => "==", | |
.gte => ">=", | |
.gt => ">", | |
.neq => "!=", | |
}; | |
} | |
pub const AdditionOp = enum { | |
concat, | |
add, | |
sub, | |
// TODO: start out not supporting these operations in zigscript | |
add_wrap, | |
sub_wrap, | |
add_sat, | |
sub_sat, | |
pub fn str(self: AdditionOp) []const u8 { | |
return switch (self) { | |
.concat => "++", | |
.add => "+", | |
.sub => "-", | |
.add_wrap => "+%", | |
.sub_wrap => "-%", | |
.add_sat => "+|", | |
.sub_sat => "-|", | |
}; | |
} | |
}; | |
pub const MultiplyOp = enum { | |
/// merges error sets or performs boolean OR | |
double_pipe, | |
mul, | |
pub fn str(self: MultiplyOp) []const u8 { | |
return switch (self) { | |
}; | |
} | |
}; | |
pub const PrefixOp = enum { | |
not, | |
negate, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment