typstyle_core/liteval/
mod.rs

1//! Evaluate simple constant Typst expressions in code mode without scopes or VMs.
2//!
3//! Currently, this is only used for determine table columns.
4
5use typst_syntax::ast::*;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum Value {
9    None,
10    Auto,
11    Int(i64),
12    /// Only represented by length.
13    Array(usize),
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum EvalError {
18    NotSupported,
19    InvalidOperation,
20}
21
22pub type EvalResult = Result<Value, EvalError>;
23
24pub trait Liteval {
25    fn liteval(&self) -> EvalResult;
26}
27
28impl Liteval for Expr<'_> {
29    fn liteval(&self) -> EvalResult {
30        match self {
31            Expr::None(v) => v.liteval(),
32            Expr::Auto(v) => v.liteval(),
33            Expr::Int(v) => v.liteval(),
34            Expr::Parenthesized(v) => v.liteval(),
35            Expr::Array(v) => v.liteval(),
36            Expr::Unary(v) => v.liteval(),
37            Expr::Binary(v) => v.liteval(),
38            _ => Err(EvalError::NotSupported),
39        }
40    }
41}
42
43impl Liteval for None<'_> {
44    fn liteval(&self) -> EvalResult {
45        Ok(Value::None)
46    }
47}
48
49impl Liteval for Auto<'_> {
50    fn liteval(&self) -> EvalResult {
51        Ok(Value::Auto)
52    }
53}
54
55impl Liteval for Int<'_> {
56    fn liteval(&self) -> EvalResult {
57        Ok(Value::Int(self.get()))
58    }
59}
60
61impl Liteval for Parenthesized<'_> {
62    fn liteval(&self) -> EvalResult {
63        self.expr().liteval()
64    }
65}
66
67impl Liteval for Array<'_> {
68    fn liteval(&self) -> EvalResult {
69        Ok(Value::Array(self.items().count()))
70    }
71}
72
73impl Liteval for Unary<'_> {
74    fn liteval(&self) -> EvalResult {
75        let expr = self.expr().liteval()?;
76        match self.op() {
77            UnOp::Pos => match expr {
78                Value::Int(i) => Ok(Value::Int(i)),
79                _ => Err(EvalError::InvalidOperation),
80            },
81            UnOp::Neg => match expr {
82                Value::Int(i) => Ok(Value::Int(-i)),
83                _ => Err(EvalError::InvalidOperation),
84            },
85            UnOp::Not => Err(EvalError::NotSupported),
86        }
87    }
88}
89impl Liteval for Binary<'_> {
90    fn liteval(&self) -> EvalResult {
91        let lhs = self.lhs().liteval()?;
92        let rhs = self.rhs().liteval()?;
93        match self.op() {
94            BinOp::Add => match (lhs, rhs) {
95                (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l + r)),
96                (Value::Array(l), Value::Array(r)) => Ok(Value::Array(l + r)),
97                _ => Err(EvalError::InvalidOperation),
98            },
99            BinOp::Sub => match (lhs, rhs) {
100                (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l - r)),
101                _ => Err(EvalError::InvalidOperation),
102            },
103            BinOp::Mul => match (lhs, rhs) {
104                (Value::Int(l), Value::Int(r)) => Ok(Value::Int(l * r)),
105                (Value::Array(l), Value::Int(r)) if r >= 0 => Ok(Value::Array(l * r as usize)),
106                (Value::Int(l), Value::Array(r)) if l >= 0 => Ok(Value::Array(l as usize * r)),
107                _ => Err(EvalError::InvalidOperation),
108            },
109            BinOp::Div => match (lhs, rhs) {
110                (Value::Int(l), Value::Int(r)) if r != 0 => Ok(Value::Int(l / r)),
111                _ => Err(EvalError::InvalidOperation),
112            },
113            _ => Err(EvalError::NotSupported),
114        }
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    fn test_liteval(code: &str, expected: Value) {
123        let root = typst_syntax::parse_code(code);
124        let expr = root.cast::<Code>().unwrap().exprs().next().unwrap();
125        assert_eq!(expr.liteval(), Ok(expected), "expr: {expr:#?}");
126    }
127
128    #[test]
129    fn test_simple_expr() {
130        use Value::*;
131
132        test_liteval("none", None);
133        test_liteval("auto", Auto);
134        test_liteval("0", Int(0));
135        test_liteval("1 + 2", Int(3));
136        test_liteval("1 * 2", Int(2));
137        test_liteval("1 - 2", Int(-1));
138        test_liteval("(1 + 2) * 3", Int(9));
139        test_liteval("(1fr,)", Array(1));
140        test_liteval("(1pt, 2em) * 3", Array(6));
141        test_liteval("(1, 2) + (3, 4, 5)", Array(5));
142        test_liteval("(1,) * 2 + 2 * (3, 4)", Array(6));
143        test_liteval("((1,) * 2 + 2 * (3,)) * 4", Array(16));
144    }
145}