use std::{ cmp::PartialEq, convert::{From, TryFrom}, fmt, }; use crate::{ app::AppState, bitmap::Axis, guide::Guide, lisp::{ error::{EvalError, LispError}, eval::lookup, number::LispNumber, Environment, }, }; #[derive(Debug, Copy, PartialEq, Clone)] pub enum Arity { Exact(usize), Atleast(usize), Atmost(usize), Range(usize, usize), None, } impl Arity { pub fn check(self, args: &[T]) -> Result<(), LispError> { if !match self { Arity::Exact(a) => args.len() == a, Arity::Atleast(a) => args.len() >= a, Arity::Atmost(a) => args.len() <= a, Arity::Range(low, high) => args.len() >= low && args.len() <= high, Arity::None => true, } { Err(self.to_error()) } else { Ok(()) } } pub fn to_error(self) -> LispError { LispError::Eval(EvalError::ArgumentCount(self)) } } #[derive(Clone)] pub struct PrimitiveFunc { pub arity: Arity, // minimim arity pub closure: fn(&[LispExpr], &mut AppState) -> Result, pub name: &'static str, } impl PrimitiveFunc { pub fn call(&self, args: &[LispExpr], app: &mut AppState) -> Result { self.arity.check(args)?; (self.closure)(args, app) } } pub type Ident = String; pub type BoolLit = bool; #[derive(Clone)] pub struct LispFunction { pub params: Vec, pub body: Box, } #[derive(Clone)] pub enum LispExpr { Unit, Number(LispNumber), List(Vec), DottedList(Vec), StringLit(String), BoolLit(bool), Ident(Ident), PrimitiveFunc(PrimitiveFunc), Function(LispFunction), Char(char), // none of these depths should be zero Quasiquote(Box, u32), Comma(Box, u32), CommaAt(Box, u32), Quote(Box, u32), } impl LispExpr { pub fn comma(self, n: u32) -> LispExpr { match self { LispExpr::Comma(v, i) => LispExpr::Comma(v, i.checked_add(n).expect("comma overflow")), LispExpr::CommaAt(v, i) => LispExpr::CommaAt(v, i + n), v => LispExpr::Comma(Box::new(v), n), } } pub fn comma_at(self, n: u32) -> LispExpr { match self { LispExpr::CommaAt(v, i) => { LispExpr::CommaAt(v, i.checked_add(n).expect("comma_at overflow")) } v => LispExpr::CommaAt(Box::new(v), n), } } pub fn quote(self, n: u32) -> LispExpr { match self { LispExpr::Quote(v, i) => LispExpr::Quote(v, i.checked_add(n).expect("quote overflow")), v => LispExpr::Quote(Box::new(v), n), } } pub fn quasiquote(self, n: u32) -> LispExpr { match self { LispExpr::Quasiquote(v, i) => { LispExpr::Quasiquote(v, i.checked_add(n).expect("quasiquote overflow")) } v => LispExpr::Quasiquote(Box::new(v), n), } } pub fn compare(&self, other: &Self, envs: &[Environment]) -> Result { match (self, other) { (LispExpr::Unit, LispExpr::Unit) => Ok(true), (LispExpr::Number(s), LispExpr::Number(o)) => Ok(s == o), (LispExpr::List(s), LispExpr::List(o)) => Ok(s .iter() .zip(o) .all(|(a, b)| matches!(a.compare(b, envs), Ok(true)))), (LispExpr::List(s), LispExpr::Unit) => Ok(s.is_empty()), (LispExpr::Unit, LispExpr::List(s)) => Ok(s.is_empty()), (LispExpr::StringLit(s), LispExpr::StringLit(o)) => Ok(s == o), (LispExpr::Char(s), LispExpr::Char(o)) => Ok(s == o), (LispExpr::BoolLit(s), LispExpr::BoolLit(o)) => Ok(s == o), (LispExpr::Ident(s), LispExpr::Ident(o)) => { let s = lookup(envs, s)?; let o = lookup(envs, o)?; s.compare(&o, envs) } (LispExpr::PrimitiveFunc(s), LispExpr::PrimitiveFunc(o)) => { let PrimitiveFunc { closure: s, .. } = s; let PrimitiveFunc { closure: o, .. } = o; Ok(*s as usize == *o as usize) } (LispExpr::Function(_), LispExpr::Function(_)) => Err(EvalError::BadForm.into()), (LispExpr::Quasiquote(s, sd), LispExpr::Quasiquote(o, od)) => { Ok(s.compare(o, envs)? && sd == od) } (LispExpr::Comma(s, sd), LispExpr::Comma(o, od)) => Ok(s.compare(o, envs)? && sd == od), (LispExpr::CommaAt(s, sd), LispExpr::CommaAt(o, od)) => { Ok(s.compare(o, envs)? && sd == od) } (LispExpr::Quote(s, sd), LispExpr::Quote(o, od)) => Ok(s.compare(o, envs)? && sd == od), _ => Err(EvalError::TypeMismatch.into()), } } // have these be code gen'd somehow pub fn unwrap_number(&self) -> LispNumber { match &self { LispExpr::Number(p) => *p, _ => panic!("attempt to call `unwrap_number` on invalid type"), } } pub fn unwrap_list(&self) -> Vec { match &self { LispExpr::List(p) => p.clone(), _ => panic!("attempt to call `unwrap_list` on invalid type"), } } pub fn unwrap_dotted_list(&self) -> Vec { match &self { LispExpr::DottedList(p) => p.clone(), _ => panic!("attempt to call `unwrap_dotted_list` on invalid type"), } } pub fn unwrap_stringlit(&self) -> String { match &self { LispExpr::StringLit(p) => p.clone(), _ => panic!("attempt to call `unwrap_stringlit` on invalid type"), } } pub fn unwrap_ident(&self) -> String { match &self { LispExpr::Ident(p) => p.clone(), _ => panic!("attempt to call `unwrap_ident` on invalid type"), } } pub fn unwrap_primitive_func(&self) -> PrimitiveFunc { match &self { LispExpr::PrimitiveFunc(p) => p.clone(), _ => panic!("attempt to call `unwrap_primitive_func` on invalid type"), } } pub fn unwrap_function(&self) -> LispFunction { match &self { LispExpr::Function(p) => p.clone(), _ => panic!("attempt to call `unwrap_function` on invalid type"), } } pub fn cast_bool(&self) -> bool { !matches!(self, LispExpr::BoolLit(false)) } } impl fmt::Display for LispExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LispExpr::Unit => write!(f, "()")?, LispExpr::Number(n) => write!(f, "{}", n)?, LispExpr::List(l) => { write!( f, "({})", &l.iter().map(|expr| format!("{}", expr)).collect::>()[..].join(" ") )?; } LispExpr::DottedList(l) => { write!(f, "(")?; for item in &l[..l.len() - 1] { write!(f, "{} ", item)?; } write!(f, ". {})", &l[l.len() - 1])?; } LispExpr::StringLit(s) => write!(f, "{:?}", s)?, LispExpr::Char(c) => write!(f, "{}", c)?, LispExpr::BoolLit(b) => { if *b { write!(f, "#t")? } else { write!(f, "#f")? } } LispExpr::Ident(s) => write!(f, "{}", s)?, LispExpr::PrimitiveFunc(_) => write!(f, "<#primitive>")?, LispExpr::Function(func) => write!(f, "<#lambda {}>", func.params.join(" "))?, LispExpr::Quasiquote(val, depth) => { write!(f, "{}{}", "`".repeat(*depth as usize), val)? } LispExpr::Comma(val, depth) => write!(f, "{}{}", ",".repeat(*depth as usize), val)?, LispExpr::CommaAt(val, depth) => write!(f, "{}@{}", ",".repeat(*depth as usize), val)?, LispExpr::Quote(val, depth) => write!(f, "{}{}", "'".repeat(*depth as usize - 1), val)?, }; Ok(()) } } impl fmt::Debug for LispExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LispExpr::Unit => f.debug_tuple("Unit").finish(), LispExpr::Number(n) => write!(f, "Number({:?})", n), LispExpr::List(l) => { write!(f, "List ")?; f.debug_list().entries(l.iter()).finish() } LispExpr::DottedList(l) => { write!(f, "DottedList ")?; f.debug_list().entries(l.iter()).finish() } LispExpr::StringLit(s) => write!(f, "String({:?})", s), LispExpr::Char(c) => write!(f, "Char({:?})", c), LispExpr::BoolLit(b) => write!(f, "Bool({:?})", b), LispExpr::Ident(s) => write!(f, "Ident({})", s), LispExpr::PrimitiveFunc(_) => write!(f, "Primitive"), LispExpr::Function(func) => write!(f, "<#lambda> {}", func.params.join(" ")), LispExpr::Quasiquote(val, depth) => write!(f, "{}{}", "`".repeat(*depth as usize), val), LispExpr::Comma(val, depth) => write!(f, "{}{}", ",".repeat(*depth as usize), val), LispExpr::CommaAt(val, depth) => write!(f, "{}@{}", ",".repeat(*depth as usize), val), LispExpr::Quote(val, depth) => write!(f, "{}{}", "'".repeat(*depth as usize), val), } } } impl PartialEq for LispExpr { fn eq(&self, other: &Self) -> bool { match (self, other) { (LispExpr::Unit, LispExpr::Unit) => true, (LispExpr::Number(s), LispExpr::Number(o)) => s == o, (LispExpr::List(s), LispExpr::List(o)) => s.iter().zip(o).all(|(a, b)| a == b), (LispExpr::StringLit(s), LispExpr::StringLit(o)) => s == o, (LispExpr::Char(s), LispExpr::Char(o)) => s == o, (LispExpr::BoolLit(s), LispExpr::BoolLit(o)) => s == o, (LispExpr::Ident(s), LispExpr::Ident(o)) => s == o, (LispExpr::PrimitiveFunc(_), LispExpr::PrimitiveFunc(_)) => false, (LispExpr::Function(_), LispExpr::Function(_)) => false, (LispExpr::Quasiquote(s, sd), LispExpr::Quasiquote(o, od)) => (s, sd) == (o, od), (LispExpr::Comma(s, sd), LispExpr::Comma(o, od)) => (s, sd) == (o, od), (LispExpr::CommaAt(s, sd), LispExpr::CommaAt(o, od)) => (s, sd) == (o, od), (LispExpr::Quote(s, sd), LispExpr::Quote(o, od)) => (s, sd) == (o, od), _ => false, } } } impl TryFrom for LispNumber { type Error = LispError; fn try_from(value: LispExpr) -> Result { match value { LispExpr::Number(i) => Ok(i), _ => Err(LispError::Eval(EvalError::TypeMismatch)), } } } impl From for LispExpr { fn from(num: i64) -> Self { LispExpr::Number(num.into()) } } impl<'a> TryFrom<&'a LispExpr> for &'a LispNumber { type Error = LispError; fn try_from(value: &'a LispExpr) -> Result { match value { LispExpr::Number(i) => Ok(i), _ => Err(LispError::Eval(EvalError::TypeMismatch)), } } } impl TryFrom for String { type Error = LispError; fn try_from(value: LispExpr) -> Result { match value { LispExpr::StringLit(i) => Ok(i), LispExpr::Ident(i) => Ok(i), _ => Err(LispError::Eval(EvalError::TypeMismatch)), } } } impl AsRef for LispExpr { fn as_ref(&self) -> &str { match self { LispExpr::StringLit(i) => &i, LispExpr::Ident(i) => &i, _ => panic!("invalid downcast!"), } } } impl TryFrom for BoolLit { type Error = LispError; fn try_from(value: LispExpr) -> Result { match value { LispExpr::BoolLit(i) => Ok(i), _ => Err(LispError::Eval(EvalError::TypeMismatch)), } } } // conversion implementations impl From for LispExpr { fn from(axis: Axis) -> LispExpr { LispExpr::Quote( Box::new(LispExpr::Ident(match axis { Axis::X => "X".into(), Axis::Y => "Y".into(), })), 1, ) } } impl From for LispExpr { fn from(guide: Guide) -> LispExpr { LispExpr::List(vec![ guide.axis.into(), LispExpr::Number(LispNumber::Integer(guide.offset as i64)), ]) } }