polydat_core/iteration/comprehension/ir/
compile.rs1use crate::iteration::comprehension::ast::Comprehension;
20use crate::iteration::comprehension::strategy::StrategyName;
21
22use super::op::{Op, OrderStreamingKind};
23use super::program::Program;
24
25pub fn compile(ast: &Comprehension) -> Program {
33 let mut ops = Vec::new();
34 emit(ast, &mut ops);
35 ops.push(Op::Dispense);
36 Program::new(ops)
37}
38
39fn emit(ast: &Comprehension, ops: &mut Vec<Op>) {
41 match ast {
42 Comprehension::Clause { name, source } => {
43 ops.push(Op::PushClause {
44 name: name.clone(),
45 source: source.clone(),
46 });
47 }
48 Comprehension::Cartesian { children } => {
49 for child in children {
50 emit(child, ops);
51 }
52 ops.push(Op::Cartesian { n: children.len() });
53 }
54 Comprehension::Zip { children, mode } => {
55 for child in children {
56 emit(child, ops);
57 }
58 ops.push(Op::Zip {
59 n: children.len(),
60 mode: *mode,
61 });
62 }
63 Comprehension::Union { children } => {
64 for child in children {
65 emit(child, ops);
66 }
67 ops.push(Op::Union { n: children.len() });
68 }
69 Comprehension::Filter { child, predicate } => {
70 emit(child, ops);
71 ops.push(Op::Filter {
72 predicate: predicate.clone(),
73 });
74 }
75 Comprehension::Order {
76 child,
77 strategy,
78 truncation,
79 } => {
80 emit(child, ops);
81 ops.push(order_op(child, *strategy, *truncation));
82 }
83 }
84}
85
86fn order_op(child: &Comprehension, strategy: StrategyName, truncation: Option<u64>) -> Op {
90 if matches!(strategy, StrategyName::Lex) {
91 return Op::OrderStreaming {
92 kind: OrderStreamingKind::Lex,
93 truncation,
94 };
95 }
96 let metadata = child.metadata();
99 let indexed = metadata.index_addressable.is_some();
100 Op::OrderMaterialize {
101 strategy,
102 truncation,
103 indexed,
104 input_index_fn: metadata.index_addressable,
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::iteration::comprehension::source::{LiteralValue, Source};
112 use crate::iteration::comprehension::strategy::ZipMode;
113
114 fn clause(name: &str, vs: &[i64]) -> Comprehension {
115 Comprehension::clause(
116 name,
117 Source::Literal {
118 values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
119 },
120 )
121 }
122
123 #[test]
124 fn compile_single_clause_emits_3_opcodes() {
125 let ast = clause("k", &[1, 2, 3]);
126 let prog = compile(&ast);
127 assert_eq!(prog.len(), 2);
128 assert!(matches!(prog.ops()[0], Op::PushClause { .. }));
130 assert!(matches!(prog.ops()[1], Op::Dispense));
131 }
132
133 #[test]
134 fn compile_cartesian_emits_children_then_combinator() {
135 let ast = Comprehension::cartesian(vec![clause("a", &[1, 2]), clause("b", &[10, 20])]);
136 let prog = compile(&ast);
137 assert_eq!(prog.len(), 4);
139 match &prog.ops()[2] {
140 Op::Cartesian { n: 2 } => {}
141 other => panic!("expected Cartesian(2), got {other:?}"),
142 }
143 }
144
145 #[test]
146 fn compile_order_lex_emits_streaming() {
147 let inner = clause("k", &[1, 2, 3]);
148 let ast = Comprehension::order(inner, StrategyName::Lex, Some(2));
149 let prog = compile(&ast);
150 assert!(matches!(
152 prog.ops()[1],
153 Op::OrderStreaming {
154 kind: OrderStreamingKind::Lex,
155 truncation: Some(2)
156 }
157 ));
158 }
159
160 #[test]
161 fn compile_order_halton_emits_materialize_indexed() {
162 let cart = Comprehension::cartesian(vec![clause("a", &[1, 2, 3]), clause("b", &[10, 20])]);
164 let ast = Comprehension::order(cart, StrategyName::Halton, Some(3));
165 let prog = compile(&ast);
166 let last_non_dispense = &prog.ops()[prog.len() - 2];
167 match last_non_dispense {
168 Op::OrderMaterialize {
169 strategy: StrategyName::Halton,
170 truncation: Some(3),
171 indexed: true,
172 ..
173 } => {}
174 other => panic!("expected OrderMaterialize indexed, got {other:?}"),
175 }
176 }
177
178 #[test]
179 fn compile_order_halton_over_filter_is_naive() {
180 let cart = Comprehension::cartesian(vec![clause("a", &[1, 2, 3]), clause("b", &[10, 20])]);
183 let filtered = Comprehension::filter(cart, "{a} > 0");
184 let ast = Comprehension::order(filtered, StrategyName::Halton, Some(2));
185 let prog = compile(&ast);
186 let order_op = prog
187 .ops()
188 .iter()
189 .find(|op| matches!(op, Op::OrderMaterialize { .. }))
190 .unwrap();
191 assert!(matches!(
192 order_op,
193 Op::OrderMaterialize { indexed: false, .. }
194 ));
195 }
196
197 #[test]
198 fn compile_zip_emits_zip_with_mode() {
199 let ast = Comprehension::zip(
200 vec![clause("x", &[1, 2, 3]), clause("y", &[10, 20, 30])],
201 ZipMode::Strict,
202 );
203 let prog = compile(&ast);
204 assert!(matches!(
205 prog.ops()[2],
206 Op::Zip {
207 n: 2,
208 mode: ZipMode::Strict
209 }
210 ));
211 }
212
213 #[test]
214 fn compile_terminates_with_dispense() {
215 let ast = clause("k", &[1]);
216 let prog = compile(&ast);
217 assert!(matches!(prog.ops().last(), Some(Op::Dispense)));
218 }
219}