vexity 0.0.4

Tiny scripting language for hacking on abstractions of financial markets.
Documentation
use std::{collections::HashMap, f64::consts::PI};
use vexity::{
    interpreter::{Interpreter, VarValue},
    parser::parse,
};

#[test]
fn test_basic_var_defs() {
    let script = r#"
        let x = 5
        let y = 10.53
        let z = "Hello 🌎"
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let x = interpreter
        .vars
        .get("x")
        .and_then(|value| match value {
            VarValue::Int(i) => Some(*i),
            _ => None,
        })
        .expect("Variable `x` is not in interpreter state");
    assert_eq!(x, 5);

    let y = interpreter
        .vars
        .get("y")
        .and_then(|value| match value {
            VarValue::Float(f) => Some(*f),
            _ => None,
        })
        .expect("Variable `y` is not in interpreter state");
    assert_eq!(y, 10.53);

    let z = interpreter
        .vars
        .get("z")
        .and_then(|value| match value {
            VarValue::String(s) => Some(s),
            _ => None,
        })
        .expect("Variable `z` is not in interpreter state");
    assert_eq!(z.as_str(), "Hello 🌎")
}

#[test]
fn test_basic_array_defs() {
    let script = r#"
        let x = [1, 2, 3, 4, 5]
        let y = [1.5, 2.5, 3.5, 4.5, 5.5]
        let z = ["Some", "array", "of", "strings", "!"]
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let x = interpreter
        .vars
        .get("x")
        .and_then(|value| match value {
            VarValue::Array(a) => Some(
                a.iter()
                    .map(|val| match val {
                        VarValue::Int(i) => *i,
                        _ => panic!("Variable `x` should be an array of integers"),
                    })
                    .collect::<Vec<i32>>(),
            ),
            _ => None,
        })
        .expect("Variable `x` is not in interpreter state");
    assert_eq!(x.len(), 5);
    assert_eq!(x[0], 1);
    assert_eq!(x[1], 2);
    assert_eq!(x[2], 3);
    assert_eq!(x[3], 4);
    assert_eq!(x[4], 5);

    let y = interpreter
        .vars
        .get("y")
        .and_then(|value| match value {
            VarValue::Array(a) => Some(
                a.iter()
                    .map(|val| match val {
                        VarValue::Float(f) => *f,
                        _ => panic!("Variable `y` should be an array of floats"),
                    })
                    .collect::<Vec<f64>>(),
            ),
            _ => None,
        })
        .expect("Variable `y` is not in interpreter state");
    assert_eq!(y.len(), 5);
    assert_eq!(y[0], 1.5);
    assert_eq!(y[1], 2.5);
    assert_eq!(y[2], 3.5);
    assert_eq!(y[3], 4.5);
    assert_eq!(y[4], 5.5);

    let z = interpreter
        .vars
        .get("z")
        .and_then(|value| match value {
            VarValue::Array(a) => Some(
                a.iter()
                    .map(|val| match val {
                        VarValue::String(s) => s.clone(),
                        _ => panic!("Variable `z` should be an array of strings"),
                    })
                    .collect::<Vec<String>>(),
            ),
            _ => None,
        })
        .expect("Variable `z` is not in interpreter state");
    assert_eq!(z.len(), 5);
    assert_eq!(z[0].as_str(), "Some");
    assert_eq!(z[1].as_str(), "array");
    assert_eq!(z[2].as_str(), "of");
    assert_eq!(z[3].as_str(), "strings");
    assert_eq!(z[4].as_str(), "!");
}

#[test]
fn test_basic_hashmap_defs() {
    let script = r#"
        let x = { a: 1, b: 2, c: 3 }
        let y = { pi: 3.141592653589793, e: 2.71 }
        let z = { hello: "world", foo: "bar" }
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let x = interpreter
        .vars
        .get("x")
        .and_then(|value| match value {
            VarValue::HashMap(m) => Some(
                m.iter()
                    .map(|(k, v)| {
                        let i = match v {
                            VarValue::Int(i) => *i,
                            _ => panic!("x[{k}] should be an integer"),
                        };
                        (k.clone(), i)
                    })
                    .collect::<HashMap<String, i32>>(),
            ),
            _ => None,
        })
        .expect("Variable `x` is not a hashmap");

    assert_eq!(x.get("a"), Some(&1));
    assert_eq!(x.get("b"), Some(&2));
    assert_eq!(x.get("c"), Some(&3));

    let y = interpreter
        .vars
        .get("y")
        .and_then(|value| match value {
            VarValue::HashMap(m) => Some(
                m.iter()
                    .map(|(k, v)| {
                        let f = match v {
                            VarValue::Float(f) => *f,
                            _ => panic!("y[{k}] should be a float"),
                        };
                        (k.clone(), f)
                    })
                    .collect::<HashMap<String, f64>>(),
            ),
            _ => None,
        })
        .expect("Variable `y` is not a hashmap");

    assert!((y.get("pi").unwrap() - PI).abs() < 1e-6);
    assert!((y.get("e").unwrap() - 2.71).abs() < 1e-6);

    let z = interpreter
        .vars
        .get("z")
        .and_then(|value| match value {
            VarValue::HashMap(m) => Some(
                m.iter()
                    .map(|(k, v)| {
                        let s = match v {
                            VarValue::String(s) => s.clone(),
                            _ => panic!("z[{k}] should be a string"),
                        };
                        (k.clone(), s)
                    })
                    .collect::<HashMap<String, String>>(),
            ),
            _ => None,
        })
        .expect("Variable `z` is not a hashmap");

    assert_eq!(z.get("hello").map(String::as_str), Some("world"));
    assert_eq!(z.get("foo").map(String::as_str), Some("bar"));
}

