hamelin_eval 0.9.7

Expression evaluation for Hamelin query language
Documentation
use std::sync::Arc;

use crate::eval::evaluator::eval;
use crate::value::Value;
use hamelin_lib::tree::builder::{field, field_ref, pair, tuple, ExpressionBuilder};
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::tree::typed_ast::expression::TypedExpressionKind;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::{INT, STRING};

use super::test_helpers::TestContext;

#[test]
fn test_eval_tuple_literal() {
    let ctx = TestContext::default();

    let expr = tuple().element(1).element("hello").element(true);

    let result = ctx.eval_expr(&expr);
    match result {
        Value::Tuple(tup) => {
            assert_eq!(tup.len(), 3);
            assert_eq!(tup[0], Value::Int(1));
            assert_eq!(tup[1], Value::String("hello".to_string()));
            assert_eq!(tup[2], Value::Boolean(true));
        }
        _ => panic!("Expected Tuple value"),
    }
}

#[test]
fn test_eval_tuple_with_variables() {
    let mut ctx = TestContext::default();
    ctx.set("name", Value::String("Alice".to_string()), STRING);
    ctx.set("age", Value::Int(30), INT);

    // Create tuple with variables (name, age, "static")
    let expr = tuple()
        .element(field_ref("name"))
        .element(field_ref("age"))
        .element("static");

    let result = ctx.eval_expr(&expr);
    assert_eq!(
        result,
        Value::Tuple(vec![
            Value::String("Alice".to_string()),
            Value::Int(30),
            Value::String("static".to_string())
        ])
    );
}

#[test]
fn test_eval_pair_literal() {
    let ctx = TestContext::default();

    // Simple pair of integers
    let expr = pair(1, 2);
    let result = ctx.eval_expr(&expr);
    assert_eq!(result, Value::Tuple(vec![Value::Int(1), Value::Int(2)]));

    // Pair with different types
    let expr = pair(42, "hello");
    let result = ctx.eval_expr(&expr);
    assert_eq!(
        result,
        Value::Tuple(vec![Value::Int(42), Value::String("hello".to_string())])
    );

    // Nested pair
    let expr = pair(pair(1, 2), 3);
    let result = ctx.eval_expr(&expr);
    assert_eq!(
        result,
        Value::Tuple(vec![
            Value::Tuple(vec![Value::Int(1), Value::Int(2)]),
            Value::Int(3)
        ])
    );
}

