020 interpreter if e2e

✓ Passing This code compiles and runs correctly.

Code

// Test: Interpreter ~if End-to-End
// Full integration test: parse source with ~if, execute, verify branch taken
// Uses inline types since $std module import not yet supported

~import "$std/runtime"
~import "$std/interpreter"


const std = @import("std");
const ast = @import("ast");

// ============================================================================
// Inline interpreter types (copy from interpreter.kz)
// ============================================================================

pub const FieldValue = struct {
    name: []const u8,
    value: []const u8,
};

pub const Value = struct {
    branch: []const u8,
    fields: []const FieldValue,
    payload_json: ?[]const u8,

    pub fn fromDispatch(branch_name: []const u8, payload: ?[]const u8) Value {
        return .{
            .branch = branch_name,
            .fields = &[_]FieldValue{},
            .payload_json = payload,
        };
    }
};

pub const Environment = struct {
    bindings: std.StringHashMap(Value),
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator) Environment {
        return .{
            .bindings = std.StringHashMap(Value).init(allocator),
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *Environment) void {
        self.bindings.deinit();
    }

    pub fn bind(self: *Environment, name: []const u8, value: Value) !void {
        try self.bindings.put(name, value);
    }

    pub fn get(self: *Environment, name: []const u8) ?Value {
        return self.bindings.get(name);
    }
};

pub const DispatchResult = struct {
    branch: []const u8,
    payload_json: ?[]const u8,
};

// Simplified expression bindings for ~if
pub const ExprBindings = struct {
    values: std.StringHashMap(bool),
    allocator: std.mem.Allocator,

    pub fn init(allocator: std.mem.Allocator) ExprBindings {
        return .{
            .values = std.StringHashMap(bool).init(allocator),
            .allocator = allocator,
        };
    }

    pub fn deinit(self: *ExprBindings) void {
        self.values.deinit();
    }
};

pub const InterpreterContext = struct {
    env: *Environment,
    expr_bindings: *ExprBindings,
    allocator: std.mem.Allocator,
    dispatcher: ?*const fn(*const ast.Invocation, *DispatchResult) anyerror!void,
};

// Dummy dispatcher
fn dummy_dispatcher(inv: *const ast.Invocation, result: *DispatchResult) !void {
    _ = inv;
    result.branch = "unknown";
    result.payload_json = null;
}

// Simplified executeFlow for ~if only
fn executeFlow(flow: *const ast.Flow, ctx: *InterpreterContext) !Value {
    const inv = &flow.invocation;

    var dispatch_result: DispatchResult = undefined;

    // Check if this is ~if
    const is_if = inv.path.module_qualifier == null and
                  inv.path.segments.len == 1 and
                  std.mem.eql(u8, inv.path.segments[0], "if");

    if (is_if) {
        std.debug.print("  [EXEC] Detected ~if\n", .{});

        var condition_result: bool = false;

        if (inv.args.len > 0) {
            const first_arg = &inv.args[0];
            std.debug.print("  [EXEC] Arg value: '{s}'\n", .{first_arg.value});

            // Parse the value as boolean
            if (std.mem.eql(u8, first_arg.value, "true")) {
                condition_result = true;
            } else if (std.mem.eql(u8, first_arg.value, "false")) {
                condition_result = false;
            }
        }

        dispatch_result.branch = if (condition_result) "then" else "else";
        dispatch_result.payload_json = null;
        std.debug.print("  [EXEC] Condition={}, branch='{s}'\n", .{condition_result, dispatch_result.branch});
    } else {
        if (ctx.dispatcher) |dispatcher| {
            try dispatcher(inv, &dispatch_result);
        } else {
            return error.NoDispatcher;
        }
    }

    // Find matching continuation and execute
    for (flow.continuations) |cont| {
        if (std.mem.eql(u8, cont.branch, dispatch_result.branch)) {
            std.debug.print("  [EXEC] Matched continuation: | {s} |>\n", .{cont.branch});

            if (cont.node) |node| {
                if (node == .branch_constructor) {
                    const bc = node.branch_constructor;
                    std.debug.print("  [EXEC] Building branch constructor: {s}\n", .{bc.branch_name});

                    var fields = try ctx.allocator.alloc(FieldValue, bc.fields.len);
                    for (bc.fields, 0..) |field, i| {
                        var value = field.expression_str orelse field.name;
                        // Strip quotes from string literals
                        if (value.len >= 2 and value[0] == '"' and value[value.len-1] == '"') {
                            value = value[1..value.len-1];
                        }
                        fields[i] = .{
                            .name = field.name,
                            .value = value,
                        };
                    }

                    return Value{
                        .branch = bc.branch_name,
                        .fields = fields,
                        .payload_json = null,
                    };
                }
            }

            return Value.fromDispatch(dispatch_result.branch, dispatch_result.payload_json);
        }
    }

    return error.NoBranchMatch;
}

