feat: cheaper runtime value type, float and int arithmetic rules
This commit is contained in:
parent
9b5cd4038f
commit
92e8bcd866
13 changed files with 630 additions and 448 deletions
|
|
@ -303,12 +303,18 @@ const GenIr = struct {
|
|||
return new_index;
|
||||
}
|
||||
|
||||
fn addInt(gi: *GenIr, value: u64) !Ir.Inst.Ref {
|
||||
fn addInt(gi: *GenIr, value: i64) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = .int, .data = .{
|
||||
.int = value,
|
||||
} });
|
||||
}
|
||||
|
||||
fn addFloat(gi: *GenIr, value: f64) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = .float, .data = .{
|
||||
.float = value,
|
||||
} });
|
||||
}
|
||||
|
||||
fn addUnaryNode(gi: *GenIr, tag: Ir.Inst.Tag, arg: Ir.Inst.Ref) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = tag, .data = .{
|
||||
.un = .{ .lhs = arg },
|
||||
|
|
@ -531,10 +537,7 @@ fn setDeclVarPayload(
|
|||
astgen.appendBlockBody(body);
|
||||
}
|
||||
|
||||
fn setDeclStitchPayload(
|
||||
decl_index: Ir.Inst.Index,
|
||||
body_block: *GenIr,
|
||||
) !void {
|
||||
fn setDeclStitchPayload(decl_index: Ir.Inst.Index, body_block: *GenIr) !void {
|
||||
defer body_block.unstack();
|
||||
|
||||
const astgen = body_block.astgen;
|
||||
|
|
@ -659,10 +662,51 @@ fn logicalOp(
|
|||
gen.setLabel(else_label);
|
||||
}
|
||||
|
||||
fn parseNumberLiteral(bytes: []const u8) union(enum) {
|
||||
int: i64,
|
||||
float: f64,
|
||||
failure: union(enum) {
|
||||
duplicate_period: usize,
|
||||
invalid_character: usize,
|
||||
},
|
||||
} {
|
||||
var is_float = false;
|
||||
var period = false;
|
||||
|
||||
for (bytes, 0..) |c, i| {
|
||||
switch (c) {
|
||||
'.' => {
|
||||
if (period) return .{ .failure = .{ .duplicate_period = i } };
|
||||
period = true;
|
||||
is_float = true;
|
||||
},
|
||||
'0'...'9' => {},
|
||||
else => return .{ .failure = .{ .invalid_character = i } },
|
||||
}
|
||||
}
|
||||
if (is_float) {
|
||||
const value = std.fmt.parseFloat(f64, bytes) catch |err| switch (err) {
|
||||
error.InvalidCharacter => unreachable,
|
||||
};
|
||||
return .{ .float = value };
|
||||
} else {
|
||||
const value = std.fmt.parseInt(i64, bytes, 10) catch |err| switch (err) {
|
||||
error.InvalidCharacter => unreachable,
|
||||
error.Overflow => unreachable,
|
||||
};
|
||||
return .{ .int = value };
|
||||
}
|
||||
}
|
||||
|
||||
fn numberLiteral(block: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const lexeme = block.astgen.tree.nodeSlice(node);
|
||||
const int_value = try std.fmt.parseUnsigned(u64, lexeme, 10);
|
||||
return block.addInt(int_value);
|
||||
const astgen = block.astgen;
|
||||
const lexeme = astgen.tree.nodeSlice(node);
|
||||
switch (parseNumberLiteral(lexeme)) {
|
||||
.int => |int| return block.addInt(int),
|
||||
.float => |float| return block.addFloat(float),
|
||||
// TODO: exact offset reporting
|
||||
.failure => return fail(block.astgen, node, "invalid number literal", .{}),
|
||||
}
|
||||
}
|
||||
|
||||
fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
|
|
|
|||
|
|
@ -178,6 +178,8 @@ pub const Inst = struct {
|
|||
cmp_lte,
|
||||
/// Uses the `int` union field.
|
||||
int,
|
||||
/// Uses the `float` union field.
|
||||
float,
|
||||
/// Uses the `str` union field.
|
||||
str,
|
||||
block,
|
||||
|
|
@ -209,7 +211,8 @@ pub const Inst = struct {
|
|||
lhs: Ref,
|
||||
rhs: Ref,
|
||||
},
|
||||
int: u64,
|
||||
int: i64,
|
||||
float: f64,
|
||||
str: struct {
|
||||
/// Offset into `string_bytes`.
|
||||
start: NullTerminatedString,
|
||||
|
|
|
|||
121
src/Sema.zig
121
src/Sema.zig
|
|
@ -20,7 +20,7 @@ const InnerError = error{
|
|||
AnalysisFail,
|
||||
TooManyConstants,
|
||||
InvalidJump,
|
||||
};
|
||||
} || anyerror;
|
||||
|
||||
pub const ValueInfo = union(enum) {
|
||||
none,
|
||||
|
|
@ -34,9 +34,24 @@ pub const ValueInfo = union(enum) {
|
|||
pub const Value = struct {
|
||||
ip_index: InternPool.Index,
|
||||
|
||||
pub fn unwrap(value: Value, ip: *InternPool) u64 {
|
||||
const t = ip.values.items[@intFromEnum(value.toInterned())];
|
||||
return t.int;
|
||||
pub const Unwrapped = union(enum) {
|
||||
int: i64,
|
||||
float: f64,
|
||||
|
||||
fn toFloat(v: Value.Unwrapped) f64 {
|
||||
return switch (v) {
|
||||
.int => |i| @floatFromInt(i),
|
||||
.float => |f| f,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn unwrap(value: Value, ip: *InternPool) Unwrapped {
|
||||
switch (ip.values.items[@intFromEnum(value.toInterned())]) {
|
||||
.int => |int| return .{ .int = int },
|
||||
.float => |float| return .{ .float = @bitCast(float) },
|
||||
.str => @panic("String unwrapping not implemented!"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fromInterned(index: InternPool.Index) Value {
|
||||
|
|
@ -264,12 +279,59 @@ pub const Builder = struct {
|
|||
}
|
||||
};
|
||||
|
||||
fn foldConstant(
|
||||
lhs: Value.Unwrapped,
|
||||
rhs: Value.Unwrapped,
|
||||
op: Story.Opcode,
|
||||
) !Value.Unwrapped {
|
||||
if (lhs == .int and rhs == .int) {
|
||||
switch (op) {
|
||||
.add => return .{ .int = lhs.int +% rhs.int },
|
||||
.sub => return .{ .int = lhs.int -% rhs.int },
|
||||
.mul => return .{ .int = lhs.int *% rhs.int },
|
||||
.div => {
|
||||
if (rhs.int == 0)
|
||||
return error.DivisionByZero;
|
||||
return .{ .int = @divTrunc(lhs.int, rhs.int) };
|
||||
},
|
||||
.mod => if (rhs.int == 0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .int = @mod(lhs.int, rhs.int) },
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
const l = lhs.toFloat();
|
||||
const r = rhs.toFloat();
|
||||
switch (op) {
|
||||
.add => return .{ .float = l + r },
|
||||
.sub => return .{ .float = l - r },
|
||||
.mul => return .{ .float = l * r },
|
||||
.div => if (r == 0.0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .float = l / r },
|
||||
.mod => if (r == 0.0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .float = @mod(l, r) },
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn irInt(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
||||
const value = sema.ir.instructions[@intFromEnum(inst)].data.int;
|
||||
const ip_index = try sema.module.intern_pool.getOrPutInt(sema.gpa, value);
|
||||
return .{ .value = ip_index };
|
||||
}
|
||||
|
||||
fn irFloat(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
||||
const value = sema.ir.instructions[@intFromEnum(inst)].data.float;
|
||||
const ip_index = try sema.module.intern_pool.getOrPutFloat(sema.gpa, value);
|
||||
return .{ .value = ip_index };
|
||||
}
|
||||
|
||||
fn irStr(sema: *Sema, inst: Ir.Inst.Index) InnerError!ValueInfo {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.str;
|
||||
const ip_index = try sema.module.intern_pool.getOrPutStr(sema.gpa, data.start);
|
||||
|
|
@ -282,6 +344,7 @@ fn irUnaryOp(
|
|||
inst: Ir.Inst.Index,
|
||||
op: Story.Opcode,
|
||||
) InnerError!ValueInfo {
|
||||
const gpa = sema.gpa;
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
||||
const ip = &sema.module.intern_pool;
|
||||
const lhs = sema.resolveInst(data.lhs);
|
||||
|
|
@ -290,15 +353,24 @@ fn irUnaryOp(
|
|||
|
||||
if (sema.resolveValue(lhs)) |lhs_value| {
|
||||
const lhs_unwrapped = lhs_value.unwrap(ip);
|
||||
_ = lhs_unwrapped;
|
||||
const new_value = switch (op) {
|
||||
//.not => !lhs_unwrapped,
|
||||
//.neg => -lhs_unwrapped,
|
||||
else => unreachable,
|
||||
};
|
||||
return .{
|
||||
.value = try ip.getOrPutInt(sema.gpa, new_value),
|
||||
};
|
||||
switch (lhs_unwrapped) {
|
||||
.int => |int| {
|
||||
const new_value = switch (op) {
|
||||
.not => return error.TypeError,
|
||||
.neg => -int,
|
||||
else => unreachable,
|
||||
};
|
||||
return .{ .value = try ip.getOrPutInt(gpa, new_value) };
|
||||
},
|
||||
.float => |float| {
|
||||
const new_value = switch (op) {
|
||||
.not => return error.TypeError,
|
||||
.neg => -float,
|
||||
else => unreachable,
|
||||
};
|
||||
return .{ .value = try ip.getOrPutFloat(gpa, new_value) };
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
try builder.ensureLoad(lhs);
|
||||
|
|
@ -323,18 +395,16 @@ fn irBinaryOp(
|
|||
|
||||
if (sema.resolveValue(lhs)) |lhs_value| {
|
||||
if (sema.resolveValue(rhs)) |rhs_value| {
|
||||
const lhs_unwrapped = lhs_value.unwrap(ip);
|
||||
const rhs_unwrapped = rhs_value.unwrap(ip);
|
||||
const new_value = switch (op) {
|
||||
.add => lhs_unwrapped + rhs_unwrapped,
|
||||
.sub => lhs_unwrapped - rhs_unwrapped,
|
||||
.mul => lhs_unwrapped * rhs_unwrapped,
|
||||
.div => lhs_unwrapped / rhs_unwrapped,
|
||||
else => unreachable,
|
||||
};
|
||||
return .{
|
||||
.value = try ip.getOrPutInt(sema.gpa, new_value),
|
||||
};
|
||||
const lhs_unwrap = lhs_value.unwrap(ip);
|
||||
const rhs_unwrap = rhs_value.unwrap(ip);
|
||||
switch (try foldConstant(lhs_unwrap, rhs_unwrap, op)) {
|
||||
.int => |int| return .{
|
||||
.value = try ip.getOrPutInt(sema.gpa, int),
|
||||
},
|
||||
.float => |float| return .{
|
||||
.value = try ip.getOrPutFloat(sema.gpa, float),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -703,6 +773,7 @@ fn analyzeBodyInner(
|
|||
},
|
||||
.load => try irLoad(sema, builder, inst),
|
||||
.int => try irInt(sema, inst),
|
||||
.float => try irFloat(sema, inst),
|
||||
.str => try irStr(sema, inst),
|
||||
.add => try irBinaryOp(sema, builder, inst, .add),
|
||||
.sub => try irBinaryOp(sema, builder, inst, .sub),
|
||||
|
|
|
|||
378
src/Story.zig
378
src/Story.zig
|
|
@ -15,10 +15,10 @@ is_exited: bool = false,
|
|||
can_advance: bool = false,
|
||||
choice_index: usize = 0,
|
||||
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
||||
code_chunks: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
constants_pool: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
globals: std.StringHashMapUnmanaged(?*Object) = .empty,
|
||||
stack: std.ArrayListUnmanaged(?*Object) = .empty,
|
||||
code_chunks: std.ArrayListUnmanaged(*Object.Code) = .empty,
|
||||
constants_pool: std.ArrayListUnmanaged(Value) = .empty,
|
||||
globals: std.StringHashMapUnmanaged(?Value) = .empty,
|
||||
stack: std.ArrayListUnmanaged(?Value) = .empty,
|
||||
call_stack: std.ArrayListUnmanaged(CallFrame) = .empty,
|
||||
stack_max: usize = 128,
|
||||
gc_objects: std.SinglyLinkedList = .{},
|
||||
|
|
@ -27,6 +27,158 @@ string_bytes: []const u8 = &.{},
|
|||
|
||||
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
||||
|
||||
pub const Value = union(enum) {
|
||||
bool: bool,
|
||||
int: i64,
|
||||
float: f64,
|
||||
object: *Object,
|
||||
|
||||
pub fn tagBytes(v: Value) []const u8 {
|
||||
return switch (v) {
|
||||
.bool => "Bool",
|
||||
.int => "Int",
|
||||
.float => "Float",
|
||||
.object => "Object",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isNumeric(v: Value) bool {
|
||||
return v == .int or v == .float;
|
||||
}
|
||||
|
||||
pub fn isTruthy(v: Value) bool {
|
||||
return switch (v) {
|
||||
//.nil => false,
|
||||
.bool => |b| b,
|
||||
.int => |i| i != 0,
|
||||
.float => |f| f != 0.0,
|
||||
.object => true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toFloat(v: Value) f64 {
|
||||
return switch (v) {
|
||||
.int => |i| @floatFromInt(i),
|
||||
.float => |f| f,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn add(lhs: Value, rhs: Value, story: *Story) !Value {
|
||||
return arith(lhs, rhs, .add, story);
|
||||
}
|
||||
|
||||
pub fn arith(lhs: Value, rhs: Value, op: Opcode, story: *Story) !Value {
|
||||
if (lhs.isNumeric() and rhs.isNumeric())
|
||||
return numericArith(lhs, rhs, op);
|
||||
if (op == .add) {
|
||||
if (lhs == .object and lhs.object.tag == .string)
|
||||
return concat(lhs, rhs, story);
|
||||
if (rhs == .object and rhs.object.tag == .string)
|
||||
return concat(lhs, rhs, story);
|
||||
}
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
pub fn numericArith(lhs: Value, rhs: Value, op: Story.Opcode) !Value {
|
||||
if (!lhs.isNumeric() or !rhs.isNumeric()) return error.TypeError;
|
||||
|
||||
if (lhs == .int and rhs == .int) {
|
||||
switch (op) {
|
||||
.add => return .{ .int = lhs.int +% rhs.int },
|
||||
.sub => return .{ .int = lhs.int -% rhs.int },
|
||||
.mul => return .{ .int = lhs.int *% rhs.int },
|
||||
.div => {
|
||||
if (rhs.int == 0)
|
||||
return error.DivisionByZero;
|
||||
return .{ .int = @divTrunc(lhs.int, rhs.int) };
|
||||
},
|
||||
.mod => if (rhs.int == 0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .int = @mod(lhs.int, rhs.int) },
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
const l = lhs.toFloat();
|
||||
const r = rhs.toFloat();
|
||||
switch (op) {
|
||||
.add => return .{ .float = l + r },
|
||||
.sub => return .{ .float = l - r },
|
||||
.mul => return .{ .float = l * r },
|
||||
.div => if (r == 0.0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .float = l / r },
|
||||
.mod => if (r == 0.0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .float = @mod(l, r) },
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eql(lhs: Value, rhs: Value) bool {
|
||||
return switch (lhs) {
|
||||
.bool => |l| rhs == .bool and l == rhs.bool,
|
||||
.int => |l| switch (rhs) {
|
||||
.int => |r| l == r,
|
||||
.float => |r| @as(f64, @floatFromInt(l)) == r,
|
||||
else => false,
|
||||
},
|
||||
.float => |l| switch (rhs) {
|
||||
.int => |r| l == @as(f64, @floatFromInt(r)),
|
||||
.float => |r| l == r,
|
||||
else => false,
|
||||
},
|
||||
.object => |l| rhs == .object and l == rhs.object,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn compare(lhs: Value, rhs: Value, op: Story.Opcode) !Value {
|
||||
return switch (op) {
|
||||
.cmp_eq => .{ .bool = lhs.eql(rhs) },
|
||||
.cmp_neq => .{ .bool = !lhs.eql(rhs) },
|
||||
.cmp_lt, .cmp_gt, .cmp_lte, .cmp_gte => blk: {
|
||||
if (!lhs.isNumeric() or !rhs.isNumeric()) return error.TypeError;
|
||||
const l = lhs.toFloat();
|
||||
const r = rhs.toFloat();
|
||||
break :blk .{
|
||||
.bool = switch (op) {
|
||||
.cmp_lt => l < r,
|
||||
.cmp_gt => l > r,
|
||||
.cmp_lte => l <= r,
|
||||
.cmp_gte => l >= r,
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn logicalNot(lhs: Value) Value {
|
||||
return .{ .bool = !lhs.isTruthy() };
|
||||
}
|
||||
|
||||
pub fn concat(lhs: Value, rhs: Value, story: *Story) !Value {
|
||||
const lhs_object = try Object.String.fromValue(story, lhs);
|
||||
const rhs_object = try Object.String.fromValue(story, rhs);
|
||||
const str_object = try Object.String.concat(story, lhs_object, rhs_object);
|
||||
return .{ .object = &str_object.base };
|
||||
}
|
||||
|
||||
pub fn negate(lhs: Value) !Value {
|
||||
switch (lhs) {
|
||||
.bool => return error.TypeError,
|
||||
.int => |int| return .{ .int = -int },
|
||||
.float => |float| return .{ .float = -float },
|
||||
.object => return error.TypeError,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const CallFrame = struct {
|
||||
ip: usize,
|
||||
sp: usize,
|
||||
|
|
@ -64,6 +216,7 @@ pub const Opcode = enum(u8) {
|
|||
neg,
|
||||
not,
|
||||
cmp_eq,
|
||||
cmp_neq,
|
||||
cmp_lt,
|
||||
cmp_gt,
|
||||
cmp_lte,
|
||||
|
|
@ -115,72 +268,6 @@ pub fn deinit(story: *Story) void {
|
|||
gpa.free(story.string_bytes);
|
||||
}
|
||||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
var story_dumper: Dumper = .{ .story = story };
|
||||
|
||||
try writer.writeAll("=== Constants ===\n");
|
||||
for (story.constants_pool.items) |global_constant| {
|
||||
try story_dumper.dumpObject(writer, global_constant);
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Globals ===\n");
|
||||
|
||||
var global_iter = story.globals.iterator();
|
||||
while (global_iter.next()) |entry| {
|
||||
try writer.print("{s} => ...\n", .{entry.key_ptr.*});
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Knots ===\n");
|
||||
try writer.flush();
|
||||
|
||||
var knots_iter = story.globals.iterator();
|
||||
while (knots_iter.next()) |entry| {
|
||||
if (entry.value_ptr.*) |global| {
|
||||
switch (global.tag) {
|
||||
.knot => {
|
||||
try writer.writeAll("*");
|
||||
story_dumper.indent_level += 2;
|
||||
try story_dumper.dumpKnot(writer, @ptrCast(global));
|
||||
story_dumper.indent_level -= 2;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void {
|
||||
try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp});
|
||||
var story_dumper: Dumper = .{ .story = story };
|
||||
const stack = &story.stack;
|
||||
const stack_top = story.stack.items.len;
|
||||
if (stack_top > 0) {
|
||||
// FIXME: There has to be a better way to do this.
|
||||
if (stack_top > 1) {
|
||||
for (stack.items[frame.sp .. stack.items.len - 1]) |slot| {
|
||||
if (slot) |object| {
|
||||
try story_dumper.dumpObject(writer, object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
}
|
||||
if (stack.items[stack.items.len - 1]) |object| {
|
||||
try story_dumper.dumpObject(writer, object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll("]\n");
|
||||
_ = try story_dumper.dumpInst(writer, frame.callee, frame.ip, true);
|
||||
return writer.flush();
|
||||
}
|
||||
|
||||
fn isCallStackEmpty(vm: *const Story) bool {
|
||||
return vm.call_stack.items.len == 0;
|
||||
}
|
||||
|
|
@ -189,34 +276,34 @@ fn currentFrame(vm: *Story) *CallFrame {
|
|||
return &vm.call_stack.items[vm.call_stack.items.len - 1];
|
||||
}
|
||||
|
||||
fn peekStack(vm: *Story, offset: usize) ?*Object {
|
||||
fn peekStack(vm: *Story, offset: usize) ?Value {
|
||||
const stack_top = vm.stack.items.len;
|
||||
if (stack_top <= offset) return null;
|
||||
|
||||
return vm.stack.items[stack_top - offset - 1];
|
||||
}
|
||||
|
||||
fn pushStack(vm: *Story, object: *Object) !void {
|
||||
fn pushStack(vm: *Story, value: Value) !void {
|
||||
const gpa = vm.allocator;
|
||||
const stack_top = vm.stack.items.len;
|
||||
const max_stack_top = vm.stack_max;
|
||||
if (stack_top >= max_stack_top) return error.StackOverflow;
|
||||
|
||||
return vm.stack.append(gpa, object);
|
||||
return vm.stack.append(gpa, value);
|
||||
}
|
||||
|
||||
fn popStack(vm: *Story) ?*Object {
|
||||
fn popStack(vm: *Story) ?Value {
|
||||
return vm.stack.pop() orelse unreachable;
|
||||
}
|
||||
|
||||
fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !*Object {
|
||||
fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !Value {
|
||||
if (offset >= frame.callee.code.constants.len) return error.InvalidArgument;
|
||||
|
||||
const constant_index = frame.callee.code.constants[offset];
|
||||
return story.constants_pool.items[constant_index];
|
||||
}
|
||||
|
||||
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
||||
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?Value {
|
||||
const stack_top = vm.stack.items.len;
|
||||
const stack_offset = frame.sp + offset;
|
||||
assert(stack_top > stack_offset);
|
||||
|
|
@ -224,7 +311,7 @@ fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
|||
return vm.stack.items[stack_offset];
|
||||
}
|
||||
|
||||
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: *Object) void {
|
||||
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: Value) void {
|
||||
const stack_top = vm.stack.items.len;
|
||||
const stack_offset = frame.sp + offset;
|
||||
assert(stack_top > stack_offset);
|
||||
|
|
@ -233,16 +320,35 @@ fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: *Object) void {
|
|||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn getGlobal(vm: *Story, key: *const Object.String) !*Object {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
const val = vm.globals.get(key_bytes) orelse return error.InvalidVariable;
|
||||
return val orelse unreachable;
|
||||
fn getGlobal(vm: *Story, key: Value) !Value {
|
||||
switch (key) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.string => {
|
||||
const str_object: *Object.String = @ptrCast(object);
|
||||
if (vm.globals.get(str_object.toSlice())) |value| {
|
||||
// FIXME: Support for nil
|
||||
return value.?;
|
||||
}
|
||||
return error.InvalidVariable;
|
||||
},
|
||||
else => return error.TypeError,
|
||||
},
|
||||
else => return error.TypeError,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn setGlobal(vm: *Story, key: *const Object.String, value: *Object) !void {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
return vm.globals.putAssumeCapacity(key_bytes, value);
|
||||
fn setGlobal(vm: *Story, key: Value, value: Value) !void {
|
||||
switch (key) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.string => {
|
||||
const str_object: *Object.String = @ptrCast(object);
|
||||
return vm.globals.putAssumeCapacity(str_object.toSlice(), value);
|
||||
},
|
||||
else => return error.TypeError,
|
||||
},
|
||||
else => return error.TypeError,
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||
|
|
@ -259,7 +365,7 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
const frame = vm.currentFrame();
|
||||
const code = std.mem.bytesAsSlice(Opcode, frame.callee.code.bytecode);
|
||||
if (vm.dump_writer) |w| {
|
||||
vm.trace(w, frame) catch {};
|
||||
Dumper.trace(vm, w, frame) catch {};
|
||||
}
|
||||
switch (code[frame.ip]) {
|
||||
.exit => {
|
||||
|
|
@ -268,31 +374,23 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
return .empty;
|
||||
},
|
||||
.true => {
|
||||
// TODO: Intern this value.
|
||||
const true_object = try Object.Number.create(vm, .{
|
||||
.boolean = true,
|
||||
});
|
||||
try vm.pushStack(@ptrCast(true_object));
|
||||
const value: Value = .{ .bool = true };
|
||||
try vm.pushStack(value);
|
||||
frame.ip += 1;
|
||||
},
|
||||
.false => {
|
||||
// TODO: Intern this value.
|
||||
const false_object = try Object.Number.create(vm, .{
|
||||
.boolean = false,
|
||||
});
|
||||
try vm.pushStack(@ptrCast(false_object));
|
||||
const value: Value = .{ .bool = false };
|
||||
try vm.pushStack(value);
|
||||
frame.ip += 1;
|
||||
},
|
||||
.pop => {
|
||||
if (vm.popStack()) |_| {} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
if (vm.popStack()) |_| {} else return error.InvalidArgument;
|
||||
frame.ip += 1;
|
||||
},
|
||||
.add => {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.add(vm, lhs, rhs);
|
||||
const value = try Value.add(lhs, rhs, vm);
|
||||
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
|
|
@ -302,29 +400,26 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
.sub, .mul, .div, .mod => |op| {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.Number.performArithmetic(vm, op, @ptrCast(lhs), @ptrCast(rhs));
|
||||
const value = try Value.arith(lhs, rhs, op, vm);
|
||||
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
try vm.pushStack(value);
|
||||
frame.ip += 1;
|
||||
},
|
||||
.neg => {
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
_ = Object.Number.negate(@ptrCast(arg));
|
||||
if (vm.popStack()) |arg| {
|
||||
const value = try Value.negate(arg);
|
||||
try vm.pushStack(value);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
frame.ip += 1;
|
||||
},
|
||||
.not => {
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
const value = try Object.Number.create(vm, .{
|
||||
.boolean = arg.isFalsey(),
|
||||
});
|
||||
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
if (vm.popStack()) |arg| {
|
||||
const value = Value.logicalNot(arg);
|
||||
try vm.pushStack(value);
|
||||
} else {
|
||||
return error.StackOverflow;
|
||||
}
|
||||
|
|
@ -333,20 +428,20 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
.cmp_eq => {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.cmpEql(vm, @ptrCast(lhs), @ptrCast(rhs));
|
||||
const value = try Value.compare(lhs, rhs, .cmp_eq);
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
try vm.pushStack(value);
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
.cmp_lt, .cmp_gt, .cmp_lte, .cmp_gte => |op| {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.Number.performLogic(vm, op, @ptrCast(lhs), @ptrCast(rhs));
|
||||
const value = try Value.compare(lhs, rhs, op);
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
try vm.pushStack(value);
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
|
|
@ -359,7 +454,7 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
frame.ip += 3;
|
||||
|
||||
if (vm.peekStack(0)) |condition| {
|
||||
if (!condition.isFalsey()) {
|
||||
if (condition.isTruthy()) {
|
||||
frame.ip += arg_offset;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -371,7 +466,7 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
frame.ip += 3;
|
||||
|
||||
if (vm.peekStack(0)) |condition| {
|
||||
if (condition.isFalsey()) {
|
||||
if (!condition.isTruthy()) {
|
||||
frame.ip += arg_offset;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -402,19 +497,16 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
.load_global => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const global_name = try vm.getConstant(frame, arg_offset);
|
||||
assert(global_name.tag == .string);
|
||||
|
||||
const global_value = try vm.getGlobal(@ptrCast(global_name));
|
||||
const global_value = try vm.getGlobal(global_name);
|
||||
try vm.pushStack(global_value);
|
||||
frame.ip += 2;
|
||||
},
|
||||
.store_global => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const global_name = try vm.getConstant(frame, arg_offset);
|
||||
assert(global_name.tag == .string);
|
||||
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
try vm.setGlobal(@ptrCast(global_name), arg);
|
||||
try vm.setGlobal(global_name, arg);
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(arg);
|
||||
} else {
|
||||
|
|
@ -426,10 +518,9 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
// FIXME: This should be more strict.
|
||||
// Its not because theres a bug in when these instructions are
|
||||
// emitted.
|
||||
if (vm.peekStack(0)) |object| {
|
||||
const string_object = try Object.String.fromObject(vm, object);
|
||||
const string_bytes = string_object.bytes[0..string_object.length];
|
||||
try stream_writer.writer.writeAll(string_bytes);
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
const str_object = try Object.String.fromValue(vm, arg);
|
||||
try stream_writer.writer.writeAll(str_object.toSlice());
|
||||
_ = vm.popStack();
|
||||
} //else {
|
||||
//return error.InvalidArgument;
|
||||
|
|
@ -474,8 +565,8 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, arg_offset)) |knot| {
|
||||
try divertToKnot(vm, @ptrCast(knot));
|
||||
if (peekStack(vm, arg_offset)) |value| {
|
||||
try divertToValue(vm, value);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
|
@ -484,17 +575,15 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, 0)) |obj| {
|
||||
assert(obj.tag == .knot);
|
||||
const knot_obj: *Object.Knot = @ptrCast(obj);
|
||||
const arg_obj = try vm.getConstant(frame, arg_offset);
|
||||
assert(arg_obj.tag == .string);
|
||||
const knot_attr: *Object.String = @ptrCast(arg_obj);
|
||||
if (peekStack(vm, 0)) |value| {
|
||||
const knot_object: *Object.Knot = @ptrCast(value.object);
|
||||
const arg_value = try vm.getConstant(frame, arg_offset);
|
||||
const knot_attr: *Object.String = @ptrCast(arg_value.object);
|
||||
|
||||
_ = popStack(vm);
|
||||
|
||||
if (knot_obj.members.get(knot_attr.toSlice())) |attr_obj| {
|
||||
try vm.pushStack(attr_obj);
|
||||
if (knot_object.members.get(knot_attr.toSlice())) |attr_object| {
|
||||
try vm.pushStack(.{ .object = attr_object });
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
|
@ -519,8 +608,9 @@ pub fn advance(story: *Story, gpa: std.mem.Allocator) ![]const u8 {
|
|||
|
||||
pub fn getKnot(vm: *Story, name: []const u8) ?*Object.Knot {
|
||||
const knot: ?*Object.Knot = blk: {
|
||||
if (vm.globals.get(name)) |object| {
|
||||
break :blk @ptrCast(object);
|
||||
if (vm.globals.get(name)) |value| {
|
||||
// TODO: Do a check here.
|
||||
break :blk @ptrCast(value.?.object);
|
||||
}
|
||||
break :blk null;
|
||||
};
|
||||
|
|
@ -545,6 +635,16 @@ fn divertToKnot(vm: *Story, knot: *Object.Knot) !void {
|
|||
vm.can_advance = true;
|
||||
}
|
||||
|
||||
fn divertToValue(vm: *Story, value: Value) !void {
|
||||
switch (value) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.knot => try divertToKnot(vm, @ptrCast(object)),
|
||||
else => return error.TypeError,
|
||||
},
|
||||
else => return error.TypeError,
|
||||
}
|
||||
}
|
||||
|
||||
fn divert(vm: *Story, knot_name: []const u8) !void {
|
||||
return if (getKnot(vm, knot_name)) |knot| {
|
||||
return divertToKnot(vm, knot);
|
||||
|
|
@ -566,6 +666,10 @@ pub fn selectChoiceIndex(story: *Story, index: usize) !void {
|
|||
story.can_advance = true;
|
||||
}
|
||||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
return Dumper.dump(story, writer);
|
||||
}
|
||||
|
||||
pub fn loadFromString(
|
||||
gpa: std.mem.Allocator,
|
||||
source_bytes: [:0]const u8,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const Story = @import("../Story.zig");
|
||||
const Value = Story.Value;
|
||||
const Object = Story.Object;
|
||||
const Opcode = Story.Opcode;
|
||||
const assert = std.debug.assert;
|
||||
|
|
@ -21,6 +22,8 @@ fn dumpByteInst(
|
|||
op: Opcode,
|
||||
) !usize {
|
||||
const code = knot.code;
|
||||
const constants_pool = &self.story.constants_pool;
|
||||
|
||||
assert(code.bytecode.len > offset + 1);
|
||||
const arg = code.bytecode[offset + 1];
|
||||
if (op == .load_const) {
|
||||
|
|
@ -28,9 +31,9 @@ fn dumpByteInst(
|
|||
try w.writeAll(" (");
|
||||
if (code.constants.len > arg) {
|
||||
const constant_index = code.constants[arg];
|
||||
if (self.story.constants_pool.items.len > constant_index) {
|
||||
const global_constant = self.story.constants_pool.items[constant_index];
|
||||
try self.dumpObject(w, global_constant);
|
||||
if (constants_pool.items.len > constant_index) {
|
||||
const global_constant = &constants_pool.items[constant_index];
|
||||
try self.dumpValue(w, global_constant);
|
||||
} else {
|
||||
try w.writeAll("invalid!");
|
||||
}
|
||||
|
|
@ -52,15 +55,24 @@ fn dumpGlobalInst(
|
|||
op: Opcode,
|
||||
) !usize {
|
||||
const code = knot.code;
|
||||
const constants_pool = &self.story.constants_pool;
|
||||
|
||||
assert(code.bytecode.len > offset + 1);
|
||||
const arg = code.bytecode[offset + 1];
|
||||
assert(code.constants.len > arg);
|
||||
const constant_index = code.constants[arg];
|
||||
const global_constant = self.story.constants_pool.items[constant_index];
|
||||
assert(global_constant.tag == .string);
|
||||
const global_name: *Object.String = @ptrCast(global_constant);
|
||||
const name_bytes = global_name.bytes[0..global_name.length];
|
||||
try w.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes });
|
||||
const global_constant = constants_pool.items[constant_index];
|
||||
switch (global_constant) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.string => {
|
||||
const global_name: *Object.String = @ptrCast(object);
|
||||
const name_bytes = global_name.bytes[0..global_name.length];
|
||||
try w.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes });
|
||||
},
|
||||
else => unreachable,
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
|
|
@ -265,40 +277,108 @@ pub fn dumpKnot(self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot) !voi
|
|||
return w.flush();
|
||||
}
|
||||
|
||||
pub fn dumpObject(_: Dumper, w: *std.Io.Writer, obj: *const Object) !void {
|
||||
const type_string = obj.tag.toStr();
|
||||
switch (obj.tag) {
|
||||
.number => {
|
||||
const typed_object: *const Object.Number = @ptrCast(obj);
|
||||
switch (typed_object.data) {
|
||||
.boolean => |value| try w.print("<type={s} value={s}, address={*}>", .{
|
||||
type_string,
|
||||
if (value) "true" else "false",
|
||||
obj,
|
||||
}),
|
||||
.floating => |value| try w.print("<type={s} value={d}, address={*}>", .{
|
||||
type_string,
|
||||
value,
|
||||
obj,
|
||||
}),
|
||||
.integer => |value| try w.print("<type={s} value={d}, address={*}>", .{
|
||||
type_string,
|
||||
value,
|
||||
obj,
|
||||
}),
|
||||
}
|
||||
},
|
||||
.string => {
|
||||
const typed_object: *const Object.String = @ptrCast(obj);
|
||||
const string_bytes = typed_object.bytes[0..typed_object.length];
|
||||
try w.print("<type={s} value=\"{s}\", address={*}>", .{
|
||||
type_string,
|
||||
string_bytes,
|
||||
obj,
|
||||
});
|
||||
},
|
||||
.code, .knot => {
|
||||
try w.print("<type={s} address={*}>", .{ type_string, obj });
|
||||
pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void {
|
||||
switch (value.*) {
|
||||
.bool => |boolean| try w.print("<{s} value={s}>", .{
|
||||
value.tagBytes(),
|
||||
if (boolean) "true" else "false",
|
||||
}),
|
||||
.int => |int| try w.print("<{s} value={d}, address={*}>", .{
|
||||
value.tagBytes(),
|
||||
int,
|
||||
value,
|
||||
}),
|
||||
.float => |float| try w.print("<{s} value={d}, address={*}>", .{
|
||||
value.tagBytes(),
|
||||
float,
|
||||
value,
|
||||
}),
|
||||
.object => |object| switch (object.tag) {
|
||||
.string => {
|
||||
const typed_object: *const Object.String = @ptrCast(object);
|
||||
const string_bytes = typed_object.bytes[0..typed_object.length];
|
||||
try w.print("<{s} value=\"{s}\", address={*}>", .{
|
||||
object.tag.tagBytes(),
|
||||
string_bytes,
|
||||
object,
|
||||
});
|
||||
},
|
||||
else => try w.print("<{s} address={*}>", .{ object.tag.tagBytes(), object }),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
var story_dumper: Dumper = .{ .story = story };
|
||||
|
||||
try writer.writeAll("=== Constants ===\n");
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < story.constants_pool.items.len) : (i += 1) {
|
||||
const global_constant = &story.constants_pool.items[i];
|
||||
try story_dumper.dumpValue(writer, global_constant);
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Globals ===\n");
|
||||
|
||||
var global_iter = story.globals.iterator();
|
||||
while (global_iter.next()) |entry| {
|
||||
try writer.print("{s} => ...\n", .{entry.key_ptr.*});
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Knots ===\n");
|
||||
try writer.flush();
|
||||
|
||||
var knots_iter = story.globals.iterator();
|
||||
while (knots_iter.next()) |entry| {
|
||||
if (entry.value_ptr.*) |global| {
|
||||
switch (global) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.knot => {
|
||||
try writer.writeAll("*");
|
||||
story_dumper.indent_level += 2;
|
||||
try story_dumper.dumpKnot(writer, @ptrCast(object));
|
||||
story_dumper.indent_level -= 2;
|
||||
},
|
||||
else => {},
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *Story.CallFrame) !void {
|
||||
var dumper: Dumper = .{ .story = story };
|
||||
const stack = &story.stack;
|
||||
const stack_top = story.stack.items.len;
|
||||
|
||||
try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp});
|
||||
|
||||
if (stack_top > 0) {
|
||||
// FIXME: There has to be a better way to do this.
|
||||
if (stack_top > 1) {
|
||||
var i: usize = frame.sp;
|
||||
while (i < stack.items.len - 1) : (i += 1) {
|
||||
if (stack.items[i]) |*value| {
|
||||
try dumper.dumpValue(writer, value);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
}
|
||||
if (stack.items[stack.items.len - 1]) |*object| {
|
||||
try dumper.dumpValue(writer, object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll("]\n");
|
||||
_ = try dumper.dumpInst(writer, frame.callee, frame.ip, true);
|
||||
return writer.flush();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,193 +8,42 @@ const Story = @import("../Story.zig");
|
|||
const Object = @This();
|
||||
|
||||
tag: Tag,
|
||||
is_marked: bool,
|
||||
node: std.SinglyLinkedList.Node,
|
||||
is_marked: bool = false,
|
||||
node: std.SinglyLinkedList.Node = .{},
|
||||
|
||||
pub const Tag = enum {
|
||||
number,
|
||||
string,
|
||||
code,
|
||||
knot,
|
||||
|
||||
pub fn toStr(tag: Tag) []const u8 {
|
||||
pub fn tagBytes(tag: Tag) []const u8 {
|
||||
return switch (tag) {
|
||||
.number => "Number",
|
||||
.string => "String",
|
||||
.code => "Code",
|
||||
.knot => "Knot",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ObjectType(comptime tag: Tag) type {
|
||||
return switch (tag) {
|
||||
.string => Object.String,
|
||||
.code => Object.Code,
|
||||
.knot => Object.Knot,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn destroy(obj: *Object, story: *Story) void {
|
||||
switch (obj.tag) {
|
||||
.number => {
|
||||
const typed_obj: *Object.Number = @alignCast(@fieldParentPtr("base", obj));
|
||||
inline for (comptime std.meta.fields(Tag)) |field| {
|
||||
const tag = @field(Tag, field.name);
|
||||
if (obj.tag == tag) {
|
||||
const typed_obj: *Tag.ObjectType(tag) = @alignCast(@fieldParentPtr("base", obj));
|
||||
typed_obj.destroy(story);
|
||||
},
|
||||
.string => {
|
||||
const typed_obj: *Object.String = @alignCast(@fieldParentPtr("base", obj));
|
||||
typed_obj.destroy(story);
|
||||
},
|
||||
.code => {
|
||||
const typed_obj: *Object.Code = @alignCast(@fieldParentPtr("base", obj));
|
||||
typed_obj.destroy(story);
|
||||
},
|
||||
.knot => {
|
||||
const typed_obj: *Object.Knot = @alignCast(@fieldParentPtr("base", obj));
|
||||
typed_obj.destroy(story);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(story: *Story, lhs: *Object, rhs: *Object) !*Object {
|
||||
if (lhs.tag == .string or rhs.tag == .string) {
|
||||
const _lhs = try Object.String.fromObject(story, lhs);
|
||||
const _rhs = try Object.String.fromObject(story, rhs);
|
||||
const result = try Object.String.concat(story, _lhs, _rhs);
|
||||
return @ptrCast(result);
|
||||
} else if (lhs.tag == .number or rhs.tag == .number) {
|
||||
const _lhs = try Object.Number.fromObject(story, lhs);
|
||||
const _rhs = try Object.Number.fromObject(story, rhs);
|
||||
const result = try Object.Number.performArithmetic(story, .add, _lhs, _rhs);
|
||||
return @ptrCast(result);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmpEql(story: *Story, lhs: *Object, rhs: *Object) !*Object {
|
||||
// TODO: This is temporary
|
||||
if (lhs.tag != .number or lhs.tag != rhs.tag) return error.InvalidComparison;
|
||||
const result = try Object.Number.performLogic(story, .cmp_eq, @ptrCast(lhs), @ptrCast(rhs));
|
||||
return @ptrCast(result);
|
||||
}
|
||||
|
||||
pub fn isFalsey(obj: *Object) bool {
|
||||
switch (obj.tag) {
|
||||
.number => {
|
||||
const number: *Object.Number = @ptrCast(obj);
|
||||
switch (number.data) {
|
||||
.boolean => |value| return !value,
|
||||
else => return false,
|
||||
}
|
||||
},
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
||||
pub const Number = struct {
|
||||
base: Object,
|
||||
data: Data,
|
||||
|
||||
pub const Data = union(enum) {
|
||||
boolean: bool,
|
||||
integer: i64,
|
||||
floating: f64,
|
||||
};
|
||||
|
||||
const Type = Object.Number;
|
||||
|
||||
pub fn create(story: *Story, data: Data) error{OutOfMemory}!*Object.Number {
|
||||
const gpa = story.allocator;
|
||||
const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type));
|
||||
const obj: *Type = @ptrCast(raw);
|
||||
obj.* = .{
|
||||
.base = .{
|
||||
.tag = .number,
|
||||
.is_marked = false,
|
||||
.node = .{},
|
||||
},
|
||||
.data = data,
|
||||
};
|
||||
|
||||
story.gc_objects.prepend(&obj.base.node);
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn destroy(obj: *Object.Number, story: *Story) void {
|
||||
const gpa = story.allocator;
|
||||
const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj);
|
||||
gpa.free(base[0..@sizeOf(Type)]);
|
||||
}
|
||||
|
||||
pub fn fromObject(story: *Story, object: *Object) !*Object.Number {
|
||||
const data: Object.Number.Data = v: switch (object.tag) {
|
||||
.number => {
|
||||
const number: *Object.Number = @ptrCast(object);
|
||||
switch (number.data) {
|
||||
.boolean => |value| break :v .{ .integer = @intFromBool(value) },
|
||||
.integer => |value| break :v .{ .integer = value },
|
||||
.floating => |value| break :v .{ .floating = value },
|
||||
}
|
||||
},
|
||||
else => break :v .{ .boolean = true },
|
||||
};
|
||||
|
||||
const obj = Object.Number.create(story, data);
|
||||
// ink_gc_own(story, obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
fn logicalOp(comptime T: type, op: Story.Opcode, lhs: T, rhs: T) bool {
|
||||
switch (op) {
|
||||
.cmp_eq => return lhs == rhs,
|
||||
.cmp_lt => return lhs < rhs,
|
||||
.cmp_gt => return lhs > rhs,
|
||||
.cmp_lte => return lhs <= rhs,
|
||||
.cmp_gte => return lhs >= rhs,
|
||||
else => unreachable,
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn arithmeticOp(comptime T: type, op: Story.Opcode, lhs: T, rhs: T) T {
|
||||
switch (op) {
|
||||
.add => return lhs + rhs,
|
||||
.sub => return lhs - rhs,
|
||||
.mul => return lhs * rhs,
|
||||
.div => return @divTrunc(lhs, rhs),
|
||||
.mod => return @mod(lhs, rhs),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn negate(object: *Object.Number) *Object.Number {
|
||||
switch (object.data) {
|
||||
.integer => |*value| value.* = -value.*,
|
||||
.floating => |*value| value.* = -value.*,
|
||||
else => unreachable,
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
pub fn performLogic(
|
||||
story: *Story,
|
||||
op: Story.Opcode,
|
||||
lhs: *Object.Number,
|
||||
rhs: *Object.Number,
|
||||
) !*Object.Number {
|
||||
return .create(story, .{
|
||||
.boolean = logicalOp(i64, op, lhs.data.integer, rhs.data.integer),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn performArithmetic(
|
||||
story: *Story,
|
||||
op: Story.Opcode,
|
||||
lhs: *Object.Number,
|
||||
rhs: *Object.Number,
|
||||
) !*Object.Number {
|
||||
if (lhs.data == .floating or rhs.data == .floating) {
|
||||
return .create(story, .{
|
||||
.floating = arithmeticOp(f64, op, lhs.data.floating, rhs.data.floating),
|
||||
});
|
||||
}
|
||||
return .create(story, .{
|
||||
.integer = arithmeticOp(i64, op, lhs.data.integer, rhs.data.integer),
|
||||
});
|
||||
}
|
||||
};
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub const String = struct {
|
||||
base: Object,
|
||||
|
|
@ -215,11 +64,7 @@ pub const String = struct {
|
|||
const object: *Type = @ptrCast(raw);
|
||||
|
||||
object.* = .{
|
||||
.base = .{
|
||||
.tag = .string,
|
||||
.is_marked = false,
|
||||
.node = .{},
|
||||
},
|
||||
.base = .{ .tag = .string },
|
||||
.hash = 0,
|
||||
.length = options.bytes.len,
|
||||
.bytes = undefined,
|
||||
|
|
@ -246,22 +91,29 @@ pub const String = struct {
|
|||
return obj.bytes[0..obj.length];
|
||||
}
|
||||
|
||||
pub fn fromObject(story: *Story, obj: *Object) !*Object.String {
|
||||
switch (obj.tag) {
|
||||
.number => {
|
||||
// NOTE: 20 bytes should be enough.
|
||||
const print_buffer_len = 20;
|
||||
var print_buffer: [print_buffer_len]u8 = undefined;
|
||||
const number_object: *Object.Number = @ptrCast(obj);
|
||||
const number_bytes = try std.fmt.bufPrint(&print_buffer, "{}", .{
|
||||
number_object.data.integer,
|
||||
});
|
||||
return .create(story, .{
|
||||
.bytes = number_bytes,
|
||||
pub fn fromValue(story: *Story, value: Story.Value) !*Object.String {
|
||||
// NOTE: 20 bytes should be enough.
|
||||
const print_buffer_len = 20;
|
||||
var print_buffer: [print_buffer_len]u8 = undefined;
|
||||
switch (value) {
|
||||
.bool => |boolean| {
|
||||
const bytes = try std.fmt.bufPrint(&print_buffer, "{s}", .{
|
||||
if (boolean) "true" else "false",
|
||||
});
|
||||
return .create(story, .{ .bytes = bytes });
|
||||
},
|
||||
.int => |int| {
|
||||
const bytes = try std.fmt.bufPrint(&print_buffer, "{d}", .{int});
|
||||
return .create(story, .{ .bytes = bytes });
|
||||
},
|
||||
.float => |float| {
|
||||
const bytes = try std.fmt.bufPrint(&print_buffer, "{d}", .{float});
|
||||
return .create(story, .{ .bytes = bytes });
|
||||
},
|
||||
.object => |object| switch (object.tag) {
|
||||
.string => return @ptrCast(object),
|
||||
else => return error.TypeError,
|
||||
},
|
||||
.string => return @ptrCast(obj),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -275,9 +127,7 @@ pub const String = struct {
|
|||
@memcpy(bytes[lhs.length..], rhs.bytes[0..rhs.length]);
|
||||
//ink_gc_disown(story, INK_OBJ(lhs));
|
||||
//ink_gc_disown(story, INK_OBJ(rhs));
|
||||
return .create(story, .{
|
||||
.bytes = bytes,
|
||||
});
|
||||
return .create(story, .{ .bytes = bytes });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -311,11 +161,7 @@ pub const Code = struct {
|
|||
const obj: *Type = @ptrCast(raw);
|
||||
|
||||
obj.* = .{
|
||||
.base = .{
|
||||
.tag = .code,
|
||||
.is_marked = false,
|
||||
.node = .{},
|
||||
},
|
||||
.base = .{ .tag = .code },
|
||||
.args_count = options.args_count,
|
||||
.locals_count = options.locals_count,
|
||||
.stack_size = options.stack_size,
|
||||
|
|
@ -358,11 +204,7 @@ pub const Knot = struct {
|
|||
const obj: *Type = @ptrCast(raw);
|
||||
|
||||
obj.* = .{
|
||||
.base = .{
|
||||
.tag = .knot,
|
||||
.is_marked = false,
|
||||
.node = .{},
|
||||
},
|
||||
.base = .{ .tag = .knot },
|
||||
.name = try .create(story, .{
|
||||
.bytes = options.name,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ test "fixture - constant folding" {
|
|||
try testRuntimeFixture("constant-folding");
|
||||
}
|
||||
|
||||
test "fixture - arithmetic" {
|
||||
try testRuntimeFixture("arithmetic");
|
||||
}
|
||||
|
||||
const Options = struct {
|
||||
input_reader: *std.Io.Reader,
|
||||
error_writer: *std.Io.Writer,
|
||||
|
|
|
|||
0
src/Story/testdata/arithmetic/input.txt
vendored
Normal file
0
src/Story/testdata/arithmetic/input.txt
vendored
Normal file
7
src/Story/testdata/arithmetic/story.ink
vendored
Normal file
7
src/Story/testdata/arithmetic/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{ 2 * 3 + 5 * 6 }
|
||||
{8 mod 3}
|
||||
{13 % 5}
|
||||
{ 7 / 3 }
|
||||
{ 5 / 2.0 }
|
||||
{ 10 - 2 }
|
||||
{ 2 * (5-1) }
|
||||
7
src/Story/testdata/arithmetic/transcript.txt
vendored
Normal file
7
src/Story/testdata/arithmetic/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
36
|
||||
2
|
||||
3
|
||||
2
|
||||
2.5
|
||||
8
|
||||
8
|
||||
|
|
@ -4,6 +4,7 @@ const AstGen = @import("AstGen.zig");
|
|||
const Sema = @import("Sema.zig");
|
||||
const Ir = @import("Ir.zig");
|
||||
const Story = @import("Story.zig");
|
||||
const Value = Story.Value;
|
||||
const Object = Story.Object;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
|
|
@ -89,7 +90,8 @@ pub const InternPool = struct {
|
|||
};
|
||||
|
||||
pub const Key = union(enum) {
|
||||
int: u64,
|
||||
int: i64,
|
||||
float: u64, // We can't hash floating point numbers.
|
||||
str: Ir.NullTerminatedString,
|
||||
};
|
||||
|
||||
|
|
@ -102,11 +104,7 @@ pub const InternPool = struct {
|
|||
return index;
|
||||
} else {
|
||||
const new_index: Index = @enumFromInt(ip.values.items.len);
|
||||
const new_value: Key = switch (key) {
|
||||
.int => |int| .{ .int = int },
|
||||
.str => |str| .{ .str = str },
|
||||
};
|
||||
try ip.values.append(gpa, new_value);
|
||||
try ip.values.append(gpa, key);
|
||||
try ip.values_map.put(gpa, key, new_index);
|
||||
return new_index;
|
||||
}
|
||||
|
|
@ -115,13 +113,23 @@ pub const InternPool = struct {
|
|||
pub fn getOrPutInt(
|
||||
ip: *InternPool,
|
||||
gpa: std.mem.Allocator,
|
||||
value: u64,
|
||||
value: i64,
|
||||
) error{OutOfMemory}!Index {
|
||||
return ip.getOrPutValue(gpa, .{
|
||||
.int = value,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getOrPutFloat(
|
||||
ip: *InternPool,
|
||||
gpa: std.mem.Allocator,
|
||||
value: f64,
|
||||
) error{OutOfMemory}!Index {
|
||||
return ip.getOrPutValue(gpa, .{
|
||||
.float = @bitCast(value),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getOrPutStr(
|
||||
ip: *InternPool,
|
||||
gpa: std.mem.Allocator,
|
||||
|
|
@ -372,35 +380,38 @@ pub const Module = struct {
|
|||
return module;
|
||||
}
|
||||
|
||||
fn makeValueFromInterned(
|
||||
mod: *Module,
|
||||
story: *Story,
|
||||
value: InternPool.Key,
|
||||
) !Value {
|
||||
switch (value) {
|
||||
.int => |int| return .{ .int = @intCast(int) },
|
||||
.float => |float| return .{ .float = @bitCast(float) },
|
||||
.str => |str| {
|
||||
const bytes = mod.ir.nullTerminatedString(str);
|
||||
const str_object = try Object.String.create(story, .{
|
||||
.bytes = bytes,
|
||||
});
|
||||
return .{ .object = &str_object.base };
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setupStoryRuntime(mod: *Module, gpa: std.mem.Allocator, story: *Story) !void {
|
||||
assert(mod.errors.items.len == 0);
|
||||
const constants_len = mod.intern_pool.values.items.len;
|
||||
|
||||
try story.constants_pool.ensureUnusedCapacity(gpa, constants_len);
|
||||
for (mod.intern_pool.values.items) |constant| {
|
||||
switch (constant) {
|
||||
.int => |value| {
|
||||
const obj = try Object.Number.create(story, .{
|
||||
.integer = @intCast(value),
|
||||
});
|
||||
story.constants_pool.appendAssumeCapacity(&obj.base);
|
||||
},
|
||||
.str => |str| {
|
||||
const bytes = mod.ir.nullTerminatedString(str);
|
||||
const obj = try Object.String.create(story, .{
|
||||
.bytes = bytes,
|
||||
});
|
||||
story.constants_pool.appendAssumeCapacity(&obj.base);
|
||||
},
|
||||
}
|
||||
for (mod.intern_pool.values.items) |value| {
|
||||
const obj = try mod.makeValueFromInterned(story, value);
|
||||
story.constants_pool.appendAssumeCapacity(obj);
|
||||
}
|
||||
for (mod.globals.items) |global| {
|
||||
const key_bytes = mod.intern_pool.getStrBytes(mod.ir, global.key);
|
||||
const constant_data = mod.intern_pool.values.items[@intFromEnum(global.value)];
|
||||
const value_obj = try Object.Number.create(story, .{
|
||||
.integer = @intCast(constant_data.int),
|
||||
});
|
||||
try story.globals.put(gpa, key_bytes, @ptrCast(value_obj));
|
||||
const value = mod.intern_pool.values.items[@intFromEnum(global.value)];
|
||||
const obj = try mod.makeValueFromInterned(story, value);
|
||||
try story.globals.put(gpa, key_bytes, obj);
|
||||
}
|
||||
for (mod.knots.items) |knot| {
|
||||
const name_bytes = mod.intern_pool.getStrBytes(mod.ir, knot.name_index);
|
||||
|
|
@ -415,7 +426,8 @@ pub const Module = struct {
|
|||
.code_bytes = try code_chunk.bytecode.toOwnedSlice(gpa),
|
||||
}),
|
||||
});
|
||||
try story.globals.put(gpa, name_bytes, @ptrCast(knot_object));
|
||||
const value: Value = .{ .object = &knot_object.base };
|
||||
try story.globals.put(gpa, name_bytes, value);
|
||||
}
|
||||
for (mod.stitches.items) |stitch| {
|
||||
const name_bytes = mod.intern_pool.getStrBytes(mod.ir, stitch.name_index);
|
||||
|
|
@ -433,7 +445,8 @@ pub const Module = struct {
|
|||
if (stitch.knot_index) |index| {
|
||||
const parent_knot = mod.knots.items[@intFromEnum(index)];
|
||||
const parent_knot_name = mod.intern_pool.getStrBytes(mod.ir, parent_knot.name_index);
|
||||
const parent_knot_obj: *Object.Knot = @ptrCast(story.globals.get(parent_knot_name).?);
|
||||
const parent_knot_value = story.globals.get(parent_knot_name).?;
|
||||
const parent_knot_obj: *Object.Knot = @ptrCast(parent_knot_value.?.object);
|
||||
try parent_knot_obj.members.put(gpa, name_bytes, &stitch_obj.base);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const ink = @import("ink");
|
||||
const Story = ink.Story;
|
||||
const fatal = std.process.fatal;
|
||||
|
||||
var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||
|
|
@ -88,7 +89,7 @@ fn mainArgs(
|
|||
const stderr = std.fs.File.stderr();
|
||||
var stderr_writer = stderr.writer(&stderr_buffer);
|
||||
|
||||
var story = ink.Story.loadFromString(gpa, source_bytes, .{
|
||||
var story = Story.loadFromString(gpa, source_bytes, .{
|
||||
.error_writer = &stderr_writer.interface,
|
||||
.dump_writer = &stdout_writer.interface,
|
||||
.use_color = use_color,
|
||||
|
|
@ -107,7 +108,7 @@ fn mainArgs(
|
|||
return if (!compile_only) run(gpa, &story);
|
||||
}
|
||||
|
||||
fn run(gpa: std.mem.Allocator, story: *ink.Story) !void {
|
||||
fn run(gpa: std.mem.Allocator, story: *Story) !void {
|
||||
const stdin = std.fs.File.stdin();
|
||||
var stdin_reader = stdin.reader(&stdin_buffer);
|
||||
|
||||
|
|
|
|||
|
|
@ -48,11 +48,16 @@ pub const Writer = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn writeIntegerInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
||||
fn writeIntInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
||||
const data = self.code.instructions[@intFromEnum(inst)].data.int;
|
||||
try w.print("{d}", .{data});
|
||||
}
|
||||
|
||||
fn writeFloatInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
||||
const data = self.code.instructions[@intFromEnum(inst)].data.float;
|
||||
try w.print("{d}", .{data});
|
||||
}
|
||||
|
||||
fn writeStringInst(self: *Writer, w: *std.Io.Writer, inst: Ir.Inst.Index) Error!void {
|
||||
const data = self.code.instructions[@intFromEnum(inst)].data.str;
|
||||
try self.writeStringRef(w, data.start);
|
||||
|
|
@ -310,7 +315,8 @@ pub const Writer = struct {
|
|||
.cmp_gte => try self.writeBinaryInst(w, inst),
|
||||
.cmp_lt => try self.writeBinaryInst(w, inst),
|
||||
.cmp_lte => try self.writeBinaryInst(w, inst),
|
||||
.int => try self.writeIntegerInst(w, inst),
|
||||
.int => try self.writeIntInst(w, inst),
|
||||
.float => try self.writeFloatInst(w, inst),
|
||||
.str => try self.writeStringInst(w, inst),
|
||||
.content_push => try self.writeUnaryInst(w, inst),
|
||||
.content_flush => try self.writeUnaryInst(w, inst),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue