callisp/
ast.rs

1//! The AST module contains structs and enums for the abstract syntax tree.
2
3use crate::env::Environment;
4use crate::error::LispError;
5use crate::eval;
6use dyn_clone::DynClone;
7
8use std::fmt::{Debug, Display};
9
10/// Stores an expression.
11#[derive(Debug, Clone)]
12pub enum Ast {
13    /// An atom, such as a number, string, or symbol.
14    Atom(LispAtom),
15
16    /// A list created using ().
17    List(Vec<Ast>),
18
19    /// A callable function.
20    Function(Box<dyn LispCallable>),
21
22    /// Basically a none type.
23    Unspecified,
24}
25
26impl PartialEq for Ast {
27    fn eq(&self, other: &Self) -> bool {
28        match self {
29            Ast::Atom(val) => match other {
30                Ast::Atom(other) => val == other,
31                _ => false,
32            },
33            Ast::List(items) => match other {
34                Ast::List(other) => items == other,
35                _ => false,
36            },
37            // TODO: Maybe two functions are equal if they have the same body?
38            Ast::Function(_) => false,
39            Ast::Unspecified => false,
40        }
41    }
42}
43
44impl Display for Ast {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            Self::Atom(atom) => write!(f, "{}", atom),
48            Self::List(list) => {
49                write!(f, "(")?;
50
51                // Display a space-separated list of inner items
52                let mut list = list.iter();
53                if let Some(ast) = list.next() {
54                    write!(f, "{}", ast)?;
55
56                    for ast in list {
57                        write!(f, " {}", ast)?;
58                    }
59                }
60
61                write!(f, ")")
62            }
63            Self::Function(_) => write!(f, "<function>"),
64            Self::Unspecified => Ok(()), // unspecified doesn't display anything
65        }
66    }
67}
68
69/// Lisp atom.
70#[derive(Clone, Debug, PartialEq)]
71pub enum LispAtom {
72    /// A lisp symbol.
73    Symbol(String),
74
75    /// A lisp string. Stored as a Rust string which means it can store any unicode character.
76    String(String),
77
78    /// A lisp boolean.
79    Bool(bool),
80
81    /// A lisp number. Currently this is a double precision float.
82    Number(f64),
83}
84
85impl Display for LispAtom {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            Self::Symbol(symbol) => write!(f, "{}", symbol),
89            Self::String(s) => write!(f, "\"{}\"", s),
90            Self::Number(num) => write!(f, "{}", num),
91            Self::Bool(b) => write!(f, "{}", b),
92        }
93    }
94}
95
96impl Clone for Box<dyn LispCallable> {
97    fn clone(&self) -> Self {
98        dyn_clone::clone_box(&**self)
99    }
100}
101
102/// A struct representing the possible values of arity a function can have. Arity is just the
103/// number of arguments a function takes.
104#[derive(Debug, Clone)]
105pub enum FunctionArity {
106    /// The function can be called with a number of arguments greater than or equal to the
107    /// contained value.
108    AtLeast(usize),
109
110    /// The function can be called with only the specified amount of arguments.
111    Exactly(usize),
112
113    /// The function can be called with any of the amounts of arguments stored in the vector.
114    Multi(Vec<usize>),
115}
116
117impl FunctionArity {
118    /// Check is the number of arguments passed to a function matches that functions arity.
119    pub fn check_arity(&self, num_args: usize) -> Result<(), LispError> {
120        match self {
121            Self::AtLeast(num_params) => {
122                if num_args >= *num_params {
123                    Ok(())
124                } else {
125                    Err(LispError::Type)
126                }
127            }
128            Self::Exactly(num_params) => {
129                if num_args == *num_params {
130                    Ok(())
131                } else {
132                    Err(LispError::Type)
133                }
134            }
135            Self::Multi(options) => {
136                if options.contains(&num_args) {
137                    Ok(())
138                } else {
139                    Err(LispError::Type)
140                }
141            }
142        }
143    }
144}
145
146/// Trait used to define Lisp functions.
147pub trait LispCallable: Debug + DynClone {
148    /// Return the arity of the function.
149    fn arity(&self) -> &FunctionArity;
150
151    /// Call the function and return the result.
152    fn call(&self, args: Vec<Ast>, env: &mut Environment) -> Result<Ast, LispError>;
153}
154
155/// Function created using `lambda`.
156#[derive(Debug, Clone)]
157pub struct LispLambda {
158    arity: FunctionArity,
159    bindings: Vec<String>,
160    body: Ast,
161}
162
163impl LispLambda {
164    /// Create a new lambda function with specified arity, bindings, and body.
165    pub fn new(arity: FunctionArity, bindings: Vec<String>, body: Ast) -> Self {
166        Self {
167            arity,
168            bindings,
169            body,
170        }
171    }
172}
173
174impl LispCallable for LispLambda {
175    fn arity(&self) -> &FunctionArity {
176        &self.arity
177    }
178
179    fn call(&self, args: Vec<Ast>, env: &mut Environment) -> Result<Ast, LispError> {
180        // Create bindings
181        env.new_scope(self.bindings.iter().cloned().zip(args).collect());
182
183        // Evaluate in new environment
184        let res = eval::eval_expr(self.body.clone(), env);
185
186        env.pop_scope();
187
188        res
189    }
190}