summaryrefslogtreecommitdiff
path: root/crates/typst-eval/src/markup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-eval/src/markup.rs')
-rw-r--r--crates/typst-eval/src/markup.rs275
1 files changed, 275 insertions, 0 deletions
diff --git a/crates/typst-eval/src/markup.rs b/crates/typst-eval/src/markup.rs
new file mode 100644
index 00000000..e28eb9dd
--- /dev/null
+++ b/crates/typst-eval/src/markup.rs
@@ -0,0 +1,275 @@
+use typst_library::diag::{warning, At, SourceResult};
+use typst_library::foundations::{
+ Content, Label, NativeElement, Repr, Smart, Symbol, Unlabellable, Value,
+};
+use typst_library::math::EquationElem;
+use typst_library::model::{
+ EmphElem, EnumItem, HeadingElem, LinkElem, ListItem, ParbreakElem, RefElem,
+ StrongElem, Supplement, TermItem, Url,
+};
+use typst_library::text::{
+ LinebreakElem, RawContent, RawElem, SmartQuoteElem, SpaceElem, TextElem,
+};
+use typst_syntax::ast::{self, AstNode};
+
+use crate::{Eval, Vm};
+
+impl Eval for ast::Markup<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ eval_markup(vm, &mut self.exprs())
+ }
+}
+
+/// Evaluate a stream of markup.
+fn eval_markup<'a>(
+ vm: &mut Vm,
+ exprs: &mut impl Iterator<Item = ast::Expr<'a>>,
+) -> SourceResult<Content> {
+ let flow = vm.flow.take();
+ let mut seq = Vec::with_capacity(exprs.size_hint().1.unwrap_or_default());
+
+ while let Some(expr) = exprs.next() {
+ match expr {
+ ast::Expr::Set(set) => {
+ let styles = set.eval(vm)?;
+ if vm.flow.is_some() {
+ break;
+ }
+
+ seq.push(eval_markup(vm, exprs)?.styled_with_map(styles))
+ }
+ ast::Expr::Show(show) => {
+ let recipe = show.eval(vm)?;
+ if vm.flow.is_some() {
+ break;
+ }
+
+ let tail = eval_markup(vm, exprs)?;
+ seq.push(tail.styled_with_recipe(&mut vm.engine, vm.context, recipe)?)
+ }
+ expr => match expr.eval(vm)? {
+ Value::Label(label) => {
+ if let Some(elem) =
+ seq.iter_mut().rev().find(|node| !node.can::<dyn Unlabellable>())
+ {
+ if elem.label().is_some() {
+ vm.engine.sink.warn(warning!(
+ elem.span(), "content labelled multiple times";
+ hint: "only the last label is used, the rest are ignored",
+ ));
+ }
+
+ *elem = std::mem::take(elem).labelled(label);
+ } else {
+ vm.engine.sink.warn(warning!(
+ expr.span(),
+ "label `{}` is not attached to anything",
+ label.repr()
+ ));
+ }
+ }
+ value => seq.push(value.display().spanned(expr.span())),
+ },
+ }
+
+ if vm.flow.is_some() {
+ break;
+ }
+ }
+
+ if flow.is_some() {
+ vm.flow = flow;
+ }
+
+ Ok(Content::sequence(seq))
+}
+
+impl Eval for ast::Text<'_> {
+ type Output = Content;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(TextElem::packed(self.get().clone()))
+ }
+}
+
+impl Eval for ast::Space<'_> {
+ type Output = Content;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(SpaceElem::shared().clone())
+ }
+}
+
+impl Eval for ast::Linebreak<'_> {
+ type Output = Content;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(LinebreakElem::shared().clone())
+ }
+}
+
+impl Eval for ast::Parbreak<'_> {
+ type Output = Content;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(ParbreakElem::shared().clone())
+ }
+}
+
+impl Eval for ast::Escape<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Symbol(Symbol::single(self.get().into())))
+ }
+}
+
+impl Eval for ast::Shorthand<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Symbol(Symbol::single(self.get().into())))
+ }
+}
+
+impl Eval for ast::SmartQuote<'_> {
+ type Output = Content;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(SmartQuoteElem::new().with_double(self.double()).pack())
+ }
+}
+
+impl Eval for ast::Strong<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let body = self.body();
+ if body.exprs().next().is_none() {
+ vm.engine
+ .sink
+ .warn(warning!(
+ self.span(), "no text within stars";
+ hint: "using multiple consecutive stars (e.g. **) has no additional effect",
+ ));
+ }
+
+ Ok(StrongElem::new(body.eval(vm)?).pack())
+ }
+}
+
+impl Eval for ast::Emph<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let body = self.body();
+ if body.exprs().next().is_none() {
+ vm.engine
+ .sink
+ .warn(warning!(
+ self.span(), "no text within underscores";
+ hint: "using multiple consecutive underscores (e.g. __) has no additional effect"
+ ));
+ }
+
+ Ok(EmphElem::new(body.eval(vm)?).pack())
+ }
+}
+
+impl Eval for ast::Raw<'_> {
+ type Output = Content;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ let lines = self.lines().map(|line| (line.get().clone(), line.span())).collect();
+ let mut elem = RawElem::new(RawContent::Lines(lines)).with_block(self.block());
+ if let Some(lang) = self.lang() {
+ elem.push_lang(Some(lang.get().clone()));
+ }
+ Ok(elem.pack())
+ }
+}
+
+impl Eval for ast::Link<'_> {
+ type Output = Content;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ let url = Url::new(self.get().clone()).at(self.span())?;
+ Ok(LinkElem::from_url(url).pack())
+ }
+}
+
+impl Eval for ast::Label<'_> {
+ type Output = Value;
+
+ fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(Value::Label(Label::new(self.get())))
+ }
+}
+
+impl Eval for ast::Ref<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let target = Label::new(self.target());
+ let mut elem = RefElem::new(target);
+ if let Some(supplement) = self.supplement() {
+ elem.push_supplement(Smart::Custom(Some(Supplement::Content(
+ supplement.eval(vm)?,
+ ))));
+ }
+ Ok(elem.pack())
+ }
+}
+
+impl Eval for ast::Heading<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let depth = self.depth();
+ let body = self.body().eval(vm)?;
+ Ok(HeadingElem::new(body).with_depth(depth).pack())
+ }
+}
+
+impl Eval for ast::ListItem<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(ListItem::new(self.body().eval(vm)?).pack())
+ }
+}
+
+impl Eval for ast::EnumItem<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let body = self.body().eval(vm)?;
+ let mut elem = EnumItem::new(body);
+ if let Some(number) = self.number() {
+ elem.push_number(Some(number));
+ }
+ Ok(elem.pack())
+ }
+}
+
+impl Eval for ast::TermItem<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let term = self.term().eval(vm)?;
+ let description = self.description().eval(vm)?;
+ Ok(TermItem::new(term, description).pack())
+ }
+}
+
+impl Eval for ast::Equation<'_> {
+ type Output = Content;
+
+ fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let body = self.body().eval(vm)?;
+ let block = self.block();
+ Ok(EquationElem::new(body).with_block(block).pack())
+ }
+}