lispi 0.3.3

Lisp I interpreter
Documentation
use std::borrow::Cow;
use std::borrow::Cow::*;

use crate::{
    elementary_functions::{car, cdr, cons},
    list_functions::assoc_v,
    types::{Atom, NullableList, SExpression, Symbol},
};

use super::special_form_glue::*;

// /// The universal lisp function AKA the interpreter:
// /// applies a function f to a list of arguments x
// pub fn apply(f: SExpression, x: List) -> Option<SExpression> {
//     eval(cons(f, appq(x.into())).into(),&NIL.into())
// }

// /// applies QUOTE to each symbol in an expression
// pub(crate) fn appq(m: SExpression) -> SExpression {
//     if null(m.clone()) {
//         return NIL.into();
//     }
//     match m {
//         SExpression::List(l) => cons(appq(car(l.clone())), appq(cdr(l))).into(),
//         SExpression::Atom(Atom::Symbol(Symbol::ElementaryFunction(f))) => f.into(),
//         SExpression::Atom(a) => cons(SpecialForm::QUOTE, a).into(),
//     }
// }

pub static mut TRACKLIST: Vec<Atom> = Vec::new();

/// evaluates an expression using association list `a` and returns the S-expression
/// and the new association list if `e` was evaluated succesfully
pub fn eval(e: SExpression, a: &NullableList) -> Option<(SExpression, Cow<'_, NullableList>)> {
    match e {
        SExpression::Atom(e_atom) => match a.clone() {
            NullableList::List(a_list) => assoc_v(e_atom.clone(), a_list).map_or_else(
                || Some((e_atom.into(), Borrowed(a))),
                |e| Some((e, Borrowed(a))),
            ),
            NullableList::NIL => Some((e_atom.into(), Borrowed(a))),
        },
        SExpression::List(e_list) => match car(e_list.clone()) {
            SExpression::Atom(function) => {
                // accessing mutable statics is unsafe because it can cause undefined behaviour in mult-threaded applications
                // however since this entire project is single-threaded i consider this to be safe :)
                unsafe {
                    if TRACKLIST.contains(&function) {
                        log::info!("ENTERING {e_list}");
                    }
                }
                match function.clone() {
                    Atom::Symbol(Symbol::SpecialForm(s)) => s.eval(e_list.clone(), a),
                    Atom::Symbol(Symbol::ElementaryFunction(f)) => {
                        f.eval(e_list.clone(), a).map(|(e, a)| (e, Borrowed(a)))
                    }
                    Atom::Symbol(Symbol::BuiltinFunc(f)) => {
                        f.eval(e_list.clone(), a).map(|(e, a)| (e, Borrowed(a)))
                    }
                    Atom::Symbol(Symbol::Other(s)) => {
                        handle_other_symbol(s, e_list.clone(), a).map(|e| (e, Borrowed(a)))
                    }
                    _ => {
                        log::error!("Tried to use {} like a function", e_list);
                        None
                    }
                }
                .map(|v| {
                    unsafe {
                        if TRACKLIST.contains(&function) {
                            log::info!("END OF {e_list}, VALUE IS\n{}", v.0);
                        }
                    }
                    v
                })
            }
            SExpression::List(compound_func) => match car(compound_func.clone()) {
                SExpression::Atom(Atom::Symbol(Symbol::SpecialForm(sf))) => sf.eval(e_list, a),
                SExpression::Atom(Atom::Number(_)) => {
                    log::error!("Tried to use a number like a special form: {}", e_list);
                    None
                }
                _ => {
                    let f = car(compound_func.clone());
                    let nested_func = eval(f.into(), a)?.0;
                    let nested_func_args = cdr(compound_func);
                    let (func, new_a) = eval(cons(nested_func, nested_func_args).into(), a)?;
                    let val = eval(cons(func, cdr(e_list)).into(), &new_a)?.0;
                    Some((val, Borrowed(a)))
                }
            },
        },
    }
}

// #[test]
// fn test_appq() {
//     use crate::list_macros::list;
//     assert_eq!(appq("A".into()), cons(SpecialForm::QUOTE, "A").into());
//     assert_eq!(appq(NIL.into()), NIL.into());
//     assert_eq!(
//         appq(ElementaryFunction::ATOM.into()),
//         ElementaryFunction::ATOM.into()
//     );
//     assert_eq!(
//         appq(list!["A", "B", "C"].into()),
//         list![
//             cons(SpecialForm::QUOTE, "A"),
//             cons(SpecialForm::QUOTE, "B"),
//             cons(SpecialForm::QUOTE, "C")
//         ]
//         .into()
//     );
//     assert_eq!(
//         appq(list!["A", list!["B", "C"]].into()),
//         list![
//             cons(SpecialForm::QUOTE, "A"),
//             list![cons(SpecialForm::QUOTE, "B"), cons(SpecialForm::QUOTE, "C")]
//         ]
//         .into()
//     );
// }

