Skip to main content

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, EquationElem, 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::Equation<'_> {
13    type Output = Content;
14
15    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
16        let body = self.body().eval(vm)?;
17        let block = self.block();
18        Ok(EquationElem::new(body).with_block(block).pack())
19    }
20}
21
22impl Eval for ast::Math<'_> {
23    type Output = Content;
24
25    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
26        Ok(Content::sequence(
27            self.exprs()
28                .map(|expr| expr.eval_display(vm))
29                .collect::<SourceResult<Vec<_>>>()?,
30        ))
31    }
32}
33
34impl Eval for ast::MathText<'_> {
35    type Output = Content;
36
37    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
38        match self.get() {
39            MathTextKind::Grapheme(text) => Ok(SymbolElem::packed(text.clone())),
40            MathTextKind::Number(text) => Ok(TextElem::packed(text.clone())),
41        }
42    }
43}
44
45impl Eval for ast::MathIdent<'_> {
46    type Output = Value;
47
48    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
49        let span = self.span();
50        Ok(vm
51            .scopes
52            .get_in_math(&self)
53            .at(span)?
54            .read_checked((&mut vm.engine, span))
55            .clone())
56    }
57}
58
59impl Eval for ast::MathFieldAccess<'_> {
60    type Output = Value;
61
62    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
63        let target = self.target().eval(vm)?;
64        let field = self.field();
65        crate::code::access_field(vm, target, field.as_str(), field.span())
66    }
67}
68
69impl Eval for ast::MathAccess<'_> {
70    type Output = Value;
71
72    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
73        let value = match self {
74            ast::MathAccess::MathIdent(ident) => ident.eval(vm)?,
75            ast::MathAccess::MathFieldAccess(access) => access.eval(vm)?,
76        };
77        // We need to call `trace_at` for the value because we did not evaluate
78        // via `ast::Expr::eval()`.
79        vm.trace_at(self.span(), &value);
80        Ok(value)
81    }
82}
83
84impl Eval for ast::MathShorthand<'_> {
85    type Output = Value;
86
87    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
88        Ok(Value::Symbol(Symbol::runtime_char(self.get())))
89    }
90}
91
92impl Eval for ast::MathAlignPoint<'_> {
93    type Output = Content;
94
95    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
96        Ok(AlignPointElem::shared().clone())
97    }
98}
99
100impl Eval for ast::MathDelimited<'_> {
101    type Output = Content;
102
103    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
104        let open = self.open().eval_display(vm)?;
105        let body = self.body().eval(vm)?;
106        let close = self.close().eval_display(vm)?;
107        Ok(LrElem::new(open + body + close).pack())
108    }
109}
110
111impl Eval for ast::MathAttach<'_> {
112    type Output = Content;
113
114    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
115        let base = self.base().eval_display(vm)?;
116        let mut elem = AttachElem::new(base);
117
118        if let Some(expr) = self.top() {
119            elem.t.set(Some(expr.eval_display(vm)?));
120        }
121
122        // Always attach primes in scripts style (not limits style),
123        // i.e. at the top-right corner.
124        if let Some(primes) = self.primes() {
125            elem.tr.set(Some(primes.eval(vm)?));
126        }
127
128        if let Some(expr) = self.bottom() {
129            elem.b.set(Some(expr.eval_display(vm)?));
130        }
131
132        Ok(elem.pack())
133    }
134}
135
136impl Eval for ast::MathPrimes<'_> {
137    type Output = Content;
138
139    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
140        Ok(PrimesElem::new(self.count()).pack())
141    }
142}
143
144impl Eval for ast::MathFrac<'_> {
145    type Output = Content;
146
147    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
148        let num_expr = self.num();
149        let num = num_expr.eval_display(vm)?;
150        let denom_expr = self.denom();
151        let denom = denom_expr.eval_display(vm)?;
152
153        let num_depar =
154            matches!(num_expr, ast::Expr::Math(math) if math.was_deparenthesized());
155        let denom_depar =
156            matches!(denom_expr, ast::Expr::Math(math) if math.was_deparenthesized());
157
158        Ok(FracElem::new(num, denom)
159            .with_num_deparenthesized(num_depar)
160            .with_denom_deparenthesized(denom_depar)
161            .pack())
162    }
163}
164
165impl Eval for ast::MathRoot<'_> {
166    type Output = Content;
167
168    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
169        // Use `TextElem` to match `MathTextKind::Number` above.
170        let index = self.index().map(|i| TextElem::packed(eco_format!("{i}")));
171        let radicand = self.radicand().eval_display(vm)?;
172        Ok(RootElem::new(radicand).with_index(index).pack())
173    }
174}
175
176trait ExprExt {
177    fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content>;
178}
179
180impl ExprExt for ast::Expr<'_> {
181    fn eval_display(&self, vm: &mut Vm) -> SourceResult<Content> {
182        Ok(self.eval(vm)?.display().spanned(self.span()))
183    }
184}