lispi 0.3.3

Lisp I interpreter
Documentation
use crate::interpreter::eval;
use crate::{
    elementary_functions::*,
    list_macros::compose_car_cdr,
    types::{List, NullableList, SExpression},
};

pub(crate) fn atom_fn(e_list: List, a: &NullableList) -> Option<SExpression> {
    match compose_car_cdr("cadr", &e_list) {
        Some(arg) => Some(atom(eval(arg, a)?.0).into()),
        None => {
            log::error!("ATOM requires an argument.");
            None
        }
    }
}

pub(crate) fn car_fn(e_list: List, a: &NullableList) -> Option<SExpression> {
    match eval(
        compose_car_cdr("cadr", &e_list).or_else(|| {
            log::error!("CAR requires a list as argument.");
            None
        })?,
        a,
    )?
    .0
    {
        SExpression::List(list) => Some(car(list)),
        SExpression::Atom(argument) => {
            log::error!(
                "CAR requires an argument to be a list, but was: {}",
                argument
            );
            None
        }
    }
}

pub(crate) fn cdr_fn(e_list: List, a: &NullableList) -> Option<SExpression> {
    match eval(
        compose_car_cdr("cadr", &e_list).or_else(|| {
            log::error!("CDR requires a list as argument.");
            None
        })?,
        a,
    )?
    .0
    {
        SExpression::List(list) => Some(cdr(list)),
        SExpression::Atom(argument) => {
            log::error!(
                "CDR requires an argument to be a list, but was: {}",
                argument
            );
            None
        }
    }
}

pub(crate) fn car_cdr_composition(s: &str, e_list: List, a: &NullableList) -> Option<SExpression> {
    match eval(
        compose_car_cdr("cadr", &e_list).or_else(|| {
            log::error!("{s} requires a list as argument.");
            None
        })?,
        a,
    )?
    .0
    {
        SExpression::List(list) => compose_car_cdr(s, &list),
        SExpression::Atom(argument) => {
            log::error!(
                "{s} requires an argument to be a list, but was: {}",
                argument
            );
            None
        }
    }
}

pub(crate) fn cons_fn(e_list: List, a: &NullableList) -> Option<SExpression> {
    match cdr(e_list.clone()) {
        SExpression::List(arguments) => Some(
            cons(
                eval(car(arguments.clone()), a)?.0,
                eval(
                    compose_car_cdr("cadr", &arguments).or_else(|| {
                        log::error!(
                            "CONS requires two arguments, but only one was provided: {}",
                            e_list
                        );
                        None
                    })?,
                    a,
                )?
                .0,
            )
            .into(),
        ),
        SExpression::Atom(_argument) => {
            log::error!(
                "Invalid use of CONS: {}, two arguments are required.",
                e_list
            );
            None
        }
    }
}

pub(crate) fn eq_fn(e_list: List, a: &NullableList) -> Option<SExpression> {
    match cdr(e_list.clone()) {
        SExpression::List(arguments) => {
            let x = eval(car(arguments.clone()), a)?.0;
            let y = eval(
                compose_car_cdr("cadr", &arguments).or_else(|| {
                    log::error!(
                        "EQ requires two arguments, but only one was provided: {}",
                        e_list
                    );
                    None
                })?,
                a,
            )?
            .0;
            if let SExpression::Atom(ax) = x {
                if let SExpression::Atom(ay) = y {
                    Some(eq(ax, ay).into())
                } else {
                    log::error!(
                        "EQ requires its arguments to be atoms, use EQUAL to compare lists: {}",
                        e_list
                    );
                    None
                }
            } else {
                log::error!(
                    "EQ requires its arguments to be atoms, use EQUAL to compare lists: {}",
                    e_list
                );
                None
            }
        }
        SExpression::Atom(_) => {
            log::error!("Invalid use of EQ: {} two arguments are required.", e_list);
            None
        }
    }
}

#[test]
fn test_atom_fn() {
    use crate::list;
    use crate::types::{NIL, T};
    assert_eq!(
        atom_fn(
            list![ElementaryFunction::ATOM, list![SpecialForm::QUOTE, "x"]],
            &NIL.into()
        ),
        Some(T.into())
    );
    assert_eq!(atom_fn(list![ElementaryFunction::ATOM], &NIL.into()), None);
}