#[test]
fn test_eval_empty_tuple() {
    let ctx = TestContext::default();

    // Empty tuples may not be supported and could create an error expression
    let expr_builder = tuple();
    let expr = type_check_expression(
        expr_builder.build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;

    // Check if the expression is an error expression (type system may prevent empty tuples)
    if matches!(
        expr.kind,
        hamelin_lib::tree::typed_ast::expression::TypedExpressionKind::Error(_)
    ) {
        // Empty tuples are not supported - this is expected behavior
        return;
    }

    // If not an error, it should evaluate successfully
    let result = eval(&expr, &ctx.env).unwrap();

    match result {
        Value::Tuple(tup) => {
            assert_eq!(tup.len(), 0);
        }
        _ => panic!("Expected empty Tuple value"),
    }
}

#[test]
fn test_eval_single_element_tuple() {
    let ctx = TestContext::default();

    let expr = tuple().element("solo");
    let result = ctx.eval_expr(&expr);

    assert_eq!(
        result,
        Value::Tuple(vec![Value::String("solo".to_string())])
    );
}

#[test]
fn test_eval_nested_tuples() {
    let ctx = TestContext::default();

    // Create nested tuples: ((1, 2), (3, 4))
    let inner_tuple1 = tuple().element(1).element(2);
    let inner_tuple2 = tuple().element(3).element(4);
    let expr = tuple().element(inner_tuple1).element(inner_tuple2);

    let result = ctx.eval_expr(&expr);

    let expected = Value::Tuple(vec![
        Value::Tuple(vec![Value::Int(1), Value::Int(2)]),
        Value::Tuple(vec![Value::Int(3), Value::Int(4)]),
    ]);

    assert_eq!(result, expected);
}

#[test]
fn test_eval_tuple_with_mixed_types() {
    let ctx = TestContext::default();

    let expr = tuple()
        .element(42) // Int
        .element("text") // String
        .element(true) // Boolean
        .element(3.14); // Double

    let result = ctx.eval_expr(&expr);

    let expected = Value::Tuple(vec![
        Value::Int(42),
        Value::String("text".to_string()),
        Value::Boolean(true),
        Value::Double(3.14),
    ]);

    assert_eq!(result, expected);
}

#[test]
fn test_eval_pair_with_nulls() {
    use hamelin_lib::tree::builder::null;

    let ctx = TestContext::default();

    // Pair with one null
    let expr = pair(null(), 42);
    let result = ctx.eval_expr(&expr);
    assert_eq!(result, Value::Tuple(vec![Value::Null, Value::Int(42)]));

    // Pair with both nulls
    let expr = pair(null(), null());
    let result = ctx.eval_expr(&expr);
    assert_eq!(result, Value::Tuple(vec![Value::Null, Value::Null]));
}

#[test]
fn test_eval_tuple_access_length() {
    let ctx = TestContext::default();

    let expr = tuple().element("a").element("b").element("c");
    let result = ctx.eval_expr(&expr);

    match result {
        Value::Tuple(tup) => {
            assert_eq!(tup.len(), 3);
            assert_eq!(tup[0], Value::String("a".to_string()));
            assert_eq!(tup[1], Value::String("b".to_string()));
            assert_eq!(tup[2], Value::String("c".to_string()));
        }
        _ => panic!("Expected Tuple value"),
    }
}

#[test]
fn test_eval_tuple_field_access() {
    use hamelin_lib::tree::builder::field;

    let ctx = TestContext::default();

    // Access first element (f0)
    let f0_expr = field(tuple().element("first").element(42).element(true), "f0");
    let result = ctx.eval_expr(&f0_expr);
    assert_eq!(result, Value::String("first".to_string()));

    // Access second element (f1)
    let f1_expr = field(tuple().element("first").element(42).element(true), "f1");
    let result = ctx.eval_expr(&f1_expr);
    assert_eq!(result, Value::Int(42));

    // Access third element (f2)
    let f2_expr = field(tuple().element("first").element(42).element(true), "f2");
    let result = ctx.eval_expr(&f2_expr);
    assert_eq!(result, Value::Boolean(true));
}

#[test]
fn test_eval_pair_field_access() {
    use hamelin_lib::tree::builder::field;

    let ctx = TestContext::default();

    // Access first element (f0)
    let f0_expr = field(pair("hello", 123), "f0");
    let result = ctx.eval_expr(&f0_expr);
    assert_eq!(result, Value::String("hello".to_string()));

    // Access second element (f1)
    let f1_expr = field(pair("hello", 123), "f1");
    let result = ctx.eval_expr(&f1_expr);
    assert_eq!(result, Value::Int(123));
}

#[test]
fn test_eval_nested_tuple_field_access() {
    use hamelin_lib::tree::builder::field;

    let ctx = TestContext::default();

    // Access first inner tuple and then its first element: tuple.f0.f0
    let nested_expr = field(
        field(
            tuple()
                .element(tuple().element(1).element(2))
                .element(tuple().element(3).element(4)),
            "f0",
        ),
        "f0",
    );
    let result = ctx.eval_expr(&nested_expr);
    assert_eq!(result, Value::Int(1));

    // Access second inner tuple and then its second element: tuple.f1.f1
    let nested_expr = field(
        field(
            tuple()
                .element(tuple().element(1).element(2))
                .element(tuple().element(3).element(4)),
            "f1",
        ),
        "f1",
    );
    let result = ctx.eval_expr(&nested_expr);
    assert_eq!(result, Value::Int(4));
}

#[test]
fn test_eval_tuple_field_access_out_of_bounds() {
    use hamelin_lib::tree::builder::field;

    let ctx = TestContext::default();

    // Try to access f2 (third element) on tuple with only 2 elements - should fail during type checking
    let f2_expr = field(tuple().element("first").element("second"), "f2");

    // This should result in an error expression during typing
    let typed_expr = type_check_expression(
        f2_expr.build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;

    // Check if it's an error expression
    assert!(matches!(typed_expr.kind, TypedExpressionKind::Error(_)));
}

#[test]
fn test_eval_tuple_field_access_invalid_field_name() {
    let ctx = TestContext::default();

    // Try to access with invalid field names
    let invalid_names = vec!["0", "1", "x", "field", "f", "f-1"];

    for invalid_name in invalid_names {
        let invalid_expr = field(tuple().element("first").element("second"), invalid_name);
        let typed_expr = type_check_expression(
            invalid_expr.build(),
            ExpressionTypeCheckOptions::builder()
                .bindings(Arc::new(ctx.translation_env.clone()))
                .build(),
        )
        .output;

        // Should result in an error expression
        assert!(
            matches!(
                typed_expr.kind,
                hamelin_lib::tree::typed_ast::expression::TypedExpressionKind::Error(_)
            ),
            "Field name '{}' should have caused an error",
            invalid_name
        );
    }
}