#[test]
fn test_basic_var_bin_ops() {
    let script = r#"
        let a = 100
        let b = 1
        let a_minus_b = a - b

        let j = 50
        let k = 2
        let j_divided_by_k = j / k
        let j_multiplied_by_k = j * k

        let x = 5
        let y = 5
        let x_plus_y = x + y
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let a_minus_b = interpreter
        .vars
        .get("a_minus_b")
        .and_then(|value| match value {
            VarValue::Int(i) => Some(*i),
            _ => None,
        })
        .expect("Variable `a_minues_b` not in interpreter state");
    assert_eq!(a_minus_b, 99);

    let x_plus_y = interpreter
        .vars
        .get("x_plus_y")
        .and_then(|value| match value {
            VarValue::Int(i) => Some(*i),
            _ => None,
        })
        .expect("Variable `x_plus_y` not in interpreter state");
    assert_eq!(x_plus_y, 10);

    let j_over_k = interpreter
        .vars
        .get("j_divided_by_k")
        .and_then(|value| match value {
            VarValue::Int(i) => Some(*i),
            _ => None,
        })
        .expect("Variable `j_divided_by_k` not in interpreter state");
    assert_eq!(j_over_k, 25);

    let jk = interpreter
        .vars
        .get("j_multiplied_by_k")
        .and_then(|value| match value {
            VarValue::Int(i) => Some(*i),
            _ => None,
        })
        .expect("Variable `j_multiplied_by_k` not in interpreter state");
    assert_eq!(jk, 100)
}

#[test]
fn test_basic_function_def() {
    let script = r#"
        fn add_one(x) {
            x + 1
        }

        let x = 5
        let x_plus_one = add_one(x)
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let x_plus_one = interpreter
        .vars
        .get("x_plus_one")
        .and_then(|value| match value {
            VarValue::Int(i) => Some(*i),
            _ => None,
        })
        .expect("Variable `x_plus_one` is not in interpreter state");
    assert_eq!(x_plus_one, 6);
}

#[test]
fn test_basic_map_def() {
    let script = r#"
        let values = [1, 2, 3, 4, 5]
        let values_squared = values.map(|x| x * x)
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let values_squared = interpreter
        .vars
        .get("values_squared")
        .and_then(|value| match value {
            VarValue::Array(a) => Some(
                a.iter()
                    .map(|value| match value {
                        VarValue::Int(i) => *i,
                        _ => panic!("Variable `x_squared` should be an array of integers"),
                    })
                    .collect::<Vec<i32>>(),
            ),
            _ => None,
        })
        .expect("Variable `x_squared` is not in interpreter state");
    assert_eq!(values_squared.len(), 5);
    assert_eq!(values_squared[0], 1);
    assert_eq!(values_squared[1], 4);
    assert_eq!(values_squared[2], 9);
    assert_eq!(values_squared[3], 16);
    assert_eq!(values_squared[4], 25);
}

#[test]
fn test_array_length() {
    let script = r#"
        let a = [10, 20, 30]
        let n = a.length
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let len = interpreter
        .vars
        .get("n")
        .and_then(|value| match value {
            VarValue::Int(i) => Some(*i),
            _ => None,
        })
        .expect("Variable `n` is not in interpreter state");

    assert_eq!(len, 3);
}

#[test]
fn test_basic_range_def() {
    let script = r#"
        let squares = (0..5).map(|i| i * i)
    "#;

    let statements = parse(script).expect("Parsing failed");
    let mut interpreter = Interpreter::new();
    interpreter.run(statements);

    let squares = interpreter
        .vars
        .get("squares")
        .and_then(|value| match value {
            VarValue::Array(arr) => Some(
                arr.iter()
                    .map(|v| match v {
                        VarValue::Int(i) => *i,
                        _ => panic!("Expected array of ints in `squares`"),
                    })
                    .collect::<Vec<i32>>(),
            ),
            _ => None,
        })
        .expect("Variable `squares` is not in interpreter state");

    assert_eq!(squares, vec![0, 1, 4, 9, 16]);
}