018 runtime if

✓ Passing This code compiles and runs correctly.

Code

// Test: Runtime If with Expression Evaluation
// Tests runtime conditionals using expression evaluation against bindings
// Note: Uses inline types since module import not yet supported

~import "$std/runtime"
~import "$std/build"
~import "$std/runtime_control"

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

// ============================================================================
// VALUE TYPE - Runtime expression values
// ============================================================================

pub const Value = union(enum) {
    bool_val: bool,
    int_val: i64,
    float_val: f64,
    string_val: []const u8,
    null_val: void,

    pub fn isTruthy(self: Value) bool {
        return switch (self) {
            .bool_val => |b| b,
            .int_val => |n| n != 0,
            .float_val => |f| f != 0.0,
            .string_val => |s| s.len > 0,
            .null_val => false,
        };
    }
};

// ============================================================================
// BINDINGS - Name to value mapping
// ============================================================================

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

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

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

    pub fn set(self: *Bindings, name: []const u8, value: Value) !void {
        try self.values.put(name, value);
    }

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

// ============================================================================
// EXPRESSION EVALUATOR
// ============================================================================

pub const EvalError = error{
    UnknownBinding,
    TypeMismatch,
    DivisionByZero,
    InvalidExpression,
    UnsupportedOperator,
};

pub fn evaluate(expression: *const ast.Expression, bindings: *const Bindings) EvalError!Value {
    return evaluateNode(&expression.node, bindings);
}

fn evaluateNode(node: *const ast.ExprNode, bindings: *const Bindings) EvalError!Value {
    return switch (node.*) {
        .literal => |lit| evaluateLiteral(lit),
        .identifier => |id| evaluateIdentifier(id, bindings),
        .binary => |bin| evaluateBinaryOp(&bin, bindings),
        .unary => |un| evaluateUnaryOp(&un, bindings),
        .field_access => |fa| evaluateFieldAccess(&fa, bindings),
        .grouped => |g| evaluate(g, bindings),
    };
}

fn evaluateLiteral(lit: ast.Literal) Value {
    return switch (lit) {
        .number => |n| blk: {
            if (std.fmt.parseInt(i64, n, 10)) |i| {
                break :blk .{ .int_val = i };
            } else |_| {}
            if (std.fmt.parseFloat(f64, n)) |f| {
                break :blk .{ .float_val = f };
            } else |_| {}
            break :blk .{ .int_val = 0 };
        },
        .string => |s| .{ .string_val = s },
        .boolean => |b| .{ .bool_val = b },
    };
}

fn evaluateIdentifier(id: []const u8, bindings: *const Bindings) EvalError!Value {
    return bindings.get(id) orelse EvalError.UnknownBinding;
}

fn evaluateFieldAccess(fa: *const ast.FieldAccess, bindings: *const Bindings) EvalError!Value {
    const obj_node = &fa.object.node;
    if (obj_node.* == .identifier) {
        const obj_name = obj_node.identifier;
        var key_buf: [256]u8 = undefined;
        const key = std.fmt.bufPrint(&key_buf, "{s}.{s}", .{obj_name, fa.field}) catch {
            return EvalError.InvalidExpression;
        };
        if (bindings.get(key)) |val| {
            return val;
        }
    }
    return EvalError.UnknownBinding;
}

fn evaluateBinaryOp(bin: *const ast.BinaryOp, bindings: *const Bindings) EvalError!Value {
    const left = try evaluate(bin.left, bindings);
    const right = try evaluate(bin.right, bindings);

    return switch (bin.op) {
        .equal => compareEq(left, right),
        .not_equal => compareNeq(left, right),
        .less => compareLt(left, right),
        .greater => compareGt(left, right),
        .less_equal => compareLte(left, right),
        .greater_equal => compareGte(left, right),
        .and_op => .{ .bool_val = left.isTruthy() and right.isTruthy() },
        .or_op => .{ .bool_val = left.isTruthy() or right.isTruthy() },
        .add => arithmeticOp(left, right, .add),
        .subtract => arithmeticOp(left, right, .sub),
        .multiply => arithmeticOp(left, right, .mul),
        .divide => arithmeticOp(left, right, .div),
        .modulo => arithmeticOp(left, right, .mod),
        .string_concat => EvalError.UnsupportedOperator,
    };
}

