summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/lib.rs6
-rw-r--r--library/src/math/accent.rs4
-rw-r--r--library/src/math/root.rs2
-rw-r--r--src/ide/highlight.rs128
-rw-r--r--src/model/eval.rs291
-rw-r--r--src/model/func.rs16
-rw-r--r--src/model/library.rs14
-rw-r--r--src/syntax/ast.rs292
-rw-r--r--src/syntax/kind.rs37
-rw-r--r--src/syntax/lexer.rs21
-rw-r--r--src/syntax/node.rs29
-rw-r--r--src/syntax/parser.rs222
-rw-r--r--src/syntax/reparser.rs68
-rw-r--r--tests/typ/basics/heading.typ4
-rw-r--r--tests/typ/basics/list.typ2
-rw-r--r--tests/typ/compiler/array.typ109
-rw-r--r--tests/typ/compiler/bench.typ2
-rw-r--r--tests/typ/compiler/block.typ52
-rw-r--r--tests/typ/compiler/break-continue.typ4
-rw-r--r--tests/typ/compiler/call.typ22
-rw-r--r--tests/typ/compiler/closure.typ28
-rw-r--r--tests/typ/compiler/construct.typ2
-rw-r--r--tests/typ/compiler/dict.typ42
-rw-r--r--tests/typ/compiler/field.typ16
-rw-r--r--tests/typ/compiler/for.typ8
-rw-r--r--tests/typ/compiler/if.typ10
-rw-r--r--tests/typ/compiler/import.typ4
-rw-r--r--tests/typ/compiler/include.typ4
-rw-r--r--tests/typ/compiler/label.typ4
-rw-r--r--tests/typ/compiler/let.typ4
-rw-r--r--tests/typ/compiler/methods.typ20
-rw-r--r--tests/typ/compiler/ops-invalid.typ108
-rw-r--r--tests/typ/compiler/ops-prec.typ14
-rw-r--r--tests/typ/compiler/ops.typ24
-rw-r--r--tests/typ/compiler/repr.typ44
-rw-r--r--tests/typ/compiler/set.typ12
-rw-r--r--tests/typ/compiler/shorthand.typ2
-rw-r--r--tests/typ/compiler/show-bare.typ6
-rw-r--r--tests/typ/compiler/show-node.typ8
-rw-r--r--tests/typ/compiler/spread.typ22
-rw-r--r--tests/typ/compiler/string.typ16
-rw-r--r--tests/typ/compiler/while.typ10
-rw-r--r--tests/typ/compute/foundations.typ8
-rw-r--r--tests/typ/compute/utility.typ2
-rw-r--r--tests/typ/layout/page-margin.typ12
-rw-r--r--tests/typ/layout/page.typ6
-rw-r--r--tests/typ/layout/pagebreak.typ4
-rw-r--r--tests/typ/layout/par-bidi.typ2
-rw-r--r--tests/typ/layout/par-justify.typ2
-rw-r--r--tests/typ/layout/repeat.typ2
-rw-r--r--tests/typ/layout/transform.typ4
-rw-r--r--tests/typ/math/style.typ8
-rw-r--r--tests/typ/meta/document.typ8
-rw-r--r--tests/typ/meta/outline.typ2
-rw-r--r--tests/typ/text/em.typ4
-rw-r--r--tests/typ/text/emphasis.typ10
-rw-r--r--tests/typ/text/font.typ2
-rw-r--r--tests/typ/text/quotes.typ2
-rw-r--r--tests/typ/text/space.typ8
-rw-r--r--tests/typ/visualize/line.typ8
-rw-r--r--tests/typ/visualize/shape-fill-stroke.typ2
-rw-r--r--tests/typ/visualize/shape-rect.typ12
-rw-r--r--tools/support/typst.tmLanguage.json38
63 files changed, 1002 insertions, 877 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs
index c4b6710f..bdb2cc90 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -189,11 +189,11 @@ fn items() -> LangItems {
term_item: |term, description| {
layout::ListItem::Term(basics::TermItem { term, description }).pack()
},
- math_formula: |body, block| math::FormulaNode { body, block }.pack(),
+ formula: |body, block| math::FormulaNode { body, block }.pack(),
math_atom: |atom| math::AtomNode(atom).pack(),
- math_delimited: |body| math::LrNode(body).pack(),
+ math_align_point: || math::AlignPointNode.pack(),
+ math_delimited: |open, body, close| math::LrNode(open + body + close).pack(),
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
- math_align_point: || math::AlignPointNode.pack(),
}
}
diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs
index a15a6020..6829554c 100644
--- a/library/src/math/accent.rs
+++ b/library/src/math/accent.rs
@@ -133,7 +133,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
/// Extract a single character from content.
fn extract(accent: &Content) -> Option<char> {
- let atom = accent.to::<FormulaNode>()?.body.to::<AtomNode>()?;
+ let atom = accent.to::<AtomNode>()?;
let mut chars = atom.0.chars();
let c = chars.next().filter(|_| chars.next().is_none())?;
Some(combining(c))
@@ -166,6 +166,8 @@ fn combining(c: char) -> char {
'\u{2190}' => '\u{20d6}',
'\u{2192}' => '\u{20d7}',
'\u{2212}' => '\u{0305}',
+ '\u{223C}' => '\u{0303}',
+ '\u{22C5}' => '\u{0307}',
'\u{27f6}' => '\u{20d7}',
_ => c,
}
diff --git a/library/src/math/root.rs b/library/src/math/root.rs
index 10573066..bae3bbb6 100644
--- a/library/src/math/root.rs
+++ b/library/src/math/root.rs
@@ -155,7 +155,7 @@ fn layout(
/// Select a precomposed radical, if the font has it.
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> {
- let node = index?.to::<FormulaNode>()?.body.to::<AtomNode>()?;
+ let node = index?.to::<AtomNode>()?;
let c = match node.0.as_str() {
"3" => '∛',
"4" => '∜',
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs
index 75201d93..b1df94d1 100644
--- a/src/ide/highlight.rs
+++ b/src/ide/highlight.rs
@@ -7,7 +7,7 @@ pub enum Category {
Comment,
/// Punctuation in code.
Punctuation,
- /// An escape sequence, shorthand or symbol notation.
+ /// An escape sequence or shorthand.
Escape,
/// Strong markup.
Strong,
@@ -97,7 +97,6 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Parbreak => None,
SyntaxKind::Escape => Some(Category::Escape),
SyntaxKind::Shorthand => Some(Category::Escape),
- SyntaxKind::Symbol => Some(Category::Escape),
SyntaxKind::SmartQuote => None,
SyntaxKind::Strong => Some(Category::Strong),
SyntaxKind::Emph => Some(Category::Emph),
@@ -113,12 +112,22 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::EnumMarker => Some(Category::ListMarker),
SyntaxKind::TermItem => None,
SyntaxKind::TermMarker => Some(Category::ListMarker),
+ SyntaxKind::Formula => None,
+
SyntaxKind::Math => None,
- SyntaxKind::Atom => None,
- SyntaxKind::Delimited => None,
- SyntaxKind::Script => None,
- SyntaxKind::Frac => None,
- SyntaxKind::AlignPoint => Some(Category::MathOperator),
+ SyntaxKind::MathAtom => None,
+ SyntaxKind::MathIdent => highlight_ident(node),
+ SyntaxKind::MathDelimited => None,
+ SyntaxKind::MathScript => None,
+ SyntaxKind::MathFrac => None,
+ SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
+
+ SyntaxKind::Hashtag if node.before_error() => None,
+ SyntaxKind::Hashtag => node
+ .next_leaf()
+ .filter(|node| node.kind() != SyntaxKind::Dollar)
+ .as_ref()
+ .and_then(highlight),
SyntaxKind::LeftBrace => Some(Category::Punctuation),
SyntaxKind::RightBrace => Some(Category::Punctuation),
@@ -134,14 +143,14 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
_ => Some(Category::Operator),
},
SyntaxKind::Underscore => match node.parent_kind() {
- Some(SyntaxKind::Script) => Some(Category::MathOperator),
+ Some(SyntaxKind::MathScript) => Some(Category::MathOperator),
_ => None,
},
SyntaxKind::Dollar => Some(Category::MathDelimiter),
SyntaxKind::Plus => Some(Category::Operator),
SyntaxKind::Minus => Some(Category::Operator),
SyntaxKind::Slash => Some(match node.parent_kind() {
- Some(SyntaxKind::Frac) => Category::MathOperator,
+ Some(SyntaxKind::MathFrac) => Category::MathOperator,
_ => Category::Operator,
}),
SyntaxKind::Hat => Some(Category::MathOperator),
@@ -183,47 +192,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Include => Some(Category::Keyword),
SyntaxKind::As => Some(Category::Keyword),
- SyntaxKind::Ident => match node.parent_kind() {
- Some(
- SyntaxKind::Markup
- | SyntaxKind::Math
- | SyntaxKind::Script
- | SyntaxKind::Frac,
- ) => Some(Category::Interpolated),
- Some(SyntaxKind::FuncCall) => Some(Category::Function),
- Some(SyntaxKind::FieldAccess)
- if node.parent().and_then(|p| p.parent_kind())
- == Some(SyntaxKind::SetRule)
- && node.next_sibling().is_none() =>
- {
- Some(Category::Function)
- }
- Some(SyntaxKind::FieldAccess)
- if node
- .parent()
- .and_then(|p| p.parent())
- .filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
- .and_then(|gp| gp.parent())
- .map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
- && node.next_sibling().is_none() =>
- {
- Some(Category::Function)
- }
- Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
- Some(Category::Function)
- }
- Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => {
- Some(Category::Function)
- }
- Some(SyntaxKind::SetRule) => Some(Category::Function),
- Some(SyntaxKind::ShowRule)
- if node.prev_sibling().as_ref().map(|v| v.kind())
- == Some(SyntaxKind::Show) =>
- {
- Some(Category::Function)
- }
- _ => None,
- },
+ SyntaxKind::Code => None,
+ SyntaxKind::Ident => highlight_ident(node),
SyntaxKind::Bool => Some(Category::Keyword),
SyntaxKind::Int => Some(Category::Number),
SyntaxKind::Float => Some(Category::Number),
@@ -238,7 +208,11 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Keyed => None,
SyntaxKind::Unary => None,
SyntaxKind::Binary => None,
- SyntaxKind::FieldAccess => None,
+ SyntaxKind::FieldAccess => match node.parent_kind() {
+ Some(SyntaxKind::Markup | SyntaxKind::Math) => Some(Category::Interpolated),
+ Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
+ _ => None,
+ },
SyntaxKind::FuncCall => None,
SyntaxKind::MethodCall => None,
SyntaxKind::Args => None,
@@ -266,6 +240,52 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
}
}
+/// Highlight an identifier based on context.
+fn highlight_ident(node: &LinkedNode) -> Option<Category> {
+ match node.parent_kind() {
+ Some(
+ SyntaxKind::Markup
+ | SyntaxKind::Math
+ | SyntaxKind::MathFrac
+ | SyntaxKind::MathScript,
+ ) => Some(Category::Interpolated),
+ Some(SyntaxKind::FuncCall) => Some(Category::Function),
+ Some(SyntaxKind::FieldAccess)
+ if node.parent().and_then(|p| p.parent_kind())
+ == Some(SyntaxKind::SetRule)
+ && node.next_sibling().is_none() =>
+ {
+ Some(Category::Function)
+ }
+ Some(SyntaxKind::FieldAccess)
+ if node
+ .parent()
+ .and_then(|p| p.parent())
+ .filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
+ .and_then(|gp| gp.parent())
+ .map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
+ && node.next_sibling().is_none() =>
+ {
+ Some(Category::Function)
+ }
+ Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
+ Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
+ Some(Category::Function)
+ }
+ Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => {
+ Some(Category::Function)
+ }
+ Some(SyntaxKind::SetRule) => Some(Category::Function),
+ Some(SyntaxKind::ShowRule)
+ if node.prev_sibling().as_ref().map(|v| v.kind())
+ == Some(SyntaxKind::Show) =>
+ {
+ Some(Category::Function)
+ }
+ _ => None,
+ }
+}
+
#[cfg(test)]
mod tests {
use std::ops::Range;
@@ -300,7 +320,8 @@ mod tests {
test(
"#f(x + 1)",
&[
- (0..2, Function),
+ (0..1, Function),
+ (1..2, Function),
(2..3, Punctuation),
(5..6, Operator),
(7..8, Number),
@@ -311,7 +332,8 @@ mod tests {
test(
"#let f(x) = x",
&[
- (0..4, Keyword),
+ (0..1, Keyword),
+ (1..4, Keyword),
(5..6, Function),
(6..7, Punctuation),
(8..9, Punctuation),
diff --git a/src/model/eval.rs b/src/model/eval.rs
index 6de328bc..6a1884eb 100644
--- a/src/model/eval.rs
+++ b/src/model/eval.rs
@@ -234,7 +234,7 @@ fn eval_markup(
*node = mem::take(node).labelled(label);
}
}
- value => seq.push(value.display().spanned(expr.span())),
+ value => seq.push(value.display()),
},
}
@@ -254,11 +254,9 @@ impl Eval for ast::Expr {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ let span = self.span();
let forbidden = |name| {
- error!(
- self.span(),
- "{} is only allowed directly in code and content blocks", name
- )
+ error!(span, "{} is only allowed directly in code and content blocks", name)
};
match self {
@@ -266,9 +264,9 @@ impl Eval for ast::Expr {
Self::Space(v) => v.eval(vm).map(Value::Content),
Self::Linebreak(v) => v.eval(vm).map(Value::Content),
Self::Parbreak(v) => v.eval(vm).map(Value::Content),
- Self::Escape(v) => v.eval(vm).map(Value::Content),
- Self::Shorthand(v) => v.eval(vm).map(Value::Content),
Self::Symbol(v) => v.eval(vm).map(Value::Content),
+ Self::Escape(v) => v.eval(vm),
+ Self::Shorthand(v) => v.eval(vm),
Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
Self::Strong(v) => v.eval(vm).map(Value::Content),
Self::Emph(v) => v.eval(vm).map(Value::Content),
@@ -280,11 +278,14 @@ impl Eval for ast::Expr {
Self::List(v) => v.eval(vm).map(Value::Content),
Self::Enum(v) => v.eval(vm).map(Value::Content),
Self::Term(v) => v.eval(vm).map(Value::Content),
- Self::Atom(v) => v.eval(vm).map(Value::Content),
- Self::Delimited(v) => v.eval(vm).map(Value::Content),
- Self::Script(v) => v.eval(vm).map(Value::Content),
- Self::Frac(v) => v.eval(vm).map(Value::Content),
- Self::AlignPoint(v) => v.eval(vm).map(Value::Content),
+ Self::Formula(v) => v.eval(vm).map(Value::Content),
+ Self::Math(v) => v.eval(vm).map(Value::Content),
+ Self::MathAtom(v) => v.eval(vm).map(Value::Content),
+ Self::MathIdent(v) => v.eval(vm),
+ Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
+ Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
+ Self::MathScript(v) => v.eval(vm).map(Value::Content),
+ Self::MathFrac(v) => v.eval(vm).map(Value::Content),
Self::Ident(v) => v.eval(vm),
Self::None(v) => v.eval(vm),
Self::Auto(v) => v.eval(vm),
@@ -295,7 +296,6 @@ impl Eval for ast::Expr {
Self::Str(v) => v.eval(vm),
Self::Code(v) => v.eval(vm),
Self::Content(v) => v.eval(vm).map(Value::Content),
- Self::Math(v) => v.eval(vm).map(Value::Content),
Self::Array(v) => v.eval(vm).map(Value::Array),
Self::Dict(v) => v.eval(vm).map(Value::Dict),
Self::Parenthesized(v) => v.eval(vm),
@@ -316,29 +316,12 @@ impl Eval for ast::Expr {
Self::Break(v) => v.eval(vm),
Self::Continue(v) => v.eval(vm),
Self::Return(v) => v.eval(vm),
- }
- }
-}
+ }?
+ .spanned(span);
-impl ast::Expr {
- fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
- Ok(match self {
- Self::Escape(v) => v.eval_in_math(vm)?,
- Self::Shorthand(v) => v.eval_in_math(vm)?,
- Self::Symbol(v) => v.eval_in_math(vm)?,
- Self::Ident(v) => v.eval_in_math(vm)?,
- Self::FuncCall(v) => v.eval_in_math(vm)?,
- _ => self.eval(vm)?.display_in_math(),
}
- .spanned(self.span()))
- }
- fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> {
- Ok(match self {
- Self::Delimited(v) => v.eval_without_parens(vm)?,
- _ => self.eval_in_math(vm)?,
- }
- .spanned(self.span()))
+ Ok(v)
}
}
@@ -375,44 +358,22 @@ impl Eval for ast::Parbreak {
}
impl Eval for ast::Escape {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.text)(self.get().into()))
- }
-}
+ type Output = Value;
-impl ast::Escape {
- fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
- Ok((vm.items.math_atom)(self.get().into()))
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ // This can be in markup and math, going through a string ensure
+ // that either text or atom is picked.
+ Ok(Value::Str(self.get().into()))
}
}
impl Eval for ast::Shorthand {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.text)(self.get().into()))
- }
-}
-
-impl ast::Shorthand {
- fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
- Ok((vm.items.math_atom)(self.get().into()))
- }
-}
-
-impl Eval for ast::Symbol {
- type Output = Content;
-
- fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.symbol)(self.get().into()))
- }
-}
+ type Output = Value;
-impl ast::Symbol {
- fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
- Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op:square".into()))
+ fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
+ // This can be in markup and math, going through a string ensure
+ // that either text or atom is picked.
+ Ok(Value::Str(self.get().into()))
}
}
@@ -513,108 +474,95 @@ impl Eval for ast::TermItem {
}
}
-impl Eval for ast::Math {
+impl Eval for ast::Formula {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let seq = self
- .exprs()
- .map(|expr| expr.eval_in_math(vm))
- .collect::<SourceResult<_>>()?;
+ let body = self.body().eval(vm)?;
let block = self.block();
- Ok((vm.items.math_formula)(Content::sequence(seq), block))
+ Ok((vm.items.formula)(body, block))
}
}
-impl Eval for ast::Atom {
+impl Eval for ast::Math {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.math_atom)(self.get().clone()))
+ Ok(Content::sequence(
+ self.exprs()
+ .map(|expr| Ok(expr.eval(vm)?.display_in_math()))
+ .collect::<SourceResult<_>>()?,
+ ))
}
}
-impl Eval for ast::Delimited {
+impl Eval for ast::MathAtom {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let seq = self
- .exprs()
- .map(|expr| expr.eval_in_math(vm))
- .collect::<SourceResult<_>>()?;
- Ok((vm.items.math_delimited)(Content::sequence(seq)))
+ Ok((vm.items.math_atom)(self.get().clone()))
}
}
-impl ast::Delimited {
- fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> {
- let exprs: Vec<_> = self.exprs().collect();
- let mut slice = exprs.as_slice();
- if let (Some(ast::Expr::Atom(first)), Some(ast::Expr::Atom(last))) =
- (exprs.first(), exprs.last())
- {
- if first.get() == "(" && last.get() == ")" {
- slice = &exprs[1..exprs.len() - 1];
- }
- }
- let seq = slice
- .iter()
- .map(|expr| expr.eval_in_math(vm))
- .collect::<SourceResult<_>>()?;
- Ok((vm.items.math_delimited)(Content::sequence(seq)))
+impl Eval for ast::MathIdent {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(vm.scopes.get_in_math(self).cloned().at(self.span())?)
}
}
-impl Eval for ast::Script {
+impl Eval for ast::MathAlignPoint {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let base = self.base().eval_in_math(vm)?;
- let sub = self.sub().map(|expr| expr.eval_without_parens(vm)).transpose()?;
- let sup = self.sup().map(|expr| expr.eval_without_parens(vm)).transpose()?;
- Ok((vm.items.math_script)(base, sub, sup))
+ Ok((vm.items.math_align_point)())
}
}
-impl Eval for ast::Frac {
+impl Eval for ast::MathDelimited {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let num = self.num().eval_without_parens(vm)?;
- let denom = self.denom().eval_without_parens(vm)?;
- Ok((vm.items.math_frac)(num, denom))
+ let open = self.open().eval(vm)?;
+ let body = self.body().eval(vm)?;
+ let close = self.close().eval(vm)?;
+ Ok((vm.items.math_delimited)(open, body, close))
}
}
-impl Eval for ast::AlignPoint {
+impl Eval for ast::MathScript {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- Ok((vm.items.math_align_point)())
+ let base = self.base().eval(vm)?.display_in_math();
+ let sub = self
+ .sub()
+ .map(|expr| expr.eval(vm).map(Value::display_in_math))
+ .transpose()?;
+ let sup = self
+ .sup()
+ .map(|expr| expr.eval(vm).map(Value::display_in_math))
+ .transpose()?;
+ Ok((vm.items.math_script)(base, sub, sup))
}
}
-impl Eval for ast::Ident {
- type Output = Value;
+impl Eval for ast::MathFrac {
+ type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let value = vm.scopes.get(self).cloned().at(self.span())?;
- Ok(match value {
- Value::Func(func) => Value::Func(func.spanned(self.span())),
- value => value,
- })
+ let num = self.num().eval(vm)?.display_in_math();
+ let denom = self.denom().eval(vm)?.display_in_math();
+ Ok((vm.items.math_frac)(num, denom))
}
}
-impl ast::Ident {
- fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
- if self.as_untyped().len() == self.len()
- && matches!(vm.scopes.get_in_math(self), Ok(Value::Func(_)) | Err(_))
- {
- Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into()))
- } else {
- Ok(vm.scopes.get_in_math(self).at(self.span())?.clone().display_in_math())
- }
+impl Eval for ast::Ident {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ Ok(vm.scopes.get(self).cloned().at(self.span())?)
}
}
@@ -686,12 +634,20 @@ impl Eval for ast::CodeBlock {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
vm.scopes.enter();
- let output = eval_code(vm, &mut self.exprs())?;
+ let output = self.body().eval(vm)?;
vm.scopes.exit();
Ok(output)
}
}
+impl Eval for ast::Code {
+ type Output = Value;
+
+ fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
+ eval_code(vm, &mut self.exprs())
+ }
+}
+
/// Evaluate a stream of expressions.
fn eval_code(
vm: &mut Vm,
@@ -901,23 +857,9 @@ impl Eval for ast::FieldAccess {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
- let object = self.target().eval(vm)?;
- let span = self.field().span();
- let field = self.field().take();
-
- Ok(match object {
- Value::Dict(dict) => dict.at(&field).at(span)?.clone(),
- Value::Content(content) => content
- .field(&field)
- .ok_or_else(|| format!("unknown field `{field}`"))
- .at(span)?,
- Value::Module(module) => module.get(&field).cloned().at(span)?,
- v => bail!(
- self.target().span(),
- "expected dictionary or content, found {}",
- v.type_name()
- ),
- })
+ let value = self.target().eval(vm)?;
+ let field = self.field();
+ value.field(&field).at(field.span())
}
}
@@ -926,27 +868,13 @@ impl Eval for ast::FuncCall {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let callee = self.callee();
- let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?;
- let args = self.args().eval(vm)?;
- Self::eval_call(vm, &callee, args, self.span())
- }
-}
-
-impl ast::FuncCall {
- fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
- let callee = match self.callee() {
- ast::Expr::Ident(ident) => {
- vm.scopes.get_in_math(&ident).at(ident.span())?.clone()
- }
- expr => expr.eval(vm)?,
- };
+ let callee_span = callee.span();
+ let in_math = matches!(callee, ast::Expr::MathIdent(_));
+ let callee = callee.eval(vm)?;
+ let mut args = self.args().eval(vm)?;
- if let Value::Func(callee) = callee {
- let args = self.args().eval(vm)?;
- Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math())
- } else {
+ if in_math && !matches!(callee, Value::Func(_)) {
let mut body = (vm.items.math_atom)('('.into());
- let mut args = self.args().eval(vm)?;
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 {
body += (vm.items.math_atom)(','.into());
@@ -954,23 +882,26 @@ impl ast::FuncCall {
body += arg;
}
body += (vm.items.math_atom)(')'.into());
- Ok(callee.display_in_math() + body)
+ return Ok(Value::Content(callee.display_in_math() + body));
}
- }
- fn eval_call(
- vm: &mut Vm,
- callee: &Func,
- args: Args,
- span: Span,
- ) -> SourceResult<Value> {
- if vm.depth >= MAX_CALL_DEPTH {
- bail!(span, "maximum function call depth exceeded");
- }
+ let callee = callee.cast::<Func>().at(callee_span)?;
+ complete_call(vm, &callee, args, self.span())
+ }
+}
- let point = || Tracepoint::Call(callee.name().map(Into::into));
- callee.call(vm, args).trace(vm.world, point, span)
+fn complete_call(
+ vm: &mut Vm,
+ callee: &Func,
+ args: Args,
+ span: Span,
+) -> SourceResult<Value> {
+ if vm.depth >= MAX_CALL_DEPTH {
+ bail!(span, "maximum function call depth exceeded");
}
+
+ let point = || Tracepoint::Call(callee.name().map(Into::into));
+ callee.call(vm, args).trace(vm.world, point, span)
}
impl Eval for ast::MethodCall {
@@ -988,7 +919,7 @@ impl Eval for ast::MethodCall {
if let Value::Func(callee) =
module.get(&method).cloned().at(method.span())?
{
- return ast::FuncCall::eval_call(vm, &callee, args, self.span());
+ return complete_call(vm, &callee, args, self.span());
}
}
@@ -999,7 +930,7 @@ impl Eval for ast::MethodCall {
if let Value::Module(module) = &value {
if let Value::Func(callee) = module.get(&method).at(method.span())? {
- return ast::FuncCall::eval_call(vm, callee, args, self.span());
+ return complete_call(vm, callee, args, self.span());
}
}
@@ -1367,7 +1298,7 @@ impl Eval for ast::ModuleInclude {
}
/// Process an import of a module relative to the current location.
-fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> {
+fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult<Module> {
let path = match source {
Value::Str(path) => path,
Value::Module(module) => return Ok(module),
@@ -1386,7 +1317,8 @@ fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> {
// Evaluate the file.
let source = vm.world.source(id);
let point = || Tracepoint::Import;
- eval(vm.world, vm.route, source).trace(vm.world, point, span)
+ eval(vm.world, vm.route, TrackedMut::reborrow_mut(&mut vm.tracer), source)
+ .trace(vm.world, point, span)
}
impl Eval for ast::LoopBreak {
@@ -1446,7 +1378,12 @@ impl Access for ast::Expr {
impl Access for ast::Ident {
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
- vm.scopes.get_mut(self).at(self.span())
+ let span = self.span();
+ let value = vm.scopes.get_mut(self).at(span)?;
+ if vm.traced == Some(span) {
+ vm.tracer.trace(value.clone());
+ }
+ Ok(value)
}
}
diff --git a/src/model/func.rs b/src/model/func.rs
index 73b7f1c7..00e59bbd 100644
--- a/src/model/func.rs
+++ b/src/model/func.rs
@@ -505,17 +505,17 @@ mod tests {
fn test_captures() {
// Let binding and function definition.
test("#let x = x", &["x"]);
- test("#let x; {x + y}", &["y"]);
+ test("#let x; #{x + y}", &["y"]);
test("#let f(x, y) = x + y", &[]);
test("#let f(x, y) = f", &[]);
test("#let f = (x, y) => f", &["f"]);
// Closure with different kinds of params.
- test("{(x, y) => x + z}", &["z"]);
- test("{(x: y, z) => x + z}", &["y"]);
- test("{(..x) => x + y}", &["y"]);
- test("{(x, y: x + z) => x + y}", &["x", "z"]);
- test("{x => x; x}", &["x"]);
+ test("#{(x, y) => x + z}", &["z"]);
+ test("#{(x: y, z) => x + z}", &["y"]);
+ test("#{(..x) => x + y}", &["y"]);
+ test("#{(x, y: x + z) => x + y}", &["x", "z"]);
+ test("#{x => x; x}", &["x"]);
// Show rule.
test("#show y: x => x", &["y"]);
@@ -532,7 +532,7 @@ mod tests {
test("#import x + y: x, y, z", &["x", "y"]);
// Blocks.
- test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
- test("[#let x = 1]#x", &["x"]);
+ test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
+ test("#[#let x = 1]#x", &["x"]);
}
}
diff --git a/src/model/library.rs b/src/model/library.rs
index a64b0263..cd9db10f 100644
--- a/src/model/library.rs
+++ b/src/model/library.rs
@@ -68,19 +68,19 @@ pub struct LangItems {
/// An item in a term list: `/ Term: Details`.
pub term_item: fn(term: Content, description: Content) -> Content,
/// A mathematical formula: `$x$`, `$ x^2 $`.
- pub math_formula: fn(body: Content, block: bool) -> Content,
- /// A subsection in a math formula that is surrounded by matched delimiters:
- /// `[x + y]`.
- pub math_delimited: fn(body: Content) -> Content,
+ pub formula: fn(body: Content, block: bool) -> Content,
/// An atom in a formula: `x`, `+`, `12`.
pub math_atom: fn(atom: EcoString) -> Content,
+ /// An alignment point in a formula: `&`.
+ pub math_align_point: fn() -> Content,
+ /// A subsection in a math formula that is surrounded by matched delimiters:
+ /// `[x + y]`.
+ pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
/// A base with optional sub- and superscripts in a formula: `a_1^2`.
pub math_script:
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
/// A fraction in a formula: `x/2`.
pub math_frac: fn(num: Content, denom: Content) -> Content,
- /// An alignment point in a formula: `&`.
- pub math_align_point: fn() -> Content,
}
impl Debug for LangItems {
@@ -108,7 +108,7 @@ impl Hash for LangItems {
self.list_item.hash(state);
self.enum_item.hash(state);
self.term_item.hash(state);
- self.math_formula.hash(state);
+ self.formula.hash(state);
self.math_atom.hash(state);
self.math_script.hash(state);
self.math_frac.hash(state);
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 4bab0c42..b9186787 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -29,7 +29,7 @@ pub trait AstNode: Sized {
macro_rules! node {
($(#[$attr:meta])* $name:ident) => {
- #[derive(Debug, Clone, PartialEq, Hash)]
+ #[derive(Debug, Default, Clone, PartialEq, Hash)]
#[repr(transparent)]
$(#[$attr])*
pub struct $name(SyntaxNode);
@@ -114,18 +114,22 @@ pub enum Expr {
/// An item in a term list: `/ Term: Details`.
Term(TermItem),
/// A math formula: `$x$`, `$ x^2 $`.
+ Formula(Formula),
+ /// A math formula: `$x$`, `$ x^2 $`.
Math(Math),
/// An atom in a math formula: `x`, `+`, `12`.
- Atom(Atom),
+ MathAtom(MathAtom),
+ /// An identifier in a math formula: `pi`.
+ MathIdent(MathIdent),
+ /// An alignment point in a math formula: `&`.
+ MathAlignPoint(MathAlignPoint),
/// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`.
- Delimited(Delimited),
+ MathDelimited(MathDelimited),
/// A base with optional sub- and superscripts in a math formula: `a_1^2`.
- Script(Script),
+ MathScript(MathScript),
/// A fraction in a math formula: `x/2`.
- Frac(Frac),
- /// An alignment point in a math formula: `&`.
- AlignPoint(AlignPoint),
+ MathFrac(MathFrac),
/// An identifier: `left`.
Ident(Ident),
/// The `none` literal.
@@ -205,7 +209,6 @@ impl AstNode for Expr {
SyntaxKind::Text => node.cast().map(Self::Text),
SyntaxKind::Escape => node.cast().map(Self::Escape),
SyntaxKind::Shorthand => node.cast().map(Self::Shorthand),
- SyntaxKind::Symbol => node.cast().map(Self::Symbol),
SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote),
SyntaxKind::Strong => node.cast().map(Self::Strong),
SyntaxKind::Emph => node.cast().map(Self::Emph),
@@ -217,12 +220,14 @@ impl AstNode for Expr {
SyntaxKind::ListItem => node.cast().map(Self::List),
SyntaxKind::EnumItem => node.cast().map(Self::Enum),
SyntaxKind::TermItem => node.cast().map(Self::Term),
+ SyntaxKind::Formula => node.cast().map(Self::Formula),
SyntaxKind::Math => node.cast().map(Self::Math),
- SyntaxKind::Atom => node.cast().map(Self::Atom),
- SyntaxKind::Delimited => node.cast().map(Self::Delimited),
- SyntaxKind::Script => node.cast().map(Self::Script),
- SyntaxKind::Frac => node.cast().map(Self::Frac),
- SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint),
+ SyntaxKind::MathAtom => node.cast().map(Self::MathAtom),
+ SyntaxKind::MathIdent => node.cast().map(Self::MathIdent),
+ SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint),
+ SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited),
+ SyntaxKind::MathScript => node.cast().map(Self::MathScript),
+ SyntaxKind::MathFrac => node.cast().map(Self::MathFrac),
SyntaxKind::Ident => node.cast().map(Self::Ident),
SyntaxKind::None => node.cast().map(Self::None),
SyntaxKind::Auto => node.cast().map(Self::Auto),
@@ -265,7 +270,6 @@ impl AstNode for Expr {
Self::Parbreak(v) => v.as_untyped(),
Self::Escape(v) => v.as_untyped(),
Self::Shorthand(v) => v.as_untyped(),
- Self::Symbol(v) => v.as_untyped(),
Self::SmartQuote(v) => v.as_untyped(),
Self::Strong(v) => v.as_untyped(),
Self::Emph(v) => v.as_untyped(),
@@ -277,12 +281,14 @@ impl AstNode for Expr {
Self::List(v) => v.as_untyped(),
Self::Enum(v) => v.as_untyped(),
Self::Term(v) => v.as_untyped(),
+ Self::Formula(v) => v.as_untyped(),
Self::Math(v) => v.as_untyped(),
- Self::Atom(v) => v.as_untyped(),
- Self::Delimited(v) => v.as_untyped(),
- Self::Script(v) => v.as_untyped(),
- Self::Frac(v) => v.as_untyped(),
- Self::AlignPoint(v) => v.as_untyped(),
+ Self::MathAtom(v) => v.as_untyped(),
+ Self::MathIdent(v) => v.as_untyped(),
+ Self::MathAlignPoint(v) => v.as_untyped(),
+ Self::MathDelimited(v) => v.as_untyped(),
+ Self::MathScript(v) => v.as_untyped(),
+ Self::MathFrac(v) => v.as_untyped(),
Self::Ident(v) => v.as_untyped(),
Self::None(v) => v.as_untyped(),
Self::Auto(v) => v.as_untyped(),
@@ -317,6 +323,12 @@ impl AstNode for Expr {
}
}
+impl Default for Expr {
+ fn default() -> Self {
+ Expr::Space(Space::default())
+ }
+}
+
node! {
/// Plain text without markup.
Text
@@ -360,9 +372,9 @@ impl Escape {
u32::from_str_radix(hex, 16)
.ok()
.and_then(std::char::from_u32)
- .expect("unicode escape is invalid")
+ .unwrap_or_default()
} else {
- s.eat().expect("escape is missing escaped character")
+ s.eat().unwrap_or_default()
}
}
}
@@ -378,10 +390,11 @@ impl Shorthand {
pub fn get(&self) -> char {
match self.0.text().as_str() {
"~" => '\u{00A0}',
- "..." => '\u{2026}',
"--" => '\u{2013}',
"---" => '\u{2014}',
"-?" => '\u{00AD}',
+ "..." => '…',
+ "*" => '∗',
"!=" => '≠',
"<=" => '≤',
">=" => '≥',
@@ -432,7 +445,7 @@ node! {
impl Strong {
/// The contents of the strong node.
pub fn body(&self) -> Markup {
- self.0.cast_first_match().expect("strong emphasis is missing body")
+ self.0.cast_first_match().unwrap_or_default()
}
}
@@ -444,7 +457,7 @@ node! {
impl Emph {
/// The contents of the emphasis node.
pub fn body(&self) -> Markup {
- self.0.cast_first_match().expect("emphasis is missing body")
+ self.0.cast_first_match().unwrap_or_default()
}
}
@@ -568,7 +581,7 @@ node! {
impl Heading {
/// The contents of the heading.
pub fn body(&self) -> Markup {
- self.0.cast_first_match().expect("heading is missing markup body")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The section depth (numer of equals signs).
@@ -577,7 +590,7 @@ impl Heading {
.children()
.find(|node| node.kind() == SyntaxKind::HeadingMarker)
.and_then(|node| node.len().try_into().ok())
- .expect("heading is missing marker")
+ .unwrap_or(NonZeroUsize::new(1).unwrap())
}
}
@@ -589,7 +602,7 @@ node! {
impl ListItem {
/// The contents of the list item.
pub fn body(&self) -> Markup {
- self.0.cast_first_match().expect("list item is missing body")
+ self.0.cast_first_match().unwrap_or_default()
}
}
@@ -609,7 +622,7 @@ impl EnumItem {
/// The contents of the list item.
pub fn body(&self) -> Markup {
- self.0.cast_first_match().expect("enum item is missing body")
+ self.0.cast_first_match().unwrap_or_default()
}
}
@@ -621,41 +634,53 @@ node! {
impl TermItem {
/// The term described by the item.
pub fn term(&self) -> Markup {
- self.0.cast_first_match().expect("term list item is missing term")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The description of the term.
pub fn description(&self) -> Markup {
- self.0
- .cast_last_match()
- .expect("term list item is missing description")
+ self.0.cast_last_match().unwrap_or_default()
}
}
node! {
/// A math formula: `$x$`, `$ x^2 $`.
- Math
+ Formula
}
-impl Math {
- /// The expressions the formula consists of.
- pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
- self.0.children().filter_map(Expr::cast_with_space)
+impl Formula {
+ /// The contained math.
+ pub fn body(&self) -> Math {
+ self.0.cast_first_match().unwrap_or_default()
}
/// Whether the formula should be displayed as a separate block.
pub fn block(&self) -> bool {
- matches!(self.exprs().next(), Some(Expr::Space(_)))
- && matches!(self.exprs().last(), Some(Expr::Space(_)))
+ let is_space = |node: Option<&SyntaxNode>| {
+ node.map(SyntaxNode::kind) == Some(SyntaxKind::Space)
+ };
+ is_space(self.0.children().nth(1)) && is_space(self.0.children().nth_back(1))
+ }
+}
+
+node! {
+ /// Math markup.
+ Math
+}
+
+impl Math {
+ /// The expressions the mathematical content consists of.
+ pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
+ self.0.children().filter_map(Expr::cast_with_space)
}
}
node! {
/// A atom in a formula: `x`, `+`, `12`.
- Atom
+ MathAtom
}
-impl Atom {
+impl MathAtom {
/// Get the atom's text.
pub fn get(&self) -> &EcoString {
self.0.text()
@@ -663,27 +688,72 @@ impl Atom {
}
node! {
+ /// An identifier in a math formula: `pi`.
+ MathIdent
+}
+
+impl MathIdent {
+ /// Get the identifier.
+ pub fn get(&self) -> &EcoString {
+ self.0.text()
+ }
+
+ /// Take out the contained identifier.
+ pub fn take(self) -> EcoString {
+ self.0.into_text()
+ }
+
+ /// Get the identifier as a string slice.
+ pub fn as_str(&self) -> &str {
+ self.get()
+ }
+}
+
+impl Deref for MathIdent {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ self.as_str()
+ }
+}
+
+node! {
+ /// An alignment point in a formula: `&`.
+ MathAlignPoint
+}
+
+node! {
/// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`.
- Delimited
+ MathDelimited
}
-impl Delimited {
+impl MathDelimited {
+ /// The opening delimiter.
+ pub fn open(&self) -> MathAtom {
+ self.0.cast_first_match().unwrap_or_default()
+ }
+
/// The contents, including the delimiters.
- pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
- self.0.children().filter_map(Expr::cast_with_space)
+ pub fn body(&self) -> Math {
+ self.0.cast_first_match().unwrap_or_default()
+ }
+
+ /// The closing delimiter.
+ pub fn close(&self) -> MathAtom {
+ self.0.cast_last_match().unwrap_or_default()
}
}
node! {
/// A base with an optional sub- and superscript in a formula: `a_1^2`.
- Script
+ MathScript
}
-impl Script {
+impl MathScript {
/// The base of the script.
pub fn base(&self) -> Expr {
- self.0.cast_first_match().expect("script node is missing base")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The subscript.
@@ -705,44 +775,35 @@ impl Script {
node! {
/// A fraction in a formula: `x/2`
- Frac
+ MathFrac
}
-impl Frac {
+impl MathFrac {
/// The numerator.
pub fn num(&self) -> Expr {
- self.0.cast_first_match().expect("fraction is missing numerator")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The denominator.
pub fn denom(&self) -> Expr {
- self.0.cast_last_match().expect("fraction is missing denominator")
+ self.0.cast_last_match().unwrap_or_default()
}
}
node! {
- /// An alignment point in a formula: `&`.
- AlignPoint
-}
-
-node! {
/// An identifier: `it`.
Ident
}
impl Ident {
/// Get the identifier.
- pub fn get(&self) -> &str {
- self.0.text().trim_start_matches('#')
+ pub fn get(&self) -> &EcoString {
+ self.0.text()
}
/// Take out the contained identifier.
pub fn take(self) -> EcoString {
- let text = self.0.into_text();
- match text.strip_prefix('#') {
- Some(text) => text.into(),
- Option::None => text,
- }
+ self.0.into_text()
}
/// Get the identifier as a string slice.
@@ -789,7 +850,7 @@ node! {
impl Int {
/// Get the integer value.
pub fn get(&self) -> i64 {
- self.0.text().parse().expect("integer is invalid")
+ self.0.text().parse().unwrap_or_default()
}
}
@@ -801,7 +862,7 @@ node! {
impl Float {
/// Get the floating-point value.
pub fn get(&self) -> f64 {
- self.0.text().parse().expect("float is invalid")
+ self.0.text().parse().unwrap_or_default()
}
}
@@ -821,7 +882,7 @@ impl Numeric {
.count();
let split = text.len() - count;
- let value = text[..split].parse().expect("number is invalid");
+ let value = text[..split].parse().unwrap_or_default();
let unit = match &text[split..] {
"pt" => Unit::Length(AbsUnit::Pt),
"mm" => Unit::Length(AbsUnit::Mm),
@@ -910,7 +971,19 @@ node! {
}
impl CodeBlock {
- /// The list of expressions contained in the block.
+ /// The contained code.
+ pub fn body(&self) -> Code {
+ self.0.cast_first_match().unwrap_or_default()
+ }
+}
+
+node! {
+ /// Code.
+ Code
+}
+
+impl Code {
+ /// The list of expressions contained in the code.
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
@@ -924,7 +997,7 @@ node! {
impl ContentBlock {
/// The contained markup.
pub fn body(&self) -> Markup {
- self.0.cast_first_match().expect("content block is missing body")
+ self.0.cast_first_match().unwrap_or_default()
}
}
@@ -936,9 +1009,7 @@ node! {
impl Parenthesized {
/// The wrapped expression.
pub fn expr(&self) -> Expr {
- self.0
- .cast_first_match()
- .expect("parenthesized expression is missing expression")
+ self.0.cast_first_match().unwrap_or_default()
}
}
@@ -1029,12 +1100,12 @@ node! {
impl Named {
/// The name: `thickness`.
pub fn name(&self) -> Ident {
- self.0.cast_first_match().expect("named pair is missing name")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The right-hand side of the pair: `3pt`.
pub fn expr(&self) -> Expr {
- self.0.cast_last_match().expect("named pair is missing expression")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1049,12 +1120,12 @@ impl Keyed {
self.0
.children()
.find_map(|node| node.cast::<Str>())
- .expect("keyed pair is missing key")
+ .unwrap_or_default()
}
/// The right-hand side of the pair: `true`.
pub fn expr(&self) -> Expr {
- self.0.cast_last_match().expect("keyed pair is missing expression")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1069,12 +1140,12 @@ impl Unary {
self.0
.children()
.find_map(|node| UnOp::from_kind(node.kind()))
- .expect("unary operation is missing operator")
+ .unwrap_or(UnOp::Pos)
}
/// The expression to operate on: `x`.
pub fn expr(&self) -> Expr {
- self.0.cast_last_match().expect("unary operation is missing child")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1137,21 +1208,17 @@ impl Binary {
SyntaxKind::In if not => Some(BinOp::NotIn),
_ => BinOp::from_kind(node.kind()),
})
- .expect("binary operation is missing operator")
+ .unwrap_or(BinOp::Add)
}
/// The left-hand side of the operation: `a`.
pub fn lhs(&self) -> Expr {
- self.0
- .cast_first_match()
- .expect("binary operation is missing left-hand side")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The right-hand side of the operation: `b`.
pub fn rhs(&self) -> Expr {
- self.0
- .cast_last_match()
- .expect("binary operation is missing right-hand side")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1317,12 +1384,12 @@ node! {
impl FieldAccess {
/// The expression to access the field on.
pub fn target(&self) -> Expr {
- self.0.cast_first_match().expect("field access is missing object")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The name of the field.
pub fn field(&self) -> Ident {
- self.0.cast_last_match().expect("field access is missing name")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1334,14 +1401,12 @@ node! {
impl FuncCall {
/// The function to call.
pub fn callee(&self) -> Expr {
- self.0.cast_first_match().expect("function call is missing callee")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The arguments to the function.
pub fn args(&self) -> Args {
- self.0
- .cast_last_match()
- .expect("function call is missing argument list")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1353,19 +1418,17 @@ node! {
impl MethodCall {
/// The expression to call the method on.
pub fn target(&self) -> Expr {
- self.0.cast_first_match().expect("method call is missing target")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The name of the method.
pub fn method(&self) -> Ident {
- self.0.cast_last_match().expect("method call is missing name")
+ self.0.cast_last_match().unwrap_or_default()
}
/// The arguments to the method.
pub fn args(&self) -> Args {
- self.0
- .cast_last_match()
- .expect("method call is missing argument list")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1428,14 +1491,13 @@ impl Closure {
self.0
.children()
.find(|x| x.kind() == SyntaxKind::Params)
- .expect("closure is missing parameter list")
- .children()
+ .map_or([].iter(), |params| params.children())
.filter_map(SyntaxNode::cast)
}
/// The body of the closure.
pub fn body(&self) -> Expr {
- self.0.cast_last_match().expect("closure is missing body")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1479,10 +1541,8 @@ impl LetBinding {
pub fn binding(&self) -> Ident {
match self.0.cast_first_match() {
Some(Expr::Ident(binding)) => binding,
- Some(Expr::Closure(closure)) => {
- closure.name().expect("let-bound closure is missing name")
- }
- _ => panic!("let is missing binding"),
+ Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(),
+ _ => Ident::default(),
}
}
@@ -1506,12 +1566,12 @@ node! {
impl SetRule {
/// The function to set style properties for.
pub fn target(&self) -> Expr {
- self.0.cast_first_match().expect("set rule is missing target")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The style properties to set.
pub fn args(&self) -> Args {
- self.0.cast_last_match().expect("set rule is missing argument list")
+ self.0.cast_last_match().unwrap_or_default()
}
/// A condition under which the set rule applies.
@@ -1540,7 +1600,7 @@ impl ShowRule {
/// The transformation recipe.
pub fn transform(&self) -> Expr {
- self.0.cast_last_match().expect("show rule is missing transform")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1552,7 +1612,7 @@ node! {
impl Conditional {
/// The condition which selects the body to evaluate.
pub fn condition(&self) -> Expr {
- self.0.cast_first_match().expect("conditional is missing condition")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The expression to evaluate if the condition is true.
@@ -1561,7 +1621,7 @@ impl Conditional {
.children()
.filter_map(SyntaxNode::cast)
.nth(1)
- .expect("conditional is missing body")
+ .unwrap_or_default()
}
/// The expression to evaluate if the condition is false.
@@ -1578,12 +1638,12 @@ node! {
impl WhileLoop {
/// The condition which selects whether to evaluate the body.
pub fn condition(&self) -> Expr {
- self.0.cast_first_match().expect("while loop is missing condition")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The expression to evaluate while the condition is true.
pub fn body(&self) -> Expr {
- self.0.cast_last_match().expect("while loop is missing body")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1595,17 +1655,17 @@ node! {
impl ForLoop {
/// The pattern to assign to.
pub fn pattern(&self) -> ForPattern {
- self.0.cast_first_match().expect("for loop is missing pattern")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The expression to iterate over.
pub fn iter(&self) -> Expr {
- self.0.cast_first_match().expect("for loop is missing iterable")
+ self.0.cast_first_match().unwrap_or_default()
}
/// The expression to evaluate for each iteration.
pub fn body(&self) -> Expr {
- self.0.cast_last_match().expect("for loop is missing body")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1628,7 +1688,7 @@ impl ForPattern {
/// The value part of the pattern.
pub fn value(&self) -> Ident {
- self.0.cast_last_match().expect("for loop pattern is missing value")
+ self.0.cast_last_match().unwrap_or_default()
}
}
@@ -1640,7 +1700,7 @@ node! {
impl ModuleImport {
/// The module or path from which the items should be imported.
pub fn source(&self) -> Expr {
- self.0.cast_last_match().expect("module import is missing source")
+ self.0.cast_last_match().unwrap_or_default()
}
/// The items to be imported.
@@ -1673,7 +1733,7 @@ node! {
impl ModuleInclude {
/// The module or path from which the content should be included.
pub fn source(&self) -> Expr {
- self.0.cast_last_match().expect("module include is missing path")
+ self.0.cast_last_match().unwrap_or_default()
}
}
diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs
index 206df911..34e2fce7 100644
--- a/src/syntax/kind.rs
+++ b/src/syntax/kind.rs
@@ -58,19 +58,26 @@ pub enum SyntaxKind {
/// Introduces a term item: `/`.
TermMarker,
/// A mathematical formula: `$x$`, `$ x^2 $`.
+ Formula,
+
+ /// Mathematical markup.
Math,
/// An atom in math: `x`, `+`, `12`.
- Atom,
+ MathAtom,
+ /// An identifier in math: `pi`.
+ MathIdent,
+ /// An alignment point in math: `&`.
+ MathAlignPoint,
/// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`.
- Delimited,
+ MathDelimited,
/// A base with optional sub- and superscripts in math: `a_1^2`.
- Script,
+ MathScript,
/// A fraction in math: `x/2`.
- Frac,
- /// An alignment point in math: `&`.
- AlignPoint,
+ MathFrac,
+ /// A hashtag that switches into code mode: `#`.
+ Hashtag,
/// A left curly brace, starting a code block: `{`.
LeftBrace,
/// A right curly brace, terminating a code block: `}`.
@@ -175,6 +182,8 @@ pub enum SyntaxKind {
/// The `as` keyword.
As,
+ /// Code.
+ Code,
/// An identifier: `it`.
Ident,
/// A boolean: `true`, `false`.
@@ -338,12 +347,15 @@ impl SyntaxKind {
Self::EnumMarker => "enum marker",
Self::TermItem => "term list item",
Self::TermMarker => "term marker",
- Self::Math => "math formula",
- Self::Delimited => "delimited math",
- Self::Atom => "math atom",
- Self::Script => "script",
- Self::Frac => "fraction",
- Self::AlignPoint => "alignment point",
+ Self::Formula => "math formula",
+ Self::Math => "math",
+ Self::MathIdent => "math identifier",
+ Self::MathAtom => "math atom",
+ Self::MathAlignPoint => "math alignment point",
+ Self::MathDelimited => "delimited math",
+ Self::MathScript => "math script",
+ Self::MathFrac => "math fraction",
+ Self::Hashtag => "hashtag",
Self::LeftBrace => "opening brace",
Self::RightBrace => "closing brace",
Self::LeftBracket => "opening bracket",
@@ -394,6 +406,7 @@ impl SyntaxKind {
Self::Import => "keyword `import`",
Self::Include => "keyword `include`",
Self::As => "keyword `as`",
+ Self::Code => "code",
Self::Ident => "identifier",
Self::Bool => "boolean",
Self::Int => "integer",
diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs
index 1064939d..0735270b 100644
--- a/src/syntax/lexer.rs
+++ b/src/syntax/lexer.rs
@@ -376,7 +376,7 @@ impl Lexer<'_> {
Some('-') if !s.at(['-', '?']) => {}
Some('.') if !s.at("..") => {}
Some('h') if !s.at("ttp://") && !s.at("ttps://") => {}
- Some('@' | '#') if !s.at(is_id_start) => {}
+ Some('@') if !s.at(is_id_start) => {}
_ => break,
}
@@ -410,15 +410,8 @@ impl Lexer<'_> {
'\\' => self.backslash(),
':' if self.s.at(is_id_start) => self.maybe_symbol(),
'"' => self.string(),
- '#' if self.s.eat_if('{') => SyntaxKind::LeftBrace,
- '#' if self.s.eat_if('[') => SyntaxKind::LeftBracket,
- '#' if self.s.at(is_id_start) => {
- match keyword(self.s.eat_while(is_id_continue)) {
- Some(keyword) => keyword,
- None => SyntaxKind::Ident,
- }
- }
+ '.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'|' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'<' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'<' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
@@ -429,13 +422,17 @@ impl Lexer<'_> {
'-' if self.s.eat_if('>') => SyntaxKind::Shorthand,
'=' if self.s.eat_if('>') => SyntaxKind::Shorthand,
':' if self.s.eat_if('=') => SyntaxKind::Shorthand,
- '.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
+ '[' if self.s.eat_if('|') => SyntaxKind::Shorthand,
+ '|' if self.s.eat_if(']') => SyntaxKind::Shorthand,
+ '|' if self.s.eat_if('|') => SyntaxKind::Shorthand,
+ '*' => SyntaxKind::Shorthand,
+ '#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag,
'_' => SyntaxKind::Underscore,
'$' => SyntaxKind::Dollar,
'/' => SyntaxKind::Slash,
'^' => SyntaxKind::Hat,
- '&' => SyntaxKind::AlignPoint,
+ '&' => SyntaxKind::MathAlignPoint,
// Identifiers and symbol notation.
c if is_math_id_start(c) && self.s.at(is_math_id_continue) => {
@@ -479,7 +476,7 @@ impl Lexer<'_> {
.map_or(0, str::len);
self.s.jump(start + len);
}
- SyntaxKind::Atom
+ SyntaxKind::MathAtom
}
}
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index 038d13bc..d133fc5d 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -95,6 +95,11 @@ impl SyntaxNode {
}
}
+ /// Whether the node can be cast to the given AST node.
+ pub fn is<T: AstNode>(&self) -> bool {
+ self.cast::<T>().is_some()
+ }
+
/// Try to convert the node to a typed AST node.
pub fn cast<T: AstNode>(&self) -> Option<T> {
T::from_untyped(self)
@@ -144,6 +149,16 @@ impl SyntaxNode {
}
}
+ /// Convert the child to another kind.
+ pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) {
+ debug_assert!(!kind.is_error());
+ match &mut self.0 {
+ Repr::Leaf(leaf) => leaf.kind = kind,
+ Repr::Inner(inner) => Arc::make_mut(inner).kind = kind,
+ Repr::Error(_) => panic!("cannot convert error"),
+ }
+ }
+
/// Convert the child to an error.
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
let len = self.len();
@@ -695,6 +710,14 @@ impl<'a> LinkedNode<'a> {
Some(next)
}
}
+
+ /// Whether an error follows directly after the node.
+ pub fn before_error(&self) -> bool {
+ let Some(parent) = self.parent() else { return false };
+ let Some(index) = self.index.checked_add(1) else { return false };
+ let Some(node) = parent.node.children().nth(index) else { return false };
+ node.kind().is_error()
+ }
}
/// Access to leafs.
@@ -865,8 +888,8 @@ mod tests {
// Go back to "#set". Skips the space.
let prev = node.prev_sibling().unwrap();
- assert_eq!(prev.offset(), 0);
- assert_eq!(prev.text(), "#set");
+ assert_eq!(prev.offset(), 1);
+ assert_eq!(prev.text(), "set");
}
#[test]
@@ -875,7 +898,7 @@ mod tests {
let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
let prev = leaf.prev_leaf().unwrap();
assert_eq!(leaf.text(), "fun");
- assert_eq!(prev.text(), "#set");
+ assert_eq!(prev.text(), "set");
let source = Source::detached("#let x = 10");
let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap();
diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs
index 07f53372..a046b685 100644
--- a/src/syntax/parser.rs
+++ b/src/syntax/parser.rs
@@ -18,9 +18,7 @@ pub fn parse(text: &str) -> SyntaxNode {
/// This is only used for syntax highlighting.
pub fn parse_code(text: &str) -> SyntaxNode {
let mut p = Parser::new(text, 0, LexMode::Code);
- let m = p.marker();
code(&mut p, |_| false);
- p.wrap(m, SyntaxKind::CodeBlock);
p.finish().into_iter().next().unwrap()
}
@@ -31,7 +29,15 @@ fn markup(
mut stop: impl FnMut(SyntaxKind) -> bool,
) {
let m = p.marker();
- while !p.eof() && !stop(p.current) {
+ let mut nesting: usize = 0;
+ while !p.eof() {
+ match p.current() {
+ SyntaxKind::LeftBracket => nesting += 1,
+ SyntaxKind::RightBracket if nesting > 0 => nesting -= 1,
+ _ if stop(p.current) => break,
+ _ => {}
+ }
+
if p.newline() {
at_start = true;
if min_indent > 0 && p.column(p.current_end()) < min_indent {
@@ -54,10 +60,18 @@ pub(super) fn reparse_markup(
text: &str,
range: Range<usize>,
at_start: &mut bool,
+ nesting: &mut usize,
mut stop: impl FnMut(SyntaxKind) -> bool,
) -> Option<Vec<SyntaxNode>> {
let mut p = Parser::new(text, range.start, LexMode::Markup);
- while !p.eof() && !stop(p.current) && p.current_start() < range.end {
+ while !p.eof() && p.current_start() < range.end {
+ match p.current() {
+ SyntaxKind::LeftBracket => *nesting += 1,
+ SyntaxKind::RightBracket if *nesting > 0 => *nesting -= 1,
+ _ if stop(p.current) => break,
+ _ => {}
+ }
+
if p.newline() {
*at_start = true;
p.eat();
@@ -75,53 +89,41 @@ pub(super) fn reparse_markup(
fn markup_expr(p: &mut Parser, at_start: &mut bool) {
match p.current() {
+ SyntaxKind::Space
+ | SyntaxKind::Parbreak
+ | SyntaxKind::LineComment
+ | SyntaxKind::BlockComment => {
+ p.eat();
+ return;
+ }
+
+ SyntaxKind::Text
+ | SyntaxKind::Linebreak
+ | SyntaxKind::Escape
+ | SyntaxKind::Shorthand
+ | SyntaxKind::SmartQuote
+ | SyntaxKind::Raw
+ | SyntaxKind::Link
+ | SyntaxKind::Label
+ | SyntaxKind::Ref => p.eat(),
+
+ SyntaxKind::Hashtag => embedded_code_expr(p),
SyntaxKind::Star => strong(p),
SyntaxKind::Underscore => emph(p),
SyntaxKind::HeadingMarker if *at_start => heading(p),
SyntaxKind::ListMarker if *at_start => list_item(p),
SyntaxKind::EnumMarker if *at_start => enum_item(p),
SyntaxKind::TermMarker if *at_start => term_item(p),
- SyntaxKind::Dollar => equation(p),
+ SyntaxKind::Dollar => formula(p),
- SyntaxKind::HeadingMarker
+ SyntaxKind::LeftBracket
+ | SyntaxKind::RightBracket
+ | SyntaxKind::HeadingMarker
| SyntaxKind::ListMarker
| SyntaxKind::EnumMarker
| SyntaxKind::TermMarker
| SyntaxKind::Colon => p.convert(SyntaxKind::Text),
- SyntaxKind::Ident
- | SyntaxKind::Let
- | SyntaxKind::Set
- | SyntaxKind::Show
- | SyntaxKind::If
- | SyntaxKind::While
- | SyntaxKind::For
- | SyntaxKind::Import
- | SyntaxKind::Include
- | SyntaxKind::Break
- | SyntaxKind::Continue
- | SyntaxKind::Return
- | SyntaxKind::LeftBrace
- | SyntaxKind::LeftBracket => embedded_code_expr(p),
-
- SyntaxKind::Text
- | SyntaxKind::Linebreak
- | SyntaxKind::Escape
- | SyntaxKind::Shorthand
- | SyntaxKind::Symbol
- | SyntaxKind::SmartQuote
- | SyntaxKind::Raw
- | SyntaxKind::Link
- | SyntaxKind::Label
- | SyntaxKind::Ref => p.eat(),
-
- SyntaxKind::Space
- | SyntaxKind::Parbreak
- | SyntaxKind::LineComment
- | SyntaxKind::BlockComment => {
- p.eat();
- return;
- }
_ => {}
}
@@ -130,7 +132,7 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
fn strong(p: &mut Parser) {
let m = p.marker();
- p.expect(SyntaxKind::Star);
+ p.assert(SyntaxKind::Star);
markup(p, false, 0, |kind| {
kind == SyntaxKind::Star
|| kind == SyntaxKind::Parbreak
@@ -142,7 +144,7 @@ fn strong(p: &mut Parser) {
fn emph(p: &mut Parser) {
let m = p.marker();
- p.expect(SyntaxKind::Underscore);
+ p.assert(SyntaxKind::Underscore);
markup(p, false, 0, |kind| {
kind == SyntaxKind::Underscore
|| kind == SyntaxKind::Parbreak
@@ -154,7 +156,7 @@ fn emph(p: &mut Parser) {
fn heading(p: &mut Parser) {
let m = p.marker();
- p.expect(SyntaxKind::HeadingMarker);
+ p.assert(SyntaxKind::HeadingMarker);
whitespace(p);
markup(p, false, usize::MAX, |kind| {
kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket
@@ -164,7 +166,7 @@ fn heading(p: &mut Parser) {
fn list_item(p: &mut Parser) {
let m = p.marker();
- p.expect(SyntaxKind::ListMarker);
+ p.assert(SyntaxKind::ListMarker);
let min_indent = p.column(p.prev_end());
whitespace(p);
markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket);
@@ -173,7 +175,7 @@ fn list_item(p: &mut Parser) {
fn enum_item(p: &mut Parser) {
let m = p.marker();
- p.expect(SyntaxKind::EnumMarker);
+ p.assert(SyntaxKind::EnumMarker);
let min_indent = p.column(p.prev_end());
whitespace(p);
markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket);
@@ -182,7 +184,7 @@ fn enum_item(p: &mut Parser) {
fn term_item(p: &mut Parser) {
let m = p.marker();
- p.expect(SyntaxKind::TermMarker);
+ p.assert(SyntaxKind::TermMarker);
let min_indent = p.column(p.prev_end());
whitespace(p);
markup(p, false, usize::MAX, |kind| {
@@ -200,17 +202,18 @@ fn whitespace(p: &mut Parser) {
}
}
-fn equation(p: &mut Parser) {
+fn formula(p: &mut Parser) {
let m = p.marker();
p.enter(LexMode::Math);
- p.expect(SyntaxKind::Dollar);
+ p.assert(SyntaxKind::Dollar);
math(p, |kind| kind == SyntaxKind::Dollar);
p.expect(SyntaxKind::Dollar);
p.exit();
- p.wrap(m, SyntaxKind::Math);
+ p.wrap(m, SyntaxKind::Formula);
}
fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
+ let m = p.marker();
while !p.eof() && !stop(p.current()) {
let prev = p.prev_end();
math_expr(p);
@@ -218,6 +221,7 @@ fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
p.unexpected();
}
}
+ p.wrap(m, SyntaxKind::Math);
}
fn math_expr(p: &mut Parser) {
@@ -227,45 +231,44 @@ fn math_expr(p: &mut Parser) {
fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
let m = p.marker();
match p.current() {
- SyntaxKind::Ident => {
+ SyntaxKind::Hashtag => embedded_code_expr(p),
+ SyntaxKind::MathIdent => {
p.eat();
- if p.directly_at(SyntaxKind::Atom) && p.current_text() == "(" {
+ if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" {
math_args(p);
p.wrap(m, SyntaxKind::FuncCall);
+ } else {
+ while p.directly_at(SyntaxKind::MathAtom)
+ && p.current_text() == "."
+ && matches!(
+ p.lexer.clone().next(),
+ SyntaxKind::MathIdent | SyntaxKind::MathAtom
+ )
+ {
+ p.convert(SyntaxKind::Dot);
+ p.convert(SyntaxKind::Ident);
+ p.wrap(m, SyntaxKind::FieldAccess);
+ }
}
}
- SyntaxKind::Atom if math_class(p.current_text()) == Some(MathClass::Fence) => {
- math_delimited(p, MathClass::Fence)
- }
-
- SyntaxKind::Atom if math_class(p.current_text()) == Some(MathClass::Opening) => {
- math_delimited(p, MathClass::Closing)
+ SyntaxKind::MathAtom => {
+ if math_class(p.current_text()) == Some(MathClass::Fence) {
+ math_delimited(p, MathClass::Fence)
+ } else if math_class(p.current_text()) == Some(MathClass::Opening) {
+ math_delimited(p, MathClass::Closing)
+ } else {
+ p.eat()
+ }
}
- SyntaxKind::Let
- | SyntaxKind::Set
- | SyntaxKind::Show
- | SyntaxKind::If
- | SyntaxKind::While
- | SyntaxKind::For
- | SyntaxKind::Import
- | SyntaxKind::Include
- | SyntaxKind::Break
- | SyntaxKind::Continue
- | SyntaxKind::Return
- | SyntaxKind::LeftBrace
- | SyntaxKind::LeftBracket => embedded_code_expr(p),
-
- SyntaxKind::Atom
- | SyntaxKind::Linebreak
+ SyntaxKind::Linebreak
| SyntaxKind::Escape
| SyntaxKind::Shorthand
- | SyntaxKind::Symbol
- | SyntaxKind::AlignPoint
+ | SyntaxKind::MathAlignPoint
| SyntaxKind::Str => p.eat(),
- _ => return,
+ _ => p.expected("expression"),
}
while !p.eof() && !p.at(stop) {
@@ -282,10 +285,19 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
ast::Assoc::Right => {}
}
+ if kind == SyntaxKind::MathFrac {
+ math_unparen(p, m);
+ }
+
p.eat();
+ let m2 = p.marker();
math_expr_prec(p, prec, stop);
+ math_unparen(p, m2);
+
if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) {
+ let m3 = p.marker();
math_expr_prec(p, prec, SyntaxKind::Eof);
+ math_unparen(p, m3);
}
p.wrap(m, kind);
@@ -294,11 +306,13 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
fn math_delimited(p: &mut Parser, stop: MathClass) {
let m = p.marker();
- p.expect(SyntaxKind::Atom);
+ p.assert(SyntaxKind::MathAtom);
+ let m2 = p.marker();
while !p.eof() && !p.at(SyntaxKind::Dollar) {
if math_class(p.current_text()) == Some(stop) {
- p.eat();
- p.wrap(m, SyntaxKind::Delimited);
+ p.wrap(m2, SyntaxKind::Math);
+ p.assert(SyntaxKind::MathAtom);
+ p.wrap(m, SyntaxKind::MathDelimited);
return;
}
@@ -310,6 +324,22 @@ fn math_delimited(p: &mut Parser, stop: MathClass) {
}
}
+fn math_unparen(p: &mut Parser, m: Marker) {
+ let Some(node) = p.nodes.get_mut(m.0) else { return };
+ if node.kind() != SyntaxKind::MathDelimited {
+ return;
+ }
+
+ if let [first, .., last] = node.children_mut() {
+ if first.text() == "(" && last.text() == ")" {
+ first.convert_to_kind(SyntaxKind::LeftParen);
+ last.convert_to_kind(SyntaxKind::RightParen);
+ }
+ }
+
+ node.convert_to_kind(SyntaxKind::Math);
+}
+
fn math_class(text: &str) -> Option<MathClass> {
let mut chars = text.chars();
chars
@@ -321,20 +351,20 @@ fn math_class(text: &str) -> Option<MathClass> {
fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> {
match kind {
SyntaxKind::Underscore => {
- Some((SyntaxKind::Script, SyntaxKind::Hat, ast::Assoc::Right, 2))
+ Some((SyntaxKind::MathScript, SyntaxKind::Hat, ast::Assoc::Right, 2))
}
SyntaxKind::Hat => {
- Some((SyntaxKind::Script, SyntaxKind::Underscore, ast::Assoc::Right, 2))
+ Some((SyntaxKind::MathScript, SyntaxKind::Underscore, ast::Assoc::Right, 2))
}
SyntaxKind::Slash => {
- Some((SyntaxKind::Frac, SyntaxKind::Eof, ast::Assoc::Left, 1))
+ Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1))
}
_ => None,
}
}
fn math_args(p: &mut Parser) {
- p.expect(SyntaxKind::Atom);
+ p.assert(SyntaxKind::MathAtom);
let m = p.marker();
let mut m2 = p.marker();
while !p.eof() && !p.at(SyntaxKind::Dollar) {
@@ -359,10 +389,11 @@ fn math_args(p: &mut Parser) {
p.wrap(m2, SyntaxKind::Math);
}
p.wrap(m, SyntaxKind::Args);
- p.expect(SyntaxKind::Atom);
+ p.expect(SyntaxKind::MathAtom);
}
fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
+ let m = p.marker();
while !p.eof() && !stop(p.current()) {
p.stop_at_newline(true);
let prev = p.prev_end();
@@ -379,6 +410,7 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
p.unexpected();
}
}
+ p.wrap(m, SyntaxKind::Code);
}
fn code_expr(p: &mut Parser) {
@@ -386,6 +418,10 @@ fn code_expr(p: &mut Parser) {
}
fn embedded_code_expr(p: &mut Parser) {
+ p.stop_at_newline(true);
+ p.enter(LexMode::Code);
+ p.assert(SyntaxKind::Hashtag);
+
let stmt = matches!(
p.current(),
SyntaxKind::Let
@@ -395,13 +431,12 @@ fn embedded_code_expr(p: &mut Parser) {
| SyntaxKind::Include
);
- p.stop_at_newline(true);
- p.enter(LexMode::Code);
code_expr_prec(p, true, 0);
let semi = p.eat_if(SyntaxKind::Semicolon);
if stmt && !semi && !p.eof() && !p.at(SyntaxKind::RightBracket) {
p.expected("semicolon or line break");
}
+
p.exit();
p.unstop();
}
@@ -424,7 +459,10 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
continue;
}
- if atomic {
+ let at_field_or_method =
+ p.directly_at(SyntaxKind::Dot) && p.lexer.clone().next() == SyntaxKind::Ident;
+
+ if atomic && !at_field_or_method {
break;
}
@@ -480,7 +518,7 @@ fn code_primary(p: &mut Parser, atomic: bool) {
p.eat();
if !atomic && p.at(SyntaxKind::Arrow) {
p.wrap(m, SyntaxKind::Params);
- p.expect(SyntaxKind::Arrow);
+ p.assert(SyntaxKind::Arrow);
code_expr(p);
p.wrap(m, SyntaxKind::Closure);
}
@@ -489,7 +527,7 @@ fn code_primary(p: &mut Parser, atomic: bool) {
SyntaxKind::LeftBrace => code_block(p),
SyntaxKind::LeftBracket => content_block(p),
SyntaxKind::LeftParen => with_paren(p),
- SyntaxKind::Dollar => equation(p),
+ SyntaxKind::Dollar => formula(p),
SyntaxKind::Let => let_binding(p),
SyntaxKind::Set => set_rule(p),
SyntaxKind::Show => show_rule(p),
@@ -536,7 +574,7 @@ fn code_block(p: &mut Parser) {
let m = p.marker();
p.enter(LexMode::Code);
p.stop_at_newline(false);
- p.expect(SyntaxKind::LeftBrace);
+ p.assert(SyntaxKind::LeftBrace);
code(p, |kind| kind == SyntaxKind::RightBrace);
p.expect(SyntaxKind::RightBrace);
p.exit();
@@ -547,7 +585,7 @@ fn code_block(p: &mut Parser) {
fn content_block(p: &mut Parser) {
let m = p.marker();
p.enter(LexMode::Markup);
- p.expect(SyntaxKind::LeftBracket);
+ p.assert(SyntaxKind::LeftBracket);
markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket);
p.expect(SyntaxKind::RightBracket);
p.exit();
@@ -560,7 +598,7 @@ fn with_paren(p: &mut Parser) {
if p.at(SyntaxKind::Arrow) {
validate_params(p, m);
p.wrap(m, SyntaxKind::Params);
- p.expect(SyntaxKind::Arrow);
+ p.assert(SyntaxKind::Arrow);
code_expr(p);
kind = SyntaxKind::Closure;
}
@@ -574,7 +612,7 @@ fn with_paren(p: &mut Parser) {
fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind {
p.stop_at_newline(false);
- p.expect(SyntaxKind::LeftParen);
+ p.assert(SyntaxKind::LeftParen);
let mut count = 0;
let mut parenthesized = true;
diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs
index 4c01e4cc..de845abf 100644
--- a/src/syntax/reparser.rs
+++ b/src/syntax/reparser.rs
@@ -114,20 +114,30 @@ fn try_reparse(
end += 1;
}
- // Synthesize what `at_start` would be at the start of the reparse.
+ // Also take hashtag.
+ if start > 0 && children[start - 1].kind() == SyntaxKind::Hashtag {
+ start -= 1;
+ }
+
+ // Synthesize what `at_start` and `nesting` would be at the start of the
+ // reparse.
let mut prefix_len = 0;
+ let mut nesting = 0;
let mut at_start = true;
for child in &children[..start] {
prefix_len += child.len();
next_at_start(child, &mut at_start);
+ next_nesting(child, &mut nesting);
}
// Determine what `at_start` will have to be at the end of the reparse.
let mut prev_len = 0;
let mut prev_at_start_after = at_start;
+ let mut prev_nesting_after = nesting;
for child in &children[start..end] {
prev_len += child.len();
next_at_start(child, &mut prev_at_start_after);
+ next_nesting(child, &mut prev_nesting_after);
}
let shifted = offset + prefix_len;
@@ -139,11 +149,11 @@ fn try_reparse(
};
if let Some(newborns) =
- reparse_markup(text, new_range.clone(), &mut at_start, |kind| {
+ reparse_markup(text, new_range.clone(), &mut at_start, &mut nesting, |kind| {
kind == stop_kind
})
{
- if at_start == prev_at_start_after {
+ if at_start == prev_at_start_after && nesting == prev_nesting_after {
return node
.replace_children(start..end, newborns)
.is_ok()
@@ -188,6 +198,17 @@ fn next_at_start(node: &SyntaxNode, at_start: &mut bool) {
}
}
+/// Update `nesting` based on the node.
+fn next_nesting(node: &SyntaxNode, nesting: &mut usize) {
+ if node.kind() == SyntaxKind::Text {
+ match node.text().as_str() {
+ "[" => *nesting += 1,
+ "]" if *nesting > 0 => *nesting -= 1,
+ _ => {}
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use std::ops::Range;
@@ -209,9 +230,13 @@ mod tests {
panic!("test failed");
}
if incremental {
- assert_ne!(source.len_bytes(), range.len());
+ assert_ne!(source.len_bytes(), range.len(), "should have been incremental");
} else {
- assert_eq!(source.len_bytes(), range.len());
+ assert_eq!(
+ source.len_bytes(),
+ range.len(),
+ "shouldn't have been incremental"
+ );
}
}
@@ -220,6 +245,7 @@ mod tests {
test("abc~def~ghi", 5..6, "+", true);
test("~~~~~~~", 3..4, "A", true);
test("abc~~", 1..2, "", true);
+ test("#var. hello", 5..6, " ", false);
test("#var;hello", 9..10, "a", false);
test("https:/world", 7..7, "/", false);
test("hello world", 7..12, "walkers", false);
@@ -228,8 +254,8 @@ mod tests {
test("a d e", 1..3, " b c d", false);
test("~*~*~", 2..2, "*", false);
test("::1\n2. a\n3", 7..7, "4", true);
- test("* {1+2} *", 5..6, "3", true);
- test("{(0, 1, 2)}", 5..6, "11pt", false);
+ test("* #{1+2} *", 6..7, "3", true);
+ test("#{(0, 1, 2)}", 6..7, "11pt", true);
test("\n= A heading", 4..4, "n evocative", false);
test("#call() abc~d", 7..7, "[]", true);
test("a your thing a", 6..7, "a", false);
@@ -239,24 +265,26 @@ mod tests {
test("#for", 4..4, "//", false);
test("a\n#let \nb", 7..7, "i", true);
test("#let x = (1, 2 + ;~ Five\r\n\r", 20..23, "2.", true);
- test(r"{{let x = z}; a = 1} b", 6..6, "//", false);
+ test(r"#{{let x = z}; a = 1} b", 7..7, "//", false);
test(r#"a ```typst hello```"#, 16..17, "", false);
}
#[test]
fn test_reparse_block() {
- test("Hello { x + 1 }!", 8..9, "abc", true);
- test("A{}!", 2..2, "\"", false);
- test("{ [= x] }!", 4..4, "=", true);
- test("[[]]", 2..2, "\\", false);
- test("[[ab]]", 3..4, "\\", false);
- test("{}}", 1..1, "{", false);
- test("A: [BC]", 5..5, "{", false);
- test("A: [BC]", 5..5, "{}", true);
- test("{\"ab\"}A", 4..4, "c", true);
- test("{\"ab\"}A", 4..5, "c", false);
- test("a[]b", 2..2, "{", false);
- test("a{call(); abc}b", 7..7, "[]", true);
+ test("Hello #{ x + 1 }!", 9..10, "abc", true);
+ test("A#{}!", 3..3, "\"", false);
+ test("#{ [= x] }!", 5..5, "=", true);
+ test("#[[]]", 3..3, "\\", false);
+ test("#[[ab]]", 4..5, "\\", false);
+ test("#{}}", 2..2, "{", false);
+ test("A: #[BC]", 6..6, "{", true);
+ test("A: #[BC]", 6..6, "#{", false);
+ test("A: #[BC]", 6..6, "#{}", true);
+ test("#{\"ab\"}A", 5..5, "c", true);
+ test("#{\"ab\"}A", 5..6, "c", false);
+ test("a#[]b", 3..3, "#{", false);
+ test("a#{call(); abc}b", 8..8, "[]", true);
test("a #while x {\n g(x) \n} b", 12..12, "//", true);
+ test("a#[]b", 3..3, "[hey]", true);
}
}
diff --git a/tests/typ/basics/heading.typ b/tests/typ/basics/heading.typ
index 4e9550b9..d843a2e8 100644
--- a/tests/typ/basics/heading.typ
+++ b/tests/typ/basics/heading.typ
@@ -21,7 +21,7 @@ No heading
// Parsed as headings if at start of the context.
/**/ = Level 1
-{[== Level 2]}
+#{[== Level 2]}
#box[=== Level 3]
// Not at the start of the context.
@@ -33,7 +33,7 @@ No = heading
---
// Blocks can continue the heading.
-= [This
+= #[This
is
multiline.
]
diff --git a/tests/typ/basics/list.typ b/tests/typ/basics/list.typ
index b8bd59ea..1c111dcb 100644
--- a/tests/typ/basics/list.typ
+++ b/tests/typ/basics/list.typ
@@ -24,7 +24,7 @@ _Shopping list_
---
- Level 1
- - Level [
+ - Level #[
2 through content block
]
diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ
index e01f8896..3d4d6106 100644
--- a/tests/typ/compiler/array.typ
+++ b/tests/typ/compiler/array.typ
@@ -7,19 +7,19 @@
#set page(width: 150pt)
// Empty.
-{()}
+#{()}
// Not an array, just a parenthesized expression.
-{(1)}
+#{(1)}
// One item and trailing comma.
-{(-1,)}
+#{(-1,)}
// No trailing comma.
-{(true, false)}
+#{(true, false)}
// Multiple lines and items and trailing comma.
-{("1"
+#{("1"
, rgb("002")
,)}
@@ -30,7 +30,7 @@
---
// Test lvalue and rvalue access.
-{
+#{
let array = (1, 2)
array.at(1) += 5 + array.at(0)
test(array, (1, 8))
@@ -38,7 +38,7 @@
---
// Test different lvalue method.
-{
+#{
let array = (1, 2, 3)
array.first() = 7
array.at(1) *= 8
@@ -47,12 +47,12 @@
---
// Test rvalue out of bounds.
-// Error: 2-17 array index out of bounds (index: 5, len: 3)
-{(1, 2, 3).at(5)}
+// Error: 3-18 array index out of bounds (index: 5, len: 3)
+#{(1, 2, 3).at(5)}
---
// Test lvalue out of bounds.
-{
+#{
let array = (1, 2, 3)
// Error: 3-14 array index out of bounds (index: 3, len: 3)
array.at(3) = 5
@@ -60,19 +60,19 @@
---
// Test bad lvalue.
-// Error: 2:3-2:14 cannot mutate a temporary value
+// Error: 2:4-2:15 cannot mutate a temporary value
#let array = (1, 2, 3)
-{ array.len() = 4 }
+#{ array.len() = 4 }
---
// Test bad lvalue.
-// Error: 2:3-2:15 type array has no method `yolo`
+// Error: 2:4-2:16 type array has no method `yolo`
#let array = (1, 2, 3)
-{ array.yolo() = 4 }
+#{ array.yolo() = 4 }
---
// Test negative indices.
-{
+#{
let array = (1, 2, 3, 4)
test(array.at(0), 1)
test(array.at(-1), 4)
@@ -89,16 +89,16 @@
#test((1, 2, 3).last(), 3)
---
-// Error: 3-13 array is empty
-{ ().first() }
+// Error: 4-14 array is empty
+#{ ().first() }
---
-// Error: 3-12 array is empty
-{ ().last() }
+// Error: 4-13 array is empty
+#{ ().last() }
---
// Test the `push` and `pop` methods.
-{
+#{
let tasks = (a: (1, 2, 3), b: (4, 5, 6))
tasks.at("a").pop()
tasks.b.push(7)
@@ -108,7 +108,7 @@
---
// Test the `insert` and `remove` methods.
-{
+#{
let array = (0, 1, 2, 4, 5)
array.insert(3, 3)
test(array, range(6))
@@ -117,9 +117,10 @@
}
---
-// Error: 2:17-2:19 missing argument: index
+// Error: 2:18-2:20 missing argument: index
#let numbers = ()
-{ numbers.insert() }
+#{ numbers.insert() }
+
---
// Test the `slice` method.
#test((1, 2, 3, 4).slice(2), (3, 4))
@@ -132,12 +133,12 @@
#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D")
---
-// Error: 3-31 array index out of bounds (index: 12, len: 10)
-{ range(10).slice(9, count: 3) }
+// Error: 4-32 array index out of bounds (index: 12, len: 10)
+#{ range(10).slice(9, count: 3) }
---
-// Error: 3-25 array index out of bounds (index: -4, len: 3)
-{ (1, 2, 3).slice(0, -4) }
+// Error: 4-26 array index out of bounds (index: -4, len: 3)
+#{ (1, 2, 3).slice(0, -4) }
---
// Test the `position` method.
@@ -162,8 +163,8 @@
#test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10)
---
-// Error: 21-31 function must have exactly two parameters
-{ (1, 2, 3).fold(0, () => none) }
+// Error: 22-32 function must have exactly two parameters
+#{ (1, 2, 3).fold(0, () => none) }
---
// Test the `rev` method.
@@ -177,17 +178,17 @@
#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)")
---
-// Error: 2-22 cannot join boolean with boolean
-{(true, false).join()}
+// Error: 3-23 cannot join boolean with boolean
+#{(true, false).join()}
---
-// Error: 2-20 cannot join string with integer
-{("a", "b").join(1)}
+// Error: 3-21 cannot join string with integer
+#{("a", "b").join(1)}
---
// Test joining content.
// Ref: true
-{([One], [Two], [Three]).join([, ], last: [ and ])}.
+#{([One], [Two], [Three]).join([, ], last: [ and ])}.
---
// Test the `sorted` method.
@@ -197,37 +198,37 @@
#test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10))
---
-// Error: 2-26 cannot order content and content
-{([Hi], [There]).sorted()}
+// Error: 3-27 cannot order content and content
+#{([Hi], [There]).sorted()}
---
-// Error: 2-18 array index out of bounds (index: -4, len: 3)
-{(1, 2, 3).at(-4)}
+// Error: 3-19 array index out of bounds (index: -4, len: 3)
+#{(1, 2, 3).at(-4)}
---
-// Error: 3 expected closing paren
-{(}
+// Error: 4 expected closing paren
+#{(}
-// Error: 2-3 unexpected closing paren
-{)}
+// Error: 3-4 unexpected closing paren
+#{)}
-// Error: 4-6 unexpected end of block comment
-{(1*/2)}
+// Error: 5-7 unexpected end of block comment
+#{(1*/2)}
-// Error: 6-8 invalid number suffix
-{(1, 1u 2)}
+// Error: 7-9 invalid number suffix
+#{(1, 1u 2)}
-// Error: 3-4 unexpected comma
-{(,1)}
+// Error: 4-5 unexpected comma
+#{(,1)}
// Missing expression makes named pair incomplete, making this an empty array.
-// Error: 5 expected expression
-{(a:)}
+// Error: 6 expected expression
+#{(a:)}
// Named pair after this is already identified as an array.
-// Error: 6-10 expected expression, found named pair
-{(1, b: 2)}
+// Error: 7-11 expected expression, found named pair
+#{(1, b: 2)}
// Keyed pair after this is already identified as an array.
-// Error: 6-14 expected expression, found keyed pair
-{(1, "key": 2)}
+// Error: 7-15 expected expression, found keyed pair
+#{(1, "key": 2)}
diff --git a/tests/typ/compiler/bench.typ b/tests/typ/compiler/bench.typ
index 6aff1ac2..22795966 100644
--- a/tests/typ/compiler/bench.typ
+++ b/tests/typ/compiler/bench.typ
@@ -7,7 +7,7 @@
// ... but also "content" values. While these contain markup,
// they are also values and can be summed, stored in arrays etc.
// There are also more standard control flow structures, like #if and #for.
-#let university = [*Technische Universität {city}*]
+#let university = [*Technische Universität #{city}*]
#let faculty = [*Fakultät II, Institut for Mathematik*]
// The `box` function just places content into a rectangular container. When
diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ
index 7fb7738b..204cab1e 100644
--- a/tests/typ/compiler/block.typ
+++ b/tests/typ/compiler/block.typ
@@ -5,14 +5,14 @@
// Ref: true
// Evaluates to join of none, [My ] and the two loop bodies.
-{
+#{
let parts = ("my fri", "end.")
[Hello, ]
- for s in parts [{s}]
+ for s in parts [#s]
}
// Evaluates to join of the content and strings.
-{
+#{
[How]
if true {
" are"
@@ -50,7 +50,7 @@
---
// Some things can't be joined.
-{
+#{
[A]
// Error: 3-4 cannot join content with integer
1
@@ -59,7 +59,7 @@
---
// Block directly in markup also creates a scope.
-{ let x = 1 }
+#{ let x = 1 }
// Error: 7-8 unknown variable
#test(x, 1)
@@ -73,22 +73,22 @@
#test(a, 1)
-// Error: 2-3 unknown variable
-{b}
+// Error: 3-4 unknown variable
+#{b}
---
// Double block creates a scope.
-{{
+#{{
import "module.typ": b
test(b, 1)
}}
-// Error: 2-3 unknown variable
-{b}
+// Error: 3-4 unknown variable
+#{b}
---
// Multiple nested scopes.
-{
+#{
let a = "a1"
{
let a = "a2"
@@ -104,28 +104,28 @@
---
// Content blocks also create a scope.
-[#let x = 1]
+#[#let x = 1]
-// Error: 2-3 unknown variable
-{x}
+// Error: 3-4 unknown variable
+#{x}
---
// Multiple unseparated expressions in one line.
-// Error: 2-4 invalid number suffix
-{1u}
+// Error: 3-5 invalid number suffix
+#{1u}
// Should output `1`.
-// Error: 3 expected semicolon or line break
-{1 2}
+// Error: 4 expected semicolon or line break
+#{1 2}
// Should output `2`.
-// Error: 12 expected semicolon or line break
-// Error: 22 expected semicolon or line break
-{let x = -1 let y = 3 x + y}
+// Error: 13 expected semicolon or line break
+// Error: 23 expected semicolon or line break
+#{let x = -1 let y = 3 x + y}
// Should output `3`.
-{
+#{
// Error: 6 expected identifier
// Error: 10 expected block
for "v"
@@ -138,9 +138,9 @@
}
---
-// Error: 2 expected closing brace
-{
+// Error: 3 expected closing brace
+#{
---
-// Error: 1-2 unexpected closing brace
-}
+// Error: 2 expected expression
+#}
diff --git a/tests/typ/compiler/break-continue.typ b/tests/typ/compiler/break-continue.typ
index fb3222fa..566234a7 100644
--- a/tests/typ/compiler/break-continue.typ
+++ b/tests/typ/compiler/break-continue.typ
@@ -96,7 +96,7 @@
#let x = { continue }
---
-// Error: 1-10 cannot continue outside of loop
+// Error: 2-10 cannot continue outside of loop
#continue
---
@@ -104,7 +104,7 @@
// Should output `Hello World 🌎`.
#for _ in range(10) {
[Hello ]
- [World {
+ [World #{
[🌎]
break
}]
diff --git a/tests/typ/compiler/call.typ b/tests/typ/compiler/call.typ
index 087e4694..690d55d3 100644
--- a/tests/typ/compiler/call.typ
+++ b/tests/typ/compiler/call.typ
@@ -6,7 +6,7 @@
// Ommitted space.
#let f() = {}
-[#f()*Bold*]
+#[#f()*Bold*]
// Call return value of function with body.
#let f(x, body) = (y) => [#x] + body + [#y]
@@ -34,7 +34,7 @@
#test(alias(alias), "function")
// Callee expressions.
-{
+#{
// Wrapped in parens.
test((type)("hi"), "string")
@@ -48,25 +48,25 @@
#set text(family: "Arial", family: "Helvetica")
---
-// Error: 2-6 expected function, found boolean
-{true()}
+// Error: 3-7 expected function, found boolean
+#{true()}
---
#let x = "x"
-// Error: 1-3 expected function, found string
+// Error: 2-3 expected function, found string
#x()
---
#let f(x) = x
-// Error: 1-6 expected function, found integer
+// Error: 2-6 expected function, found integer
#f(1)(2)
---
#let f(x) = x
-// Error: 1-6 expected function, found content
+// Error: 2-6 expected function, found content
#f[1](2)
---
@@ -90,16 +90,16 @@
// Error: 7-12 expected identifier, found string
#func("abc": 2)
-// Error: 7-10 expected identifier, found group
-{func((x):1)}
+// Error: 8-11 expected identifier, found group
+#{func((x):1)}
---
// Error: 2:1 expected closing bracket
#func[`a]`
---
-// Error: 7 expected closing paren
-{func(}
+// Error: 8 expected closing paren
+#{func(}
---
// Error: 2:1 expected quote
diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ
index f1604b19..1551a66d 100644
--- a/tests/typ/compiler/closure.typ
+++ b/tests/typ/compiler/closure.typ
@@ -12,7 +12,7 @@
---
// Basic closure without captures.
-{
+#{
let adder = (x, y) => x + y
test(adder(2, 3), 5)
}
@@ -20,7 +20,7 @@
---
// Pass closure as argument and return closure.
// Also uses shorthand syntax for a single argument.
-{
+#{
let chain = (f, g) => (x) => f(g(x))
let f = x => x + 1
let g = x => 2 * x
@@ -30,7 +30,7 @@
---
// Capture environment.
-{
+#{
let mark = "!"
let greet = {
let hi = "Hi"
@@ -48,7 +48,7 @@
---
// Redefined variable.
-{
+#{
let x = 1
let f() = {
let x = x + 2
@@ -59,7 +59,7 @@
---
// Import bindings.
-{
+#{
let b = "module.typ"
let f() = {
import b: b
@@ -70,7 +70,7 @@
---
// For loop bindings.
-{
+#{
let v = (1, 2, 3)
let f() = {
let s = 0
@@ -82,7 +82,7 @@
---
// Let + closure bindings.
-{
+#{
let g = "hi"
let f() = {
let g() = "bye"
@@ -93,7 +93,7 @@
---
// Parameter bindings.
-{
+#{
let x = 5
let g() = {
let f(x, y: x) = x + y
@@ -105,7 +105,7 @@
---
// Don't leak environment.
-{
+#{
// Error: 16-17 unknown variable
let func() = x
let x = "hi"
@@ -114,7 +114,7 @@
---
// Too few arguments.
-{
+#{
let types(x, y) = "[" + type(x) + ", " + type(y) + "]"
test(types(14%, 12pt), "[ratio, length]")
@@ -124,7 +124,7 @@
---
// Too many arguments.
-{
+#{
let f(x) = x + 1
// Error: 8-13 unexpected argument
@@ -133,7 +133,7 @@
---
// Named arguments.
-{
+#{
let greet(name, birthday: false) = {
if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!"
}
@@ -156,8 +156,8 @@
#let f(a, b, a: none, b: none, c, b) = none
---
-// Error: 6-16 expected identifier, named pair or argument sink, found keyed pair
-{(a, "named": b) => none}
+// Error: 7-17 expected identifier, named pair or argument sink, found keyed pair
+#{(a, "named": b) => none}
---
// Error: 10-15 expected identifier, found string
diff --git a/tests/typ/compiler/construct.typ b/tests/typ/compiler/construct.typ
index 3ed8fed3..161827cb 100644
--- a/tests/typ/compiler/construct.typ
+++ b/tests/typ/compiler/construct.typ
@@ -18,7 +18,7 @@
---
// The inner rectangle should also be yellow here.
// (and therefore invisible)
-[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
+#[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
---
// The inner rectangle should not be yellow here.
diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ
index 5f00ef3a..c34b5478 100644
--- a/tests/typ/compiler/dict.typ
+++ b/tests/typ/compiler/dict.typ
@@ -5,7 +5,7 @@
// Ref: true
// Empty
-{(:)}
+#{(:)}
// Two pairs and string key.
#let dict = (normal: 1, "spacy key": 2)
@@ -16,7 +16,7 @@
---
// Test lvalue and rvalue access.
-{
+#{
let dict = (a: 1, "b b": 1)
dict.at("b b") += 1
dict.state = (ok: true, err: false)
@@ -29,7 +29,7 @@
---
// Test rvalue missing key.
-{
+#{
let dict = (a: 1, b: 2)
// Error: 11-23 dictionary does not contain key "c"
let x = dict.at("c")
@@ -37,7 +37,7 @@
---
// Missing lvalue is not automatically none-initialized.
-{
+#{
let dict = (:)
// Error: 3-9 dictionary does not contain key "b"
dict.b += 1
@@ -51,39 +51,39 @@
#test(dict.values(), (3, 1, 2))
#test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2")
-{ dict.remove("c") }
+#{ dict.remove("c") }
#test("c" in dict, false)
#test(dict, (a: 3, b: 1))
---
-// Error: 24-29 duplicate key
-{(first: 1, second: 2, first: 3)}
+// Error: 25-30 duplicate key
+#{(first: 1, second: 2, first: 3)}
---
-// Error: 17-20 duplicate key
-{(a: 1, "b": 2, "a": 3)}
+// Error: 18-21 duplicate key
+#{(a: 1, "b": 2, "a": 3)}
---
// Simple expression after already being identified as a dictionary.
-// Error: 9-10 expected named or keyed pair, found identifier
-{(a: 1, b)}
+// Error: 10-11 expected named or keyed pair, found identifier
+#{(a: 1, b)}
// Identified as dictionary due to initial colon.
-// Error: 4-5 expected named or keyed pair, found integer
-// Error: 5 expected comma
-// Error: 12-16 expected identifier or string, found boolean
-// Error: 17 expected expression
-{(:1 b:"", true:)}
+// Error: 5-6 expected named or keyed pair, found integer
+// Error: 6 expected comma
+// Error: 13-17 expected identifier or string, found boolean
+// Error: 18 expected expression
+#{(:1 b:"", true:)}
-// Error: 3-8 expected identifier or string, found binary expression
-{(a + b: "hey")}
+// Error: 4-9 expected identifier or string, found binary expression
+#{(a + b: "hey")}
---
-// Error: 3-15 cannot mutate a temporary value
-{ (key: "val").other = "some" }
+// Error: 4-16 cannot mutate a temporary value
+#{ (key: "val").other = "some" }
---
-{
+#{
let object = none
// Error: 3-9 expected dictionary, found none
object.property = "value"
diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ
index 0195c6d8..c99d910d 100644
--- a/tests/typ/compiler/field.typ
+++ b/tests/typ/compiler/field.typ
@@ -5,7 +5,7 @@
// Test field on dictionary.
#let dict = (nothing: "ness", hello: "world")
#test(dict.nothing, "ness")
-{
+#{
let world = dict
.hello
@@ -23,12 +23,12 @@
- C
---
-// Error: 6-13 dictionary does not contain key "invalid"
-{(:).invalid}
+// Error: 7-14 dictionary does not contain key "invalid"
+#{(:).invalid}
---
-// Error: 2-7 expected dictionary or content, found boolean
-{false.ok}
+// Error: 9-11 cannot access fields on type boolean
+#{false.ok}
---
// Error: 29-32 unknown field `fun`
@@ -36,6 +36,6 @@
= A
---
-// Error: 8 expected identifier
-// Error: 8 expected semicolon or line break
-{false.true}
+// Error: 9 expected identifier
+// Error: 9 expected semicolon or line break
+#{false.true}
diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ
index 7a530b73..cf717411 100644
--- a/tests/typ/compiler/for.typ
+++ b/tests/typ/compiler/for.typ
@@ -10,12 +10,12 @@
// Dictionary is not traversed in insertion order.
// Should output `Age: 2. Name: Typst.`.
#for k, v in (Name: "Typst", Age: 2) [
- {k}: {v}.
+ #k: #v.
]
// Block body.
// Should output `[1st, 2nd, 3rd, 4th]`.
-{
+#{
"["
for v in (1, 2, 3, 4) {
if v > 1 [, ]
@@ -97,8 +97,8 @@
// Error: 5 expected identifier
#for//
-// Error: 5 expected identifier
-{for}
+// Error: 6 expected identifier
+#{for}
// Error: 7 expected keyword `in`
#for v
diff --git a/tests/typ/compiler/if.typ b/tests/typ/compiler/if.typ
index 3b35ebd8..23ed9259 100644
--- a/tests/typ/compiler/if.typ
+++ b/tests/typ/compiler/if.typ
@@ -40,7 +40,7 @@
}
// Content block can be argument or body depending on whitespace.
-{
+#{
if "content" == type[b] [Fi] else [Nope]
if "content" == type [Nope] else [ve.]
}
@@ -76,7 +76,7 @@
// Value of if expressions.
// Ref: false
-{
+#{
let x = 1
let y = 2
let z
@@ -109,13 +109,13 @@
// Error: 4 expected expression
#if
-// Error: 4 expected expression
-{if}
+// Error: 5 expected expression
+#{if}
// Error: 6 expected block
#if x
-// Error: 1-6 unexpected keyword `else`
+// Error: 2 expected expression
#else {}
// Should output `x`.
diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ
index a657be50..f97b1ae0 100644
--- a/tests/typ/compiler/import.typ
+++ b/tests/typ/compiler/import.typ
@@ -49,8 +49,8 @@
#test((module,).at(0).item(1, 2), 3)
// Doesn't work because of mutating name.
-// Error: 2-11 cannot mutate a temporary value
-{(module,).at(0).push()}
+// Error: 3-12 cannot mutate a temporary value
+#{(module,).at(0).push()}
---
// Who needs whitespace anyways?
diff --git a/tests/typ/compiler/include.typ b/tests/typ/compiler/include.typ
index 289fea21..a655e9f2 100644
--- a/tests/typ/compiler/include.typ
+++ b/tests/typ/compiler/include.typ
@@ -15,7 +15,7 @@
#chap2
---
-{
+#{
// Error: 19-38 file not found (searched at typ/compiler/modules/chap3.typ)
let x = include "modules/chap3.typ"
}
@@ -24,7 +24,7 @@
#include "modules/chap1.typ"
// The variables of the file should not appear in this scope.
-// Error: 1-6 unknown variable
+// Error: 2-6 unknown variable
#name
---
diff --git a/tests/typ/compiler/label.typ b/tests/typ/compiler/label.typ
index 795c0435..d4ff15d7 100644
--- a/tests/typ/compiler/label.typ
+++ b/tests/typ/compiler/label.typ
@@ -26,7 +26,7 @@ The end.
it
}
-This is a thing [that <last>] happened.
+This is a thing #[that <last>] happened.
---
// Test abusing dynamic labels for styling.
@@ -50,7 +50,7 @@ _Visible_
---
// Test that label only works within one content block.
#show <strike>: strike
-*This is* [<strike>] *protected.*
+*This is* #[<strike>] *protected.*
*This is not.* <strike>
---
diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ
index 3a879ce7..aa1132b9 100644
--- a/tests/typ/compiler/let.typ
+++ b/tests/typ/compiler/let.typ
@@ -36,8 +36,8 @@ Three
// Error: 5 expected identifier
#let
-// Error: 5 expected identifier
-{let}
+// Error: 6 expected identifier
+#{let}
// Error: 5 expected identifier
// Error: 5 expected semicolon or line break
diff --git a/tests/typ/compiler/methods.typ b/tests/typ/compiler/methods.typ
index f468320b..ae0945df 100644
--- a/tests/typ/compiler/methods.typ
+++ b/tests/typ/compiler/methods.typ
@@ -7,7 +7,7 @@
---
// Test mutating indexed value.
-{
+#{
let matrix = (((1,), (2,)), ((3,), (4,)))
matrix.at(1).at(0).push(5)
test(matrix, (((1,), (2,)), ((3, 5), (4,))))
@@ -15,7 +15,7 @@
---
// Test multiline chain in code block.
-{
+#{
let rewritten = "Hello. This is a sentence. And one more."
.split(".")
.map(s => s.trim())
@@ -27,20 +27,20 @@
}
---
-// Error: 2:3-2:16 type array has no method `fun`
+// Error: 2:4-2:17 type array has no method `fun`
#let numbers = ()
-{ numbers.fun() }
+#{ numbers.fun() }
---
-// Error: 2:3-2:44 cannot mutate a temporary value
+// Error: 2:4-2:45 cannot mutate a temporary value
#let numbers = (1, 2, 3)
-{ numbers.map(v => v / 2).sorted().map(str).remove(4) }
+#{ numbers.map(v => v / 2).sorted().map(str).remove(4) }
---
-// Error: 2:3-2:19 cannot mutate a temporary value
+// Error: 2:4-2:20 cannot mutate a temporary value
#let numbers = (1, 2, 3)
-{ numbers.sorted() = 1 }
+#{ numbers.sorted() = 1 }
---
-// Error: 3-6 cannot mutate a constant
-{ box.push(1) }
+// Error: 4-7 cannot mutate a constant
+#{ box.push(1) }
diff --git a/tests/typ/compiler/ops-invalid.typ b/tests/typ/compiler/ops-invalid.typ
index d51e42fb..3356a24c 100644
--- a/tests/typ/compiler/ops-invalid.typ
+++ b/tests/typ/compiler/ops-invalid.typ
@@ -2,8 +2,8 @@
// Ref: false
---
-// Error: 3 expected expression
-{-}
+// Error: 4 expected expression
+#{-}
---
// Error: 10 expected expression
@@ -14,115 +14,115 @@
#test({2*}, 2)
---
-// Error: 2-12 cannot apply '+' to content
-{+([] + [])}
+// Error: 3-13 cannot apply '+' to content
+#{+([] + [])}
---
-// Error: 2-5 cannot apply '-' to string
-{-""}
+// Error: 3-6 cannot apply '-' to string
+#{-""}
---
-// Error: 2-8 cannot apply 'not' to array
-{not ()}
+// Error: 3-9 cannot apply 'not' to array
+#{not ()}
---
-// Error: 2-18 cannot apply '<=' to relative length and ratio
-{30% + 1pt <= 40%}
+// Error: 3-19 cannot apply '<=' to relative length and ratio
+#{30% + 1pt <= 40%}
---
-// Error: 2-13 cannot apply '<=' to length and length
-{1em <= 10pt}
+// Error: 3-14 cannot apply '<=' to length and length
+#{1em <= 10pt}
---
-// Error: 2-11 cannot divide by zero
-{1.2 / 0.0}
+// Error: 3-12 cannot divide by zero
+#{1.2 / 0.0}
---
-// Error: 2-7 cannot divide by zero
-{1 / 0}
+// Error: 3-8 cannot divide by zero
+#{1 / 0}
---
-// Error: 2-14 cannot divide by zero
-{15deg / 0deg}
+// Error: 3-15 cannot divide by zero
+#{15deg / 0deg}
---
// Special messages for +, -, * and /.
-// Error: 03-10 cannot add integer and string
-{(1 + "2", 40% - 1)}
+// Error: 4-11 cannot add integer and string
+#{(1 + "2", 40% - 1)}
---
-// Error: 14-22 cannot add integer and string
-{ let x = 1; x += "2" }
+// Error: 15-23 cannot add integer and string
+#{ let x = 1; x += "2" }
---
-// Error: 3-12 cannot divide ratio by length
-{ 10% / 5pt }
+// Error: 4-13 cannot divide ratio by length
+#{ 10% / 5pt }
---
-// Error: 3-12 cannot divide these two lengths
-{ 1em / 5pt }
+// Error: 4-13 cannot divide these two lengths
+#{ 1em / 5pt }
---
-// Error: 3-19 cannot divide relative length by ratio
-{ (10% + 1pt) / 5% }
+// Error: 4-20 cannot divide relative length by ratio
+#{ (10% + 1pt) / 5% }
---
-// Error: 3-28 cannot divide these two relative lengths
-{ (10% + 1pt) / (20% + 1pt) }
+// Error: 4-29 cannot divide these two relative lengths
+#{ (10% + 1pt) / (20% + 1pt) }
---
-// Error: 12-19 cannot subtract integer from ratio
-{(1234567, 40% - 1)}
+// Error: 13-20 cannot subtract integer from ratio
+#{(1234567, 40% - 1)}
---
-// Error: 2-10 cannot multiply integer with boolean
-{2 * true}
+// Error: 3-11 cannot multiply integer with boolean
+#{2 * true}
---
-// Error: 2-10 cannot divide integer by length
-{3 / 12pt}
+// Error: 3-11 cannot divide integer by length
+#{3 / 12pt}
---
-// Error: 2-9 cannot repeat this string -1 times
-{-1 * ""}
+// Error: 3-10 cannot repeat this string -1 times
+#{-1 * ""}
---
-{
+#{
let x = 2
for _ in range(61) {
(x) *= 2
}
- // Error: 4-18 cannot repeat this string 4611686018427387904 times
- {x * "abcdefgh"}
+ // Error: 3-17 cannot repeat this string 4611686018427387904 times
+ x * "abcdefgh"
}
---
-// Error: 4-5 unknown variable
-{ (x) = "" }
+// Error: 5-6 unknown variable
+#{ (x) = "" }
---
-// Error: 3-8 cannot mutate a temporary value
-{ 1 + 2 += 3 }
+// Error: 4-9 cannot mutate a temporary value
+#{ 1 + 2 += 3 }
---
-// Error: 2:2-2:7 cannot apply 'not' to string
+// Error: 2:3-2:8 cannot apply 'not' to string
#let x = "Hey"
-{not x = "a"}
+#{not x = "a"}
---
-// Error: 7-8 unknown variable
-{ 1 + x += 3 }
+// Error: 8-9 unknown variable
+#{ 1 + x += 3 }
---
-// Error: 3-4 unknown variable
-{ z = 1 }
+// Error: 4-5 unknown variable
+#{ z = 1 }
---
-// Error: 3-7 cannot mutate a constant
-{ rect = "hi" }
+// Error: 4-8 cannot mutate a constant
+#{ rect = "hi" }
---
// Works if we define rect beforehand
// (since then it doesn't resolve to the standard library version anymore).
#let rect = ""
-{ rect = "hi" }
+#{ rect = "hi" }
diff --git a/tests/typ/compiler/ops-prec.typ b/tests/typ/compiler/ops-prec.typ
index eba0c8a9..2fa90382 100644
--- a/tests/typ/compiler/ops-prec.typ
+++ b/tests/typ/compiler/ops-prec.typ
@@ -14,9 +14,14 @@
---
// Assignment binds stronger than boolean operations.
-// Error: 2:2-2:7 cannot mutate a temporary value
+// Error: 2:3-2:8 cannot mutate a temporary value
#let x = false
-{not x = "a"}
+#{not x = "a"}
+
+---
+// Precedence doesn't matter for chained unary operators.
+// Error: 3-12 cannot apply '-' to boolean
+#{-not true}
---
// Parentheses override precedence.
@@ -25,8 +30,3 @@
// Error: 14 expected closing paren
#test({(1 + 1}, 2)
-
----
-// Precedence doesn't matter for chained unary operators.
-// Error: 2-11 cannot apply '-' to boolean
-{-not true}
diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ
index 722a9c8d..3e72476c 100644
--- a/tests/typ/compiler/ops.typ
+++ b/tests/typ/compiler/ops.typ
@@ -4,7 +4,7 @@
---
// Test adding content.
// Ref: true
-{[*Hello* ] + [world!]}
+#{[*Hello* ] + [world!]}
---
// Test math operators.
@@ -176,17 +176,17 @@
// Test assignment operators.
#let x = 0
-{ x = 10 } #test(x, 10)
-{ x -= 5 } #test(x, 5)
-{ x += 1 } #test(x, 6)
-{ x *= x } #test(x, 36)
-{ x /= 2.0 } #test(x, 18.0)
-{ x = "some" } #test(x, "some")
-{ x += "thing" } #test(x, "something")
+#{ x = 10 } #test(x, 10)
+#{ x -= 5 } #test(x, 5)
+#{ x += 1 } #test(x, 6)
+#{ x *= x } #test(x, 36)
+#{ x /= 2.0 } #test(x, 18.0)
+#{ x = "some" } #test(x, "some")
+#{ x += "thing" } #test(x, "something")
---
-// Error: 3-6 cannot mutate a constant
-{ box = 1 }
+// Error: 4-7 cannot mutate a constant
+#{ box = 1 }
---
// Test `in` operator.
@@ -204,8 +204,8 @@
/* fun comment? */ in "abc", false)
---
-// Error: 9 expected keyword `in`
-{"a" not}
+// Error: 10 expected keyword `in`
+#{"a" not}
---
// Test `with` method.
diff --git a/tests/typ/compiler/repr.typ b/tests/typ/compiler/repr.typ
index bdea9c82..a22fdd49 100644
--- a/tests/typ/compiler/repr.typ
+++ b/tests/typ/compiler/repr.typ
@@ -2,33 +2,33 @@
---
// Literal values.
-{auto} \
-{none} (empty) \
-{true} \
-{false}
+#auto \
+#none (empty) \
+#true \
+#false
---
// Numerical values.
-{1} \
-{1.0e-4} \
-{3.15} \
-{1e-10} \
-{50.368%} \
-{0.0000012345pt} \
-{4.5cm} \
-{12e1pt} \
-{2.5rad} \
-{45deg} \
-{1.7em} \
-{1cm + 0em} \
-{2em + 10pt} \
-{2.3fr}
+#1 \
+#1.0e-4 \
+#3.15 \
+#1e-10 \
+#50.368% \
+#0.0000012345pt \
+#4.5cm \
+#12e1pt \
+#2.5rad \
+#45deg \
+#1.7em \
+#{1cm + 0em} \
+#{2em + 10pt} \
+#2.3fr
---
// Colors and strokes.
#set text(0.8em)
#rgb("f7a205") \
-{2pt + rgb("f7a205")}
+#{2pt + rgb("f7a205")}
---
// Strings and escaping.
@@ -43,6 +43,6 @@
// Functions are invisible.
Nothing
#let f(x) = x
-{f}
-{rect}
-{() => none}
+#f
+#rect
+#{() => none}
diff --git a/tests/typ/compiler/set.typ b/tests/typ/compiler/set.typ
index 034bfab8..39c3e613 100644
--- a/tests/typ/compiler/set.typ
+++ b/tests/typ/compiler/set.typ
@@ -3,18 +3,18 @@
---
// Test that text is affected by instantiation-site bold.
#let x = [World]
-Hello *{x}*
+Hello *#x*
---
// Test that lists are affected by correct indents.
#let fruit = [
- Apple
- Orange
- #list(body-indent: 20pt, [Pear])
+ #list(body-indent: 20pt)[Pear]
]
- Fruit
-[#set list(indent: 10pt)
+#[#set list(indent: 10pt)
#fruit]
- No more fruit
@@ -28,7 +28,7 @@ Hello *{x}*
---
// Test that scoping works as expected.
-{
+#{
if true {
set text(blue)
[Blue ]
@@ -62,5 +62,5 @@ Hello *{x}*
#set text(red) if 1 + 2
---
-// Error: 11-25 set is only allowed directly in code and content blocks
-{ let x = set text(blue) }
+// Error: 12-26 set is only allowed directly in code and content blocks
+#{ let x = set text(blue) }
diff --git a/tests/typ/compiler/shorthand.typ b/tests/typ/compiler/shorthand.typ
index 5c94dab0..02c42ab0 100644
--- a/tests/typ/compiler/shorthand.typ
+++ b/tests/typ/compiler/shorthand.typ
@@ -17,4 +17,4 @@ a~b
---
#set text("Roboto")
-A... vs {"A..."}
+A... vs #"A..."
diff --git a/tests/typ/compiler/show-bare.typ b/tests/typ/compiler/show-bare.typ
index 8b8d0852..2f076ade 100644
--- a/tests/typ/compiler/show-bare.typ
+++ b/tests/typ/compiler/show-bare.typ
@@ -16,7 +16,7 @@ in booklovers and the great fulfiller of human need.
---
// Test bare show in content block.
-A [_B #show c => [*#c*]; C_] D
+A #[_B #show c => [*#c*]; C_] D
---
// Test style precedence.
@@ -29,5 +29,5 @@ Forest
Ignored
---
-// Error: 4-18 show is only allowed directly in code and content blocks
-{ (show body => 2) * body }
+// Error: 5-19 show is only allowed directly in code and content blocks
+#{ (show body => 2) * body }
diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ
index 46663c68..4aba4e9b 100644
--- a/tests/typ/compiler/show-node.typ
+++ b/tests/typ/compiler/show-node.typ
@@ -14,7 +14,7 @@
// Test full reset.
#show heading: [B]
#show heading: set text(size: 10pt, weight: 400)
-A [= Heading] C
+A #[= Heading] C
---
// Test full removal.
@@ -58,7 +58,7 @@ Another text.
---
// Test that scoping works as expected.
-{
+#{
let world = [ World ]
show "W": strong
world
@@ -100,5 +100,5 @@ Another text.
#show red: []
---
-// Error: 7-25 show is only allowed directly in code and content blocks
-{ 1 + show heading: none }
+// Error: 8-26 show is only allowed directly in code and content blocks
+#{ 1 + show heading: none }
diff --git a/tests/typ/compiler/spread.typ b/tests/typ/compiler/spread.typ
index 244e9fb9..ce3d8cea 100644
--- a/tests/typ/compiler/spread.typ
+++ b/tests/typ/compiler/spread.typ
@@ -3,7 +3,7 @@
---
// Test standard argument overriding.
-{
+#{
let f(style: "normal", weight: "regular") = {
"(style: " + style + ", weight: " + weight + ")"
}
@@ -16,7 +16,7 @@
---
// Test multiple calls.
-{
+#{
let f(b, c: "!") = b + c
let g(a, ..sink) = a + f(..sink)
test(g("a", "b", c: "c"), "abc")
@@ -24,7 +24,7 @@
---
// Test doing things with arguments.
-{
+#{
let save(..args) = {
test(type(args), "arguments")
test(repr(args), "(1, 2, three: true)")
@@ -35,14 +35,14 @@
---
// Test spreading array and dictionary.
-{
+#{
let more = (3, -3, 6, 10)
test(min(1, 2, ..more), -3)
test(max(..more, 9), 10)
test(max(..more, 11), 11)
}
-{
+#{
let more = (c: 3, d: 4)
let tostr(..args) = repr(args)
test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)")
@@ -69,14 +69,14 @@
---
// Test spreading into array and dictionary.
-{
+#{
let l = (1, 2, 3)
let r = (5, 6, 7)
test((..l, 4, ..r), range(1, 8))
test((..none), ())
}
-{
+#{
let x = (a: 1)
let y = (b: 2)
let z = (a: 3)
@@ -85,9 +85,9 @@
}
---
-// Error: 11-17 cannot spread dictionary into array
-{(1, 2, ..(a: 1))}
+// Error: 12-18 cannot spread dictionary into array
+#{(1, 2, ..(a: 1))}
---
-// Error: 5-11 cannot spread array into dictionary
-{(..(1, 2), a: 1)}
+// Error: 6-12 cannot spread array into dictionary
+#{(..(1, 2), a: 1)}
diff --git a/tests/typ/compiler/string.typ b/tests/typ/compiler/string.typ
index 8ac515a5..57699074 100644
--- a/tests/typ/compiler/string.typ
+++ b/tests/typ/compiler/string.typ
@@ -13,12 +13,12 @@
#test("🏳️‍🌈A🏳️‍⚧️".last(), "🏳️‍⚧️")
---
-// Error: 3-13 string is empty
-{ "".first() }
+// Error: 4-14 string is empty
+#{ "".first() }
---
-// Error: 3-12 string is empty
-{ "".last() }
+// Error: 4-13 string is empty
+#{ "".last() }
---
// Test the `at` method.
@@ -27,8 +27,8 @@
#test("Hey: 🏳️‍🌈 there!".at(5), "🏳️‍🌈")
---
-// Error: 3-16 string index out of bounds (index: 5, len: 5)
-{ "Hello".at(5) }
+// Error: 4-17 string index out of bounds (index: 5, len: 5)
+#{ "Hello".at(5) }
---
// Test the `slice` method.
@@ -135,8 +135,8 @@
#test("hello world".trim(regex(".")), "")
---
-// Error: 17-21 expected either `start` or `end`
-{"abc".trim(at: left)}
+// Error: 18-22 expected either `start` or `end`
+#{"abc".trim(at: left)}
---
// Test the `split` method.
diff --git a/tests/typ/compiler/while.typ b/tests/typ/compiler/while.typ
index d495a84a..8070d2e0 100644
--- a/tests/typ/compiler/while.typ
+++ b/tests/typ/compiler/while.typ
@@ -4,7 +4,7 @@
// Should output `2 4 6 8 10`.
#let i = 0
#while i < 10 [
- { i += 2 }
+ #{ i += 2 }
#i
]
@@ -26,7 +26,7 @@
#test(while false {}, none)
#let i = 0
-#test(type(while i < 1 [{ i += 1 }]), "content")
+#test(type(while i < 1 [#{ i += 1 }]), "content")
---
// Condition must be boolean.
@@ -38,7 +38,7 @@
#while 2 < "hello".len() {}
---
-// Error: 2:1-2:24 loop seems to be infinite
+// Error: 2:2-2:24 loop seems to be infinite
#let i = 1
#while i > 0 { i += 1 }
@@ -46,8 +46,8 @@
// Error: 7 expected expression
#while
-// Error: 7 expected expression
-{while}
+// Error: 8 expected expression
+#{while}
// Error: 9 expected block
#while x
diff --git a/tests/typ/compute/foundations.typ b/tests/typ/compute/foundations.typ
index 6fc93a75..1b2ab473 100644
--- a/tests/typ/compute/foundations.typ
+++ b/tests/typ/compute/foundations.typ
@@ -43,8 +43,8 @@ Blue #move(dy: -0.15em)[🌊]
```
---
-// Error: 7-19 cannot continue outside of loop
-#eval("{continue}")
+// Error: 7-18 cannot continue outside of loop
+#eval("#continue")
---
// Error: 7-33 cannot access file system from here
@@ -72,5 +72,5 @@ _No relative giraffe!_
```
---
-// Error: 7-16 expected comma
-#eval("{(1 2)}")
+// Error: 7-15 expected comma
+#eval("#(1 2)")
diff --git a/tests/typ/compute/utility.typ b/tests/typ/compute/utility.typ
index c99c0858..73bb2e2d 100644
--- a/tests/typ/compute/utility.typ
+++ b/tests/typ/compute/utility.typ
@@ -8,7 +8,7 @@
// Test custom paragraphs with user code.
#set text(8pt)
-{
+#{
let sentences = lorem(59)
.split(".")
.filter(s => s != "")
diff --git a/tests/typ/layout/page-margin.typ b/tests/typ/layout/page-margin.typ
index 9be2484f..53bc27ce 100644
--- a/tests/typ/layout/page-margin.typ
+++ b/tests/typ/layout/page-margin.typ
@@ -2,7 +2,7 @@
---
// Set all margins at once.
-[
+#[
#set page(height: 20pt, margin: 5pt)
#place(top + left)[TL]
#place(bottom + right)[BR]
@@ -11,10 +11,10 @@
---
// Set individual margins.
#set page(height: 40pt)
-[#set page(margin: (left: 0pt)); #align(left)[Left]]
-[#set page(margin: (right: 0pt)); #align(right)[Right]]
-[#set page(margin: (top: 0pt)); #align(top)[Top]]
-[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]]
+#[#set page(margin: (left: 0pt)); #align(left)[Left]]
+#[#set page(margin: (right: 0pt)); #align(right)[Right]]
+#[#set page(margin: (top: 0pt)); #align(top)[Top]]
+#[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]]
// Ensure that specific margins override general margins.
-[#set page(margin: (rest: 0pt, left: 20pt)); Overriden]
+#[#set page(margin: (rest: 0pt, left: 20pt)); Overriden]
diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ
index 50b6a394..2815e230 100644
--- a/tests/typ/layout/page.typ
+++ b/tests/typ/layout/page.typ
@@ -14,11 +14,11 @@
// Set width and height.
// Should result in one high and one wide page.
#set page(width: 80pt, height: 80pt)
-[#set page(width: 40pt);High]
-[#set page(height: 40pt);Wide]
+#[#set page(width: 40pt);High]
+#[#set page(height: 40pt);Wide]
// Flipped predefined paper.
-[#set page(paper: "a11", flipped: true);Flipped A11]
+#[#set page(paper: "a11", flipped: true);Flipped A11]
---
// Test page fill.
diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ
index 349a2fc2..f5921191 100644
--- a/tests/typ/layout/pagebreak.typ
+++ b/tests/typ/layout/pagebreak.typ
@@ -26,12 +26,12 @@ Second
// Test a combination of pagebreaks, styled pages and pages with bodies.
// Should result in three five pages, with the fourth one being forest-colored.
#set page(width: 80pt, height: 30pt)
-[#set page(width: 60pt); First]
+#[#set page(width: 60pt); First]
#pagebreak()
#pagebreak()
Third
#page(height: 20pt, fill: forest)[]
-Fif[#set page();th]
+Fif#[#set page();th]
---
// Test hard and weak pagebreak followed by page with body.
diff --git a/tests/typ/layout/par-bidi.typ b/tests/typ/layout/par-bidi.typ
index 11c0cafa..4baefcd3 100644
--- a/tests/typ/layout/par-bidi.typ
+++ b/tests/typ/layout/par-bidi.typ
@@ -47,7 +47,7 @@ Lריווח #h(1cm) R
---
// Test whether L1 whitespace resetting destroys stuff.
-الغالب #h(70pt) ن{" "}ة
+الغالب #h(70pt) ن#" "ة
---
// Test setting a vertical direction.
diff --git a/tests/typ/layout/par-justify.typ b/tests/typ/layout/par-justify.typ
index e3d61322..5a9012d1 100644
--- a/tests/typ/layout/par-justify.typ
+++ b/tests/typ/layout/par-justify.typ
@@ -30,4 +30,4 @@ D E F #linebreak(justify: true)
// Test that there are no hick-ups with justification enabled and
// basically empty paragraph.
#set par(justify: true)
-{""}
+#""
diff --git a/tests/typ/layout/repeat.typ b/tests/typ/layout/repeat.typ
index 82d64b94..05b055af 100644
--- a/tests/typ/layout/repeat.typ
+++ b/tests/typ/layout/repeat.typ
@@ -12,7 +12,7 @@
)
#for section in sections [
- {section.at(0)} #repeat[.] {section.at(1)} \
+ #section.at(0) #repeat[.] #section.at(1) \
]
---
diff --git a/tests/typ/layout/transform.typ b/tests/typ/layout/transform.typ
index 2dde626b..58a053e9 100644
--- a/tests/typ/layout/transform.typ
+++ b/tests/typ/layout/transform.typ
@@ -3,13 +3,13 @@
---
// Test creating the TeX and XeTeX logos.
#let size = 11pt
-#let tex = [{
+#let tex = {
[T]
h(-0.14 * size)
move(dy: 0.22 * size)[E]
h(-0.12 * size)
[X]
-}]
+}
#let xetex = {
[X]
diff --git a/tests/typ/math/style.typ b/tests/typ/math/style.typ
index a8b9c7a5..5089610b 100644
--- a/tests/typ/math/style.typ
+++ b/tests/typ/math/style.typ
@@ -3,10 +3,10 @@
#let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v)))
#let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both])
-#for k in kinds {
- cells.push(raw(repr(k).trim("<function ").trim(">")))
- for m in modifiers {
- cells.push($ #m(#k(part)) $)
+#for kk in kinds {
+ cells.push(raw(repr(kk).trim("<function ").trim(">")))
+ for mm in modifiers {
+ cells.push($ mm(kk(part)) $)
}
}
diff --git a/tests/typ/meta/document.typ b/tests/typ/meta/document.typ
index 1fcb8109..bbe8dafa 100644
--- a/tests/typ/meta/document.typ
+++ b/tests/typ/meta/document.typ
@@ -8,23 +8,23 @@
---
Hello
-// Error: 1-30 must appear before any content
+// Error: 2-30 must appear before any content
#set document(title: "Hello")
---
#box[
- // Error: 3-32 not allowed here
+ // Error: 4-32 not allowed here
#set document(title: "Hello")
]
---
#box[
- // Error: 3-18 not allowed here
+ // Error: 4-18 not allowed here
#set page("a4")
]
---
#box[
- // Error: 3-15 not allowed here
+ // Error: 4-15 not allowed here
#pagebreak()
]
diff --git a/tests/typ/meta/outline.typ b/tests/typ/meta/outline.typ
index 96629f8c..1f882cec 100644
--- a/tests/typ/meta/outline.typ
+++ b/tests/typ/meta/outline.typ
@@ -11,7 +11,7 @@
= Analyse
#lorem(10)
-[
+#[
#set heading(outlined: false)
== Methodik
#lorem(6)
diff --git a/tests/typ/text/em.typ b/tests/typ/text/em.typ
index dd0a436e..f901aae4 100644
--- a/tests/typ/text/em.typ
+++ b/tests/typ/text/em.typ
@@ -3,10 +3,10 @@
---
#set text(size: 5pt)
A // 5pt
-[
+#[
#set text(size: 2em)
B // 10pt
- [
+ #[
#set text(size: 1.5em + 1pt)
C // 16pt
#text(size: 2em)[D] // 32pt
diff --git a/tests/typ/text/emphasis.typ b/tests/typ/text/emphasis.typ
index f4570031..0191ac87 100644
--- a/tests/typ/text/emphasis.typ
+++ b/tests/typ/text/emphasis.typ
@@ -8,7 +8,7 @@ _Emphasized and *strong* words!_
hello_world Nutzer*innen
// Can contain paragraph in nested content block.
-_Still [
+_Still #[
] emphasized._
@@ -24,7 +24,7 @@ Normal
*Bold*
#set strong(delta: 150)
-*Medium* and *[*Bold*]*
+*Medium* and *#[*Bold*]*
---
// Error: 13 expected underscore
@@ -38,6 +38,6 @@ _Hello
World
---
-// Error: 25 expected star
-// Error: 25 expected underscore
-[_Cannot *be interleaved]
+// Error: 26 expected star
+// Error: 26 expected underscore
+#[_Cannot *be interleaved]
diff --git a/tests/typ/text/font.typ b/tests/typ/text/font.typ
index 0889a0f3..d2add0f1 100644
--- a/tests/typ/text/font.typ
+++ b/tests/typ/text/font.typ
@@ -25,7 +25,7 @@
Emoji: 🐪, 🌋, 🏞
// Colors.
-[
+#[
#set text(fill: eastern)
This is #text(rgb("FA644B"))[way more] colorful.
]
diff --git a/tests/typ/text/quotes.typ b/tests/typ/text/quotes.typ
index 0c64867a..c63caa74 100644
--- a/tests/typ/text/quotes.typ
+++ b/tests/typ/text/quotes.typ
@@ -50,4 +50,4 @@ He's told some books contain questionable "example text".
// Test changing properties within text.
"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me.
-Some people's thought on this would be [#set smartquote(enabled: false); "strange."]
+Some people's thought on this would be #[#set smartquote(enabled: false); "strange."]
diff --git a/tests/typ/text/space.typ b/tests/typ/text/space.typ
index 8dcc59e3..51970a48 100644
--- a/tests/typ/text/space.typ
+++ b/tests/typ/text/space.typ
@@ -7,7 +7,7 @@ C #let x = 2;D #test(x, 2) \
E#if true [F]G \
H #if true{"I"} J \
K #if true [L] else []M \
-#let c = true; N#while c [{c = false}O] P \
+#let c = true; N#while c [#{c = false}O] P \
#let c = true; Q #while c { c = false; "R" } S \
T#for _ in (none,) {"U"}V
@@ -19,11 +19,11 @@ A /**/B/**/ C
---
// Test that a run consisting only of whitespace isn't trimmed.
-A[#set text("IBM Plex Serif"); ]B
+A#text("IBM Plex Serif")[ ]B
---
// Test font change after space.
-Left [#set text("IBM Plex Serif");Right].
+Left #text("IBM Plex Serif")[Right].
---
// Test that linebreak consumed surrounding spaces.
@@ -31,7 +31,7 @@ Left [#set text("IBM Plex Serif");Right].
---
// Test that space at start of non-backslash-linebreak line isn't trimmed.
-A{"\n"} B
+A#"\n" B
---
// Test that trailing space does not force a line break.
diff --git a/tests/typ/visualize/line.typ b/tests/typ/visualize/line.typ
index 9cc9b8cc..2085daa1 100644
--- a/tests/typ/visualize/line.typ
+++ b/tests/typ/visualize/line.typ
@@ -6,10 +6,10 @@
---
// Test the `end` argument.
-{
- line(end: (10pt, 0pt))
- line(start: (0pt, 10pt), end: (0pt, 0pt))
- line(end: (15pt, 15pt))
+#{
+ line(end: (10pt, 0pt))
+ line(start: (0pt, 10pt), end: (0pt, 0pt))
+ line(end: (15pt, 15pt))
}
#v(.5cm)
diff --git a/tests/typ/visualize/shape-fill-stroke.typ b/tests/typ/visualize/shape-fill-stroke.typ
index d14d0981..8820a9fd 100644
--- a/tests/typ/visualize/shape-fill-stroke.typ
+++ b/tests/typ/visualize/shape-fill-stroke.typ
@@ -16,7 +16,7 @@
variant(fill: forest, stroke: black + 2pt),
variant(fill: forest, stroke: conifer + 2pt),
) {
- (align(horizon)[{i + 1}.], item, [])
+ (align(horizon)[#{i + 1}.], item, [])
}
#grid(
diff --git a/tests/typ/visualize/shape-rect.typ b/tests/typ/visualize/shape-rect.typ
index c8518bbd..ff80dfb9 100644
--- a/tests/typ/visualize/shape-rect.typ
+++ b/tests/typ/visualize/shape-rect.typ
@@ -24,9 +24,9 @@
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
// These are inline with text.
-\{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67"))
- #rect(width: 0.5in, height: 7pt, fill: rgb("edd466"))
- #rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))\}
+{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67"))
+ #rect(width: 0.5in, height: 7pt, fill: rgb("edd466"))
+ #rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))}
// Rounded corners.
#rect(width: 2cm, radius: 60%)
@@ -39,10 +39,8 @@
))
// Different strokes.
-[
- #set rect(stroke: (right: red))
- #rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt))
-]
+#set rect(stroke: (right: red))
+#rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt))
---
// Error: 15-38 unexpected key "cake", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"
diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json
index 692fe652..d546eccb 100644
--- a/tools/support/typst.tmLanguage.json
+++ b/tools/support/typst.tmLanguage.json
@@ -24,21 +24,7 @@
},
"common": {
"patterns": [
- { "include": "#comments" },
- {
- "name": "meta.block.code.typst",
- "begin": "{",
- "end": "}",
- "captures": { "0": { "name": "punctuation.definition.block.code.typst" } },
- "patterns": [{ "include": "#code" }]
- },
- {
- "name": "meta.block.content.typst",
- "begin": "\\[",
- "end": "\\]",
- "captures": { "0": { "name": "punctuation.definition.block.content.typst" } },
- "patterns": [{ "include": "#markup" }]
- }
+ { "include": "#comments" }
]
},
"markup": {
@@ -213,8 +199,14 @@
},
{
"name": "entity.other.interpolated.typst",
- "match": "(#)[[:alpha:]_][[:alnum:]_-]*",
+ "match": "(#)[[:alpha:]_][.[:alnum:]_-]*",
"captures": { "1": { "name": "punctuation.definition.variable.typst" } }
+ },
+ {
+ "name": "meta.block.content.typst",
+ "begin": "#",
+ "end": "\\s",
+ "patterns": [{ "include": "#code" }]
}
]
},
@@ -222,6 +214,20 @@
"patterns": [
{ "include": "#common" },
{
+ "name": "meta.block.code.typst",
+ "begin": "{",
+ "end": "}",
+ "captures": { "0": { "name": "punctuation.definition.block.code.typst" } },
+ "patterns": [{ "include": "#code" }]
+ },
+ {
+ "name": "meta.block.content.typst",
+ "begin": "\\[",
+ "end": "\\]",
+ "captures": { "0": { "name": "punctuation.definition.block.content.typst" } },
+ "patterns": [{ "include": "#markup" }]
+ },
+ {
"name": "comment.line.double-slash.typst",
"begin": "//",
"end": "\n",