1use crate::ast::{
4 Comparison, ComparisonExpression, ComparisonOperand, ComparisonOperator, ObservationExpression,
5 Pattern, Qualifier,
6};
7use crate::error::Span;
8use crate::ir::{
9 Block, BlockId, BlockKind, InstrId, Instruction, Op, Operand, Program, SCHEMA_VERSION,
10};
11
12pub fn lower(pattern: &Pattern) -> Program {
26 let mut l = Lowerer {
27 next_instr: 0,
28 next_block: 0,
29 blocks: Vec::new(),
30 };
31 let main_id = l.fresh_block();
32 let mut main_instrs = Vec::new();
33 let result = l.lower_observation(&pattern.expression, &mut main_instrs);
34 l.push(&mut main_instrs, Op::Ret { value: result }, None);
35 Program {
36 schema_version: SCHEMA_VERSION,
37 blocks: l.blocks,
38 main: Block {
39 id: main_id,
40 kind: BlockKind::Main,
41 instructions: main_instrs,
42 },
43 }
44}
45
46trait Lowerable {
49 fn lower_into(&self, l: &mut Lowerer, into: &mut Vec<Instruction>) -> InstrId;
50}
51
52impl Lowerable for ObservationExpression {
53 fn lower_into(&self, l: &mut Lowerer, into: &mut Vec<Instruction>) -> InstrId {
54 l.lower_observation(self, into)
55 }
56}
57
58impl Lowerable for ComparisonExpression {
59 fn lower_into(&self, l: &mut Lowerer, into: &mut Vec<Instruction>) -> InstrId {
60 l.lower_comparison(self, into)
61 }
62}
63
64struct Lowerer {
65 next_instr: u32,
66 next_block: u32,
67 blocks: Vec<Block>,
68}
69
70impl Lowerer {
71 fn fresh_block(&mut self) -> BlockId {
72 let id = BlockId(self.next_block);
73 self.next_block += 1;
74 id
75 }
76
77 fn push(&mut self, into: &mut Vec<Instruction>, op: Op, span: Option<Span>) -> InstrId {
79 let id = InstrId(self.next_instr);
80 self.next_instr += 1;
81 into.push(Instruction { id, op, span });
82 id
83 }
84
85 fn lower_observation(
86 &mut self,
87 e: &ObservationExpression,
88 into: &mut Vec<Instruction>,
89 ) -> InstrId {
90 match e {
91 ObservationExpression::Observation { expression, span } => {
92 let block = self.lower_comparison_block(expression);
93 self.push(into, Op::Observe { block }, Some(*span))
94 }
95 ObservationExpression::And(xs) => {
96 self.lower_chain(xs, into, |lhs, rhs| Op::And { lhs, rhs })
97 }
98 ObservationExpression::Or(xs) => {
99 self.lower_chain(xs, into, |lhs, rhs| Op::Or { lhs, rhs })
100 }
101 ObservationExpression::FollowedBy(xs) => {
102 self.lower_chain(xs, into, |lhs, rhs| Op::FollowedBy { lhs, rhs })
103 }
104 ObservationExpression::Qualified {
105 expression,
106 qualifier,
107 span,
108 } => {
109 let input = self.lower_observation(expression, into);
110 let op = match qualifier {
111 Qualifier::Within { seconds } => Op::Within {
112 input,
113 seconds: *seconds,
114 },
115 Qualifier::Repeats { count } => Op::Repeats {
116 input,
117 count: *count,
118 },
119 Qualifier::StartStop { start, stop } => Op::StartStop {
120 input,
121 start: start.clone(),
122 stop: stop.clone(),
123 },
124 };
125 self.push(into, op, Some(*span))
126 }
127 }
128 }
129
130 fn lower_comparison_block(&mut self, e: &ComparisonExpression) -> BlockId {
132 let id = self.fresh_block();
133 let mut instrs = Vec::new();
134 let result = self.lower_comparison(e, &mut instrs);
135 self.push(&mut instrs, Op::Yield { value: result }, None);
136 self.blocks.push(Block {
137 id,
138 kind: BlockKind::Comparison,
139 instructions: instrs,
140 });
141 id
142 }
143
144 fn lower_comparison(
145 &mut self,
146 e: &ComparisonExpression,
147 into: &mut Vec<Instruction>,
148 ) -> InstrId {
149 match e {
150 ComparisonExpression::Test(c) => self.lower_test(c, into),
151 ComparisonExpression::And(xs) => {
152 self.lower_chain(xs, into, |lhs, rhs| Op::And { lhs, rhs })
153 }
154 ComparisonExpression::Or(xs) => {
155 self.lower_chain(xs, into, |lhs, rhs| Op::Or { lhs, rhs })
156 }
157 }
158 }
159
160 fn lower_chain<E: Lowerable>(
165 &mut self,
166 operands: &[E],
167 into: &mut Vec<Instruction>,
168 op: fn(InstrId, InstrId) -> Op,
169 ) -> InstrId {
170 let (first, rest) = operands
171 .split_first()
172 .expect("chain nodes have at least two operands");
173 let mut acc = first.lower_into(self, into);
174 for operand in rest {
175 let rhs = operand.lower_into(self, into);
176 acc = self.push(into, op(acc, rhs), None);
177 }
178 acc
179 }
180
181 fn lower_test(&mut self, c: &Comparison, into: &mut Vec<Instruction>) -> InstrId {
182 let lhs = self.push(
183 into,
184 Op::Load {
185 path: c.path.clone(),
186 },
187 Some(c.path.span),
188 );
189 let rhs = if c.operator == ComparisonOperator::Exists {
191 Operand::Absent
192 } else {
193 match &c.value {
194 ComparisonOperand::Literal(lit) => Operand::Literal(lit.clone()),
195 ComparisonOperand::Set(items) => Operand::Set(items.clone()),
196 }
197 };
198 self.push(
199 into,
200 Op::Compare {
201 operator: c.operator,
202 negated: c.negated,
203 lhs,
204 rhs,
205 },
206 Some(c.span),
207 )
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::ast::ComparisonOperator;
215 use crate::ir::{BlockKind, Op, Operand, SCHEMA_VERSION};
216 use crate::parse;
217
218 #[test]
219 fn lowers_a_single_comparison() {
220 let p = parse("[file:size > 1024]").unwrap();
221 let prog = lower(&p);
222
223 assert_eq!(prog.schema_version, SCHEMA_VERSION);
224 assert_eq!(prog.blocks.len(), 1);
225 assert_eq!(prog.blocks[0].kind, BlockKind::Comparison);
226 assert_eq!(prog.main.kind, BlockKind::Main);
227
228 let ops: Vec<&Op> = prog.blocks[0].instructions.iter().map(|i| &i.op).collect();
229 assert!(matches!(ops[0], Op::Load { .. }), "got {:?}", ops[0]);
230 assert!(
231 matches!(
232 ops[1],
233 Op::Compare {
234 operator: ComparisonOperator::GreaterThan,
235 negated: false,
236 ..
237 }
238 ),
239 "got {:?}",
240 ops[1]
241 );
242 assert!(matches!(ops[2], Op::Yield { .. }), "got {:?}", ops[2]);
243
244 let main_ops: Vec<&Op> = prog.main.instructions.iter().map(|i| &i.op).collect();
245 assert!(matches!(main_ops[0], Op::Observe { .. }));
246 assert!(matches!(main_ops[1], Op::Ret { .. }));
247 }
248
249 #[test]
250 fn compare_references_its_load() {
251 let p = parse("[file:size > 1024]").unwrap();
252 let prog = lower(&p);
253 let load_id = prog.blocks[0].instructions[0].id;
254 match &prog.blocks[0].instructions[1].op {
255 Op::Compare { lhs, .. } => assert_eq!(*lhs, load_id),
256 other => panic!("expected a compare, got {other:?}"),
257 }
258 }
259
260 #[test]
261 fn exists_lowers_to_an_absent_operand() {
262 let p = parse("[EXISTS file:name]").unwrap();
263 let prog = lower(&p);
264 match &prog.blocks[0].instructions[1].op {
265 Op::Compare { operator, rhs, .. } => {
266 assert_eq!(*operator, ComparisonOperator::Exists);
267 assert_eq!(*rhs, Operand::Absent);
268 }
269 other => panic!("expected a compare, got {other:?}"),
270 }
271 }
272
273 #[test]
274 fn in_lowers_to_a_set_operand() {
275 let p = parse("[ipv4-addr:value IN ('1.1.1.1', '8.8.8.8')]").unwrap();
276 let prog = lower(&p);
277 match &prog.blocks[0].instructions[1].op {
278 Op::Compare { rhs, .. } => match rhs {
279 Operand::Set(items) => assert_eq!(items.len(), 2),
280 other => panic!("expected a set, got {other:?}"),
281 },
282 other => panic!("expected a compare, got {other:?}"),
283 }
284 }
285
286 #[test]
287 fn each_observation_gets_its_own_block() {
288 let p = parse("[file:name='a'] FOLLOWEDBY [file:name='b']").unwrap();
289 let prog = lower(&p);
290 assert_eq!(prog.blocks.len(), 2);
291 let main_ops: Vec<&Op> = prog.main.instructions.iter().map(|i| &i.op).collect();
292 assert!(matches!(main_ops[0], Op::Observe { .. }));
293 assert!(matches!(main_ops[1], Op::Observe { .. }));
294 assert!(matches!(main_ops[2], Op::FollowedBy { .. }));
295 assert!(matches!(main_ops[3], Op::Ret { .. }));
296 }
297
298 #[test]
299 fn chains_lower_to_left_associative_binary_ops() {
300 let p = parse("[x:v=1] OR [x:v=2] OR [x:v=3]").unwrap();
301 let prog = lower(&p);
302 let main = &prog.main.instructions;
303 let ops: Vec<&Op> = main.iter().map(|i| &i.op).collect();
304 assert!(matches!(ops[0], Op::Observe { .. }));
305 assert!(matches!(ops[1], Op::Observe { .. }));
306 assert_eq!(
307 *ops[2],
308 Op::Or {
309 lhs: main[0].id,
310 rhs: main[1].id
311 }
312 );
313 assert!(matches!(ops[3], Op::Observe { .. }));
314 assert_eq!(
315 *ops[4],
316 Op::Or {
317 lhs: main[2].id,
318 rhs: main[3].id
319 }
320 );
321 assert!(matches!(ops[5], Op::Ret { .. }));
322
323 let p = parse("[x:v=1 AND x:v=2 AND x:v=3]").unwrap();
325 let block = &lower(&p).blocks[0].instructions;
326 let ands: Vec<&Instruction> = block
327 .iter()
328 .filter(|i| matches!(i.op, Op::And { .. }))
329 .collect();
330 assert_eq!(ands.len(), 2);
331 let Op::And { lhs, .. } = ands[1].op else {
332 unreachable!()
333 };
334 assert_eq!(lhs, ands[0].id);
335 }
336
337 #[test]
338 fn nested_qualifiers_lower_inner_first() {
339 let p = parse("[file:name='a'] REPEATS 2 TIMES WITHIN 60 SECONDS").unwrap();
340 let prog = lower(&p);
341 let main_ops: Vec<&Op> = prog.main.instructions.iter().map(|i| &i.op).collect();
342 assert!(matches!(main_ops[0], Op::Observe { .. }));
343 assert!(matches!(main_ops[1], Op::Repeats { count: 2, .. }));
344 assert!(matches!(main_ops[2], Op::Within { .. }));
345 assert!(matches!(main_ops[3], Op::Ret { .. }));
346 }
347
348 #[test]
349 fn instructions_carry_source_spans() {
350 let src = "[file:size > 1024]";
351 let prog = lower(&parse(src).unwrap());
352 for instr in &prog.blocks[0].instructions {
353 if matches!(instr.op, Op::Yield { .. }) {
355 continue;
356 }
357 let span = instr.span.expect("lowered instruction should have a span");
358 assert!(span.end <= src.len());
359 assert!(span.start < span.end, "span should be non-empty");
360 }
361 }
362}