// Helper to run interpreter - returns branch name (owned by page allocator)
fn runInterpreter(source: []const u8) ?[]const u8 {
    const koru_parser = @import("koru_parser");
    const koru_errors = @import("koru_errors");
    const koru_ast = @import("koru_ast");

    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    var reporter = koru_errors.ErrorReporter.init(allocator, "test", source) catch return null;
    defer reporter.deinit();

    var parser = koru_parser.Parser.init(allocator, source, "test", &[_][]const u8{}, null) catch return null;
    defer parser.deinit();

    const parse_result = parser.parse() catch return null;

    var flow_to_run: ?*const koru_ast.Flow = null;
    for (parse_result.source_file.items) |*item| {
        if (item.* == .flow) {
            flow_to_run = &item.flow;
            break;
        }
    }

    if (flow_to_run == null) return null;
    const flow = flow_to_run.?;

    var env = Environment.init(allocator);
    defer env.deinit();

    var expr_bindings = ExprBindings.init(allocator);
    defer expr_bindings.deinit();

    var ctx = InterpreterContext{
        .env = &env,
        .expr_bindings = &expr_bindings,
        .allocator = allocator,
        .dispatcher = &dummy_dispatcher,
    };

    const result = executeFlow(flow, &ctx) catch return null;

    // Copy branch name to page allocator so it survives arena cleanup
    const branch_copy = std.heap.page_allocator.dupe(u8, result.branch) catch return null;
    return branch_copy;
}

pub fn main() void {
    std.debug.print("\n", .{});
    std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
    std.debug.print("║     INTERPRETER ~if END-TO-END TEST                          ║\n", .{});
    std.debug.print("╚══════════════════════════════════════════════════════════════╝\n\n", .{});

    // ========================================================================
    // Test 1: ~if(true) should take | then |> branch
    // ========================================================================
    std.debug.print("Test 1: ~if(true) -> | then |> -> yes branch\n", .{});
    {
        const source =
            \\~if(true)
            \\| then |> yes { result: "passed" }
            \\| else |> no { result: "failed" }
        ;

        if (runInterpreter(source)) |branch| {
            std.debug.print("  Result branch: {s}\n", .{branch});
            if (std.mem.eql(u8, branch, "yes")) {
                std.debug.print("  ✓ TEST 1 PASSED - took | then |> branch\n\n", .{});
            } else {
                std.debug.print("  ✗ TEST 1 FAILED - expected 'yes', got '{s}'\n\n", .{branch});
            }
        } else {
            std.debug.print("  ✗ TEST 1 FAILED - interpreter returned null\n\n", .{});
        }
    }

    // ========================================================================
    // Test 2: ~if(false) should take | else |> branch
    // ========================================================================
    std.debug.print("Test 2: ~if(false) -> | else |> -> no branch\n", .{});
    {
        const source =
            \\~if(false)
            \\| then |> yes { result: "failed" }
            \\| else |> no { result: "passed" }
        ;

        if (runInterpreter(source)) |branch| {
            std.debug.print("  Result branch: {s}\n", .{branch});
            if (std.mem.eql(u8, branch, "no")) {
                std.debug.print("  ✓ TEST 2 PASSED - took | else |> branch\n\n", .{});
            } else {
                std.debug.print("  ✗ TEST 2 FAILED - expected 'no', got '{s}'\n\n", .{branch});
            }
        } else {
            std.debug.print("  ✗ TEST 2 FAILED - interpreter returned null\n\n", .{});
        }
    }

    std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
    std.debug.print("║     INTERPRETER ~if E2E TESTS COMPLETE                       ║\n", .{});
    std.debug.print("╚══════════════════════════════════════════════════════════════╝\n", .{});
}
input.kz

Expected Output


╔══════════════════════════════════════════════════════════════╗
║     INTERPRETER ~if END-TO-END TEST                          ║
╚══════════════════════════════════════════════════════════════╝

Test 1: ~if(true) -> | then |> -> yes branch
  [EXEC] Detected ~if
  [EXEC] Arg value: 'true'
  [EXEC] Condition=true, branch='then'
  [EXEC] Matched continuation: | then |>
  [EXEC] Building branch constructor: yes
  Result branch: yes
  ✓ TEST 1 PASSED - took | then |> branch

Test 2: ~if(false) -> | else |> -> no branch
  [EXEC] Detected ~if
  [EXEC] Arg value: 'false'
  [EXEC] Condition=false, branch='else'
  [EXEC] Matched continuation: | else |>
  [EXEC] Building branch constructor: no
  Result branch: no
  ✓ TEST 2 PASSED - took | else |> branch

╔══════════════════════════════════════════════════════════════╗
║     INTERPRETER ~if E2E TESTS COMPLETE                       ║
╚══════════════════════════════════════════════════════════════╝

Test Configuration

MUST_RUN