use core::fmt;
use std::{any::{type_name, self}, mem};
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()
}
}
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()
}
}