eval/interpreter2: add integer literals, addition, parameter binding, and lookup to minimal evaluator; add Roc-style test (|n| n + 1)(41) -> 42

This commit is contained in:
Richard Feldman 2025-09-24 11:41:07 -04:00
parent a389e2ff23
commit 5a55c86b07
No known key found for this signature in database
2 changed files with 82 additions and 7 deletions

View file

@ -52,6 +52,7 @@ pub const Interpreter2 = struct {
return true;
}
};
const Binding = struct { pattern_idx: can.CIR.Pattern.Idx, value: StackValue };
allocator: std.mem.Allocator,
runtime_types: *types.store.Store,
runtime_layout_store: layout.Store,
@ -74,6 +75,7 @@ pub const Interpreter2 = struct {
// Minimal eval support
stack_memory: stack.Stack,
bindings: std.ArrayList(Binding),
pub fn init(allocator: std.mem.Allocator, env: *can.ModuleEnv) !Interpreter2 {
const rt_types_ptr = try allocator.create(types.store.Store);
@ -94,6 +96,7 @@ pub const Interpreter2 = struct {
.snapshots = try snapshot_mod.Store.initCapacity(allocator, 256),
.unify_scratch = try unify.Scratch.init(allocator),
.stack_memory = try stack.Stack.initCapacity(allocator, 4096),
.bindings = try std.ArrayList(Binding).initCapacity(allocator, 8),
};
result.runtime_layout_store = try layout.Store.init(env, result.runtime_types);
return result;
@ -107,6 +110,36 @@ pub const Interpreter2 = struct {
fn evalExprMinimal(self: *Interpreter2, expr_idx: can.CIR.Expr.Idx, roc_ops: *RocOps) !StackValue {
const expr = self.env.store.getExpr(expr_idx);
switch (expr) {
.e_int => |int_lit| {
// Use runtime type to choose layout
const ct_var = can.ModuleEnv.varFrom(expr_idx);
const rt_var = try self.translateTypeVar(self.env, ct_var);
const layout_val = try self.getRuntimeLayout(rt_var);
var value = try self.pushRaw(layout_val, 0);
// Write integer as i128 respecting precision via StackValue
value.is_initialized = false;
value.setInt(int_lit.value.toI128());
value.is_initialized = true;
return value;
},
.e_binop => |binop| {
// Only support add for now
if (binop.op != .add) return error.NotImplemented;
const lhs = try self.evalExprMinimal(binop.lhs, roc_ops);
const rhs = try self.evalExprMinimal(binop.rhs, roc_ops);
// Expect ints
if (!(lhs.layout.tag == .scalar and lhs.layout.data.scalar.tag == .int)) return error.TypeMismatch;
if (!(rhs.layout.tag == .scalar and rhs.layout.data.scalar.tag == .int)) return error.TypeMismatch;
const ct_var = can.ModuleEnv.varFrom(expr_idx);
const rt_var = try self.translateTypeVar(self.env, ct_var);
const result_layout = try self.getRuntimeLayout(rt_var);
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const sum = lhs.asI128() + rhs.asI128();
out.setInt(sum);
out.is_initialized = true;
return out;
},
.e_str => |str_expr| {
const segments = self.env.store.sliceExpr(str_expr.span);
if (segments.len == 0) {
@ -151,12 +184,23 @@ pub const Interpreter2 = struct {
const lambda = func_expr.e_lambda;
// Evaluate argument
const arg_val = try self.evalExprMinimal(arg_idx, roc_ops);
// For identity-like lambda: if body is e_lookup_local of first param, just return arg
const body_expr = self.env.store.getExpr(lambda.body);
if (body_expr == .e_lookup_local) {
// Return a copy of arg onto the stack
const copied = try self.pushCopy(arg_val);
return copied;
// Bind the first param to the evaluated argument
const params = self.env.store.slicePatterns(lambda.args);
if (params.len != 1) return error.NotImplemented;
try self.bindings.append(.{ .pattern_idx = params[0], .value = arg_val });
defer _ = self.bindings.pop();
// Evaluate body with binding
return try self.evalExprMinimal(lambda.body, roc_ops);
},
.e_lookup_local => |lookup| {
// Search bindings in reverse
var i: usize = self.bindings.items.len;
while (i > 0) {
i -= 1;
const b = self.bindings.items[i];
if (b.pattern_idx == lookup.pattern_idx) {
return try self.pushCopy(b.value);
}
}
return error.NotImplemented;
},
@ -208,6 +252,7 @@ pub const Interpreter2 = struct {
self.problems.deinit(self.allocator);
self.unify_scratch.deinit();
self.stack_memory.deinit();
self.bindings.deinit();
}
/// Ensure the slot array can index at least `min_len` entries; zero-fill new entries.
@ -251,7 +296,12 @@ pub const Interpreter2 = struct {
.int => |p| try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = p } } } }),
.frac => |p| try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .frac = p } } } }),
},
else => return error.NotImplemented,
.int_precision => |p| try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = p } } } }),
.frac_precision => |p| try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .frac = p } } } }),
.num_unbound, .int_unbound => try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = types.Num.Int.Precision.default } } } }),
.frac_unbound => try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .frac = types.Num.Frac.Precision.default } } } }),
.num_poly, .int_poly => try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .int = types.Num.Int.Precision.default } } } }),
.frac_poly => try self.runtime_types.freshFromContent(.{ .structure = .{ .num = .{ .num_compact = .{ .frac = types.Num.Frac.Precision.default } } } }),
},
.tuple => |t| {
const ct_elems = module.types.sliceVars(t.elems);

View file

@ -131,3 +131,28 @@ test "interpreter2: (|x| x)(\"Hello\") yields \"Hello\"" {
const got_out_roc = expected_out_roc; // In a future step, render REPL-style from result
try std.testing.expectEqualStrings(expected_out_roc, got_out_roc);
}
test "interpreter2: (|n| n + 1)(41) yields 42" {
const roc_src = "(|n| n + 1)(41)";
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);
try std.testing.expect(result.layout.tag == .scalar and result.layout.data.scalar.tag == .int);
try std.testing.expectEqual(@as(i128, 42), result.asI128());
}