1use crate::computation::rational::{NumericFailure, RationalInteger};
6use crate::formatting::expression::{CommandView, PredicateView, Syntax, SyntaxNode, SyntaxView};
7use crate::parsing::arena::{ExprKind, ExprRef};
8use crate::parsing::ast::{
9 CalendarPeriodUnit, DateCalendarKind, DateRelativeKind, ListPredicateForm, LogicalConnective,
10};
11use crate::planning::execution_plan::ExecutionPlan;
12use crate::planning::instance_list::RowHop;
13use crate::planning::ordered_dispatch::DispatchKey;
14use crate::planning::semantics::{
15 primitive_boolean_arc, primitive_date_arc, primitive_number_arc, primitive_ratio_arc,
16 primitive_text_arc, primitive_time_arc, ArithmeticComputation, ComparisonComputation,
17 DataDefinition, DataPath, Expression, ExpressionKind, LemmaType, ListPredKind, LiteralValue,
18 MathematicalComputation, PathSegment, RangeBound, RoundingMode, RowEnd, RulePath,
19 SemanticConversionTarget, Source, SpecIdentity, ValueKind, VetoExpression,
20};
21use crate::planning::typing::{
22 comparison_type, compute_arithmetic_result_type, date_predicate_type,
23 infer_range_type_from_endpoint_types, logical_connective_type, logical_not_type, math_op_type,
24 past_future_range_type, piecewise_type, quantity_integer_power_type, range_bound_result_type,
25 range_containment_type, result_is_veto_type, step_rounding_type, unit_conversion_type,
26 MeasureScope,
27};
28use crate::Error;
29use indexmap::IndexMap;
30use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
31use std::sync::Arc;
32
33mod rewrite;
34
35fn normalization_error(source: Option<Source>, failure: NumericFailure, context: &str) -> Error {
36 Error::validation(format!("{context}: {failure}"), source, None::<String>)
37}
38
39fn literal_from_folded_rational(rational: RationalInteger) -> LiteralValue {
44 LiteralValue::number(rational)
45}
46
47pub(crate) struct NormalizedRule {
49 pub(crate) body: NormalFormId,
50 pub(crate) source_type: Arc<LemmaType>,
53}
54
55pub(crate) struct NormalizeContext<'a> {
58 pub(crate) data: &'a IndexMap<DataPath, DataDefinition>,
59 pub(crate) row_templates: &'a BTreeMap<SpecIdentity, Arc<ExecutionPlan>>,
61 pub(crate) self_identity: &'a SpecIdentity,
63 pub(crate) rule_order: &'a [RulePath],
65 pub(crate) measure_scope: MeasureScope<'a>,
67 pub(crate) max_normalized_expression_nodes: usize,
68}
69
70struct LowerCtx<'a> {
72 completed_rules: &'a HashMap<RulePath, NormalFormId>,
73 rule_types: &'a HashMap<RulePath, Arc<LemmaType>>,
75 data: &'a IndexMap<DataPath, DataDefinition>,
76 row_templates: &'a BTreeMap<SpecIdentity, Arc<ExecutionPlan>>,
77 self_identity: &'a SpecIdentity,
78 rule_order: &'a [RulePath],
79}
80
81impl LowerCtx<'_> {
82 fn row_member_chain(
84 &self,
85 identity: &SpecIdentity,
86 path: &[String],
87 name: &str,
88 ) -> Option<Vec<RowHop>> {
89 let parts: Vec<&str> = path
90 .iter()
91 .map(String::as_str)
92 .chain(std::iter::once(name))
93 .collect();
94 let rules: BTreeSet<RulePath>;
95 let view = if identity == self.self_identity {
96 rules = self.rule_order.iter().cloned().collect();
97 crate::planning::instance_list::RowEndView::Root {
98 data: self.data,
99 rules: &rules,
100 }
101 } else {
102 crate::planning::instance_list::RowEndView::Plan(self.template(identity))
103 };
104 crate::planning::instance_list::row_end_chain(identity, view, self.row_templates, &parts)
105 }
106
107 fn row_end_type(&self, identity: &SpecIdentity, end: &RowEnd) -> Arc<LemmaType> {
108 if identity != self.self_identity {
109 return crate::planning::instance_list::row_end_type(self.template(identity), end);
110 }
111 match end {
112 RowEnd::Data(path) => crate::planning::instance_list::row_data_type(self.data, path),
113 RowEnd::Rule(rule) => Arc::clone(self.rule_types.get(rule).unwrap_or_else(|| {
114 panic!("BUG: row rule '{}' has no rule type", rule.input_key())
115 })),
116 }
117 }
118
119 fn template(&self, identity: &SpecIdentity) -> &ExecutionPlan {
120 self.row_templates
121 .get(identity)
122 .unwrap_or_else(|| panic!("BUG: row field of '{identity}' without a template plan"))
123 }
124}
125
126pub(crate) fn build_normalized_rule(
127 ctx: &NormalizeContext<'_>,
128 completed_rules: &HashMap<RulePath, NormalFormId>,
129 rule_types: &HashMap<RulePath, Arc<LemmaType>>,
130 expression: &Expression,
131 source: Option<Source>,
132 interner: &mut NormalFormInterner,
133) -> Result<NormalizedRule, Error> {
134 let max_normalized_expression_nodes = ctx.max_normalized_expression_nodes;
135 let lower = LowerCtx {
136 completed_rules,
137 rule_types,
138 data: ctx.data,
139 row_templates: ctx.row_templates,
140 self_identity: ctx.self_identity,
141 rule_order: ctx.rule_order,
142 };
143
144 let mut cells = Cells::new(interner, ctx.measure_scope);
145 let root = to_normal_form(expression, &mut cells, &lower);
146 let source_type = Arc::clone(cells.result_type(root));
147 let nf = rewrite::normalize(root, cells, source.clone())?;
148
149 if normal_form_exceeds_node_budget(&interner.forms, nf, max_normalized_expression_nodes) {
150 return Err(expression_node_limit_error(
151 max_normalized_expression_nodes,
152 source,
153 ));
154 }
155
156 Ok(NormalizedRule {
157 body: nf,
158 source_type,
159 })
160}
161
162fn expression_node_limit_error(limit: usize, source: Option<Source>) -> Error {
163 Error::resource_limit_exceeded(
164 "max_normalized_expression_nodes",
165 format!("{limit} expression nodes"),
166 format!(
167 "more than {limit} unique normal-form cells reachable from the rule root (rule references count as one cell)"
168 ),
169 "Restructure the rule or reduce repeated references to other rules",
170 source,
171 None,
172 None,
173 )
174}
175
176pub(crate) fn normal_form_exceeds_node_budget(
182 forms: &[NormalForm],
183 root: NormalFormId,
184 budget: usize,
185) -> bool {
186 let mut visited = HashSet::new();
187 let mut worklist = vec![root];
188 while let Some(current) = worklist.pop() {
189 if !visited.insert(current) {
190 continue;
191 }
192 if visited.len() > budget {
193 return true;
194 }
195 let cell = forms.get(current.index()).unwrap_or_else(|| {
196 panic!(
197 "BUG: NormalFormId {} out of range during node budget walk (table len {})",
198 current.0,
199 forms.len()
200 )
201 });
202 if cell.rule_ref.is_some() {
203 continue;
204 }
205 cell.kind.for_each_child(|child| worklist.push(child));
206 if let Some(origin) = cell.origin {
207 worklist.push(origin);
208 }
209 }
210 false
211}
212
213#[cfg(test)]
217pub(crate) fn normal_form_depth(forms: &[NormalForm], root: NormalFormId) -> usize {
218 let mut memo: HashMap<NormalFormId, usize> = HashMap::new();
219 let mut stack: Vec<(NormalFormId, bool)> = vec![(root, false)];
221 while let Some((id, children_pushed)) = stack.pop() {
222 if memo.contains_key(&id) {
223 continue;
224 }
225 let cell = forms.get(id.index()).unwrap_or_else(|| {
226 panic!(
227 "BUG: NormalFormId {} out of range during depth walk (table len {})",
228 id.0,
229 forms.len()
230 )
231 });
232 if cell.rule_ref.is_some() {
233 memo.insert(id, 1);
234 continue;
235 }
236 if !children_pushed {
237 stack.push((id, true));
238 cell.kind.for_each_child(|child| {
239 if !memo.contains_key(&child) {
240 stack.push((child, false));
241 }
242 });
243 if let Some(origin) = cell.origin {
244 if !memo.contains_key(&origin) {
245 stack.push((origin, false));
246 }
247 }
248 continue;
249 }
250 let mut depth = 1usize;
251 cell.kind.for_each_child(|child| {
252 let child_depth = *memo.get(&child).unwrap_or_else(|| {
253 panic!(
254 "BUG: child NormalFormId {} missing from depth memo after post-order push",
255 child.0
256 )
257 });
258 depth = depth.max(1 + child_depth);
259 });
260 if let Some(origin) = cell.origin {
261 let origin_depth = *memo.get(&origin).unwrap_or_else(|| {
262 panic!(
263 "BUG: origin NormalFormId {} missing from depth memo after post-order push",
264 origin.0
265 )
266 });
267 depth = depth.max(1 + origin_depth);
268 }
269 memo.insert(id, depth);
270 }
271 *memo
272 .get(&root)
273 .unwrap_or_else(|| panic!("BUG: root NormalFormId {} missing from depth memo", root.0))
274}
275
276#[derive(Clone, Debug, PartialEq, Eq, Hash)]
278pub enum LeafKind {
279 Literal(LiteralValue),
280 DataPath(crate::planning::semantics::DataPath),
281 RowValue {
283 level: u32,
284 },
285}
286
287#[derive(Clone, Debug, PartialEq, Eq, Hash)]
290pub struct RowRead {
291 pub ends: Vec<RowEnd>,
292 pub result_type: Arc<LemmaType>,
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
297pub struct NormalFormId(u32);
298
299impl NormalFormId {
300 pub fn index(self) -> usize {
301 self.0 as usize
302 }
303
304 pub fn from_index(index: usize) -> Self {
305 Self(u32::try_from(index).expect("BUG: normal_forms table exceeds u32::MAX"))
306 }
307}
308
309#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
313pub struct ArithmeticTerm {
314 pub operand: NormalFormId,
315 pub inverse: bool,
316}
317
318impl ArithmeticTerm {
319 pub(crate) fn direct(operand: NormalFormId) -> Self {
320 Self {
321 operand,
322 inverse: false,
323 }
324 }
325
326 pub(crate) fn inverted(operand: NormalFormId) -> Self {
327 Self {
328 operand,
329 inverse: true,
330 }
331 }
332}
333
334#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
336pub enum DispatchRegion {
337 Body(NormalFormId),
339 Conjunctive(NormalFormId),
343}
344
345impl DispatchRegion {
346 pub fn cell(self) -> NormalFormId {
347 match self {
348 DispatchRegion::Body(id) | DispatchRegion::Conjunctive(id) => id,
349 }
350 }
351
352 fn map_cell(self, map: &mut impl FnMut(NormalFormId) -> NormalFormId) -> Self {
353 match self {
354 DispatchRegion::Body(id) => DispatchRegion::Body(map(id)),
355 DispatchRegion::Conjunctive(id) => DispatchRegion::Conjunctive(map(id)),
356 }
357 }
358}
359
360#[derive(Clone, Debug, PartialEq, Eq, Hash)]
363pub enum NormalFormKind {
364 Leaf(LeafKind),
365 Sum(Vec<ArithmeticTerm>),
366 Product(Vec<ArithmeticTerm>),
367 Subtract(NormalFormId, NormalFormId),
368 Divide(NormalFormId, NormalFormId),
369 Power(NormalFormId, NormalFormId),
370 Modulo(NormalFormId, NormalFormId),
371 Negate(NormalFormId),
372 Reciprocal(NormalFormId),
373 Comparison(NormalFormId, ComparisonComputation, NormalFormId),
374 Logical(LogicalConnective, Vec<NormalFormId>),
376 Not(NormalFormId),
377 MathOp(MathematicalComputation, NormalFormId),
378 StepRounding(RoundingMode, NormalFormId, Option<NormalFormId>),
380 UnitConversion(NormalFormId, SemanticConversionTarget),
381 Veto(VetoExpression),
382 DateRelative(DateRelativeKind, NormalFormId),
383 DateCalendar(DateCalendarKind, CalendarPeriodUnit, NormalFormId),
384 RangeLiteral(NormalFormId, NormalFormId),
385 PastFutureRange(DateRelativeKind, NormalFormId),
386 RangeContainment(NormalFormId, NormalFormId),
387 RangeBound(RangeBound, NormalFormId),
388 ResultIsVeto(NormalFormId),
389 Now,
390 Piecewise(Vec<(NormalFormId, NormalFormId)>),
391 OrderedDispatch {
395 scrutinee: NormalFormId,
396 boundaries: Arc<[DispatchKey]>,
399 regions: Vec<DispatchRegion>,
401 },
402 SpecMember {
404 base: NormalFormId,
405 path: Vec<String>,
406 name: String,
407 row: RowRead,
408 },
409 RowField {
412 level: u32,
413 identity: SpecIdentity,
414 end: RowEnd,
415 qualifier: Option<Vec<String>>,
417 },
418 ListCommand {
421 list: NormalFormId,
422 op: NormalFormListOp,
423 args: Vec<NormalFormId>,
424 row_level: Option<u32>,
425 },
426}
427
428#[derive(Clone, Debug, PartialEq, Eq, Hash)]
429pub enum NormalFormListOp {
430 Find,
431 First,
432 Last,
433 Count,
434 Sum,
435 Avg,
436 Product,
437 Min,
438 Max,
439 Predicate {
441 kind: crate::planning::semantics::ListPredKind,
442 form: ListPredicateForm,
443 },
444 Sort {
445 descending: bool,
446 },
447 Top,
448 With,
449 WithRow {
451 identity: Arc<SpecIdentity>,
452 fields: Vec<DataPath>,
453 },
454 Key {
456 fields: Vec<DataPath>,
457 },
458}
459
460impl NormalFormKind {
461 pub fn for_each_child(&self, mut f: impl FnMut(NormalFormId)) {
464 match self {
465 NormalFormKind::Leaf(_) | NormalFormKind::Veto(_) | NormalFormKind::Now => {}
466 NormalFormKind::Sum(terms) | NormalFormKind::Product(terms) => {
467 terms.iter().for_each(|term| f(term.operand));
468 }
469 NormalFormKind::Logical(_, children) => children.iter().copied().for_each(f),
470 NormalFormKind::Subtract(a, b)
471 | NormalFormKind::Divide(a, b)
472 | NormalFormKind::Power(a, b)
473 | NormalFormKind::Modulo(a, b)
474 | NormalFormKind::Comparison(a, _, b)
475 | NormalFormKind::RangeLiteral(a, b)
476 | NormalFormKind::RangeContainment(a, b) => {
477 f(*a);
478 f(*b);
479 }
480 NormalFormKind::StepRounding(_, a, step) => {
481 f(*a);
482 if let Some(step) = step {
483 f(*step);
484 }
485 }
486 NormalFormKind::Negate(x)
487 | NormalFormKind::Reciprocal(x)
488 | NormalFormKind::Not(x)
489 | NormalFormKind::MathOp(_, x)
490 | NormalFormKind::UnitConversion(x, _)
491 | NormalFormKind::DateRelative(_, x)
492 | NormalFormKind::DateCalendar(_, _, x)
493 | NormalFormKind::PastFutureRange(_, x)
494 | NormalFormKind::RangeBound(_, x)
495 | NormalFormKind::ResultIsVeto(x) => f(*x),
496 NormalFormKind::Piecewise(arms) => arms.iter().for_each(|(condition, body)| {
497 f(*condition);
498 f(*body);
499 }),
500 NormalFormKind::OrderedDispatch {
501 scrutinee, regions, ..
502 } => {
503 f(*scrutinee);
504 regions.iter().for_each(|region| f(region.cell()));
505 }
506 NormalFormKind::SpecMember { base, .. } => f(*base),
507 NormalFormKind::RowField { .. } => {}
508 NormalFormKind::ListCommand { list, args, .. } => {
509 f(*list);
510 args.iter().copied().for_each(f);
511 }
512 }
513 }
514
515 pub fn children(&self) -> Vec<NormalFormId> {
517 let mut children = Vec::new();
518 self.for_each_child(|child| children.push(child));
519 children
520 }
521
522 pub(crate) fn map_children(&self, mut map: impl FnMut(NormalFormId) -> NormalFormId) -> Self {
525 match self {
526 NormalFormKind::Leaf(leaf) => NormalFormKind::Leaf(leaf.clone()),
527 NormalFormKind::Sum(terms) => NormalFormKind::Sum(
528 terms
529 .iter()
530 .map(|term| ArithmeticTerm {
531 operand: map(term.operand),
532 inverse: term.inverse,
533 })
534 .collect(),
535 ),
536 NormalFormKind::Product(terms) => NormalFormKind::Product(
537 terms
538 .iter()
539 .map(|term| ArithmeticTerm {
540 operand: map(term.operand),
541 inverse: term.inverse,
542 })
543 .collect(),
544 ),
545 NormalFormKind::Logical(connective, children) => {
546 NormalFormKind::Logical(*connective, children.iter().copied().map(map).collect())
547 }
548 NormalFormKind::Subtract(a, b) => NormalFormKind::Subtract(map(*a), map(*b)),
549 NormalFormKind::Divide(a, b) => NormalFormKind::Divide(map(*a), map(*b)),
550 NormalFormKind::Power(a, b) => NormalFormKind::Power(map(*a), map(*b)),
551 NormalFormKind::Modulo(a, b) => NormalFormKind::Modulo(map(*a), map(*b)),
552 NormalFormKind::Comparison(a, op, b) => {
553 NormalFormKind::Comparison(map(*a), op.clone(), map(*b))
554 }
555 NormalFormKind::RangeLiteral(a, b) => NormalFormKind::RangeLiteral(map(*a), map(*b)),
556 NormalFormKind::RangeContainment(a, b) => {
557 NormalFormKind::RangeContainment(map(*a), map(*b))
558 }
559 NormalFormKind::Negate(x) => NormalFormKind::Negate(map(*x)),
560 NormalFormKind::Reciprocal(x) => NormalFormKind::Reciprocal(map(*x)),
561 NormalFormKind::Not(x) => NormalFormKind::Not(map(*x)),
562 NormalFormKind::MathOp(op, x) => NormalFormKind::MathOp(op.clone(), map(*x)),
563 NormalFormKind::StepRounding(mode, x, step) => {
564 NormalFormKind::StepRounding(*mode, map(*x), step.map(map))
565 }
566 NormalFormKind::UnitConversion(x, target) => {
567 NormalFormKind::UnitConversion(map(*x), target.clone())
568 }
569 NormalFormKind::Veto(v) => NormalFormKind::Veto(v.clone()),
570 NormalFormKind::DateRelative(kind, x) => NormalFormKind::DateRelative(*kind, map(*x)),
571 NormalFormKind::DateCalendar(kind, unit, x) => {
572 NormalFormKind::DateCalendar(*kind, *unit, map(*x))
573 }
574 NormalFormKind::PastFutureRange(kind, x) => {
575 NormalFormKind::PastFutureRange(*kind, map(*x))
576 }
577 NormalFormKind::RangeBound(bound, x) => NormalFormKind::RangeBound(*bound, map(*x)),
578 NormalFormKind::ResultIsVeto(x) => NormalFormKind::ResultIsVeto(map(*x)),
579 NormalFormKind::Now => NormalFormKind::Now,
580 NormalFormKind::Piecewise(arms) => NormalFormKind::Piecewise(
581 arms.iter()
582 .map(|(condition, body)| (map(*condition), map(*body)))
583 .collect(),
584 ),
585 NormalFormKind::OrderedDispatch {
586 scrutinee,
587 boundaries,
588 regions,
589 } => NormalFormKind::OrderedDispatch {
590 scrutinee: map(*scrutinee),
591 boundaries: boundaries.clone(),
592 regions: regions
593 .iter()
594 .map(|region| region.map_cell(&mut map))
595 .collect(),
596 },
597 NormalFormKind::SpecMember {
598 base,
599 path,
600 name,
601 row,
602 } => NormalFormKind::SpecMember {
603 base: map(*base),
604 path: path.clone(),
605 name: name.clone(),
606 row: row.clone(),
607 },
608 NormalFormKind::RowField { .. } => self.clone(),
609 NormalFormKind::ListCommand {
610 list,
611 op,
612 args,
613 row_level,
614 } => NormalFormKind::ListCommand {
615 list: map(*list),
616 op: op.clone(),
617 args: args.iter().copied().map(map).collect(),
618 row_level: *row_level,
619 },
620 }
621 }
622}
623
624#[derive(Clone, Debug)]
634pub struct NormalForm {
635 pub kind: NormalFormKind,
636 pub result_type: Arc<LemmaType>,
638 pub fold_types: Vec<Arc<LemmaType>>,
641 pub source: Option<Source>,
643 pub origin: Option<NormalFormId>,
645 pub rule_ref: Option<RulePath>,
647}
648
649pub(crate) struct Typed {
652 pub(crate) result_type: Arc<LemmaType>,
653 pub(crate) fold_types: Vec<Arc<LemmaType>>,
654}
655
656type ConsKey = (NormalFormKind, Option<RulePath>, Arc<LemmaType>);
662
663pub(crate) struct NormalFormInterner {
665 forms: Vec<NormalForm>,
666 cons: HashMap<ConsKey, NormalFormId>,
667}
668
669impl NormalFormInterner {
670 pub(crate) fn new() -> Self {
671 Self {
672 forms: Vec::new(),
673 cons: HashMap::new(),
674 }
675 }
676
677 pub(crate) fn intern(
678 &mut self,
679 kind: NormalFormKind,
680 source: Option<Source>,
681 origin: Option<NormalFormId>,
682 rule_ref: Option<RulePath>,
683 typed: Typed,
684 ) -> NormalFormId {
685 let Typed {
686 result_type,
687 fold_types,
688 } = typed;
689 let key = (kind, rule_ref, result_type);
690 if origin.is_none() {
691 if let Some(id) = self.cons.get(&key) {
692 return *id;
693 }
694 }
695 let id = NormalFormId(
696 u32::try_from(self.forms.len()).expect("BUG: normal_forms table exceeds u32::MAX"),
697 );
698 if origin.is_none() {
699 self.cons.insert(key.clone(), id);
700 }
701 self.forms.push(NormalForm {
702 kind: key.0,
703 result_type: key.2,
704 fold_types,
705 source,
706 origin,
707 rule_ref: key.1,
708 });
709 id
710 }
711
712 pub(crate) fn get(&self, id: NormalFormId) -> &NormalForm {
713 self.forms.get(id.index()).unwrap_or_else(|| {
714 panic!(
715 "BUG: NormalFormId {} out of range (table len {})",
716 id.0,
717 self.forms.len()
718 )
719 })
720 }
721
722 pub(crate) fn result_type(&self, id: NormalFormId) -> &Arc<LemmaType> {
723 &self.get(id).result_type
724 }
725
726 pub(crate) fn len(&self) -> usize {
728 self.forms.len()
729 }
730
731 pub(crate) fn extract_reachable(
737 &self,
738 roots: &[NormalFormId],
739 ) -> (Vec<NormalForm>, Vec<NormalFormId>) {
740 let mut reachable: HashSet<u32> = HashSet::new();
741 let mut worklist: Vec<NormalFormId> = roots.to_vec();
742 while let Some(id) = worklist.pop() {
743 if id.index() >= self.forms.len() {
744 panic!(
745 "BUG: NormalFormId {} out of range during reachable extract (table len {})",
746 id.0,
747 self.forms.len()
748 );
749 }
750 if !reachable.insert(id.0) {
751 continue;
752 }
753 let cell = &self.forms[id.index()];
754 cell.kind.for_each_child(|child| worklist.push(child));
755 if let Some(origin) = cell.origin {
756 worklist.push(origin);
757 }
758 }
759 let mut old_ids: Vec<u32> = reachable.into_iter().collect();
760 old_ids.sort_unstable();
761 let mut remap: HashMap<u32, NormalFormId> = HashMap::with_capacity(old_ids.len());
762 for (new_index, old) in old_ids.iter().copied().enumerate() {
763 remap.insert(
764 old,
765 NormalFormId(
766 u32::try_from(new_index).expect("BUG: normal_forms table exceeds u32::MAX"),
767 ),
768 );
769 }
770 let mut table = Vec::with_capacity(old_ids.len());
771 for old in &old_ids {
772 let cell = &self.forms[*old as usize];
773 table.push(NormalForm {
774 kind: cell.kind.map_children(|child| {
775 *remap.get(&child.0).unwrap_or_else(|| {
776 panic!("BUG: child NormalFormId {} missing from remap", child.0)
777 })
778 }),
779 result_type: Arc::clone(&cell.result_type),
780 fold_types: cell.fold_types.clone(),
781 source: cell.source.clone(),
782 origin: cell.origin.map(|o| {
783 *remap.get(&o.0).unwrap_or_else(|| {
784 panic!("BUG: origin NormalFormId {} missing from remap", o.0)
785 })
786 }),
787 rule_ref: cell.rule_ref.clone(),
788 });
789 }
790 let remapped_roots = roots
791 .iter()
792 .map(|r| {
793 *remap.get(&r.0).unwrap_or_else(|| {
794 panic!("BUG: root NormalFormId {} not in reachable set", r.0)
795 })
796 })
797 .collect();
798 (table, remapped_roots)
799 }
800}
801
802pub(crate) fn data_path_result_type(
806 data: &IndexMap<DataPath, DataDefinition>,
807 path: &DataPath,
808) -> Arc<LemmaType> {
809 match data.get(path) {
810 Some(DataDefinition::Value { resolved_type, .. })
811 | Some(DataDefinition::TypeDeclaration { resolved_type, .. })
812 | Some(DataDefinition::Reference { resolved_type, .. })
813 | Some(DataDefinition::InstanceList { resolved_type, .. }) => {
814 Arc::new(resolved_type.as_ref().without_written_unit())
815 }
816 Some(DataDefinition::Import { row, .. }) => Arc::new(LemmaType::primitive(
817 crate::planning::semantics::TypeSpecification::Spec {
818 spec: row.identity.as_ref().clone(),
819 },
820 )),
821 None => Arc::new(LemmaType::undetermined_type()),
824 }
825}
826
827pub(crate) struct Cells<'a> {
831 interner: &'a mut NormalFormInterner,
832 measure_scope: MeasureScope<'a>,
833}
834
835pub(crate) struct RowFieldEnd {
839 pub(crate) end: RowEnd,
840 pub(crate) qualifier: Option<Vec<String>>,
841}
842
843impl<'a> Cells<'a> {
844 pub(crate) fn new(
845 interner: &'a mut NormalFormInterner,
846 measure_scope: MeasureScope<'a>,
847 ) -> Self {
848 Self {
849 interner,
850 measure_scope,
851 }
852 }
853
854 pub(crate) fn get(&self, id: NormalFormId) -> &NormalForm {
855 self.interner.get(id)
856 }
857
858 pub(crate) fn result_type(&self, id: NormalFormId) -> &Arc<LemmaType> {
859 self.interner.result_type(id)
860 }
861
862 pub(crate) fn len(&self) -> usize {
864 self.interner.len()
865 }
866
867 pub(crate) fn intern_empty(&mut self, kind: NormalFormKind) -> NormalFormId {
868 let typed = self.type_of(&kind, None);
869 self.interner.intern(kind, None, None, None, typed)
870 }
871
872 pub(crate) fn intern_literal_leaf(
873 &mut self,
874 literal: LiteralValue,
875 result_type: Arc<LemmaType>,
876 ) -> NormalFormId {
877 self.interner.intern(
878 NormalFormKind::Leaf(LeafKind::Literal(literal)),
879 None,
880 None,
881 None,
882 Typed {
883 result_type,
884 fold_types: Vec::new(),
885 },
886 )
887 }
888
889 fn intern_row_field(
891 &mut self,
892 level: u32,
893 identity: SpecIdentity,
894 row_end: RowFieldEnd,
895 result_type: Arc<LemmaType>,
896 ) -> NormalFormId {
897 let RowFieldEnd { end, qualifier } = row_end;
898 self.interner.intern(
899 NormalFormKind::RowField {
900 level,
901 identity,
902 end,
903 qualifier,
904 },
905 None,
906 None,
907 None,
908 Typed {
909 result_type,
910 fold_types: Vec::new(),
911 },
912 )
913 }
914
915 fn intern_row_value_leaf(&mut self, level: u32, result_type: Arc<LemmaType>) -> NormalFormId {
916 self.interner.intern(
917 NormalFormKind::Leaf(LeafKind::RowValue { level }),
918 None,
919 None,
920 None,
921 Typed {
922 result_type,
923 fold_types: Vec::new(),
924 },
925 )
926 }
927
928 fn intern_data_leaf(&mut self, path: DataPath, result_type: Arc<LemmaType>) -> NormalFormId {
929 self.interner.intern(
930 NormalFormKind::Leaf(LeafKind::DataPath(path)),
931 None,
932 None,
933 None,
934 Typed {
935 result_type,
936 fold_types: Vec::new(),
937 },
938 )
939 }
940
941 fn intern_rule_ref(
945 &mut self,
946 body: NormalFormId,
947 rule_path: RulePath,
948 result_type: Arc<LemmaType>,
949 ) -> NormalFormId {
950 let kind = self.interner.get(body).kind.clone();
951 self.interner.intern(
952 kind,
953 None,
954 None,
955 Some(rule_path),
956 Typed {
957 result_type,
958 fold_types: Vec::new(),
959 },
960 )
961 }
962
963 pub(crate) fn rebuild(
964 &mut self,
965 kind: NormalFormKind,
966 source: Option<Source>,
967 origin: Option<NormalFormId>,
968 ) -> NormalFormId {
969 let typed = self.type_of(&kind, origin);
970 self.interner.intern(kind, source, origin, None, typed)
971 }
972
973 pub(crate) fn fold_into(
974 &mut self,
975 kind: NormalFormKind,
976 destroyed_id: NormalFormId,
977 ) -> NormalFormId {
978 let typed = self.type_of(&kind, Some(destroyed_id));
979 self.interner
980 .intern(kind, None, Some(destroyed_id), None, typed)
981 }
982
983 pub(crate) fn fold_into_survivor(
984 &mut self,
985 survivor_id: NormalFormId,
986 destroyed_id: NormalFormId,
987 ) -> NormalFormId {
988 let survivor = self.interner.get(survivor_id);
989 if survivor.origin.is_some() {
992 return survivor_id;
993 }
994 self.interner.intern(
995 survivor.kind.clone(),
996 survivor.source.clone(),
997 Some(destroyed_id),
998 survivor.rule_ref.clone(),
999 Typed {
1000 result_type: Arc::clone(&survivor.result_type),
1001 fold_types: survivor.fold_types.clone(),
1002 },
1003 )
1004 }
1005
1006 pub(crate) fn replace_with_survivor(
1010 &mut self,
1011 survivor_id: NormalFormId,
1012 destroyed_id: NormalFormId,
1013 ) -> NormalFormId {
1014 let survivor = self.interner.get(survivor_id);
1015 self.interner.intern(
1016 survivor.kind.clone(),
1017 survivor.source.clone(),
1018 Some(destroyed_id),
1019 survivor.rule_ref.clone(),
1020 Typed {
1021 result_type: Arc::clone(&survivor.result_type),
1022 fold_types: survivor.fold_types.clone(),
1023 },
1024 )
1025 }
1026
1027 fn fold_arithmetic_child_types(
1028 &self,
1029 terms: &[ArithmeticTerm],
1030 direct: ArithmeticComputation,
1031 inverted: ArithmeticComputation,
1032 ) -> Vec<Arc<LemmaType>> {
1033 let (first, rest) = terms
1034 .split_first()
1035 .expect("BUG: arithmetic NormalForm with zero children");
1036 assert!(
1037 !first.inverse,
1038 "BUG: arithmetic NormalForm starts with an inverse term"
1039 );
1040 let mut fold_types = Vec::with_capacity(terms.len());
1041 let mut acc = Arc::clone(self.result_type(first.operand));
1042 fold_types.push(Arc::clone(&acc));
1043 for term in rest {
1044 let op = if term.inverse { &inverted } else { &direct };
1045 acc = compute_arithmetic_result_type(
1046 acc,
1047 op,
1048 Arc::clone(self.result_type(term.operand)),
1049 &self.measure_scope,
1050 );
1051 fold_types.push(Arc::clone(&acc));
1052 }
1053 fold_types
1054 }
1055
1056 fn binary_arithmetic_type(
1057 &self,
1058 left: NormalFormId,
1059 op: ArithmeticComputation,
1060 right: NormalFormId,
1061 ) -> Arc<LemmaType> {
1062 let left_type = self.result_type(left);
1063 if op == ArithmeticComputation::Power && left_type.is_measure() {
1064 if let Some(exponent) = self.integer_exponent_leaf(right) {
1065 return quantity_integer_power_type(left_type, exponent, &self.measure_scope);
1066 }
1067 }
1068 compute_arithmetic_result_type(
1069 Arc::clone(left_type),
1070 &op,
1071 Arc::clone(self.result_type(right)),
1072 &self.measure_scope,
1073 )
1074 }
1075
1076 fn integer_exponent_leaf(&self, id: NormalFormId) -> Option<i32> {
1077 let NormalFormKind::Leaf(LeafKind::Literal(literal)) = &self.get(id).kind else {
1078 return None;
1079 };
1080 let ValueKind::Number(number) = &literal.value else {
1081 return None;
1082 };
1083 if !number.is_integer() {
1084 return None;
1085 }
1086 number.numer_to_i32()
1087 }
1088}
1089
1090fn literal_value_default_result_type(value: &ValueKind) -> Arc<LemmaType> {
1094 match value {
1095 ValueKind::Number(_) => primitive_number_arc().clone(),
1096 ValueKind::Boolean(_) => primitive_boolean_arc().clone(),
1097 ValueKind::Text(_) => primitive_text_arc().clone(),
1098 ValueKind::Date(_) => primitive_date_arc().clone(),
1099 ValueKind::Time(_) => primitive_time_arc().clone(),
1100 ValueKind::Ratio(_) => primitive_ratio_arc().clone(),
1101 ValueKind::Measure(_) | ValueKind::UnscaledMeasure { .. } => {
1102 panic!(
1103 "BUG: Measure literal result_type must be stamped from expression/fold survivor type"
1104 )
1105 }
1106 ValueKind::Range(_, _) => {
1107 panic!("BUG: Range literal result_type must be stamped from expression/endpoint types")
1108 }
1109 ValueKind::Spec(_) => {
1110 panic!("BUG: Spec literal result_type must be stamped from the spec identity")
1111 }
1112 ValueKind::List(_) => {
1113 panic!("BUG: List literal result_type must be stamped from the list type")
1114 }
1115 }
1116}
1117
1118impl Cells<'_> {
1119 fn type_of(&self, kind: &NormalFormKind, pre_image: Option<NormalFormId>) -> Typed {
1132 let (result_type, fold_types) = match kind {
1133 NormalFormKind::Leaf(LeafKind::Literal(literal)) => (
1134 match pre_image {
1135 Some(destroyed) => Arc::clone(self.result_type(destroyed)),
1136 None => literal_value_default_result_type(&literal.value),
1137 },
1138 Vec::new(),
1139 ),
1140 NormalFormKind::Leaf(LeafKind::DataPath(_)) => {
1141 panic!("BUG: DataPath leaf result_type must be stamped from plan.data")
1142 }
1143 NormalFormKind::RowField { .. } => {
1144 panic!("BUG: row field result_type must be stamped from the row spec")
1145 }
1146 NormalFormKind::Leaf(LeafKind::RowValue { .. }) => {
1147 panic!("BUG: row value leaf result_type must be stamped from its list")
1148 }
1149 NormalFormKind::Now => (primitive_date_arc().clone(), Vec::new()),
1150 NormalFormKind::SpecMember { row, .. } => (Arc::clone(&row.result_type), Vec::new()),
1151 NormalFormKind::ListCommand { list, op, args, .. } => (
1152 list_command_result_type(
1153 self.result_type(*list),
1154 op,
1155 args.first().map(|argument| self.result_type(*argument)),
1156 &self.measure_scope,
1157 ),
1158 Vec::new(),
1159 ),
1160 NormalFormKind::Veto(_) => (Arc::new(LemmaType::veto_type()), Vec::new()),
1161 NormalFormKind::Sum(terms) => {
1162 let fold_types = self.fold_arithmetic_child_types(
1163 terms,
1164 ArithmeticComputation::Add,
1165 ArithmeticComputation::Subtract,
1166 );
1167 let result_type = Arc::clone(
1168 fold_types
1169 .last()
1170 .expect("BUG: Sum fold_types empty after fold_arithmetic_child_types"),
1171 );
1172 (result_type, fold_types)
1173 }
1174 NormalFormKind::Product(terms) => {
1175 let fold_types = self.fold_arithmetic_child_types(
1176 terms,
1177 ArithmeticComputation::Multiply,
1178 ArithmeticComputation::Divide,
1179 );
1180 let result_type = Arc::clone(
1181 fold_types
1182 .last()
1183 .expect("BUG: Product fold_types empty after fold_arithmetic_child_types"),
1184 );
1185 (result_type, fold_types)
1186 }
1187 NormalFormKind::Subtract(a, b) => (
1188 self.binary_arithmetic_type(*a, ArithmeticComputation::Subtract, *b),
1189 Vec::new(),
1190 ),
1191 NormalFormKind::Divide(a, b) => (
1192 self.binary_arithmetic_type(*a, ArithmeticComputation::Divide, *b),
1193 Vec::new(),
1194 ),
1195 NormalFormKind::Power(a, b) => (
1196 self.binary_arithmetic_type(*a, ArithmeticComputation::Power, *b),
1197 Vec::new(),
1198 ),
1199 NormalFormKind::Modulo(a, b) => (
1200 self.binary_arithmetic_type(*a, ArithmeticComputation::Modulo, *b),
1201 Vec::new(),
1202 ),
1203 NormalFormKind::Negate(x) => (
1206 compute_arithmetic_result_type(
1207 primitive_number_arc().clone(),
1208 &ArithmeticComputation::Multiply,
1209 Arc::clone(self.result_type(*x)),
1210 &self.measure_scope,
1211 ),
1212 Vec::new(),
1213 ),
1214 NormalFormKind::Reciprocal(x) => (
1215 compute_arithmetic_result_type(
1216 primitive_number_arc().clone(),
1217 &ArithmeticComputation::Divide,
1218 Arc::clone(self.result_type(*x)),
1219 &self.measure_scope,
1220 ),
1221 Vec::new(),
1222 ),
1223 NormalFormKind::Logical(_, children) => {
1224 let mut types = children.iter().map(|id| self.result_type(*id));
1225 let first = types.next().expect("BUG: And has at least two operands");
1226 let ty = types.fold(Arc::clone(first), |acc, next| {
1227 logical_connective_type(&acc, next)
1228 });
1229 (ty, Vec::new())
1230 }
1231 NormalFormKind::Comparison(a, op, b) => (
1232 comparison_type(self.result_type(*a), op, self.result_type(*b)),
1233 Vec::new(),
1234 ),
1235 NormalFormKind::RangeContainment(a, b) => (
1236 range_containment_type(self.result_type(*a), self.result_type(*b)),
1237 Vec::new(),
1238 ),
1239 NormalFormKind::Not(x) => (logical_not_type(self.result_type(*x)), Vec::new()),
1240 NormalFormKind::DateRelative(_, x) | NormalFormKind::DateCalendar(_, _, x) => {
1241 (date_predicate_type(self.result_type(*x)), Vec::new())
1242 }
1243 NormalFormKind::ResultIsVeto(_) => (result_is_veto_type(), Vec::new()),
1244 NormalFormKind::MathOp(op, x) => (
1245 math_op_type(op, self.result_type(*x), &self.measure_scope),
1246 Vec::new(),
1247 ),
1248 NormalFormKind::StepRounding(_, x, step) => (
1249 step_rounding_type(
1250 self.result_type(*x),
1251 step.map(|step| self.result_type(step)),
1252 ),
1253 Vec::new(),
1254 ),
1255 NormalFormKind::UnitConversion(x, target) => (
1256 unit_conversion_type(self.result_type(*x), target),
1257 Vec::new(),
1258 ),
1259 NormalFormKind::RangeLiteral(a, b) => {
1260 let (a, b) = (self.result_type(*a), self.result_type(*b));
1261 let ty = if a.vetoed() || b.vetoed() {
1262 Arc::new(LemmaType::veto_type())
1263 } else {
1264 infer_range_type_from_endpoint_types(a.as_ref(), b.as_ref())
1265 };
1266 (ty, Vec::new())
1267 }
1268 NormalFormKind::PastFutureRange(_, offset) => (
1269 past_future_range_type(self.result_type(*offset)),
1270 Vec::new(),
1271 ),
1272 NormalFormKind::RangeBound(_, operand) => (
1273 range_bound_result_type(self.result_type(*operand), &self.measure_scope),
1274 Vec::new(),
1275 ),
1276 NormalFormKind::Piecewise(arms) => (
1277 piecewise_type(
1278 arms.iter()
1279 .map(|(_, body)| Arc::clone(self.result_type(*body))),
1280 &self.measure_scope,
1281 ),
1282 Vec::new(),
1283 ),
1284 NormalFormKind::OrderedDispatch { .. } => (
1285 Arc::clone(self.result_type(pre_image.expect(
1286 "BUG: OrderedDispatch is only built over the Piecewise it re-indexes",
1287 ))),
1288 Vec::new(),
1289 ),
1290 };
1291 if result_type.is_undetermined() {
1295 let input_undetermined = match kind {
1296 NormalFormKind::Leaf(_) | NormalFormKind::Now | NormalFormKind::Veto(_) => false,
1297 NormalFormKind::OrderedDispatch { .. } => {
1298 pre_image.is_some_and(|id| self.result_type(id).is_undetermined())
1299 }
1300 NormalFormKind::Sum(terms) | NormalFormKind::Product(terms) => terms
1301 .iter()
1302 .any(|term| self.result_type(term.operand).is_undetermined()),
1303 NormalFormKind::Logical(_, children) => children
1304 .iter()
1305 .any(|id| self.result_type(*id).is_undetermined()),
1306 NormalFormKind::Subtract(a, b)
1307 | NormalFormKind::Divide(a, b)
1308 | NormalFormKind::Power(a, b)
1309 | NormalFormKind::Modulo(a, b)
1310 | NormalFormKind::RangeLiteral(a, b) => {
1311 self.result_type(*a).is_undetermined() || self.result_type(*b).is_undetermined()
1312 }
1313 NormalFormKind::StepRounding(_, a, step) => {
1314 self.result_type(*a).is_undetermined()
1315 || step.is_some_and(|step| self.result_type(step).is_undetermined())
1316 }
1317 NormalFormKind::Comparison(_, _, _) | NormalFormKind::RangeContainment(_, _) => {
1322 true
1323 }
1324 NormalFormKind::Negate(x)
1325 | NormalFormKind::Reciprocal(x)
1326 | NormalFormKind::UnitConversion(x, _)
1327 | NormalFormKind::RangeBound(_, x)
1328 | NormalFormKind::ResultIsVeto(x) => self.result_type(*x).is_undetermined(),
1329 NormalFormKind::Not(_)
1330 | NormalFormKind::DateRelative(_, _)
1331 | NormalFormKind::DateCalendar(_, _, _)
1332 | NormalFormKind::MathOp(_, _)
1333 | NormalFormKind::PastFutureRange(_, _) => true,
1334 NormalFormKind::Piecewise(arms) => arms.iter().any(|(condition, body)| {
1335 self.result_type(*condition).is_undetermined()
1336 || self.result_type(*body).is_undetermined()
1337 }),
1338 NormalFormKind::SpecMember { base, .. } => {
1339 self.result_type(*base).is_undetermined()
1340 }
1341 NormalFormKind::RowField { .. } => false,
1342 NormalFormKind::ListCommand { list, args, .. } => {
1343 self.result_type(*list).is_undetermined()
1344 || args
1345 .iter()
1346 .any(|id| self.result_type(*id).is_undetermined())
1347 }
1348 };
1349 assert!(
1350 input_undetermined,
1351 "BUG: NormalForm cell stamped undetermined type after validation"
1352 );
1353 }
1354 Typed {
1355 result_type,
1356 fold_types,
1357 }
1358 }
1359}
1360
1361impl LowerCtx<'_> {
1362 fn completed_rule(&self, path: &RulePath) -> NormalFormId {
1364 *self.completed_rules.get(path).unwrap_or_else(|| {
1365 panic!(
1366 "BUG: rule '{}' read before it was planned (missing dependency edge)",
1367 path.input_key()
1368 )
1369 })
1370 }
1371
1372 fn rule_type(&self, path: &RulePath) -> Arc<LemmaType> {
1373 Arc::clone(self.rule_types.get(path).unwrap_or_else(|| {
1374 panic!("BUG: rule '{}' has no RuleNode.rule_type", path.input_key())
1375 }))
1376 }
1377}
1378
1379fn to_normal_form(tree: &Expression, cells: &mut Cells<'_>, lower: &LowerCtx<'_>) -> NormalFormId {
1381 let mut lowered: Vec<NormalFormId> = Vec::with_capacity(tree.table.len());
1382 let mut children: Vec<NormalFormId> = Vec::new();
1383 for id in tree.table.ids() {
1384 let node = tree.at(id);
1385 children.clear();
1386 node.kind()
1387 .for_each_child(|child| children.push(lowered[child.index()]));
1388 lowered.push(lower_node(node, &children, &lowered, cells, lower));
1389 }
1390 *lowered.last().expect("BUG: expression tree has a root")
1391}
1392
1393#[derive(Clone, Copy, PartialEq, Eq)]
1395enum ArithmeticGroup {
1396 Sum,
1397 Product,
1398}
1399
1400fn arithmetic_group(op: &ArithmeticComputation) -> Option<ArithmeticGroup> {
1401 match op {
1402 ArithmeticComputation::Add | ArithmeticComputation::Subtract => Some(ArithmeticGroup::Sum),
1403 ArithmeticComputation::Multiply | ArithmeticComputation::Divide => {
1404 Some(ArithmeticGroup::Product)
1405 }
1406 ArithmeticComputation::Power | ArithmeticComputation::Modulo => None,
1407 }
1408}
1409
1410fn lower_arithmetic_chain(
1417 first: NormalFormId,
1418 steps: &[(ArithmeticComputation, NormalFormId)],
1419 cells: &mut Cells<'_>,
1420) -> NormalFormId {
1421 let mut current = first;
1422 let mut rest = steps;
1423 while let Some((op, operand)) = rest.first() {
1424 let Some(group) = arithmetic_group(op) else {
1425 current = lower_binary_arithmetic(op, current, *operand, cells);
1426 rest = &rest[1..];
1427 continue;
1428 };
1429 let run_len = rest
1430 .iter()
1431 .take_while(|(op, _)| arithmetic_group(op) == Some(group))
1432 .count();
1433 current = lower_arithmetic_run(current, &rest[..run_len], group, cells);
1434 rest = &rest[run_len..];
1435 }
1436 current
1437}
1438
1439fn lower_arithmetic_run(
1440 first: NormalFormId,
1441 operands: &[(ArithmeticComputation, NormalFormId)],
1442 group: ArithmeticGroup,
1443 cells: &mut Cells<'_>,
1444) -> NormalFormId {
1445 let has_subtract_or_divide = operands.iter().any(|(op, _)| {
1446 matches!(
1447 op,
1448 ArithmeticComputation::Subtract | ArithmeticComputation::Divide
1449 )
1450 });
1451 let all_numeric = {
1452 let mut memo = HashMap::new();
1453 std::iter::once(first)
1454 .chain(operands.iter().map(|(_, id)| *id))
1455 .all(|id| rewrite::numeric_only(cells, &mut memo, id))
1456 };
1457 if !has_subtract_or_divide || (all_numeric && operands.len() > 1) {
1458 let terms: Vec<ArithmeticTerm> = std::iter::once(ArithmeticTerm::direct(first))
1459 .chain(operands.iter().map(|(op, id)| ArithmeticTerm {
1460 operand: *id,
1461 inverse: matches!(
1462 op,
1463 ArithmeticComputation::Subtract | ArithmeticComputation::Divide
1464 ),
1465 }))
1466 .collect();
1467 let kind = match group {
1468 ArithmeticGroup::Sum => NormalFormKind::Sum(terms),
1469 ArithmeticGroup::Product => NormalFormKind::Product(terms),
1470 };
1471 return cells.intern_empty(kind);
1472 }
1473 operands.iter().fold(first, |current, (op, operand)| {
1474 lower_binary_arithmetic(op, current, *operand, cells)
1475 })
1476}
1477
1478fn lower_binary_arithmetic(
1479 op: &ArithmeticComputation,
1480 left: NormalFormId,
1481 right: NormalFormId,
1482 cells: &mut Cells<'_>,
1483) -> NormalFormId {
1484 let kind = match op {
1485 ArithmeticComputation::Subtract => NormalFormKind::Subtract(left, right),
1486 ArithmeticComputation::Divide => NormalFormKind::Divide(left, right),
1487 ArithmeticComputation::Add => NormalFormKind::Sum(vec![
1488 ArithmeticTerm::direct(left),
1489 ArithmeticTerm::direct(right),
1490 ]),
1491 ArithmeticComputation::Multiply => NormalFormKind::Product(vec![
1492 ArithmeticTerm::direct(left),
1493 ArithmeticTerm::direct(right),
1494 ]),
1495 ArithmeticComputation::Power => NormalFormKind::Power(left, right),
1496 ArithmeticComputation::Modulo => NormalFormKind::Modulo(left, right),
1497 };
1498 cells.intern_empty(kind)
1499}
1500
1501fn lower_spec_member(
1504 base: NormalFormId,
1505 path: &[String],
1506 name: &str,
1507 cells: &mut Cells<'_>,
1508 lower: &LowerCtx<'_>,
1509) -> NormalFormId {
1510 let crate::planning::semantics::TypeSpecification::Spec { spec } =
1511 &cells.result_type(base).specifications
1512 else {
1513 panic!(
1514 "BUG: member '{}' read on a base that is not spec-typed after typing",
1515 crate::planning::semantics::spec_member_input_key(path, name)
1516 );
1517 };
1518 let chain = lower.row_member_chain(spec, path, name).unwrap_or_else(|| {
1519 panic!(
1520 "BUG: row member '{}' of '{spec}' passed typing",
1521 crate::planning::semantics::spec_member_input_key(path, name)
1522 )
1523 });
1524 let last = chain.last().expect("BUG: a row end chain has a last hop");
1525 let mut result_type = lower.row_end_type(&last.identity, &last.end);
1526 if let NormalFormKind::Leaf(LeafKind::DataPath(data_path)) = &cells.get(base).kind {
1527 result_type = reanchor_lowered_member_type(lower, data_path, path, result_type);
1528 }
1529 result_type = Arc::new(result_type.as_ref().without_written_unit());
1530 cells.intern_empty(NormalFormKind::SpecMember {
1531 base,
1532 path: path.to_vec(),
1533 name: name.to_string(),
1534 row: RowRead {
1535 ends: chain.into_iter().map(|hop| hop.end).collect(),
1536 result_type,
1537 },
1538 })
1539}
1540
1541fn reanchor_lowered_member_type(
1544 lower: &LowerCtx<'_>,
1545 base_path: &DataPath,
1546 member_path: &[String],
1547 lemma_type: Arc<LemmaType>,
1548) -> Arc<LemmaType> {
1549 use crate::planning::semantics::{DataDefinition, LemmaType, SpecIdentity};
1550 let Some(DataDefinition::Import { row, .. }) = lower.data.get(base_path) else {
1551 return lemma_type;
1552 };
1553 LemmaType::reanchor_through_import_path(
1554 lemma_type,
1555 &base_path.data,
1556 row.identity.as_ref(),
1557 member_path,
1558 |identity: &SpecIdentity, segment: &str| {
1559 let template = lower.template(identity);
1560 let slot = DataPath::local(segment.to_string());
1561 match template.data.get(&slot) {
1562 Some(DataDefinition::Import { row, .. }) => Some(row.identity.as_ref().clone()),
1563 _ => None,
1564 }
1565 },
1566 )
1567}
1568
1569fn lower_node(
1571 node: ExprRef<'_, ExpressionKind>,
1572 children: &[NormalFormId],
1573 lowered: &[NormalFormId],
1574 cells: &mut Cells<'_>,
1575 lower: &LowerCtx<'_>,
1576) -> NormalFormId {
1577 match node.kind() {
1578 ExpressionKind::Literal(lit) => {
1579 cells.intern_literal_leaf(lit.to_literal(), Arc::clone(&lit.lemma_type))
1580 }
1581 ExpressionKind::DataPath(p) => {
1584 cells.intern_data_leaf(p.clone(), data_path_result_type(lower.data, p))
1585 }
1586 ExpressionKind::SpecMember { path, name, .. } => {
1587 lower_spec_member(children[0], path, name, cells, lower)
1588 }
1589 ExpressionKind::RulePath(path) => {
1590 let body = lower.completed_rule(path);
1591 cells.intern_rule_ref(
1592 body,
1593 path.clone(),
1594 Arc::new(lower.rule_type(path).as_ref().without_written_unit()),
1595 )
1596 }
1597 ExpressionKind::Logical { connective, .. } => {
1598 cells.intern_empty(NormalFormKind::Logical(*connective, children.to_vec()))
1599 }
1600 ExpressionKind::Arithmetic { steps, .. } => {
1601 let (first, operands) = children
1602 .split_first()
1603 .expect("BUG: arithmetic chain has a first operand");
1604 assert_eq!(
1605 operands.len(),
1606 steps.len(),
1607 "BUG: arithmetic chain lowered to one operand per step"
1608 );
1609 let steps: Vec<(ArithmeticComputation, NormalFormId)> = steps
1610 .iter()
1611 .zip(operands)
1612 .map(|(step, operand)| (step.op.clone(), *operand))
1613 .collect();
1614 lower_arithmetic_chain(*first, &steps, cells)
1615 }
1616 ExpressionKind::Comparison(_, op, _) => {
1617 let &[left_id, right_id] = children else {
1618 unreachable!("BUG: comparison has two operands")
1619 };
1620 cells.intern_empty(NormalFormKind::Comparison(left_id, op.clone(), right_id))
1621 }
1622 ExpressionKind::UnitConversion(_, target) => {
1623 let &[inner_id] = children else {
1624 unreachable!("BUG: unit conversion has one operand")
1625 };
1626 cells.rebuild(
1627 NormalFormKind::UnitConversion(inner_id, target.clone()),
1628 node.source().cloned(),
1629 None,
1630 )
1631 }
1632 ExpressionKind::LogicalNegation(_, _) => {
1633 let &[inner_id] = children else {
1634 unreachable!("BUG: negation has one operand")
1635 };
1636 cells.intern_empty(NormalFormKind::Not(inner_id))
1637 }
1638 ExpressionKind::MathematicalComputation(op, _) => {
1639 let &[inner_id] = children else {
1640 unreachable!("BUG: math operator has one operand")
1641 };
1642 cells.intern_empty(NormalFormKind::MathOp(op.clone(), inner_id))
1643 }
1644 ExpressionKind::StepRounding { mode, .. } => match *children {
1645 [operand_id] => {
1646 cells.intern_empty(NormalFormKind::StepRounding(*mode, operand_id, None))
1647 }
1648 [operand_id, step_id] => cells.intern_empty(NormalFormKind::StepRounding(
1649 *mode,
1650 operand_id,
1651 Some(step_id),
1652 )),
1653 _ => unreachable!("BUG: step rounding has an operand and an optional step"),
1654 },
1655 ExpressionKind::Veto(v) => cells.intern_empty(NormalFormKind::Veto(v.clone())),
1656 ExpressionKind::Now => cells.intern_empty(NormalFormKind::Now),
1657 ExpressionKind::DateRelative(kind, _) => {
1658 let &[inner_id] = children else {
1659 unreachable!("BUG: date relative has one operand")
1660 };
1661 cells.intern_empty(NormalFormKind::DateRelative(*kind, inner_id))
1662 }
1663 ExpressionKind::DateCalendar(kind, unit, _) => {
1664 let &[inner_id] = children else {
1665 unreachable!("BUG: date calendar has one operand")
1666 };
1667 cells.intern_empty(NormalFormKind::DateCalendar(*kind, *unit, inner_id))
1668 }
1669 ExpressionKind::RangeLiteral(_, _) => {
1670 let &[left_id, right_id] = children else {
1671 unreachable!("BUG: range literal has two operands")
1672 };
1673 cells.intern_empty(NormalFormKind::RangeLiteral(left_id, right_id))
1674 }
1675 ExpressionKind::PastFutureRange(kind, _) => {
1676 let &[inner_id] = children else {
1677 unreachable!("BUG: past/future range has one operand")
1678 };
1679 cells.intern_empty(NormalFormKind::PastFutureRange(*kind, inner_id))
1680 }
1681 ExpressionKind::RangeBound(bound, _) => {
1682 let &[inner_id] = children else {
1683 unreachable!("BUG: range bound has one operand")
1684 };
1685 cells.intern_empty(NormalFormKind::RangeBound(*bound, inner_id))
1686 }
1687 ExpressionKind::RangeContainment(_, _) => {
1688 let &[left_id, right_id] = children else {
1689 unreachable!("BUG: range containment has two operands")
1690 };
1691 cells.intern_empty(NormalFormKind::RangeContainment(left_id, right_id))
1692 }
1693 ExpressionKind::ResultIsVeto(_) => {
1694 let &[operand_id] = children else {
1695 unreachable!("BUG: result-is-veto has one operand")
1696 };
1697 cells.intern_empty(NormalFormKind::ResultIsVeto(operand_id))
1698 }
1699 ExpressionKind::Piecewise(_) => {
1700 let mapped = children
1701 .chunks_exact(2)
1702 .map(|pair| (pair[0], pair[1]))
1703 .collect();
1704 cells.intern_empty(NormalFormKind::Piecewise(mapped))
1705 }
1706 ExpressionKind::ListCommand { op, row_level, .. } => {
1707 let (list_id, args) = children
1708 .split_first()
1709 .expect("BUG: list command has a list operand");
1710 cells.intern_empty(NormalFormKind::ListCommand {
1711 list: *list_id,
1712 op: list_op_kind(op),
1713 args: args.to_vec(),
1714 row_level: *row_level,
1715 })
1716 }
1717 ExpressionKind::RowField {
1718 level,
1719 identity,
1720 end,
1721 qualifier,
1722 } => cells.intern_row_field(
1723 *level,
1724 identity.clone(),
1725 RowFieldEnd {
1726 end: end.clone(),
1727 qualifier: qualifier.clone(),
1728 },
1729 Arc::new(
1730 lower
1731 .row_end_type(identity, end)
1732 .as_ref()
1733 .without_written_unit(),
1734 ),
1735 ),
1736 ExpressionKind::RowValue { level, list } => {
1737 let list_cell = *lowered.get(list.index()).expect(
1738 "BUG: row value type-only list reference must point to an earlier expression",
1739 );
1740 let element_type = cells.result_type(list_cell).list_element_lemma_type();
1741 cells.intern_row_value_leaf(
1742 *level,
1743 Arc::new(element_type.as_ref().without_written_unit()),
1744 )
1745 }
1746 }
1747}
1748
1749fn list_op_kind(op: &crate::planning::semantics::ListOp) -> NormalFormListOp {
1752 use crate::planning::semantics::{ListOp, ListReduce};
1753 match op {
1754 ListOp::Find { .. } => NormalFormListOp::Find,
1755 ListOp::First => NormalFormListOp::First,
1756 ListOp::Last => NormalFormListOp::Last,
1757 ListOp::Count => NormalFormListOp::Count,
1758 ListOp::Reduce { kind, .. } => match kind {
1759 ListReduce::Sum => NormalFormListOp::Sum,
1760 ListReduce::Avg => NormalFormListOp::Avg,
1761 ListReduce::Product => NormalFormListOp::Product,
1762 ListReduce::Min => NormalFormListOp::Min,
1763 ListReduce::Max => NormalFormListOp::Max,
1764 },
1765 ListOp::Predicate { kind, form, .. } => NormalFormListOp::Predicate {
1766 kind: *kind,
1767 form: *form,
1768 },
1769 ListOp::Sort { descending, .. } => NormalFormListOp::Sort {
1770 descending: *descending,
1771 },
1772 ListOp::Top { .. } => NormalFormListOp::Top,
1773 ListOp::With { .. } => NormalFormListOp::With,
1774 ListOp::WithRow { identity, fields } => NormalFormListOp::WithRow {
1775 identity: Arc::new(identity.clone()),
1776 fields: fields.iter().map(|(path, _)| path.clone()).collect(),
1777 },
1778 ListOp::Key { fields } => NormalFormListOp::Key {
1779 fields: fields.clone(),
1780 },
1781 }
1782}
1783
1784fn list_command_result_type(
1787 list_type: &Arc<LemmaType>,
1788 op: &NormalFormListOp,
1789 first_argument: Option<&Arc<LemmaType>>,
1790 measure_scope: &crate::planning::typing::MeasureScope<'_>,
1791) -> Arc<LemmaType> {
1792 use crate::planning::typing::{
1793 list_command_result_type as stamp, list_has_instance_rows, ListCommandResultKind,
1794 };
1795 let instance_rows = list_has_instance_rows(list_type);
1796 let kind = match op {
1797 NormalFormListOp::Count => ListCommandResultKind::Count,
1798 NormalFormListOp::Predicate {
1799 kind: ListPredKind::Any | ListPredKind::All,
1800 ..
1801 } => ListCommandResultKind::BooleanPredicate,
1802 NormalFormListOp::Predicate {
1803 kind: ListPredKind::Where,
1804 ..
1805 }
1806 | NormalFormListOp::Sort { .. }
1807 | NormalFormListOp::Top
1808 | NormalFormListOp::With
1809 | NormalFormListOp::WithRow { .. }
1810 | NormalFormListOp::Key { .. } => ListCommandResultKind::KeepList,
1811 NormalFormListOp::Sum
1812 | NormalFormListOp::Avg
1813 | NormalFormListOp::Product
1814 | NormalFormListOp::Min
1815 | NormalFormListOp::Max
1816 if instance_rows =>
1817 {
1818 ListCommandResultKind::InstanceReduce
1819 }
1820 NormalFormListOp::Find
1821 | NormalFormListOp::First
1822 | NormalFormListOp::Last
1823 | NormalFormListOp::Sum
1824 | NormalFormListOp::Avg
1825 | NormalFormListOp::Product
1826 | NormalFormListOp::Min
1827 | NormalFormListOp::Max => ListCommandResultKind::Element,
1828 };
1829 let result = stamp(list_type, kind, first_argument, measure_scope);
1830 match op {
1831 NormalFormListOp::Key { fields } => {
1832 crate::planning::typing::refine_list_key_type(&result, fields)
1833 }
1834 _ => result,
1835 }
1836}
1837
1838fn cell(forms: &[NormalForm], id: NormalFormId) -> &NormalForm {
1839 forms.get(id.index()).unwrap_or_else(|| {
1840 panic!(
1841 "BUG: NormalFormId {} out of range (table len {})",
1842 id.0,
1843 forms.len()
1844 )
1845 })
1846}
1847
1848pub(crate) fn explanation_display(
1853 forms: &[NormalForm],
1854 id: NormalFormId,
1855 qualifier: Option<&NameScope>,
1856) -> String {
1857 Syntax(NfNode {
1858 forms,
1859 id,
1860 qualifier,
1861 })
1862 .to_string()
1863}
1864
1865fn displayed_cell(forms: &[NormalForm], mut id: NormalFormId) -> &NormalForm {
1867 loop {
1868 let nf = cell(forms, id);
1869 match nf.origin {
1870 Some(origin) => id = origin,
1871 None => return nf,
1872 }
1873 }
1874}
1875
1876#[derive(Clone, Copy, PartialEq, Eq)]
1877enum SpineLevel {
1878 Additive,
1879 Multiplicative,
1880}
1881
1882fn arithmetic_chain(
1886 forms: &[NormalForm],
1887 top: NormalFormId,
1888) -> (
1889 NormalFormId,
1890 Vec<(&'static ArithmeticComputation, NormalFormId)>,
1891) {
1892 let level = match &displayed_cell(forms, top).kind {
1893 NormalFormKind::Sum(_) | NormalFormKind::Subtract(..) => SpineLevel::Additive,
1894 NormalFormKind::Product(_) | NormalFormKind::Divide(..) | NormalFormKind::Modulo(..) => {
1895 SpineLevel::Multiplicative
1896 }
1897 _ => unreachable!("BUG: arithmetic_chain on a non-arithmetic cell"),
1898 };
1899 let mut steps = Vec::new();
1900 let mut current = top;
1901 loop {
1902 let shown = displayed_cell(forms, current);
1903 if shown.rule_ref.is_some() {
1904 break;
1905 }
1906 match (&shown.kind, level) {
1907 (NormalFormKind::Sum(terms), SpineLevel::Additive) => {
1908 let (first, rest) = terms
1909 .split_first()
1910 .expect("BUG: Sum cell has at least one operand");
1911 assert!(!first.inverse, "BUG: Sum starts with an inverse term");
1912 steps.extend(rest.iter().rev().map(|term| {
1913 let op = if term.inverse {
1914 &ArithmeticComputation::Subtract
1915 } else {
1916 &ArithmeticComputation::Add
1917 };
1918 (op, term.operand)
1919 }));
1920 current = first.operand;
1921 }
1922 (NormalFormKind::Subtract(left, right), SpineLevel::Additive) => {
1923 steps.push((&ArithmeticComputation::Subtract, *right));
1924 current = *left;
1925 }
1926 (NormalFormKind::Product(terms), SpineLevel::Multiplicative) => {
1927 let (first, rest) = terms
1928 .split_first()
1929 .expect("BUG: Product cell has at least one operand");
1930 assert!(!first.inverse, "BUG: Product starts with an inverse term");
1931 steps.extend(rest.iter().rev().map(|term| {
1932 let op = if term.inverse {
1933 &ArithmeticComputation::Divide
1934 } else {
1935 &ArithmeticComputation::Multiply
1936 };
1937 (op, term.operand)
1938 }));
1939 current = first.operand;
1940 }
1941 (NormalFormKind::Divide(left, right), SpineLevel::Multiplicative) => {
1942 steps.push((&ArithmeticComputation::Divide, *right));
1943 current = *left;
1944 }
1945 (NormalFormKind::Modulo(left, right), SpineLevel::Multiplicative) => {
1946 steps.push((&ArithmeticComputation::Modulo, *right));
1947 current = *left;
1948 }
1949 _ => break,
1950 }
1951 }
1952 steps.reverse();
1953 (current, steps)
1954}
1955
1956pub(crate) struct NameScope {
1961 pub(crate) data: Vec<PathSegment>,
1962 pub(crate) rules: Vec<PathSegment>,
1963}
1964
1965#[derive(Clone, Copy)]
1967pub(crate) struct NfNode<'a> {
1968 pub(crate) forms: &'a [NormalForm],
1969 pub(crate) id: NormalFormId,
1970 pub(crate) qualifier: Option<&'a NameScope>,
1972}
1973
1974impl<'a> NfNode<'a> {
1975 fn at(self, id: NormalFormId) -> Self {
1976 Self {
1977 forms: self.forms,
1978 id,
1979 qualifier: self.qualifier,
1980 }
1981 }
1982}
1983
1984#[derive(Clone, Copy)]
1986pub(crate) enum NfAtom<'a> {
1987 Literal(&'a LiteralValue, &'a LemmaType),
1988 DataPath(&'a DataPath),
1989 QualifiedDataPath {
1991 prefix: &'a [PathSegment],
1992 path: &'a DataPath,
1993 },
1994 Rule(&'a RulePath),
1995 QualifiedRule {
1997 prefix: &'a [PathSegment],
1998 path: &'a RulePath,
1999 },
2000 Row(&'a RowEnd),
2001 QualifiedRow {
2003 qualifier: &'a [String],
2004 end: &'a RowEnd,
2005 },
2006 SpecMemberField {
2007 path: &'a [String],
2008 name: &'a str,
2009 },
2010 Now,
2011 Veto(&'a VetoExpression),
2012}
2013
2014impl std::fmt::Display for NfAtom<'_> {
2015 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2016 match self {
2017 NfAtom::Literal(literal, lemma_type) => {
2018 write!(f, "{}", literal.display_value_with_type(lemma_type))
2019 }
2020 NfAtom::DataPath(path) => write!(f, "{}", path.input_key()),
2021 NfAtom::QualifiedDataPath { prefix, path } => {
2022 for segment in prefix.iter() {
2023 write!(f, "{}.", segment.input_key())?;
2024 }
2025 write!(f, "{}", path.input_key())
2026 }
2027 NfAtom::Rule(path) => write!(f, "{}", path.input_key()),
2028 NfAtom::QualifiedRule { prefix, path } => {
2029 for segment in prefix.iter() {
2030 write!(f, "{}.", segment.input_key())?;
2031 }
2032 write!(f, "{}", path.input_key())
2033 }
2034 NfAtom::Row(end) => write!(f, "{end}"),
2035 NfAtom::QualifiedRow { qualifier, end } => {
2036 for (i, segment) in qualifier.iter().enumerate() {
2037 if i > 0 {
2038 f.write_str(".")?;
2039 }
2040 f.write_str(segment)?;
2041 }
2042 f.write_str(".")?;
2043 write!(f, "{end}")
2044 }
2045 NfAtom::SpecMemberField { path, name } => {
2046 write!(
2047 f,
2048 "{}",
2049 crate::planning::semantics::spec_member_input_key(path, name)
2050 )
2051 }
2052 NfAtom::Now => write!(f, "now"),
2053 NfAtom::Veto(veto) => write!(f, "{veto}"),
2054 }
2055 }
2056}
2057
2058fn value_starts_with_sign(value: &ValueKind) -> bool {
2059 let mut current = value;
2060 loop {
2061 match current {
2062 ValueKind::Number(n) | ValueKind::Measure(n) | ValueKind::Ratio(n) => {
2063 return n.numer_is_negative()
2064 }
2065 ValueKind::Range(left, _) => current = &left.value,
2066 _ => return false,
2067 }
2068 }
2069}
2070
2071fn single_argument(args: &[NormalFormId], command: &str) -> NormalFormId {
2072 match args {
2073 [argument] => *argument,
2074 other => panic!(
2075 "BUG: `{command}` cell must carry one argument, got {}",
2076 other.len()
2077 ),
2078 }
2079}
2080
2081fn optional_argument(args: &[NormalFormId], command: &str) -> Option<NormalFormId> {
2082 match args {
2083 [] => None,
2084 [argument] => Some(*argument),
2085 other => panic!(
2086 "BUG: `{command}` cell carries at most one argument, got {}",
2087 other.len()
2088 ),
2089 }
2090}
2091
2092impl<'a> SyntaxNode<'a> for NfNode<'a> {
2093 type Atom = NfAtom<'a>;
2094
2095 fn view(self) -> SyntaxView<'a, Self> {
2096 let nf = displayed_cell(self.forms, self.id);
2097 let atom = |atom| SyntaxView::Atom {
2098 atom,
2099 starts_with_sign: false,
2100 };
2101 if let Some(path) = &nf.rule_ref {
2102 return match self.qualifier {
2103 Some(scope) if !scope.rules.is_empty() => atom(NfAtom::QualifiedRule {
2104 prefix: &scope.rules,
2105 path,
2106 }),
2107 Some(_) | None => atom(NfAtom::Rule(path)),
2108 };
2109 }
2110 let node = |id: &NormalFormId| self.at(*id);
2111 let nodes = |ids: &[NormalFormId]| ids.iter().map(node).collect::<Vec<_>>();
2112 match &nf.kind {
2113 NormalFormKind::Leaf(LeafKind::Literal(literal)) => SyntaxView::Atom {
2114 atom: NfAtom::Literal(literal, nf.result_type.as_ref()),
2115 starts_with_sign: value_starts_with_sign(&literal.value),
2116 },
2117 NormalFormKind::Leaf(LeafKind::DataPath(path)) => match self.qualifier {
2118 Some(scope) if !scope.data.is_empty() => atom(NfAtom::QualifiedDataPath {
2119 prefix: &scope.data,
2120 path,
2121 }),
2122 Some(_) | None => atom(NfAtom::DataPath(path)),
2123 },
2124 NormalFormKind::RowField {
2125 end,
2126 qualifier: Some(segments),
2127 ..
2128 } => atom(NfAtom::QualifiedRow {
2129 qualifier: segments,
2130 end,
2131 }),
2132 NormalFormKind::RowField {
2133 end,
2134 qualifier: None,
2135 ..
2136 } => atom(NfAtom::Row(end)),
2137 NormalFormKind::Leaf(LeafKind::RowValue { .. }) => SyntaxView::ImplicitRowValue,
2138 NormalFormKind::Now => atom(NfAtom::Now),
2139 NormalFormKind::Veto(veto) => atom(NfAtom::Veto(veto)),
2140 NormalFormKind::Comparison(left, op, right) => {
2141 SyntaxView::Comparison(node(left), op, node(right))
2142 }
2143 NormalFormKind::Logical(connective, children) => SyntaxView::Logical {
2144 connective: *connective,
2145 operands: nodes(children),
2146 },
2147 NormalFormKind::Not(operand) => {
2148 let shown = displayed_cell(self.forms, *operand);
2149 match &shown.kind {
2150 NormalFormKind::ResultIsVeto(inner) if shown.rule_ref.is_none() => {
2151 SyntaxView::IsNotVeto(node(inner))
2152 }
2153 _ => SyntaxView::Not(node(operand)),
2154 }
2155 }
2156 NormalFormKind::ResultIsVeto(operand) => SyntaxView::IsVeto(node(operand)),
2157 NormalFormKind::Sum(_)
2158 | NormalFormKind::Product(_)
2159 | NormalFormKind::Subtract(..)
2160 | NormalFormKind::Divide(..)
2161 | NormalFormKind::Modulo(..) => {
2162 let (first, steps) = arithmetic_chain(self.forms, self.id);
2163 let level = match &nf.kind {
2164 NormalFormKind::Sum(_) | NormalFormKind::Subtract(..) => {
2165 &ArithmeticComputation::Add
2166 }
2167 _ => &ArithmeticComputation::Multiply,
2168 };
2169 SyntaxView::Chain {
2170 level,
2171 operands: std::iter::once(node(&first))
2172 .chain(steps.iter().map(|(_, operand)| node(operand)))
2173 .collect(),
2174 ops: steps.iter().map(|(op, _)| *op).collect(),
2175 }
2176 }
2177 NormalFormKind::Power(left, right) => SyntaxView::Chain {
2178 level: &ArithmeticComputation::Power,
2179 operands: vec![node(left), node(right)],
2180 ops: vec![&ArithmeticComputation::Power],
2181 },
2182 NormalFormKind::Negate(operand) => SyntaxView::Negate(node(operand)),
2183 NormalFormKind::Reciprocal(operand) => SyntaxView::Reciprocal(node(operand)),
2184 NormalFormKind::MathOp(op, operand) => SyntaxView::Prefix(op, node(operand)),
2185 NormalFormKind::StepRounding(mode, operand, step) => SyntaxView::ListCommand {
2186 list: node(operand),
2187 command: CommandView::Step(mode.keyword(), step.as_ref().map(node)),
2188 },
2189 NormalFormKind::RangeBound(bound, operand) => SyntaxView::Prefix(bound, node(operand)),
2190 NormalFormKind::UnitConversion(value, target) => {
2191 SyntaxView::UnitConversion(node(value), target)
2192 }
2193 NormalFormKind::DateRelative(kind, date) => SyntaxView::DateRelative(kind, node(date)),
2194 NormalFormKind::DateCalendar(kind, unit, date) => {
2195 SyntaxView::DateCalendar(kind, unit, node(date))
2196 }
2197 NormalFormKind::RangeLiteral(left, right) => {
2198 SyntaxView::RangeLiteral(node(left), node(right))
2199 }
2200 NormalFormKind::PastFutureRange(kind, offset) => {
2201 SyntaxView::PastFutureRange(kind, node(offset))
2202 }
2203 NormalFormKind::RangeContainment(value, range) => {
2204 SyntaxView::RangeContainment(node(value), node(range))
2205 }
2206 NormalFormKind::Piecewise(arms) => SyntaxView::Piecewise(
2207 arms.iter()
2208 .map(|(condition, result)| (node(condition), node(result)))
2209 .collect(),
2210 ),
2211 NormalFormKind::SpecMember {
2212 base, path, name, ..
2213 } => {
2214 let base_cell = cell(self.forms, *base);
2215 let named = base_cell.rule_ref.is_some()
2216 || matches!(
2217 base_cell.kind,
2218 NormalFormKind::Leaf(_) | NormalFormKind::SpecMember { .. }
2219 );
2220 SyntaxView::Member {
2221 base: node(base),
2222 path: NfAtom::SpecMemberField {
2223 path,
2224 name: name.as_str(),
2225 },
2226 parenthesize_base: !named,
2227 }
2228 }
2229 NormalFormKind::OrderedDispatch { .. } => unreachable!(
2230 "BUG: OrderedDispatch always carries its Piecewise pre-image as origin, \
2231 so displayed_cell never stops at it"
2232 ),
2233 NormalFormKind::ListCommand { list, op, args, .. } => {
2234 let reduce = |name: &'static str| {
2235 CommandView::Reduce(name, optional_argument(args, name).map(|id| node(&id)))
2236 };
2237 let command = match op {
2238 NormalFormListOp::Find => CommandView::Find(nodes(args)),
2239 NormalFormListOp::First => CommandView::First,
2240 NormalFormListOp::Last => CommandView::Last,
2241 NormalFormListOp::Count => CommandView::Count,
2242 NormalFormListOp::Sum => reduce("sum"),
2243 NormalFormListOp::Avg => reduce("avg"),
2244 NormalFormListOp::Product => reduce("product"),
2245 NormalFormListOp::Min => reduce("min"),
2246 NormalFormListOp::Max => reduce("max"),
2247 NormalFormListOp::Predicate { kind, form } => {
2248 let name = kind.keyword();
2249 let expr = node(&single_argument(args, name));
2250 let view = match form {
2251 ListPredicateForm::Value => PredicateView::Value(expr),
2252 ListPredicateForm::Instance => PredicateView::Instance(expr),
2253 };
2254 CommandView::Predicate(name, view)
2255 }
2256 NormalFormListOp::Sort { descending } => CommandView::Sort {
2257 descending: *descending,
2258 argument: optional_argument(args, "sort").map(|id| node(&id)),
2259 },
2260 NormalFormListOp::Top => CommandView::Top(node(&single_argument(args, "top"))),
2261 NormalFormListOp::With => {
2262 CommandView::With(args.iter().map(|id| (None, node(id))).collect())
2263 }
2264 NormalFormListOp::WithRow { fields, .. } => {
2265 assert_eq!(
2266 fields.len(),
2267 args.len(),
2268 "BUG: `with` row cell must carry one value per field"
2269 );
2270 CommandView::With(
2271 fields
2272 .iter()
2273 .zip(args)
2274 .map(|(field, value)| (Some(NfAtom::DataPath(field)), node(value)))
2275 .collect(),
2276 )
2277 }
2278 NormalFormListOp::Key { fields } => {
2279 CommandView::Key(fields.iter().map(NfAtom::DataPath).collect())
2280 }
2281 };
2282 SyntaxView::ListCommand {
2283 list: node(list),
2284 command,
2285 }
2286 }
2287 }
2288 }
2289}
2290
2291#[cfg(test)]
2292mod tests {
2293 use super::*;
2294
2295 static EMPTY_ROW_TEMPLATES: BTreeMap<SpecIdentity, Arc<ExecutionPlan>> = BTreeMap::new();
2296 static TEST_SELF_IDENTITY: std::sync::LazyLock<SpecIdentity> =
2297 std::sync::LazyLock::new(|| SpecIdentity {
2298 repository: None,
2299 spec: "test".to_string(),
2300 effective: crate::parsing::ast::EffectiveDate::Origin,
2301 slice: crate::parsing::ast::EffectiveDate::Origin,
2302 });
2303 use crate::computation::arithmetic::SignatureIndex;
2304 use crate::computation::rational::{rational_new, rational_one, rational_zero};
2305 use crate::parsing::arena::{ExprId, ExprTable, ExprTree};
2306 use crate::parsing::ast::{ChainStep, ConversionTypeKind};
2307 use crate::planning::semantics::TypedLiteral;
2308 use crate::planning::semantics::{
2309 ComparisonComputation, DataPath, ExpressionKind, NegationType, SemanticConversionTarget,
2310 ValueKind,
2311 };
2312 use crate::planning::unit_index::UnitIndex;
2313 use std::rc::Rc;
2314
2315 fn interner_forms(interner: &NormalFormInterner) -> &[NormalForm] {
2316 &interner.forms
2317 }
2318
2319 fn empty_named_types() -> &'static IndexMap<String, Arc<LemmaType>> {
2320 use std::sync::OnceLock;
2321 static EMPTY: OnceLock<IndexMap<String, Arc<LemmaType>>> = OnceLock::new();
2322 EMPTY.get_or_init(IndexMap::new)
2323 }
2324
2325 fn empty_measure_scope<'a>(
2326 unit_index: &'a UnitIndex,
2327 signature_index: &'a SignatureIndex,
2328 ) -> MeasureScope<'a> {
2329 MeasureScope {
2330 unit_index,
2331 signature_index,
2332 named_types: empty_named_types(),
2333 }
2334 }
2335
2336 fn algebraic_scope() -> IndexMap<DataPath, DataDefinition> {
2339 use crate::parsing::source::{Source as ParseSource, SourceType};
2340 use crate::Span;
2341 let source = ParseSource::new(
2342 SourceType::Volatile,
2343 Span {
2344 start: 0,
2345 end: 0,
2346 line: 1,
2347 col: 1,
2348 },
2349 );
2350 let mut data = IndexMap::new();
2351 for (name, resolved_type) in [
2352 ("x", primitive_number_arc().clone()),
2353 ("y", primitive_number_arc().clone()),
2354 ("flag", primitive_boolean_arc().clone()),
2355 ("flag2", primitive_boolean_arc().clone()),
2356 ] {
2357 data.insert(
2358 DataPath::new(vec![], name.into()),
2359 DataDefinition::TypeDeclaration {
2360 resolved_type,
2361 declared_suggestion: None,
2362 declared_value: None,
2363 source: source.clone(),
2364 },
2365 );
2366 }
2367 data
2368 }
2369
2370 fn normalize_expression(expr: &Ex) -> Result<Expression, Error> {
2372 let data = algebraic_scope();
2373 normalize_expression_with_data(expr, &data).map(|(expr, _)| expr)
2374 }
2375
2376 fn normalize_expression_with_data(
2379 expr: &Ex,
2380 data: &IndexMap<DataPath, DataDefinition>,
2381 ) -> Result<(Expression, NormalFormInterner), Error> {
2382 let tree = expr.tree();
2383 let source = tree.root_source().cloned();
2384 let mut interner = NormalFormInterner::new();
2385 let root = lower_with_data(&tree, &mut interner, data);
2386 let unit_index = UnitIndex::new();
2387 let signature_index = SignatureIndex::new();
2388 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
2389 let nf = rewrite::normalize(
2390 root,
2391 Cells::new(&mut interner, measure_scope),
2392 source.clone(),
2393 )?;
2394 let expression = to_expression(interner_forms(&interner), nf, source);
2395 Ok((expression, interner))
2396 }
2397
2398 fn to_normal_form_empty(expr: &Ex, interner: &mut NormalFormInterner) -> NormalFormId {
2399 let data = algebraic_scope();
2400 to_normal_form_with_data(expr, interner, &data)
2401 }
2402
2403 fn to_normal_form_with_data(
2404 expr: &Ex,
2405 interner: &mut NormalFormInterner,
2406 data: &IndexMap<DataPath, DataDefinition>,
2407 ) -> NormalFormId {
2408 lower_with_data(&expr.tree(), interner, data)
2409 }
2410
2411 fn lower_with_data(
2412 expr: &Expression,
2413 interner: &mut NormalFormInterner,
2414 data: &IndexMap<DataPath, DataDefinition>,
2415 ) -> NormalFormId {
2416 let completed_rules = HashMap::new();
2417 let rule_types = HashMap::new();
2418 let lower = LowerCtx {
2419 completed_rules: &completed_rules,
2420 rule_types: &rule_types,
2421 data,
2422 row_templates: &EMPTY_ROW_TEMPLATES,
2423 self_identity: &TEST_SELF_IDENTITY,
2424 rule_order: &[],
2425 };
2426 let unit_index = UnitIndex::new();
2427 let signature_index = SignatureIndex::new();
2428 let mut cells = Cells::new(interner, empty_measure_scope(&unit_index, &signature_index));
2429 to_normal_form(expr, &mut cells, &lower)
2430 }
2431
2432 struct Lowering<'f> {
2434 forms: &'f [NormalForm],
2435 source: Option<Source>,
2436 table: ExprTable<ExpressionKind>,
2437 }
2438
2439 fn to_expression(forms: &[NormalForm], id: NormalFormId, source: Option<Source>) -> Expression {
2440 let mut lowering = Lowering {
2441 forms,
2442 source,
2443 table: ExprTable::default(),
2444 };
2445 let root = lowering.child(id);
2446 ExprTree::new(lowering.table, root)
2447 }
2448
2449 impl Lowering<'_> {
2450 fn child(&mut self, id: NormalFormId) -> ExprId {
2451 let kind = self.kind(id);
2452 self.table.push(kind, self.source.clone())
2453 }
2454
2455 fn kind(&mut self, id: NormalFormId) -> ExpressionKind {
2456 let forms = self.forms;
2457 let source = self.source.clone();
2458 let nf = cell(forms, id);
2459 if let Some(path) = &nf.rule_ref {
2460 return ExpressionKind::RulePath(path.clone());
2461 }
2462 match &nf.kind {
2463 NormalFormKind::Leaf(LeafKind::Literal(lit)) => {
2464 ExpressionKind::Literal(Box::new(TypedLiteral {
2465 value: lit.value.clone(),
2466 lemma_type: Arc::clone(&nf.result_type),
2467 }))
2468 }
2469 NormalFormKind::Leaf(LeafKind::DataPath(p)) => ExpressionKind::DataPath(p.clone()),
2470 NormalFormKind::Sum(terms) => self.sum_kind(terms),
2471 NormalFormKind::Product(terms) => self.product_kind(terms),
2472 NormalFormKind::Subtract(a, b) => {
2473 self.binary_kind(*a, ArithmeticComputation::Subtract, *b)
2474 }
2475 NormalFormKind::Divide(a, b) => {
2476 self.binary_kind(*a, ArithmeticComputation::Divide, *b)
2477 }
2478 NormalFormKind::Power(base, exp) => {
2479 self.binary_kind(*base, ArithmeticComputation::Power, *exp)
2480 }
2481 NormalFormKind::Modulo(a, b) => {
2482 self.binary_kind(*a, ArithmeticComputation::Modulo, *b)
2483 }
2484 NormalFormKind::Negate(x) => {
2485 let zero = self.table.push(
2486 ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_zero()))),
2487 source.clone(),
2488 );
2489 let operand = self.child(*x);
2490 arithmetic_kind(zero, vec![(ArithmeticComputation::Subtract, operand)])
2491 }
2492 NormalFormKind::Reciprocal(x) => {
2493 let one = self.table.push(
2494 ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_one()))),
2495 source.clone(),
2496 );
2497 let operand = self.child(*x);
2498 arithmetic_kind(one, vec![(ArithmeticComputation::Divide, operand)])
2499 }
2500 NormalFormKind::Comparison(a, op, b) => {
2501 ExpressionKind::Comparison(self.child(*a), op.clone(), self.child(*b))
2502 }
2503 NormalFormKind::Logical(connective, children) => {
2504 self.logical_kind(*connective, children)
2505 }
2506 NormalFormKind::Not(x) => {
2507 ExpressionKind::LogicalNegation(self.child(*x), NegationType::Not)
2508 }
2509 NormalFormKind::MathOp(op, x) => {
2510 ExpressionKind::MathematicalComputation(op.clone(), self.child(*x))
2511 }
2512 NormalFormKind::StepRounding(mode, x, step) => ExpressionKind::StepRounding {
2513 operand: self.child(*x),
2514 mode: *mode,
2515 step: step.map(|step| self.child(step)),
2516 },
2517 NormalFormKind::UnitConversion(x, target) => {
2518 ExpressionKind::UnitConversion(self.child(*x), target.clone())
2519 }
2520 NormalFormKind::Veto(v) => ExpressionKind::Veto(v.clone()),
2521 NormalFormKind::Now => ExpressionKind::Now,
2522 NormalFormKind::DateRelative(kind, x) => {
2523 ExpressionKind::DateRelative(*kind, self.child(*x))
2524 }
2525 NormalFormKind::DateCalendar(kind, unit, x) => {
2526 ExpressionKind::DateCalendar(*kind, *unit, self.child(*x))
2527 }
2528 NormalFormKind::RangeLiteral(a, b) => {
2529 ExpressionKind::RangeLiteral(self.child(*a), self.child(*b))
2530 }
2531 NormalFormKind::PastFutureRange(kind, x) => {
2532 ExpressionKind::PastFutureRange(*kind, self.child(*x))
2533 }
2534 NormalFormKind::RangeBound(bound, x) => {
2535 ExpressionKind::RangeBound(*bound, self.child(*x))
2536 }
2537 NormalFormKind::RangeContainment(a, b) => {
2538 ExpressionKind::RangeContainment(self.child(*a), self.child(*b))
2539 }
2540 NormalFormKind::ResultIsVeto(operand) => {
2541 ExpressionKind::ResultIsVeto(self.child(*operand))
2542 }
2543 NormalFormKind::Piecewise(arms) => ExpressionKind::Piecewise(
2544 arms.iter()
2545 .map(|(condition, result)| (self.child(*condition), self.child(*result)))
2546 .collect(),
2547 ),
2548 NormalFormKind::SpecMember {
2549 base, path, name, ..
2550 } => ExpressionKind::SpecMember {
2551 base: self.child(*base),
2552 path: path.clone(),
2553 name: name.clone(),
2554 },
2555 NormalFormKind::RowField {
2556 level,
2557 identity,
2558 end,
2559 qualifier,
2560 ..
2561 } => ExpressionKind::RowField {
2562 level: *level,
2563 identity: identity.clone(),
2564 end: end.clone(),
2565 qualifier: qualifier.clone(),
2566 },
2567 NormalFormKind::Leaf(LeafKind::RowValue { .. }) => {
2568 panic!("BUG: normal-form RowValue cannot be rebuilt without its list context")
2569 }
2570 NormalFormKind::OrderedDispatch { .. } => self.kind(
2572 nf.origin
2573 .expect("BUG: OrderedDispatch without its Piecewise pre-image"),
2574 ),
2575 NormalFormKind::ListCommand {
2576 list,
2577 op,
2578 args,
2579 row_level,
2580 } => {
2581 let list_expr = self.child(*list);
2582 let arg_exprs: Vec<ExprId> = args.iter().map(|id| self.child(*id)).collect();
2583 use crate::planning::semantics::{ListOp, ListReduce};
2584 let planned = match op {
2585 NormalFormListOp::Find => ListOp::Find { keys: arg_exprs },
2586 NormalFormListOp::First => ListOp::First,
2587 NormalFormListOp::Last => ListOp::Last,
2588 NormalFormListOp::Count => ListOp::Count,
2589 NormalFormListOp::Sum => ListOp::Reduce {
2590 kind: ListReduce::Sum,
2591 argument: arg_exprs.into_iter().next(),
2592 },
2593 NormalFormListOp::Avg => ListOp::Reduce {
2594 kind: ListReduce::Avg,
2595 argument: arg_exprs.into_iter().next(),
2596 },
2597 NormalFormListOp::Product => ListOp::Reduce {
2598 kind: ListReduce::Product,
2599 argument: arg_exprs.into_iter().next(),
2600 },
2601 NormalFormListOp::Min => ListOp::Reduce {
2602 kind: ListReduce::Min,
2603 argument: arg_exprs.into_iter().next(),
2604 },
2605 NormalFormListOp::Max => ListOp::Reduce {
2606 kind: ListReduce::Max,
2607 argument: arg_exprs.into_iter().next(),
2608 },
2609 NormalFormListOp::Predicate { kind, form } => ListOp::Predicate {
2610 kind: *kind,
2611 form: *form,
2612 expr: arg_exprs
2613 .into_iter()
2614 .next()
2615 .expect("BUG: predicate needs a row expression"),
2616 },
2617 NormalFormListOp::Sort { descending } => ListOp::Sort {
2618 descending: *descending,
2619 argument: arg_exprs.into_iter().next(),
2620 },
2621 NormalFormListOp::Top => ListOp::Top {
2622 n: arg_exprs.into_iter().next().expect("BUG: top needs n"),
2623 },
2624 NormalFormListOp::With => ListOp::With { rows: arg_exprs },
2625 NormalFormListOp::WithRow { identity, fields } => ListOp::WithRow {
2626 identity: SpecIdentity::clone(identity),
2627 fields: fields.iter().cloned().zip(arg_exprs).collect(),
2628 },
2629 NormalFormListOp::Key { fields } => ListOp::Key {
2630 fields: fields.clone(),
2631 },
2632 };
2633 ExpressionKind::ListCommand {
2634 list: list_expr,
2635 op: planned,
2636 row_level: *row_level,
2637 }
2638 }
2639 }
2640 }
2641
2642 fn binary_kind(
2643 &mut self,
2644 left: NormalFormId,
2645 op: ArithmeticComputation,
2646 right: NormalFormId,
2647 ) -> ExpressionKind {
2648 let left = self.child(left);
2649 let right = self.child(right);
2650 arithmetic_kind(left, vec![(op, right)])
2651 }
2652
2653 fn sum_kind(&mut self, terms: &[ArithmeticTerm]) -> ExpressionKind {
2654 match terms {
2655 [] => ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_zero()))),
2656 [one] => self.kind(one.operand),
2657 [first, rest @ ..] => self.terms_kind(
2658 first.operand,
2659 rest,
2660 ArithmeticComputation::Add,
2661 ArithmeticComputation::Subtract,
2662 ),
2663 }
2664 }
2665
2666 fn product_kind(&mut self, terms: &[ArithmeticTerm]) -> ExpressionKind {
2667 match terms {
2668 [] => ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_one()))),
2669 [one] => self.kind(one.operand),
2670 [first, rest @ ..] => self.terms_kind(
2671 first.operand,
2672 rest,
2673 ArithmeticComputation::Multiply,
2674 ArithmeticComputation::Divide,
2675 ),
2676 }
2677 }
2678
2679 fn terms_kind(
2680 &mut self,
2681 first: NormalFormId,
2682 rest: &[ArithmeticTerm],
2683 direct: ArithmeticComputation,
2684 inverse: ArithmeticComputation,
2685 ) -> ExpressionKind {
2686 let first = self.child(first);
2687 let steps = rest
2688 .iter()
2689 .map(|term| {
2690 let op = if term.inverse {
2691 inverse.clone()
2692 } else {
2693 direct.clone()
2694 };
2695 (op, self.child(term.operand))
2696 })
2697 .collect();
2698 arithmetic_kind(first, steps)
2699 }
2700
2701 fn logical_kind(
2702 &mut self,
2703 connective: LogicalConnective,
2704 children: &[NormalFormId],
2705 ) -> ExpressionKind {
2706 match children {
2707 [] => unreachable!("BUG: Logical has at least one operand"),
2708 [one] => self.kind(*one),
2709 [first, rest @ ..] => {
2710 let first = self.child(*first);
2711 let operands = rest.iter().map(|id| self.child(*id)).collect();
2712 logical_kind(connective, first, operands)
2713 }
2714 }
2715 }
2716 }
2717
2718 fn step_source() -> Source {
2720 Source::new(
2721 crate::parsing::source::SourceType::Volatile,
2722 crate::Span {
2723 start: 0,
2724 end: 0,
2725 line: 1,
2726 col: 1,
2727 },
2728 )
2729 }
2730
2731 fn arithmetic_kind(
2732 first: ExprId,
2733 steps: Vec<(ArithmeticComputation, ExprId)>,
2734 ) -> ExpressionKind {
2735 ExpressionKind::Arithmetic {
2736 first,
2737 steps: steps
2738 .into_iter()
2739 .map(|(op, operand)| ChainStep {
2740 op,
2741 operand,
2742 source_location: step_source(),
2743 })
2744 .collect(),
2745 }
2746 }
2747
2748 fn binary_arithmetic(
2750 kind: &ExpressionKind,
2751 ) -> Option<(&ExprId, &ArithmeticComputation, &ExprId)> {
2752 match kind {
2753 ExpressionKind::Arithmetic { first, steps } => match steps.as_slice() {
2754 [step] => Some((first, &step.op, &step.operand)),
2755 _ => None,
2756 },
2757 _ => None,
2758 }
2759 }
2760
2761 fn logical_kind(
2762 connective: LogicalConnective,
2763 first: ExprId,
2764 operands: Vec<ExprId>,
2765 ) -> ExpressionKind {
2766 ExpressionKind::Logical {
2767 connective,
2768 first,
2769 steps: operands
2770 .into_iter()
2771 .map(|operand| ChainStep {
2772 op: (),
2773 operand,
2774 source_location: step_source(),
2775 })
2776 .collect(),
2777 }
2778 }
2779
2780 fn num_expr(n: i64) -> Ex {
2781 Ex::leaf(
2782 ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_new(n, 1)))),
2783 None,
2784 )
2785 }
2786
2787 fn bool_expr(b: bool) -> Ex {
2788 Ex::leaf(
2789 ExpressionKind::Literal(Box::new(TypedLiteral::from_bool(b))),
2790 None,
2791 )
2792 }
2793
2794 fn dx() -> Ex {
2795 Ex::leaf(
2796 ExpressionKind::DataPath(DataPath::new(vec![], "x".into())),
2797 None,
2798 )
2799 }
2800
2801 fn dflag() -> Ex {
2802 Ex::leaf(
2803 ExpressionKind::DataPath(DataPath::new(vec![], "flag".into())),
2804 None,
2805 )
2806 }
2807
2808 fn dflag2() -> Ex {
2809 Ex::leaf(
2810 ExpressionKind::DataPath(DataPath::new(vec![], "flag2".into())),
2811 None,
2812 )
2813 }
2814
2815 type PushNodes = dyn Fn(&mut ExprTable<ExpressionKind>) -> ExprId;
2817
2818 #[derive(Clone)]
2820 struct Ex(Rc<PushNodes>);
2821
2822 impl Ex {
2823 fn leaf(kind: ExpressionKind, source: Option<Source>) -> Self {
2824 Self(Rc::new(move |table| {
2825 table.push(kind.clone(), source.clone())
2826 }))
2827 }
2828
2829 fn push(&self, table: &mut ExprTable<ExpressionKind>) -> ExprId {
2830 (self.0)(table)
2831 }
2832
2833 fn tree(&self) -> Expression {
2834 let mut table = ExprTable::default();
2835 let root = self.push(&mut table);
2836 ExprTree::new(table, root)
2837 }
2838 }
2839
2840 fn compose_expr(children: &[&Ex], make: impl Fn(&[ExprId]) -> ExpressionKind + 'static) -> Ex {
2841 let children: Vec<Ex> = children.iter().map(|child| (*child).clone()).collect();
2842 Ex(Rc::new(move |table| {
2843 let ids: Vec<ExprId> = children.iter().map(|child| child.push(table)).collect();
2844 table.push(make(&ids), None)
2845 }))
2846 }
2847
2848 fn unless_chain(default: Ex, arms: Vec<(Ex, Ex)>) -> Ex {
2851 if arms.is_empty() {
2852 return default;
2853 }
2854 Ex(Rc::new(move |table| {
2855 let default_result = default.push(table);
2856 let default_condition = table.push(
2857 ExpressionKind::Literal(Box::new(TypedLiteral::from_bool(true))),
2858 None,
2859 );
2860 let mut pieces = vec![(default_condition, default_result)];
2861 for (condition, result) in &arms {
2862 let condition = condition.push(table);
2863 let result = result.push(table);
2864 pieces.push((condition, result));
2865 }
2866 table.push(ExpressionKind::Piecewise(pieces), None)
2867 }))
2868 }
2869
2870 fn add_expr(a: Ex, b: Ex) -> Ex {
2871 compose_expr(&[&a, &b], |ids| {
2872 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Add, ids[1])])
2873 })
2874 }
2875
2876 fn mul_expr(a: Ex, b: Ex) -> Ex {
2877 compose_expr(&[&a, &b], |ids| {
2878 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Multiply, ids[1])])
2879 })
2880 }
2881
2882 fn div_expr(a: Ex, b: Ex) -> Ex {
2883 compose_expr(&[&a, &b], |ids| {
2884 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Divide, ids[1])])
2885 })
2886 }
2887
2888 fn pow_expr(a: Ex, b: Ex) -> Ex {
2889 compose_expr(&[&a, &b], |ids| {
2890 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Power, ids[1])])
2891 })
2892 }
2893
2894 fn and_expr(a: Ex, b: Ex) -> Ex {
2895 compose_expr(&[&a, &b], |ids| {
2896 logical_kind(LogicalConnective::And, ids[0], vec![ids[1]])
2897 })
2898 }
2899
2900 fn or_expr(a: Ex, b: Ex) -> Ex {
2901 compose_expr(&[&a, &b], |ids| {
2902 logical_kind(LogicalConnective::Or, ids[0], vec![ids[1]])
2903 })
2904 }
2905
2906 fn not_expr(inner: Ex) -> Ex {
2907 compose_expr(&[&inner], |ids| {
2908 ExpressionKind::LogicalNegation(ids[0], NegationType::Not)
2909 })
2910 }
2911
2912 fn lt_expr(a: Ex, b: Ex) -> Ex {
2913 compose_expr(&[&a, &b], |ids| {
2914 ExpressionKind::Comparison(ids[0], ComparisonComputation::LessThan, ids[1])
2915 })
2916 }
2917
2918 fn sub_expr(a: Ex, b: Ex) -> Ex {
2919 compose_expr(&[&a, &b], |ids| {
2920 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Subtract, ids[1])])
2921 })
2922 }
2923
2924 fn math_expr(op: MathematicalComputation, operand: Ex) -> Ex {
2925 compose_expr(&[&operand], move |ids| {
2926 ExpressionKind::MathematicalComputation(op.clone(), ids[0])
2927 })
2928 }
2929
2930 mod ordered_dispatch_fold {
2931 use super::*;
2932 use crate::parsing::source::{Source as ParseSource, SourceType};
2933 use crate::planning::ordered_dispatch::{region_count, DispatchKey};
2934 use crate::planning::semantics::{
2935 primitive_boolean_arc, primitive_date_arc, primitive_number_arc, primitive_text_arc,
2936 LemmaType, TypeSpecification,
2937 };
2938 use crate::Span;
2939
2940 fn test_source() -> ParseSource {
2941 ParseSource::new(
2942 SourceType::Volatile,
2943 Span {
2944 start: 0,
2945 end: 0,
2946 line: 1,
2947 col: 1,
2948 },
2949 )
2950 }
2951
2952 fn data_path(name: &str) -> DataPath {
2953 DataPath::new(vec![], name.into())
2954 }
2955
2956 fn declared(name: &str, resolved_type: Arc<LemmaType>) -> (DataPath, DataDefinition) {
2957 (
2958 data_path(name),
2959 DataDefinition::TypeDeclaration {
2960 resolved_type,
2961 declared_suggestion: None,
2962 declared_value: None,
2963 source: test_source(),
2964 },
2965 )
2966 }
2967
2968 fn scope() -> IndexMap<DataPath, DataDefinition> {
2970 let mut data = IndexMap::new();
2971 for entry in [
2972 declared("x", primitive_number_arc().clone()),
2973 declared("y", primitive_number_arc().clone()),
2974 declared("code", primitive_text_arc().clone()),
2975 declared("flag", primitive_boolean_arc().clone()),
2976 declared("day", primitive_date_arc().clone()),
2977 declared(
2978 "rate",
2979 Arc::new(LemmaType::primitive(TypeSpecification::ratio())),
2980 ),
2981 ] {
2982 data.insert(entry.0, entry.1);
2983 }
2984 data
2985 }
2986
2987 fn path_expr(name: &str) -> Ex {
2988 Ex::leaf(ExpressionKind::DataPath(data_path(name)), None)
2989 }
2990
2991 fn text_expr(text: &str) -> Ex {
2992 Ex::leaf(
2993 ExpressionKind::Literal(Box::new(TypedLiteral::text(text.to_string()))),
2994 None,
2995 )
2996 }
2997
2998 fn compare(left: Ex, op: ComparisonComputation, right: Ex) -> Ex {
2999 compose_expr(&[&left, &right], move |ids| {
3000 ExpressionKind::Comparison(ids[0], op.clone(), ids[1])
3001 })
3002 }
3003
3004 fn chain(default: Ex, arms: Vec<(Ex, Ex)>) -> Ex {
3006 unless_chain(default, arms)
3007 }
3008
3009 fn fold(expr: &Ex) -> (NormalFormId, NormalFormInterner) {
3011 let data = scope();
3012 let mut interner = NormalFormInterner::new();
3013 let root = to_normal_form_with_data(expr, &mut interner, &data);
3014 let unit_index = UnitIndex::new();
3015 let signature_index = SignatureIndex::new();
3016 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
3017 let nf = rewrite::normalize(root, Cells::new(&mut interner, measure_scope), None)
3018 .expect("normalize");
3019 (nf, interner)
3020 }
3021
3022 fn table(expr: &Ex) -> (Vec<DispatchKey>, Vec<String>) {
3024 let (nf, interner) = fold(expr);
3025 let NormalFormKind::OrderedDispatch {
3026 boundaries,
3027 regions,
3028 ..
3029 } = &interner.get(nf).kind
3030 else {
3031 panic!("expected OrderedDispatch, got {:?}", interner.get(nf).kind);
3032 };
3033 let rendered = regions
3034 .iter()
3035 .map(|region| explanation_display(interner_forms(&interner), region.cell(), None))
3036 .collect();
3037 (boundaries.to_vec(), rendered)
3038 }
3039
3040 fn assert_declines(expr: &Ex, reason: &str) {
3041 let (nf, interner) = fold(expr);
3042 assert!(
3043 !matches!(
3044 interner.get(nf).kind,
3045 NormalFormKind::OrderedDispatch { .. }
3046 ),
3047 "must not fold ({reason}), got {:?}",
3048 interner.get(nf).kind
3049 );
3050 }
3051
3052 #[test]
3053 fn equality_chain_on_text_folds_to_point_regions() {
3054 let expr = chain(
3055 num_expr(0),
3056 vec![
3057 (
3058 compare(
3059 path_expr("code"),
3060 ComparisonComputation::Is,
3061 text_expr("NL"),
3062 ),
3063 num_expr(1),
3064 ),
3065 (
3066 compare(
3067 path_expr("code"),
3068 ComparisonComputation::Is,
3069 text_expr("BE"),
3070 ),
3071 num_expr(2),
3072 ),
3073 ],
3074 );
3075 let (boundaries, regions) = table(&expr);
3076 assert_eq!(
3077 boundaries,
3078 vec![
3079 DispatchKey::Text(std::sync::Arc::from("BE")),
3080 DispatchKey::Text(std::sync::Arc::from("NL"))
3081 ]
3082 );
3083 assert_eq!(regions, vec!["0", "2", "0", "1", "0"]);
3085 }
3086
3087 #[test]
3088 fn later_arm_wins_when_two_arms_share_a_constant() {
3089 let expr = chain(
3090 num_expr(0),
3091 vec![
3092 (
3093 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3094 num_expr(1),
3095 ),
3096 (
3097 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3098 num_expr(2),
3099 ),
3100 ],
3101 );
3102 let (boundaries, regions) = table(&expr);
3103 assert_eq!(boundaries.len(), 1, "duplicates collapse to one breakpoint");
3104 assert_eq!(regions, vec!["0", "2", "0"]);
3105 }
3106
3107 #[test]
3108 fn greater_than_and_greater_or_equal_differ_at_the_boundary() {
3109 let strict = chain(
3110 num_expr(0),
3111 vec![(
3112 compare(
3113 path_expr("x"),
3114 ComparisonComputation::GreaterThan,
3115 num_expr(5),
3116 ),
3117 num_expr(1),
3118 )],
3119 );
3120 assert_eq!(table(&strict).1, vec!["0", "0", "1"]);
3121
3122 let inclusive = chain(
3123 num_expr(0),
3124 vec![(
3125 compare(
3126 path_expr("x"),
3127 ComparisonComputation::GreaterThanOrEqual,
3128 num_expr(5),
3129 ),
3130 num_expr(1),
3131 )],
3132 );
3133 assert_eq!(table(&inclusive).1, vec!["0", "1", "1"]);
3134 }
3135
3136 #[test]
3137 fn is_not_claims_every_region_but_its_own_point() {
3138 let expr = chain(
3139 num_expr(0),
3140 vec![(
3141 compare(path_expr("x"), ComparisonComputation::IsNot, num_expr(5)),
3142 num_expr(1),
3143 )],
3144 );
3145 assert_eq!(table(&expr).1, vec!["1", "0", "1"]);
3146 }
3147
3148 #[test]
3149 fn overlapping_ordering_arms_resolve_by_arm_order() {
3150 let expr = chain(
3152 num_expr(0),
3153 vec![
3154 (
3155 compare(
3156 path_expr("x"),
3157 ComparisonComputation::GreaterThan,
3158 num_expr(5),
3159 ),
3160 num_expr(1),
3161 ),
3162 (
3163 compare(
3164 path_expr("x"),
3165 ComparisonComputation::GreaterThan,
3166 num_expr(10),
3167 ),
3168 num_expr(2),
3169 ),
3170 ],
3171 );
3172 let (boundaries, regions) = table(&expr);
3173 assert_eq!(boundaries.len(), 2);
3174 assert_eq!(regions.len(), region_count(2));
3175 assert_eq!(regions, vec!["0", "0", "1", "1", "2"]);
3177 }
3178
3179 #[test]
3180 fn literal_on_the_left_mirrors_the_operator() {
3181 let expr = chain(
3182 num_expr(0),
3183 vec![(
3184 compare(num_expr(5), ComparisonComputation::LessThan, path_expr("x")),
3185 num_expr(1),
3186 )],
3187 );
3188 assert_eq!(table(&expr).1, vec!["0", "0", "1"]);
3190 }
3191
3192 #[test]
3193 fn dates_fold_with_ordering_operators() {
3194 let day = |year: i32| {
3195 Ex::leaf(
3196 ExpressionKind::Literal(Box::new(TypedLiteral::date(
3197 crate::planning::semantics::SemanticDateTime {
3198 year,
3199 month: 1,
3200 day: 1,
3201 hour: 0,
3202 minute: 0,
3203 second: 0,
3204 microsecond: 0,
3205 timezone: None,
3206 },
3207 ))),
3208 None,
3209 )
3210 };
3211 let expr = chain(
3212 num_expr(0),
3213 vec![(
3214 compare(
3215 path_expr("day"),
3216 ComparisonComputation::GreaterThanOrEqual,
3217 day(2020),
3218 ),
3219 num_expr(1),
3220 )],
3221 );
3222 let (boundaries, regions) = table(&expr);
3223 assert!(matches!(boundaries.as_slice(), [DispatchKey::Date(_)]));
3224 assert_eq!(regions, vec!["0", "1", "1"]);
3225 }
3226
3227 #[test]
3228 fn calendar_failure_literal_is_a_planning_error() {
3229 let invalid_day = Ex::leaf(
3230 ExpressionKind::Literal(Box::new(TypedLiteral::date(
3231 crate::planning::semantics::SemanticDateTime {
3232 year: 2020,
3233 month: 13,
3234 day: 1,
3235 hour: 0,
3236 minute: 0,
3237 second: 0,
3238 microsecond: 0,
3239 timezone: None,
3240 },
3241 ))),
3242 None,
3243 );
3244 let expr = chain(
3245 num_expr(0),
3246 vec![(
3247 compare(path_expr("day"), ComparisonComputation::Is, invalid_day),
3248 num_expr(1),
3249 )],
3250 );
3251 let data = scope();
3252 let mut interner = NormalFormInterner::new();
3253 let root = to_normal_form_with_data(&expr, &mut interner, &data);
3254 let unit_index = UnitIndex::new();
3255 let signature_index = SignatureIndex::new();
3256 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
3257 let err = rewrite::normalize(root, Cells::new(&mut interner, measure_scope), None)
3258 .expect_err(
3259 "invalid calendar literal must fail planning, not silently stay Piecewise",
3260 );
3261 let message = err.to_string();
3262 assert!(
3263 message.contains("ordered dispatch") && message.contains("Invalid date"),
3264 "unexpected error: {message}"
3265 );
3266 }
3267
3268 #[test]
3269 fn the_pre_image_is_kept_as_the_fold_origin() {
3270 let expr = chain(
3271 num_expr(0),
3272 vec![(
3273 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3274 num_expr(1),
3275 )],
3276 );
3277 let (nf, interner) = fold(&expr);
3278 let origin = interner
3279 .get(nf)
3280 .origin
3281 .expect("dispatch must record its pre-image");
3282 assert!(
3283 matches!(interner.get(origin).kind, NormalFormKind::Piecewise(_)),
3284 "origin must be the Piecewise, got {:?}",
3285 interner.get(origin).kind
3286 );
3287 }
3288
3289 #[test]
3290 fn a_collapsed_arm_keeps_the_whole_origin_chain() {
3291 let expr = chain(
3294 num_expr(0),
3295 vec![
3296 (lt_expr(num_expr(1), num_expr(0)), num_expr(9)),
3297 (
3298 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3299 num_expr(1),
3300 ),
3301 ],
3302 );
3303 let (nf, interner) = fold(&expr);
3304 let origin = interner
3305 .get(nf)
3306 .origin
3307 .expect("dispatch must record its pre-image");
3308 assert!(
3309 matches!(interner.get(origin).kind, NormalFormKind::Piecewise(_)),
3310 "origin must be the Piecewise, got {:?}",
3311 interner.get(origin).kind
3312 );
3313 let recorded = interner
3314 .get(origin)
3315 .origin
3316 .expect("collapse origin must survive the second fold");
3317 let NormalFormKind::Piecewise(arms) = &interner.get(recorded).kind else {
3318 panic!("recorded pre-image must be a Piecewise");
3319 };
3320 assert_eq!(arms.len(), 3, "the dropped arm must still be recorded");
3321 }
3322
3323 #[test]
3324 fn text_declines_ordering_operators() {
3325 assert_declines(
3326 &chain(
3327 num_expr(0),
3328 vec![(
3329 compare(
3330 path_expr("code"),
3331 ComparisonComputation::GreaterThan,
3332 text_expr("NL"),
3333 ),
3334 num_expr(1),
3335 )],
3336 ),
3337 "text has no ordering comparison",
3338 );
3339 }
3340
3341 #[test]
3342 fn arms_on_different_scrutinees_decline() {
3343 assert_declines(
3344 &chain(
3345 num_expr(0),
3346 vec![
3347 (
3348 compare(path_expr("x"), ComparisonComputation::Is, num_expr(1)),
3349 num_expr(1),
3350 ),
3351 (
3352 compare(path_expr("y"), ComparisonComputation::Is, num_expr(2)),
3353 num_expr(2),
3354 ),
3355 ],
3356 ),
3357 "two scrutinees",
3358 );
3359 }
3360
3361 #[test]
3362 fn equality_or_of_is_comparisons_declines() {
3363 assert_declines(
3364 &chain(
3365 num_expr(0),
3366 vec![(
3367 or_expr(
3368 compare(path_expr("x"), ComparisonComputation::Is, num_expr(1)),
3369 compare(path_expr("y"), ComparisonComputation::Is, num_expr(2)),
3370 ),
3371 num_expr(1),
3372 )],
3373 ),
3374 "or of equality comparisons is not an And peel",
3375 );
3376 }
3377
3378 #[test]
3379 fn boolean_scrutinee_declines() {
3380 assert_declines(
3381 &chain(
3382 num_expr(0),
3383 vec![(
3384 compare(
3385 path_expr("flag"),
3386 ComparisonComputation::Is,
3387 bool_expr(true),
3388 ),
3389 num_expr(1),
3390 )],
3391 ),
3392 "boolean has at most two keys",
3393 );
3394 }
3395
3396 #[test]
3397 fn mismatched_key_and_scrutinee_types_decline() {
3398 assert_declines(
3399 &chain(
3400 num_expr(0),
3401 vec![(
3402 compare(path_expr("code"), ComparisonComputation::Is, num_expr(1)),
3403 num_expr(1),
3404 )],
3405 ),
3406 "text scrutinee against a number key",
3407 );
3408 assert_declines(
3409 &chain(
3410 num_expr(0),
3411 vec![(
3412 compare(path_expr("rate"), ComparisonComputation::Is, num_expr(1)),
3413 num_expr(1),
3414 )],
3415 ),
3416 "ratio scrutinee against a plain number key",
3417 );
3418 }
3419
3420 #[test]
3421 fn a_determined_expression_scrutinee_folds() {
3422 let expr = chain(
3424 num_expr(0),
3425 vec![(
3426 compare(
3427 add_expr(path_expr("x"), num_expr(1)),
3428 ComparisonComputation::Is,
3429 num_expr(5),
3430 ),
3431 num_expr(1),
3432 )],
3433 );
3434 let (boundaries, regions) = table(&expr);
3435 assert_eq!(boundaries.len(), 1);
3436 assert_eq!(regions, vec!["0", "1", "0"]);
3437 }
3438
3439 #[test]
3440 fn conjunctive_key_lut_folds_to_nested_dispatch() {
3441 let expr = chain(
3442 num_expr(0),
3443 vec![
3444 (
3445 and_expr(
3446 compare(path_expr("x"), ComparisonComputation::Is, num_expr(2)),
3447 compare(
3448 path_expr("y"),
3449 ComparisonComputation::GreaterThanOrEqual,
3450 num_expr(1),
3451 ),
3452 ),
3453 num_expr(10),
3454 ),
3455 (
3456 and_expr(
3457 compare(path_expr("x"), ComparisonComputation::Is, num_expr(2)),
3458 compare(
3459 path_expr("y"),
3460 ComparisonComputation::GreaterThanOrEqual,
3461 num_expr(5),
3462 ),
3463 ),
3464 num_expr(20),
3465 ),
3466 (
3467 and_expr(
3468 compare(path_expr("x"), ComparisonComputation::Is, num_expr(3)),
3469 compare(
3470 path_expr("y"),
3471 ComparisonComputation::GreaterThanOrEqual,
3472 num_expr(1),
3473 ),
3474 ),
3475 num_expr(30),
3476 ),
3477 ],
3478 );
3479 let (nf, interner) = fold(&expr);
3480 let NormalFormKind::OrderedDispatch {
3481 boundaries,
3482 regions,
3483 ..
3484 } = &interner.get(nf).kind
3485 else {
3486 panic!(
3487 "expected outer OrderedDispatch, got {:?}",
3488 interner.get(nf).kind
3489 );
3490 };
3491 assert_eq!(boundaries.len(), 2, "outer keys are the two zone values");
3492 let point_bodies: Vec<_> = regions
3494 .iter()
3495 .enumerate()
3496 .filter(|(index, _)| index % 2 == 1)
3497 .map(|(_, region)| &interner.get(region.cell()).kind)
3498 .collect();
3499 assert_eq!(point_bodies.len(), 2);
3500 for kind in &point_bodies {
3501 assert!(
3502 matches!(kind, NormalFormKind::OrderedDispatch { .. }),
3503 "point region must be an inner OrderedDispatch, got {kind:?}"
3504 );
3505 }
3506 for (index, region) in regions.iter().enumerate() {
3507 if index % 2 == 0 {
3508 assert_eq!(
3509 explanation_display(interner_forms(&interner), region.cell(), None),
3510 "0",
3511 "interval region must stay the default"
3512 );
3513 }
3514 }
3515 }
3516
3517 #[test]
3518 fn conjunctive_key_without_shared_is_scrutinee_declines() {
3519 assert_declines(
3521 &chain(
3522 num_expr(0),
3523 vec![
3524 (
3525 and_expr(
3526 compare(
3527 path_expr("x"),
3528 ComparisonComputation::GreaterThan,
3529 num_expr(1),
3530 ),
3531 compare(
3532 path_expr("y"),
3533 ComparisonComputation::GreaterThan,
3534 num_expr(1),
3535 ),
3536 ),
3537 num_expr(1),
3538 ),
3539 (
3540 and_expr(
3541 compare(
3542 path_expr("x"),
3543 ComparisonComputation::GreaterThan,
3544 num_expr(2),
3545 ),
3546 compare(
3547 path_expr("y"),
3548 ComparisonComputation::GreaterThan,
3549 num_expr(2),
3550 ),
3551 ),
3552 num_expr(2),
3553 ),
3554 ],
3555 ),
3556 "no Is key conjunct shared across arms",
3557 );
3558 }
3559
3560 #[test]
3561 fn an_unknown_data_path_declines() {
3562 assert_declines(
3563 &chain(
3564 num_expr(0),
3565 vec![(
3566 compare(path_expr("absent"), ComparisonComputation::Is, num_expr(5)),
3567 num_expr(1),
3568 )],
3569 ),
3570 "the scrutinee has no resolved type in scope",
3571 );
3572 }
3573
3574 #[test]
3575 fn a_non_comparison_condition_declines() {
3576 assert_declines(
3577 &chain(num_expr(0), vec![(path_expr("flag"), num_expr(1))]),
3578 "a bare boolean condition is not a comparison",
3579 );
3580 }
3581
3582 #[test]
3583 fn a_comparison_between_two_data_paths_declines() {
3584 assert_declines(
3585 &chain(
3586 num_expr(0),
3587 vec![(
3588 compare(path_expr("x"), ComparisonComputation::Is, path_expr("y")),
3589 num_expr(1),
3590 )],
3591 ),
3592 "neither side is a constant",
3593 );
3594 }
3595
3596 #[test]
3599 fn folded_table_agrees_with_the_piecewise_it_replaced() {
3600 let operators = [
3601 ComparisonComputation::Is,
3602 ComparisonComputation::IsNot,
3603 ComparisonComputation::GreaterThan,
3604 ComparisonComputation::GreaterThanOrEqual,
3605 ComparisonComputation::LessThan,
3606 ComparisonComputation::LessThanOrEqual,
3607 ];
3608 let constants = [5i64, 2, 5, 8];
3610 let mut checked = 0usize;
3611
3612 for first in &operators {
3613 for second in &operators {
3614 for third in &operators {
3615 let arm_operators = [first, second, third];
3616 let arms: Vec<(Ex, Ex)> = arm_operators
3617 .iter()
3618 .enumerate()
3619 .map(|(index, operator)| {
3620 (
3621 compare(
3622 path_expr("x"),
3623 (*operator).clone(),
3624 num_expr(constants[index]),
3625 ),
3626 num_expr(index as i64 + 1),
3627 )
3628 })
3629 .collect();
3630 let expr = chain(num_expr(0), arms);
3631 let (boundaries, regions) = table(&expr);
3632
3633 for probe in [-100i64, 1, 2, 3, 5, 6, 8, 9, 100] {
3635 let expected = arm_operators
3636 .iter()
3637 .enumerate()
3638 .rev()
3639 .find_map(|(index, operator)| {
3640 predicate_holds(probe, operator, constants[index])
3641 .then_some(index as i64 + 1)
3642 })
3643 .unwrap_or(0);
3644 let region = crate::planning::ordered_dispatch::region_for_value(
3645 &boundaries,
3646 &DispatchKey::Rational(rational_new(probe, 1)).as_probe(),
3647 )
3648 .expect("integers compare");
3649 assert_eq!(
3650 regions[region],
3651 expected.to_string(),
3652 "operators {first} / {second} / {third} disagree at x = {probe}"
3653 );
3654 checked += 1;
3655 }
3656 }
3657 }
3658 }
3659 assert_eq!(checked, 6 * 6 * 6 * 9);
3660 }
3661
3662 fn predicate_holds(value: i64, operator: &ComparisonComputation, constant: i64) -> bool {
3663 match operator {
3664 ComparisonComputation::Is => value == constant,
3665 ComparisonComputation::IsNot => value != constant,
3666 ComparisonComputation::GreaterThan => value > constant,
3667 ComparisonComputation::GreaterThanOrEqual => value >= constant,
3668 ComparisonComputation::LessThan => value < constant,
3669 ComparisonComputation::LessThanOrEqual => value <= constant,
3670 }
3671 }
3672 }
3673
3674 #[test]
3675 fn flatten_associative_sum_nested() {
3676 let inner = add_expr(num_expr(1), num_expr(2));
3677 let expr = add_expr(inner, num_expr(3));
3678 let norm = normalize_expression(&expr).expect("normalize");
3679 assert!(matches!(
3680 norm.root_kind(),
3681 ExpressionKind::Literal(ref l)
3682 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(6, 1))
3683 ));
3684 }
3685
3686 #[test]
3687 fn flatten_associative_product_nested() {
3688 let inner = mul_expr(num_expr(2), num_expr(3));
3689 let expr = mul_expr(inner, num_expr(4));
3690 let norm = normalize_expression(&expr).expect("normalize");
3691 assert!(matches!(
3692 norm.root_kind(),
3693 ExpressionKind::Literal(ref l)
3694 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(24, 1))
3695 ));
3696 }
3697
3698 #[test]
3699 fn and_chain_lowers_to_one_flat_cell() {
3700 let chain = compose_expr(
3701 &[&bool_expr(true), &bool_expr(true), &bool_expr(false)],
3702 |ids| logical_kind(LogicalConnective::And, ids[0], vec![ids[1], ids[2]]),
3703 );
3704 let mut interner = NormalFormInterner::new();
3705 let id = to_normal_form_empty(&chain, &mut interner);
3706 let NormalFormKind::Logical(LogicalConnective::And, children) = &interner.get(id).kind
3707 else {
3708 panic!("expected And(..), got {:?}", interner.get(id).kind);
3709 };
3710 let [first, second, third] = children.as_slice() else {
3711 panic!("expected three flat operands, got {children:?}");
3712 };
3713 for operand in [first, second, third] {
3714 assert!(matches!(
3715 &interner.get(*operand).kind,
3716 NormalFormKind::Leaf(LeafKind::Literal(_))
3717 ));
3718 }
3719 }
3720
3721 #[test]
3722 fn parenthesized_sum_flattens_into_parent() {
3723 let y = Ex::leaf(
3724 ExpressionKind::DataPath(DataPath::new(vec![], "y".into())),
3725 None,
3726 );
3727 for expr in [
3728 add_expr(dx(), add_expr(y.clone(), num_expr(2))),
3729 add_expr(add_expr(dx(), y.clone()), num_expr(2)),
3730 ] {
3731 let norm = normalize_expression(&expr).expect("normalize");
3732 let ExpressionKind::Arithmetic { steps, .. } = norm.root_kind() else {
3733 panic!("expected one sum, got {:?}", norm.root_kind());
3734 };
3735 assert_eq!(steps.len(), 2, "expected three flat terms, got {norm:?}");
3736 }
3737 }
3738
3739 #[test]
3740 fn parenthesized_conjunction_flattens_into_parent() {
3741 let x_below_one = lt_expr(dx(), num_expr(1));
3742 for expr in [
3743 and_expr(dflag(), and_expr(dflag2(), x_below_one.clone())),
3744 and_expr(and_expr(dflag(), dflag2()), x_below_one.clone()),
3745 ] {
3746 let norm = normalize_expression(&expr).expect("normalize");
3747 let ExpressionKind::Logical {
3748 connective: LogicalConnective::And,
3749 steps,
3750 ..
3751 } = norm.root_kind()
3752 else {
3753 panic!("expected one conjunction, got {:?}", norm.root_kind());
3754 };
3755 assert_eq!(steps.len(), 2, "expected three flat operands, got {norm:?}");
3756 }
3757 }
3758
3759 #[test]
3760 fn or_chain_lowers_to_one_flat_cell() {
3761 let chain = compose_expr(
3762 &[&bool_expr(false), &bool_expr(false), &bool_expr(true)],
3763 |ids| logical_kind(LogicalConnective::Or, ids[0], vec![ids[1], ids[2]]),
3764 );
3765 let mut interner = NormalFormInterner::new();
3766 let id = to_normal_form_empty(&chain, &mut interner);
3767 let NormalFormKind::Logical(LogicalConnective::Or, children) = &interner.get(id).kind
3768 else {
3769 panic!("expected Or(..), got {:?}", interner.get(id).kind);
3770 };
3771 let [first, second, third] = children.as_slice() else {
3772 panic!("expected three flat operands, got {children:?}");
3773 };
3774 for operand in [first, second, third] {
3775 assert!(matches!(
3776 &interner.get(*operand).kind,
3777 NormalFormKind::Leaf(LeafKind::Literal(_))
3778 ));
3779 }
3780 }
3781
3782 #[test]
3783 fn parenthesized_disjunction_flattens_into_parent() {
3784 let x_below_one = lt_expr(dx(), num_expr(1));
3785 for expr in [
3786 or_expr(dflag(), or_expr(dflag2(), x_below_one.clone())),
3787 or_expr(or_expr(dflag(), dflag2()), x_below_one.clone()),
3788 ] {
3789 let norm = normalize_expression(&expr).expect("normalize");
3790 let ExpressionKind::Logical {
3791 connective: LogicalConnective::Or,
3792 steps,
3793 ..
3794 } = norm.root_kind()
3795 else {
3796 panic!("expected one disjunction, got {:?}", norm.root_kind());
3797 };
3798 assert_eq!(steps.len(), 2, "expected three flat operands, got {norm:?}");
3799 }
3800 }
3801
3802 #[test]
3803 fn mixed_or_and_does_not_flatten() {
3804 let expr = or_expr(dflag(), and_expr(dflag2(), lt_expr(dx(), num_expr(1))));
3805 let norm = normalize_expression(&expr).expect("normalize");
3806 let ExpressionKind::Logical {
3807 connective: LogicalConnective::Or,
3808 first,
3809 steps,
3810 } = norm.root_kind()
3811 else {
3812 panic!("expected or root, got {:?}", norm.root_kind());
3813 };
3814 assert_eq!(steps.len(), 1);
3815 assert!(matches!(
3816 norm.at(*first).kind(),
3817 ExpressionKind::DataPath(ref p) if p.data == "flag"
3818 ));
3819 assert!(matches!(
3820 norm.at(steps[0].operand).kind(),
3821 ExpressionKind::Logical {
3822 connective: LogicalConnective::And,
3823 ..
3824 }
3825 ));
3826 }
3827
3828 #[test]
3829 fn true_or_data_absorbs_to_true() {
3830 let norm = normalize_expression(&or_expr(bool_expr(true), dflag())).expect("normalize");
3831 assert!(matches!(
3832 norm.root_kind(),
3833 ExpressionKind::Literal(ref literal)
3834 if matches!(literal.value, crate::planning::semantics::ValueKind::Boolean(true))
3835 ));
3836 }
3837
3838 #[test]
3839 fn false_or_data_folds_to_data() {
3840 let norm = normalize_expression(&or_expr(bool_expr(false), dflag())).expect("normalize");
3841 assert!(matches!(
3842 norm.root_kind(),
3843 ExpressionKind::DataPath(ref path) if path.data == "flag"
3844 ));
3845 }
3846
3847 #[test]
3848 fn identity_add_zero() {
3849 let expr = add_expr(dx(), num_expr(0));
3850 let norm = normalize_expression(&expr).expect("normalize");
3851 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3852 }
3853
3854 #[test]
3855 fn identity_mul_one() {
3856 let expr = mul_expr(dx(), num_expr(1));
3857 let norm = normalize_expression(&expr).expect("normalize");
3858 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3859 }
3860
3861 #[test]
3862 fn identity_mul_zero_with_data_path_is_not_folded() {
3863 let expr = mul_expr(dx(), num_expr(0));
3866 let norm = normalize_expression(&expr).expect("normalize");
3867 let Some((left, ArithmeticComputation::Multiply, right)) =
3868 binary_arithmetic(norm.root_kind())
3869 else {
3870 panic!("expected preserved multiply, got {:?}", norm.root_kind());
3871 };
3872 assert!(matches!(norm.at(*left).kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3873 assert!(matches!(
3874 norm.at(*right).kind(),
3875 ExpressionKind::Literal(ref l)
3876 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(0, 1))
3877 ));
3878 }
3879
3880 #[test]
3881 fn identity_mul_zero_with_literal_folds() {
3882 let expr = mul_expr(num_expr(7), num_expr(0));
3884 let norm = normalize_expression(&expr).expect("normalize");
3885 assert!(matches!(
3886 norm.root_kind(),
3887 ExpressionKind::Literal(ref l)
3888 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(0, 1))
3889 ));
3890 }
3891
3892 #[test]
3893 fn identity_pow_one_and_zero() {
3894 let p1 = normalize_expression(&pow_expr(dx(), num_expr(1))).expect("normalize");
3895 assert!(matches!(p1.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3896 let p0 = normalize_expression(&pow_expr(dx(), num_expr(0))).expect("normalize");
3899 let Some((base, ArithmeticComputation::Power, exp)) = binary_arithmetic(p0.root_kind())
3900 else {
3901 panic!("expected preserved power, got {:?}", p0.root_kind());
3902 };
3903 assert!(matches!(p0.at(*base).kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3904 assert!(matches!(
3905 p0.at(*exp).kind(),
3906 ExpressionKind::Literal(ref l)
3907 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(0, 1))
3908 ));
3909 let literal_pow_zero =
3911 normalize_expression(&pow_expr(num_expr(7), num_expr(0))).expect("normalize");
3912 assert!(matches!(
3913 literal_pow_zero.root_kind(),
3914 ExpressionKind::Literal(ref l)
3915 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(1, 1))
3916 ));
3917 }
3918
3919 #[test]
3920 fn double_logical_negation() {
3921 let norm = normalize_expression(¬_expr(not_expr(bool_expr(true)))).expect("normalize");
3922 assert!(matches!(
3923 norm.root_kind(),
3924 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(true))
3925 ));
3926 }
3927
3928 #[test]
3929 fn double_numeric_negation() {
3930 let zero = num_expr(0);
3931 let inner = sub_expr(zero.clone(), dx());
3932 let expr = sub_expr(zero, inner);
3933 let norm = normalize_expression(&expr).expect("normalize");
3934 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3935 }
3936
3937 #[test]
3938 fn double_reciprocal_with_data_path_is_not_collapsed() {
3939 let one = num_expr(1);
3942 let inner = div_expr(one.clone(), dx());
3943 let expr = div_expr(one, inner);
3944 let norm = normalize_expression(&expr).expect("normalize");
3945 let Some((_, ArithmeticComputation::Divide, outer_denominator)) =
3946 binary_arithmetic(norm.root_kind())
3947 else {
3948 panic!(
3949 "expected preserved outer division, got {:?}",
3950 norm.root_kind()
3951 );
3952 };
3953 let Some((_, ArithmeticComputation::Divide, inner_denominator)) =
3954 binary_arithmetic(norm.at(*outer_denominator).kind())
3955 else {
3956 panic!(
3957 "expected preserved inner division, got {:?}",
3958 norm.at(*outer_denominator).kind()
3959 );
3960 };
3961 assert!(
3962 matches!(norm.at(*outer_denominator).child(*inner_denominator).kind(), ExpressionKind::DataPath(ref p) if p.data == "x")
3963 );
3964 }
3965
3966 #[test]
3967 fn sqrt_squared_numeric_folds_to_exact_rational() {
3968 let sqrt2 = math_expr(MathematicalComputation::Sqrt, num_expr(2));
3969 let expr = pow_expr(sqrt2, num_expr(2));
3970 let norm = normalize_expression(&expr).expect("normalize");
3971 assert!(matches!(
3972 norm.root_kind(),
3973 ExpressionKind::Literal(ref l)
3974 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(2, 1))
3975 ));
3976 }
3977
3978 #[test]
3980 fn literal_power_with_irrational_result_stays_power() {
3981 let half_lit = literal_from_folded_rational(rational_new(1, 2));
3982 let half = Ex::leaf(
3983 ExpressionKind::Literal(Box::new(TypedLiteral {
3984 value: half_lit.value,
3985 lemma_type: primitive_number_arc().clone(),
3986 })),
3987 None,
3988 );
3989 let expr = pow_expr(num_expr(2), half);
3990 let norm = normalize_expression(&expr).expect("normalize");
3991 assert!(
3992 matches!(
3993 binary_arithmetic(norm.root_kind()),
3994 Some((_, ArithmeticComputation::Power, _))
3995 ),
3996 "expected symbolic Power, got {:?}",
3997 norm.root_kind()
3998 );
3999 }
4000
4001 #[test]
4002 fn negated_conjunction_preserved_for_data_paths() {
4003 let expr = not_expr(and_expr(dflag(), dflag2()));
4007 let norm = normalize_expression(&expr).expect("normalize");
4008 let ExpressionKind::LogicalNegation(inner, _) = norm.root_kind() else {
4009 panic!("expected preserved negation, got {:?}", norm.root_kind());
4010 };
4011 assert!(
4012 matches!(
4013 norm.at(*inner).kind(),
4014 ExpressionKind::Logical {
4015 connective: LogicalConnective::And,
4016 ..
4017 }
4018 ),
4019 "expected preserved conjunction under the negation, got {:?}",
4020 norm.at(*inner).kind()
4021 );
4022 }
4023
4024 #[test]
4025 fn power_law_nested_power() {
4026 let expr = pow_expr(pow_expr(dx(), num_expr(2)), num_expr(3));
4027 let norm = normalize_expression(&expr).expect("normalize");
4028 assert!(matches!(
4029 binary_arithmetic(norm.root_kind()),
4030 Some((_, ArithmeticComputation::Power, _))
4031 ));
4032 let Some((base, ArithmeticComputation::Power, exp)) = binary_arithmetic(norm.root_kind())
4033 else {
4034 unreachable!();
4035 };
4036 assert!(matches!(
4037 norm.at(*base).kind(),
4038 ExpressionKind::DataPath(ref p) if p.data == "x"
4039 ));
4040 assert!(matches!(
4041 norm.at(*exp).kind(),
4042 ExpressionKind::Literal(ref l)
4043 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(6, 1))
4044 ));
4045 }
4046
4047 #[test]
4048 fn power_law_like_base_product() {
4049 let expr = mul_expr(pow_expr(dx(), num_expr(2)), pow_expr(dx(), num_expr(3)));
4050 let norm = normalize_expression(&expr).expect("normalize");
4051 let Some((base, ArithmeticComputation::Power, exp)) = binary_arithmetic(norm.root_kind())
4052 else {
4053 panic!("expected single power, got {:?}", norm.root_kind());
4054 };
4055 assert!(matches!(norm.at(*base).kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
4056 assert!(matches!(
4057 norm.at(*exp).kind(),
4058 ExpressionKind::Literal(ref l)
4059 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(5, 1))
4060 ));
4061 }
4062
4063 #[test]
4064 fn negated_comparison_not_less_than() {
4065 let cmp = lt_expr(dx(), num_expr(0));
4066 let norm = normalize_expression(¬_expr(cmp)).expect("normalize");
4067 let ExpressionKind::Comparison(_, op, _) = norm.root_kind() else {
4068 panic!("expected Comparison, got {:?}", norm.root_kind());
4069 };
4070 assert_eq!(*op, ComparisonComputation::GreaterThanOrEqual);
4071 }
4072
4073 #[test]
4074 fn logical_short_circuit_and_false_with_data_path_folds() {
4075 let expr = and_expr(bool_expr(false), dflag());
4078 let norm = normalize_expression(&expr).expect("normalize");
4079 assert!(matches!(
4080 norm.root_kind(),
4081 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(false))
4082 ));
4083 }
4084
4085 #[test]
4086 fn logical_short_circuit_and_false_all_literal_folds() {
4087 let expr = and_expr(bool_expr(false), bool_expr(true));
4088 let norm = normalize_expression(&expr).expect("normalize");
4089 assert!(matches!(
4090 norm.root_kind(),
4091 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(false))
4092 ));
4093 }
4094
4095 #[test]
4096 fn logical_and_false_on_right_with_data_path_folds() {
4097 let expr = and_expr(dflag(), bool_expr(false));
4098 let norm = normalize_expression(&expr).expect("normalize");
4099 assert!(matches!(
4100 norm.root_kind(),
4101 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(false))
4102 ));
4103 }
4104
4105 #[test]
4106 fn logical_idempotency_and_duplicate_paths() {
4107 let a = dflag();
4108 let expr = and_expr(a.clone(), a);
4109 let norm = normalize_expression(&expr).expect("normalize");
4110 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "flag"));
4111 }
4112
4113 #[test]
4114 fn unit_conversion_fold_number_to_number() {
4115 let inner = num_expr(42);
4116 let expr = compose_expr(&[&inner], |ids| {
4117 ExpressionKind::UnitConversion(
4118 ids[0],
4119 SemanticConversionTarget::Type(ConversionTypeKind::Number),
4120 )
4121 });
4122 let norm = normalize_expression(&expr).expect("normalize");
4123 assert!(matches!(
4124 norm.root_kind(),
4125 ExpressionKind::Literal(ref l)
4126 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(42, 1))
4127 ));
4128 }
4129
4130 #[test]
4131 fn piecewise_collapse_last_true_unless_keeps_only_that_result() {
4132 let piecewise = unless_chain(num_expr(0), vec![(bool_expr(true), num_expr(42))]);
4133 let norm = normalize_expression(&piecewise).expect("normalize");
4134 assert!(matches!(
4135 norm.root_kind(),
4136 ExpressionKind::Literal(ref l)
4137 if matches!(l.value, ValueKind::Number(ref n) if n == &rational_new(42, 1))
4138 ));
4139 }
4140
4141 #[test]
4142 fn collapsed_unless_false_retains_piecewise_origin() {
4143 let piecewise = unless_chain(num_expr(1), vec![(bool_expr(false), num_expr(2))]).tree();
4144 let mut interner = NormalFormInterner::new();
4145 let completed_rules = HashMap::new();
4146 let rule_types = HashMap::new();
4147 let data = IndexMap::new();
4148 let unit_index = UnitIndex::new();
4149 let signature_index = SignatureIndex::new();
4150 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
4151 let lower = LowerCtx {
4152 completed_rules: &completed_rules,
4153 rule_types: &rule_types,
4154 data: &data,
4155 row_templates: &EMPTY_ROW_TEMPLATES,
4156 self_identity: &TEST_SELF_IDENTITY,
4157 rule_order: &[],
4158 };
4159 let root = to_normal_form(
4160 &piecewise,
4161 &mut Cells::new(&mut interner, measure_scope),
4162 &lower,
4163 );
4164 let nf = rewrite::normalize(root, Cells::new(&mut interner, measure_scope), None)
4165 .expect("normalize");
4166 let cell = interner.get(nf);
4167 assert!(
4168 cell.origin.is_some(),
4169 "collapsed unless must keep piecewise origin, kind={:?}",
4170 cell.kind
4171 );
4172 let origin = cell.origin.expect("origin");
4173 assert!(
4174 matches!(interner.get(origin).kind, NormalFormKind::Piecewise(_)),
4175 "origin must be piecewise, got {:?}",
4176 interner.get(origin).kind
4177 );
4178 }
4179
4180 #[test]
4181 fn piecewise_collapse_drops_arm_with_statically_false_condition() {
4182 let piecewise = unless_chain(
4183 num_expr(7),
4184 vec![(lt_expr(num_expr(1), num_expr(0)), num_expr(99))],
4185 );
4186 let norm = normalize_expression(&piecewise).expect("normalize");
4187 assert!(matches!(
4188 norm.root_kind(),
4189 ExpressionKind::Literal(ref l)
4190 if matches!(l.value, ValueKind::Number(ref n) if n == &rational_new(7, 1))
4191 ));
4192 }
4193
4194 #[test]
4195 fn constant_fold_divide_of_literals_to_number() {
4196 let expr = div_expr(num_expr(5), num_expr(2));
4197 let norm = normalize_expression(&expr).expect("normalize");
4198 assert!(matches!(
4199 norm.root_kind(),
4200 ExpressionKind::Literal(ref l)
4201 if matches!(l.value, ValueKind::Number(ref n) if n == &rational_new(5, 2))
4202 ));
4203 }
4204
4205 #[test]
4206 fn constant_fold_divide_comparison_to_true() {
4207 let expr = lt_expr(div_expr(num_expr(5), num_expr(2)), num_expr(4));
4208 let norm = normalize_expression(&expr).expect("normalize");
4209 assert!(matches!(
4210 norm.root_kind(),
4211 ExpressionKind::Literal(ref l) if matches!(l.value, ValueKind::Boolean(true))
4212 ));
4213 }
4214
4215 #[test]
4216 fn constant_fold_nested_arithmetic_comparison_to_true() {
4217 let expr = lt_expr(
4219 div_expr(
4220 mul_expr(add_expr(num_expr(1), num_expr(2)), num_expr(3)),
4221 num_expr(4),
4222 ),
4223 num_expr(10),
4224 );
4225 let norm = normalize_expression(&expr).expect("normalize");
4226 assert!(matches!(
4227 norm.root_kind(),
4228 ExpressionKind::Literal(ref l) if matches!(l.value, ValueKind::Boolean(true))
4229 ));
4230 }
4231
4232 #[test]
4233 fn power_laws_sqrt_squared_is_not_merged_for_data_paths() {
4234 let x = Ex::leaf(
4235 ExpressionKind::DataPath(DataPath::new(vec![], "x".into())),
4236 None,
4237 );
4238 let expr = mul_expr(
4239 math_expr(MathematicalComputation::Sqrt, x.clone()),
4240 math_expr(MathematicalComputation::Sqrt, x),
4241 );
4242 let norm = normalize_expression(&expr).expect("normalize");
4243 let mut interner = NormalFormInterner::new();
4244 let id = lower_with_data(&norm, &mut interner, &algebraic_scope());
4245 assert!(
4246 matches!(&interner.get(id).kind, NormalFormKind::Product(ref children) if children.len() == 2),
4247 "sqrt(x)*sqrt(x) must stay a product of two powers, got {:?}",
4248 interner.get(id).kind
4249 );
4250 }
4251
4252 #[test]
4255 fn exp_log_not_folded_for_data_path() {
4256 let inner = math_expr(MathematicalComputation::Log, dx());
4259 let expr = math_expr(MathematicalComputation::Exp, inner);
4260 let norm = normalize_expression(&expr).expect("normalize");
4261 let ExpressionKind::MathematicalComputation(MathematicalComputation::Exp, inner) =
4262 norm.root_kind()
4263 else {
4264 panic!("expected preserved exp, got {:?}", norm.root_kind());
4265 };
4266 assert!(
4267 matches!(
4268 norm.at(*inner).kind(),
4269 ExpressionKind::MathematicalComputation(MathematicalComputation::Log, _)
4270 ),
4271 "expected preserved log under exp, got {:?}",
4272 norm.at(*inner).kind()
4273 );
4274 }
4275
4276 #[test]
4277
4278 fn constant_fold_add() {
4279 let expr = add_expr(num_expr(2), num_expr(3));
4280 let norm = normalize_expression(&expr).expect("normalize");
4281 assert!(matches!(
4282 norm.root_kind(),
4283 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(5, 1))
4284 ));
4285 }
4286
4287 #[test]
4290 fn member_read_on_import_lowers_to_row_ends() {
4291 let mut engine = crate::Engine::new();
4292 engine
4293 .load([(
4294 crate::SourceType::Path(Arc::new(std::path::PathBuf::from("member.lemma"))),
4295 r#"
4296spec leaf
4297data x: 3
4298
4299spec mid
4300uses sub: leaf
4301
4302spec outer
4303uses i: mid
4304rule r: i.sub.x
4305"#
4306 .to_string(),
4307 )])
4308 .expect("load member fixture");
4309 let plan = engine
4310 .plans
4311 .get_plans(None, "outer")
4312 .and_then(|plans| plans.values().next())
4313 .expect("outer plan");
4314 let rule = plan
4315 .rules
4316 .get(&RulePath::new(Vec::new(), "r".to_string()))
4317 .expect("rule r");
4318 let NormalFormKind::SpecMember { base, row, .. } = &plan.normal_form(rule.normal_form).kind
4319 else {
4320 panic!(
4321 "`i.sub.x` lowers to a member read, got {:?}",
4322 plan.normal_form(rule.normal_form).kind
4323 );
4324 };
4325 assert!(matches!(
4326 &plan.normal_form(*base).kind,
4327 NormalFormKind::Leaf(LeafKind::DataPath(path)) if path.input_key() == "i"
4328 ));
4329 assert_eq!(
4330 row.ends,
4331 vec![
4332 RowEnd::Data(DataPath::local("sub".to_string())),
4333 RowEnd::Data(DataPath::local("x".to_string())),
4334 ]
4335 );
4336 assert!(plan.data.keys().all(|path| path.segments.is_empty()));
4337 }
4338}