1
use core::fmt;
2

            
3
use num_rational::Ratio;
4

            
5
pub type Fraction = Ratio<i64>;
6

            
7
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8
pub enum WasmType {
9
    I32,
10
    Ratio,
11
    ConsRef,
12
    StringRef,
13
}
14

            
15
impl fmt::Display for WasmType {
16
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17
        match self {
18
            Self::I32 => write!(f, "i32"),
19
            Self::Ratio => write!(f, "ratio"),
20
            Self::ConsRef => write!(f, "cons"),
21
            Self::StringRef => write!(f, "string"),
22
        }
23
    }
24
}
25

            
26
#[derive(Debug, Clone, PartialEq)]
27
pub struct LambdaParams {
28
    pub required: Vec<String>,
29
    pub optional: Vec<(String, Option<Expr>)>,
30
    pub rest: Option<String>,
31
    pub key: Vec<(String, Option<Expr>)>,
32
    pub aux: Vec<(String, Option<Expr>)>,
33
}
34

            
35
impl LambdaParams {
36
997918
    pub fn simple(params: Vec<String>) -> Self {
37
997918
        Self {
38
997918
            required: params,
39
997918
            optional: Vec::new(),
40
997918
            rest: None,
41
997918
            key: Vec::new(),
42
997918
            aux: Vec::new(),
43
997918
        }
44
997918
    }
45
}
46

            
47
#[derive(Debug, Clone, PartialEq)]
48
pub enum Expr {
49
    Nil,
50
    Bool(bool),
51
    Number(Fraction),
52
    String(String),
53
    Symbol(String),
54
    Keyword(String),
55
    Cons(Box<Expr>, Box<Expr>),
56
    List(Vec<Expr>),
57
    Quote(Box<Expr>),
58
    Quasiquote(Box<Expr>),
59
    Unquote(Box<Expr>),
60
    UnquoteSplicing(Box<Expr>),
61
    Lambda(LambdaParams, Box<Expr>),
62
    RuntimeValue(crate::runtime::Value),
63
    WasmRuntime(WasmType),
64
    /// Value stored in WASM local variable (index, type)
65
    WasmLocal(u32, WasmType),
66
}
67

            
68
impl Expr {
69
    #[must_use]
70
12
    pub fn cons(car: Expr, cdr: Expr) -> Self {
71
12
        Expr::Cons(Box::new(car), Box::new(cdr))
72
12
    }
73

            
74
    /// Returns the `WasmType` if this expression is a runtime value (`WasmRuntime` or `WasmLocal`).
75
    #[must_use]
76
28386
    pub fn wasm_type(&self) -> Option<WasmType> {
77
28386
        match self {
78
26670
            Self::WasmRuntime(ty) | Self::WasmLocal(_, ty) => Some(*ty),
79
1716
            _ => None,
80
        }
81
28386
    }
82

            
83
    /// True if this is a runtime value (`WasmRuntime` or `WasmLocal`).
84
    #[must_use]
85
18410
    pub fn is_wasm_runtime(&self) -> bool {
86
18410
        matches!(self, Self::WasmRuntime(_) | Self::WasmLocal(_, _))
87
18410
    }
88
}
89

            
90
#[derive(Debug, Clone, PartialEq)]
91
pub struct Annotation {
92
    pub name: String,
93
    pub value: Expr,
94
}
95

            
96
impl Expr {
97
    #[must_use]
98
9
    pub fn is_atom(&self) -> bool {
99
9
        !matches!(self, Expr::List(_) | Expr::Cons(_, _))
100
9
    }
101

            
102
    #[must_use]
103
575233
    pub fn as_symbol(&self) -> Option<&str> {
104
575233
        match self {
105
574880
            Expr::Symbol(s) => Some(s),
106
353
            _ => None,
107
        }
108
575233
    }
109

            
110
    #[must_use]
111
10467
    pub fn as_list(&self) -> Option<&[Expr]> {
112
10467
        match self {
113
10422
            Expr::List(l) => Some(l),
114
45
            _ => None,
115
        }
116
10467
    }
117
}
118

            
119
#[derive(Debug, Clone, Default)]
120
pub struct Program {
121
    pub exprs: Vec<Expr>,
122
    pub annotations: Vec<Annotation>,
123
}
124

            
125
impl Program {
126
    #[must_use]
127
8
    pub fn new(exprs: Vec<Expr>) -> Self {
128
8
        Self {
129
8
            exprs,
130
8
            annotations: Vec::new(),
131
8
        }
132
8
    }
133

            
134
    #[must_use]
135
32076
    pub fn with_annotations(exprs: Vec<Expr>, annotations: Vec<Annotation>) -> Self {
136
32076
        Self { exprs, annotations }
137
32076
    }
138
}
139

            
140
#[cfg(test)]
141
mod tests {
142
    use super::*;
143

            
144
    #[test]
145
1
    fn test_expr_is_atom() {
146
1
        assert!(Expr::Nil.is_atom());
147
1
        assert!(Expr::Bool(true).is_atom());
148
1
        assert!(Expr::Number(Fraction::from_integer(42)).is_atom());
149
1
        assert!(Expr::String("hello".into()).is_atom());
150
1
        assert!(Expr::Symbol("foo".into()).is_atom());
151
1
        assert!(Expr::Keyword("bar".into()).is_atom());
152
1
        assert!(!Expr::List(vec![]).is_atom());
153
1
        assert!(!Expr::cons(Expr::Nil, Expr::Nil).is_atom());
154
1
        assert!(Expr::Lambda(LambdaParams::simple(vec![]), Box::new(Expr::Nil)).is_atom());
155
1
    }
156

            
157
    #[test]
158
1
    fn test_expr_as_symbol() {
159
1
        assert_eq!(Expr::Symbol("foo".into()).as_symbol(), Some("foo"));
160
1
        assert_eq!(Expr::Number(Fraction::from_integer(1)).as_symbol(), None);
161
1
    }
162

            
163
    #[test]
164
1
    fn test_expr_as_list() {
165
1
        let list = Expr::List(vec![Expr::Symbol("a".into())]);
166
1
        assert!(list.as_list().is_some());
167
1
        assert_eq!(list.as_list().unwrap().len(), 1);
168
1
        assert!(Expr::Symbol("a".into()).as_list().is_none());
169
1
    }
170
}