typst_eval/
markup.rs

1use typst_library::diag::{warning, At, SourceResult};
2use typst_library::foundations::{
3    Content, Label, NativeElement, Repr, Smart, Symbol, Unlabellable, Value,
4};
5use typst_library::math::EquationElem;
6use typst_library::model::{
7    EmphElem, EnumItem, HeadingElem, LinkElem, ListItem, ParbreakElem, RefElem,
8    StrongElem, Supplement, TermItem, Url,
9};
10use typst_library::text::{
11    LinebreakElem, RawContent, RawElem, SmartQuoteElem, SpaceElem, TextElem,
12};
13use typst_syntax::ast::{self, AstNode};
14use typst_utils::PicoStr;
15
16use crate::{Eval, Vm};
17
18impl Eval for ast::Markup<'_> {
19    type Output = Content;
20
21    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
22        eval_markup(vm, &mut self.exprs())
23    }
24}
25
26/// Evaluate a stream of markup.
27fn eval_markup<'a>(
28    vm: &mut Vm,
29    exprs: &mut impl Iterator<Item = ast::Expr<'a>>,
30) -> SourceResult<Content> {
31    let flow = vm.flow.take();
32    let mut seq = Vec::with_capacity(exprs.size_hint().1.unwrap_or_default());
33
34    while let Some(expr) = exprs.next() {
35        match expr {
36            ast::Expr::Set(set) => {
37                let styles = set.eval(vm)?;
38                if vm.flow.is_some() {
39                    break;
40                }
41
42                seq.push(eval_markup(vm, exprs)?.styled_with_map(styles))
43            }
44            ast::Expr::Show(show) => {
45                let recipe = show.eval(vm)?;
46                if vm.flow.is_some() {
47                    break;
48                }
49
50                let tail = eval_markup(vm, exprs)?;
51                seq.push(tail.styled_with_recipe(&mut vm.engine, vm.context, recipe)?)
52            }
53            expr => match expr.eval(vm)? {
54                Value::Label(label) => {
55                    if let Some(elem) =
56                        seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>())
57                    {
58                        if elem.label().is_some() {
59                            vm.engine.sink.warn(warning!(
60                                elem.span(), "content labelled multiple times";
61                                hint: "only the last label is used, the rest are ignored",
62                            ));
63                        }
64
65                        *elem = std::mem::take(elem).labelled(label);
66                    } else {
67                        vm.engine.sink.warn(warning!(
68                            expr.span(),
69                            "label `{}` is not attached to anything",
70                            label.repr()
71                        ));
72                    }
73                }
74                value => seq.push(value.display().spanned(expr.span())),
75            },
76        }
77
78        if vm.flow.is_some() {
79            break;
80        }
81    }
82
83    if flow.is_some() {
84        vm.flow = flow;
85    }
86
87    Ok(Content::sequence(seq))
88}
89
90impl Eval for ast::Text<'_> {
91    type Output = Content;
92
93    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
94        Ok(TextElem::packed(self.get().clone()))
95    }
96}
97
98impl Eval for ast::Space<'_> {
99    type Output = Content;
100
101    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
102        Ok(SpaceElem::shared().clone())
103    }
104}
105
106impl Eval for ast::Linebreak<'_> {
107    type Output = Content;
108
109    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
110        Ok(LinebreakElem::shared().clone())
111    }
112}
113
114impl Eval for ast::Parbreak<'_> {
115    type Output = Content;
116
117    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
118        Ok(ParbreakElem::shared().clone())
119    }
120}
121
122impl Eval for ast::Escape<'_> {
123    type Output = Value;
124
125    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
126        Ok(Value::Symbol(Symbol::single(self.get())))
127    }
128}
129
130impl Eval for ast::Shorthand<'_> {
131    type Output = Value;
132
133    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
134        Ok(Value::Symbol(Symbol::single(self.get())))
135    }
136}
137
138impl Eval for ast::SmartQuote<'_> {
139    type Output = Content;
140
141    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
142        Ok(SmartQuoteElem::new().with_double(self.double()).pack())
143    }
144}
145
146impl Eval for ast::Strong<'_> {
147    type Output = Content;
148
149    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
150        let body = self.body();
151        if body.exprs().next().is_none() {
152            vm.engine
153                .sink
154                .warn(warning!(
155                    self.span(), "no text within stars";
156                    hint: "using multiple consecutive stars (e.g. **) has no additional effect",
157                ));
158        }
159
160        Ok(StrongElem::new(body.eval(vm)?).pack())
161    }
162}
163
164impl Eval for ast::Emph<'_> {
165    type Output = Content;
166
167    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
168        let body = self.body();
169        if body.exprs().next().is_none() {
170            vm.engine
171                .sink
172                .warn(warning!(
173                    self.span(), "no text within underscores";
174                    hint: "using multiple consecutive underscores (e.g. __) has no additional effect"
175                ));
176        }
177
178        Ok(EmphElem::new(body.eval(vm)?).pack())
179    }
180}
181
182impl Eval for ast::Raw<'_> {
183    type Output = Content;
184
185    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
186        let lines = self.lines().map(|line| (line.get().clone(), line.span())).collect();
187        let mut elem = RawElem::new(RawContent::Lines(lines)).with_block(self.block());
188        if let Some(lang) = self.lang() {
189            elem.push_lang(Some(lang.get().clone()));
190        }
191        Ok(elem.pack())
192    }
193}
194
195impl Eval for ast::Link<'_> {
196    type Output = Content;
197
198    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
199        let url = Url::new(self.get().clone()).at(self.span())?;
200        Ok(LinkElem::from_url(url).pack())
201    }
202}
203
204impl Eval for ast::Label<'_> {
205    type Output = Value;
206
207    fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
208        Ok(Value::Label(Label::new(PicoStr::intern(self.get()))))
209    }
210}
211
212impl Eval for ast::Ref<'_> {
213    type Output = Content;
214
215    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
216        let target = Label::new(PicoStr::intern(self.target()));
217        let mut elem = RefElem::new(target);
218        if let Some(supplement) = self.supplement() {
219            elem.push_supplement(Smart::Custom(Some(Supplement::Content(
220                supplement.eval(vm)?,
221            ))));
222        }
223        Ok(elem.pack())
224    }
225}
226
227impl Eval for ast::Heading<'_> {
228    type Output = Content;
229
230    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
231        let depth = self.depth();
232        let body = self.body().eval(vm)?;
233        Ok(HeadingElem::new(body).with_depth(depth).pack())
234    }
235}
236
237impl Eval for ast::ListItem<'_> {
238    type Output = Content;
239
240    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
241        Ok(ListItem::new(self.body().eval(vm)?).pack())
242    }
243}
244
245impl Eval for ast::EnumItem<'_> {
246    type Output = Content;
247
248    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
249        let body = self.body().eval(vm)?;
250        let mut elem = EnumItem::new(body);
251        if let Some(number) = self.number() {
252            elem.push_number(Some(number));
253        }
254        Ok(elem.pack())
255    }
256}
257
258impl Eval for ast::TermItem<'_> {
259    type Output = Content;
260
261    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
262        let term = self.term().eval(vm)?;
263        let description = self.description().eval(vm)?;
264        Ok(TermItem::new(term, description).pack())
265    }
266}
267
268impl Eval for ast::Equation<'_> {
269    type Output = Content;
270
271    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
272        let body = self.body().eval(vm)?;
273        let block = self.block();
274        Ok(EquationElem::new(body).with_block(block).pack())
275    }
276}