fn evaluateUnaryOp(un: *const ast.UnaryOp, bindings: *const Bindings) EvalError!Value {
    const operand = try evaluate(un.operand, bindings);
    return switch (un.op) {
        .not => .{ .bool_val = !operand.isTruthy() },
        .negate => switch (operand) {
            .int_val => |n| .{ .int_val = -n },
            .float_val => |f| .{ .float_val = -f },
            else => EvalError.TypeMismatch,
        },
    };
}

fn compareEq(left: Value, right: Value) Value {
    const result = switch (left) {
        .bool_val => |l| switch (right) { .bool_val => |r| l == r, else => false },
        .int_val => |l| switch (right) {
            .int_val => |r| l == r,
            .float_val => |r| @as(f64, @floatFromInt(l)) == r,
            else => false,
        },
        .float_val => |l| switch (right) {
            .int_val => |r| l == @as(f64, @floatFromInt(r)),
            .float_val => |r| l == r,
            else => false,
        },
        .string_val => |l| switch (right) { .string_val => |r| std.mem.eql(u8, l, r), else => false },
        .null_val => switch (right) { .null_val => true, else => false },
    };
    return .{ .bool_val = result };
}

fn compareNeq(left: Value, right: Value) Value {
    return .{ .bool_val = !compareEq(left, right).bool_val };
}

fn compareLt(left: Value, right: Value) EvalError!Value {
    return switch (left) {
        .int_val => |l| switch (right) {
            .int_val => |r| .{ .bool_val = l < r },
            .float_val => |r| .{ .bool_val = @as(f64, @floatFromInt(l)) < r },
            else => EvalError.TypeMismatch,
        },
        .float_val => |l| switch (right) {
            .int_val => |r| .{ .bool_val = l < @as(f64, @floatFromInt(r)) },
            .float_val => |r| .{ .bool_val = l < r },
            else => EvalError.TypeMismatch,
        },
        else => EvalError.TypeMismatch,
    };
}

fn compareGt(left: Value, right: Value) EvalError!Value {
    return switch (left) {
        .int_val => |l| switch (right) {
            .int_val => |r| .{ .bool_val = l > r },
            .float_val => |r| .{ .bool_val = @as(f64, @floatFromInt(l)) > r },
            else => EvalError.TypeMismatch,
        },
        .float_val => |l| switch (right) {
            .int_val => |r| .{ .bool_val = l > @as(f64, @floatFromInt(r)) },
            .float_val => |r| .{ .bool_val = l > r },
            else => EvalError.TypeMismatch,
        },
        else => EvalError.TypeMismatch,
    };
}

fn compareLte(left: Value, right: Value) EvalError!Value {
    return .{ .bool_val = !(try compareGt(left, right)).bool_val };
}

fn compareGte(left: Value, right: Value) EvalError!Value {
    return .{ .bool_val = !(try compareLt(left, right)).bool_val };
}

const ArithOp = enum { add, sub, mul, div, mod };

fn arithmeticOp(left: Value, right: Value, op: ArithOp) EvalError!Value {
    return switch (left) {
        .int_val => |l| switch (right) {
            .int_val => |r| intOp(l, r, op),
            .float_val => |r| floatOp(@as(f64, @floatFromInt(l)), r, op),
            else => EvalError.TypeMismatch,
        },
        .float_val => |l| switch (right) {
            .int_val => |r| floatOp(l, @as(f64, @floatFromInt(r)), op),
            .float_val => |r| floatOp(l, r, op),
            else => EvalError.TypeMismatch,
        },
        else => EvalError.TypeMismatch,
    };
}

