bubbles-dialogue 0.7.0

Lightweight engine-agnostic dialogue runtime for Rust games.
Documentation
use super::*;
use crate::compiler::expr::parse_expr_at;
use crate::value::HashMapStorage;

fn no_fns(_: &str, _: Vec<Value>) -> Result<Value> {
    Err(DialogueError::Function {
        name: "<none>".into(),
        message: "no functions registered".into(),
    })
}

fn ev(src: &str) -> Value {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at(src, "<test>", 0).unwrap();
    eval(&expr, &storage, &no_fns).unwrap()
}

#[test]
fn eval_addition() {
    assert_eq!(ev("1 + 2"), Value::Number(3.0));
}

#[test]
fn eval_precedence() {
    assert_eq!(ev("1 + 2 * 3"), Value::Number(7.0));
}

#[test]
fn eval_parentheses() {
    assert_eq!(ev("(1 + 2) * 3"), Value::Number(9.0));
}

#[test]
fn eval_comparison() {
    assert_eq!(ev("3 > 2"), Value::Bool(true));
    assert_eq!(ev("1 >= 2"), Value::Bool(false));
}

#[test]
fn eval_logical_and_short_circuit() {
    assert_eq!(ev("false && true"), Value::Bool(false));
    assert_eq!(ev("true && true"), Value::Bool(true));
}

#[test]
fn eval_logical_or_short_circuit() {
    assert_eq!(ev("true || false"), Value::Bool(true));
    assert_eq!(ev("false || false"), Value::Bool(false));
}

#[test]
fn eval_string_concat() {
    assert_eq!(
        ev(r#""hello" + " world""#),
        Value::Text("hello world".into())
    );
}

#[test]
fn eval_unary_neg() {
    assert_eq!(ev("-3"), Value::Number(-3.0));
}

#[test]
fn eval_unary_not() {
    assert_eq!(ev("!false"), Value::Bool(true));
}

#[test]
fn negate_non_number_errors() {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at(r#"-"hello""#, "<test>", 0).unwrap();
    let err = eval(&expr, &storage, &no_fns).unwrap_err();
    assert!(
        matches!(err, DialogueError::TypeMismatch { ref context, .. } if context.contains('-')),
        "got {err}"
    );
}

#[test]
fn subtract_non_numbers_errors() {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at(r#""a" - 1"#, "<test>", 0).unwrap();
    let err = eval(&expr, &storage, &no_fns).unwrap_err();
    assert!(
        matches!(err, DialogueError::TypeMismatch { ref context, .. } if context.contains('-')),
        "got {err}"
    );
}

#[test]
fn compare_non_numbers_errors() {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at(r#""a" < 1"#, "<test>", 0).unwrap();
    let err = eval(&expr, &storage, &no_fns).unwrap_err();
    assert!(
        matches!(err, DialogueError::TypeMismatch { ref context, .. } if context.contains('<')),
        "got {err}"
    );
}

#[test]
fn modulo_by_zero_errors() {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at("5 % 0", "<test>", 0).unwrap();
    let err = eval(&expr, &storage, &no_fns).unwrap_err();
    assert!(err.to_string().contains("modulo"), "got {err}");
}

#[test]
fn multiply_non_numbers_errors() {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at(r#""x" * 2"#, "<test>", 0).unwrap();
    assert!(eval(&expr, &storage, &no_fns).is_err());
}

#[test]
fn comparison_operators_on_numbers() {
    assert_eq!(ev("1 != 2"), Value::Bool(true));
    assert_eq!(ev("2 != 2"), Value::Bool(false));
    assert_eq!(ev("1 < 2"), Value::Bool(true));
    assert_eq!(ev("2 <= 2"), Value::Bool(true));
    assert_eq!(ev("3 > 2"), Value::Bool(true));
    assert_eq!(ev("3 >= 4"), Value::Bool(false));
}

#[test]
fn div_and_rem_on_numbers() {
    assert_eq!(ev("9 / 2"), Value::Number(4.5));
    assert_eq!(ev("7 % 3"), Value::Number(1.0));
}

#[test]
fn add_number_and_bool_is_type_error() {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at("1 + true", "<test>", 0).unwrap();
    assert!(eval(&expr, &storage, &no_fns).is_err());
}

#[test]
fn bool_equality() {
    assert_eq!(ev("true == true"), Value::Bool(true));
    assert_eq!(ev("true == false"), Value::Bool(false));
}

#[test]
fn text_equality_and_inequality() {
    assert_eq!(ev(r#""a" == "a""#), Value::Bool(true));
    assert_eq!(ev(r#""a" != "b""#), Value::Bool(true));
}

#[test]
fn rem_requires_numbers() {
    let storage = HashMapStorage::new();
    let expr = parse_expr_at(r#""x" % 2"#, "<test>", 0).unwrap();
    assert!(eval(&expr, &storage, &no_fns).is_err());
}