036 interpreter for loop

✓ Passing This code compiles and runs correctly.

Code

// Test: Interpreter for loop
//
// Tests that ~for(0..N) | each i |> body | done |> ... works

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

const std = @import("std");

// Event that prints a number
~pub event print_num { n: []const u8 }
| printed {}

~proc print_num {
    std.debug.print("[print_num] n = {s}\n", .{n});
    return .{ .printed = .{} };
}

// Register events including control flow
~std.runtime:register(scope: "test") {
    for
    print_num
}

// Test flow: loop 3 times, print each iteration
const TEST_FLOW =
    \\~for(0..3)
    \\| each i |> print_num(n: i)
    \\| done |> result { status: "complete" }
;

~std.interpreter:run(source: TEST_FLOW, dispatcher: dispatch_test)
| result r |> std.io:print.ln("Result: {{ r.value.branch:s }}")
| exhausted e |> std.io:print.ln("Exhausted: {{ e.last_event }}")
| parse_error e |> std.io:print.ln("Parse error: {{ e.message }}")
| validation_error e |> std.io:print.ln("Validation error: {{ e.message }}")
| dispatch_error e |> std.io:print.ln("Dispatch error: {{ e.message }}")
input.kz

Imported Files

// HONEST INTERPRETER BENCHMARK
//
// Task: Sum numbers 0..99 using for loop
// Measure: Parse once, execute N times, report per-execution time
// Compare: Python doing the same thing

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

const std = @import("std");

// ============================================================================
// Events for computation
// ============================================================================

~pub event add { a: []const u8, b: []const u8 }
| sum { result: []const u8 }

~proc add {
    const a_int = std.fmt.parseInt(i64, a, 10) catch 0;
    const b_int = std.fmt.parseInt(i64, b, 10) catch 0;
    const result_int = a_int + b_int;
    var buf: [32]u8 = undefined;
    const result_str = std.fmt.bufPrint(&buf, "{d}", .{result_int}) catch "0";
    return .{ .sum = .{ .result = result_str } };
}

~pub event init_sum {}
| value { n: []const u8 }

~proc init_sum {
    return .{ .value = .{ .n = "0" } };
}

// Register events including for
~std.runtime:register(scope: "bench") {
    for
    add
    init_sum
}

// The benchmark flow: sum 0..100
const BENCH_FLOW =
    \\~for(0..100)
    \\| each i |> add(a: "0", b: i)
    \\| done |> result { status: "complete" }
;

// Simple flow for comparison (no loop)
const SIMPLE_FLOW =
    \\~add(a: "21", b: "21")
;

// ============================================================================
// BENCHMARK
// ============================================================================

