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 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 #[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 #[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}