Skip to main content

lemma/planning/
show_expression.rs

1//! Show rule graph: resolved branch expressions projected for [`crate::Engine::show`].
2//!
3//! Built once from [`crate::planning::graph::RuleNode`] branches during plan build.
4//! Not NormalForm and not evaluation narration.
5
6use crate::parsing::ast::{
7    ArithmeticComputation, CalendarPeriodUnit, ComparisonComputation, DateCalendarKind,
8    DateRelativeKind, MathematicalComputation, PrimitiveKind, VetoExpression,
9};
10use crate::planning::semantics::{
11    Expression, ExpressionKind, SemanticConversionTarget, TypedLiteral,
12};
13use crate::result_value::{type_scoped_result_value_from_literal, RuleResultValue};
14use serde::{Deserialize, Serialize};
15use std::collections::HashSet;
16
17/// One arm of a rule's flat last-match table on Show.
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct ShowBranch {
20    /// Absent on the default arm; present on each `unless`.
21    pub condition: Option<ShowExpression>,
22    pub result: ShowExpression,
23}
24
25/// Local rule on Show: result type, authored branches, stored topo deps.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct ShowRule {
28    pub lemma_type: crate::planning::semantics::LemmaType,
29    pub branches: Vec<ShowBranch>,
30    /// Local rule names this rule depends on (planning topo). Always present.
31    pub depends_on_rules: Vec<String>,
32}
33
34/// Conversion target on a Show `as` expression (no owning type — inspection only).
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum ShowConversionTarget {
38    Type(PrimitiveKind),
39    Unit { unit_name: String },
40}
41
42/// Resolved expression tree for Show branches (`tag = "type"` on the API mirror).
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub enum ShowExpression {
45    Literal(Box<RuleResultValue>),
46    Data {
47        name: String,
48    },
49    Rule {
50        name: String,
51    },
52    And {
53        left: Box<ShowExpression>,
54        right: Box<ShowExpression>,
55    },
56    Not {
57        operand: Box<ShowExpression>,
58    },
59    Arithmetic {
60        op: ArithmeticComputation,
61        left: Box<ShowExpression>,
62        right: Box<ShowExpression>,
63    },
64    Comparison {
65        op: ComparisonComputation,
66        left: Box<ShowExpression>,
67        right: Box<ShowExpression>,
68    },
69    UnitConversion {
70        operand: Box<ShowExpression>,
71        target: ShowConversionTarget,
72    },
73    Math {
74        op: MathematicalComputation,
75        operand: Box<ShowExpression>,
76    },
77    Veto {
78        message: Option<String>,
79    },
80    Now,
81    DateRelative {
82        kind: DateRelativeKind,
83        operand: Box<ShowExpression>,
84    },
85    DateCalendar {
86        kind: DateCalendarKind,
87        unit: CalendarPeriodUnit,
88        operand: Box<ShowExpression>,
89    },
90    RangeLiteral {
91        from: Box<ShowExpression>,
92        to: Box<ShowExpression>,
93    },
94    PastFutureRange {
95        kind: DateRelativeKind,
96        operand: Box<ShowExpression>,
97    },
98    RangeContainment {
99        value: Box<ShowExpression>,
100        range: Box<ShowExpression>,
101    },
102    IsVeto {
103        operand: Box<ShowExpression>,
104    },
105}
106
107/// Project a resolved planning expression into the Show tree.
108///
109/// `local_rule_names` are local `RulePath.rule` values present on the plan. A local
110/// `rule` leaf missing from that set is a bug.
111pub fn show_expression_from(
112    expression: &Expression,
113    local_rule_names: &HashSet<String>,
114) -> ShowExpression {
115    match &expression.kind {
116        ExpressionKind::Literal(typed) => ShowExpression::Literal(Box::new(literal_to_show(typed))),
117        ExpressionKind::DataPath(path) => ShowExpression::Data {
118            name: path.input_key(),
119        },
120        ExpressionKind::RulePath(path) => {
121            if path.segments.is_empty() && !local_rule_names.contains(&path.rule) {
122                panic!(
123                    "BUG: local rule leaf '{}' not in plan local rules",
124                    path.rule
125                );
126            }
127            ShowExpression::Rule {
128                name: path.to_string(),
129            }
130        }
131        ExpressionKind::LogicalAnd(left, right) => ShowExpression::And {
132            left: Box::new(show_expression_from(left, local_rule_names)),
133            right: Box::new(show_expression_from(right, local_rule_names)),
134        },
135        ExpressionKind::LogicalNegation(operand, _) => ShowExpression::Not {
136            operand: Box::new(show_expression_from(operand, local_rule_names)),
137        },
138        ExpressionKind::Arithmetic(left, op, right) => ShowExpression::Arithmetic {
139            op: op.clone(),
140            left: Box::new(show_expression_from(left, local_rule_names)),
141            right: Box::new(show_expression_from(right, local_rule_names)),
142        },
143        ExpressionKind::Comparison(left, op, right) => ShowExpression::Comparison {
144            op: op.clone(),
145            left: Box::new(show_expression_from(left, local_rule_names)),
146            right: Box::new(show_expression_from(right, local_rule_names)),
147        },
148        ExpressionKind::UnitConversion(operand, target) => ShowExpression::UnitConversion {
149            operand: Box::new(show_expression_from(operand, local_rule_names)),
150            target: conversion_target_from(target),
151        },
152        ExpressionKind::MathematicalComputation(op, operand) => ShowExpression::Math {
153            op: op.clone(),
154            operand: Box::new(show_expression_from(operand, local_rule_names)),
155        },
156        ExpressionKind::Veto(VetoExpression { message }) => ShowExpression::Veto {
157            message: message.clone(),
158        },
159        ExpressionKind::Now => ShowExpression::Now,
160        ExpressionKind::DateRelative(kind, operand) => ShowExpression::DateRelative {
161            kind: *kind,
162            operand: Box::new(show_expression_from(operand, local_rule_names)),
163        },
164        ExpressionKind::DateCalendar(kind, unit, operand) => ShowExpression::DateCalendar {
165            kind: *kind,
166            unit: *unit,
167            operand: Box::new(show_expression_from(operand, local_rule_names)),
168        },
169        ExpressionKind::RangeLiteral(from, to) => ShowExpression::RangeLiteral {
170            from: Box::new(show_expression_from(from, local_rule_names)),
171            to: Box::new(show_expression_from(to, local_rule_names)),
172        },
173        ExpressionKind::PastFutureRange(kind, operand) => ShowExpression::PastFutureRange {
174            kind: *kind,
175            operand: Box::new(show_expression_from(operand, local_rule_names)),
176        },
177        ExpressionKind::RangeContainment(value, range) => ShowExpression::RangeContainment {
178            value: Box::new(show_expression_from(value, local_rule_names)),
179            range: Box::new(show_expression_from(range, local_rule_names)),
180        },
181        ExpressionKind::ResultIsVeto(operand) => ShowExpression::IsVeto {
182            operand: Box::new(show_expression_from(operand, local_rule_names)),
183        },
184        ExpressionKind::Piecewise(_) => {
185            panic!("BUG: piecewise inside a rule branch")
186        }
187    }
188}
189
190/// Project `RuleNode.branches` into Show arms (default first, no synthetic true).
191pub fn show_branches_from(
192    branches: &[(Option<Expression>, Expression)],
193    local_rule_names: &HashSet<String>,
194) -> Vec<ShowBranch> {
195    branches
196        .iter()
197        .map(|(condition, result)| ShowBranch {
198            condition: condition
199                .as_ref()
200                .map(|expression| show_expression_from(expression, local_rule_names)),
201            result: show_expression_from(result, local_rule_names),
202        })
203        .collect()
204}
205
206/// Local names from `RuleNode.depends_on_rules` (BTreeSet order). Imported paths omitted.
207pub fn project_depends_on_rules(
208    deps: &std::collections::BTreeSet<crate::planning::semantics::RulePath>,
209    local_rule_names: &HashSet<String>,
210) -> Vec<String> {
211    let mut out = Vec::new();
212    for path in deps {
213        if !path.segments.is_empty() {
214            continue;
215        }
216        if !local_rule_names.contains(&path.rule) {
217            panic!(
218                "BUG: local depends_on_rules entry '{}' missing from plan local rules",
219                path.rule
220            );
221        }
222        out.push(path.rule.clone());
223    }
224    out
225}
226
227fn literal_to_show(typed: &TypedLiteral) -> RuleResultValue {
228    type_scoped_result_value_from_literal(&typed.to_literal(), typed.lemma_type.as_ref())
229        .unwrap_or_else(|failure| {
230            panic!(
231                "BUG: show branch literal failed type_scoped_result_value_from_literal: {}",
232                crate::result_value::rule_result_value_failure_message(failure)
233            )
234        })
235}
236
237fn conversion_target_from(target: &SemanticConversionTarget) -> ShowConversionTarget {
238    match target {
239        SemanticConversionTarget::Type(kind) => ShowConversionTarget::Type(*kind),
240        SemanticConversionTarget::Unit { unit_name, .. } => ShowConversionTarget::Unit {
241            unit_name: unit_name.clone(),
242        },
243    }
244}