hamelin_eval 0.10.13

Expression evaluation for Hamelin query language
Documentation
use crate::eval::environment::Environment;
use crate::eval::error::EvalError;
use crate::eval::evaluator::eval;
use crate::eval::tests::test_helpers::TestContext;
use crate::value::{DecimalValue, Value};
use hamelin_lib::tree::builder::{
    add, divide, field_ref, modulo, multiply, subtract, ExpressionBuilder,
};
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::INT;
use std::sync::Arc;

use super::test_helpers::setup_environment;

#[test]
fn test_eval_binary_addition_int() {
    let env = setup_environment();

    // Test: 5 + 3 = 8
    let expr =
        type_check_expression(add(5, 3).build(), ExpressionTypeCheckOptions::default()).output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(result, Value::Int(8));
}

#[test]
fn test_eval_binary_addition_double() {
    let env = setup_environment();

    // Test: 2.5 + 3.7 = 6.2
    let expr =
        type_check_expression(add(2.5, 3.7).build(), ExpressionTypeCheckOptions::default()).output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(result, Value::Double(6.2));
}

#[test]
fn test_eval_binary_addition_mixed_types() {
    let env = setup_environment();

    // Test: 5 + 3.2 = 8.2 (int + double = double)
    let expr =
        type_check_expression(add(5, 3.2).build(), ExpressionTypeCheckOptions::default()).output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(result, Value::Double(8.2));
}

#[test]
fn test_eval_binary_subtraction() {
    let env = setup_environment();

    // Test: 10 - 4 = 6
    let expr = type_check_expression(
        subtract(10, 4).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(result, Value::Int(6));
}

#[test]
fn test_eval_binary_multiplication() {
    let env = setup_environment();

    // Test: 6 * 7 = 42
    let expr = type_check_expression(
        multiply(6, 7).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(result, Value::Int(42));
}

#[test]
fn test_eval_binary_division() {
    let env = setup_environment();

    // Test: 15 / 3 = 5
    let expr =
        type_check_expression(divide(15, 3).build(), ExpressionTypeCheckOptions::default()).output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(result, Value::Int(5));
}

#[test]
fn test_eval_binary_division_by_zero() {
    let env = setup_environment();

    // Test: 10 / 0 should error
    let expr =
        type_check_expression(divide(10, 0).build(), ExpressionTypeCheckOptions::default()).output;
    let result = eval(&expr, &env);

    assert!(result.is_err());
    assert!(matches!(
        result.unwrap_err(),
        EvalError::ExecutionError { .. }
    ));
}

#[test]
fn test_eval_modulo_operator() {
    let mut ctx = TestContext::default();
    ctx.set("x", Value::Int(10), INT);
    ctx.set("y", Value::Int(3), INT);

    let expr = type_check_expression(
        modulo(field_ref("x"), field_ref("y")).build(),
        ExpressionTypeCheckOptions::builder()
            .bindings(Arc::new(ctx.translation_env.clone()))
            .build(),
    )
    .output;
    let result = eval(&expr, &ctx.env).unwrap();
    assert_eq!(result, Value::Int(1));
}

#[test]
fn test_eval_modulo_division_by_zero() {
    let env = setup_environment();

    // Test: 10 % 0 should error
    let expr =
        type_check_expression(modulo(10, 0).build(), ExpressionTypeCheckOptions::default()).output;
    let result = eval(&expr, &env);

    assert!(result.is_err());
    assert!(matches!(
        result.unwrap_err(),
        EvalError::ExecutionError { .. }
    ));
}

#[test]
fn test_eval_nested_expressions() {
    let env = setup_environment();

    // Test: (5 + 3) * (10 - 2) = 8 * 8 = 64
    let expr = type_check_expression(
        multiply(add(5, 3), subtract(10, 2)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(result, Value::Int(64));
}

#[test]
fn test_eval_string_concatenation() {
    let env = Environment::new();

    let expr = type_check_expression(
        add("hello", " world").build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();
    assert_eq!(result, Value::String("hello world".to_string()));
}

#[test]
fn test_eval_decimal_addition() {
    use hamelin_lib::tree::builder::decimal_from_parts;

    let env = setup_environment();

    // Test: 3.14 + 2.86 = 6.00
    let expr = type_check_expression(
        add(decimal_from_parts(314, 3, 2), decimal_from_parts(286, 3, 2)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(
        result,
        Value::Decimal(DecimalValue {
            unscaled: 600,
            scale: 2
        })
    );
}

#[test]
fn test_eval_decimal_subtraction() {
    use hamelin_lib::tree::builder::decimal_from_parts;

    let env = setup_environment();

    // Test: 5.75 - 2.25 = 3.50
    let expr = type_check_expression(
        subtract(decimal_from_parts(575, 3, 2), decimal_from_parts(225, 3, 2)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(
        result,
        Value::Decimal(DecimalValue {
            unscaled: 350,
            scale: 2
        })
    );
}

#[test]
fn test_eval_decimal_multiplication() {
    use hamelin_lib::tree::builder::decimal_from_parts;

    let env = setup_environment();

    // Test: 2.5 * 3.0 = 7.50
    let expr = type_check_expression(
        multiply(decimal_from_parts(25, 2, 1), decimal_from_parts(30, 2, 1)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(
        result,
        Value::Decimal(DecimalValue {
            unscaled: 750,
            scale: 2
        })
    );
}

#[test]
fn test_eval_decimal_division() {
    use hamelin_lib::tree::builder::decimal_from_parts;

    let env = setup_environment();

    // Test: 10.0 / 2.0 = 5.000000 (with added precision)
    let expr = type_check_expression(
        divide(decimal_from_parts(100, 2, 1), decimal_from_parts(20, 2, 1)).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    // Division adds 6 decimal places for precision, so scale becomes 1 - 1 + 6 = 6
    assert_eq!(
        result,
        Value::Decimal(DecimalValue {
            unscaled: 5000000,
            scale: 6
        })
    );
}

#[test]
fn test_eval_decimal_mixed_with_int() {
    use hamelin_lib::tree::builder::decimal_from_parts;

    let env = setup_environment();

    // Test: 3.14 + 5 = 8.14
    let expr = type_check_expression(
        add(decimal_from_parts(314, 3, 2), 5).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    assert_eq!(
        result,
        Value::Decimal(DecimalValue {
            unscaled: 814,
            scale: 2
        })
    );
}

#[test]
fn test_eval_decimal_mixed_with_double() {
    use hamelin_lib::tree::builder::decimal_from_parts;

    let env = setup_environment();

    // Test: 3.14 + 2.5 = 5.64 (decimal + double = double)
    let expr = type_check_expression(
        add(decimal_from_parts(314, 3, 2), 2.5).build(),
        ExpressionTypeCheckOptions::default(),
    )
    .output;
    let result = eval(&expr, &env).unwrap();

    // Use approximate comparison for floating point
    if let Value::Double(d) = result {
        assert!((d - 5.64).abs() < 1e-10, "Expected ~5.64, got {}", d);
    } else {
        panic!("Expected Double, got {:?}", result);
    }
}