#[test]
fn test_car_fn() {
    use crate::list;
    use crate::types::NIL;
    assert_eq!(
        car_fn(
            list![
                ElementaryFunction::CAR,
                list![SpecialForm::QUOTE, list!["A", "B", "C"]]
            ],
            &NIL.into(),
        ),
        Some("A".into())
    );
    assert_eq!(
        car_fn(
            list![ElementaryFunction::CAR, list![SpecialForm::QUOTE, "A"]],
            &NIL.into(),
        ),
        None
    );
    assert_eq!(car_fn(list![ElementaryFunction::CAR], &NIL.into(),), None);
}

#[test]
fn test_cdr_fn() {
    use crate::list;
    use crate::types::NIL;
    assert_eq!(
        cdr_fn(
            list![
                ElementaryFunction::CDR,
                list![SpecialForm::QUOTE, list!["A", "B", "C"]]
            ],
            &NIL.into(),
        ),
        Some(list!["B", "C"].into())
    );
    assert_eq!(
        cdr_fn(
            list![
                ElementaryFunction::CDR,
                list![SpecialForm::QUOTE, list!["A", "B"]]
            ],
            &NIL.into(),
        ),
        Some(list!["B"].into())
    );
    assert_eq!(
        cdr_fn(list![ElementaryFunction::CDR, "A"], &NIL.into()),
        None
    );
    assert_eq!(cdr_fn(list![ElementaryFunction::CDR], &NIL.into()), None);
}

#[test]
fn test_car_cdr_composition() {
    use crate::list;
    use crate::types::NIL;
    assert_eq!(
        car_cdr_composition(
            "CAAR",
            list![
                ElementaryFunction::CarCdrComposition("CAAR".to_string()),
                list![
                    ElementaryFunction::CONS,
                    list![ElementaryFunction::CONS, 1, 2],
                    list![ElementaryFunction::CONS, 3, 4]
                ]
            ],
            &NIL.into()
        ),
        Some(1.into())
    );
    assert_eq!(
        car_cdr_composition(
            "CDAR",
            list![
                ElementaryFunction::CarCdrComposition("CDAR".to_string()),
                list![
                    ElementaryFunction::CONS,
                    list![ElementaryFunction::CONS, 1, 2],
                    list![ElementaryFunction::CONS, 3, 4]
                ]
            ],
            &NIL.into()
        ),
        Some(2.into())
    );
    assert_eq!(
        car_cdr_composition(
            "CADR",
            list![
                ElementaryFunction::CarCdrComposition("CADR".to_string()),
                list![
                    ElementaryFunction::CONS,
                    list![ElementaryFunction::CONS, 1, 2],
                    list![ElementaryFunction::CONS, 3, 4]
                ]
            ],
            &NIL.into()
        ),
        Some(3.into())
    );
    assert_eq!(
        car_cdr_composition(
            "CDDR",
            list![
                ElementaryFunction::CarCdrComposition("CDDR".to_string()),
                list![
                    ElementaryFunction::CONS,
                    list![ElementaryFunction::CONS, 1, 2],
                    list![ElementaryFunction::CONS, 3, 4]
                ]
            ],
            &NIL.into()
        ),
        Some(4.into())
    );
}

#[test]
fn test_cons_fn() {
    use crate::list;
    use crate::types::NIL;
    assert_eq!(
        cons_fn(list![ElementaryFunction::CONS, "A", "B"], &NIL.into()),
        Some(cons("A", "B").into())
    );
    assert_eq!(
        cons_fn(list![ElementaryFunction::CONS, "A"], &NIL.into()),
        None
    );
    assert_eq!(cons_fn(list![ElementaryFunction::CONS], &NIL.into()), None);
}

#[test]
fn test_eq_fn() {
    use crate::list;
    use crate::types::{F, NIL, T};
    assert_eq!(
        eq_fn(list![ElementaryFunction::EQ, "A", "A"], &NIL.into()),
        Some(T.into())
    );
    assert_eq!(
        eq_fn(list![ElementaryFunction::EQ, "A", "B"], &NIL.into()),
        Some(F.into())
    );
    assert_eq!(eq_fn(list![ElementaryFunction::EQ, "A"], &NIL.into()), None);
    assert_eq!(eq_fn(list![ElementaryFunction::EQ], &NIL.into()), None);
}