1
use num_rational::Ratio;
2

            
3
pub type Fraction = Ratio<i64>;
4

            
5
#[derive(Debug, Clone, PartialEq)]
6
pub struct LambdaParams {
7
    pub required: Vec<String>,
8
    pub optional: Vec<(String, Option<Expr>)>,
9
    pub rest: Option<String>,
10
    pub key: Vec<(String, Option<Expr>)>,
11
    pub aux: Vec<(String, Option<Expr>)>,
12
}
13

            
14
impl LambdaParams {
15
424498
    pub fn simple(params: Vec<String>) -> Self {
16
424498
        Self {
17
424498
            required: params,
18
424498
            optional: Vec::new(),
19
424498
            rest: None,
20
424498
            key: Vec::new(),
21
424498
            aux: Vec::new(),
22
424498
        }
23
424498
    }
24
}
25

            
26
#[derive(Debug, Clone, PartialEq)]
27
pub enum Expr {
28
    Nil,
29
    Bool(bool),
30
    Number(Fraction),
31
    String(String),
32
    Symbol(String),
33
    Keyword(String),
34
    Cons(Box<Expr>, Box<Expr>),
35
    List(Vec<Expr>),
36
    Quote(Box<Expr>),
37
    Quasiquote(Box<Expr>),
38
    Unquote(Box<Expr>),
39
    UnquoteSplicing(Box<Expr>),
40
    Lambda(LambdaParams, Box<Expr>),
41
    RuntimeValue(crate::runtime::Value),
42
}
43

            
44
impl Expr {
45
    #[must_use]
46
12
    pub fn cons(car: Expr, cdr: Expr) -> Self {
47
12
        Expr::Cons(Box::new(car), Box::new(cdr))
48
12
    }
49
}
50

            
51
#[derive(Debug, Clone, PartialEq)]
52
pub struct Annotation {
53
    pub name: String,
54
    pub value: Expr,
55
}
56

            
57
impl Expr {
58
    #[must_use]
59
9
    pub fn is_atom(&self) -> bool {
60
9
        !matches!(self, Expr::List(_) | Expr::Cons(_, _))
61
9
    }
62

            
63
    #[must_use]
64
253319
    pub fn as_symbol(&self) -> Option<&str> {
65
253319
        match self {
66
253046
            Expr::Symbol(s) => Some(s),
67
273
            _ => None,
68
        }
69
253319
    }
70

            
71
    #[must_use]
72
3335
    pub fn as_list(&self) -> Option<&[Expr]> {
73
3335
        match self {
74
3300
            Expr::List(l) => Some(l),
75
35
            _ => None,
76
        }
77
3335
    }
78
}
79

            
80
#[derive(Debug, Clone, Default)]
81
pub struct Program {
82
    pub exprs: Vec<Expr>,
83
    pub annotations: Vec<Annotation>,
84
}
85

            
86
impl Program {
87
    #[must_use]
88
8
    pub fn new(exprs: Vec<Expr>) -> Self {
89
8
        Self {
90
8
            exprs,
91
8
            annotations: Vec::new(),
92
8
        }
93
8
    }
94

            
95
    #[must_use]
96
20986
    pub fn with_annotations(exprs: Vec<Expr>, annotations: Vec<Annotation>) -> Self {
97
20986
        Self { exprs, annotations }
98
20986
    }
99
}
100

            
101
#[cfg(test)]
102
mod tests {
103
    use super::*;
104

            
105
    #[test]
106
1
    fn test_expr_is_atom() {
107
1
        assert!(Expr::Nil.is_atom());
108
1
        assert!(Expr::Bool(true).is_atom());
109
1
        assert!(Expr::Number(Fraction::from_integer(42)).is_atom());
110
1
        assert!(Expr::String("hello".into()).is_atom());
111
1
        assert!(Expr::Symbol("foo".into()).is_atom());
112
1
        assert!(Expr::Keyword("bar".into()).is_atom());
113
1
        assert!(!Expr::List(vec![]).is_atom());
114
1
        assert!(!Expr::cons(Expr::Nil, Expr::Nil).is_atom());
115
1
        assert!(Expr::Lambda(LambdaParams::simple(vec![]), Box::new(Expr::Nil)).is_atom());
116
1
    }
117

            
118
    #[test]
119
1
    fn test_expr_as_symbol() {
120
1
        assert_eq!(Expr::Symbol("foo".into()).as_symbol(), Some("foo"));
121
1
        assert_eq!(Expr::Number(Fraction::from_integer(1)).as_symbol(), None);
122
1
    }
123

            
124
    #[test]
125
1
    fn test_expr_as_list() {
126
1
        let list = Expr::List(vec![Expr::Symbol("a".into())]);
127
1
        assert!(list.as_list().is_some());
128
1
        assert_eq!(list.as_list().unwrap().len(), 1);
129
1
        assert!(Expr::Symbol("a".into()).as_list().is_none());
130
1
    }
131
}