locrian 0.2.2

A simple embeddable functional programming language.
Documentation
use crate::{
    eval::{EvalFn, EvalResult, eval},
    parser::ExprValue,
    stdlib::{func::func, quote::call},
};

pub(crate) fn map(args: Vec<EvalResult>) -> EvalResult {
    if let Some(EvalResult::Array(arr)) = args.get(0)
        && let Some(EvalResult::Quoted(block, ctx)) = args.get(1)
    {
        EvalResult::Array(
            arr.iter()
                .enumerate()
                .map(|(idx, it)| {
                    eval(
                        block.as_ref().clone(),
                        ctx.with_var("$it", it.clone().into())
                            .with_var("$i", ExprValue::Number(idx as f64)),
                    )
                })
                .collect::<Result<Vec<_>, _>>()
                .unwrap_or(vec![]),
        )
    } else {
        EvalResult::None
    }
}

pub(crate) fn reduce(args: Vec<EvalResult>) -> EvalResult {
    if let Some(EvalResult::Array(arr)) = args.get(0)
        && let Some(quoted) = args.get(1)
    {
        arr.iter()
            .map(|v| v.clone())
            .reduce(|a, b| call(vec![quoted.clone(), EvalResult::Array(vec![a, b])]))
            .unwrap_or(EvalResult::None)
    } else {
        EvalResult::None
    }
}

pub(crate) static append: EvalFn = func!(
    r#"
    array:merge($0, [$1])
"#
);

pub(crate) fn merge(args: Vec<EvalResult>) -> EvalResult {
    if let Some(EvalResult::Array(arr)) = args.get(0)
        && let Some(EvalResult::Array(arr2)) = args.get(1)
    {
        let mut new_arr = arr.clone();
        let mut new_arr2 = arr2.clone();
        new_arr.append(&mut new_arr2);
        EvalResult::Array(new_arr)
    } else {
        EvalResult::None
    }
}

pub(crate) fn at(args: Vec<EvalResult>) -> EvalResult {
    if let Some(EvalResult::Array(arr)) = args.get(0)
        && let Some(EvalResult::Number(idx)) = args.get(1)
    {
        arr.get(*idx as usize).unwrap_or(&EvalResult::None).clone()
    } else {
        EvalResult::None
    }
}

pub(crate) fn length(args: Vec<EvalResult>) -> EvalResult {
    if let Some(EvalResult::Array(arr)) = args.get(0) {
        EvalResult::Number(arr.len() as f64)
    } else {
        EvalResult::None
    }
}

pub(crate) fn slice(args: Vec<EvalResult>) -> EvalResult {
    if let Some(EvalResult::Array(arr)) = args.get(0)
        && let Some(EvalResult::Number(start)) = args.get(1)
        && let Some(EvalResult::Number(end)) = args.get(2)
    {
        EvalResult::Array(
            arr[(*start as usize)..(*end as usize)]
                .iter()
                .cloned()
                .collect(),
        )
    } else {
        EvalResult::None
    }
}

pub(crate) static head: EvalFn = func!(
    r#"
        array:at($0, 0)
    "#
);

pub(crate) static tail: EvalFn = func!(
    r#"
        use(["array"], 'slice($0, 1, length($0)))
    "#
);

pub(crate) static last: EvalFn = func!(
    r#"
        use(["array", "num"], 'at($0, sub(length($0), 1)))
    "#
);

pub(crate) static filter: EvalFn = func!(
    r#"
        let({"arr": $0, "predicate": $1}, 'use(["array", "bool"],
            'reduce(arr,
                'if(predicate($1),
                    'append($0, $1),
                    '$0)))
    "#
);

pub(crate) static find: EvalFn = func!(
    r#"
        use(["array"], 'head(filter($0, $1)))
    "#
);

#[cfg(test)]
mod tests {
    use crate::{eval::EvalResult, eval_str, stdlib::STDLIB};

    #[test]
    fn test_map() {
        assert_eq!(
            eval_str(
                r#"use(["array", "string", "num"], 'concat(...map([2, 4, 6], 'mul($it, 2))))"#,
                STDLIB.clone()
            )
            .unwrap(),
            EvalResult::String("4812".to_string())
        );
    }

    #[test]
    fn test_at() {
        assert_eq!(
            eval_str(r#"use(["array"], 'at([2, 4, 6], 1))"#, STDLIB.clone()).unwrap(),
            EvalResult::Number(4 as f64)
        );
    }

    #[test]
    fn test_length() {
        assert_eq!(
            eval_str(r#"use(["array"], 'length([2, 4, 6]))"#, STDLIB.clone()).unwrap(),
            EvalResult::Number(3 as f64)
        );
    }
    #[test]
    fn test_head() {
        assert_eq!(
            eval_str(r#"use(["array"], 'head([2, 4, 6]))"#, STDLIB.clone()).unwrap(),
            EvalResult::Number(2 as f64)
        );
    }

    #[test]
    fn test_slice() {
        assert_eq!(
            eval_str(r#"use(["array"], 'slice([2, 4, 6], 1, 3))"#, STDLIB.clone()).unwrap(),
            EvalResult::Array(vec![
                EvalResult::Number(4 as f64),
                EvalResult::Number(6 as f64)
            ])
        );
    }

    #[test]
    fn test_tail() {
        assert_eq!(
            eval_str(r#"use(["array"], 'tail([2, 4, 6]))"#, STDLIB.clone()).unwrap(),
            EvalResult::Array(vec![
                EvalResult::Number(4 as f64),
                EvalResult::Number(6 as f64)
            ])
        );
    }
    #[test]
    fn test_last() {
        assert_eq!(
            eval_str(r#"use(["array"], 'last([2, 4, 6]))"#, STDLIB.clone()).unwrap(),
            EvalResult::Number(6 as f64)
        );
    }
}