Skip to main content

sim_codec/implementation/
lowering.rs

1//! Structure-preserving expression lowering helpers.
2
3use sim_kernel::Expr;
4
5/// Lower infix, prefix, and postfix operator nodes into explicit call nodes.
6///
7/// Container structure is preserved: list, vector, map, set, and block nodes
8/// remain the same kind of node while their children are lowered recursively.
9/// Quoted payloads are intentionally left unchanged so quotation remains an
10/// opaque expression boundary.
11pub fn lower_operator_nodes(expr: Expr) -> Expr {
12    match expr {
13        Expr::Infix {
14            operator,
15            left,
16            right,
17        } => Expr::Call {
18            operator: Box::new(Expr::Symbol(operator)),
19            args: vec![lower_operator_nodes(*left), lower_operator_nodes(*right)],
20        },
21        Expr::Prefix { operator, arg } | Expr::Postfix { operator, arg } => Expr::Call {
22            operator: Box::new(Expr::Symbol(operator)),
23            args: vec![lower_operator_nodes(*arg)],
24        },
25        Expr::Call { operator, args } => Expr::Call {
26            operator: Box::new(lower_operator_nodes(*operator)),
27            args: args.into_iter().map(lower_operator_nodes).collect(),
28        },
29        Expr::List(items) => Expr::List(items.into_iter().map(lower_operator_nodes).collect()),
30        Expr::Vector(items) => Expr::Vector(items.into_iter().map(lower_operator_nodes).collect()),
31        Expr::Map(entries) => Expr::Map(
32            entries
33                .into_iter()
34                .map(|(key, value)| (lower_operator_nodes(key), lower_operator_nodes(value)))
35                .collect(),
36        ),
37        Expr::Set(items) => Expr::Set(items.into_iter().map(lower_operator_nodes).collect()),
38        Expr::Block(items) => Expr::Block(items.into_iter().map(lower_operator_nodes).collect()),
39        Expr::Annotated { expr, annotations } => Expr::Annotated {
40            expr: Box::new(lower_operator_nodes(*expr)),
41            annotations,
42        },
43        other => other,
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use sim_kernel::{Expr, QuoteMode, Symbol};
50
51    use super::lower_operator_nodes;
52
53    fn sym(name: &str) -> Symbol {
54        Symbol::new(name)
55    }
56
57    fn sym_expr(name: &str) -> Expr {
58        Expr::Symbol(sym(name))
59    }
60
61    fn infix(operator: &str, left: Expr, right: Expr) -> Expr {
62        Expr::Infix {
63            operator: sym(operator),
64            left: Box::new(left),
65            right: Box::new(right),
66        }
67    }
68
69    fn prefix(operator: &str, arg: Expr) -> Expr {
70        Expr::Prefix {
71            operator: sym(operator),
72            arg: Box::new(arg),
73        }
74    }
75
76    fn postfix(operator: &str, arg: Expr) -> Expr {
77        Expr::Postfix {
78            operator: sym(operator),
79            arg: Box::new(arg),
80        }
81    }
82
83    fn call(operator: &str, args: Vec<Expr>) -> Expr {
84        Expr::Call {
85            operator: Box::new(sym_expr(operator)),
86            args,
87        }
88    }
89
90    #[test]
91    fn lowers_nested_operators_inside_calls() {
92        let expr = Expr::Call {
93            operator: Box::new(prefix("resolve", sym_expr("f"))),
94            args: vec![
95                infix("+", sym_expr("a"), postfix("!", sym_expr("b"))),
96                prefix("-", infix("*", sym_expr("c"), sym_expr("d"))),
97            ],
98        };
99
100        let expected = Expr::Call {
101            operator: Box::new(call("resolve", vec![sym_expr("f")])),
102            args: vec![
103                call("+", vec![sym_expr("a"), call("!", vec![sym_expr("b")])]),
104                call("-", vec![call("*", vec![sym_expr("c"), sym_expr("d")])]),
105            ],
106        };
107
108        assert_eq!(lower_operator_nodes(expr), expected);
109    }
110
111    #[test]
112    fn preserves_container_shapes_while_lowering_children() {
113        let expr = Expr::Block(vec![
114            Expr::List(vec![infix("+", sym_expr("a"), sym_expr("b"))]),
115            Expr::Vector(vec![prefix("-", sym_expr("x"))]),
116            Expr::Map(vec![(
117                infix("*", sym_expr("k1"), sym_expr("k2")),
118                postfix("?", sym_expr("v")),
119            )]),
120            Expr::Set(vec![infix("/", sym_expr("n"), sym_expr("d"))]),
121        ]);
122
123        let expected = Expr::Block(vec![
124            Expr::List(vec![call("+", vec![sym_expr("a"), sym_expr("b")])]),
125            Expr::Vector(vec![call("-", vec![sym_expr("x")])]),
126            Expr::Map(vec![(
127                call("*", vec![sym_expr("k1"), sym_expr("k2")]),
128                call("?", vec![sym_expr("v")]),
129            )]),
130            Expr::Set(vec![call("/", vec![sym_expr("n"), sym_expr("d")])]),
131        ]);
132
133        assert_eq!(lower_operator_nodes(expr), expected);
134    }
135
136    #[test]
137    fn leaves_quoted_operator_payloads_unchanged() {
138        let quoted = Expr::Quote {
139            mode: QuoteMode::Quote,
140            expr: Box::new(infix("+", sym_expr("a"), sym_expr("b"))),
141        };
142
143        assert_eq!(lower_operator_nodes(quoted.clone()), quoted);
144    }
145}