fn intOp(l: i64, r: i64, op: ArithOp) EvalError!Value {
    return .{ .int_val = switch (op) {
        .add => l + r,
        .sub => l - r,
        .mul => l * r,
        .div => if (r == 0) return EvalError.DivisionByZero else @divTrunc(l, r),
        .mod => if (r == 0) return EvalError.DivisionByZero else @mod(l, r),
    } };
}

fn floatOp(l: f64, r: f64, op: ArithOp) EvalError!Value {
    return .{ .float_val = switch (op) {
        .add => l + r,
        .sub => l - r,
        .mul => l * r,
        .div => if (r == 0.0) return EvalError.DivisionByZero else l / r,
        .mod => @mod(l, r),
    } };
}

// ============================================================================
// MAIN TEST
// ============================================================================

pub fn main() void {
    std.debug.print("\n", .{});
    std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
    std.debug.print("║           RUNTIME IF TEST                                    ║\n", .{});
    std.debug.print("╚══════════════════════════════════════════════════════════════╝\n\n", .{});

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

    // ========================================================================
    // Test 1: Simple boolean true
    // ========================================================================
    std.debug.print("Test 1: Boolean true condition\n", .{});
    {
        var bindings = Bindings.init(allocator);
        defer bindings.deinit();

        bindings.set("active", .{ .bool_val = true }) catch unreachable;

        const expr_node = ast.ExprNode{ .identifier = "active" };
        const expression = ast.Expression{ .node = expr_node };

        const result = evaluate(&expression, &bindings) catch |err| {
            std.debug.print("  ERROR: {s}\n", .{@errorName(err)});
            return;
        };

        std.debug.print("  active = {}\n", .{result.bool_val});
        std.debug.print("  isTruthy() = {}\n", .{result.isTruthy()});

        if (result.isTruthy()) {
            std.debug.print("  Branch: | then |>\n", .{});
            std.debug.print("  ✓ TEST 1 PASSED\n\n", .{});
        } else {
            std.debug.print("  ✗ TEST 1 FAILED\n\n", .{});
        }
    }

    // ========================================================================
    // Test 2: Numeric comparison - score > 50 (true case)
    // ========================================================================
    std.debug.print("Test 2: Numeric comparison (75 > 50)\n", .{});
    {
        var bindings = Bindings.init(allocator);
        defer bindings.deinit();

        bindings.set("score", .{ .int_val = 75 }) catch unreachable;
        bindings.set("threshold", .{ .int_val = 50 }) catch unreachable;

        const left_node = ast.ExprNode{ .identifier = "score" };
        const left_expr = allocator.create(ast.Expression) catch unreachable;
        left_expr.* = .{ .node = left_node };

        const right_node = ast.ExprNode{ .identifier = "threshold" };
        const right_expr = allocator.create(ast.Expression) catch unreachable;
        right_expr.* = .{ .node = right_node };

        const binary_node = ast.ExprNode{ .binary = .{
            .op = .greater,
            .left = left_expr,
            .right = right_expr,
        }};
        const expression = ast.Expression{ .node = binary_node };

        const result = evaluate(&expression, &bindings) catch |err| {
            std.debug.print("  ERROR: {s}\n", .{@errorName(err)});
            return;
        };

        std.debug.print("  score (75) > threshold (50) = {}\n", .{result.bool_val});

        if (result.isTruthy()) {
            std.debug.print("  Branch: | then |>\n", .{});
            std.debug.print("  ✓ TEST 2 PASSED\n\n", .{});
        } else {
            std.debug.print("  ✗ TEST 2 FAILED\n\n", .{});
        }
    }

    // ========================================================================
    // Test 3: Numeric comparison - score > threshold (false case)
    // ========================================================================
    std.debug.print("Test 3: Numeric comparison (25 > 50) - else branch\n", .{});
    {
        var bindings = Bindings.init(allocator);
        defer bindings.deinit();

        bindings.set("score", .{ .int_val = 25 }) catch unreachable;
        bindings.set("threshold", .{ .int_val = 50 }) catch unreachable;

        const left_node = ast.ExprNode{ .identifier = "score" };
        const left_expr = allocator.create(ast.Expression) catch unreachable;
        left_expr.* = .{ .node = left_node };

        const right_node = ast.ExprNode{ .identifier = "threshold" };
        const right_expr = allocator.create(ast.Expression) catch unreachable;
        right_expr.* = .{ .node = right_node };

        const binary_node = ast.ExprNode{ .binary = .{
            .op = .greater,
            .left = left_expr,
            .right = right_expr,
        }};
        const expression = ast.Expression{ .node = binary_node };

        const result = evaluate(&expression, &bindings) catch |err| {
            std.debug.print("  ERROR: {s}\n", .{@errorName(err)});
            return;
        };

        std.debug.print("  score (25) > threshold (50) = {}\n", .{result.bool_val});

        if (!result.isTruthy()) {
            std.debug.print("  Branch: | else |>\n", .{});
            std.debug.print("  ✓ TEST 3 PASSED\n\n", .{});
        } else {
            std.debug.print("  ✗ TEST 3 FAILED\n\n", .{});
        }
    }

    // ========================================================================
    // Test 4: String equality
    // ========================================================================
    std.debug.print("Test 4: String equality (role == \"admin\")\n", .{});
    {
        var bindings = Bindings.init(allocator);
        defer bindings.deinit();

        bindings.set("role", .{ .string_val = "admin" }) catch unreachable;

        const left_node = ast.ExprNode{ .identifier = "role" };
        const left_expr = allocator.create(ast.Expression) catch unreachable;
        left_expr.* = .{ .node = left_node };

        const right_node = ast.ExprNode{ .literal = .{ .string = "admin" } };
        const right_expr = allocator.create(ast.Expression) catch unreachable;
        right_expr.* = .{ .node = right_node };

        const binary_node = ast.ExprNode{ .binary = .{
            .op = .equal,
            .left = left_expr,
            .right = right_expr,
        }};
        const expression = ast.Expression{ .node = binary_node };

        const result = evaluate(&expression, &bindings) catch |err| {
            std.debug.print("  ERROR: {s}\n", .{@errorName(err)});
            return;
        };

        std.debug.print("  role == \"admin\" = {}\n", .{result.bool_val});

        if (result.isTruthy()) {
            std.debug.print("  Branch: | then |>\n", .{});
            std.debug.print("  ✓ TEST 4 PASSED\n\n", .{});
        } else {
            std.debug.print("  ✗ TEST 4 FAILED\n\n", .{});
        }
    }

    std.debug.print("╔══════════════════════════════════════════════════════════════╗\n", .{});
    std.debug.print("║           RUNTIME IF TESTS COMPLETE                          ║\n", .{});
    std.debug.print("╚══════════════════════════════════════════════════════════════╝\n", .{});
}
input.kz

Expected Output


╔══════════════════════════════════════════════════════════════╗
║           RUNTIME IF TEST                                    ║
╚══════════════════════════════════════════════════════════════╝

Test 1: Boolean true condition
  active = true
  isTruthy() = true
  Branch: | then |>
  ✓ TEST 1 PASSED

Test 2: Numeric comparison (75 > 50)
  score (75) > threshold (50) = true
  Branch: | then |>
  ✓ TEST 2 PASSED

Test 3: Numeric comparison (25 > 50) - else branch
  score (25) > threshold (50) = false
  Branch: | else |>
  ✓ TEST 3 PASSED

Test 4: String equality (role == "admin")
  role == "admin" = true
  Branch: | then |>
  ✓ TEST 4 PASSED

╔══════════════════════════════════════════════════════════════╗
║           RUNTIME IF TESTS COMPLETE                          ║
╚══════════════════════════════════════════════════════════════╝

Test Configuration

MUST_RUN