use std::{cmp::PartialEq, convert::TryFrom, fmt}; use crate::app::AppState; use crate::lisp::{ error::{EvalError, LispError}, eval::lookup_extended, number::LispNumber, EnvList, }; #[derive(Debug, Copy, PartialEq, Clone)] pub enum Arity { Exact(usize), Atleast(usize), Atmost(usize), Range(usize, usize), None, } impl Arity { pub fn is_valid(self, args: &[T]) -> bool { 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, } } 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, } impl PrimitiveFunc { pub fn call(&self, args: &[LispExpr], app: &mut AppState) -> Result { if !self.arity.is_valid(args) { return Err(EvalError::ArgumentCount(self.arity).into()); } (self.closure)(args, app) } } pub type Ident = String; pub type BoolLit = bool; #[derive(Clone)] pub struct LispFunction { pub params: Vec, pub body: Vec, } #[derive(Clone)] pub enum LispExpr { Unit, Number(LispNumber), List(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: &EnvList) -> 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::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_extended(envs, s)?; let o = lookup_extended(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()), } } } 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) => { for expr in l.iter() { write!(f, " {} ", expr)? } } 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), 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) => 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<'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), _ => 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)), } } } pub fn is_ident>(expr: E) -> bool { matches!(expr.as_ref(), LispExpr::Ident(_)) } pub fn is_number>(expr: E) -> bool { matches!(expr.as_ref(), LispExpr::Number(_)) }