typst_eval/
math.rs

1use ecow::eco_format;
2use typst_library::diag::{At, SourceResult};
3use typst_library::foundations::{Content, NativeElement, Symbol, SymbolElem, Value};
4use typst_library::math::{
5    AlignPointElem, AttachElem, FracElem, LrElem, PrimesElem, RootElem,
6};
7use typst_library::text::TextElem;
8use typst_syntax::ast::{self, AstNode, MathTextKind};
9
10use crate::{Eval, Vm};
11
12impl Eval for ast::Math<'_> {
13    type Output = Content;
14    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
15        Ok(Content::sequence(
16            self.exprs()
17                .map(|expr| expr.eval_display(vm))
18                .collect::<SourceResult<Vec<_>>>()?,
19        ))
20    }
21}
22
23impl Eval for ast::MathText<'_> {
24    type Output = Content;
25
26    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
27        match self.get() {
28            MathTextKind::Character(c) => Ok(SymbolElem::packed(c)),
29            MathTextKind::Number(text) => Ok(TextElem::packed(text.clone())),
30        }
31    }
32}
33
34impl Eval for ast::MathIdent<'_> {
35    type Output = Value;
36
37    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
38        let span = self.span();
39        Ok(vm
40            .scopes
41            .get_in_math(&self)
42            .at(span)?
43            .read_checked((&mut vm.engine, span))
44            .clone())
45    }
46}
47
48impl Eval for ast::MathShorthand<'_> {
49    type Output = Value;
50
51    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
52        Ok(Value::Symbol(Symbol::runtime_char(self.get())))
53    }
54}
55
56impl Eval for ast::MathAlignPoint<'_> {
57    type Output = Content;
58
59    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
60        Ok(AlignPointElem::shared().clone())
61    }
62}
63
64impl Eval for ast::MathDelimited<'_> {
65    type Output = Content;
66
67    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
68        let open = self.open().eval_display(vm)?;
69        let body = self.body().eval(vm)?;
70        let close = self.close().eval_display(vm)?;
71        Ok(LrElem::new(open + body + close).pack())
72    }
73}
74
75impl Eval for ast::MathAttach<'_> {
76    type Output = Content;
77
78    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
79        let base = self.base().eval_display(vm)?;
80        let mut elem = AttachElem::new(base);
81
82        if let Some(expr) = self.top() {
83            elem.t.set(Some(expr.eval_display(vm)?));
84        }
85
86        // Always attach primes in scripts style (not limits style),
87        // i.e. at the top-right corner.
88        if let Some(primes) = self.primes() {
89            elem.tr.set(Some(primes.eval(vm)?));
90        }
91
92        if let Some(expr) = self.bottom() {
93            elem.b.set(Some(expr.eval_display(vm)?));
94        }
95
96        Ok(elem.pack())
97    }
98}
99
100impl Eval for ast::MathPrimes<'_> {
101    type Output = Content;
102
103    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
104        Ok(PrimesElem::new(self.count()).pack())
105    }
106}
107
108impl Eval for ast::MathFrac<'_> {
109    type Output = Content;
110
111    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
112        let num_expr = self.num();
113        let num = num_expr.eval_display(vm)?;
114        let denom_expr = self.denom();
115        let denom = denom_expr.eval_display(vm)?;
116
117        let num_depar =
118            matches!(num_expr, ast::Expr::Math(math) if math.was_deparenthesized());
119        let denom_depar =
120            matches!(denom_expr, ast::Expr::Math(math) if math.was_deparenthesized());
121
122        Ok(FracElem::new(num, denom)
123            .with_num_deparenthesized(num_depar)
124            .with_denom_deparenthesized(denom_depar)
125            .pack())
126    }
127}
128
129impl Eval for ast::MathRoot<'_> {
130    type Output = Content;
131
132    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
133        // Use `TextElem` to match `MathTextKind::Number` above.
134        let index = self.index().map(|i| TextElem::packed(eco_format!("{i}")));
135        let radicand = self.radicand().eval_display(vm)?;
136        Ok(RootElem::new(radicand).with_index(index).pack())
137    }
138}
139
140trait ExprExt {
141    fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content>;
142}
143
144impl ExprExt for ast::Expr<'_> {
145    fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content> {
146        Ok(self.eval(vm)?.display().spanned(self.span()))
147    }
148}