pub fn main() void {
    const koru_parser = @import("koru_parser");

    std.debug.print("\n", .{});
    std.debug.print("============================================================\n", .{});
    std.debug.print("HONEST INTERPRETER BENCHMARK\n", .{});
    std.debug.print("============================================================\n\n", .{});

    const ITERATIONS = 1000;

    // ========================================================================
    // BENCHMARK 1: Simple dispatch (no loop) - parse once, dispatch N times
    // ========================================================================
    std.debug.print("[1] SIMPLE DISPATCH: ~add(a: \"21\", b: \"21\")\n", .{});
    std.debug.print("    Iterations: {d}\n", .{ITERATIONS});
    {
        var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
        defer arena.deinit();
        const allocator = arena.allocator();

        // Parse once
        var parser = koru_parser.Parser.init(allocator, SIMPLE_FLOW, "bench", &[_][]const u8{}, null) catch {
            std.debug.print("    ERROR: Parser init failed\n", .{});
            return;
        };
        defer parser.deinit();

        const parse_result = parser.parse() catch {
            std.debug.print("    ERROR: Parse failed\n", .{});
            return;
        };

        // Find the flow
        var inv_ptr: ?*const @import("ast").Invocation = null;
        for (parse_result.source_file.items) |*item| {
            if (item.* == .flow) {
                inv_ptr = &item.flow.invocation;
                break;
            }
        }

        if (inv_ptr == null) {
            std.debug.print("    ERROR: No flow found\n", .{});
            return;
        }

        // Warm up
        for (0..10) |_| {
            var result: koru_std.runtime.DispatchResult = undefined;
            dispatch_bench(inv_ptr.?, &result) catch continue;
        }

        // Time it
        const start = std.time.nanoTimestamp();

        for (0..ITERATIONS) |_| {
            var result: koru_std.runtime.DispatchResult = undefined;
            dispatch_bench(inv_ptr.?, &result) catch continue;
        }

        const end = std.time.nanoTimestamp();
        const total_ns = end - start;
        const per_iter_ns = @divTrunc(total_ns, ITERATIONS);

        std.debug.print("    Total: {d} ms\n", .{@divTrunc(total_ns, 1_000_000)});
        std.debug.print("    Per dispatch: {d} ns\n", .{per_iter_ns});
    }

    std.debug.print("\n", .{});

    // ========================================================================
    // BENCHMARK 2: Full interpreter with for loop
    // ========================================================================
    std.debug.print("[2] FULL INTERPRETER: ~for(0..100) with adds\n", .{});
    std.debug.print("    Iterations: {d}\n", .{ITERATIONS});
    {
        var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
        defer arena.deinit();
        const allocator = arena.allocator();

        // Parse once
        var parser = koru_parser.Parser.init(allocator, BENCH_FLOW, "bench", &[_][]const u8{}, null) catch {
            std.debug.print("    ERROR: Parser init failed\n", .{});
            return;
        };
        defer parser.deinit();

        const parse_result = parser.parse() catch {
            std.debug.print("    ERROR: Parse failed\n", .{});
            return;
        };

        // Find the flow
        var flow_ptr: ?*const @import("ast").Flow = null;
        for (parse_result.source_file.items) |*item| {
            if (item.* == .flow) {
                flow_ptr = &item.flow;
                break;
            }
        }

        if (flow_ptr == null) {
            std.debug.print("    ERROR: No flow found\n", .{});
            return;
        }

        // Warm up (run a few iterations) using reusable env/bindings
        var env = koru_std.interpreter.Environment.init(allocator);
        defer env.deinit();
        var expr_bindings = koru_std.interpreter.ExprBindings.init(allocator);
        defer expr_bindings.deinit();

        for (0..3) |_| {
            env.clear();
            expr_bindings.clear();
            var ctx = koru_std.interpreter.InterpreterContext{
                .env = &env,
                .expr_bindings = &expr_bindings,
                .allocator = allocator,
                .dispatcher = &dispatch_bench,
            };
            _ = koru_std.interpreter.executeFlow(flow_ptr.?, &ctx) catch continue;
        }

        // Time it
        const start = std.time.nanoTimestamp();

        for (0..ITERATIONS) |_| {
            env.clear();
            expr_bindings.clear();
            var ctx = koru_std.interpreter.InterpreterContext{
                .env = &env,
                .expr_bindings = &expr_bindings,
                .allocator = allocator,
                .dispatcher = &dispatch_bench,
            };
            _ = koru_std.interpreter.executeFlow(flow_ptr.?, &ctx) catch continue;
        }

        const end = std.time.nanoTimestamp();
        const total_ns = end - start;
        const per_iter_ns = @divTrunc(total_ns, ITERATIONS);
        const per_iter_us = @divTrunc(per_iter_ns, 1000);

        std.debug.print("    Total: {d} ms\n", .{@divTrunc(total_ns, 1_000_000)});
        std.debug.print("    Per execution: {d} us ({d} ns)\n", .{per_iter_us, per_iter_ns});
        std.debug.print("    (Each execution = 100 loop iterations + 100 add dispatches)\n", .{});
    }

    // ========================================================================
    // BENCHMARK 3: Parse time only
    // ========================================================================
    std.debug.print("[3] PARSE ONLY: ~for(0..100) flow\n", .{});
    std.debug.print("    Iterations: {d}\n", .{ITERATIONS});
    {
        // Time parsing only
        const start = std.time.nanoTimestamp();

        for (0..ITERATIONS) |_| {
            var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
            defer arena.deinit();
            var parser = koru_parser.Parser.init(arena.allocator(), BENCH_FLOW, "bench", &[_][]const u8{}, null) catch continue;
            defer parser.deinit();
            _ = parser.parse() catch continue;
        }

        const end = std.time.nanoTimestamp();
        const total_ns = end - start;
        const per_iter_ns = @divTrunc(total_ns, ITERATIONS);
        const per_iter_us = @divTrunc(per_iter_ns, 1000);

        std.debug.print("    Total: {d} ms\n", .{@divTrunc(total_ns, 1_000_000)});
        std.debug.print("    Per parse: {d} us ({d} ns)\n", .{per_iter_us, per_iter_ns});
    }

    std.debug.print("\n", .{});
    std.debug.print("============================================================\n", .{});
    std.debug.print("COMPARE WITH: python3 benchmark.py\n", .{});
    std.debug.print("============================================================\n", .{});
}
benchmark.kz