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