diff --git a/src/AstGen.zig b/src/AstGen.zig index 4d7dfdd..b827ffa 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -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 { diff --git a/src/Ir.zig b/src/Ir.zig index 5ddadb9..0091e93 100644 --- a/src/Ir.zig +++ b/src/Ir.zig @@ -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, diff --git a/src/Sema.zig b/src/Sema.zig index 4de2fb7..a6b1c8b 100644 --- a/src/Sema.zig +++ b/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), diff --git a/src/Story.zig b/src/Story.zig index 28fcb77..bb64e5d 100644 --- a/src/Story.zig +++ b/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, diff --git a/src/Story/Dumper.zig b/src/Story/Dumper.zig index 234690c..b6af3b7 100644 --- a/src/Story/Dumper.zig +++ b/src/Story/Dumper.zig @@ -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_string, - if (value) "true" else "false", - obj, - }), - .floating => |value| try w.print("", .{ - type_string, - value, - obj, - }), - .integer => |value| try w.print("", .{ - 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_string, - string_bytes, - obj, - }); - }, - .code, .knot => { - try w.print("", .{ 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(); +} diff --git a/src/Story/Object.zig b/src/Story/Object.zig index beb9733..6ff1553 100644 --- a/src/Story/Object.zig +++ b/src/Story/Object.zig @@ -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, }), diff --git a/src/Story/runtime_tests.zig b/src/Story/runtime_tests.zig index 1c8cfb3..c92bdea 100644 --- a/src/Story/runtime_tests.zig +++ b/src/Story/runtime_tests.zig @@ -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, diff --git a/src/Story/testdata/arithmetic/input.txt b/src/Story/testdata/arithmetic/input.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Story/testdata/arithmetic/story.ink b/src/Story/testdata/arithmetic/story.ink new file mode 100644 index 0000000..d1b0ef3 --- /dev/null +++ b/src/Story/testdata/arithmetic/story.ink @@ -0,0 +1,7 @@ +{ 2 * 3 + 5 * 6 } +{8 mod 3} +{13 % 5} +{ 7 / 3 } +{ 5 / 2.0 } +{ 10 - 2 } +{ 2 * (5-1) } diff --git a/src/Story/testdata/arithmetic/transcript.txt b/src/Story/testdata/arithmetic/transcript.txt new file mode 100644 index 0000000..f9d1642 --- /dev/null +++ b/src/Story/testdata/arithmetic/transcript.txt @@ -0,0 +1,7 @@ +36 +2 +3 +2 +2.5 +8 +8 diff --git a/src/compile.zig b/src/compile.zig index 4358919..566bc24 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -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); } } diff --git a/src/main.zig b/src/main.zig index 0406be3..1ce11d1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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); diff --git a/src/print_ir.zig b/src/print_ir.zig index d39db11..269c98a 100644 --- a/src/print_ir.zig +++ b/src/print_ir.zig @@ -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),