1use std::collections::{BTreeSet, HashMap};
2use std::iter::zip;
3
4use itertools::Itertools;
5use num::ToPrimitive;
6use rtlola_hir::hir::{
7 ActivationCondition, Aggregation, ArithLogOp, ConcretePacingType, ConcreteValueType, Constant,
8 DepAnaTrait, DiscreteAggr, Expression, ExpressionKind, FnExprKind, Inlined,
9 InstanceAggregation, InstanceSelection, Literal, MemBoundTrait, Offset, OrderedTrait, Origin,
10 OutputKind, SlidingAggr, StreamAccessKind, StreamReference, TypedTrait, WidenExprKind, Window,
11 WindowReference,
12};
13use rtlola_hir::{CompleteMode, RtLolaHir};
14use rtlola_parser::ast::{InstanceOperation, Tag, WindowOperation};
15use rtlola_reporting::Span;
16
17use crate::mir::{self, Close, Eval, EvalClause, Mir, PacingLocality, PacingType, Spawn, Trigger};
18
19impl Mir {
20 pub fn from_hir(hir: RtLolaHir<CompleteMode>) -> Mir {
22 let sr_map: HashMap<StreamReference, StreamReference> = hir
23 .inputs()
24 .sorted_by(|a, b| Ord::cmp(&a.sr(), &b.sr()))
25 .enumerate()
26 .map(|(new_ref, i)| (i.sr(), StreamReference::In(new_ref)))
27 .chain(
28 hir.outputs()
29 .sorted_by(|a, b| Ord::cmp(&a.sr(), &b.sr()))
30 .enumerate()
31 .map(|(new_ref, o)| (o.sr(), StreamReference::Out(new_ref))),
32 )
33 .collect();
34
35 let inputs = hir
36 .inputs()
37 .sorted_by(|a, b| Ord::cmp(&a.sr(), &b.sr()))
38 .map(|i| {
39 let sr = i.sr();
40 mir::InputStream {
41 name: i.name.clone(),
42 ty: Self::lower_value_type(&hir.stream_type(sr).value_ty),
43 accessed_by: Self::lower_accessed_streams(
44 &sr_map,
45 hir.direct_accessed_by_with(sr),
46 ),
47 aggregated_by: hir
48 .aggregated_by(sr)
49 .into_iter()
50 .map(|(sr, origin, wr)| (sr_map[&sr], origin, wr))
51 .collect(),
52 aggregates: Vec::new(),
53 layer: hir.stream_layers(sr),
54 memory_bound: hir.memory_bound(sr),
55 reference: sr_map[&sr],
56 tags: Self::lower_tags(&i.tags),
57 #[cfg(feature = "spanned")]
58 tags_span: i.tags.iter().map(|(k, v)| (k.clone(), v.span)).collect(),
59 #[cfg(feature = "spanned")]
60 span: i.span(),
61 }
62 })
63 .collect::<Vec<mir::InputStream>>();
64 assert!(
65 inputs
66 .iter()
67 .enumerate()
68 .all(|(idx, i)| idx == i.reference.in_ix()),
69 "SRefs need to enumerated from 0 to the number of streams"
70 );
71
72 let outputs = hir.outputs().map(|o| {
73 let sr = o.sr();
74 mir::OutputStream {
75 name: o.name(),
76 kind: o.kind.clone(),
77 ty: Self::lower_value_type(&hir.stream_type(sr).value_ty),
78 spawn: Self::lower_spawn(&hir, &sr_map, sr),
79 eval: Self::lower_eval(&hir, &sr_map, sr),
80 close: Self::lower_close(&hir, &sr_map, sr),
81 accesses: Self::lower_accessed_streams(&sr_map, hir.direct_accesses_with(sr)),
82 accessed_by: Self::lower_accessed_streams(&sr_map, hir.direct_accessed_by_with(sr)),
83 aggregated_by: hir
84 .aggregated_by(sr)
85 .into_iter()
86 .map(|(sr, origin, wr)| (sr_map[&sr], origin, wr))
87 .collect(),
88 aggregates: hir
89 .aggregates(sr)
90 .into_iter()
91 .map(|(sr, origin, wr)| (sr_map[&sr], origin, wr))
92 .collect(),
93 memory_bound: hir.memory_bound(sr),
94 layer: hir.stream_layers(sr),
95 reference: sr_map[&sr],
96 params: Self::lower_parameters(
97 hir.output(sr).expect("is output stream").params(),
98 &hir,
99 sr,
100 ),
101 tags: Self::lower_tags(&o.tags),
102 #[cfg(feature = "spanned")]
103 tags_span: o.tags.iter().map(|(k, v)| (k.clone(), v.span)).collect(),
104 #[cfg(feature = "spanned")]
105 span: o.span(),
106 }
107 });
108
109 let outputs = outputs
110 .sorted_by(|a, b| Ord::cmp(&a.reference, &b.reference))
111 .collect::<Vec<_>>();
112
113 assert!(
114 outputs
115 .iter()
116 .enumerate()
117 .all(|(idx, o)| idx == o.reference.out_ix()),
118 "SRefs need to enumerated from 0 to the number of streams"
119 );
120
121 let time_driven = outputs
122 .iter()
123 .filter(|o| hir.is_periodic(o.reference))
124 .map(|o| Self::lower_periodic(&hir, &sr_map, o.reference))
125 .collect::<Vec<mir::TimeDrivenStream>>();
126 let event_driven = outputs
127 .iter()
128 .filter(|o| hir.is_event(o.reference))
129 .map(|o| Self::lower_event_based(&hir, &sr_map, o.reference))
130 .collect::<Vec<mir::EventDrivenStream>>();
131
132 let discrete_windows = hir
133 .discrete_windows()
134 .into_iter()
135 .sorted_by(|a, b| Ord::cmp(&a.reference().idx(), &b.reference().idx()))
136 .map(|win| Self::lower_discrete_window(&hir, &sr_map, win))
137 .collect::<Vec<mir::DiscreteWindow>>();
138
139 assert!(
140 discrete_windows
141 .iter()
142 .enumerate()
143 .all(|(idx, w)| idx == w.reference.idx()),
144 "WRefs need to enumerate from 0 to the number of discrete windows"
145 );
146
147 let sliding_windows = hir
148 .sliding_windows()
149 .into_iter()
150 .sorted_by(|a, b| Ord::cmp(&a.reference().idx(), &b.reference().idx()))
151 .map(|win| Self::lower_sliding_window(&hir, &sr_map, win))
152 .collect::<Vec<mir::SlidingWindow>>();
153 assert!(
154 sliding_windows
155 .iter()
156 .enumerate()
157 .all(|(idx, w)| idx == w.reference.idx()),
158 "WRefs need to enumerate from 0 to the number of sliding windows"
159 );
160
161 let instance_aggregations = hir
162 .instance_aggregations()
163 .into_iter()
164 .sorted_by(|a, b| Ord::cmp(&a.reference().idx(), &b.reference().idx()))
165 .map(|win| Self::lower_instance_aggregation(&hir, &sr_map, win))
166 .collect::<Vec<mir::InstanceAggregation>>();
167 assert!(
168 instance_aggregations
169 .iter()
170 .enumerate()
171 .all(|(idx, w)| idx == w.reference.idx()),
172 "WRefs need to enumerate from 0 to the number of discrete windows"
173 );
174
175 let triggers = outputs
176 .iter()
177 .filter_map(|output| {
178 matches!(&output.kind, OutputKind::Trigger(_)).then_some(output.reference)
179 })
180 .enumerate()
181 .map(|(trigger_reference, output_reference)| Trigger {
182 trigger_reference,
183 output_reference,
184 })
185 .collect();
186
187 let global_tags = Self::lower_tags(hir.global_tags());
188 #[cfg(feature = "spanned")]
189 let global_tags_span = hir
190 .global_tags()
191 .iter()
192 .map(|(k, v)| (k.clone(), v.span))
193 .collect();
194
195 Mir {
196 inputs,
197 outputs,
198 time_driven,
199 event_driven,
200 discrete_windows,
201 sliding_windows,
202 instance_aggregations,
203 triggers,
204 global_tags,
205 #[cfg(feature = "spanned")]
206 global_tags_span,
207 }
208 }
209
210 fn lower_event_based(
211 hir: &RtLolaHir<CompleteMode>,
212 sr_map: &HashMap<StreamReference, StreamReference>,
213 sr: StreamReference,
214 ) -> mir::EventDrivenStream {
215 if let ConcretePacingType::Event(ac) = hir.stream_type(sr).eval_pacing {
216 mir::EventDrivenStream {
217 reference: sr_map[&sr],
218 ac: Self::lower_activation_condition(&ac, sr_map),
219 }
220 } else {
221 unreachable!()
222 }
223 }
224
225 fn lower_activation_condition(
226 ac: &ActivationCondition,
227 sr_map: &HashMap<StreamReference, StreamReference>,
228 ) -> mir::ActivationCondition {
229 let lower_conjunction = |conjs: &BTreeSet<StreamReference>| -> mir::ActivationCondition {
230 if conjs.len() == 1 {
231 let sref = conjs.iter().next().unwrap();
232 mir::ActivationCondition::Stream(sr_map[sref])
233 } else {
234 mir::ActivationCondition::Conjunction(
235 conjs
236 .iter()
237 .map(|sr| mir::ActivationCondition::Stream(sr_map[sr]))
238 .collect(),
239 )
240 }
241 };
242
243 match ac {
244 ActivationCondition::Models(disjuncts) if disjuncts.len() == 1 => {
245 let conj = disjuncts.iter().next().unwrap();
246 lower_conjunction(conj)
247 }
248 ActivationCondition::Models(disjuncts) => mir::ActivationCondition::Disjunction(
249 disjuncts.iter().map(lower_conjunction).collect(),
250 ),
251 ActivationCondition::True => mir::ActivationCondition::True,
252 }
253 }
254
255 fn lower_periodic(
256 hir: &RtLolaHir<CompleteMode>,
257 sr_map: &HashMap<StreamReference, StreamReference>,
258 sr: StreamReference,
259 ) -> mir::TimeDrivenStream {
260 match &hir.stream_type(sr).eval_pacing {
261 ConcretePacingType::FixedGlobalPeriodic(f) => mir::TimeDrivenStream {
262 reference: sr_map[&sr],
263 frequency: *f,
264 locality: PacingLocality::Global,
265 },
266 ConcretePacingType::FixedLocalPeriodic(f) => mir::TimeDrivenStream {
267 reference: sr_map[&sr],
268 frequency: *f,
269 locality: PacingLocality::Local,
270 },
271 _ => unreachable!(),
272 }
273 }
274
275 fn lower_pacing_type(
276 cpt: ConcretePacingType,
277 sr_map: &HashMap<StreamReference, StreamReference>,
278 ) -> PacingType {
279 match cpt {
280 ConcretePacingType::Event(ac) => {
281 PacingType::Event(Self::lower_activation_condition(&ac, sr_map))
282 }
283 ConcretePacingType::FixedLocalPeriodic(freq) => PacingType::LocalPeriodic(freq),
284 ConcretePacingType::FixedGlobalPeriodic(freq) => PacingType::GlobalPeriodic(freq),
285 ConcretePacingType::Constant => PacingType::Constant,
286 other => {
287 unreachable!("Ensured by pacing type checker: {:?}", other)
288 }
289 }
290 }
291
292 fn lower_spawn(
293 hir: &RtLolaHir<CompleteMode>,
294 sr_map: &HashMap<StreamReference, StreamReference>,
295 sr: StreamReference,
296 ) -> Spawn {
297 let ty = hir.stream_type(sr);
298 let spawn_pacing = Self::lower_pacing_type(ty.spawn_pacing, sr_map);
299 let hir_spawn_expr = hir.spawn_expr(sr);
300 let hir_spawn_condition = hir.spawn_cond(sr);
301 let spawn_cond = hir_spawn_condition.map(|expr| Self::lower_expr(hir, sr_map, expr));
302 let spawn_expression = hir_spawn_expr.map(|expr| Self::lower_expr(hir, sr_map, expr));
303 #[cfg(feature = "spanned")]
304 let spawn_span = hir.spawn(sr).map(|s| s.span).unwrap_or(Span::Unknown);
305 Spawn {
306 expression: spawn_expression,
307 pacing: spawn_pacing,
308 condition: spawn_cond,
309 #[cfg(feature = "spanned")]
310 span: spawn_span,
311 }
312 }
313
314 fn lower_eval(
315 hir: &RtLolaHir<CompleteMode>,
316 sr_map: &HashMap<StreamReference, StreamReference>,
317 sr: StreamReference,
318 ) -> Eval {
319 assert_eq!(
320 hir.eval_expr(sr).unwrap().len(),
321 hir.eval_cond(sr).unwrap().len()
322 );
323
324 let clauses = zip(hir.eval_expr(sr).unwrap(), hir.eval_cond(sr).unwrap())
325 .enumerate()
326 .map(|(idx, (expr, cond))| {
327 let expr = Self::lower_expr(hir, sr_map, expr);
328 let condition = cond.map(|f| Self::lower_expr(hir, sr_map, f));
329 let pacing = Self::lower_pacing_type(hir.eval_pacing_type(sr, idx), sr_map);
330 #[cfg(feature = "spanned")]
331 let eval_span = hir.eval(sr).unwrap()[idx].span;
332 EvalClause {
333 pacing,
334 condition,
335 expression: expr,
336 #[cfg(feature = "spanned")]
337 span: eval_span,
338 }
339 })
340 .collect();
341
342 let eval_pacing = Self::lower_pacing_type(hir.stream_type(sr).eval_pacing, sr_map);
343 Eval {
344 clauses,
345 eval_pacing,
346 }
347 }
348
349 fn lower_close(
350 hir: &RtLolaHir<CompleteMode>,
351 sr_map: &HashMap<StreamReference, StreamReference>,
352 sr: StreamReference,
353 ) -> Close {
354 let (close, close_pacing, close_self_ref, _close_span) = hir
355 .close_cond(sr)
356 .map(|expr| {
357 let cpt = hir.stream_type(sr).close_pacing;
358 let close_self_ref = matches!(
359 hir.expr_type(expr.id()).spawn_pacing,
360 ConcretePacingType::Event(_)
361 | ConcretePacingType::FixedGlobalPeriodic(_)
362 | ConcretePacingType::FixedLocalPeriodic(_)
363 );
364 let close_span = hir.close(sr).unwrap().span;
365 (
366 Some(Self::lower_expr(hir, sr_map, expr)),
367 Self::lower_pacing_type(cpt, sr_map),
368 close_self_ref,
369 close_span,
370 )
371 })
372 .unwrap_or((None, PacingType::Constant, false, Span::Unknown));
373 Close {
374 condition: close,
375 pacing: close_pacing,
376 has_self_reference: close_self_ref,
377 #[cfg(feature = "spanned")]
378 span: _close_span,
379 }
380 }
381
382 fn lower_tags(tags: &HashMap<String, Tag>) -> HashMap<String, Option<String>> {
383 tags.iter()
384 .map(|(key, tag)| (key.to_owned(), tag.value.to_owned()))
385 .collect()
386 }
387
388 fn lower_sliding_window(
389 hir: &RtLolaHir<CompleteMode>,
390 sr_map: &HashMap<StreamReference, StreamReference>,
391 win: &Window<SlidingAggr>,
392 ) -> mir::SlidingWindow {
393 let origin = Self::lower_window_origin(hir, win.reference(), win.caller);
394 mir::SlidingWindow {
395 target: sr_map[&win.target],
396 caller: sr_map[&win.caller],
397 duration: win.aggr.duration,
398 num_buckets: hir.num_buckets(win.reference()),
399 bucket_size: hir.bucket_size(win.reference()),
400 wait: win.aggr.wait,
401 op: Self::lower_window_operation(win.aggr.op),
402 reference: win.reference(),
403 ty: Self::lower_value_type(&hir.expr_type(win.id()).value_ty),
404 pacing: Self::lower_origin_pacing(hir, win.caller, &origin, sr_map),
405 origin,
406 }
407 }
408
409 fn lower_discrete_window(
410 hir: &RtLolaHir<CompleteMode>,
411 sr_map: &HashMap<StreamReference, StreamReference>,
412 win: &Window<DiscreteAggr>,
413 ) -> mir::DiscreteWindow {
414 let origin = Self::lower_window_origin(hir, win.reference(), win.caller);
415 mir::DiscreteWindow {
416 target: sr_map[&win.target],
417 caller: sr_map[&win.caller],
418 duration: win.aggr.duration,
419 wait: win.aggr.wait,
420 op: Self::lower_window_operation(win.aggr.op),
421 reference: win.reference(),
422 ty: Self::lower_value_type(&hir.expr_type(win.id()).value_ty),
423 pacing: Self::lower_origin_pacing(hir, win.caller, &origin, sr_map),
424 origin,
425 }
426 }
427
428 fn lower_instance_aggregation(
429 hir: &RtLolaHir<CompleteMode>,
430 sr_map: &HashMap<StreamReference, StreamReference>,
431 win: &InstanceAggregation,
432 ) -> mir::InstanceAggregation {
433 let origin = Self::lower_window_origin(hir, win.reference(), win.caller);
434 mir::InstanceAggregation {
435 target: sr_map[&win.target],
436 caller: sr_map[&win.caller],
437 reference: win.reference(),
438 selection: Self::lower_instance_selection(&win.selection, hir, win.reference(), sr_map),
439 aggr: Self::lower_instance_operation(win.aggr),
440 ty: Self::lower_value_type(&hir.expr_type(win.id()).value_ty),
441 pacing: Self::lower_origin_pacing(hir, win.caller, &origin, sr_map),
442 origin,
443 }
444 }
445
446 fn lower_window_origin(
447 hir: &RtLolaHir<CompleteMode>,
448 window: WindowReference,
449 caller: StreamReference,
450 ) -> Origin {
451 hir.aggregates(caller)
452 .iter()
453 .find_map(|(_sr, origin, wref)| (*wref == window).then_some(*origin))
454 .unwrap()
455 }
456
457 fn lower_origin_pacing(
458 hir: &RtLolaHir<CompleteMode>,
459 sr: StreamReference,
460 origin: &Origin,
461 sr_map: &HashMap<StreamReference, StreamReference>,
462 ) -> PacingType {
463 let ty = hir.stream_type(sr);
464 let pacing = match origin {
465 Origin::Spawn => ty.spawn_pacing,
466 Origin::Filter(i) | Origin::Eval(i) => hir.eval_pacing_type(sr, *i),
467 Origin::Close => ty.close_pacing,
468 };
469 Self::lower_pacing_type(pacing, sr_map)
470 }
471
472 fn lower_value_type(ty: &ConcreteValueType) -> mir::Type {
473 match ty {
474 ConcreteValueType::Bool => mir::Type::Bool,
475 ConcreteValueType::Integer8 => mir::Type::Int(mir::IntTy::Int8),
476 ConcreteValueType::Integer16 => mir::Type::Int(mir::IntTy::Int16),
477 ConcreteValueType::Integer32 => mir::Type::Int(mir::IntTy::Int32),
478 ConcreteValueType::Integer64 => mir::Type::Int(mir::IntTy::Int64),
479 ConcreteValueType::Integer128 => mir::Type::Int(mir::IntTy::Int128),
480 ConcreteValueType::Integer256 => mir::Type::Int(mir::IntTy::Int256),
481 ConcreteValueType::UInteger8 => mir::Type::UInt(mir::UIntTy::UInt8),
482 ConcreteValueType::UInteger16 => mir::Type::UInt(mir::UIntTy::UInt16),
483 ConcreteValueType::UInteger32 => mir::Type::UInt(mir::UIntTy::UInt32),
484 ConcreteValueType::UInteger64 => mir::Type::UInt(mir::UIntTy::UInt64),
485 ConcreteValueType::UInteger128 => mir::Type::UInt(mir::UIntTy::UInt128),
486 ConcreteValueType::UInteger256 => mir::Type::UInt(mir::UIntTy::UInt256),
487 ConcreteValueType::Float32 => mir::Type::Float(mir::FloatTy::Float32),
488 ConcreteValueType::Float64 => mir::Type::Float(mir::FloatTy::Float64),
489 ConcreteValueType::Fixed64_32 => mir::Type::Fixed(mir::FixedTy::Fixed64_32),
490 ConcreteValueType::Fixed32_16 => mir::Type::Fixed(mir::FixedTy::Fixed32_16),
491 ConcreteValueType::Fixed16_8 => mir::Type::Fixed(mir::FixedTy::Fixed16_8),
492 ConcreteValueType::UFixed64_32 => mir::Type::UFixed(mir::FixedTy::Fixed64_32),
493 ConcreteValueType::UFixed32_16 => mir::Type::UFixed(mir::FixedTy::Fixed32_16),
494 ConcreteValueType::UFixed16_8 => mir::Type::UFixed(mir::FixedTy::Fixed16_8),
495 ConcreteValueType::Tuple(elements) => {
496 let elements = elements
497 .iter()
498 .map(Self::lower_value_type)
499 .collect::<Vec<_>>();
500 mir::Type::Tuple(elements)
501 }
502 ConcreteValueType::TString => mir::Type::String,
503 ConcreteValueType::Byte => mir::Type::Bytes,
504 ConcreteValueType::Option(v) => mir::Type::Option(Box::new(Self::lower_value_type(v))),
505 }
506 }
507
508 fn lower_expr(
509 hir: &RtLolaHir<CompleteMode>,
510 sr_map: &HashMap<StreamReference, StreamReference>,
511 expr: &Expression,
512 ) -> mir::Expression {
513 let ty = Self::lower_value_type(&hir.expr_type(expr.id()).value_ty);
514 mir::Expression {
515 kind: Self::lower_expression_kind(hir, sr_map, &expr.kind, &ty),
516 ty,
517 #[cfg(feature = "spanned")]
518 span: expr.span(),
519 }
520 }
521
522 fn lower_expression_kind(
523 hir: &RtLolaHir<CompleteMode>,
524 sr_map: &HashMap<StreamReference, StreamReference>,
525 expr: &ExpressionKind,
526 ty: &mir::Type,
527 ) -> mir::ExpressionKind {
528 match expr {
529 ExpressionKind::LoadConstant(constant) => {
530 mir::ExpressionKind::LoadConstant(Self::lower_constant(constant, ty))
531 }
532 ExpressionKind::ArithLog(op, args) => {
533 let op = Self::lower_arith_log_op(*op);
534 let args = args
535 .iter()
536 .map(|arg| Self::lower_expr(hir, sr_map, arg))
537 .collect::<Vec<mir::Expression>>();
538 mir::ExpressionKind::ArithLog(op, args)
539 }
540 ExpressionKind::StreamAccess(sr, kind, para) => mir::ExpressionKind::StreamAccess {
541 target: sr_map[sr],
542 access_kind: Self::lower_stream_access_kind(*kind),
543 parameters: para
544 .iter()
545 .map(|p| Self::lower_expr(hir, sr_map, p))
546 .collect(),
547 },
548 ExpressionKind::ParameterAccess(sr, para) => {
549 mir::ExpressionKind::ParameterAccess(sr_map[sr], *para)
550 }
551 ExpressionKind::LambdaParameterAccess { wref, pref } => {
552 mir::ExpressionKind::LambdaParameterAccess {
553 wref: *wref,
554 pref: *pref,
555 }
556 }
557 ExpressionKind::Ite {
558 condition,
559 consequence,
560 alternative,
561 } => {
562 let condition = Box::new(Self::lower_expr(hir, sr_map, condition));
563 let consequence = Box::new(Self::lower_expr(hir, sr_map, consequence));
564 let alternative = Box::new(Self::lower_expr(hir, sr_map, alternative));
565 mir::ExpressionKind::Ite {
566 condition,
567 consequence,
568 alternative,
569 }
570 }
571 ExpressionKind::Tuple(elements) => {
572 let elements = elements
573 .iter()
574 .map(|element| Self::lower_expr(hir, sr_map, element))
575 .collect::<Vec<mir::Expression>>();
576 mir::ExpressionKind::Tuple(elements)
577 }
578 ExpressionKind::TupleAccess(tuple, element_pos) => {
579 let tuple = Box::new(Self::lower_expr(hir, sr_map, tuple));
580 let element_pos = *element_pos;
581 mir::ExpressionKind::TupleAccess(tuple, element_pos)
582 }
583 ExpressionKind::Function(kind) => {
584 let FnExprKind { name, args, .. } = kind;
585 match name.as_ref() {
586 "cast" => {
587 assert_eq!(args.len(), 1);
588 let expr = Box::new(Self::lower_expr(hir, sr_map, &args[0]));
589 mir::ExpressionKind::Convert { expr }
590 }
591 _ => {
592 let args = args
593 .iter()
594 .map(|arg| Self::lower_expr(hir, sr_map, arg))
595 .collect::<Vec<mir::Expression>>();
596 mir::ExpressionKind::Function(name.clone(), args)
597 }
598 }
599 }
600 ExpressionKind::Widen(kind) => {
601 let WidenExprKind { expr, .. } = kind;
602 let expr = Box::new(Self::lower_expr(hir, sr_map, expr));
603 mir::ExpressionKind::Convert { expr }
604 }
605 ExpressionKind::Default { expr, default } => {
606 let expr = Box::new(Self::lower_expr(hir, sr_map, expr));
607 let default = Box::new(Self::lower_expr(hir, sr_map, default));
608 mir::ExpressionKind::Default { expr, default }
609 }
610 }
611 }
612
613 fn lower_constant(constant: &Constant, ty: &mir::Type) -> mir::Constant {
614 match constant {
615 Constant::Basic(lit) => Self::lower_constant_literal(lit, ty),
616 Constant::Inlined(Inlined { lit, .. }) => Self::lower_constant_literal(lit, ty),
617 }
618 }
619
620 fn lower_constant_literal(constant: &Literal, ty: &mir::Type) -> mir::Constant {
621 match constant {
622 Literal::Str(s) => mir::Constant::Str(s.clone()),
623 Literal::Bool(b) => mir::Constant::Bool(*b),
624 Literal::Integer(i) => match ty {
625 mir::Type::Int(_) => mir::Constant::Int(*i),
626 mir::Type::UInt(_) => mir::Constant::UInt(*i as u64),
627 _ => unreachable!(),
628 },
629 Literal::SInt(i) => {
630 match ty {
632 mir::Type::Int(_) => mir::Constant::Int(*i as i64),
633 mir::Type::UInt(_) => mir::Constant::UInt(*i as u64),
634 _ => unreachable!(),
635 }
636 }
637 Literal::Decimal(f) => match ty {
638 mir::Type::Float(_) => mir::Constant::Float(f.to_f64().unwrap()),
639 mir::Type::Fixed(_) | mir::Type::UFixed(_) => mir::Constant::Decimal(*f),
640 _ => unreachable!(),
641 },
642 }
643 }
644
645 fn lower_arith_log_op(op: ArithLogOp) -> mir::ArithLogOp {
646 match op {
647 ArithLogOp::Not => mir::ArithLogOp::Not,
648 ArithLogOp::Neg => mir::ArithLogOp::Neg,
649 ArithLogOp::Add => mir::ArithLogOp::Add,
650 ArithLogOp::Sub => mir::ArithLogOp::Sub,
651 ArithLogOp::Mul => mir::ArithLogOp::Mul,
652 ArithLogOp::Div => mir::ArithLogOp::Div,
653 ArithLogOp::Rem => mir::ArithLogOp::Rem,
654 ArithLogOp::Pow => mir::ArithLogOp::Pow,
655 ArithLogOp::And => mir::ArithLogOp::And,
656 ArithLogOp::Or => mir::ArithLogOp::Or,
657 ArithLogOp::BitXor => mir::ArithLogOp::BitXor,
658 ArithLogOp::BitAnd => mir::ArithLogOp::BitAnd,
659 ArithLogOp::BitOr => mir::ArithLogOp::BitOr,
660 ArithLogOp::BitNot => mir::ArithLogOp::BitNot,
661 ArithLogOp::Shl => mir::ArithLogOp::Shl,
662 ArithLogOp::Shr => mir::ArithLogOp::Shr,
663 ArithLogOp::Eq => mir::ArithLogOp::Eq,
664 ArithLogOp::Lt => mir::ArithLogOp::Lt,
665 ArithLogOp::Le => mir::ArithLogOp::Le,
666 ArithLogOp::Ne => mir::ArithLogOp::Ne,
667 ArithLogOp::Ge => mir::ArithLogOp::Ge,
668 ArithLogOp::Gt => mir::ArithLogOp::Gt,
669 }
670 }
671
672 fn lower_window_operation(op: WindowOperation) -> mir::WindowOperation {
673 match op {
674 WindowOperation::Count => mir::WindowOperation::Count,
675 WindowOperation::Min => mir::WindowOperation::Min,
676 WindowOperation::Max => mir::WindowOperation::Max,
677 WindowOperation::Sum => mir::WindowOperation::Sum,
678 WindowOperation::Product => mir::WindowOperation::Product,
679 WindowOperation::Average => mir::WindowOperation::Average,
680 WindowOperation::Integral => mir::WindowOperation::Integral,
681 WindowOperation::Conjunction => mir::WindowOperation::Conjunction,
682 WindowOperation::Disjunction => mir::WindowOperation::Disjunction,
683 WindowOperation::Last => mir::WindowOperation::Last,
684 WindowOperation::Variance => mir::WindowOperation::Variance,
685 WindowOperation::Covariance => mir::WindowOperation::Covariance,
686 WindowOperation::StandardDeviation => mir::WindowOperation::StandardDeviation,
687 WindowOperation::NthPercentile(x) => mir::WindowOperation::NthPercentile(x),
688 WindowOperation::TrueRatio => unreachable!("True Ratio is Syntactic Sugar"),
689 }
690 }
691
692 fn lower_instance_operation(op: InstanceOperation) -> mir::InstanceOperation {
693 match op {
694 InstanceOperation::Count => mir::InstanceOperation::Count,
695 InstanceOperation::Min => mir::InstanceOperation::Min,
696 InstanceOperation::Max => mir::InstanceOperation::Max,
697 InstanceOperation::Sum => mir::InstanceOperation::Sum,
698 InstanceOperation::Product => mir::InstanceOperation::Product,
699 InstanceOperation::Average => mir::InstanceOperation::Average,
700 InstanceOperation::Conjunction => mir::InstanceOperation::Conjunction,
701 InstanceOperation::Disjunction => mir::InstanceOperation::Disjunction,
702 InstanceOperation::Variance => mir::InstanceOperation::Variance,
703 InstanceOperation::Covariance => mir::InstanceOperation::Covariance,
704 InstanceOperation::StandardDeviation => mir::InstanceOperation::StandardDeviation,
705 InstanceOperation::NthPercentile(x) => mir::InstanceOperation::NthPercentile(x),
706 InstanceOperation::TrueRatio => unreachable!("True Ratio is Syntactic Sugar"),
707 }
708 }
709
710 fn lower_instance_selection(
711 sel: &InstanceSelection,
712 hir: &RtLolaHir<CompleteMode>,
713 wref: WindowReference,
714 sr_map: &HashMap<StreamReference, StreamReference>,
715 ) -> mir::InstanceSelection {
716 match sel {
717 InstanceSelection::Fresh => mir::InstanceSelection::Fresh,
718 InstanceSelection::All => mir::InstanceSelection::All,
719 InstanceSelection::FilteredFresh { parameters, cond } => {
720 let target = hir.single_instance_aggregation(wref).target;
721 mir::InstanceSelection::FilteredFresh {
722 parameters: Self::lower_parameters(parameters.iter(), hir, target),
723 cond: Box::new(Self::lower_expr(hir, sr_map, cond)),
724 }
725 }
726 InstanceSelection::FilteredAll { parameters, cond } => {
727 let target = hir.single_instance_aggregation(wref).target;
728 mir::InstanceSelection::FilteredAll {
729 parameters: Self::lower_parameters(parameters.iter(), hir, target),
730 cond: Box::new(Self::lower_expr(hir, sr_map, cond)),
731 }
732 }
733 }
734 }
735
736 fn lower_stream_access_kind(kind: StreamAccessKind) -> mir::StreamAccessKind {
737 match kind {
738 StreamAccessKind::Sync => mir::StreamAccessKind::Sync,
739 StreamAccessKind::DiscreteWindow(wref) => mir::StreamAccessKind::DiscreteWindow(wref),
740 StreamAccessKind::SlidingWindow(wref) => mir::StreamAccessKind::SlidingWindow(wref),
741 StreamAccessKind::InstanceAggregation(wref) => {
742 mir::StreamAccessKind::InstanceAggregation(wref)
743 }
744 StreamAccessKind::Hold => mir::StreamAccessKind::Hold,
745 StreamAccessKind::Offset(o) => mir::StreamAccessKind::Offset(Self::lower_offset(o)),
746 StreamAccessKind::Get => mir::StreamAccessKind::Get,
747 StreamAccessKind::Fresh => mir::StreamAccessKind::Fresh,
748 }
749 }
750
751 fn lower_offset(offset: Offset) -> mir::Offset {
752 match offset {
753 Offset::FutureDiscrete(o) => mir::Offset::Future(o),
754 Offset::PastDiscrete(o) => mir::Offset::Past(o),
755 Offset::FutureRealTime(_) | Offset::PastRealTime(_) => {
756 unreachable!("Real-time Lookups should be already transformed to discrete lookups.")
757 }
758 }
759 }
760
761 fn lower_accessed_streams(
762 sr_map: &HashMap<StreamReference, StreamReference>,
763 streams: Vec<(StreamReference, Vec<(Origin, StreamAccessKind)>)>,
764 ) -> Vec<(StreamReference, Vec<(Origin, mir::StreamAccessKind)>)> {
765 streams
766 .into_iter()
767 .map(|(sref, kinds)| {
768 (
769 sr_map[&sref],
770 kinds
771 .into_iter()
772 .map(|(origin, kind)| (origin, Self::lower_stream_access_kind(kind)))
773 .collect(),
774 )
775 })
776 .collect()
777 }
778
779 fn lower_parameters<'a>(
780 params: impl Iterator<Item = &'a rtlola_hir::hir::Parameter>,
781 hir: &RtLolaHir<CompleteMode>,
782 sr: StreamReference,
783 ) -> Vec<mir::Parameter> {
784 params
785 .map(|parameter| mir::Parameter {
786 name: parameter.name.clone(),
787 ty: Self::lower_value_type(&hir.get_parameter_type(sr, parameter.index())),
788 idx: parameter.index(),
789 #[cfg(feature = "spanned")]
790 span: parameter.span(),
791 })
792 .collect()
793 }
794}
795
796#[cfg(test)]
797#[cfg(not(feature = "spanned"))]
798mod tests {
799 use num::rational::Rational64 as Rational;
800 use num::FromPrimitive;
801 use rtlola_hir::config::FrontendConfig;
802 use rtlola_parser::ParserConfig;
803 use uom::si::frequency::hertz;
804 use uom::si::rational64::Frequency as UOM_Frequency;
805
806 use super::*;
807 use crate::mir::IntTy::Int8;
808 use crate::mir::{PacingType, Stream};
809
810 fn lower_spec(spec: &str) -> (RtLolaHir<CompleteMode>, mir::RtLolaMir) {
811 lower_spec_with_config((&ParserConfig::for_string(spec.into())).into())
812 }
813
814 fn lower_spec_with_config(config: FrontendConfig) -> (RtLolaHir<CompleteMode>, mir::RtLolaMir) {
815 let ast = config
816 .parser_config()
817 .parse()
818 .unwrap_or_else(|e| panic!("{:?}", e));
819 let hir = rtlola_hir::fully_analyzed(ast, &config).expect("Invalid AST:");
820 (hir.clone(), Mir::from_hir(hir))
821 }
822
823 #[test]
824 fn check_event_based_streams() {
825 let spec = "input a: Float64\ninput b:Float64\noutput c := a + b\noutput d := a.hold().defaults(to:0.0) + b\noutput e := a + 9.0\ntrigger d < e\ntrigger a < 5.0";
826 let (hir, mir) = lower_spec(spec);
827
828 assert_eq!(mir.inputs.len(), 2);
829 assert_eq!(mir.outputs.len(), 5);
830 assert_eq!(mir.event_driven.len(), 5);
831 assert_eq!(mir.time_driven.len(), 0);
832 assert_eq!(mir.discrete_windows.len(), 0);
833 assert_eq!(mir.sliding_windows.len(), 0);
834 assert_eq!(mir.triggers.len(), 2);
835 let hir_a = hir.inputs().find(|i| i.name == "a".to_string()).unwrap();
836 let mir_a = mir
837 .inputs
838 .iter()
839 .find(|i| i.name == "a".to_string())
840 .unwrap();
841 assert_eq!(hir_a.sr(), mir_a.reference);
842 let hir_d = hir.outputs().find(|i| i.name() == "d".to_string()).unwrap();
843 let mir_d = mir
844 .outputs
845 .iter()
846 .find(|i| i.name == "d".to_string())
847 .unwrap();
848 assert_eq!(hir_d.sr(), mir_d.reference);
849 }
850
851 #[test]
852 fn check_time_driven_streams() {
853 let spec = "input a: Int64\ninput b:Int64\noutput c @1Hz:= a.aggregate(over: 2s, using: sum)+ b.aggregate(over: 4s, using:sum)\noutput d @4Hz:= a.hold().defaults(to:0) + b.hold().defaults(to: 0)\noutput e @0.5Hz := a.aggregate(over: 4s, using: sum) + 9\ntrigger d < e";
854 let (hir, mir) = lower_spec(spec);
855
856 assert_eq!(mir.inputs.len(), 2);
857 assert_eq!(mir.outputs.len(), 4);
858 assert_eq!(mir.event_driven.len(), 0);
859 assert_eq!(mir.time_driven.len(), 4);
860 assert_eq!(mir.discrete_windows.len(), 0);
861 assert_eq!(mir.sliding_windows.len(), 3);
862 assert_eq!(mir.triggers.len(), 1);
863 let hir_a = hir.inputs().find(|i| i.name == "a".to_string()).unwrap();
864 let mir_a = mir
865 .inputs
866 .iter()
867 .find(|i| i.name == "a".to_string())
868 .unwrap();
869 assert_eq!(hir_a.sr(), mir_a.reference);
870 let hir_d = hir.outputs().find(|i| i.name() == "d".to_string()).unwrap();
871 let mir_d = mir
872 .outputs
873 .iter()
874 .find(|i| i.name == "d".to_string())
875 .unwrap();
876 assert_eq!(hir_d.sr(), mir_d.reference);
877 }
878
879 #[test]
880 fn check_stream_with_parameter() {
881 let spec = "input a: Int8\n\
882 output d(para) spawn with a when a > 6 eval @a with para";
883 let (hir, mir) = lower_spec(spec);
884
885 assert_eq!(mir.inputs.len(), 1);
886 assert_eq!(mir.outputs.len(), 1);
887 assert_eq!(mir.event_driven.len(), 1);
888 assert_eq!(mir.time_driven.len(), 0);
889 assert_eq!(mir.discrete_windows.len(), 0);
890 assert_eq!(mir.sliding_windows.len(), 0);
891 assert_eq!(mir.triggers.len(), 0);
892 let hir_a = hir.inputs().find(|i| i.name == "a".to_string()).unwrap();
893 let mir_a = mir
894 .inputs
895 .iter()
896 .find(|i| i.name == "a".to_string())
897 .unwrap();
898 assert_eq!(hir_a.sr(), mir_a.reference);
899 let hir_d = hir.outputs().find(|i| i.name() == "d".to_string()).unwrap();
900 let mir_d = mir
901 .outputs
902 .iter()
903 .find(|i| i.name == "d".to_string())
904 .unwrap();
905 assert_eq!(hir_d.sr(), mir_d.reference);
906 assert_eq!(
907 &mir_d.spawn.expression,
908 &Some(mir::Expression {
909 kind: mir::ExpressionKind::StreamAccess {
910 target: mir_a.reference,
911 parameters: vec![],
912 access_kind: mir::StreamAccessKind::Sync,
913 },
914 ty: mir::Type::Int(Int8),
915 })
916 );
917 assert!(matches!(
918 &mir_d.spawn.condition,
919 &Some(mir::Expression {
920 kind: mir::ExpressionKind::ArithLog(mir::ArithLogOp::Gt, _),
921 ty: _,
922 })
923 ));
924 assert_eq!(
925 &mir_d.spawn.pacing,
926 &PacingType::Event(mir::ActivationCondition::Stream(mir_a.reference))
927 );
928 assert_eq!(
929 &mir_d.eval.clauses[0].expression,
930 &mir::Expression {
931 kind: mir::ExpressionKind::ParameterAccess(mir_d.reference, 0),
932 ty: mir::Type::Int(Int8),
933 }
934 );
935 }
936
937 #[test]
938 fn check_spawn_filter_close() {
939 let spec = "input a: Int8\n\
940 output d spawn @1Hz when a.hold().defaults(to:0) > 6 eval @a when a = 42 with a close when a = 1337";
941 let (_, mir) = lower_spec(spec);
942
943 assert_eq!(mir.inputs.len(), 1);
944 assert_eq!(mir.outputs.len(), 1);
945 assert_eq!(mir.event_driven.len(), 1);
946 assert_eq!(mir.time_driven.len(), 0);
947 assert_eq!(mir.discrete_windows.len(), 0);
948 assert_eq!(mir.sliding_windows.len(), 0);
949 assert_eq!(mir.triggers.len(), 0);
950
951 let mir_d = mir
952 .outputs
953 .iter()
954 .find(|i| i.name == "d".to_string())
955 .unwrap();
956
957 assert!(mir_d.spawn.expression.is_none());
958 assert!(matches!(
959 &mir_d.spawn.condition,
960 Some(mir::Expression {
961 kind: mir::ExpressionKind::ArithLog(mir::ArithLogOp::Gt, _),
962 ty: _,
963 })
964 ));
965 assert_eq!(
966 mir_d.spawn.pacing,
967 PacingType::GlobalPeriodic(UOM_Frequency::new::<hertz>(Rational::from_u8(1).unwrap()))
968 );
969 assert!(matches!(
970 mir_d.eval.clauses[0].condition,
971 Some(mir::Expression {
972 kind: mir::ExpressionKind::ArithLog(mir::ArithLogOp::Eq, _),
973 ty: _,
974 })
975 ));
976 assert!(matches!(
977 mir_d.close.condition,
978 Some(mir::Expression {
979 kind: mir::ExpressionKind::ArithLog(mir::ArithLogOp::Eq, _),
980 ty: _,
981 })
982 ));
983 }
984
985 #[test]
986 fn test_periodic_trigger() {
987 let spec = "input a: Bool\n\
988 trigger @1Hz a.hold(or: false)";
989 let (_, mir) = lower_spec(spec);
990
991 assert_eq!(mir.inputs.len(), 1);
992 assert_eq!(mir.outputs.len(), 1);
993 assert_eq!(mir.event_driven.len(), 0);
994 assert_eq!(mir.time_driven.len(), 1);
995 assert_eq!(mir.discrete_windows.len(), 0);
996 assert_eq!(mir.sliding_windows.len(), 0);
997 assert_eq!(mir.triggers.len(), 1);
998 }
999
1000 #[test]
1001 fn test_instance_window() {
1002 let spec = "input a: Int32\n\
1003 output b(p: Bool) spawn with a == 42 eval with a\n\
1004 output c @1Hz := b(false).aggregate(over: 1s, using: sum)";
1005 let (_, mir) = lower_spec(spec);
1006
1007 let expr = mir.outputs[1].eval.clauses[0].expression.kind.clone();
1008 assert!(
1009 matches!(expr, mir::ExpressionKind::StreamAccess {target: _, parameters: paras, access_kind: mir::StreamAccessKind::SlidingWindow(_)} if paras.len() == 1)
1010 );
1011 }
1012
1013 #[test]
1014 fn test_cast_lowering() {
1015 let spec = "input a: Int64\n\
1016 output b := cast<Int64, Float64>(a)";
1017 let (_, mir) = lower_spec(spec);
1018 assert!(matches!(
1019 mir.outputs[0].eval.clauses[0].expression.kind,
1020 mir::ExpressionKind::Convert { expr: _ }
1021 ));
1022 }
1023
1024 #[test]
1025 fn test_multiple_eval_clauses() {
1026 let spec = "input a: Int64\ninput b: Int64\ninput c: Bool\n\
1027 output d eval @(c&&a) when c with a eval @(c&&b) when !c with b";
1028 let (_, mir) = lower_spec(spec);
1029 assert_eq!(mir.outputs.len(), 1);
1030 assert_eq!(mir.inputs.len(), 3);
1031 assert_eq!(mir.triggers.len(), 0);
1032
1033 let output = &mir.outputs[0];
1034 assert_eq!(output.eval.clauses.len(), 2);
1035 assert_eq!(
1036 output.eval.eval_pacing,
1037 PacingType::Event(mir::ActivationCondition::Disjunction(vec![
1038 mir::ActivationCondition::Conjunction(vec![
1039 mir::ActivationCondition::Stream(StreamReference::In(0)),
1041 mir::ActivationCondition::Stream(StreamReference::In(1)),
1042 mir::ActivationCondition::Stream(StreamReference::In(2)),
1043 ]),
1044 mir::ActivationCondition::Conjunction(vec![
1045 mir::ActivationCondition::Stream(StreamReference::In(0)),
1046 mir::ActivationCondition::Stream(StreamReference::In(2)),
1047 ]),
1048 mir::ActivationCondition::Conjunction(vec![
1049 mir::ActivationCondition::Stream(StreamReference::In(1)),
1050 mir::ActivationCondition::Stream(StreamReference::In(2)),
1051 ]),
1052 ]))
1053 );
1054 assert_eq!(
1055 output.eval.clauses[0].pacing,
1056 PacingType::Event(mir::ActivationCondition::Conjunction(vec![
1057 mir::ActivationCondition::Stream(StreamReference::In(0)),
1058 mir::ActivationCondition::Stream(StreamReference::In(2)),
1059 ]))
1060 );
1061 assert_eq!(
1062 output.eval.clauses[1].pacing,
1063 PacingType::Event(mir::ActivationCondition::Conjunction(vec![
1064 mir::ActivationCondition::Stream(StreamReference::In(1)),
1065 mir::ActivationCondition::Stream(StreamReference::In(2)),
1066 ]))
1067 );
1068 }
1069
1070 #[test]
1071 fn spawn_close_ac_only() {
1072 let spec = "input a : UInt64\noutput b spawn @a eval @true with true";
1073 let (_, mir) = lower_spec(spec);
1074 assert!(mir.outputs[0].is_spawned());
1075
1076 let spec = "input a : UInt64\noutput b spawn when a == 0 eval @Global(1Hz) with true close @Local(5s)";
1077 let (_, mir) = lower_spec(spec);
1078 assert!(mir.outputs[0].is_closed());
1079 }
1080
1081 #[test]
1082 fn tagged_streams() {
1083 let spec = "#[key=\"value\", key2=\"value2\"]\n\
1084 input a : Bool
1085 #[warning]
1086 trigger a";
1087 let (_, mir) = lower_spec(spec);
1088 let input_tags = &mir.inputs[0].tags;
1089 assert_eq!(input_tags.len(), 2);
1090 assert!(input_tags["key"].as_ref().unwrap() == "value");
1091 assert!(input_tags["key2"].as_ref().unwrap() == "value2");
1092 let trigger_tags = &mir.outputs[0].tags;
1093 assert_eq!(trigger_tags.len(), 1);
1094 assert_eq!(trigger_tags["warning"], None);
1095 }
1096
1097 #[test]
1098 fn global_tags() {
1099 let spec = "#![key=\"value\"]\n\
1100 #![warning]
1101 input a : Bool";
1102 let (_, mir) = lower_spec(spec);
1103 let global_tags = &mir.global_tags;
1104 assert_eq!(global_tags.len(), 2);
1105 assert_eq!(global_tags["key"].as_ref().unwrap(), "value");
1106 assert_eq!(global_tags["warning"].as_ref(), None);
1107 }
1108
1109 #[test]
1110 fn lower_true_ratio_aggregation() {
1111 let spec = "input a: Bool\n\
1112 output b (p) spawn with a eval when a = p with a\n\
1113 output c (p) spawn with a eval @1Hz with b(p).aggregate(over: 1s, using: true_ratio).defaults(to: 0.0)\n";
1114 let (_, _) = lower_spec(spec);
1115 }
1116
1117 #[test]
1118 fn lower_true_ratio_instance_aggregation() {
1119 let spec = "input a: UInt8\n\
1120 output a' (p) spawn @a with a eval @a when a = p with a + p > 5\n\
1121 output b eval @1Hz with a'.aggregate(over_instances: all, using: true_ratio).defaults(to: 0.0)";
1122 let (_, _) = lower_spec(spec);
1123 }
1124
1125 #[test]
1126 fn test_probability() {
1127 let spec = "input a : UInt64\n\
1128 output c := prob(of: a > 10, given: a < 5)";
1129 let (_, _) = lower_spec(spec);
1130 }
1131
1132 #[test]
1133 fn test_probability_parameterized() {
1134 let spec = r#"import math
1135
1136input id : Int64
1137input sensible_feature: String
1138input associated_feature: Int64
1139input decision: Bool
1140input idDec: Int64
1141
1142/// Database
1143output sensible_feature_per(user)
1144 spawn with id
1145 eval when id == user with sensible_feature
1146 close @(decision & idDec) when user == idDec
1147
1148output relation_per(f)
1149 spawn with sensible_feature
1150 eval with prob(of: decision, given: sensible_feature_per(idDec).hold(or: "D") == f)
1151
1152output statParity
1153 eval @true with abs(relation_per("M").hold(or: 1.0) - relation_per("F").hold(or: 1.0))
1154
1155trigger statParity > 0.1"#;
1156 let (_, _) = lower_spec(spec);
1157 }
1158}