Unify Interpreter2 bool semantics

This commit is contained in:
Richard Feldman 2025-09-25 00:47:28 -04:00
parent 90841c3b65
commit b719ba70bd
No known key found for this signature in database
5 changed files with 672 additions and 271 deletions

View file

@ -113,8 +113,10 @@
5) Match (wider coverage)
- Current minimal support: assign, underscore, int & string literals, nominal passthrough, OR patterns.
- Progress:
- Tuple destructuring patterns now match in Interpreter2 (see `match (1, 2)` test).
- TODO:
- Tuple and record destructuring patterns (bind subcomponents recursively).
- Record destructuring patterns (bind sub-components recursively).
- Tag union patterns (tag name and payloads) once tag unions are represented for runtime.
- Guards support.
- Add tests incrementally (starting from simple patterns), always with Roc syntax and early compilation failure

File diff suppressed because it is too large Load diff

View file

@ -46,6 +46,13 @@ pub fn renderValueRocWithType(ctx: *RenderCtx, value: StackValue, rt_var: types.
tag_index = @intCast(value.asI128());
have_tag = true;
}
if (have_tag and tag_index < tags.len) {
const tag_name = ctx.env.getIdent(tags.items(.name)[tag_index]);
var out = std.ArrayList(u8).init(gpa);
errdefer out.deinit();
try out.appendSlice(tag_name);
return out.toOwnedSlice();
}
} else if (value.layout.tag == .record) {
var acc = try value.asRecord(ctx.layout_store);
if (acc.findFieldIndex(ctx.env, "tag")) |idx| {
@ -135,8 +142,12 @@ pub fn renderValueRoc(ctx: *RenderCtx, value: StackValue) ![]u8 {
try buf.append('"');
for (s) |ch| {
switch (ch) {
'\\' => { try buf.appendSlice("\\\\"); },
'"' => { try buf.appendSlice("\\\""); },
'\\' => {
try buf.appendSlice("\\\\");
},
'"' => {
try buf.appendSlice("\\\"");
},
else => try buf.append(ch),
}
}
@ -194,4 +205,3 @@ pub fn renderValueRoc(ctx: *RenderCtx, value: StackValue) ![]u8 {
}
return try std.fmt.allocPrint(gpa, "<unsupported>", .{});
}

View file

@ -186,6 +186,116 @@ test "interpreter2: literal True renders True" {
try std.testing.expectEqualStrings("True", rendered);
}
test "interpreter2: True == False yields False" {
const roc_src = "True == False";
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter2.init(std.testing.allocator, resources.module_env);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
var ops = RocOps{
.env = @ptrCast(&host),
.roc_alloc = testRocAlloc,
.roc_dealloc = testRocDealloc,
.roc_realloc = testRocRealloc,
.roc_dbg = testRocDbg,
.roc_expect_failed = testRocExpectFailed,
.roc_crashed = testRocCrashed,
.host_fns = undefined,
};
const result = try interp2.evalMinimal(resources.expr_idx, &ops);
const ct_var = can.ModuleEnv.varFrom(resources.expr_idx);
const rt_var = try interp2.translateTypeVar(resources.module_env, ct_var);
const rendered = try interp2.renderValueRocWithType(result, rt_var);
defer std.testing.allocator.free(rendered);
try std.testing.expectEqualStrings("False", rendered);
}
test "interpreter2: \"hi\" == \"hi\" yields True" {
const roc_src = "\"hi\" == \"hi\"";
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
try helpers.runExpectBool(roc_src, true, .no_trace);
var interp2 = try Interpreter2.init(std.testing.allocator, resources.module_env);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
var ops = RocOps{
.env = @ptrCast(&host),
.roc_alloc = testRocAlloc,
.roc_dealloc = testRocDealloc,
.roc_realloc = testRocRealloc,
.roc_dbg = testRocDbg,
.roc_expect_failed = testRocExpectFailed,
.roc_crashed = testRocCrashed,
.host_fns = undefined,
};
const result = try interp2.evalMinimal(resources.expr_idx, &ops);
const ct_var = can.ModuleEnv.varFrom(resources.expr_idx);
const rt_var = try interp2.translateTypeVar(resources.module_env, ct_var);
const rendered = try interp2.renderValueRocWithType(result, rt_var);
defer std.testing.allocator.free(rendered);
try std.testing.expectEqualStrings("True", rendered);
}
test "interpreter2: match tuple pattern destructures" {
const roc_src = "match (1, 2) { (1, b) => b, _ => 0 }";
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter2.init(std.testing.allocator, resources.module_env);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
var ops = RocOps{
.env = @ptrCast(&host),
.roc_alloc = testRocAlloc,
.roc_dealloc = testRocDealloc,
.roc_realloc = testRocRealloc,
.roc_dbg = testRocDbg,
.roc_expect_failed = testRocExpectFailed,
.roc_crashed = testRocCrashed,
.host_fns = undefined,
};
const result = try interp2.evalMinimal(resources.expr_idx, &ops);
const rendered = try interp2.renderValueRoc(result);
defer std.testing.allocator.free(rendered);
try std.testing.expectEqualStrings("2", rendered);
}
test "interpreter2: match bool patterns" {
const roc_src = "match True { True => 1, False => 0 }";
const resources = try helpers.parseAndCanonicalizeExpr(std.testing.allocator, roc_src);
defer helpers.cleanupParseAndCanonical(std.testing.allocator, resources);
var interp2 = try Interpreter2.init(std.testing.allocator, resources.module_env);
defer interp2.deinit();
var host = TestHost{ .allocator = std.testing.allocator };
var ops = RocOps{
.env = @ptrCast(&host),
.roc_alloc = testRocAlloc,
.roc_dealloc = testRocDealloc,
.roc_realloc = testRocRealloc,
.roc_dbg = testRocDbg,
.roc_expect_failed = testRocExpectFailed,
.roc_crashed = testRocCrashed,
.host_fns = undefined,
};
const result = try interp2.evalMinimal(resources.expr_idx, &ops);
const rendered = try interp2.renderValueRoc(result);
defer std.testing.allocator.free(rendered);
try std.testing.expectEqualStrings("1", rendered);
}
test "interpreter2: tuples and records" {
// Tuple test: (1, 2)
const src_tuple = "(1, 2)";

View file

@ -188,12 +188,12 @@ pub const Store = struct {
pub fn mkBool(self: *Self, gpa: Allocator, idents: *base.Ident.Store, ext_var: Var) std.mem.Allocator.Error!Content {
// TODO: Hardcode idents once in store, do no create fn anno
const true_ident = try idents.insert(gpa, base.Ident.for_text("True"));
const false_ident = try idents.insert(gpa, base.Ident.for_text("False"));
const true_ident = try idents.insert(gpa, base.Ident.for_text("True"));
const true_tag = try self.mkTag(true_ident, &[_]Var{});
const false_tag = try self.mkTag(false_ident, &[_]Var{});
return try self.mkTagUnion(&[_]Tag{ true_tag, false_tag }, ext_var);
const true_tag = try self.mkTag(true_ident, &[_]Var{});
return try self.mkTagUnion(&[_]Tag{ false_tag, true_tag }, ext_var);
}
pub fn mkResult(