Skip to main content

improv_engine/
typed.rs

1//! Typed expression layer: the formula AST enriched with inferred value type
2//! and dimension (the set of categories a subexpression ranges over).
3//!
4//! Produced by the compiler's first phase (`compiler::infer`). See
5//! AGENT_STEERING.md / IMPROV.txt "Formula compiler".
6
7use improv_core_model::{BinaryOp, CategoryId, FuncId, UnaryOp, Value, ValueType};
8
9/// The dimensionality of a (sub)expression: the categories it is indexed by,
10/// kept sorted and deduplicated (canonical form).
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct Dim {
13    pub categories: Vec<CategoryId>,
14}
15
16impl Dim {
17    pub fn scalar() -> Self {
18        Dim {
19            categories: Vec::new(),
20        }
21    }
22
23    pub fn of(mut categories: Vec<CategoryId>) -> Self {
24        categories.sort();
25        categories.dedup();
26        Dim { categories }
27    }
28
29    pub fn is_scalar(&self) -> bool {
30        self.categories.is_empty()
31    }
32
33    /// True if `self`'s categories are a subset of `other`'s (broadcastable
34    /// into `other`).
35    pub fn is_subset_of(&self, other: &Dim) -> bool {
36        self.categories.iter().all(|c| other.categories.contains(c))
37    }
38
39    /// Union of two dimensions (the result dim when broadcasting).
40    pub fn union(&self, other: &Dim) -> Dim {
41        let mut cats = self.categories.clone();
42        cats.extend(other.categories.iter().copied());
43        Dim::of(cats)
44    }
45}
46
47/// A value type paired with its dimension.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct TypeInfo {
50    pub value_type: ValueType,
51    pub dim: Dim,
52}
53
54/// The dimension-projection applied to a measure reference, resolved to the
55/// concrete category sets the compiler will keep / aggregate / drop.
56#[derive(Debug, Clone, PartialEq, Eq, Default)]
57pub struct ResolvedDimSpec {
58    pub by: Vec<CategoryId>,
59    pub over: Vec<CategoryId>,
60    pub except: Vec<CategoryId>,
61}
62
63#[derive(Debug, Clone, PartialEq)]
64pub enum TypedExprKind {
65    Literal(Value),
66    Ref(improv_core_model::MeasureId, ResolvedDimSpec),
67    UnaryOp(UnaryOp, Box<TypedExpr>),
68    BinaryOp(BinaryOp, Box<TypedExpr>, Box<TypedExpr>),
69    Call(FuncId, Vec<TypedExpr>),
70    /// A reduction over the `over` categories: `inputs` are the reduced
71    /// measures (same dimensions), `params` the other arguments (one value
72    /// per group), both in argument order.
73    Aggregate {
74        func: FuncId,
75        inputs: Vec<TypedExpr>,
76        params: Vec<TypedExpr>,
77        over: Vec<CategoryId>,
78        pred: Option<Box<TypedExpr>>,
79        window: Option<Window>,
80        /// An array function's landing categories (empty: not a list).
81        into: Vec<CategoryId>,
82    },
83    /// A per-item value of a category (`POSITION`, `@ITEM`): one cell per
84    /// item.
85    Items(CategoryId, Vec<(improv_core_model::ItemId, Value)>),
86    /// A fold (`SCAN`/`REDUCE`), already lowered: the whole formula.
87    Plan(Box<crate::plan::PlanNode>),
88    /// `COALESCE(a, …, default)` and `FILL(x, default)`: at each coordinate
89    /// of the domain, the first argument with a cell. The domain is `grid`
90    /// (every item combination, `FILL`) or else the cells of the arguments
91    /// that span the result (see `compiler::infer_coalesce`).
92    Coalesce {
93        args: Vec<TypedExpr>,
94        grid: Option<Vec<(CategoryId, Vec<improv_core_model::ItemId>)>>,
95    },
96    /// `RELABEL` / `SWAP`: `input` with each `(from, to, items)` category
97    /// renamed, its items mapped `(from item, to item)`; a cell whose item
98    /// has no mapping is dropped.
99    Remap {
100        input: Box<TypedExpr>,
101        maps: crate::plan::Remaps,
102    },
103    /// The value `offset` items along `cat` (`PREV` is −1), or `default`.
104    Shift {
105        input: Box<TypedExpr>,
106        cat: CategoryId,
107        offset: i64,
108        default: Option<Box<TypedExpr>>,
109    },
110}
111
112/// A window bound, relative to this item.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum Bound {
115    First,
116    Last,
117    /// This item's position plus the offset.
118    Offset(i64),
119}
120
121/// `OVER T[from..to]`: a reduction per item over a window of items.
122pub type Window = (Bound, Bound);
123
124#[derive(Debug, Clone, PartialEq)]
125pub struct TypedExpr {
126    pub kind: TypedExprKind,
127    pub ty: TypeInfo,
128}