wayver's git archive


an obsidian renderer
git clone https://git.wayver.dev/sable

sable-bases/src/filter/convert.rs@main

raw
Date Commit Message Author Files + -
2026-02-23 01:55 initial mvp wayverd 139 17808 0
...

1use itertools::Itertools as _;
2
3use super::ast::{BinOp, Expr, UnOp};
4
5pub fn ast_to_js(expr: &Expr<'_>) -> String {
6    inner(expr)
7}
8
9// TODO: #[recursive]
10fn inner(expr: &Expr<'_>) -> String {
11    match expr {
12        Expr::Ident(ident) => ident.0.to_string(),
13        Expr::Bool(value) => value.to_string(),
14        Expr::Integer(value) => value.to_string(),
15        Expr::Decimal(value) => format!("{value:?}"),
16        Expr::String(value) => format!("{value:?}"),
17        Expr::Regex(value) => format!("/{value:?}/"),
18        Expr::Function(expr, params) => {
19            let mut expr = inner(&expr.inner);
20
21            if expr == "if" {
22                expr = "__if_expr__".to_string();
23            }
24
25            let params = params
26                .inner
27                .iter()
28                .map(|param| inner(&param.inner))
29                .join(",");
30
31            format!("({expr})({params})")
32        }
33        Expr::Method(expr, name, params) => {
34            let expr = inner(&expr.inner);
35            let params = params
36                .inner
37                .iter()
38                .map(|param| inner(&param.inner))
39                .join(",");
40
41            format!("({expr}).{}({params})", name.0)
42        }
43        Expr::Member(expr, name) => {
44            let expr = inner(&expr.inner);
45
46            format!("({expr}).{}", name.0)
47        }
48        Expr::Index(expr, index) => {
49            let expr = inner(&expr.inner);
50            let index = inner(&index.inner);
51
52            format!("({expr})[{index}]")
53        }
54        Expr::BinOp(op, lhs, rhs) => {
55            let lhs = inner(&lhs.inner);
56            let rhs = inner(&rhs.inner);
57
58            let sym = match op.inner {
59                BinOp::Add => "+",
60                BinOp::Sub => "-",
61                BinOp::Mul => "*",
62                BinOp::Div => "/",
63                BinOp::Mod => "%",
64                BinOp::Equal => "==",
65                BinOp::NotEqual => "!=",
66                BinOp::GreaterThan => ">",
67                BinOp::LessThan => "<",
68                BinOp::GreaterEqual => ">=",
69                BinOp::LessEqual => "<=",
70                BinOp::And => "&&",
71                BinOp::Or => "||",
72            };
73
74            format!("({lhs} {sym} {rhs})")
75        }
76        Expr::UnOp(op, expr) => {
77            let expr = inner(&expr.inner);
78
79            let sym = match op.inner {
80                UnOp::Not => "!",
81                UnOp::Plus => "+",
82                UnOp::Neg => "-",
83            };
84
85            format!("({sym}{expr})")
86        }
87        Expr::Group(expr) => format!("({})", inner(&expr.inner)),
88    }
89}
90
91#[allow(
92    clippy::too_many_lines,
93    clippy::cognitive_complexity,
94    reason = "theses are tests..."
95)]
96#[cfg(test)]
97mod tests {
98    use chumsky::span::{SimpleSpan, Spanned};
99
100    use super::super::ast::{BinOp, Expr, Ident, UnOp};
101
102    use super::ast_to_js;
103
104    fn s<T>(t: T) -> Spanned<T> {
105        Spanned {
106            span: SimpleSpan::from(0..1),
107            inner: t,
108        }
109    }
110
111    #[test]
112    fn simple() {
113        assert_eq!("test", ast_to_js(&Expr::Ident(Ident("test".into()))));
114
115        assert_eq!("true", ast_to_js(&Expr::Bool(true)));
116        assert_eq!("false", ast_to_js(&Expr::Bool(false)));
117
118        assert_eq!("0", ast_to_js(&Expr::Integer(0)));
119        assert_eq!("1", ast_to_js(&Expr::Integer(1)));
120
121        assert_eq!("0.0", ast_to_js(&Expr::Decimal(0.0)));
122        assert_eq!("1.0", ast_to_js(&Expr::Decimal(1.0)));
123        assert_eq!(
124            "1.000000000000001",
125            ast_to_js(&Expr::Decimal(1.000_000_000_000_001))
126        );
127
128        assert_eq!("\"test\\\"s\"", ast_to_js(&Expr::String("test\"s".into())));
129
130        assert_eq!(
131            "(test)()",
132            ast_to_js(&Expr::Function(
133                Box::new(s(Expr::Ident(Ident("test".into())))),
134                s(vec![]),
135            ))
136        );
137        assert_eq!(
138            "(test)(a)",
139            ast_to_js(&Expr::Function(
140                Box::new(s(Expr::Ident(Ident("test".into())))),
141                s(vec![s(Expr::Ident(Ident("a".into())))]),
142            ))
143        );
144        assert_eq!(
145            "(test)(a,b)",
146            ast_to_js(&Expr::Function(
147                Box::new(s(Expr::Ident(Ident("test".into())))),
148                s(vec![
149                    s(Expr::Ident(Ident("a".into()))),
150                    s(Expr::Ident(Ident("b".into()))),
151                ]),
152            ))
153        );
154
155        assert_eq!(
156            "(test).test()",
157            ast_to_js(&Expr::Method(
158                Box::new(s(Expr::Ident(Ident("test".into())))),
159                s(Ident("test".into())),
160                s(vec![]),
161            ))
162        );
163        assert_eq!(
164            "(test).test(a)",
165            ast_to_js(&Expr::Method(
166                Box::new(s(Expr::Ident(Ident("test".into())))),
167                s(Ident("test".into())),
168                s(vec![s(Expr::Ident(Ident("a".into())))]),
169            ))
170        );
171        assert_eq!(
172            "(test).test(a,b)",
173            ast_to_js(&Expr::Method(
174                Box::new(s(Expr::Ident(Ident("test".into())))),
175                s(Ident("test".into())),
176                s(vec![
177                    s(Expr::Ident(Ident("a".into()))),
178                    s(Expr::Ident(Ident("b".into()))),
179                ]),
180            ))
181        );
182
183        assert_eq!(
184            "(test).test",
185            ast_to_js(&Expr::Member(
186                Box::new(s(Expr::Ident(Ident("test".into())))),
187                s(Ident("test".into())),
188            ))
189        );
190
191        assert_eq!(
192            "(test)[1]",
193            ast_to_js(&Expr::Index(
194                Box::new(s(Expr::Ident(Ident("test".into())))),
195                Box::new(s(Expr::Integer(1))),
196            ))
197        );
198
199        assert_eq!(
200            "(a + b)",
201            ast_to_js(&Expr::BinOp(
202                s(BinOp::Add),
203                Box::new(s(Expr::Ident(Ident("a".into())))),
204                Box::new(s(Expr::Ident(Ident("b".into())))),
205            ))
206        );
207        assert_eq!(
208            "(a - b)",
209            ast_to_js(&Expr::BinOp(
210                s(BinOp::Sub),
211                Box::new(s(Expr::Ident(Ident("a".into())))),
212                Box::new(s(Expr::Ident(Ident("b".into())))),
213            ))
214        );
215        assert_eq!(
216            "(a * b)",
217            ast_to_js(&Expr::BinOp(
218                s(BinOp::Mul),
219                Box::new(s(Expr::Ident(Ident("a".into())))),
220                Box::new(s(Expr::Ident(Ident("b".into())))),
221            ))
222        );
223        assert_eq!(
224            "(a / b)",
225            ast_to_js(&Expr::BinOp(
226                s(BinOp::Div),
227                Box::new(s(Expr::Ident(Ident("a".into())))),
228                Box::new(s(Expr::Ident(Ident("b".into())))),
229            ))
230        );
231        assert_eq!(
232            "(a % b)",
233            ast_to_js(&Expr::BinOp(
234                s(BinOp::Mod),
235                Box::new(s(Expr::Ident(Ident("a".into())))),
236                Box::new(s(Expr::Ident(Ident("b".into())))),
237            ))
238        );
239        assert_eq!(
240            "(a == b)",
241            ast_to_js(&Expr::BinOp(
242                s(BinOp::Equal),
243                Box::new(s(Expr::Ident(Ident("a".into())))),
244                Box::new(s(Expr::Ident(Ident("b".into())))),
245            ))
246        );
247        assert_eq!(
248            "(a != b)",
249            ast_to_js(&Expr::BinOp(
250                s(BinOp::NotEqual),
251                Box::new(s(Expr::Ident(Ident("a".into())))),
252                Box::new(s(Expr::Ident(Ident("b".into())))),
253            ))
254        );
255        assert_eq!(
256            "(a > b)",
257            ast_to_js(&Expr::BinOp(
258                s(BinOp::GreaterThan),
259                Box::new(s(Expr::Ident(Ident("a".into())))),
260                Box::new(s(Expr::Ident(Ident("b".into())))),
261            ))
262        );
263        assert_eq!(
264            "(a < b)",
265            ast_to_js(&Expr::BinOp(
266                s(BinOp::LessThan),
267                Box::new(s(Expr::Ident(Ident("a".into())))),
268                Box::new(s(Expr::Ident(Ident("b".into())))),
269            ))
270        );
271        assert_eq!(
272            "(a >= b)",
273            ast_to_js(&Expr::BinOp(
274                s(BinOp::GreaterEqual),
275                Box::new(s(Expr::Ident(Ident("a".into())))),
276                Box::new(s(Expr::Ident(Ident("b".into())))),
277            ))
278        );
279        assert_eq!(
280            "(a <= b)",
281            ast_to_js(&Expr::BinOp(
282                s(BinOp::LessEqual),
283                Box::new(s(Expr::Ident(Ident("a".into())))),
284                Box::new(s(Expr::Ident(Ident("b".into())))),
285            ))
286        );
287        assert_eq!(
288            "(a && b)",
289            ast_to_js(&Expr::BinOp(
290                s(BinOp::And),
291                Box::new(s(Expr::Ident(Ident("a".into())))),
292                Box::new(s(Expr::Ident(Ident("b".into())))),
293            ))
294        );
295        assert_eq!(
296            "(a || b)",
297            ast_to_js(&Expr::BinOp(
298                s(BinOp::Or),
299                Box::new(s(Expr::Ident(Ident("a".into())))),
300                Box::new(s(Expr::Ident(Ident("b".into())))),
301            ))
302        );
303
304        assert_eq!(
305            "(!a)",
306            ast_to_js(&Expr::UnOp(
307                s(UnOp::Not),
308                Box::new(s(Expr::Ident(Ident("a".into())))),
309            ))
310        );
311        assert_eq!(
312            "(+a)",
313            ast_to_js(&Expr::UnOp(
314                s(UnOp::Plus),
315                Box::new(s(Expr::Ident(Ident("a".into())))),
316            ))
317        );
318        assert_eq!(
319            "(-a)",
320            ast_to_js(&Expr::UnOp(
321                s(UnOp::Neg),
322                Box::new(s(Expr::Ident(Ident("a".into())))),
323            ))
324        );
325
326        assert_eq!(
327            "(a)",
328            ast_to_js(&Expr::Group(Box::new(s(Expr::Ident(Ident("a".into())))),))
329        );
330    }
331}
332