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(
804 data: &IndexMap<DataPath, DataDefinition>,
805 path: &DataPath,
806) -> Arc<LemmaType> {
807 match data.get(path) {
808 Some(DataDefinition::Value { resolved_type, .. })
809 | Some(DataDefinition::TypeDeclaration { resolved_type, .. })
810 | Some(DataDefinition::Reference { resolved_type, .. })
811 | Some(DataDefinition::InstanceList { resolved_type, .. }) => Arc::clone(resolved_type),
812 Some(DataDefinition::Import { row, .. }) => Arc::new(LemmaType::primitive(
813 crate::planning::semantics::TypeSpecification::Spec {
814 spec: row.identity.as_ref().clone(),
815 },
816 )),
817 None => Arc::new(LemmaType::undetermined_type()),
820 }
821}
822
823pub(crate) struct Cells<'a> {
827 interner: &'a mut NormalFormInterner,
828 measure_scope: MeasureScope<'a>,
829}
830
831pub(crate) struct RowFieldEnd {
835 pub(crate) end: RowEnd,
836 pub(crate) qualifier: Option<Vec<String>>,
837}
838
839impl<'a> Cells<'a> {
840 pub(crate) fn new(
841 interner: &'a mut NormalFormInterner,
842 measure_scope: MeasureScope<'a>,
843 ) -> Self {
844 Self {
845 interner,
846 measure_scope,
847 }
848 }
849
850 pub(crate) fn get(&self, id: NormalFormId) -> &NormalForm {
851 self.interner.get(id)
852 }
853
854 pub(crate) fn result_type(&self, id: NormalFormId) -> &Arc<LemmaType> {
855 self.interner.result_type(id)
856 }
857
858 pub(crate) fn len(&self) -> usize {
860 self.interner.len()
861 }
862
863 pub(crate) fn intern_empty(&mut self, kind: NormalFormKind) -> NormalFormId {
864 let typed = self.type_of(&kind, None);
865 self.interner.intern(kind, None, None, None, typed)
866 }
867
868 pub(crate) fn intern_literal_leaf(
869 &mut self,
870 literal: LiteralValue,
871 result_type: Arc<LemmaType>,
872 ) -> NormalFormId {
873 self.interner.intern(
874 NormalFormKind::Leaf(LeafKind::Literal(literal)),
875 None,
876 None,
877 None,
878 Typed {
879 result_type,
880 fold_types: Vec::new(),
881 },
882 )
883 }
884
885 fn intern_row_field(
887 &mut self,
888 level: u32,
889 identity: SpecIdentity,
890 row_end: RowFieldEnd,
891 result_type: Arc<LemmaType>,
892 ) -> NormalFormId {
893 let RowFieldEnd { end, qualifier } = row_end;
894 self.interner.intern(
895 NormalFormKind::RowField {
896 level,
897 identity,
898 end,
899 qualifier,
900 },
901 None,
902 None,
903 None,
904 Typed {
905 result_type,
906 fold_types: Vec::new(),
907 },
908 )
909 }
910
911 fn intern_row_value_leaf(&mut self, level: u32, result_type: Arc<LemmaType>) -> NormalFormId {
912 self.interner.intern(
913 NormalFormKind::Leaf(LeafKind::RowValue { level }),
914 None,
915 None,
916 None,
917 Typed {
918 result_type,
919 fold_types: Vec::new(),
920 },
921 )
922 }
923
924 fn intern_data_leaf(&mut self, path: DataPath, result_type: Arc<LemmaType>) -> NormalFormId {
925 self.interner.intern(
926 NormalFormKind::Leaf(LeafKind::DataPath(path)),
927 None,
928 None,
929 None,
930 Typed {
931 result_type,
932 fold_types: Vec::new(),
933 },
934 )
935 }
936
937 fn intern_rule_ref(
941 &mut self,
942 body: NormalFormId,
943 rule_path: RulePath,
944 result_type: Arc<LemmaType>,
945 ) -> NormalFormId {
946 let kind = self.interner.get(body).kind.clone();
947 self.interner.intern(
948 kind,
949 None,
950 None,
951 Some(rule_path),
952 Typed {
953 result_type,
954 fold_types: Vec::new(),
955 },
956 )
957 }
958
959 pub(crate) fn rebuild(
960 &mut self,
961 kind: NormalFormKind,
962 source: Option<Source>,
963 origin: Option<NormalFormId>,
964 ) -> NormalFormId {
965 let typed = self.type_of(&kind, origin);
966 self.interner.intern(kind, source, origin, None, typed)
967 }
968
969 pub(crate) fn fold_into(
970 &mut self,
971 kind: NormalFormKind,
972 destroyed_id: NormalFormId,
973 ) -> NormalFormId {
974 let typed = self.type_of(&kind, Some(destroyed_id));
975 self.interner
976 .intern(kind, None, Some(destroyed_id), None, typed)
977 }
978
979 pub(crate) fn fold_into_survivor(
980 &mut self,
981 survivor_id: NormalFormId,
982 destroyed_id: NormalFormId,
983 ) -> NormalFormId {
984 let survivor = self.interner.get(survivor_id);
985 if survivor.origin.is_some() {
988 return survivor_id;
989 }
990 self.interner.intern(
991 survivor.kind.clone(),
992 survivor.source.clone(),
993 Some(destroyed_id),
994 survivor.rule_ref.clone(),
995 Typed {
996 result_type: Arc::clone(&survivor.result_type),
997 fold_types: survivor.fold_types.clone(),
998 },
999 )
1000 }
1001
1002 pub(crate) fn replace_with_survivor(
1006 &mut self,
1007 survivor_id: NormalFormId,
1008 destroyed_id: NormalFormId,
1009 ) -> NormalFormId {
1010 let survivor = self.interner.get(survivor_id);
1011 self.interner.intern(
1012 survivor.kind.clone(),
1013 survivor.source.clone(),
1014 Some(destroyed_id),
1015 survivor.rule_ref.clone(),
1016 Typed {
1017 result_type: Arc::clone(&survivor.result_type),
1018 fold_types: survivor.fold_types.clone(),
1019 },
1020 )
1021 }
1022
1023 fn fold_arithmetic_child_types(
1024 &self,
1025 terms: &[ArithmeticTerm],
1026 direct: ArithmeticComputation,
1027 inverted: ArithmeticComputation,
1028 ) -> Vec<Arc<LemmaType>> {
1029 let (first, rest) = terms
1030 .split_first()
1031 .expect("BUG: arithmetic NormalForm with zero children");
1032 assert!(
1033 !first.inverse,
1034 "BUG: arithmetic NormalForm starts with an inverse term"
1035 );
1036 let mut fold_types = Vec::with_capacity(terms.len());
1037 let mut acc = Arc::clone(self.result_type(first.operand));
1038 fold_types.push(Arc::clone(&acc));
1039 for term in rest {
1040 let op = if term.inverse { &inverted } else { &direct };
1041 acc = compute_arithmetic_result_type(
1042 acc,
1043 op,
1044 Arc::clone(self.result_type(term.operand)),
1045 &self.measure_scope,
1046 );
1047 fold_types.push(Arc::clone(&acc));
1048 }
1049 fold_types
1050 }
1051
1052 fn binary_arithmetic_type(
1053 &self,
1054 left: NormalFormId,
1055 op: ArithmeticComputation,
1056 right: NormalFormId,
1057 ) -> Arc<LemmaType> {
1058 let left_type = self.result_type(left);
1059 if op == ArithmeticComputation::Power && left_type.is_measure() {
1060 if let Some(exponent) = self.integer_exponent_leaf(right) {
1061 return quantity_integer_power_type(left_type, exponent, &self.measure_scope);
1062 }
1063 }
1064 compute_arithmetic_result_type(
1065 Arc::clone(left_type),
1066 &op,
1067 Arc::clone(self.result_type(right)),
1068 &self.measure_scope,
1069 )
1070 }
1071
1072 fn integer_exponent_leaf(&self, id: NormalFormId) -> Option<i32> {
1073 let NormalFormKind::Leaf(LeafKind::Literal(literal)) = &self.get(id).kind else {
1074 return None;
1075 };
1076 let ValueKind::Number(number) = &literal.value else {
1077 return None;
1078 };
1079 if !number.is_integer() {
1080 return None;
1081 }
1082 number.numer_to_i32()
1083 }
1084}
1085
1086fn literal_value_default_result_type(value: &ValueKind) -> Arc<LemmaType> {
1090 match value {
1091 ValueKind::Number(_) => primitive_number_arc().clone(),
1092 ValueKind::Boolean(_) => primitive_boolean_arc().clone(),
1093 ValueKind::Text(_) => primitive_text_arc().clone(),
1094 ValueKind::Date(_) => primitive_date_arc().clone(),
1095 ValueKind::Time(_) => primitive_time_arc().clone(),
1096 ValueKind::Ratio(_) => primitive_ratio_arc().clone(),
1097 ValueKind::Measure(_) | ValueKind::UnscaledMeasure { .. } => {
1098 panic!(
1099 "BUG: Measure literal result_type must be stamped from expression/fold survivor type"
1100 )
1101 }
1102 ValueKind::Range(_, _) => {
1103 panic!("BUG: Range literal result_type must be stamped from expression/endpoint types")
1104 }
1105 ValueKind::Spec(_) => {
1106 panic!("BUG: Spec literal result_type must be stamped from the spec identity")
1107 }
1108 ValueKind::List(_) => {
1109 panic!("BUG: List literal result_type must be stamped from the list type")
1110 }
1111 }
1112}
1113
1114impl Cells<'_> {
1115 fn type_of(&self, kind: &NormalFormKind, pre_image: Option<NormalFormId>) -> Typed {
1128 let (result_type, fold_types) = match kind {
1129 NormalFormKind::Leaf(LeafKind::Literal(literal)) => (
1130 match pre_image {
1131 Some(destroyed) => Arc::clone(self.result_type(destroyed)),
1132 None => literal_value_default_result_type(&literal.value),
1133 },
1134 Vec::new(),
1135 ),
1136 NormalFormKind::Leaf(LeafKind::DataPath(_)) => {
1137 panic!("BUG: DataPath leaf result_type must be stamped from plan.data")
1138 }
1139 NormalFormKind::RowField { .. } => {
1140 panic!("BUG: row field result_type must be stamped from the row spec")
1141 }
1142 NormalFormKind::Leaf(LeafKind::RowValue { .. }) => {
1143 panic!("BUG: row value leaf result_type must be stamped from its list")
1144 }
1145 NormalFormKind::Now => (primitive_date_arc().clone(), Vec::new()),
1146 NormalFormKind::SpecMember { row, .. } => (Arc::clone(&row.result_type), Vec::new()),
1147 NormalFormKind::ListCommand { list, op, args, .. } => (
1148 list_command_result_type(
1149 self.result_type(*list),
1150 op,
1151 args.first().map(|argument| self.result_type(*argument)),
1152 &self.measure_scope,
1153 ),
1154 Vec::new(),
1155 ),
1156 NormalFormKind::Veto(_) => (Arc::new(LemmaType::veto_type()), Vec::new()),
1157 NormalFormKind::Sum(terms) => {
1158 let fold_types = self.fold_arithmetic_child_types(
1159 terms,
1160 ArithmeticComputation::Add,
1161 ArithmeticComputation::Subtract,
1162 );
1163 let result_type = Arc::clone(
1164 fold_types
1165 .last()
1166 .expect("BUG: Sum fold_types empty after fold_arithmetic_child_types"),
1167 );
1168 (result_type, fold_types)
1169 }
1170 NormalFormKind::Product(terms) => {
1171 let fold_types = self.fold_arithmetic_child_types(
1172 terms,
1173 ArithmeticComputation::Multiply,
1174 ArithmeticComputation::Divide,
1175 );
1176 let result_type = Arc::clone(
1177 fold_types
1178 .last()
1179 .expect("BUG: Product fold_types empty after fold_arithmetic_child_types"),
1180 );
1181 (result_type, fold_types)
1182 }
1183 NormalFormKind::Subtract(a, b) => (
1184 self.binary_arithmetic_type(*a, ArithmeticComputation::Subtract, *b),
1185 Vec::new(),
1186 ),
1187 NormalFormKind::Divide(a, b) => (
1188 self.binary_arithmetic_type(*a, ArithmeticComputation::Divide, *b),
1189 Vec::new(),
1190 ),
1191 NormalFormKind::Power(a, b) => (
1192 self.binary_arithmetic_type(*a, ArithmeticComputation::Power, *b),
1193 Vec::new(),
1194 ),
1195 NormalFormKind::Modulo(a, b) => (
1196 self.binary_arithmetic_type(*a, ArithmeticComputation::Modulo, *b),
1197 Vec::new(),
1198 ),
1199 NormalFormKind::Negate(x) => (
1202 compute_arithmetic_result_type(
1203 primitive_number_arc().clone(),
1204 &ArithmeticComputation::Multiply,
1205 Arc::clone(self.result_type(*x)),
1206 &self.measure_scope,
1207 ),
1208 Vec::new(),
1209 ),
1210 NormalFormKind::Reciprocal(x) => (
1211 compute_arithmetic_result_type(
1212 primitive_number_arc().clone(),
1213 &ArithmeticComputation::Divide,
1214 Arc::clone(self.result_type(*x)),
1215 &self.measure_scope,
1216 ),
1217 Vec::new(),
1218 ),
1219 NormalFormKind::Logical(_, children) => {
1220 let mut types = children.iter().map(|id| self.result_type(*id));
1221 let first = types.next().expect("BUG: And has at least two operands");
1222 let ty = types.fold(Arc::clone(first), |acc, next| {
1223 logical_connective_type(&acc, next)
1224 });
1225 (ty, Vec::new())
1226 }
1227 NormalFormKind::Comparison(a, _, b) => (
1228 comparison_type(self.result_type(*a), self.result_type(*b)),
1229 Vec::new(),
1230 ),
1231 NormalFormKind::RangeContainment(a, b) => (
1232 range_containment_type(self.result_type(*a), self.result_type(*b)),
1233 Vec::new(),
1234 ),
1235 NormalFormKind::Not(x) => (logical_not_type(self.result_type(*x)), Vec::new()),
1236 NormalFormKind::DateRelative(_, x) | NormalFormKind::DateCalendar(_, _, x) => {
1237 (date_predicate_type(self.result_type(*x)), Vec::new())
1238 }
1239 NormalFormKind::ResultIsVeto(_) => (result_is_veto_type(), Vec::new()),
1240 NormalFormKind::MathOp(op, x) => (
1241 math_op_type(op, self.result_type(*x), &self.measure_scope),
1242 Vec::new(),
1243 ),
1244 NormalFormKind::StepRounding(_, x, step) => (
1245 step_rounding_type(
1246 self.result_type(*x),
1247 step.map(|step| self.result_type(step)),
1248 ),
1249 Vec::new(),
1250 ),
1251 NormalFormKind::UnitConversion(x, target) => (
1252 unit_conversion_type(self.result_type(*x), target),
1253 Vec::new(),
1254 ),
1255 NormalFormKind::RangeLiteral(a, b) => {
1256 let (a, b) = (self.result_type(*a), self.result_type(*b));
1257 let ty = if a.vetoed() || b.vetoed() {
1258 Arc::new(LemmaType::veto_type())
1259 } else {
1260 infer_range_type_from_endpoint_types(a.as_ref(), b.as_ref())
1261 };
1262 (ty, Vec::new())
1263 }
1264 NormalFormKind::PastFutureRange(_, offset) => (
1265 past_future_range_type(self.result_type(*offset)),
1266 Vec::new(),
1267 ),
1268 NormalFormKind::RangeBound(_, operand) => (
1269 range_bound_result_type(self.result_type(*operand), &self.measure_scope),
1270 Vec::new(),
1271 ),
1272 NormalFormKind::Piecewise(arms) => (
1273 piecewise_type(
1274 arms.iter()
1275 .map(|(_, body)| Arc::clone(self.result_type(*body))),
1276 &self.measure_scope,
1277 ),
1278 Vec::new(),
1279 ),
1280 NormalFormKind::OrderedDispatch { .. } => (
1281 Arc::clone(self.result_type(pre_image.expect(
1282 "BUG: OrderedDispatch is only built over the Piecewise it re-indexes",
1283 ))),
1284 Vec::new(),
1285 ),
1286 };
1287 if result_type.is_undetermined() {
1291 let input_undetermined = match kind {
1292 NormalFormKind::Leaf(_) | NormalFormKind::Now | NormalFormKind::Veto(_) => false,
1293 NormalFormKind::OrderedDispatch { .. } => {
1294 pre_image.is_some_and(|id| self.result_type(id).is_undetermined())
1295 }
1296 NormalFormKind::Sum(terms) | NormalFormKind::Product(terms) => terms
1297 .iter()
1298 .any(|term| self.result_type(term.operand).is_undetermined()),
1299 NormalFormKind::Logical(_, children) => children
1300 .iter()
1301 .any(|id| self.result_type(*id).is_undetermined()),
1302 NormalFormKind::Subtract(a, b)
1303 | NormalFormKind::Divide(a, b)
1304 | NormalFormKind::Power(a, b)
1305 | NormalFormKind::Modulo(a, b)
1306 | NormalFormKind::RangeLiteral(a, b) => {
1307 self.result_type(*a).is_undetermined() || self.result_type(*b).is_undetermined()
1308 }
1309 NormalFormKind::StepRounding(_, a, step) => {
1310 self.result_type(*a).is_undetermined()
1311 || step.is_some_and(|step| self.result_type(step).is_undetermined())
1312 }
1313 NormalFormKind::Comparison(_, _, _) | NormalFormKind::RangeContainment(_, _) => {
1318 true
1319 }
1320 NormalFormKind::Negate(x)
1321 | NormalFormKind::Reciprocal(x)
1322 | NormalFormKind::UnitConversion(x, _)
1323 | NormalFormKind::RangeBound(_, x)
1324 | NormalFormKind::ResultIsVeto(x) => self.result_type(*x).is_undetermined(),
1325 NormalFormKind::Not(_)
1326 | NormalFormKind::DateRelative(_, _)
1327 | NormalFormKind::DateCalendar(_, _, _)
1328 | NormalFormKind::MathOp(_, _)
1329 | NormalFormKind::PastFutureRange(_, _) => true,
1330 NormalFormKind::Piecewise(arms) => arms.iter().any(|(condition, body)| {
1331 self.result_type(*condition).is_undetermined()
1332 || self.result_type(*body).is_undetermined()
1333 }),
1334 NormalFormKind::SpecMember { base, .. } => {
1335 self.result_type(*base).is_undetermined()
1336 }
1337 NormalFormKind::RowField { .. } => false,
1338 NormalFormKind::ListCommand { list, args, .. } => {
1339 self.result_type(*list).is_undetermined()
1340 || args
1341 .iter()
1342 .any(|id| self.result_type(*id).is_undetermined())
1343 }
1344 };
1345 assert!(
1346 input_undetermined,
1347 "BUG: NormalForm cell stamped undetermined type after validation"
1348 );
1349 }
1350 Typed {
1351 result_type,
1352 fold_types,
1353 }
1354 }
1355}
1356
1357impl LowerCtx<'_> {
1358 fn completed_rule(&self, path: &RulePath) -> NormalFormId {
1360 *self.completed_rules.get(path).unwrap_or_else(|| {
1361 panic!(
1362 "BUG: rule '{}' read before it was planned (missing dependency edge)",
1363 path.input_key()
1364 )
1365 })
1366 }
1367
1368 fn rule_type(&self, path: &RulePath) -> Arc<LemmaType> {
1369 Arc::clone(self.rule_types.get(path).unwrap_or_else(|| {
1370 panic!("BUG: rule '{}' has no RuleNode.rule_type", path.input_key())
1371 }))
1372 }
1373}
1374
1375fn to_normal_form(tree: &Expression, cells: &mut Cells<'_>, lower: &LowerCtx<'_>) -> NormalFormId {
1377 let mut lowered: Vec<NormalFormId> = Vec::with_capacity(tree.table.len());
1378 let mut children: Vec<NormalFormId> = Vec::new();
1379 for id in tree.table.ids() {
1380 let node = tree.at(id);
1381 children.clear();
1382 node.kind()
1383 .for_each_child(|child| children.push(lowered[child.index()]));
1384 lowered.push(lower_node(node, &children, &lowered, cells, lower));
1385 }
1386 *lowered.last().expect("BUG: expression tree has a root")
1387}
1388
1389#[derive(Clone, Copy, PartialEq, Eq)]
1391enum ArithmeticGroup {
1392 Sum,
1393 Product,
1394}
1395
1396fn arithmetic_group(op: &ArithmeticComputation) -> Option<ArithmeticGroup> {
1397 match op {
1398 ArithmeticComputation::Add | ArithmeticComputation::Subtract => Some(ArithmeticGroup::Sum),
1399 ArithmeticComputation::Multiply | ArithmeticComputation::Divide => {
1400 Some(ArithmeticGroup::Product)
1401 }
1402 ArithmeticComputation::Power | ArithmeticComputation::Modulo => None,
1403 }
1404}
1405
1406fn lower_arithmetic_chain(
1413 first: NormalFormId,
1414 steps: &[(ArithmeticComputation, NormalFormId)],
1415 cells: &mut Cells<'_>,
1416) -> NormalFormId {
1417 let mut current = first;
1418 let mut rest = steps;
1419 while let Some((op, operand)) = rest.first() {
1420 let Some(group) = arithmetic_group(op) else {
1421 current = lower_binary_arithmetic(op, current, *operand, cells);
1422 rest = &rest[1..];
1423 continue;
1424 };
1425 let run_len = rest
1426 .iter()
1427 .take_while(|(op, _)| arithmetic_group(op) == Some(group))
1428 .count();
1429 current = lower_arithmetic_run(current, &rest[..run_len], group, cells);
1430 rest = &rest[run_len..];
1431 }
1432 current
1433}
1434
1435fn lower_arithmetic_run(
1436 first: NormalFormId,
1437 operands: &[(ArithmeticComputation, NormalFormId)],
1438 group: ArithmeticGroup,
1439 cells: &mut Cells<'_>,
1440) -> NormalFormId {
1441 let has_subtract_or_divide = operands.iter().any(|(op, _)| {
1442 matches!(
1443 op,
1444 ArithmeticComputation::Subtract | ArithmeticComputation::Divide
1445 )
1446 });
1447 let all_numeric = {
1448 let mut memo = HashMap::new();
1449 std::iter::once(first)
1450 .chain(operands.iter().map(|(_, id)| *id))
1451 .all(|id| rewrite::numeric_only(cells, &mut memo, id))
1452 };
1453 if !has_subtract_or_divide || (all_numeric && operands.len() > 1) {
1454 let terms: Vec<ArithmeticTerm> = std::iter::once(ArithmeticTerm::direct(first))
1455 .chain(operands.iter().map(|(op, id)| ArithmeticTerm {
1456 operand: *id,
1457 inverse: matches!(
1458 op,
1459 ArithmeticComputation::Subtract | ArithmeticComputation::Divide
1460 ),
1461 }))
1462 .collect();
1463 let kind = match group {
1464 ArithmeticGroup::Sum => NormalFormKind::Sum(terms),
1465 ArithmeticGroup::Product => NormalFormKind::Product(terms),
1466 };
1467 return cells.intern_empty(kind);
1468 }
1469 operands.iter().fold(first, |current, (op, operand)| {
1470 lower_binary_arithmetic(op, current, *operand, cells)
1471 })
1472}
1473
1474fn lower_binary_arithmetic(
1475 op: &ArithmeticComputation,
1476 left: NormalFormId,
1477 right: NormalFormId,
1478 cells: &mut Cells<'_>,
1479) -> NormalFormId {
1480 let kind = match op {
1481 ArithmeticComputation::Subtract => NormalFormKind::Subtract(left, right),
1482 ArithmeticComputation::Divide => NormalFormKind::Divide(left, right),
1483 ArithmeticComputation::Add => NormalFormKind::Sum(vec![
1484 ArithmeticTerm::direct(left),
1485 ArithmeticTerm::direct(right),
1486 ]),
1487 ArithmeticComputation::Multiply => NormalFormKind::Product(vec![
1488 ArithmeticTerm::direct(left),
1489 ArithmeticTerm::direct(right),
1490 ]),
1491 ArithmeticComputation::Power => NormalFormKind::Power(left, right),
1492 ArithmeticComputation::Modulo => NormalFormKind::Modulo(left, right),
1493 };
1494 cells.intern_empty(kind)
1495}
1496
1497fn lower_spec_member(
1500 base: NormalFormId,
1501 path: &[String],
1502 name: &str,
1503 cells: &mut Cells<'_>,
1504 lower: &LowerCtx<'_>,
1505) -> NormalFormId {
1506 let crate::planning::semantics::TypeSpecification::Spec { spec } =
1507 &cells.result_type(base).specifications
1508 else {
1509 panic!(
1510 "BUG: member '{}' read on a base that is not spec-typed after typing",
1511 crate::planning::semantics::spec_member_input_key(path, name)
1512 );
1513 };
1514 let chain = lower.row_member_chain(spec, path, name).unwrap_or_else(|| {
1515 panic!(
1516 "BUG: row member '{}' of '{spec}' passed typing",
1517 crate::planning::semantics::spec_member_input_key(path, name)
1518 )
1519 });
1520 let last = chain.last().expect("BUG: a row end chain has a last hop");
1521 let result_type = lower.row_end_type(&last.identity, &last.end);
1522 cells.intern_empty(NormalFormKind::SpecMember {
1523 base,
1524 path: path.to_vec(),
1525 name: name.to_string(),
1526 row: RowRead {
1527 ends: chain.into_iter().map(|hop| hop.end).collect(),
1528 result_type,
1529 },
1530 })
1531}
1532
1533fn lower_node(
1535 node: ExprRef<'_, ExpressionKind>,
1536 children: &[NormalFormId],
1537 lowered: &[NormalFormId],
1538 cells: &mut Cells<'_>,
1539 lower: &LowerCtx<'_>,
1540) -> NormalFormId {
1541 match node.kind() {
1542 ExpressionKind::Literal(lit) => {
1543 cells.intern_literal_leaf(lit.to_literal(), Arc::clone(&lit.lemma_type))
1544 }
1545 ExpressionKind::DataPath(p) => {
1548 cells.intern_data_leaf(p.clone(), data_path_result_type(lower.data, p))
1549 }
1550 ExpressionKind::SpecMember { path, name, .. } => {
1551 lower_spec_member(children[0], path, name, cells, lower)
1552 }
1553 ExpressionKind::RulePath(path) => {
1554 let body = lower.completed_rule(path);
1555 cells.intern_rule_ref(body, path.clone(), lower.rule_type(path))
1556 }
1557 ExpressionKind::Logical { connective, .. } => {
1558 cells.intern_empty(NormalFormKind::Logical(*connective, children.to_vec()))
1559 }
1560 ExpressionKind::Arithmetic { steps, .. } => {
1561 let (first, operands) = children
1562 .split_first()
1563 .expect("BUG: arithmetic chain has a first operand");
1564 assert_eq!(
1565 operands.len(),
1566 steps.len(),
1567 "BUG: arithmetic chain lowered to one operand per step"
1568 );
1569 let steps: Vec<(ArithmeticComputation, NormalFormId)> = steps
1570 .iter()
1571 .zip(operands)
1572 .map(|(step, operand)| (step.op.clone(), *operand))
1573 .collect();
1574 lower_arithmetic_chain(*first, &steps, cells)
1575 }
1576 ExpressionKind::Comparison(_, op, _) => {
1577 let &[left_id, right_id] = children else {
1578 unreachable!("BUG: comparison has two operands")
1579 };
1580 cells.intern_empty(NormalFormKind::Comparison(left_id, op.clone(), right_id))
1581 }
1582 ExpressionKind::UnitConversion(_, target) => {
1583 let &[inner_id] = children else {
1584 unreachable!("BUG: unit conversion has one operand")
1585 };
1586 cells.rebuild(
1587 NormalFormKind::UnitConversion(inner_id, target.clone()),
1588 node.source().cloned(),
1589 None,
1590 )
1591 }
1592 ExpressionKind::LogicalNegation(_, _) => {
1593 let &[inner_id] = children else {
1594 unreachable!("BUG: negation has one operand")
1595 };
1596 cells.intern_empty(NormalFormKind::Not(inner_id))
1597 }
1598 ExpressionKind::MathematicalComputation(op, _) => {
1599 let &[inner_id] = children else {
1600 unreachable!("BUG: math operator has one operand")
1601 };
1602 cells.intern_empty(NormalFormKind::MathOp(op.clone(), inner_id))
1603 }
1604 ExpressionKind::StepRounding { mode, .. } => match *children {
1605 [operand_id] => {
1606 cells.intern_empty(NormalFormKind::StepRounding(*mode, operand_id, None))
1607 }
1608 [operand_id, step_id] => cells.intern_empty(NormalFormKind::StepRounding(
1609 *mode,
1610 operand_id,
1611 Some(step_id),
1612 )),
1613 _ => unreachable!("BUG: step rounding has an operand and an optional step"),
1614 },
1615 ExpressionKind::Veto(v) => cells.intern_empty(NormalFormKind::Veto(v.clone())),
1616 ExpressionKind::Now => cells.intern_empty(NormalFormKind::Now),
1617 ExpressionKind::DateRelative(kind, _) => {
1618 let &[inner_id] = children else {
1619 unreachable!("BUG: date relative has one operand")
1620 };
1621 cells.intern_empty(NormalFormKind::DateRelative(*kind, inner_id))
1622 }
1623 ExpressionKind::DateCalendar(kind, unit, _) => {
1624 let &[inner_id] = children else {
1625 unreachable!("BUG: date calendar has one operand")
1626 };
1627 cells.intern_empty(NormalFormKind::DateCalendar(*kind, *unit, inner_id))
1628 }
1629 ExpressionKind::RangeLiteral(_, _) => {
1630 let &[left_id, right_id] = children else {
1631 unreachable!("BUG: range literal has two operands")
1632 };
1633 cells.intern_empty(NormalFormKind::RangeLiteral(left_id, right_id))
1634 }
1635 ExpressionKind::PastFutureRange(kind, _) => {
1636 let &[inner_id] = children else {
1637 unreachable!("BUG: past/future range has one operand")
1638 };
1639 cells.intern_empty(NormalFormKind::PastFutureRange(*kind, inner_id))
1640 }
1641 ExpressionKind::RangeBound(bound, _) => {
1642 let &[inner_id] = children else {
1643 unreachable!("BUG: range bound has one operand")
1644 };
1645 cells.intern_empty(NormalFormKind::RangeBound(*bound, inner_id))
1646 }
1647 ExpressionKind::RangeContainment(_, _) => {
1648 let &[left_id, right_id] = children else {
1649 unreachable!("BUG: range containment has two operands")
1650 };
1651 cells.intern_empty(NormalFormKind::RangeContainment(left_id, right_id))
1652 }
1653 ExpressionKind::ResultIsVeto(_) => {
1654 let &[operand_id] = children else {
1655 unreachable!("BUG: result-is-veto has one operand")
1656 };
1657 cells.intern_empty(NormalFormKind::ResultIsVeto(operand_id))
1658 }
1659 ExpressionKind::Piecewise(_) => {
1660 let mapped = children
1661 .chunks_exact(2)
1662 .map(|pair| (pair[0], pair[1]))
1663 .collect();
1664 cells.intern_empty(NormalFormKind::Piecewise(mapped))
1665 }
1666 ExpressionKind::ListCommand { op, row_level, .. } => {
1667 let (list_id, args) = children
1668 .split_first()
1669 .expect("BUG: list command has a list operand");
1670 cells.intern_empty(NormalFormKind::ListCommand {
1671 list: *list_id,
1672 op: list_op_kind(op),
1673 args: args.to_vec(),
1674 row_level: *row_level,
1675 })
1676 }
1677 ExpressionKind::RowField {
1678 level,
1679 identity,
1680 end,
1681 qualifier,
1682 } => cells.intern_row_field(
1683 *level,
1684 identity.clone(),
1685 RowFieldEnd {
1686 end: end.clone(),
1687 qualifier: qualifier.clone(),
1688 },
1689 lower.row_end_type(identity, end),
1690 ),
1691 ExpressionKind::RowValue { level, list } => {
1692 let list_cell = *lowered.get(list.index()).expect(
1693 "BUG: row value type-only list reference must point to an earlier expression",
1694 );
1695 let element_type = cells.result_type(list_cell).list_element_lemma_type();
1696 cells.intern_row_value_leaf(*level, Arc::clone(element_type))
1697 }
1698 }
1699}
1700
1701fn list_op_kind(op: &crate::planning::semantics::ListOp) -> NormalFormListOp {
1704 use crate::planning::semantics::{ListOp, ListReduce};
1705 match op {
1706 ListOp::Find { .. } => NormalFormListOp::Find,
1707 ListOp::First => NormalFormListOp::First,
1708 ListOp::Last => NormalFormListOp::Last,
1709 ListOp::Count => NormalFormListOp::Count,
1710 ListOp::Reduce { kind, .. } => match kind {
1711 ListReduce::Sum => NormalFormListOp::Sum,
1712 ListReduce::Avg => NormalFormListOp::Avg,
1713 ListReduce::Product => NormalFormListOp::Product,
1714 ListReduce::Min => NormalFormListOp::Min,
1715 ListReduce::Max => NormalFormListOp::Max,
1716 },
1717 ListOp::Predicate { kind, form, .. } => NormalFormListOp::Predicate {
1718 kind: *kind,
1719 form: *form,
1720 },
1721 ListOp::Sort { descending, .. } => NormalFormListOp::Sort {
1722 descending: *descending,
1723 },
1724 ListOp::Top { .. } => NormalFormListOp::Top,
1725 ListOp::With { .. } => NormalFormListOp::With,
1726 ListOp::WithRow { identity, fields } => NormalFormListOp::WithRow {
1727 identity: Arc::new(identity.clone()),
1728 fields: fields.iter().map(|(path, _)| path.clone()).collect(),
1729 },
1730 ListOp::Key { fields } => NormalFormListOp::Key {
1731 fields: fields.clone(),
1732 },
1733 }
1734}
1735
1736fn list_command_result_type(
1739 list_type: &Arc<LemmaType>,
1740 op: &NormalFormListOp,
1741 first_argument: Option<&Arc<LemmaType>>,
1742 measure_scope: &crate::planning::typing::MeasureScope<'_>,
1743) -> Arc<LemmaType> {
1744 use crate::planning::typing::{
1745 list_command_result_type as stamp, list_has_instance_rows, ListCommandResultKind,
1746 };
1747 let instance_rows = list_has_instance_rows(list_type);
1748 let kind = match op {
1749 NormalFormListOp::Count => ListCommandResultKind::Count,
1750 NormalFormListOp::Predicate {
1751 kind: ListPredKind::Any | ListPredKind::All,
1752 ..
1753 } => ListCommandResultKind::BooleanPredicate,
1754 NormalFormListOp::Predicate {
1755 kind: ListPredKind::Where,
1756 ..
1757 }
1758 | NormalFormListOp::Sort { .. }
1759 | NormalFormListOp::Top
1760 | NormalFormListOp::With
1761 | NormalFormListOp::WithRow { .. }
1762 | NormalFormListOp::Key { .. } => ListCommandResultKind::KeepList,
1763 NormalFormListOp::Sum
1764 | NormalFormListOp::Avg
1765 | NormalFormListOp::Product
1766 | NormalFormListOp::Min
1767 | NormalFormListOp::Max
1768 if instance_rows =>
1769 {
1770 ListCommandResultKind::InstanceReduce
1771 }
1772 NormalFormListOp::Find
1773 | NormalFormListOp::First
1774 | NormalFormListOp::Last
1775 | NormalFormListOp::Sum
1776 | NormalFormListOp::Avg
1777 | NormalFormListOp::Product
1778 | NormalFormListOp::Min
1779 | NormalFormListOp::Max => ListCommandResultKind::Element,
1780 };
1781 let result = stamp(list_type, kind, first_argument, measure_scope);
1782 match op {
1783 NormalFormListOp::Key { fields } => {
1784 crate::planning::typing::refine_list_key_type(&result, fields)
1785 }
1786 _ => result,
1787 }
1788}
1789
1790fn cell(forms: &[NormalForm], id: NormalFormId) -> &NormalForm {
1791 forms.get(id.index()).unwrap_or_else(|| {
1792 panic!(
1793 "BUG: NormalFormId {} out of range (table len {})",
1794 id.0,
1795 forms.len()
1796 )
1797 })
1798}
1799
1800pub(crate) fn explanation_display(
1805 forms: &[NormalForm],
1806 id: NormalFormId,
1807 qualifier: Option<&NameScope>,
1808) -> String {
1809 Syntax(NfNode {
1810 forms,
1811 id,
1812 qualifier,
1813 })
1814 .to_string()
1815}
1816
1817fn displayed_cell(forms: &[NormalForm], mut id: NormalFormId) -> &NormalForm {
1819 loop {
1820 let nf = cell(forms, id);
1821 match nf.origin {
1822 Some(origin) => id = origin,
1823 None => return nf,
1824 }
1825 }
1826}
1827
1828#[derive(Clone, Copy, PartialEq, Eq)]
1829enum SpineLevel {
1830 Additive,
1831 Multiplicative,
1832}
1833
1834fn arithmetic_chain(
1838 forms: &[NormalForm],
1839 top: NormalFormId,
1840) -> (
1841 NormalFormId,
1842 Vec<(&'static ArithmeticComputation, NormalFormId)>,
1843) {
1844 let level = match &displayed_cell(forms, top).kind {
1845 NormalFormKind::Sum(_) | NormalFormKind::Subtract(..) => SpineLevel::Additive,
1846 NormalFormKind::Product(_) | NormalFormKind::Divide(..) | NormalFormKind::Modulo(..) => {
1847 SpineLevel::Multiplicative
1848 }
1849 _ => unreachable!("BUG: arithmetic_chain on a non-arithmetic cell"),
1850 };
1851 let mut steps = Vec::new();
1852 let mut current = top;
1853 loop {
1854 let shown = displayed_cell(forms, current);
1855 if shown.rule_ref.is_some() {
1856 break;
1857 }
1858 match (&shown.kind, level) {
1859 (NormalFormKind::Sum(terms), SpineLevel::Additive) => {
1860 let (first, rest) = terms
1861 .split_first()
1862 .expect("BUG: Sum cell has at least one operand");
1863 assert!(!first.inverse, "BUG: Sum starts with an inverse term");
1864 steps.extend(rest.iter().rev().map(|term| {
1865 let op = if term.inverse {
1866 &ArithmeticComputation::Subtract
1867 } else {
1868 &ArithmeticComputation::Add
1869 };
1870 (op, term.operand)
1871 }));
1872 current = first.operand;
1873 }
1874 (NormalFormKind::Subtract(left, right), SpineLevel::Additive) => {
1875 steps.push((&ArithmeticComputation::Subtract, *right));
1876 current = *left;
1877 }
1878 (NormalFormKind::Product(terms), SpineLevel::Multiplicative) => {
1879 let (first, rest) = terms
1880 .split_first()
1881 .expect("BUG: Product cell has at least one operand");
1882 assert!(!first.inverse, "BUG: Product starts with an inverse term");
1883 steps.extend(rest.iter().rev().map(|term| {
1884 let op = if term.inverse {
1885 &ArithmeticComputation::Divide
1886 } else {
1887 &ArithmeticComputation::Multiply
1888 };
1889 (op, term.operand)
1890 }));
1891 current = first.operand;
1892 }
1893 (NormalFormKind::Divide(left, right), SpineLevel::Multiplicative) => {
1894 steps.push((&ArithmeticComputation::Divide, *right));
1895 current = *left;
1896 }
1897 (NormalFormKind::Modulo(left, right), SpineLevel::Multiplicative) => {
1898 steps.push((&ArithmeticComputation::Modulo, *right));
1899 current = *left;
1900 }
1901 _ => break,
1902 }
1903 }
1904 steps.reverse();
1905 (current, steps)
1906}
1907
1908pub(crate) struct NameScope {
1913 pub(crate) data: Vec<PathSegment>,
1914 pub(crate) rules: Vec<PathSegment>,
1915}
1916
1917#[derive(Clone, Copy)]
1919pub(crate) struct NfNode<'a> {
1920 pub(crate) forms: &'a [NormalForm],
1921 pub(crate) id: NormalFormId,
1922 pub(crate) qualifier: Option<&'a NameScope>,
1924}
1925
1926impl<'a> NfNode<'a> {
1927 fn at(self, id: NormalFormId) -> Self {
1928 Self {
1929 forms: self.forms,
1930 id,
1931 qualifier: self.qualifier,
1932 }
1933 }
1934}
1935
1936#[derive(Clone, Copy)]
1938pub(crate) enum NfAtom<'a> {
1939 Literal(&'a LiteralValue, &'a LemmaType),
1940 DataPath(&'a DataPath),
1941 QualifiedDataPath {
1943 prefix: &'a [PathSegment],
1944 path: &'a DataPath,
1945 },
1946 Rule(&'a RulePath),
1947 QualifiedRule {
1949 prefix: &'a [PathSegment],
1950 path: &'a RulePath,
1951 },
1952 Row(&'a RowEnd),
1953 QualifiedRow {
1955 qualifier: &'a [String],
1956 end: &'a RowEnd,
1957 },
1958 SpecMemberField {
1959 path: &'a [String],
1960 name: &'a str,
1961 },
1962 Now,
1963 Veto(&'a VetoExpression),
1964}
1965
1966impl std::fmt::Display for NfAtom<'_> {
1967 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1968 match self {
1969 NfAtom::Literal(literal, lemma_type) => {
1970 write!(f, "{}", literal.display_value_with_type(lemma_type))
1971 }
1972 NfAtom::DataPath(path) => write!(f, "{}", path.input_key()),
1973 NfAtom::QualifiedDataPath { prefix, path } => {
1974 for segment in prefix.iter() {
1975 write!(f, "{}.", segment.input_key())?;
1976 }
1977 write!(f, "{}", path.input_key())
1978 }
1979 NfAtom::Rule(path) => write!(f, "{}", path.input_key()),
1980 NfAtom::QualifiedRule { prefix, path } => {
1981 for segment in prefix.iter() {
1982 write!(f, "{}.", segment.input_key())?;
1983 }
1984 write!(f, "{}", path.input_key())
1985 }
1986 NfAtom::Row(end) => write!(f, "{end}"),
1987 NfAtom::QualifiedRow { qualifier, end } => {
1988 for (i, segment) in qualifier.iter().enumerate() {
1989 if i > 0 {
1990 f.write_str(".")?;
1991 }
1992 f.write_str(segment)?;
1993 }
1994 f.write_str(".")?;
1995 write!(f, "{end}")
1996 }
1997 NfAtom::SpecMemberField { path, name } => {
1998 write!(
1999 f,
2000 "{}",
2001 crate::planning::semantics::spec_member_input_key(path, name)
2002 )
2003 }
2004 NfAtom::Now => write!(f, "now"),
2005 NfAtom::Veto(veto) => write!(f, "{veto}"),
2006 }
2007 }
2008}
2009
2010fn value_starts_with_sign(value: &ValueKind) -> bool {
2011 let mut current = value;
2012 loop {
2013 match current {
2014 ValueKind::Number(n) | ValueKind::Measure(n) | ValueKind::Ratio(n) => {
2015 return n.numer_is_negative()
2016 }
2017 ValueKind::Range(left, _) => current = &left.value,
2018 _ => return false,
2019 }
2020 }
2021}
2022
2023fn single_argument(args: &[NormalFormId], command: &str) -> NormalFormId {
2024 match args {
2025 [argument] => *argument,
2026 other => panic!(
2027 "BUG: `{command}` cell must carry one argument, got {}",
2028 other.len()
2029 ),
2030 }
2031}
2032
2033fn optional_argument(args: &[NormalFormId], command: &str) -> Option<NormalFormId> {
2034 match args {
2035 [] => None,
2036 [argument] => Some(*argument),
2037 other => panic!(
2038 "BUG: `{command}` cell carries at most one argument, got {}",
2039 other.len()
2040 ),
2041 }
2042}
2043
2044impl<'a> SyntaxNode<'a> for NfNode<'a> {
2045 type Atom = NfAtom<'a>;
2046
2047 fn view(self) -> SyntaxView<'a, Self> {
2048 let nf = displayed_cell(self.forms, self.id);
2049 let atom = |atom| SyntaxView::Atom {
2050 atom,
2051 starts_with_sign: false,
2052 };
2053 if let Some(path) = &nf.rule_ref {
2054 return match self.qualifier {
2055 Some(scope) if !scope.rules.is_empty() => atom(NfAtom::QualifiedRule {
2056 prefix: &scope.rules,
2057 path,
2058 }),
2059 Some(_) | None => atom(NfAtom::Rule(path)),
2060 };
2061 }
2062 let node = |id: &NormalFormId| self.at(*id);
2063 let nodes = |ids: &[NormalFormId]| ids.iter().map(node).collect::<Vec<_>>();
2064 match &nf.kind {
2065 NormalFormKind::Leaf(LeafKind::Literal(literal)) => SyntaxView::Atom {
2066 atom: NfAtom::Literal(literal, nf.result_type.as_ref()),
2067 starts_with_sign: value_starts_with_sign(&literal.value),
2068 },
2069 NormalFormKind::Leaf(LeafKind::DataPath(path)) => match self.qualifier {
2070 Some(scope) if !scope.data.is_empty() => atom(NfAtom::QualifiedDataPath {
2071 prefix: &scope.data,
2072 path,
2073 }),
2074 Some(_) | None => atom(NfAtom::DataPath(path)),
2075 },
2076 NormalFormKind::RowField {
2077 end,
2078 qualifier: Some(segments),
2079 ..
2080 } => atom(NfAtom::QualifiedRow {
2081 qualifier: segments,
2082 end,
2083 }),
2084 NormalFormKind::RowField {
2085 end,
2086 qualifier: None,
2087 ..
2088 } => atom(NfAtom::Row(end)),
2089 NormalFormKind::Leaf(LeafKind::RowValue { .. }) => SyntaxView::ImplicitRowValue,
2090 NormalFormKind::Now => atom(NfAtom::Now),
2091 NormalFormKind::Veto(veto) => atom(NfAtom::Veto(veto)),
2092 NormalFormKind::Comparison(left, op, right) => {
2093 SyntaxView::Comparison(node(left), op, node(right))
2094 }
2095 NormalFormKind::Logical(connective, children) => SyntaxView::Logical {
2096 connective: *connective,
2097 operands: nodes(children),
2098 },
2099 NormalFormKind::Not(operand) => {
2100 let shown = displayed_cell(self.forms, *operand);
2101 match &shown.kind {
2102 NormalFormKind::ResultIsVeto(inner) if shown.rule_ref.is_none() => {
2103 SyntaxView::IsNotVeto(node(inner))
2104 }
2105 _ => SyntaxView::Not(node(operand)),
2106 }
2107 }
2108 NormalFormKind::ResultIsVeto(operand) => SyntaxView::IsVeto(node(operand)),
2109 NormalFormKind::Sum(_)
2110 | NormalFormKind::Product(_)
2111 | NormalFormKind::Subtract(..)
2112 | NormalFormKind::Divide(..)
2113 | NormalFormKind::Modulo(..) => {
2114 let (first, steps) = arithmetic_chain(self.forms, self.id);
2115 let level = match &nf.kind {
2116 NormalFormKind::Sum(_) | NormalFormKind::Subtract(..) => {
2117 &ArithmeticComputation::Add
2118 }
2119 _ => &ArithmeticComputation::Multiply,
2120 };
2121 SyntaxView::Chain {
2122 level,
2123 operands: std::iter::once(node(&first))
2124 .chain(steps.iter().map(|(_, operand)| node(operand)))
2125 .collect(),
2126 ops: steps.iter().map(|(op, _)| *op).collect(),
2127 }
2128 }
2129 NormalFormKind::Power(left, right) => SyntaxView::Chain {
2130 level: &ArithmeticComputation::Power,
2131 operands: vec![node(left), node(right)],
2132 ops: vec![&ArithmeticComputation::Power],
2133 },
2134 NormalFormKind::Negate(operand) => SyntaxView::Negate(node(operand)),
2135 NormalFormKind::Reciprocal(operand) => SyntaxView::Reciprocal(node(operand)),
2136 NormalFormKind::MathOp(op, operand) => SyntaxView::Prefix(op, node(operand)),
2137 NormalFormKind::StepRounding(mode, operand, step) => SyntaxView::ListCommand {
2138 list: node(operand),
2139 command: CommandView::Step(mode.keyword(), step.as_ref().map(node)),
2140 },
2141 NormalFormKind::RangeBound(bound, operand) => SyntaxView::Prefix(bound, node(operand)),
2142 NormalFormKind::UnitConversion(value, target) => {
2143 SyntaxView::UnitConversion(node(value), target)
2144 }
2145 NormalFormKind::DateRelative(kind, date) => SyntaxView::DateRelative(kind, node(date)),
2146 NormalFormKind::DateCalendar(kind, unit, date) => {
2147 SyntaxView::DateCalendar(kind, unit, node(date))
2148 }
2149 NormalFormKind::RangeLiteral(left, right) => {
2150 SyntaxView::RangeLiteral(node(left), node(right))
2151 }
2152 NormalFormKind::PastFutureRange(kind, offset) => {
2153 SyntaxView::PastFutureRange(kind, node(offset))
2154 }
2155 NormalFormKind::RangeContainment(value, range) => {
2156 SyntaxView::RangeContainment(node(value), node(range))
2157 }
2158 NormalFormKind::Piecewise(arms) => SyntaxView::Piecewise(
2159 arms.iter()
2160 .map(|(condition, result)| (node(condition), node(result)))
2161 .collect(),
2162 ),
2163 NormalFormKind::SpecMember {
2164 base, path, name, ..
2165 } => {
2166 let base_cell = cell(self.forms, *base);
2167 let named = base_cell.rule_ref.is_some()
2168 || matches!(
2169 base_cell.kind,
2170 NormalFormKind::Leaf(_) | NormalFormKind::SpecMember { .. }
2171 );
2172 SyntaxView::Member {
2173 base: node(base),
2174 path: NfAtom::SpecMemberField {
2175 path,
2176 name: name.as_str(),
2177 },
2178 parenthesize_base: !named,
2179 }
2180 }
2181 NormalFormKind::OrderedDispatch { .. } => unreachable!(
2182 "BUG: OrderedDispatch always carries its Piecewise pre-image as origin, \
2183 so displayed_cell never stops at it"
2184 ),
2185 NormalFormKind::ListCommand { list, op, args, .. } => {
2186 let reduce = |name: &'static str| {
2187 CommandView::Reduce(name, optional_argument(args, name).map(|id| node(&id)))
2188 };
2189 let command = match op {
2190 NormalFormListOp::Find => CommandView::Find(nodes(args)),
2191 NormalFormListOp::First => CommandView::First,
2192 NormalFormListOp::Last => CommandView::Last,
2193 NormalFormListOp::Count => CommandView::Count,
2194 NormalFormListOp::Sum => reduce("sum"),
2195 NormalFormListOp::Avg => reduce("avg"),
2196 NormalFormListOp::Product => reduce("product"),
2197 NormalFormListOp::Min => reduce("min"),
2198 NormalFormListOp::Max => reduce("max"),
2199 NormalFormListOp::Predicate { kind, form } => {
2200 let name = kind.keyword();
2201 let expr = node(&single_argument(args, name));
2202 let view = match form {
2203 ListPredicateForm::Value => PredicateView::Value(expr),
2204 ListPredicateForm::Instance => PredicateView::Instance(expr),
2205 };
2206 CommandView::Predicate(name, view)
2207 }
2208 NormalFormListOp::Sort { descending } => CommandView::Sort {
2209 descending: *descending,
2210 argument: optional_argument(args, "sort").map(|id| node(&id)),
2211 },
2212 NormalFormListOp::Top => CommandView::Top(node(&single_argument(args, "top"))),
2213 NormalFormListOp::With => {
2214 CommandView::With(args.iter().map(|id| (None, node(id))).collect())
2215 }
2216 NormalFormListOp::WithRow { fields, .. } => {
2217 assert_eq!(
2218 fields.len(),
2219 args.len(),
2220 "BUG: `with` row cell must carry one value per field"
2221 );
2222 CommandView::With(
2223 fields
2224 .iter()
2225 .zip(args)
2226 .map(|(field, value)| (Some(NfAtom::DataPath(field)), node(value)))
2227 .collect(),
2228 )
2229 }
2230 NormalFormListOp::Key { fields } => {
2231 CommandView::Key(fields.iter().map(NfAtom::DataPath).collect())
2232 }
2233 };
2234 SyntaxView::ListCommand {
2235 list: node(list),
2236 command,
2237 }
2238 }
2239 }
2240 }
2241}
2242
2243#[cfg(test)]
2244mod tests {
2245 use super::*;
2246
2247 static EMPTY_ROW_TEMPLATES: BTreeMap<SpecIdentity, Arc<ExecutionPlan>> = BTreeMap::new();
2248 static TEST_SELF_IDENTITY: std::sync::LazyLock<SpecIdentity> =
2249 std::sync::LazyLock::new(|| SpecIdentity {
2250 repository: None,
2251 spec: "test".to_string(),
2252 effective: crate::parsing::ast::EffectiveDate::Origin,
2253 slice: crate::parsing::ast::EffectiveDate::Origin,
2254 });
2255 use crate::computation::arithmetic::SignatureIndex;
2256 use crate::computation::rational::{rational_new, rational_one, rational_zero};
2257 use crate::parsing::arena::{ExprId, ExprTable, ExprTree};
2258 use crate::parsing::ast::{ChainStep, ConversionTypeKind};
2259 use crate::planning::semantics::TypedLiteral;
2260 use crate::planning::semantics::{
2261 ComparisonComputation, DataPath, ExpressionKind, NegationType, SemanticConversionTarget,
2262 ValueKind,
2263 };
2264 use crate::planning::unit_index::UnitIndex;
2265 use std::rc::Rc;
2266
2267 fn interner_forms(interner: &NormalFormInterner) -> &[NormalForm] {
2268 &interner.forms
2269 }
2270
2271 fn empty_named_types() -> &'static IndexMap<String, Arc<LemmaType>> {
2272 use std::sync::OnceLock;
2273 static EMPTY: OnceLock<IndexMap<String, Arc<LemmaType>>> = OnceLock::new();
2274 EMPTY.get_or_init(IndexMap::new)
2275 }
2276
2277 fn empty_measure_scope<'a>(
2278 unit_index: &'a UnitIndex,
2279 signature_index: &'a SignatureIndex,
2280 ) -> MeasureScope<'a> {
2281 MeasureScope {
2282 unit_index,
2283 signature_index,
2284 named_types: empty_named_types(),
2285 }
2286 }
2287
2288 fn algebraic_scope() -> IndexMap<DataPath, DataDefinition> {
2291 use crate::parsing::source::{Source as ParseSource, SourceType};
2292 use crate::Span;
2293 let source = ParseSource::new(
2294 SourceType::Volatile,
2295 Span {
2296 start: 0,
2297 end: 0,
2298 line: 1,
2299 col: 1,
2300 },
2301 );
2302 let mut data = IndexMap::new();
2303 for (name, resolved_type) in [
2304 ("x", primitive_number_arc().clone()),
2305 ("y", primitive_number_arc().clone()),
2306 ("flag", primitive_boolean_arc().clone()),
2307 ("flag2", primitive_boolean_arc().clone()),
2308 ] {
2309 data.insert(
2310 DataPath::new(vec![], name.into()),
2311 DataDefinition::TypeDeclaration {
2312 resolved_type,
2313 declared_suggestion: None,
2314 declared_value: None,
2315 source: source.clone(),
2316 },
2317 );
2318 }
2319 data
2320 }
2321
2322 fn normalize_expression(expr: &Ex) -> Result<Expression, Error> {
2324 let data = algebraic_scope();
2325 normalize_expression_with_data(expr, &data).map(|(expr, _)| expr)
2326 }
2327
2328 fn normalize_expression_with_data(
2331 expr: &Ex,
2332 data: &IndexMap<DataPath, DataDefinition>,
2333 ) -> Result<(Expression, NormalFormInterner), Error> {
2334 let tree = expr.tree();
2335 let source = tree.root_source().cloned();
2336 let mut interner = NormalFormInterner::new();
2337 let root = lower_with_data(&tree, &mut interner, data);
2338 let unit_index = UnitIndex::new();
2339 let signature_index = SignatureIndex::new();
2340 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
2341 let nf = rewrite::normalize(
2342 root,
2343 Cells::new(&mut interner, measure_scope),
2344 source.clone(),
2345 )?;
2346 let expression = to_expression(interner_forms(&interner), nf, source);
2347 Ok((expression, interner))
2348 }
2349
2350 fn to_normal_form_empty(expr: &Ex, interner: &mut NormalFormInterner) -> NormalFormId {
2351 let data = algebraic_scope();
2352 to_normal_form_with_data(expr, interner, &data)
2353 }
2354
2355 fn to_normal_form_with_data(
2356 expr: &Ex,
2357 interner: &mut NormalFormInterner,
2358 data: &IndexMap<DataPath, DataDefinition>,
2359 ) -> NormalFormId {
2360 lower_with_data(&expr.tree(), interner, data)
2361 }
2362
2363 fn lower_with_data(
2364 expr: &Expression,
2365 interner: &mut NormalFormInterner,
2366 data: &IndexMap<DataPath, DataDefinition>,
2367 ) -> NormalFormId {
2368 let completed_rules = HashMap::new();
2369 let rule_types = HashMap::new();
2370 let lower = LowerCtx {
2371 completed_rules: &completed_rules,
2372 rule_types: &rule_types,
2373 data,
2374 row_templates: &EMPTY_ROW_TEMPLATES,
2375 self_identity: &TEST_SELF_IDENTITY,
2376 rule_order: &[],
2377 };
2378 let unit_index = UnitIndex::new();
2379 let signature_index = SignatureIndex::new();
2380 let mut cells = Cells::new(interner, empty_measure_scope(&unit_index, &signature_index));
2381 to_normal_form(expr, &mut cells, &lower)
2382 }
2383
2384 struct Lowering<'f> {
2386 forms: &'f [NormalForm],
2387 source: Option<Source>,
2388 table: ExprTable<ExpressionKind>,
2389 }
2390
2391 fn to_expression(forms: &[NormalForm], id: NormalFormId, source: Option<Source>) -> Expression {
2392 let mut lowering = Lowering {
2393 forms,
2394 source,
2395 table: ExprTable::default(),
2396 };
2397 let root = lowering.child(id);
2398 ExprTree::new(lowering.table, root)
2399 }
2400
2401 impl Lowering<'_> {
2402 fn child(&mut self, id: NormalFormId) -> ExprId {
2403 let kind = self.kind(id);
2404 self.table.push(kind, self.source.clone())
2405 }
2406
2407 fn kind(&mut self, id: NormalFormId) -> ExpressionKind {
2408 let forms = self.forms;
2409 let source = self.source.clone();
2410 let nf = cell(forms, id);
2411 if let Some(path) = &nf.rule_ref {
2412 return ExpressionKind::RulePath(path.clone());
2413 }
2414 match &nf.kind {
2415 NormalFormKind::Leaf(LeafKind::Literal(lit)) => {
2416 ExpressionKind::Literal(Box::new(TypedLiteral {
2417 value: lit.value.clone(),
2418 lemma_type: Arc::clone(&nf.result_type),
2419 }))
2420 }
2421 NormalFormKind::Leaf(LeafKind::DataPath(p)) => ExpressionKind::DataPath(p.clone()),
2422 NormalFormKind::Sum(terms) => self.sum_kind(terms),
2423 NormalFormKind::Product(terms) => self.product_kind(terms),
2424 NormalFormKind::Subtract(a, b) => {
2425 self.binary_kind(*a, ArithmeticComputation::Subtract, *b)
2426 }
2427 NormalFormKind::Divide(a, b) => {
2428 self.binary_kind(*a, ArithmeticComputation::Divide, *b)
2429 }
2430 NormalFormKind::Power(base, exp) => {
2431 self.binary_kind(*base, ArithmeticComputation::Power, *exp)
2432 }
2433 NormalFormKind::Modulo(a, b) => {
2434 self.binary_kind(*a, ArithmeticComputation::Modulo, *b)
2435 }
2436 NormalFormKind::Negate(x) => {
2437 let zero = self.table.push(
2438 ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_zero()))),
2439 source.clone(),
2440 );
2441 let operand = self.child(*x);
2442 arithmetic_kind(zero, vec![(ArithmeticComputation::Subtract, operand)])
2443 }
2444 NormalFormKind::Reciprocal(x) => {
2445 let one = self.table.push(
2446 ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_one()))),
2447 source.clone(),
2448 );
2449 let operand = self.child(*x);
2450 arithmetic_kind(one, vec![(ArithmeticComputation::Divide, operand)])
2451 }
2452 NormalFormKind::Comparison(a, op, b) => {
2453 ExpressionKind::Comparison(self.child(*a), op.clone(), self.child(*b))
2454 }
2455 NormalFormKind::Logical(connective, children) => {
2456 self.logical_kind(*connective, children)
2457 }
2458 NormalFormKind::Not(x) => {
2459 ExpressionKind::LogicalNegation(self.child(*x), NegationType::Not)
2460 }
2461 NormalFormKind::MathOp(op, x) => {
2462 ExpressionKind::MathematicalComputation(op.clone(), self.child(*x))
2463 }
2464 NormalFormKind::StepRounding(mode, x, step) => ExpressionKind::StepRounding {
2465 operand: self.child(*x),
2466 mode: *mode,
2467 step: step.map(|step| self.child(step)),
2468 },
2469 NormalFormKind::UnitConversion(x, target) => {
2470 ExpressionKind::UnitConversion(self.child(*x), target.clone())
2471 }
2472 NormalFormKind::Veto(v) => ExpressionKind::Veto(v.clone()),
2473 NormalFormKind::Now => ExpressionKind::Now,
2474 NormalFormKind::DateRelative(kind, x) => {
2475 ExpressionKind::DateRelative(*kind, self.child(*x))
2476 }
2477 NormalFormKind::DateCalendar(kind, unit, x) => {
2478 ExpressionKind::DateCalendar(*kind, *unit, self.child(*x))
2479 }
2480 NormalFormKind::RangeLiteral(a, b) => {
2481 ExpressionKind::RangeLiteral(self.child(*a), self.child(*b))
2482 }
2483 NormalFormKind::PastFutureRange(kind, x) => {
2484 ExpressionKind::PastFutureRange(*kind, self.child(*x))
2485 }
2486 NormalFormKind::RangeBound(bound, x) => {
2487 ExpressionKind::RangeBound(*bound, self.child(*x))
2488 }
2489 NormalFormKind::RangeContainment(a, b) => {
2490 ExpressionKind::RangeContainment(self.child(*a), self.child(*b))
2491 }
2492 NormalFormKind::ResultIsVeto(operand) => {
2493 ExpressionKind::ResultIsVeto(self.child(*operand))
2494 }
2495 NormalFormKind::Piecewise(arms) => ExpressionKind::Piecewise(
2496 arms.iter()
2497 .map(|(condition, result)| (self.child(*condition), self.child(*result)))
2498 .collect(),
2499 ),
2500 NormalFormKind::SpecMember {
2501 base, path, name, ..
2502 } => ExpressionKind::SpecMember {
2503 base: self.child(*base),
2504 path: path.clone(),
2505 name: name.clone(),
2506 },
2507 NormalFormKind::RowField {
2508 level,
2509 identity,
2510 end,
2511 qualifier,
2512 ..
2513 } => ExpressionKind::RowField {
2514 level: *level,
2515 identity: identity.clone(),
2516 end: end.clone(),
2517 qualifier: qualifier.clone(),
2518 },
2519 NormalFormKind::Leaf(LeafKind::RowValue { .. }) => {
2520 panic!("BUG: normal-form RowValue cannot be rebuilt without its list context")
2521 }
2522 NormalFormKind::OrderedDispatch { .. } => self.kind(
2524 nf.origin
2525 .expect("BUG: OrderedDispatch without its Piecewise pre-image"),
2526 ),
2527 NormalFormKind::ListCommand {
2528 list,
2529 op,
2530 args,
2531 row_level,
2532 } => {
2533 let list_expr = self.child(*list);
2534 let arg_exprs: Vec<ExprId> = args.iter().map(|id| self.child(*id)).collect();
2535 use crate::planning::semantics::{ListOp, ListReduce};
2536 let planned = match op {
2537 NormalFormListOp::Find => ListOp::Find { keys: arg_exprs },
2538 NormalFormListOp::First => ListOp::First,
2539 NormalFormListOp::Last => ListOp::Last,
2540 NormalFormListOp::Count => ListOp::Count,
2541 NormalFormListOp::Sum => ListOp::Reduce {
2542 kind: ListReduce::Sum,
2543 argument: arg_exprs.into_iter().next(),
2544 },
2545 NormalFormListOp::Avg => ListOp::Reduce {
2546 kind: ListReduce::Avg,
2547 argument: arg_exprs.into_iter().next(),
2548 },
2549 NormalFormListOp::Product => ListOp::Reduce {
2550 kind: ListReduce::Product,
2551 argument: arg_exprs.into_iter().next(),
2552 },
2553 NormalFormListOp::Min => ListOp::Reduce {
2554 kind: ListReduce::Min,
2555 argument: arg_exprs.into_iter().next(),
2556 },
2557 NormalFormListOp::Max => ListOp::Reduce {
2558 kind: ListReduce::Max,
2559 argument: arg_exprs.into_iter().next(),
2560 },
2561 NormalFormListOp::Predicate { kind, form } => ListOp::Predicate {
2562 kind: *kind,
2563 form: *form,
2564 expr: arg_exprs
2565 .into_iter()
2566 .next()
2567 .expect("BUG: predicate needs a row expression"),
2568 },
2569 NormalFormListOp::Sort { descending } => ListOp::Sort {
2570 descending: *descending,
2571 argument: arg_exprs.into_iter().next(),
2572 },
2573 NormalFormListOp::Top => ListOp::Top {
2574 n: arg_exprs.into_iter().next().expect("BUG: top needs n"),
2575 },
2576 NormalFormListOp::With => ListOp::With { rows: arg_exprs },
2577 NormalFormListOp::WithRow { identity, fields } => ListOp::WithRow {
2578 identity: SpecIdentity::clone(identity),
2579 fields: fields.iter().cloned().zip(arg_exprs).collect(),
2580 },
2581 NormalFormListOp::Key { fields } => ListOp::Key {
2582 fields: fields.clone(),
2583 },
2584 };
2585 ExpressionKind::ListCommand {
2586 list: list_expr,
2587 op: planned,
2588 row_level: *row_level,
2589 }
2590 }
2591 }
2592 }
2593
2594 fn binary_kind(
2595 &mut self,
2596 left: NormalFormId,
2597 op: ArithmeticComputation,
2598 right: NormalFormId,
2599 ) -> ExpressionKind {
2600 let left = self.child(left);
2601 let right = self.child(right);
2602 arithmetic_kind(left, vec![(op, right)])
2603 }
2604
2605 fn sum_kind(&mut self, terms: &[ArithmeticTerm]) -> ExpressionKind {
2606 match terms {
2607 [] => ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_zero()))),
2608 [one] => self.kind(one.operand),
2609 [first, rest @ ..] => self.terms_kind(
2610 first.operand,
2611 rest,
2612 ArithmeticComputation::Add,
2613 ArithmeticComputation::Subtract,
2614 ),
2615 }
2616 }
2617
2618 fn product_kind(&mut self, terms: &[ArithmeticTerm]) -> ExpressionKind {
2619 match terms {
2620 [] => ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_one()))),
2621 [one] => self.kind(one.operand),
2622 [first, rest @ ..] => self.terms_kind(
2623 first.operand,
2624 rest,
2625 ArithmeticComputation::Multiply,
2626 ArithmeticComputation::Divide,
2627 ),
2628 }
2629 }
2630
2631 fn terms_kind(
2632 &mut self,
2633 first: NormalFormId,
2634 rest: &[ArithmeticTerm],
2635 direct: ArithmeticComputation,
2636 inverse: ArithmeticComputation,
2637 ) -> ExpressionKind {
2638 let first = self.child(first);
2639 let steps = rest
2640 .iter()
2641 .map(|term| {
2642 let op = if term.inverse {
2643 inverse.clone()
2644 } else {
2645 direct.clone()
2646 };
2647 (op, self.child(term.operand))
2648 })
2649 .collect();
2650 arithmetic_kind(first, steps)
2651 }
2652
2653 fn logical_kind(
2654 &mut self,
2655 connective: LogicalConnective,
2656 children: &[NormalFormId],
2657 ) -> ExpressionKind {
2658 match children {
2659 [] => unreachable!("BUG: Logical has at least one operand"),
2660 [one] => self.kind(*one),
2661 [first, rest @ ..] => {
2662 let first = self.child(*first);
2663 let operands = rest.iter().map(|id| self.child(*id)).collect();
2664 logical_kind(connective, first, operands)
2665 }
2666 }
2667 }
2668 }
2669
2670 fn step_source() -> Source {
2672 Source::new(
2673 crate::parsing::source::SourceType::Volatile,
2674 crate::Span {
2675 start: 0,
2676 end: 0,
2677 line: 1,
2678 col: 1,
2679 },
2680 )
2681 }
2682
2683 fn arithmetic_kind(
2684 first: ExprId,
2685 steps: Vec<(ArithmeticComputation, ExprId)>,
2686 ) -> ExpressionKind {
2687 ExpressionKind::Arithmetic {
2688 first,
2689 steps: steps
2690 .into_iter()
2691 .map(|(op, operand)| ChainStep {
2692 op,
2693 operand,
2694 source_location: step_source(),
2695 })
2696 .collect(),
2697 }
2698 }
2699
2700 fn binary_arithmetic(
2702 kind: &ExpressionKind,
2703 ) -> Option<(&ExprId, &ArithmeticComputation, &ExprId)> {
2704 match kind {
2705 ExpressionKind::Arithmetic { first, steps } => match steps.as_slice() {
2706 [step] => Some((first, &step.op, &step.operand)),
2707 _ => None,
2708 },
2709 _ => None,
2710 }
2711 }
2712
2713 fn logical_kind(
2714 connective: LogicalConnective,
2715 first: ExprId,
2716 operands: Vec<ExprId>,
2717 ) -> ExpressionKind {
2718 ExpressionKind::Logical {
2719 connective,
2720 first,
2721 steps: operands
2722 .into_iter()
2723 .map(|operand| ChainStep {
2724 op: (),
2725 operand,
2726 source_location: step_source(),
2727 })
2728 .collect(),
2729 }
2730 }
2731
2732 fn num_expr(n: i64) -> Ex {
2733 Ex::leaf(
2734 ExpressionKind::Literal(Box::new(TypedLiteral::number(rational_new(n, 1)))),
2735 None,
2736 )
2737 }
2738
2739 fn bool_expr(b: bool) -> Ex {
2740 Ex::leaf(
2741 ExpressionKind::Literal(Box::new(TypedLiteral::from_bool(b))),
2742 None,
2743 )
2744 }
2745
2746 fn dx() -> Ex {
2747 Ex::leaf(
2748 ExpressionKind::DataPath(DataPath::new(vec![], "x".into())),
2749 None,
2750 )
2751 }
2752
2753 fn dflag() -> Ex {
2754 Ex::leaf(
2755 ExpressionKind::DataPath(DataPath::new(vec![], "flag".into())),
2756 None,
2757 )
2758 }
2759
2760 fn dflag2() -> Ex {
2761 Ex::leaf(
2762 ExpressionKind::DataPath(DataPath::new(vec![], "flag2".into())),
2763 None,
2764 )
2765 }
2766
2767 type PushNodes = dyn Fn(&mut ExprTable<ExpressionKind>) -> ExprId;
2769
2770 #[derive(Clone)]
2772 struct Ex(Rc<PushNodes>);
2773
2774 impl Ex {
2775 fn leaf(kind: ExpressionKind, source: Option<Source>) -> Self {
2776 Self(Rc::new(move |table| {
2777 table.push(kind.clone(), source.clone())
2778 }))
2779 }
2780
2781 fn push(&self, table: &mut ExprTable<ExpressionKind>) -> ExprId {
2782 (self.0)(table)
2783 }
2784
2785 fn tree(&self) -> Expression {
2786 let mut table = ExprTable::default();
2787 let root = self.push(&mut table);
2788 ExprTree::new(table, root)
2789 }
2790 }
2791
2792 fn compose_expr(children: &[&Ex], make: impl Fn(&[ExprId]) -> ExpressionKind + 'static) -> Ex {
2793 let children: Vec<Ex> = children.iter().map(|child| (*child).clone()).collect();
2794 Ex(Rc::new(move |table| {
2795 let ids: Vec<ExprId> = children.iter().map(|child| child.push(table)).collect();
2796 table.push(make(&ids), None)
2797 }))
2798 }
2799
2800 fn unless_chain(default: Ex, arms: Vec<(Ex, Ex)>) -> Ex {
2803 if arms.is_empty() {
2804 return default;
2805 }
2806 Ex(Rc::new(move |table| {
2807 let default_result = default.push(table);
2808 let default_condition = table.push(
2809 ExpressionKind::Literal(Box::new(TypedLiteral::from_bool(true))),
2810 None,
2811 );
2812 let mut pieces = vec![(default_condition, default_result)];
2813 for (condition, result) in &arms {
2814 let condition = condition.push(table);
2815 let result = result.push(table);
2816 pieces.push((condition, result));
2817 }
2818 table.push(ExpressionKind::Piecewise(pieces), None)
2819 }))
2820 }
2821
2822 fn add_expr(a: Ex, b: Ex) -> Ex {
2823 compose_expr(&[&a, &b], |ids| {
2824 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Add, ids[1])])
2825 })
2826 }
2827
2828 fn mul_expr(a: Ex, b: Ex) -> Ex {
2829 compose_expr(&[&a, &b], |ids| {
2830 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Multiply, ids[1])])
2831 })
2832 }
2833
2834 fn div_expr(a: Ex, b: Ex) -> Ex {
2835 compose_expr(&[&a, &b], |ids| {
2836 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Divide, ids[1])])
2837 })
2838 }
2839
2840 fn pow_expr(a: Ex, b: Ex) -> Ex {
2841 compose_expr(&[&a, &b], |ids| {
2842 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Power, ids[1])])
2843 })
2844 }
2845
2846 fn and_expr(a: Ex, b: Ex) -> Ex {
2847 compose_expr(&[&a, &b], |ids| {
2848 logical_kind(LogicalConnective::And, ids[0], vec![ids[1]])
2849 })
2850 }
2851
2852 fn or_expr(a: Ex, b: Ex) -> Ex {
2853 compose_expr(&[&a, &b], |ids| {
2854 logical_kind(LogicalConnective::Or, ids[0], vec![ids[1]])
2855 })
2856 }
2857
2858 fn not_expr(inner: Ex) -> Ex {
2859 compose_expr(&[&inner], |ids| {
2860 ExpressionKind::LogicalNegation(ids[0], NegationType::Not)
2861 })
2862 }
2863
2864 fn lt_expr(a: Ex, b: Ex) -> Ex {
2865 compose_expr(&[&a, &b], |ids| {
2866 ExpressionKind::Comparison(ids[0], ComparisonComputation::LessThan, ids[1])
2867 })
2868 }
2869
2870 fn sub_expr(a: Ex, b: Ex) -> Ex {
2871 compose_expr(&[&a, &b], |ids| {
2872 arithmetic_kind(ids[0], vec![(ArithmeticComputation::Subtract, ids[1])])
2873 })
2874 }
2875
2876 fn math_expr(op: MathematicalComputation, operand: Ex) -> Ex {
2877 compose_expr(&[&operand], move |ids| {
2878 ExpressionKind::MathematicalComputation(op.clone(), ids[0])
2879 })
2880 }
2881
2882 mod ordered_dispatch_fold {
2883 use super::*;
2884 use crate::parsing::source::{Source as ParseSource, SourceType};
2885 use crate::planning::ordered_dispatch::{region_count, DispatchKey};
2886 use crate::planning::semantics::{
2887 primitive_boolean_arc, primitive_date_arc, primitive_number_arc, primitive_text_arc,
2888 LemmaType, TypeSpecification,
2889 };
2890 use crate::Span;
2891
2892 fn test_source() -> ParseSource {
2893 ParseSource::new(
2894 SourceType::Volatile,
2895 Span {
2896 start: 0,
2897 end: 0,
2898 line: 1,
2899 col: 1,
2900 },
2901 )
2902 }
2903
2904 fn data_path(name: &str) -> DataPath {
2905 DataPath::new(vec![], name.into())
2906 }
2907
2908 fn declared(name: &str, resolved_type: Arc<LemmaType>) -> (DataPath, DataDefinition) {
2909 (
2910 data_path(name),
2911 DataDefinition::TypeDeclaration {
2912 resolved_type,
2913 declared_suggestion: None,
2914 declared_value: None,
2915 source: test_source(),
2916 },
2917 )
2918 }
2919
2920 fn scope() -> IndexMap<DataPath, DataDefinition> {
2922 let mut data = IndexMap::new();
2923 for entry in [
2924 declared("x", primitive_number_arc().clone()),
2925 declared("y", primitive_number_arc().clone()),
2926 declared("code", primitive_text_arc().clone()),
2927 declared("flag", primitive_boolean_arc().clone()),
2928 declared("day", primitive_date_arc().clone()),
2929 declared(
2930 "rate",
2931 Arc::new(LemmaType::primitive(TypeSpecification::ratio())),
2932 ),
2933 ] {
2934 data.insert(entry.0, entry.1);
2935 }
2936 data
2937 }
2938
2939 fn path_expr(name: &str) -> Ex {
2940 Ex::leaf(ExpressionKind::DataPath(data_path(name)), None)
2941 }
2942
2943 fn text_expr(text: &str) -> Ex {
2944 Ex::leaf(
2945 ExpressionKind::Literal(Box::new(TypedLiteral::text(text.to_string()))),
2946 None,
2947 )
2948 }
2949
2950 fn compare(left: Ex, op: ComparisonComputation, right: Ex) -> Ex {
2951 compose_expr(&[&left, &right], move |ids| {
2952 ExpressionKind::Comparison(ids[0], op.clone(), ids[1])
2953 })
2954 }
2955
2956 fn chain(default: Ex, arms: Vec<(Ex, Ex)>) -> Ex {
2958 unless_chain(default, arms)
2959 }
2960
2961 fn fold(expr: &Ex) -> (NormalFormId, NormalFormInterner) {
2963 let data = scope();
2964 let mut interner = NormalFormInterner::new();
2965 let root = to_normal_form_with_data(expr, &mut interner, &data);
2966 let unit_index = UnitIndex::new();
2967 let signature_index = SignatureIndex::new();
2968 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
2969 let nf = rewrite::normalize(root, Cells::new(&mut interner, measure_scope), None)
2970 .expect("normalize");
2971 (nf, interner)
2972 }
2973
2974 fn table(expr: &Ex) -> (Vec<DispatchKey>, Vec<String>) {
2976 let (nf, interner) = fold(expr);
2977 let NormalFormKind::OrderedDispatch {
2978 boundaries,
2979 regions,
2980 ..
2981 } = &interner.get(nf).kind
2982 else {
2983 panic!("expected OrderedDispatch, got {:?}", interner.get(nf).kind);
2984 };
2985 let rendered = regions
2986 .iter()
2987 .map(|region| explanation_display(interner_forms(&interner), region.cell(), None))
2988 .collect();
2989 (boundaries.to_vec(), rendered)
2990 }
2991
2992 fn assert_declines(expr: &Ex, reason: &str) {
2993 let (nf, interner) = fold(expr);
2994 assert!(
2995 !matches!(
2996 interner.get(nf).kind,
2997 NormalFormKind::OrderedDispatch { .. }
2998 ),
2999 "must not fold ({reason}), got {:?}",
3000 interner.get(nf).kind
3001 );
3002 }
3003
3004 #[test]
3005 fn equality_chain_on_text_folds_to_point_regions() {
3006 let expr = chain(
3007 num_expr(0),
3008 vec![
3009 (
3010 compare(
3011 path_expr("code"),
3012 ComparisonComputation::Is,
3013 text_expr("NL"),
3014 ),
3015 num_expr(1),
3016 ),
3017 (
3018 compare(
3019 path_expr("code"),
3020 ComparisonComputation::Is,
3021 text_expr("BE"),
3022 ),
3023 num_expr(2),
3024 ),
3025 ],
3026 );
3027 let (boundaries, regions) = table(&expr);
3028 assert_eq!(
3029 boundaries,
3030 vec![
3031 DispatchKey::Text(std::sync::Arc::from("BE")),
3032 DispatchKey::Text(std::sync::Arc::from("NL"))
3033 ]
3034 );
3035 assert_eq!(regions, vec!["0", "2", "0", "1", "0"]);
3037 }
3038
3039 #[test]
3040 fn later_arm_wins_when_two_arms_share_a_constant() {
3041 let expr = chain(
3042 num_expr(0),
3043 vec![
3044 (
3045 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3046 num_expr(1),
3047 ),
3048 (
3049 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3050 num_expr(2),
3051 ),
3052 ],
3053 );
3054 let (boundaries, regions) = table(&expr);
3055 assert_eq!(boundaries.len(), 1, "duplicates collapse to one breakpoint");
3056 assert_eq!(regions, vec!["0", "2", "0"]);
3057 }
3058
3059 #[test]
3060 fn greater_than_and_greater_or_equal_differ_at_the_boundary() {
3061 let strict = chain(
3062 num_expr(0),
3063 vec![(
3064 compare(
3065 path_expr("x"),
3066 ComparisonComputation::GreaterThan,
3067 num_expr(5),
3068 ),
3069 num_expr(1),
3070 )],
3071 );
3072 assert_eq!(table(&strict).1, vec!["0", "0", "1"]);
3073
3074 let inclusive = chain(
3075 num_expr(0),
3076 vec![(
3077 compare(
3078 path_expr("x"),
3079 ComparisonComputation::GreaterThanOrEqual,
3080 num_expr(5),
3081 ),
3082 num_expr(1),
3083 )],
3084 );
3085 assert_eq!(table(&inclusive).1, vec!["0", "1", "1"]);
3086 }
3087
3088 #[test]
3089 fn is_not_claims_every_region_but_its_own_point() {
3090 let expr = chain(
3091 num_expr(0),
3092 vec![(
3093 compare(path_expr("x"), ComparisonComputation::IsNot, num_expr(5)),
3094 num_expr(1),
3095 )],
3096 );
3097 assert_eq!(table(&expr).1, vec!["1", "0", "1"]);
3098 }
3099
3100 #[test]
3101 fn overlapping_ordering_arms_resolve_by_arm_order() {
3102 let expr = chain(
3104 num_expr(0),
3105 vec![
3106 (
3107 compare(
3108 path_expr("x"),
3109 ComparisonComputation::GreaterThan,
3110 num_expr(5),
3111 ),
3112 num_expr(1),
3113 ),
3114 (
3115 compare(
3116 path_expr("x"),
3117 ComparisonComputation::GreaterThan,
3118 num_expr(10),
3119 ),
3120 num_expr(2),
3121 ),
3122 ],
3123 );
3124 let (boundaries, regions) = table(&expr);
3125 assert_eq!(boundaries.len(), 2);
3126 assert_eq!(regions.len(), region_count(2));
3127 assert_eq!(regions, vec!["0", "0", "1", "1", "2"]);
3129 }
3130
3131 #[test]
3132 fn literal_on_the_left_mirrors_the_operator() {
3133 let expr = chain(
3134 num_expr(0),
3135 vec![(
3136 compare(num_expr(5), ComparisonComputation::LessThan, path_expr("x")),
3137 num_expr(1),
3138 )],
3139 );
3140 assert_eq!(table(&expr).1, vec!["0", "0", "1"]);
3142 }
3143
3144 #[test]
3145 fn dates_fold_with_ordering_operators() {
3146 let day = |year: i32| {
3147 Ex::leaf(
3148 ExpressionKind::Literal(Box::new(TypedLiteral::date(
3149 crate::planning::semantics::SemanticDateTime {
3150 year,
3151 month: 1,
3152 day: 1,
3153 hour: 0,
3154 minute: 0,
3155 second: 0,
3156 microsecond: 0,
3157 timezone: None,
3158 },
3159 ))),
3160 None,
3161 )
3162 };
3163 let expr = chain(
3164 num_expr(0),
3165 vec![(
3166 compare(
3167 path_expr("day"),
3168 ComparisonComputation::GreaterThanOrEqual,
3169 day(2020),
3170 ),
3171 num_expr(1),
3172 )],
3173 );
3174 let (boundaries, regions) = table(&expr);
3175 assert!(matches!(boundaries.as_slice(), [DispatchKey::Date(_)]));
3176 assert_eq!(regions, vec!["0", "1", "1"]);
3177 }
3178
3179 #[test]
3180 fn calendar_failure_literal_is_a_planning_error() {
3181 let invalid_day = Ex::leaf(
3182 ExpressionKind::Literal(Box::new(TypedLiteral::date(
3183 crate::planning::semantics::SemanticDateTime {
3184 year: 2020,
3185 month: 13,
3186 day: 1,
3187 hour: 0,
3188 minute: 0,
3189 second: 0,
3190 microsecond: 0,
3191 timezone: None,
3192 },
3193 ))),
3194 None,
3195 );
3196 let expr = chain(
3197 num_expr(0),
3198 vec![(
3199 compare(path_expr("day"), ComparisonComputation::Is, invalid_day),
3200 num_expr(1),
3201 )],
3202 );
3203 let data = scope();
3204 let mut interner = NormalFormInterner::new();
3205 let root = to_normal_form_with_data(&expr, &mut interner, &data);
3206 let unit_index = UnitIndex::new();
3207 let signature_index = SignatureIndex::new();
3208 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
3209 let err = rewrite::normalize(root, Cells::new(&mut interner, measure_scope), None)
3210 .expect_err(
3211 "invalid calendar literal must fail planning, not silently stay Piecewise",
3212 );
3213 let message = err.to_string();
3214 assert!(
3215 message.contains("ordered dispatch") && message.contains("Invalid date"),
3216 "unexpected error: {message}"
3217 );
3218 }
3219
3220 #[test]
3221 fn the_pre_image_is_kept_as_the_fold_origin() {
3222 let expr = chain(
3223 num_expr(0),
3224 vec![(
3225 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3226 num_expr(1),
3227 )],
3228 );
3229 let (nf, interner) = fold(&expr);
3230 let origin = interner
3231 .get(nf)
3232 .origin
3233 .expect("dispatch must record its pre-image");
3234 assert!(
3235 matches!(interner.get(origin).kind, NormalFormKind::Piecewise(_)),
3236 "origin must be the Piecewise, got {:?}",
3237 interner.get(origin).kind
3238 );
3239 }
3240
3241 #[test]
3242 fn a_collapsed_arm_keeps_the_whole_origin_chain() {
3243 let expr = chain(
3246 num_expr(0),
3247 vec![
3248 (lt_expr(num_expr(1), num_expr(0)), num_expr(9)),
3249 (
3250 compare(path_expr("x"), ComparisonComputation::Is, num_expr(5)),
3251 num_expr(1),
3252 ),
3253 ],
3254 );
3255 let (nf, interner) = fold(&expr);
3256 let origin = interner
3257 .get(nf)
3258 .origin
3259 .expect("dispatch must record its pre-image");
3260 assert!(
3261 matches!(interner.get(origin).kind, NormalFormKind::Piecewise(_)),
3262 "origin must be the Piecewise, got {:?}",
3263 interner.get(origin).kind
3264 );
3265 let recorded = interner
3266 .get(origin)
3267 .origin
3268 .expect("collapse origin must survive the second fold");
3269 let NormalFormKind::Piecewise(arms) = &interner.get(recorded).kind else {
3270 panic!("recorded pre-image must be a Piecewise");
3271 };
3272 assert_eq!(arms.len(), 3, "the dropped arm must still be recorded");
3273 }
3274
3275 #[test]
3276 fn text_declines_ordering_operators() {
3277 assert_declines(
3278 &chain(
3279 num_expr(0),
3280 vec![(
3281 compare(
3282 path_expr("code"),
3283 ComparisonComputation::GreaterThan,
3284 text_expr("NL"),
3285 ),
3286 num_expr(1),
3287 )],
3288 ),
3289 "text has no ordering comparison",
3290 );
3291 }
3292
3293 #[test]
3294 fn arms_on_different_scrutinees_decline() {
3295 assert_declines(
3296 &chain(
3297 num_expr(0),
3298 vec![
3299 (
3300 compare(path_expr("x"), ComparisonComputation::Is, num_expr(1)),
3301 num_expr(1),
3302 ),
3303 (
3304 compare(path_expr("y"), ComparisonComputation::Is, num_expr(2)),
3305 num_expr(2),
3306 ),
3307 ],
3308 ),
3309 "two scrutinees",
3310 );
3311 }
3312
3313 #[test]
3314 fn equality_or_of_is_comparisons_declines() {
3315 assert_declines(
3316 &chain(
3317 num_expr(0),
3318 vec![(
3319 or_expr(
3320 compare(path_expr("x"), ComparisonComputation::Is, num_expr(1)),
3321 compare(path_expr("y"), ComparisonComputation::Is, num_expr(2)),
3322 ),
3323 num_expr(1),
3324 )],
3325 ),
3326 "or of equality comparisons is not an And peel",
3327 );
3328 }
3329
3330 #[test]
3331 fn boolean_scrutinee_declines() {
3332 assert_declines(
3333 &chain(
3334 num_expr(0),
3335 vec![(
3336 compare(
3337 path_expr("flag"),
3338 ComparisonComputation::Is,
3339 bool_expr(true),
3340 ),
3341 num_expr(1),
3342 )],
3343 ),
3344 "boolean has at most two keys",
3345 );
3346 }
3347
3348 #[test]
3349 fn mismatched_key_and_scrutinee_types_decline() {
3350 assert_declines(
3351 &chain(
3352 num_expr(0),
3353 vec![(
3354 compare(path_expr("code"), ComparisonComputation::Is, num_expr(1)),
3355 num_expr(1),
3356 )],
3357 ),
3358 "text scrutinee against a number key",
3359 );
3360 assert_declines(
3361 &chain(
3362 num_expr(0),
3363 vec![(
3364 compare(path_expr("rate"), ComparisonComputation::Is, num_expr(1)),
3365 num_expr(1),
3366 )],
3367 ),
3368 "ratio scrutinee against a plain number key",
3369 );
3370 }
3371
3372 #[test]
3373 fn a_determined_expression_scrutinee_folds() {
3374 let expr = chain(
3376 num_expr(0),
3377 vec![(
3378 compare(
3379 add_expr(path_expr("x"), num_expr(1)),
3380 ComparisonComputation::Is,
3381 num_expr(5),
3382 ),
3383 num_expr(1),
3384 )],
3385 );
3386 let (boundaries, regions) = table(&expr);
3387 assert_eq!(boundaries.len(), 1);
3388 assert_eq!(regions, vec!["0", "1", "0"]);
3389 }
3390
3391 #[test]
3392 fn conjunctive_key_lut_folds_to_nested_dispatch() {
3393 let expr = chain(
3394 num_expr(0),
3395 vec![
3396 (
3397 and_expr(
3398 compare(path_expr("x"), ComparisonComputation::Is, num_expr(2)),
3399 compare(
3400 path_expr("y"),
3401 ComparisonComputation::GreaterThanOrEqual,
3402 num_expr(1),
3403 ),
3404 ),
3405 num_expr(10),
3406 ),
3407 (
3408 and_expr(
3409 compare(path_expr("x"), ComparisonComputation::Is, num_expr(2)),
3410 compare(
3411 path_expr("y"),
3412 ComparisonComputation::GreaterThanOrEqual,
3413 num_expr(5),
3414 ),
3415 ),
3416 num_expr(20),
3417 ),
3418 (
3419 and_expr(
3420 compare(path_expr("x"), ComparisonComputation::Is, num_expr(3)),
3421 compare(
3422 path_expr("y"),
3423 ComparisonComputation::GreaterThanOrEqual,
3424 num_expr(1),
3425 ),
3426 ),
3427 num_expr(30),
3428 ),
3429 ],
3430 );
3431 let (nf, interner) = fold(&expr);
3432 let NormalFormKind::OrderedDispatch {
3433 boundaries,
3434 regions,
3435 ..
3436 } = &interner.get(nf).kind
3437 else {
3438 panic!(
3439 "expected outer OrderedDispatch, got {:?}",
3440 interner.get(nf).kind
3441 );
3442 };
3443 assert_eq!(boundaries.len(), 2, "outer keys are the two zone values");
3444 let point_bodies: Vec<_> = regions
3446 .iter()
3447 .enumerate()
3448 .filter(|(index, _)| index % 2 == 1)
3449 .map(|(_, region)| &interner.get(region.cell()).kind)
3450 .collect();
3451 assert_eq!(point_bodies.len(), 2);
3452 for kind in &point_bodies {
3453 assert!(
3454 matches!(kind, NormalFormKind::OrderedDispatch { .. }),
3455 "point region must be an inner OrderedDispatch, got {kind:?}"
3456 );
3457 }
3458 for (index, region) in regions.iter().enumerate() {
3459 if index % 2 == 0 {
3460 assert_eq!(
3461 explanation_display(interner_forms(&interner), region.cell(), None),
3462 "0",
3463 "interval region must stay the default"
3464 );
3465 }
3466 }
3467 }
3468
3469 #[test]
3470 fn conjunctive_key_without_shared_is_scrutinee_declines() {
3471 assert_declines(
3473 &chain(
3474 num_expr(0),
3475 vec![
3476 (
3477 and_expr(
3478 compare(
3479 path_expr("x"),
3480 ComparisonComputation::GreaterThan,
3481 num_expr(1),
3482 ),
3483 compare(
3484 path_expr("y"),
3485 ComparisonComputation::GreaterThan,
3486 num_expr(1),
3487 ),
3488 ),
3489 num_expr(1),
3490 ),
3491 (
3492 and_expr(
3493 compare(
3494 path_expr("x"),
3495 ComparisonComputation::GreaterThan,
3496 num_expr(2),
3497 ),
3498 compare(
3499 path_expr("y"),
3500 ComparisonComputation::GreaterThan,
3501 num_expr(2),
3502 ),
3503 ),
3504 num_expr(2),
3505 ),
3506 ],
3507 ),
3508 "no Is key conjunct shared across arms",
3509 );
3510 }
3511
3512 #[test]
3513 fn an_unknown_data_path_declines() {
3514 assert_declines(
3515 &chain(
3516 num_expr(0),
3517 vec![(
3518 compare(path_expr("absent"), ComparisonComputation::Is, num_expr(5)),
3519 num_expr(1),
3520 )],
3521 ),
3522 "the scrutinee has no resolved type in scope",
3523 );
3524 }
3525
3526 #[test]
3527 fn a_non_comparison_condition_declines() {
3528 assert_declines(
3529 &chain(num_expr(0), vec![(path_expr("flag"), num_expr(1))]),
3530 "a bare boolean condition is not a comparison",
3531 );
3532 }
3533
3534 #[test]
3535 fn a_comparison_between_two_data_paths_declines() {
3536 assert_declines(
3537 &chain(
3538 num_expr(0),
3539 vec![(
3540 compare(path_expr("x"), ComparisonComputation::Is, path_expr("y")),
3541 num_expr(1),
3542 )],
3543 ),
3544 "neither side is a constant",
3545 );
3546 }
3547
3548 #[test]
3551 fn folded_table_agrees_with_the_piecewise_it_replaced() {
3552 let operators = [
3553 ComparisonComputation::Is,
3554 ComparisonComputation::IsNot,
3555 ComparisonComputation::GreaterThan,
3556 ComparisonComputation::GreaterThanOrEqual,
3557 ComparisonComputation::LessThan,
3558 ComparisonComputation::LessThanOrEqual,
3559 ];
3560 let constants = [5i64, 2, 5, 8];
3562 let mut checked = 0usize;
3563
3564 for first in &operators {
3565 for second in &operators {
3566 for third in &operators {
3567 let arm_operators = [first, second, third];
3568 let arms: Vec<(Ex, Ex)> = arm_operators
3569 .iter()
3570 .enumerate()
3571 .map(|(index, operator)| {
3572 (
3573 compare(
3574 path_expr("x"),
3575 (*operator).clone(),
3576 num_expr(constants[index]),
3577 ),
3578 num_expr(index as i64 + 1),
3579 )
3580 })
3581 .collect();
3582 let expr = chain(num_expr(0), arms);
3583 let (boundaries, regions) = table(&expr);
3584
3585 for probe in [-100i64, 1, 2, 3, 5, 6, 8, 9, 100] {
3587 let expected = arm_operators
3588 .iter()
3589 .enumerate()
3590 .rev()
3591 .find_map(|(index, operator)| {
3592 predicate_holds(probe, operator, constants[index])
3593 .then_some(index as i64 + 1)
3594 })
3595 .unwrap_or(0);
3596 let region = crate::planning::ordered_dispatch::region_for_value(
3597 &boundaries,
3598 &DispatchKey::Rational(rational_new(probe, 1)).as_probe(),
3599 )
3600 .expect("integers compare");
3601 assert_eq!(
3602 regions[region],
3603 expected.to_string(),
3604 "operators {first} / {second} / {third} disagree at x = {probe}"
3605 );
3606 checked += 1;
3607 }
3608 }
3609 }
3610 }
3611 assert_eq!(checked, 6 * 6 * 6 * 9);
3612 }
3613
3614 fn predicate_holds(value: i64, operator: &ComparisonComputation, constant: i64) -> bool {
3615 match operator {
3616 ComparisonComputation::Is => value == constant,
3617 ComparisonComputation::IsNot => value != constant,
3618 ComparisonComputation::GreaterThan => value > constant,
3619 ComparisonComputation::GreaterThanOrEqual => value >= constant,
3620 ComparisonComputation::LessThan => value < constant,
3621 ComparisonComputation::LessThanOrEqual => value <= constant,
3622 }
3623 }
3624 }
3625
3626 #[test]
3627 fn flatten_associative_sum_nested() {
3628 let inner = add_expr(num_expr(1), num_expr(2));
3629 let expr = add_expr(inner, num_expr(3));
3630 let norm = normalize_expression(&expr).expect("normalize");
3631 assert!(matches!(
3632 norm.root_kind(),
3633 ExpressionKind::Literal(ref l)
3634 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(6, 1))
3635 ));
3636 }
3637
3638 #[test]
3639 fn flatten_associative_product_nested() {
3640 let inner = mul_expr(num_expr(2), num_expr(3));
3641 let expr = mul_expr(inner, num_expr(4));
3642 let norm = normalize_expression(&expr).expect("normalize");
3643 assert!(matches!(
3644 norm.root_kind(),
3645 ExpressionKind::Literal(ref l)
3646 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(24, 1))
3647 ));
3648 }
3649
3650 #[test]
3651 fn and_chain_lowers_to_one_flat_cell() {
3652 let chain = compose_expr(
3653 &[&bool_expr(true), &bool_expr(true), &bool_expr(false)],
3654 |ids| logical_kind(LogicalConnective::And, ids[0], vec![ids[1], ids[2]]),
3655 );
3656 let mut interner = NormalFormInterner::new();
3657 let id = to_normal_form_empty(&chain, &mut interner);
3658 let NormalFormKind::Logical(LogicalConnective::And, children) = &interner.get(id).kind
3659 else {
3660 panic!("expected And(..), got {:?}", interner.get(id).kind);
3661 };
3662 let [first, second, third] = children.as_slice() else {
3663 panic!("expected three flat operands, got {children:?}");
3664 };
3665 for operand in [first, second, third] {
3666 assert!(matches!(
3667 &interner.get(*operand).kind,
3668 NormalFormKind::Leaf(LeafKind::Literal(_))
3669 ));
3670 }
3671 }
3672
3673 #[test]
3674 fn parenthesized_sum_flattens_into_parent() {
3675 let y = Ex::leaf(
3676 ExpressionKind::DataPath(DataPath::new(vec![], "y".into())),
3677 None,
3678 );
3679 for expr in [
3680 add_expr(dx(), add_expr(y.clone(), num_expr(2))),
3681 add_expr(add_expr(dx(), y.clone()), num_expr(2)),
3682 ] {
3683 let norm = normalize_expression(&expr).expect("normalize");
3684 let ExpressionKind::Arithmetic { steps, .. } = norm.root_kind() else {
3685 panic!("expected one sum, got {:?}", norm.root_kind());
3686 };
3687 assert_eq!(steps.len(), 2, "expected three flat terms, got {norm:?}");
3688 }
3689 }
3690
3691 #[test]
3692 fn parenthesized_conjunction_flattens_into_parent() {
3693 let x_below_one = lt_expr(dx(), num_expr(1));
3694 for expr in [
3695 and_expr(dflag(), and_expr(dflag2(), x_below_one.clone())),
3696 and_expr(and_expr(dflag(), dflag2()), x_below_one.clone()),
3697 ] {
3698 let norm = normalize_expression(&expr).expect("normalize");
3699 let ExpressionKind::Logical {
3700 connective: LogicalConnective::And,
3701 steps,
3702 ..
3703 } = norm.root_kind()
3704 else {
3705 panic!("expected one conjunction, got {:?}", norm.root_kind());
3706 };
3707 assert_eq!(steps.len(), 2, "expected three flat operands, got {norm:?}");
3708 }
3709 }
3710
3711 #[test]
3712 fn or_chain_lowers_to_one_flat_cell() {
3713 let chain = compose_expr(
3714 &[&bool_expr(false), &bool_expr(false), &bool_expr(true)],
3715 |ids| logical_kind(LogicalConnective::Or, ids[0], vec![ids[1], ids[2]]),
3716 );
3717 let mut interner = NormalFormInterner::new();
3718 let id = to_normal_form_empty(&chain, &mut interner);
3719 let NormalFormKind::Logical(LogicalConnective::Or, children) = &interner.get(id).kind
3720 else {
3721 panic!("expected Or(..), got {:?}", interner.get(id).kind);
3722 };
3723 let [first, second, third] = children.as_slice() else {
3724 panic!("expected three flat operands, got {children:?}");
3725 };
3726 for operand in [first, second, third] {
3727 assert!(matches!(
3728 &interner.get(*operand).kind,
3729 NormalFormKind::Leaf(LeafKind::Literal(_))
3730 ));
3731 }
3732 }
3733
3734 #[test]
3735 fn parenthesized_disjunction_flattens_into_parent() {
3736 let x_below_one = lt_expr(dx(), num_expr(1));
3737 for expr in [
3738 or_expr(dflag(), or_expr(dflag2(), x_below_one.clone())),
3739 or_expr(or_expr(dflag(), dflag2()), x_below_one.clone()),
3740 ] {
3741 let norm = normalize_expression(&expr).expect("normalize");
3742 let ExpressionKind::Logical {
3743 connective: LogicalConnective::Or,
3744 steps,
3745 ..
3746 } = norm.root_kind()
3747 else {
3748 panic!("expected one disjunction, got {:?}", norm.root_kind());
3749 };
3750 assert_eq!(steps.len(), 2, "expected three flat operands, got {norm:?}");
3751 }
3752 }
3753
3754 #[test]
3755 fn mixed_or_and_does_not_flatten() {
3756 let expr = or_expr(dflag(), and_expr(dflag2(), lt_expr(dx(), num_expr(1))));
3757 let norm = normalize_expression(&expr).expect("normalize");
3758 let ExpressionKind::Logical {
3759 connective: LogicalConnective::Or,
3760 first,
3761 steps,
3762 } = norm.root_kind()
3763 else {
3764 panic!("expected or root, got {:?}", norm.root_kind());
3765 };
3766 assert_eq!(steps.len(), 1);
3767 assert!(matches!(
3768 norm.at(*first).kind(),
3769 ExpressionKind::DataPath(ref p) if p.data == "flag"
3770 ));
3771 assert!(matches!(
3772 norm.at(steps[0].operand).kind(),
3773 ExpressionKind::Logical {
3774 connective: LogicalConnective::And,
3775 ..
3776 }
3777 ));
3778 }
3779
3780 #[test]
3781 fn true_or_data_absorbs_to_true() {
3782 let norm = normalize_expression(&or_expr(bool_expr(true), dflag())).expect("normalize");
3783 assert!(matches!(
3784 norm.root_kind(),
3785 ExpressionKind::Literal(ref literal)
3786 if matches!(literal.value, crate::planning::semantics::ValueKind::Boolean(true))
3787 ));
3788 }
3789
3790 #[test]
3791 fn false_or_data_folds_to_data() {
3792 let norm = normalize_expression(&or_expr(bool_expr(false), dflag())).expect("normalize");
3793 assert!(matches!(
3794 norm.root_kind(),
3795 ExpressionKind::DataPath(ref path) if path.data == "flag"
3796 ));
3797 }
3798
3799 #[test]
3800 fn identity_add_zero() {
3801 let expr = add_expr(dx(), num_expr(0));
3802 let norm = normalize_expression(&expr).expect("normalize");
3803 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3804 }
3805
3806 #[test]
3807 fn identity_mul_one() {
3808 let expr = mul_expr(dx(), num_expr(1));
3809 let norm = normalize_expression(&expr).expect("normalize");
3810 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3811 }
3812
3813 #[test]
3814 fn identity_mul_zero_with_data_path_is_not_folded() {
3815 let expr = mul_expr(dx(), num_expr(0));
3818 let norm = normalize_expression(&expr).expect("normalize");
3819 let Some((left, ArithmeticComputation::Multiply, right)) =
3820 binary_arithmetic(norm.root_kind())
3821 else {
3822 panic!("expected preserved multiply, got {:?}", norm.root_kind());
3823 };
3824 assert!(matches!(norm.at(*left).kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3825 assert!(matches!(
3826 norm.at(*right).kind(),
3827 ExpressionKind::Literal(ref l)
3828 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(0, 1))
3829 ));
3830 }
3831
3832 #[test]
3833 fn identity_mul_zero_with_literal_folds() {
3834 let expr = mul_expr(num_expr(7), num_expr(0));
3836 let norm = normalize_expression(&expr).expect("normalize");
3837 assert!(matches!(
3838 norm.root_kind(),
3839 ExpressionKind::Literal(ref l)
3840 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(0, 1))
3841 ));
3842 }
3843
3844 #[test]
3845 fn identity_pow_one_and_zero() {
3846 let p1 = normalize_expression(&pow_expr(dx(), num_expr(1))).expect("normalize");
3847 assert!(matches!(p1.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3848 let p0 = normalize_expression(&pow_expr(dx(), num_expr(0))).expect("normalize");
3851 let Some((base, ArithmeticComputation::Power, exp)) = binary_arithmetic(p0.root_kind())
3852 else {
3853 panic!("expected preserved power, got {:?}", p0.root_kind());
3854 };
3855 assert!(matches!(p0.at(*base).kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3856 assert!(matches!(
3857 p0.at(*exp).kind(),
3858 ExpressionKind::Literal(ref l)
3859 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(0, 1))
3860 ));
3861 let literal_pow_zero =
3863 normalize_expression(&pow_expr(num_expr(7), num_expr(0))).expect("normalize");
3864 assert!(matches!(
3865 literal_pow_zero.root_kind(),
3866 ExpressionKind::Literal(ref l)
3867 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(1, 1))
3868 ));
3869 }
3870
3871 #[test]
3872 fn double_logical_negation() {
3873 let norm = normalize_expression(¬_expr(not_expr(bool_expr(true)))).expect("normalize");
3874 assert!(matches!(
3875 norm.root_kind(),
3876 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(true))
3877 ));
3878 }
3879
3880 #[test]
3881 fn double_numeric_negation() {
3882 let zero = num_expr(0);
3883 let inner = sub_expr(zero.clone(), dx());
3884 let expr = sub_expr(zero, inner);
3885 let norm = normalize_expression(&expr).expect("normalize");
3886 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
3887 }
3888
3889 #[test]
3890 fn double_reciprocal_with_data_path_is_not_collapsed() {
3891 let one = num_expr(1);
3894 let inner = div_expr(one.clone(), dx());
3895 let expr = div_expr(one, inner);
3896 let norm = normalize_expression(&expr).expect("normalize");
3897 let Some((_, ArithmeticComputation::Divide, outer_denominator)) =
3898 binary_arithmetic(norm.root_kind())
3899 else {
3900 panic!(
3901 "expected preserved outer division, got {:?}",
3902 norm.root_kind()
3903 );
3904 };
3905 let Some((_, ArithmeticComputation::Divide, inner_denominator)) =
3906 binary_arithmetic(norm.at(*outer_denominator).kind())
3907 else {
3908 panic!(
3909 "expected preserved inner division, got {:?}",
3910 norm.at(*outer_denominator).kind()
3911 );
3912 };
3913 assert!(
3914 matches!(norm.at(*outer_denominator).child(*inner_denominator).kind(), ExpressionKind::DataPath(ref p) if p.data == "x")
3915 );
3916 }
3917
3918 #[test]
3919 fn sqrt_squared_numeric_folds_to_exact_rational() {
3920 let sqrt2 = math_expr(MathematicalComputation::Sqrt, num_expr(2));
3921 let expr = pow_expr(sqrt2, num_expr(2));
3922 let norm = normalize_expression(&expr).expect("normalize");
3923 assert!(matches!(
3924 norm.root_kind(),
3925 ExpressionKind::Literal(ref l)
3926 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(2, 1))
3927 ));
3928 }
3929
3930 #[test]
3932 fn literal_power_with_irrational_result_stays_power() {
3933 let half_lit = literal_from_folded_rational(rational_new(1, 2));
3934 let half = Ex::leaf(
3935 ExpressionKind::Literal(Box::new(TypedLiteral {
3936 value: half_lit.value,
3937 lemma_type: primitive_number_arc().clone(),
3938 })),
3939 None,
3940 );
3941 let expr = pow_expr(num_expr(2), half);
3942 let norm = normalize_expression(&expr).expect("normalize");
3943 assert!(
3944 matches!(
3945 binary_arithmetic(norm.root_kind()),
3946 Some((_, ArithmeticComputation::Power, _))
3947 ),
3948 "expected symbolic Power, got {:?}",
3949 norm.root_kind()
3950 );
3951 }
3952
3953 #[test]
3954 fn negated_conjunction_preserved_for_data_paths() {
3955 let expr = not_expr(and_expr(dflag(), dflag2()));
3959 let norm = normalize_expression(&expr).expect("normalize");
3960 let ExpressionKind::LogicalNegation(inner, _) = norm.root_kind() else {
3961 panic!("expected preserved negation, got {:?}", norm.root_kind());
3962 };
3963 assert!(
3964 matches!(
3965 norm.at(*inner).kind(),
3966 ExpressionKind::Logical {
3967 connective: LogicalConnective::And,
3968 ..
3969 }
3970 ),
3971 "expected preserved conjunction under the negation, got {:?}",
3972 norm.at(*inner).kind()
3973 );
3974 }
3975
3976 #[test]
3977 fn power_law_nested_power() {
3978 let expr = pow_expr(pow_expr(dx(), num_expr(2)), num_expr(3));
3979 let norm = normalize_expression(&expr).expect("normalize");
3980 assert!(matches!(
3981 binary_arithmetic(norm.root_kind()),
3982 Some((_, ArithmeticComputation::Power, _))
3983 ));
3984 let Some((base, ArithmeticComputation::Power, exp)) = binary_arithmetic(norm.root_kind())
3985 else {
3986 unreachable!();
3987 };
3988 assert!(matches!(
3989 norm.at(*base).kind(),
3990 ExpressionKind::DataPath(ref p) if p.data == "x"
3991 ));
3992 assert!(matches!(
3993 norm.at(*exp).kind(),
3994 ExpressionKind::Literal(ref l)
3995 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(6, 1))
3996 ));
3997 }
3998
3999 #[test]
4000 fn power_law_like_base_product() {
4001 let expr = mul_expr(pow_expr(dx(), num_expr(2)), pow_expr(dx(), num_expr(3)));
4002 let norm = normalize_expression(&expr).expect("normalize");
4003 let Some((base, ArithmeticComputation::Power, exp)) = binary_arithmetic(norm.root_kind())
4004 else {
4005 panic!("expected single power, got {:?}", norm.root_kind());
4006 };
4007 assert!(matches!(norm.at(*base).kind(), ExpressionKind::DataPath(ref p) if p.data == "x"));
4008 assert!(matches!(
4009 norm.at(*exp).kind(),
4010 ExpressionKind::Literal(ref l)
4011 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(5, 1))
4012 ));
4013 }
4014
4015 #[test]
4016 fn negated_comparison_not_less_than() {
4017 let cmp = lt_expr(dx(), num_expr(0));
4018 let norm = normalize_expression(¬_expr(cmp)).expect("normalize");
4019 let ExpressionKind::Comparison(_, op, _) = norm.root_kind() else {
4020 panic!("expected Comparison, got {:?}", norm.root_kind());
4021 };
4022 assert_eq!(*op, ComparisonComputation::GreaterThanOrEqual);
4023 }
4024
4025 #[test]
4026 fn logical_short_circuit_and_false_with_data_path_folds() {
4027 let expr = and_expr(bool_expr(false), dflag());
4030 let norm = normalize_expression(&expr).expect("normalize");
4031 assert!(matches!(
4032 norm.root_kind(),
4033 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(false))
4034 ));
4035 }
4036
4037 #[test]
4038 fn logical_short_circuit_and_false_all_literal_folds() {
4039 let expr = and_expr(bool_expr(false), bool_expr(true));
4040 let norm = normalize_expression(&expr).expect("normalize");
4041 assert!(matches!(
4042 norm.root_kind(),
4043 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(false))
4044 ));
4045 }
4046
4047 #[test]
4048 fn logical_and_false_on_right_with_data_path_folds() {
4049 let expr = and_expr(dflag(), bool_expr(false));
4050 let norm = normalize_expression(&expr).expect("normalize");
4051 assert!(matches!(
4052 norm.root_kind(),
4053 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Boolean(false))
4054 ));
4055 }
4056
4057 #[test]
4058 fn logical_idempotency_and_duplicate_paths() {
4059 let a = dflag();
4060 let expr = and_expr(a.clone(), a);
4061 let norm = normalize_expression(&expr).expect("normalize");
4062 assert!(matches!(norm.root_kind(), ExpressionKind::DataPath(ref p) if p.data == "flag"));
4063 }
4064
4065 #[test]
4066 fn unit_conversion_fold_number_to_number() {
4067 let inner = num_expr(42);
4068 let expr = compose_expr(&[&inner], |ids| {
4069 ExpressionKind::UnitConversion(
4070 ids[0],
4071 SemanticConversionTarget::Type(ConversionTypeKind::Number),
4072 )
4073 });
4074 let norm = normalize_expression(&expr).expect("normalize");
4075 assert!(matches!(
4076 norm.root_kind(),
4077 ExpressionKind::Literal(ref l)
4078 if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(42, 1))
4079 ));
4080 }
4081
4082 #[test]
4083 fn piecewise_collapse_last_true_unless_keeps_only_that_result() {
4084 let piecewise = unless_chain(num_expr(0), vec![(bool_expr(true), num_expr(42))]);
4085 let norm = normalize_expression(&piecewise).expect("normalize");
4086 assert!(matches!(
4087 norm.root_kind(),
4088 ExpressionKind::Literal(ref l)
4089 if matches!(l.value, ValueKind::Number(ref n) if n == &rational_new(42, 1))
4090 ));
4091 }
4092
4093 #[test]
4094 fn collapsed_unless_false_retains_piecewise_origin() {
4095 let piecewise = unless_chain(num_expr(1), vec![(bool_expr(false), num_expr(2))]).tree();
4096 let mut interner = NormalFormInterner::new();
4097 let completed_rules = HashMap::new();
4098 let rule_types = HashMap::new();
4099 let data = IndexMap::new();
4100 let unit_index = UnitIndex::new();
4101 let signature_index = SignatureIndex::new();
4102 let measure_scope = empty_measure_scope(&unit_index, &signature_index);
4103 let lower = LowerCtx {
4104 completed_rules: &completed_rules,
4105 rule_types: &rule_types,
4106 data: &data,
4107 row_templates: &EMPTY_ROW_TEMPLATES,
4108 self_identity: &TEST_SELF_IDENTITY,
4109 rule_order: &[],
4110 };
4111 let root = to_normal_form(
4112 &piecewise,
4113 &mut Cells::new(&mut interner, measure_scope),
4114 &lower,
4115 );
4116 let nf = rewrite::normalize(root, Cells::new(&mut interner, measure_scope), None)
4117 .expect("normalize");
4118 let cell = interner.get(nf);
4119 assert!(
4120 cell.origin.is_some(),
4121 "collapsed unless must keep piecewise origin, kind={:?}",
4122 cell.kind
4123 );
4124 let origin = cell.origin.expect("origin");
4125 assert!(
4126 matches!(interner.get(origin).kind, NormalFormKind::Piecewise(_)),
4127 "origin must be piecewise, got {:?}",
4128 interner.get(origin).kind
4129 );
4130 }
4131
4132 #[test]
4133 fn piecewise_collapse_drops_arm_with_statically_false_condition() {
4134 let piecewise = unless_chain(
4135 num_expr(7),
4136 vec![(lt_expr(num_expr(1), num_expr(0)), num_expr(99))],
4137 );
4138 let norm = normalize_expression(&piecewise).expect("normalize");
4139 assert!(matches!(
4140 norm.root_kind(),
4141 ExpressionKind::Literal(ref l)
4142 if matches!(l.value, ValueKind::Number(ref n) if n == &rational_new(7, 1))
4143 ));
4144 }
4145
4146 #[test]
4147 fn constant_fold_divide_of_literals_to_number() {
4148 let expr = div_expr(num_expr(5), num_expr(2));
4149 let norm = normalize_expression(&expr).expect("normalize");
4150 assert!(matches!(
4151 norm.root_kind(),
4152 ExpressionKind::Literal(ref l)
4153 if matches!(l.value, ValueKind::Number(ref n) if n == &rational_new(5, 2))
4154 ));
4155 }
4156
4157 #[test]
4158 fn constant_fold_divide_comparison_to_true() {
4159 let expr = lt_expr(div_expr(num_expr(5), num_expr(2)), num_expr(4));
4160 let norm = normalize_expression(&expr).expect("normalize");
4161 assert!(matches!(
4162 norm.root_kind(),
4163 ExpressionKind::Literal(ref l) if matches!(l.value, ValueKind::Boolean(true))
4164 ));
4165 }
4166
4167 #[test]
4168 fn constant_fold_nested_arithmetic_comparison_to_true() {
4169 let expr = lt_expr(
4171 div_expr(
4172 mul_expr(add_expr(num_expr(1), num_expr(2)), num_expr(3)),
4173 num_expr(4),
4174 ),
4175 num_expr(10),
4176 );
4177 let norm = normalize_expression(&expr).expect("normalize");
4178 assert!(matches!(
4179 norm.root_kind(),
4180 ExpressionKind::Literal(ref l) if matches!(l.value, ValueKind::Boolean(true))
4181 ));
4182 }
4183
4184 #[test]
4185 fn power_laws_sqrt_squared_is_not_merged_for_data_paths() {
4186 let x = Ex::leaf(
4187 ExpressionKind::DataPath(DataPath::new(vec![], "x".into())),
4188 None,
4189 );
4190 let expr = mul_expr(
4191 math_expr(MathematicalComputation::Sqrt, x.clone()),
4192 math_expr(MathematicalComputation::Sqrt, x),
4193 );
4194 let norm = normalize_expression(&expr).expect("normalize");
4195 let mut interner = NormalFormInterner::new();
4196 let id = lower_with_data(&norm, &mut interner, &algebraic_scope());
4197 assert!(
4198 matches!(&interner.get(id).kind, NormalFormKind::Product(ref children) if children.len() == 2),
4199 "sqrt(x)*sqrt(x) must stay a product of two powers, got {:?}",
4200 interner.get(id).kind
4201 );
4202 }
4203
4204 #[test]
4207 fn exp_log_not_folded_for_data_path() {
4208 let inner = math_expr(MathematicalComputation::Log, dx());
4211 let expr = math_expr(MathematicalComputation::Exp, inner);
4212 let norm = normalize_expression(&expr).expect("normalize");
4213 let ExpressionKind::MathematicalComputation(MathematicalComputation::Exp, inner) =
4214 norm.root_kind()
4215 else {
4216 panic!("expected preserved exp, got {:?}", norm.root_kind());
4217 };
4218 assert!(
4219 matches!(
4220 norm.at(*inner).kind(),
4221 ExpressionKind::MathematicalComputation(MathematicalComputation::Log, _)
4222 ),
4223 "expected preserved log under exp, got {:?}",
4224 norm.at(*inner).kind()
4225 );
4226 }
4227
4228 #[test]
4229
4230 fn constant_fold_add() {
4231 let expr = add_expr(num_expr(2), num_expr(3));
4232 let norm = normalize_expression(&expr).expect("normalize");
4233 assert!(matches!(
4234 norm.root_kind(),
4235 ExpressionKind::Literal(ref l) if matches!(l.value, crate::planning::semantics::ValueKind::Number(ref n) if n == &rational_new(5, 1))
4236 ));
4237 }
4238
4239 #[test]
4242 fn member_read_on_import_lowers_to_row_ends() {
4243 let mut engine = crate::Engine::new();
4244 engine
4245 .load([(
4246 crate::SourceType::Path(Arc::new(std::path::PathBuf::from("member.lemma"))),
4247 r#"
4248spec leaf
4249data x: 3
4250
4251spec mid
4252uses sub: leaf
4253
4254spec outer
4255uses i: mid
4256rule r: i.sub.x
4257"#
4258 .to_string(),
4259 )])
4260 .expect("load member fixture");
4261 let plan = engine
4262 .plans
4263 .get_plans(None, "outer")
4264 .and_then(|plans| plans.values().next())
4265 .expect("outer plan");
4266 let rule = plan
4267 .rules
4268 .get(&RulePath::new(Vec::new(), "r".to_string()))
4269 .expect("rule r");
4270 let NormalFormKind::SpecMember { base, row, .. } = &plan.normal_form(rule.normal_form).kind
4271 else {
4272 panic!(
4273 "`i.sub.x` lowers to a member read, got {:?}",
4274 plan.normal_form(rule.normal_form).kind
4275 );
4276 };
4277 assert!(matches!(
4278 &plan.normal_form(*base).kind,
4279 NormalFormKind::Leaf(LeafKind::DataPath(path)) if path.input_key() == "i"
4280 ));
4281 assert_eq!(
4282 row.ends,
4283 vec![
4284 RowEnd::Data(DataPath::local("sub".to_string())),
4285 RowEnd::Data(DataPath::local("x".to_string())),
4286 ]
4287 );
4288 assert!(plan.data.keys().all(|path| path.segments.is_empty()));
4289 }
4290}