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 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    IsVeto {
107        operand: Box<ShowExpression>,
108    },
109}
110
111/// Project a resolved planning expression into the Show tree.
112///
113/// `show_keys` are `RulePath::input_key` values for reachable Show rules. A
114/// `rule` leaf whose key is missing from that set is a bug.
115pub fn show_expression_from(
116    expression: &Expression,
117    show_keys: &HashSet<String>,
118) -> ShowExpression {
119    match &expression.kind {
120        ExpressionKind::Literal(typed) => ShowExpression::Literal(Box::new(typed.as_ref().clone())),
121        ExpressionKind::DataPath(path) => ShowExpression::Data {
122            name: path.input_key(),
123        },
124        ExpressionKind::RulePath(path) => {
125            let key = path.input_key();
126            if !show_keys.contains(&key) {
127                panic!("BUG: rule leaf '{key}' not in Show.rules keys");
128            }
129            ShowExpression::Rule { name: key }
130        }
131        ExpressionKind::LogicalAnd(left, right) => ShowExpression::And {
132            left: Box::new(show_expression_from(left, show_keys)),
133            right: Box::new(show_expression_from(right, show_keys)),
134        },
135        ExpressionKind::LogicalNegation(operand, _) => ShowExpression::Not {
136            operand: Box::new(show_expression_from(operand, show_keys)),
137        },
138        ExpressionKind::Arithmetic(left, op, right) => ShowExpression::Arithmetic {
139            op: op.clone(),
140            left: Box::new(show_expression_from(left, show_keys)),
141            right: Box::new(show_expression_from(right, show_keys)),
142        },
143        ExpressionKind::Comparison(left, op, right) => ShowExpression::Comparison {
144            op: op.clone(),
145            left: Box::new(show_expression_from(left, show_keys)),
146            right: Box::new(show_expression_from(right, show_keys)),
147        },
148        ExpressionKind::UnitConversion(operand, target) => ShowExpression::UnitConversion {
149            operand: Box::new(show_expression_from(operand, show_keys)),
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, show_keys)),
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, show_keys)),
163        },
164        ExpressionKind::DateCalendar(kind, unit, operand) => ShowExpression::DateCalendar {
165            kind: *kind,
166            unit: *unit,
167            operand: Box::new(show_expression_from(operand, show_keys)),
168        },
169        ExpressionKind::RangeLiteral(from, to) => ShowExpression::RangeLiteral {
170            from: Box::new(show_expression_from(from, show_keys)),
171            to: Box::new(show_expression_from(to, show_keys)),
172        },
173        ExpressionKind::PastFutureRange(kind, operand) => ShowExpression::PastFutureRange {
174            kind: *kind,
175            operand: Box::new(show_expression_from(operand, show_keys)),
176        },
177        ExpressionKind::RangeContainment(value, range) => ShowExpression::RangeContainment {
178            value: Box::new(show_expression_from(value, show_keys)),
179            range: Box::new(show_expression_from(range, show_keys)),
180        },
181        ExpressionKind::ResultIsVeto(operand) => ShowExpression::IsVeto {
182            operand: Box::new(show_expression_from(operand, show_keys)),
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    show_keys: &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, show_keys)),
201            result: show_expression_from(result, show_keys),
202        })
203        .collect()
204}
205
206/// Project `RuleNode.depends_on_rules` to Show.rules keys (BTreeSet order).
207pub fn project_depends_on_rules(
208    deps: &std::collections::BTreeSet<crate::planning::semantics::RulePath>,
209    show_keys: &HashSet<String>,
210) -> Vec<String> {
211    let mut out = Vec::new();
212    for path in deps {
213        let key = path.input_key();
214        if !show_keys.contains(&key) {
215            panic!("BUG: depends_on_rules entry '{key}' missing from Show.rules keys");
216        }
217        out.push(key);
218    }
219    out
220}
221
222fn conversion_target_from(target: &SemanticConversionTarget) -> ShowConversionTarget {
223    match target {
224        SemanticConversionTarget::Type(kind) => ShowConversionTarget::Type(*kind),
225        SemanticConversionTarget::Unit { unit_name, .. } => ShowConversionTarget::Unit {
226            unit_name: unit_name.clone(),
227        },
228    }
229}