#[cfg(test)]
mod elementary_functions_tests {
    use crate::elementary_functions::cons;
    use crate::list_macros::list;
    use crate::types::{F, NIL, T};

    use super::*;

    #[test]
    fn test_eval_car() {
        // (car '(A B)) => A
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::CAR,
                    list![SpecialForm::QUOTE, list!["A", "B"]]
                ]
                .into(),
                &NIL.into()
            ),
            Some(("A".into(), Borrowed(&NIL.into())))
        )
    }
    #[test]
    fn test_eval_cdr() {
        // (cdr '(A B)) => '(B)
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::CDR,
                    list![SpecialForm::QUOTE, list!["A", "B"]]
                ]
                .into(),
                &NIL.into()
            ),
            Some((list!["B"].into(), Borrowed(&NIL.into())))
        )
    }
    #[test]
    fn test_eval_cons() {
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::CONS,
                    list![SpecialForm::QUOTE, "A"],
                    list![SpecialForm::QUOTE, "B"]
                ]
                .into(),
                &NIL.into()
            ),
            Some((cons("A", "B").into(), Borrowed(&NIL.into())))
        );
        // (cons (cons 'A 'B) 'C) => (('A 'B) 'C)
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::CONS,
                    list![SpecialForm::QUOTE, list!["A", "B"]],
                    list![SpecialForm::QUOTE, "C"]
                ]
                .into(),
                &NIL.into()
            ),
            Some((cons(list!["A", "B"], "C").into(), Borrowed(&NIL.into())))
        );
    }
    #[test]
    fn test_eval_eq() {
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::EQ,
                    list![SpecialForm::QUOTE, "x"],
                    list![SpecialForm::QUOTE, "x"]
                ]
                .into(),
                &NIL.into()
            ),
            Some((T.into(), Borrowed(&NIL.into())))
        );
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::EQ,
                    list![SpecialForm::QUOTE, "x"],
                    list![SpecialForm::QUOTE, "y"]
                ]
                .into(),
                &NIL.into()
            ),
            Some((F.into(), Borrowed(&NIL.into())))
        );
        let a: NullableList = list![cons("x", 1), cons("y", 1)].into();
        assert_eq!(
            eval(list![ElementaryFunction::EQ, "x", "y"].into(), &a),
            Some((T.into(), Borrowed(&a)))
        );
    }
    #[test]
    fn test_eval_atom() {
        assert_eq!(
            eval(list![ElementaryFunction::ATOM, "x"].into(), &NIL.into()),
            Some((T.into(), Borrowed(&NIL.into())))
        );
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::ATOM,
                    list![SpecialForm::QUOTE, list![1, 2, 3]]
                ]
                .into(),
                &NIL.into()
            ),
            Some((F.into(), Borrowed(&NIL.into())))
        );
        let a: NullableList = list![cons("x", list![1, 2, 3])].into();
        assert_eq!(
            eval(list![ElementaryFunction::ATOM, "x"].into(), &a),
            Some((F.into(), Borrowed(&a)))
        );
    }
    #[test]
    fn test_eval_car_of_cons() {
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::CAR,
                    list![ElementaryFunction::CONS, 1, 2]
                ]
                .into(),
                &NIL.into()
            ),
            Some((1.into(), Borrowed(&NIL.into())))
        );
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::CAR,
                    list![
                        ElementaryFunction::CONS,
                        list![SpecialForm::QUOTE, "A"],
                        list![SpecialForm::QUOTE, "B"]
                    ]
                ]
                .into(),
                &NIL.into()
            ),
            Some(("A".into(), Borrowed(&NIL.into())))
        );
        assert_eq!(
            eval(
                list![
                    ElementaryFunction::CAR,
                    list![
                        ElementaryFunction::CONS,
                        list![
                            ElementaryFunction::CONS,
                            list![SpecialForm::QUOTE, "A"],
                            list![SpecialForm::QUOTE, "B"]
                        ],
                        list![SpecialForm::QUOTE, "C"]
                    ]
                ]
                .into(),
                &NIL.into()
            ),
            Some((cons("A", "B").into(), Borrowed(&NIL.into())))
        );
    }
}

#[cfg(test)]
mod invalid_sexps_tests {
    use super::*;
    use crate::list;
    use crate::types::NIL;

    #[test]
    fn test_atom_as_func() {
        assert_eq!(eval(list![list![1]].into(), &NIL.into()), None);
        assert_eq!(eval(list![list![T]].into(), &NIL.into()), None);
        assert_eq!(eval(list![list![NIL]].into(), &NIL.into()), None);
        assert_eq!(eval(list![list![1, 2]].into(), &NIL.into()), None);
    }
}