tan/
optimize.rs

1use std::collections::HashMap;
2
3use crate::expr::{format_value, Expr};
4
5// #insight The optimizer does not err.
6
7// #insight
8// The optimizer converts general Expr::List expressions into execution-friendly
9// expressions like Expr::Array, Expr::Map, etc. It also strips unnecessary
10// annotations.
11
12// #todo What does optimize do? I think it just removes some annotations.
13
14// #todo #think hm, we NEED the annotations, especially in let expressions!
15
16// #todo combine in one pass with e.g. check?
17
18pub fn optimize_fn(expr: Expr) -> Expr {
19    // #todo let annotations are lost here.
20    match expr.unpack() {
21        Expr::List(ref terms) => {
22            if !terms.is_empty() {
23                if let Expr::Symbol(s) = &terms[0].unpack() {
24                    if s == "Array" {
25                        // #todo we loose support for (Array ...)
26                        let items: Vec<Expr> =
27                            terms[1..].iter().map(|ax| ax.unpack().clone()).collect();
28                        return Expr::maybe_annotated(Expr::array(items), expr.annotations());
29                    } else if s == "Map" {
30                        // #todo we loose support for (Map ...)
31                        let items: Vec<Expr> =
32                            terms[1..].iter().map(|ax| ax.unpack().clone()).collect();
33                        let mut map = HashMap::new();
34                        for pair in items.chunks(2) {
35                            let mut k = format_value(&pair[0]);
36                            let v = pair[1].clone();
37                            // #insight
38                            // Map key inference:
39                            // (let name "George" role :admin)
40                            // (let user {_ name _ role})
41                            // user ; => {:name "George" :role :admin}
42                            // #todo should move to another place.
43                            // #todo move inference to parser?
44                            // #insight here it handles both {...} and (Map ...)
45                            if k == "_" {
46                                if let Expr::Symbol(sym) = &v {
47                                    k.clone_from(sym);
48                                }
49                                // #todo report error/warning if we cannot infere!
50                            }
51                            map.insert(k, v);
52                        }
53                        return Expr::maybe_annotated(Expr::map(map), expr.annotations());
54                    }
55                }
56            }
57            // #insight no annotations stripped.
58            expr
59        }
60        _ => expr,
61    }
62}
63
64pub fn optimize(expr: Expr) -> Expr {
65    expr.transform(&optimize_fn)
66}
67
68#[cfg(test)]
69mod tests {
70    use crate::{api::parse_string, optimize::optimize};
71
72    #[test]
73    fn optimize_rewrites_array_expressions() {
74        let input = r#"(do (let a [1 2 3 4]) (writeln (+ 2 3)))"#;
75
76        let expr = parse_string(input).unwrap();
77
78        let expr_optimized = optimize(expr);
79
80        let s = format!("{expr_optimized:?}");
81
82        // #todo This is a _very_ unstable/error-prone check.
83        assert!(s.contains("Array([Int(1), Int(2), Int(3), Int(4)])"));
84    }
85
86    // #todo the test is flaky for some reason, temporarily disabled, investigate.
87    // #[test]
88    // fn optimize_rewrites_map_expressions() {
89    //     let input = r#"(let a {:name "George" :age 25})"#;
90
91    //     let expr = parse_string(input).unwrap();
92
93    //     let expr_optimized = optimize(expr);
94
95    //     let s = format!("{expr_optimized:?}");
96
97    //     assert!(s.contains(r#"Map({"name": String("George"), "age": Int(25)})"#));
98    // }
99}