feat: folding logical operations

This commit is contained in:
Brett Broadhurst 2026-03-29 04:49:03 -06:00
parent 92e8bcd866
commit 2260ccda25
Failed to generate hash of commit
24 changed files with 310 additions and 151 deletions

View file

@ -630,38 +630,11 @@ fn binaryOp(
op: Ir.Inst.Tag, op: Ir.Inst.Tag,
) InnerError!Ir.Inst.Ref { ) InnerError!Ir.Inst.Ref {
const data = expr_node.data.bin; const data = expr_node.data.bin;
assert(data.lhs != null and data.rhs != null);
const lhs = try expr(gi, scope, data.lhs.?); const lhs = try expr(gi, scope, data.lhs.?);
const rhs = try expr(gi, scope, data.rhs.?); const rhs = try expr(gi, scope, data.rhs.?);
return gi.addBinaryNode(op, lhs, rhs); return gi.addBinaryNode(op, lhs, rhs);
} }
fn logicalOp(
gen: *GenIr,
scope: *Scope,
node: *const Ast.Node,
op: Story.Opcode,
) InnerError!void {
const data = node.data.bin;
assert(data.lhs != null and data.rhs != null);
try expr(gen, scope, data.lhs);
const else_label = try gen.makeLabel();
const fixup_offset = try gen.emitJumpInst(op);
_ = try gen.makeFixup(.{
.mode = .relative,
.label_index = else_label,
.code_offset = fixup_offset,
});
try gen.emitSimpleInst(.pop);
const rhs_label = try gen.makeLabel();
gen.setLabel(rhs_label);
try expr(gen, scope, data.rhs);
gen.setLabel(else_label);
}
fn parseNumberLiteral(bytes: []const u8) union(enum) { fn parseNumberLiteral(bytes: []const u8) union(enum) {
int: i64, int: i64,
float: f64, float: f64,
@ -731,39 +704,39 @@ fn identifier(
return block.addStrTok(.decl_ref, str.index, node.loc.start); return block.addStrTok(.decl_ref, str.index, node.loc.start);
} }
fn expr(gi: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Ref { fn expr(gi: *GenIr, scope: *Scope, optional_node: ?*const Ast.Node) InnerError!Ir.Inst.Ref {
const expr_node = optional_expr.?; const node = optional_node.?;
switch (expr_node.tag) { switch (node.tag) {
.file => unreachable, .file => unreachable,
.true_literal => return .bool_true, .true_literal => return .bool_true,
.false_literal => return .bool_false, .false_literal => return .bool_false,
.number_literal => return numberLiteral(gi, expr_node), .number_literal => return numberLiteral(gi, node),
.string_literal => return stringLiteral(gi, expr_node), .string_literal => return stringLiteral(gi, node),
.string_expr => return stringExpr(gi, expr_node), .string_expr => return stringExpr(gi, node),
.empty_string => return stringLiteral(gi, expr_node), .empty_string => return stringLiteral(gi, node),
.identifier => return identifier(gi, scope, expr_node), .identifier => return identifier(gi, scope, node),
.add_expr => return binaryOp(gi, scope, expr_node, .add), .add_expr => return binaryOp(gi, scope, node, .add),
.subtract_expr => return binaryOp(gi, scope, expr_node, .sub), .subtract_expr => return binaryOp(gi, scope, node, .sub),
.multiply_expr => return binaryOp(gi, scope, expr_node, .mul), .multiply_expr => return binaryOp(gi, scope, node, .mul),
.divide_expr => return binaryOp(gi, scope, expr_node, .div), .divide_expr => return binaryOp(gi, scope, node, .div),
.mod_expr => return binaryOp(gi, scope, expr_node, .mod), .mod_expr => return binaryOp(gi, scope, node, .mod),
.negate_expr => return unaryOp(gi, scope, expr_node, .neg), .negate_expr => return unaryOp(gi, scope, node, .neg),
.logical_and_expr => unreachable, .logical_not_expr => return unaryOp(gi, scope, node, .not),
.logical_or_expr => unreachable, .logical_and_expr => return binaryOp(gi, scope, node, .bool_and),
.logical_not_expr => return unaryOp(gi, scope, expr_node, .not), .logical_or_expr => return binaryOp(gi, scope, node, .bool_or),
.logical_equality_expr => return binaryOp(gi, scope, expr_node, .cmp_eq), .logical_equality_expr => return binaryOp(gi, scope, node, .cmp_eq),
.logical_inequality_expr => return binaryOp(gi, scope, expr_node, .cmp_neq), .logical_inequality_expr => return binaryOp(gi, scope, node, .cmp_neq),
.logical_greater_expr => return binaryOp(gi, scope, expr_node, .cmp_gt), .logical_greater_expr => return binaryOp(gi, scope, node, .cmp_gt),
.logical_greater_or_equal_expr => return binaryOp(gi, scope, expr_node, .cmp_gte), .logical_greater_or_equal_expr => return binaryOp(gi, scope, node, .cmp_gte),
.logical_lesser_expr => return binaryOp(gi, scope, expr_node, .cmp_lt), .logical_lesser_expr => return binaryOp(gi, scope, node, .cmp_lt),
.logical_lesser_or_equal_expr => return binaryOp(gi, scope, expr_node, .cmp_lte), .logical_lesser_or_equal_expr => return binaryOp(gi, scope, node, .cmp_lte),
.call_expr => unreachable, .call_expr => unreachable,
.choice_expr => unreachable, .choice_expr => unreachable,
.choice_start_expr => unreachable, .choice_start_expr => unreachable,
.choice_option_expr => unreachable, .choice_option_expr => unreachable,
.choice_inner_expr => unreachable, .choice_inner_expr => unreachable,
.divert_expr => unreachable, .divert_expr => unreachable,
.selector_expr => return fieldAccess(gi, scope, expr_node), .selector_expr => return fieldAccess(gi, scope, node),
.assign_stmt => unreachable, .assign_stmt => unreachable,
.block_stmt => unreachable, .block_stmt => unreachable,
.content_stmt => unreachable, .content_stmt => unreachable,

View file

@ -170,11 +170,21 @@ pub const Inst = struct {
neg, neg,
/// Uses the `un` union field. /// Uses the `un` union field.
not, not,
/// Uses the `bin` union field.
bool_and,
/// Uses the `bin` union field.
bool_or,
/// Uses the `bin` union field.
cmp_eq, cmp_eq,
/// Uses the `bin` union field.
cmp_neq, cmp_neq,
/// Uses the `bin` union field.
cmp_gt, cmp_gt,
/// Uses the `bin` union field.
cmp_gte, cmp_gte,
/// Uses the `bin` union field.
cmp_lt, cmp_lt,
/// Uses the `bin` union field.
cmp_lte, cmp_lte,
/// Uses the `int` union field. /// Uses the `int` union field.
int, int,

View file

@ -35,19 +35,41 @@ pub const Value = struct {
ip_index: InternPool.Index, ip_index: InternPool.Index,
pub const Unwrapped = union(enum) { pub const Unwrapped = union(enum) {
bool: bool,
int: i64, int: i64,
float: f64, float: f64,
fn toFloat(v: Value.Unwrapped) f64 { pub fn toFloat(v: Unwrapped) f64 {
return switch (v) { return switch (v) {
.int => |i| @floatFromInt(i), .bool => |boolean| @floatFromInt(@intFromBool(boolean)),
.float => |f| f, .int => |int| @floatFromInt(int),
.float => |float| float,
};
}
pub fn isTruthy(v: Unwrapped) bool {
return switch (v) {
//.null => false,
.bool => |boolean| boolean,
.int => |int| int != 0,
.float => |float| float != 0.0,
//.str => true,
};
}
pub fn coerce(value: Unwrapped) Unwrapped {
return switch (value) {
.int => value,
.float => value,
.bool => |boolean| .{ .int = if (boolean) 1 else 0 },
//else => null,
}; };
} }
}; };
pub fn unwrap(value: Value, ip: *InternPool) Unwrapped { pub fn unwrap(value: Value, ip: *InternPool) Unwrapped {
switch (ip.values.items[@intFromEnum(value.toInterned())]) { switch (ip.values.items[@intFromEnum(value.toInterned())]) {
.bool => |boolean| return .{ .bool = boolean },
.int => |int| return .{ .int = int }, .int => |int| return .{ .int = int },
.float => |float| return .{ .float = @bitCast(float) }, .float => |float| return .{ .float = @bitCast(float) },
.str => @panic("String unwrapping not implemented!"), .str => @panic("String unwrapping not implemented!"),
@ -279,43 +301,90 @@ pub const Builder = struct {
} }
}; };
fn foldArith(
lhs: Value.Unwrapped,
rhs: Value.Unwrapped,
op: Story.Opcode,
) !Value.Unwrapped {
const l = lhs.coerce();
const r = rhs.coerce();
if (l == .int and r == .int) {
return switch (op) {
.add => .{ .int = l.int +% r.int },
.sub => .{ .int = l.int -% r.int },
.mul => .{ .int = l.int *% r.int },
.div => if (r.int == 0)
error.DivisionByZero
else
.{ .int = @divTrunc(l.int, r.int) },
.mod => if (r.int == 0)
error.DivisionByZero
else
.{ .int = @mod(l.int, r.int) },
else => unreachable,
};
}
const lf = l.toFloat();
const rf = r.toFloat();
return switch (op) {
.add => .{ .float = lf + rf },
.sub => .{ .float = lf - rf },
.mul => .{ .float = lf * rf },
.div => if (rf == 0.0)
error.DivisionByZero
else
.{ .float = lf / rf },
.mod => if (rf == 0.0)
error.DivisionByZero
else
.{ .float = @mod(lf, rf) },
else => unreachable,
};
}
fn foldCmp(
lhs: Value.Unwrapped,
rhs: Value.Unwrapped,
op: Story.Opcode,
) !Value.Unwrapped {
switch (op) {
.cmp_eq => return .{ .bool = std.meta.eql(lhs, rhs) },
.cmp_neq => return .{ .bool = !std.meta.eql(lhs, rhs) },
else => {},
}
const lf = lhs.coerce().toFloat();
const rf = rhs.coerce().toFloat();
const result = switch (op) {
.cmp_lt => lf < rf,
.cmp_gt => lf > rf,
.cmp_lte => lf <= rf,
.cmp_gte => lf >= rf,
else => unreachable,
};
return .{ .bool = result };
}
fn foldConstant( fn foldConstant(
lhs: Value.Unwrapped, lhs: Value.Unwrapped,
rhs: Value.Unwrapped, rhs: Value.Unwrapped,
op: Story.Opcode, op: Story.Opcode,
) !Value.Unwrapped { ) !Value.Unwrapped {
if (lhs == .int and rhs == .int) {
switch (op) { switch (op) {
.add => return .{ .int = lhs.int +% rhs.int }, .add,
.sub => return .{ .int = lhs.int -% rhs.int }, .sub,
.mul => return .{ .int = lhs.int *% rhs.int }, .mul,
.div => { .div,
if (rhs.int == 0) .mod,
return error.DivisionByZero; => return foldArith(lhs, rhs, op),
return .{ .int = @divTrunc(lhs.int, rhs.int) }; .cmp_eq,
}, .cmp_neq,
.mod => if (rhs.int == 0) .cmp_lt,
return error.DivisionByZero .cmp_gt,
else .cmp_lte,
return .{ .int = @mod(lhs.int, rhs.int) }, .cmp_gte,
else => unreachable, => return foldCmp(lhs, rhs, op),
}
}
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, else => unreachable,
} }
} }
@ -351,20 +420,27 @@ fn irUnaryOp(
//const lhs_src: SrcLoc = .{ .src_offset = 0 }; //const lhs_src: SrcLoc = .{ .src_offset = 0 };
//try sema.analyzeArithmeticArg(builder, lhs, lhs_src); //try sema.analyzeArithmeticArg(builder, lhs, lhs_src);
if (sema.resolveValue(lhs)) |lhs_value| { if (sema.resolveValue(lhs)) |lhs_info| {
const lhs_unwrapped = lhs_value.unwrap(ip); switch (lhs_info.unwrap(ip)) {
switch (lhs_unwrapped) { .bool => |boolean| {
.int => |int| {
const new_value = switch (op) { const new_value = switch (op) {
.not => return error.TypeError, .not => !boolean,
.neg => return error.TypeError,
else => unreachable,
};
return .{ .value = ip.getOrPutBool(new_value) };
},
.int => |int| {
const new_value: i64 = switch (op) {
.not => if (int > 0) 0 else 1,
.neg => -int, .neg => -int,
else => unreachable, else => unreachable,
}; };
return .{ .value = try ip.getOrPutInt(gpa, new_value) }; return .{ .value = try ip.getOrPutInt(gpa, new_value) };
}, },
.float => |float| { .float => |float| {
const new_value = switch (op) { const new_value: f64 = switch (op) {
.not => return error.TypeError, .not => if (float > 0.0) 0.0 else 1.0,
.neg => -float, .neg => -float,
else => unreachable, else => unreachable,
}; };
@ -384,8 +460,9 @@ fn irBinaryOp(
inst: Ir.Inst.Index, inst: Ir.Inst.Index,
op: Story.Opcode, op: Story.Opcode,
) InnerError!ValueInfo { ) InnerError!ValueInfo {
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin; const gpa = sema.gpa;
const ip = &sema.module.intern_pool; const ip = &sema.module.intern_pool;
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
const lhs = sema.resolveInst(data.lhs); const lhs = sema.resolveInst(data.lhs);
const rhs = sema.resolveInst(data.rhs); const rhs = sema.resolveInst(data.rhs);
//const lhs_src: SrcLoc = .{ .src_offset = 0 }; //const lhs_src: SrcLoc = .{ .src_offset = 0 };
@ -395,16 +472,13 @@ fn irBinaryOp(
if (sema.resolveValue(lhs)) |lhs_value| { if (sema.resolveValue(lhs)) |lhs_value| {
if (sema.resolveValue(rhs)) |rhs_value| { if (sema.resolveValue(rhs)) |rhs_value| {
const lhs_unwrap = lhs_value.unwrap(ip); const lhs_coerced = lhs_value.unwrap(ip).coerce();
const rhs_unwrap = rhs_value.unwrap(ip); const rhs_coerced = rhs_value.unwrap(ip).coerce();
switch (try foldConstant(lhs_unwrap, rhs_unwrap, op)) { return switch (try foldConstant(lhs_coerced, rhs_coerced, op)) {
.int => |int| return .{ .bool => |boolean| .{ .value = ip.getOrPutBool(boolean) },
.value = try ip.getOrPutInt(sema.gpa, int), .int => |int| .{ .value = try ip.getOrPutInt(gpa, int) },
}, .float => |float| .{ .value = try ip.getOrPutFloat(gpa, float) },
.float => |float| return .{ };
.value = try ip.getOrPutFloat(sema.gpa, float),
},
}
} }
} }
@ -414,6 +488,40 @@ fn irBinaryOp(
return .none; return .none;
} }
fn irLogicalOp(
sema: *Sema,
builder: *Builder,
inst: Ir.Inst.Index,
logical_or: bool,
) InnerError!ValueInfo {
const ip = &sema.module.intern_pool;
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
const lhs = sema.resolveInst(data.lhs);
const rhs = sema.resolveInst(data.rhs);
if (sema.resolveValue(lhs)) |lhs_info| {
const lhs_value = lhs_info.unwrap(ip);
if (sema.resolveValue(rhs)) |_| {
return if (logical_or)
if (lhs_value.isTruthy()) lhs else rhs
else if (!lhs_value.isTruthy()) lhs else rhs;
}
if (logical_or and lhs_value.isTruthy()) return lhs;
if (!logical_or and !lhs_value.isTruthy()) return lhs;
try builder.ensureLoad(rhs);
return .none;
}
const else_label = try builder.addLabel();
try builder.ensureLoad(lhs);
try builder.addFixup(if (logical_or) .jmp_t else .jmp_f, else_label);
try builder.addByteOp(.pop);
try builder.ensureLoad(rhs);
builder.setLabel(else_label);
return .none;
}
fn irDeclRef( fn irDeclRef(
sema: *Sema, sema: *Sema,
builder: *Builder, builder: *Builder,
@ -672,7 +780,6 @@ fn irDivert(
sema.gpa, sema.gpa,
extra.data.field_name_start, extra.data.field_name_start,
); );
std.debug.print("Target: {any}\n", .{target});
const e = try sema.lookupInNamespace(target.namespace.?, ip_index, callee_src); const e = try sema.lookupInNamespace(target.namespace.?, ip_index, callee_src);
switch (e.tag) { switch (e.tag) {
.knot => { .knot => {
@ -782,12 +889,10 @@ fn analyzeBodyInner(
.mod => try irBinaryOp(sema, builder, inst, .mod), .mod => try irBinaryOp(sema, builder, inst, .mod),
.neg => try irUnaryOp(sema, builder, inst, .neg), .neg => try irUnaryOp(sema, builder, inst, .neg),
.not => try irUnaryOp(sema, builder, inst, .not), .not => try irUnaryOp(sema, builder, inst, .not),
.bool_and => try irLogicalOp(sema, builder, inst, false),
.bool_or => try irLogicalOp(sema, builder, inst, true),
.cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq), .cmp_eq => try irBinaryOp(sema, builder, inst, .cmp_eq),
.cmp_neq => blk: { .cmp_neq => try irBinaryOp(sema, builder, inst, .cmp_neq),
const val = try irBinaryOp(sema, builder, inst, .cmp_eq);
try builder.addByteOp(.not);
break :blk val;
},
.cmp_lt => try irBinaryOp(sema, builder, inst, .cmp_lt), .cmp_lt => try irBinaryOp(sema, builder, inst, .cmp_lt),
.cmp_lte => try irBinaryOp(sema, builder, inst, .cmp_lte), .cmp_lte => try irBinaryOp(sema, builder, inst, .cmp_lte),
.cmp_gt => try irBinaryOp(sema, builder, inst, .cmp_gt), .cmp_gt => try irBinaryOp(sema, builder, inst, .cmp_gt),

View file

@ -46,6 +46,15 @@ pub const Value = union(enum) {
return v == .int or v == .float; return v == .int or v == .float;
} }
pub fn coerce(v: Value) ?Value {
return switch (v) {
.int => v,
.float => v,
.bool => |boolean| .{ .int = if (boolean) 1 else 0 },
else => null,
};
}
pub fn isTruthy(v: Value) bool { pub fn isTruthy(v: Value) bool {
return switch (v) { return switch (v) {
//.nil => false, //.nil => false,
@ -69,8 +78,11 @@ pub const Value = union(enum) {
} }
pub fn arith(lhs: Value, rhs: Value, op: Opcode, story: *Story) !Value { pub fn arith(lhs: Value, rhs: Value, op: Opcode, story: *Story) !Value {
if (lhs.isNumeric() and rhs.isNumeric()) if (lhs.coerce()) |l| {
return numericArith(lhs, rhs, op); if (rhs.coerce()) |r| {
return numericArith(l, r, op);
}
}
if (op == .add) { if (op == .add) {
if (lhs == .object and lhs.object.tag == .string) if (lhs == .object and lhs.object.tag == .string)
return concat(lhs, rhs, story); return concat(lhs, rhs, story);
@ -177,6 +189,36 @@ pub const Value = union(enum) {
.object => return error.TypeError, .object => return error.TypeError,
} }
} }
pub fn format(value: Value, writer: *std.Io.Writer) error{WriteFailed}!void {
switch (value) {
.bool => |boolean| try writer.writeAll(if (boolean) "true" else "false"),
.int => |int| try writer.print("{d}", .{int}),
.float => |float| {
var buf: [64]u8 = undefined;
if (std.math.isNan(float)) return writer.writeAll("NaN");
if (std.math.isInf(float)) return writer.writeAll(if (float > 0) "Inf" else "-Inf");
var str = std.fmt.bufPrint(&buf, "{d:.7}", .{float}) catch |err| switch (err) {
error.NoSpaceLeft => unreachable,
else => |e| return e,
};
if (std.mem.indexOfScalar(u8, str, '.')) |dot| {
var end = str.len;
while (end > dot + 2 and str[end - 1] == '0') end -= 1;
str = str[0..end];
}
try writer.writeAll(str);
},
.object => |object| switch (object.tag) {
.string => {
const typed: *const Object.String = @ptrCast(object);
try writer.writeAll(typed.toSlice());
},
else => try writer.print("<{s} {*}>", .{ object.tag.tagBytes(), object }),
},
}
}
}; };
pub const CallFrame = struct { pub const CallFrame = struct {

View file

@ -279,20 +279,18 @@ pub fn dumpKnot(self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot) !voi
pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void { pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void {
switch (value.*) { switch (value.*) {
.bool => |boolean| try w.print("<{s} value={s}>", .{ .bool => |_| try w.print(
value.tagBytes(), "<{s} value={f}>",
if (boolean) "true" else "false", .{ value.tagBytes(), value },
}), ),
.int => |int| try w.print("<{s} value={d}, address={*}>", .{ .int => |_| try w.print(
value.tagBytes(), "<{s} value={f}, address={*}>",
int, .{ value.tagBytes(), value, value },
value, ),
}), .float => |_| try w.print(
.float => |float| try w.print("<{s} value={d}, address={*}>", .{ "<{s} value={f}, address={*}>",
value.tagBytes(), .{ value.tagBytes(), value, value },
float, ),
value,
}),
.object => |object| switch (object.tag) { .object => |object| switch (object.tag) {
.string => { .string => {
const typed_object: *const Object.String = @ptrCast(object); const typed_object: *const Object.String = @ptrCast(object);

View file

@ -87,27 +87,16 @@ pub const String = struct {
gpa.free(base[0..alloc_len]); gpa.free(base[0..alloc_len]);
} }
pub fn toSlice(obj: *Object.String) []const u8 { pub fn toSlice(obj: *const Object.String) []const u8 {
return obj.bytes[0..obj.length]; return obj.bytes[0..obj.length];
} }
pub fn fromValue(story: *Story, value: Story.Value) !*Object.String { pub fn fromValue(story: *Story, value: Story.Value) !*Object.String {
// NOTE: 20 bytes should be enough. const print_buffer_len = 64;
const print_buffer_len = 20;
var print_buffer: [print_buffer_len]u8 = undefined; var print_buffer: [print_buffer_len]u8 = undefined;
switch (value) { switch (value) {
.bool => |boolean| { .bool, .int, .float => {
const bytes = try std.fmt.bufPrint(&print_buffer, "{s}", .{ const bytes = try std.fmt.bufPrint(&print_buffer, "{f}", .{value});
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 }); return .create(story, .{ .bytes = bytes });
}, },
.object => |object| switch (object.tag) { .object => |object| switch (object.tag) {

View file

@ -18,8 +18,24 @@ test "fixture - constant folding" {
try testRuntimeFixture("constant-folding"); try testRuntimeFixture("constant-folding");
} }
test "fixture - arithmetic" { test "fixture - I118 (Literal unary)" {
try testRuntimeFixture("arithmetic"); try testRuntimeFixture("I118");
}
test "fixture - I121 (Arithmetic)" {
try testRuntimeFixture("I121");
}
test "fixture - I133 (Float printing precision)" {
try testRuntimeFixture("I133");
}
test "fixture - I134 (Native bools)" {
try testRuntimeFixture("I134");
}
test "fixture - I135 (Bools can be coerced)" {
try testRuntimeFixture("I135");
} }
const Options = struct { const Options = struct {

6
src/Story/testdata/I118/story.ink vendored Normal file
View file

@ -0,0 +1,6 @@
VAR negativeLiteral = -1
VAR negativeLiteral2 = not not false
VAR negativeLiteral3 = !(0)
{negativeLiteral + 0}
{negativeLiteral2 + 0}
{negativeLiteral3 + 0}

View file

@ -0,0 +1,3 @@
-1
0
1

0
src/Story/testdata/I121/input.txt vendored Normal file
View file

0
src/Story/testdata/I133/input.txt vendored Normal file
View file

1
src/Story/testdata/I133/story.ink vendored Normal file
View file

@ -0,0 +1 @@
{ 7 / 3.0 }

View file

@ -0,0 +1 @@
2.3333333

0
src/Story/testdata/I134/input.txt vendored Normal file
View file

2
src/Story/testdata/I134/story.ink vendored Normal file
View file

@ -0,0 +1,2 @@
{ 1 == 1 }
{ 1 != 1 }

View file

@ -0,0 +1,2 @@
true
false

0
src/Story/testdata/I135/input.txt vendored Normal file
View file

2
src/Story/testdata/I135/story.ink vendored Normal file
View file

@ -0,0 +1,2 @@
{ (1 == 1) + 1 }
{ (1 != 1) - 1 }

View file

@ -0,0 +1,2 @@
2
-1

View file

@ -85,11 +85,14 @@ pub const InternPool = struct {
code_chunks: std.ArrayListUnmanaged(*Module.CodeChunk) = .empty, code_chunks: std.ArrayListUnmanaged(*Module.CodeChunk) = .empty,
pub const Index = enum(u32) { pub const Index = enum(u32) {
bool_true,
bool_false,
none, none,
_, _,
}; };
pub const Key = union(enum) { pub const Key = union(enum) {
bool: bool,
int: i64, int: i64,
float: u64, // We can't hash floating point numbers. float: u64, // We can't hash floating point numbers.
str: Ir.NullTerminatedString, str: Ir.NullTerminatedString,
@ -110,14 +113,16 @@ pub const InternPool = struct {
} }
} }
pub fn getOrPutBool(_: *InternPool, value: bool) Index {
return if (value) return .bool_true else .bool_false;
}
pub fn getOrPutInt( pub fn getOrPutInt(
ip: *InternPool, ip: *InternPool,
gpa: std.mem.Allocator, gpa: std.mem.Allocator,
value: i64, value: i64,
) error{OutOfMemory}!Index { ) error{OutOfMemory}!Index {
return ip.getOrPutValue(gpa, .{ return ip.getOrPutValue(gpa, .{ .int = value });
.int = value,
});
} }
pub fn getOrPutFloat( pub fn getOrPutFloat(
@ -125,9 +130,7 @@ pub const InternPool = struct {
gpa: std.mem.Allocator, gpa: std.mem.Allocator,
value: f64, value: f64,
) error{OutOfMemory}!Index { ) error{OutOfMemory}!Index {
return ip.getOrPutValue(gpa, .{ return ip.getOrPutValue(gpa, .{ .float = @bitCast(value) });
.float = @bitCast(value),
});
} }
pub fn getOrPutStr( pub fn getOrPutStr(
@ -135,9 +138,7 @@ pub const InternPool = struct {
gpa: std.mem.Allocator, gpa: std.mem.Allocator,
value: Ir.NullTerminatedString, value: Ir.NullTerminatedString,
) error{OutOfMemory}!Index { ) error{OutOfMemory}!Index {
return ip.getOrPutValue(gpa, .{ return ip.getOrPutValue(gpa, .{ .str = value });
.str = value,
});
} }
pub fn getStrBytes(ip: *InternPool, ir: Ir, index: Index) []const u8 { pub fn getStrBytes(ip: *InternPool, ir: Ir, index: Index) []const u8 {
@ -370,6 +371,9 @@ pub const Module = struct {
}); });
} }
} else { } else {
try module.intern_pool.values.append(gpa, .{ .bool = true });
try module.intern_pool.values.append(gpa, .{ .bool = false });
try module.intern_pool.values.append(gpa, .{ .str = @enumFromInt(0) });
// TODO: Revisit this. // TODO: Revisit this.
module.generateFile() catch |err| switch (err) { module.generateFile() catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory, error.OutOfMemory => return error.OutOfMemory,
@ -386,6 +390,7 @@ pub const Module = struct {
value: InternPool.Key, value: InternPool.Key,
) !Value { ) !Value {
switch (value) { switch (value) {
.bool => |boolean| return .{ .bool = boolean },
.int => |int| return .{ .int = @intCast(int) }, .int => |int| return .{ .int = @intCast(int) },
.float => |float| return .{ .float = @bitCast(float) }, .float => |float| return .{ .float = @bitCast(float) },
.str => |str| { .str => |str| {

View file

@ -309,6 +309,8 @@ pub const Writer = struct {
.mod => try self.writeBinaryInst(w, inst), .mod => try self.writeBinaryInst(w, inst),
.neg => try self.writeUnaryInst(w, inst), .neg => try self.writeUnaryInst(w, inst),
.not => try self.writeUnaryInst(w, inst), .not => try self.writeUnaryInst(w, inst),
.bool_and => try self.writeBinaryInst(w, inst),
.bool_or => try self.writeBinaryInst(w, inst),
.cmp_eq => try self.writeBinaryInst(w, inst), .cmp_eq => try self.writeBinaryInst(w, inst),
.cmp_neq => try self.writeBinaryInst(w, inst), .cmp_neq => try self.writeBinaryInst(w, inst),
.cmp_gt => try self.writeBinaryInst(w, inst), .cmp_gt => try self.writeBinaryInst(w, inst),