Skip to main content

qql_core/params/
formula.rs

1//! Formula parameter binding and constant conversion.
2
3use super::value::{bind_value, resolve_param, resolve_positional};
4use crate::ast::Value;
5use crate::ast::filter::FilterExpr;
6use crate::ast::formula::FormulaExpr;
7use crate::ast::looks_like_iso_datetime;
8use crate::error::{QqlError, Span};
9use alloc::format;
10
11/// Recursively bind parameters into a `FormulaExpr` in-place.
12pub fn bind_formula<F>(
13    formula: &mut FormulaExpr,
14    lookup: &F,
15    positional: &[Value],
16    bind_filter_fn: &impl Fn(&mut FilterExpr, &F, &[Value]) -> Result<(), QqlError>,
17) -> Result<(), QqlError>
18where
19    F: Fn(&str) -> Option<Value>,
20{
21    match formula {
22        FormulaExpr::Variable { name } => {
23            if let Some(param_name) = name.strip_prefix(':') {
24                let val = resolve_param(param_name, None, lookup)?;
25                *formula = value_to_formula_constant(val, None)?;
26            } else if let Some(idx_str) = name.strip_prefix('?')
27                && let Ok(idx) = idx_str.parse::<usize>()
28            {
29                let val = resolve_positional(idx, None, positional)?;
30                *formula = value_to_formula_constant(val, None)?;
31            }
32        }
33        FormulaExpr::Sum { left, right }
34        | FormulaExpr::Sub { left, right }
35        | FormulaExpr::Mul { left, right }
36        | FormulaExpr::Div { left, right, .. }
37        | FormulaExpr::Pow {
38            base: left,
39            exponent: right,
40        } => {
41            bind_formula(left, lookup, positional, bind_filter_fn)?;
42            bind_formula(right, lookup, positional, bind_filter_fn)?;
43        }
44        FormulaExpr::Neg { operand }
45        | FormulaExpr::Abs { x: operand }
46        | FormulaExpr::Sqrt { x: operand, .. }
47        | FormulaExpr::Log { x: operand, .. }
48        | FormulaExpr::Ln { x: operand, .. }
49        | FormulaExpr::Exp { x: operand }
50        | FormulaExpr::Acosh { x: operand, .. } => {
51            bind_formula(operand, lookup, positional, bind_filter_fn)?;
52        }
53        FormulaExpr::Max { args } | FormulaExpr::Min { args } => {
54            for arg in args {
55                bind_formula(arg, lookup, positional, bind_filter_fn)?;
56            }
57        }
58        FormulaExpr::Decay { x, target, .. } => {
59            bind_formula(x, lookup, positional, bind_filter_fn)?;
60            if let Some(t) = target {
61                bind_formula(t, lookup, positional, bind_filter_fn)?;
62            }
63        }
64        FormulaExpr::Case { cond, then_, else_ } => {
65            bind_filter_fn(cond, lookup, positional)?;
66            bind_formula(then_, lookup, positional, bind_filter_fn)?;
67            bind_formula(else_, lookup, positional, bind_filter_fn)?;
68        }
69        FormulaExpr::MatchCondition { values, .. } => {
70            for v in values {
71                bind_value(v, lookup, positional)?;
72            }
73        }
74        _ => {}
75    }
76    Ok(())
77}
78
79/// Convert a bound `Value` into a `FormulaExpr` constant, datetime, or variable.
80pub fn value_to_formula_constant(val: Value, span: Option<Span>) -> Result<FormulaExpr, QqlError> {
81    match val {
82        Value::Float(f) => Ok(FormulaExpr::Constant { value: f }),
83        Value::Int(i) => Ok(FormulaExpr::Constant { value: i as f64 }),
84        Value::Str(s) => {
85            if looks_like_iso_datetime(&s) {
86                Ok(FormulaExpr::Datetime { value: s })
87            } else if let Ok(f) = s.parse::<f64>() {
88                Ok(FormulaExpr::Constant { value: f })
89            } else {
90                Ok(FormulaExpr::Variable { name: s })
91            }
92        }
93        _ => Err(QqlError::validation(
94            "QQL-BIND-TYPE-MISMATCH",
95            format!("formula parameter cannot be bound to value: {:?}", val),
96            span,
97        )),
98    }
99}