Skip to main content

nomiscript/
ast.rs

1use core::fmt;
2
3use num_rational::Ratio;
4
5pub type Fraction = Ratio<i64>;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum WasmType {
9    I32,
10    Ratio,
11    ConsRef,
12    StringRef,
13}
14
15impl 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)]
27pub 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
35impl LambdaParams {
36    pub fn simple(params: Vec<String>) -> Self {
37        Self {
38            required: params,
39            optional: Vec::new(),
40            rest: None,
41            key: Vec::new(),
42            aux: Vec::new(),
43        }
44    }
45}
46
47#[derive(Debug, Clone, PartialEq)]
48pub 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
68impl Expr {
69    #[must_use]
70    pub fn cons(car: Expr, cdr: Expr) -> Self {
71        Expr::Cons(Box::new(car), Box::new(cdr))
72    }
73
74    /// Returns the `WasmType` if this expression is a runtime value (`WasmRuntime` or `WasmLocal`).
75    #[must_use]
76    pub fn wasm_type(&self) -> Option<WasmType> {
77        match self {
78            Self::WasmRuntime(ty) | Self::WasmLocal(_, ty) => Some(*ty),
79            _ => None,
80        }
81    }
82
83    /// True if this is a runtime value (`WasmRuntime` or `WasmLocal`).
84    #[must_use]
85    pub fn is_wasm_runtime(&self) -> bool {
86        matches!(self, Self::WasmRuntime(_) | Self::WasmLocal(_, _))
87    }
88}
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct Annotation {
92    pub name: String,
93    pub value: Expr,
94}
95
96impl Expr {
97    #[must_use]
98    pub fn is_atom(&self) -> bool {
99        !matches!(self, Expr::List(_) | Expr::Cons(_, _))
100    }
101
102    #[must_use]
103    pub fn as_symbol(&self) -> Option<&str> {
104        match self {
105            Expr::Symbol(s) => Some(s),
106            _ => None,
107        }
108    }
109
110    #[must_use]
111    pub fn as_list(&self) -> Option<&[Expr]> {
112        match self {
113            Expr::List(l) => Some(l),
114            _ => None,
115        }
116    }
117}
118
119#[derive(Debug, Clone, Default)]
120pub struct Program {
121    pub exprs: Vec<Expr>,
122    pub annotations: Vec<Annotation>,
123}
124
125impl Program {
126    #[must_use]
127    pub fn new(exprs: Vec<Expr>) -> Self {
128        Self {
129            exprs,
130            annotations: Vec::new(),
131        }
132    }
133
134    #[must_use]
135    pub fn with_annotations(exprs: Vec<Expr>, annotations: Vec<Annotation>) -> Self {
136        Self { exprs, annotations }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_expr_is_atom() {
146        assert!(Expr::Nil.is_atom());
147        assert!(Expr::Bool(true).is_atom());
148        assert!(Expr::Number(Fraction::from_integer(42)).is_atom());
149        assert!(Expr::String("hello".into()).is_atom());
150        assert!(Expr::Symbol("foo".into()).is_atom());
151        assert!(Expr::Keyword("bar".into()).is_atom());
152        assert!(!Expr::List(vec![]).is_atom());
153        assert!(!Expr::cons(Expr::Nil, Expr::Nil).is_atom());
154        assert!(Expr::Lambda(LambdaParams::simple(vec![]), Box::new(Expr::Nil)).is_atom());
155    }
156
157    #[test]
158    fn test_expr_as_symbol() {
159        assert_eq!(Expr::Symbol("foo".into()).as_symbol(), Some("foo"));
160        assert_eq!(Expr::Number(Fraction::from_integer(1)).as_symbol(), None);
161    }
162
163    #[test]
164    fn test_expr_as_list() {
165        let list = Expr::List(vec![Expr::Symbol("a".into())]);
166        assert!(list.as_list().is_some());
167        assert_eq!(list.as_list().unwrap().len(), 1);
168        assert!(Expr::Symbol("a".into()).as_list().is_none());
169    }
170}