avid 0.6.1

A plug-and-play scripting language
Documentation
use core::fmt;
use std::{any::{type_name, self}, mem};

// If you are using rust-analyzer, this `unresolved-import`
// error is because `ObjectType` is generated via a macro.
// There is no error on the final build.
use crate::{
    ast::{SendCallable, SendFunc},
    stack::Stack,
    typing::{Object, ObjectType}, ErrorKind,
};

#[doc(hidden)]
pub trait AvidFunc {
    fn call(&mut self, stack: &mut Stack) -> crate::Result<(), ErrorKind>;

    fn name(&self) -> String;
}

#[doc(hidden)]
pub enum Callable<'b, 'f> {
    Builtin(Builtin),
    Unknown {
        name: Option<String>,
        func: Box<dyn AvidFunc + 'f>,
    },
    UnknownSend {
        name: Option<String>,
        func: Box<dyn SendFunc + 'f>,
    },
    Borrowed(&'b mut SendCallable<'f>),
}

impl<'b, 'f, T: FnMut(&mut Stack) -> Result<(), ErrorKind> + 'f> From<T> for Callable<'b, 'f> {
    fn from(f: T) -> Self {
        Self::Unknown {
            name: Some(any::type_name::<T>().to_string()),
            func: Box::new(f) as Box<dyn AvidFunc + 'f>
        }
    }
}

impl<'b, 'f> From<Builtin> for Callable<'b, 'f> {
    fn from(b: Builtin) -> Self {
        Self::Builtin(b)
    }
}

impl fmt::Debug for Callable<'_, '_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Builtin(arg0) => f.debug_tuple("Builtin").field(arg0).finish(),
            Self::Unknown { name, .. } => f
                .debug_tuple("Unknown")
                .field(&name.as_ref().map(String::as_str).unwrap_or("<func>"))
                .finish(),
            Self::UnknownSend { name, .. } => f
                .debug_tuple("Unknown")
                .field(&name.as_ref().map(String::as_str).unwrap_or("<func>"))
                .finish(),
            Self::Borrowed(b) => write!(f, "{b:?}"),
        }
    }
}

impl AvidFunc for Callable<'_, '_> {
    fn call(&mut self, stack: &mut Stack) -> crate::Result<(), ErrorKind> {
        match self {
            Callable::Builtin(b) => b.call(stack),
            Callable::Unknown { name: _, func } => func.call(stack),
            Callable::UnknownSend { name: _, func } => func.call(stack),
            Callable::Borrowed(b) => b.call(stack),
        }
    }

    fn name(&self) -> String {
        match self {
            Callable::Builtin(b) => b.name(),
            Callable::Unknown { name, func } => name.clone().unwrap_or_else(|| func.name()),
            Callable::UnknownSend { name, func } => name.clone().unwrap_or_else(|| func.name()),
            Callable::Borrowed(b) => b.name(),
        }
    }
}

impl<T> AvidFunc for T
where
    T: FnMut(&mut Stack) -> crate::Result<(), ErrorKind>,
{
    fn call(&mut self, stack: &mut Stack) -> crate::Result<(), ErrorKind> {
        self(stack)
    }

    fn name(&self) -> String {
        type_name::<Self>().to_string()
    }
}

// All of the built in operations
enum_type! {
    Builtin {
        Print,
        StackDebug,
        Sum,
        Subtract,
        Drop,
        Swap,
        Rot,
        Dup,
        NewList,
        AppendList,
        GetFromList,
        Eq,
        NotEq,
        Not,
        PushInt(isize),
        PushStr(String)
    }
}

impl AvidFunc for Builtin {
    fn call(&mut self, stack: &mut Stack) -> crate::Result<(), ErrorKind> {
        match self {
            Builtin::StackDebug => {
                let mut s = Stack::new();
                mem::swap(&mut s, stack);
                panic!(
                    "{:?}",
                    s.into_objects()
                        .into_iter()
                        .map(|x| x.get_type())
                        .collect::<Vec<_>>()
                );
            }
            Builtin::GetFromList => {
                let [list, idx] = stack.pop_typed(&[ObjectType::List, ObjectType::Num])?;
                let list = list.unwrap_list();
                let item = list[idx.unwrap_num() as usize].clone();
                stack.push(Object::List(list));
                stack.push(item);
            }
            Builtin::Dup => {
                let [top] = stack.pop()?;
                stack.push(top.clone());
                stack.push(top);
            }
            Builtin::Drop => {
                stack.pop::<1>()?;
            }
            Builtin::Swap => {
                let [arg1, arg2] = stack.pop()?;
                stack.push(arg2);
                stack.push(arg1);
            }
            Builtin::NewList => stack.push(Object::List(Vec::new())),
            Builtin::AppendList => {
                let [to_app] = stack.pop()?;
                let [list] = stack.pop_typed(&[ObjectType::List])?;

                let mut list = list.unwrap_list();

                list.push(to_app);

                stack.push(Object::List(list));
            }
            Builtin::Print => print!("{}", stack.pop::<1>()?[0]),
            Builtin::Sum => {
                let [arg1, arg2] = stack.pop_typed(&[ObjectType::Num, ObjectType::Num])?;

                stack.push(Object::Num(arg1.unwrap_num() + arg2.unwrap_num()));
            }
            Builtin::Subtract => {
                let [arg1, arg2] = stack.pop_typed(&[ObjectType::Num, ObjectType::Num])?;

                stack.push(Object::Num(arg1.unwrap_num() - arg2.unwrap_num()));
            }
            Builtin::PushInt(i) => stack.push(Object::Num(*i)),
            Builtin::PushStr(s) => stack.push(Object::String(s.clone())),
            Builtin::Eq => {
                let [item1, item2] = stack.pop()?;
                stack.push(Object::Bool(item1 == item2))
            }
            Builtin::NotEq => {
                let [item1, item2] = stack.pop()?;
                stack.push(Object::Bool(item1 != item2))
            }
            Builtin::Not => {
                let [arg] = stack.pop()?;
                let arg = !arg.truthy();
                stack.push(Object::Bool(arg));
            }
            Builtin::Rot => {
                let [arg1, arg2, arg3] = stack.pop()?;

                stack.push(arg3);
                stack.push(arg1);
                stack.push(arg2);
            }
        }
        Ok(())
    }

    fn name(&self) -> String {
        let n = match self {
            Builtin::Print => "print",
            Builtin::Sum => "+",
            Builtin::Subtract => "-",
            Builtin::PushInt(_) => "<pushint>",
            Builtin::PushStr(_) => "<pushstr>",
            Builtin::Drop => "drop",
            Builtin::Swap => "swap",
            Builtin::Dup => "dup",
            Builtin::NewList => "newlist",
            Builtin::AppendList => "appendlist",
            Builtin::GetFromList => "get",
            Builtin::Eq => "eq",
            Builtin::NotEq => "noteq",
            Builtin::Not => "not",
            Builtin::Rot => "rot",
            Builtin::StackDebug => "???",
        };

        n.to_string()
    }
}