refactor(formula): extract formula parser into separate crate

Extract the formula AST and parser into a dedicated `improvise-formula`
crate and convert the project into a Cargo workspace.

The root crate now re-exports `improvise-formula` as `crate::formula` to
maintain backward compatibility for internal callers. The repository map is
updated to reflect the new crate structure.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
This commit is contained in:
Edward Langley
2026-04-14 01:49:16 -07:00
parent 8b7b45587b
commit f02d905aac
8 changed files with 35 additions and 9 deletions
+77
View File
@@ -0,0 +1,77 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AggFunc {
Sum,
Avg,
Min,
Max,
Count,
}
/// Arithmetic and comparison operators used in binary expressions.
/// Having an enum (rather than a raw String) means the parser must
/// produce a valid operator; invalid operators are caught at parse
/// time rather than silently returning Empty at eval time.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
Pow,
Eq,
Ne,
Lt,
Gt,
Le,
Ge,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Filter {
pub category: String,
pub item: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Expr {
Number(f64),
Ref(String),
BinOp(BinOp, Box<Expr>, Box<Expr>),
UnaryMinus(Box<Expr>),
Agg(AggFunc, Box<Expr>, Option<Filter>),
If(Box<Expr>, Box<Expr>, Box<Expr>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Formula {
/// The raw formula text, e.g. "Profit = Revenue - Cost"
pub raw: String,
/// The item/dimension name this formula computes, e.g. "Profit"
pub target: String,
/// The category containing the target item
pub target_category: String,
/// The expression to evaluate
pub expr: Expr,
/// Optional WHERE filter
pub filter: Option<Filter>,
}
impl Formula {
pub fn new(
raw: impl Into<String>,
target: impl Into<String>,
target_category: impl Into<String>,
expr: Expr,
filter: Option<Filter>,
) -> Self {
Self {
raw: raw.into(),
target: target.into(),
target_category: target_category.into(),
expr,
filter,
}
}
}