Hashtags everywhere!

This commit is contained in:
Laurenz 2023-01-27 11:54:30 +01:00
parent 33585d9a3f
commit a8fd64f928
63 changed files with 1025 additions and 900 deletions

View File

@ -189,11 +189,11 @@ fn items() -> LangItems {
term_item: |term, description| { term_item: |term, description| {
layout::ListItem::Term(basics::TermItem { term, description }).pack() 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_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_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(), math_frac: |num, denom| math::FracNode { num, denom }.pack(),
math_align_point: || math::AlignPointNode.pack(),
} }
} }

View File

@ -133,7 +133,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
/// Extract a single character from content. /// Extract a single character from content.
fn extract(accent: &Content) -> Option<char> { 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 mut chars = atom.0.chars();
let c = chars.next().filter(|_| chars.next().is_none())?; let c = chars.next().filter(|_| chars.next().is_none())?;
Some(combining(c)) Some(combining(c))
@ -166,6 +166,8 @@ fn combining(c: char) -> char {
'\u{2190}' => '\u{20d6}', '\u{2190}' => '\u{20d6}',
'\u{2192}' => '\u{20d7}', '\u{2192}' => '\u{20d7}',
'\u{2212}' => '\u{0305}', '\u{2212}' => '\u{0305}',
'\u{223C}' => '\u{0303}',
'\u{22C5}' => '\u{0307}',
'\u{27f6}' => '\u{20d7}', '\u{27f6}' => '\u{20d7}',
_ => c, _ => c,
} }

View File

@ -155,7 +155,7 @@ fn layout(
/// Select a precomposed radical, if the font has it. /// Select a precomposed radical, if the font has it.
fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option<Frame> { 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() { let c = match node.0.as_str() {
"3" => '∛', "3" => '∛',
"4" => '∜', "4" => '∜',

View File

@ -7,7 +7,7 @@ pub enum Category {
Comment, Comment,
/// Punctuation in code. /// Punctuation in code.
Punctuation, Punctuation,
/// An escape sequence, shorthand or symbol notation. /// An escape sequence or shorthand.
Escape, Escape,
/// Strong markup. /// Strong markup.
Strong, Strong,
@ -97,7 +97,6 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Parbreak => None, SyntaxKind::Parbreak => None,
SyntaxKind::Escape => Some(Category::Escape), SyntaxKind::Escape => Some(Category::Escape),
SyntaxKind::Shorthand => Some(Category::Escape), SyntaxKind::Shorthand => Some(Category::Escape),
SyntaxKind::Symbol => Some(Category::Escape),
SyntaxKind::SmartQuote => None, SyntaxKind::SmartQuote => None,
SyntaxKind::Strong => Some(Category::Strong), SyntaxKind::Strong => Some(Category::Strong),
SyntaxKind::Emph => Some(Category::Emph), SyntaxKind::Emph => Some(Category::Emph),
@ -113,12 +112,22 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::EnumMarker => Some(Category::ListMarker), SyntaxKind::EnumMarker => Some(Category::ListMarker),
SyntaxKind::TermItem => None, SyntaxKind::TermItem => None,
SyntaxKind::TermMarker => Some(Category::ListMarker), SyntaxKind::TermMarker => Some(Category::ListMarker),
SyntaxKind::Formula => None,
SyntaxKind::Math => None, SyntaxKind::Math => None,
SyntaxKind::Atom => None, SyntaxKind::MathAtom => None,
SyntaxKind::Delimited => None, SyntaxKind::MathIdent => highlight_ident(node),
SyntaxKind::Script => None, SyntaxKind::MathDelimited => None,
SyntaxKind::Frac => None, SyntaxKind::MathScript => None,
SyntaxKind::AlignPoint => Some(Category::MathOperator), 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::LeftBrace => Some(Category::Punctuation),
SyntaxKind::RightBrace => Some(Category::Punctuation), SyntaxKind::RightBrace => Some(Category::Punctuation),
@ -134,14 +143,14 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
_ => Some(Category::Operator), _ => Some(Category::Operator),
}, },
SyntaxKind::Underscore => match node.parent_kind() { SyntaxKind::Underscore => match node.parent_kind() {
Some(SyntaxKind::Script) => Some(Category::MathOperator), Some(SyntaxKind::MathScript) => Some(Category::MathOperator),
_ => None, _ => None,
}, },
SyntaxKind::Dollar => Some(Category::MathDelimiter), SyntaxKind::Dollar => Some(Category::MathDelimiter),
SyntaxKind::Plus => Some(Category::Operator), SyntaxKind::Plus => Some(Category::Operator),
SyntaxKind::Minus => Some(Category::Operator), SyntaxKind::Minus => Some(Category::Operator),
SyntaxKind::Slash => Some(match node.parent_kind() { SyntaxKind::Slash => Some(match node.parent_kind() {
Some(SyntaxKind::Frac) => Category::MathOperator, Some(SyntaxKind::MathFrac) => Category::MathOperator,
_ => Category::Operator, _ => Category::Operator,
}), }),
SyntaxKind::Hat => Some(Category::MathOperator), SyntaxKind::Hat => Some(Category::MathOperator),
@ -183,47 +192,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Include => Some(Category::Keyword), SyntaxKind::Include => Some(Category::Keyword),
SyntaxKind::As => Some(Category::Keyword), SyntaxKind::As => Some(Category::Keyword),
SyntaxKind::Ident => match node.parent_kind() { SyntaxKind::Code => None,
Some( SyntaxKind::Ident => highlight_ident(node),
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::Bool => Some(Category::Keyword), SyntaxKind::Bool => Some(Category::Keyword),
SyntaxKind::Int => Some(Category::Number), SyntaxKind::Int => Some(Category::Number),
SyntaxKind::Float => Some(Category::Number), SyntaxKind::Float => Some(Category::Number),
@ -238,7 +208,11 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Keyed => None, SyntaxKind::Keyed => None,
SyntaxKind::Unary => None, SyntaxKind::Unary => None,
SyntaxKind::Binary => 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::FuncCall => None,
SyntaxKind::MethodCall => None, SyntaxKind::MethodCall => None,
SyntaxKind::Args => 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)] #[cfg(test)]
mod tests { mod tests {
use std::ops::Range; use std::ops::Range;
@ -300,7 +320,8 @@ mod tests {
test( test(
"#f(x + 1)", "#f(x + 1)",
&[ &[
(0..2, Function), (0..1, Function),
(1..2, Function),
(2..3, Punctuation), (2..3, Punctuation),
(5..6, Operator), (5..6, Operator),
(7..8, Number), (7..8, Number),
@ -311,7 +332,8 @@ mod tests {
test( test(
"#let f(x) = x", "#let f(x) = x",
&[ &[
(0..4, Keyword), (0..1, Keyword),
(1..4, Keyword),
(5..6, Function), (5..6, Function),
(6..7, Punctuation), (6..7, Punctuation),
(8..9, Punctuation), (8..9, Punctuation),

View File

@ -234,7 +234,7 @@ fn eval_markup(
*node = mem::take(node).labelled(label); *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; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let span = self.span();
let forbidden = |name| { let forbidden = |name| {
error!( error!(span, "{} is only allowed directly in code and content blocks", name)
self.span(),
"{} is only allowed directly in code and content blocks", name
)
}; };
match self { match self {
@ -266,9 +264,9 @@ impl Eval for ast::Expr {
Self::Space(v) => v.eval(vm).map(Value::Content), Self::Space(v) => v.eval(vm).map(Value::Content),
Self::Linebreak(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::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::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::SmartQuote(v) => v.eval(vm).map(Value::Content),
Self::Strong(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), 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::List(v) => v.eval(vm).map(Value::Content),
Self::Enum(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::Term(v) => v.eval(vm).map(Value::Content),
Self::Atom(v) => v.eval(vm).map(Value::Content), Self::Formula(v) => v.eval(vm).map(Value::Content),
Self::Delimited(v) => v.eval(vm).map(Value::Content), Self::Math(v) => v.eval(vm).map(Value::Content),
Self::Script(v) => v.eval(vm).map(Value::Content), Self::MathAtom(v) => v.eval(vm).map(Value::Content),
Self::Frac(v) => v.eval(vm).map(Value::Content), Self::MathIdent(v) => v.eval(vm),
Self::AlignPoint(v) => v.eval(vm).map(Value::Content), 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::Ident(v) => v.eval(vm),
Self::None(v) => v.eval(vm), Self::None(v) => v.eval(vm),
Self::Auto(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::Str(v) => v.eval(vm),
Self::Code(v) => v.eval(vm), Self::Code(v) => v.eval(vm),
Self::Content(v) => v.eval(vm).map(Value::Content), 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::Array(v) => v.eval(vm).map(Value::Array),
Self::Dict(v) => v.eval(vm).map(Value::Dict), Self::Dict(v) => v.eval(vm).map(Value::Dict),
Self::Parenthesized(v) => v.eval(vm), Self::Parenthesized(v) => v.eval(vm),
@ -316,29 +316,12 @@ impl Eval for ast::Expr {
Self::Break(v) => v.eval(vm), Self::Break(v) => v.eval(vm),
Self::Continue(v) => v.eval(vm), Self::Continue(v) => v.eval(vm),
Self::Return(v) => v.eval(vm), Self::Return(v) => v.eval(vm),
} }?
} .spanned(span);
} }
impl ast::Expr { Ok(v)
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()))
} }
} }
@ -375,44 +358,22 @@ impl Eval for ast::Parbreak {
} }
impl Eval for ast::Escape { impl Eval for ast::Escape {
type Output = Content; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.text)(self.get().into())) // 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 ast::Escape {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok((vm.items.math_atom)(self.get().into()))
} }
} }
impl Eval for ast::Shorthand { impl Eval for ast::Shorthand {
type Output = Content; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.text)(self.get().into())) // 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 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()))
}
}
impl ast::Symbol {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op:square".into()))
} }
} }
@ -513,20 +474,29 @@ impl Eval for ast::TermItem {
} }
} }
impl Eval for ast::Formula {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let body = self.body().eval(vm)?;
let block = self.block();
Ok((vm.items.formula)(body, block))
}
}
impl Eval for ast::Math { impl Eval for ast::Math {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let seq = self Ok(Content::sequence(
.exprs() self.exprs()
.map(|expr| expr.eval_in_math(vm)) .map(|expr| Ok(expr.eval(vm)?.display_in_math()))
.collect::<SourceResult<_>>()?; .collect::<SourceResult<_>>()?,
let block = self.block(); ))
Ok((vm.items.math_formula)(Content::sequence(seq), block))
} }
} }
impl Eval for ast::Atom { impl Eval for ast::MathAtom {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
@ -534,59 +504,15 @@ impl Eval for ast::Atom {
} }
} }
impl Eval for ast::Delimited { impl Eval for ast::MathIdent {
type Output = Content; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let seq = self Ok(vm.scopes.get_in_math(self).cloned().at(self.span())?)
.exprs()
.map(|expr| expr.eval_in_math(vm))
.collect::<SourceResult<_>>()?;
Ok((vm.items.math_delimited)(Content::sequence(seq)))
} }
} }
impl ast::Delimited { impl Eval for ast::MathAlignPoint {
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::Script {
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))
}
}
impl Eval for ast::Frac {
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))
}
}
impl Eval for ast::AlignPoint {
type Output = Content; type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
@ -594,27 +520,49 @@ impl Eval for ast::AlignPoint {
} }
} }
impl Eval for ast::MathDelimited {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
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::MathScript {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
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::MathFrac {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
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 Eval for ast::Ident { impl Eval for ast::Ident {
type Output = Value; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let value = vm.scopes.get(self).cloned().at(self.span())?; Ok(vm.scopes.get(self).cloned().at(self.span())?)
Ok(match value {
Value::Func(func) => Value::Func(func.spanned(self.span())),
value => value,
})
}
}
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())
}
} }
} }
@ -686,12 +634,20 @@ impl Eval for ast::CodeBlock {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
vm.scopes.enter(); vm.scopes.enter();
let output = eval_code(vm, &mut self.exprs())?; let output = self.body().eval(vm)?;
vm.scopes.exit(); vm.scopes.exit();
Ok(output) 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. /// Evaluate a stream of expressions.
fn eval_code( fn eval_code(
vm: &mut Vm, vm: &mut Vm,
@ -901,23 +857,9 @@ impl Eval for ast::FieldAccess {
type Output = Value; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let object = self.target().eval(vm)?; let value = self.target().eval(vm)?;
let span = self.field().span(); let field = self.field();
let field = self.field().take(); value.field(&field).at(field.span())
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()
),
})
} }
} }
@ -926,27 +868,13 @@ impl Eval for ast::FuncCall {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let callee = self.callee(); let callee = self.callee();
let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?; let callee_span = callee.span();
let args = self.args().eval(vm)?; let in_math = matches!(callee, ast::Expr::MathIdent(_));
Self::eval_call(vm, &callee, args, self.span()) let callee = callee.eval(vm)?;
}
}
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)?,
};
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 {
let mut body = (vm.items.math_atom)('('.into());
let mut args = self.args().eval(vm)?; let mut args = self.args().eval(vm)?;
if in_math && !matches!(callee, Value::Func(_)) {
let mut body = (vm.items.math_atom)('('.into());
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 { if i > 0 {
body += (vm.items.math_atom)(','.into()); body += (vm.items.math_atom)(','.into());
@ -954,11 +882,15 @@ impl ast::FuncCall {
body += arg; body += arg;
} }
body += (vm.items.math_atom)(')'.into()); body += (vm.items.math_atom)(')'.into());
Ok(callee.display_in_math() + body) return Ok(Value::Content(callee.display_in_math() + body));
}
let callee = callee.cast::<Func>().at(callee_span)?;
complete_call(vm, &callee, args, self.span())
} }
} }
fn eval_call( fn complete_call(
vm: &mut Vm, vm: &mut Vm,
callee: &Func, callee: &Func,
args: Args, args: Args,
@ -971,7 +903,6 @@ impl ast::FuncCall {
let point = || Tracepoint::Call(callee.name().map(Into::into)); let point = || Tracepoint::Call(callee.name().map(Into::into));
callee.call(vm, args).trace(vm.world, point, span) callee.call(vm, args).trace(vm.world, point, span)
} }
}
impl Eval for ast::MethodCall { impl Eval for ast::MethodCall {
type Output = Value; type Output = Value;
@ -988,7 +919,7 @@ impl Eval for ast::MethodCall {
if let Value::Func(callee) = if let Value::Func(callee) =
module.get(&method).cloned().at(method.span())? 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::Module(module) = &value {
if let Value::Func(callee) = module.get(&method).at(method.span())? { 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. /// 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 { let path = match source {
Value::Str(path) => path, Value::Str(path) => path,
Value::Module(module) => return Ok(module), Value::Module(module) => return Ok(module),
@ -1386,7 +1317,8 @@ fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> {
// Evaluate the file. // Evaluate the file.
let source = vm.world.source(id); let source = vm.world.source(id);
let point = || Tracepoint::Import; 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 { impl Eval for ast::LoopBreak {
@ -1446,7 +1378,12 @@ impl Access for ast::Expr {
impl Access for ast::Ident { impl Access for ast::Ident {
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { 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)
} }
} }

View File

@ -505,17 +505,17 @@ mod tests {
fn test_captures() { fn test_captures() {
// Let binding and function definition. // Let binding and function definition.
test("#let x = x", &["x"]); 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) = x + y", &[]);
test("#let f(x, y) = f", &[]); test("#let f(x, y) = f", &[]);
test("#let f = (x, y) => f", &["f"]); test("#let f = (x, y) => f", &["f"]);
// Closure with different kinds of params. // Closure with different kinds of params.
test("{(x, y) => x + z}", &["z"]); test("#{(x, y) => x + z}", &["z"]);
test("{(x: y, z) => x + z}", &["y"]); test("#{(x: y, z) => x + z}", &["y"]);
test("{(..x) => x + y}", &["y"]); test("#{(..x) => x + y}", &["y"]);
test("{(x, y: x + z) => x + y}", &["x", "z"]); test("#{(x, y: x + z) => x + y}", &["x", "z"]);
test("{x => x; x}", &["x"]); test("#{x => x; x}", &["x"]);
// Show rule. // Show rule.
test("#show y: x => x", &["y"]); test("#show y: x => x", &["y"]);
@ -532,7 +532,7 @@ mod tests {
test("#import x + y: x, y, z", &["x", "y"]); test("#import x + y: x, y, z", &["x", "y"]);
// Blocks. // Blocks.
test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
test("[#let x = 1]#x", &["x"]); test("#[#let x = 1]#x", &["x"]);
} }
} }

View File

@ -68,19 +68,19 @@ pub struct LangItems {
/// An item in a term list: `/ Term: Details`. /// An item in a term list: `/ Term: Details`.
pub term_item: fn(term: Content, description: Content) -> Content, pub term_item: fn(term: Content, description: Content) -> Content,
/// A mathematical formula: `$x$`, `$ x^2 $`. /// A mathematical formula: `$x$`, `$ x^2 $`.
pub math_formula: fn(body: Content, block: bool) -> Content, pub 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,
/// An atom in a formula: `x`, `+`, `12`. /// An atom in a formula: `x`, `+`, `12`.
pub math_atom: fn(atom: EcoString) -> Content, 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`. /// A base with optional sub- and superscripts in a formula: `a_1^2`.
pub math_script: pub math_script:
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content, fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
/// A fraction in a formula: `x/2`. /// A fraction in a formula: `x/2`.
pub math_frac: fn(num: Content, denom: Content) -> Content, 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 { impl Debug for LangItems {
@ -108,7 +108,7 @@ impl Hash for LangItems {
self.list_item.hash(state); self.list_item.hash(state);
self.enum_item.hash(state); self.enum_item.hash(state);
self.term_item.hash(state); self.term_item.hash(state);
self.math_formula.hash(state); self.formula.hash(state);
self.math_atom.hash(state); self.math_atom.hash(state);
self.math_script.hash(state); self.math_script.hash(state);
self.math_frac.hash(state); self.math_frac.hash(state);

View File

@ -29,7 +29,7 @@ pub trait AstNode: Sized {
macro_rules! node { macro_rules! node {
($(#[$attr:meta])* $name:ident) => { ($(#[$attr:meta])* $name:ident) => {
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Default, Clone, PartialEq, Hash)]
#[repr(transparent)] #[repr(transparent)]
$(#[$attr])* $(#[$attr])*
pub struct $name(SyntaxNode); pub struct $name(SyntaxNode);
@ -114,18 +114,22 @@ pub enum Expr {
/// An item in a term list: `/ Term: Details`. /// An item in a term list: `/ Term: Details`.
Term(TermItem), Term(TermItem),
/// A math formula: `$x$`, `$ x^2 $`. /// A math formula: `$x$`, `$ x^2 $`.
Formula(Formula),
/// A math formula: `$x$`, `$ x^2 $`.
Math(Math), Math(Math),
/// An atom in a math formula: `x`, `+`, `12`. /// 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: /// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`. /// `[x + y]`.
Delimited(Delimited), MathDelimited(MathDelimited),
/// A base with optional sub- and superscripts in a math formula: `a_1^2`. /// 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`. /// A fraction in a math formula: `x/2`.
Frac(Frac), MathFrac(MathFrac),
/// An alignment point in a math formula: `&`.
AlignPoint(AlignPoint),
/// An identifier: `left`. /// An identifier: `left`.
Ident(Ident), Ident(Ident),
/// The `none` literal. /// The `none` literal.
@ -205,7 +209,6 @@ impl AstNode for Expr {
SyntaxKind::Text => node.cast().map(Self::Text), SyntaxKind::Text => node.cast().map(Self::Text),
SyntaxKind::Escape => node.cast().map(Self::Escape), SyntaxKind::Escape => node.cast().map(Self::Escape),
SyntaxKind::Shorthand => node.cast().map(Self::Shorthand), SyntaxKind::Shorthand => node.cast().map(Self::Shorthand),
SyntaxKind::Symbol => node.cast().map(Self::Symbol),
SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote), SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote),
SyntaxKind::Strong => node.cast().map(Self::Strong), SyntaxKind::Strong => node.cast().map(Self::Strong),
SyntaxKind::Emph => node.cast().map(Self::Emph), SyntaxKind::Emph => node.cast().map(Self::Emph),
@ -217,12 +220,14 @@ impl AstNode for Expr {
SyntaxKind::ListItem => node.cast().map(Self::List), SyntaxKind::ListItem => node.cast().map(Self::List),
SyntaxKind::EnumItem => node.cast().map(Self::Enum), SyntaxKind::EnumItem => node.cast().map(Self::Enum),
SyntaxKind::TermItem => node.cast().map(Self::Term), SyntaxKind::TermItem => node.cast().map(Self::Term),
SyntaxKind::Formula => node.cast().map(Self::Formula),
SyntaxKind::Math => node.cast().map(Self::Math), SyntaxKind::Math => node.cast().map(Self::Math),
SyntaxKind::Atom => node.cast().map(Self::Atom), SyntaxKind::MathAtom => node.cast().map(Self::MathAtom),
SyntaxKind::Delimited => node.cast().map(Self::Delimited), SyntaxKind::MathIdent => node.cast().map(Self::MathIdent),
SyntaxKind::Script => node.cast().map(Self::Script), SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint),
SyntaxKind::Frac => node.cast().map(Self::Frac), SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited),
SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint), SyntaxKind::MathScript => node.cast().map(Self::MathScript),
SyntaxKind::MathFrac => node.cast().map(Self::MathFrac),
SyntaxKind::Ident => node.cast().map(Self::Ident), SyntaxKind::Ident => node.cast().map(Self::Ident),
SyntaxKind::None => node.cast().map(Self::None), SyntaxKind::None => node.cast().map(Self::None),
SyntaxKind::Auto => node.cast().map(Self::Auto), SyntaxKind::Auto => node.cast().map(Self::Auto),
@ -265,7 +270,6 @@ impl AstNode for Expr {
Self::Parbreak(v) => v.as_untyped(), Self::Parbreak(v) => v.as_untyped(),
Self::Escape(v) => v.as_untyped(), Self::Escape(v) => v.as_untyped(),
Self::Shorthand(v) => v.as_untyped(), Self::Shorthand(v) => v.as_untyped(),
Self::Symbol(v) => v.as_untyped(),
Self::SmartQuote(v) => v.as_untyped(), Self::SmartQuote(v) => v.as_untyped(),
Self::Strong(v) => v.as_untyped(), Self::Strong(v) => v.as_untyped(),
Self::Emph(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::List(v) => v.as_untyped(),
Self::Enum(v) => v.as_untyped(), Self::Enum(v) => v.as_untyped(),
Self::Term(v) => v.as_untyped(), Self::Term(v) => v.as_untyped(),
Self::Formula(v) => v.as_untyped(),
Self::Math(v) => v.as_untyped(), Self::Math(v) => v.as_untyped(),
Self::Atom(v) => v.as_untyped(), Self::MathAtom(v) => v.as_untyped(),
Self::Delimited(v) => v.as_untyped(), Self::MathIdent(v) => v.as_untyped(),
Self::Script(v) => v.as_untyped(), Self::MathAlignPoint(v) => v.as_untyped(),
Self::Frac(v) => v.as_untyped(), Self::MathDelimited(v) => v.as_untyped(),
Self::AlignPoint(v) => v.as_untyped(), Self::MathScript(v) => v.as_untyped(),
Self::MathFrac(v) => v.as_untyped(),
Self::Ident(v) => v.as_untyped(), Self::Ident(v) => v.as_untyped(),
Self::None(v) => v.as_untyped(), Self::None(v) => v.as_untyped(),
Self::Auto(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! { node! {
/// Plain text without markup. /// Plain text without markup.
Text Text
@ -360,9 +372,9 @@ impl Escape {
u32::from_str_radix(hex, 16) u32::from_str_radix(hex, 16)
.ok() .ok()
.and_then(std::char::from_u32) .and_then(std::char::from_u32)
.expect("unicode escape is invalid") .unwrap_or_default()
} else { } 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 { pub fn get(&self) -> char {
match self.0.text().as_str() { match self.0.text().as_str() {
"~" => '\u{00A0}', "~" => '\u{00A0}',
"..." => '\u{2026}',
"--" => '\u{2013}', "--" => '\u{2013}',
"---" => '\u{2014}', "---" => '\u{2014}',
"-?" => '\u{00AD}', "-?" => '\u{00AD}',
"..." => '…',
"*" => '',
"!=" => '≠', "!=" => '≠',
"<=" => '≤', "<=" => '≤',
">=" => '≥', ">=" => '≥',
@ -432,7 +445,7 @@ node! {
impl Strong { impl Strong {
/// The contents of the strong node. /// The contents of the strong node.
pub fn body(&self) -> Markup { 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 { impl Emph {
/// The contents of the emphasis node. /// The contents of the emphasis node.
pub fn body(&self) -> Markup { 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 { impl Heading {
/// The contents of the heading. /// The contents of the heading.
pub fn body(&self) -> Markup { 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). /// The section depth (numer of equals signs).
@ -577,7 +590,7 @@ impl Heading {
.children() .children()
.find(|node| node.kind() == SyntaxKind::HeadingMarker) .find(|node| node.kind() == SyntaxKind::HeadingMarker)
.and_then(|node| node.len().try_into().ok()) .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 { impl ListItem {
/// The contents of the list item. /// The contents of the list item.
pub fn body(&self) -> Markup { 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. /// The contents of the list item.
pub fn body(&self) -> Markup { 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 { impl TermItem {
/// The term described by the item. /// The term described by the item.
pub fn term(&self) -> Markup { 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. /// The description of the term.
pub fn description(&self) -> Markup { pub fn description(&self) -> Markup {
self.0 self.0.cast_last_match().unwrap_or_default()
.cast_last_match()
.expect("term list item is missing description")
} }
} }
node! { node! {
/// A math formula: `$x$`, `$ x^2 $`. /// A math formula: `$x$`, `$ x^2 $`.
Math Formula
} }
impl Math { impl Formula {
/// The expressions the formula consists of. /// The contained math.
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { pub fn body(&self) -> Math {
self.0.children().filter_map(Expr::cast_with_space) self.0.cast_first_match().unwrap_or_default()
} }
/// Whether the formula should be displayed as a separate block. /// Whether the formula should be displayed as a separate block.
pub fn block(&self) -> bool { pub fn block(&self) -> bool {
matches!(self.exprs().next(), Some(Expr::Space(_))) let is_space = |node: Option<&SyntaxNode>| {
&& matches!(self.exprs().last(), Some(Expr::Space(_))) 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! { node! {
/// A atom in a formula: `x`, `+`, `12`. /// A atom in a formula: `x`, `+`, `12`.
Atom MathAtom
} }
impl Atom { impl MathAtom {
/// Get the atom's text. /// Get the atom's text.
pub fn get(&self) -> &EcoString { pub fn get(&self) -> &EcoString {
self.0.text() self.0.text()
@ -663,27 +688,72 @@ impl Atom {
} }
node! { node! {
/// A subsection in a math formula that is surrounded by matched delimiters: /// An identifier in a math formula: `pi`.
/// `[x + y]`. MathIdent
Delimited }
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]`.
MathDelimited
}
impl MathDelimited {
/// The opening delimiter.
pub fn open(&self) -> MathAtom {
self.0.cast_first_match().unwrap_or_default()
} }
impl Delimited {
/// The contents, including the delimiters. /// The contents, including the delimiters.
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { pub fn body(&self) -> Math {
self.0.children().filter_map(Expr::cast_with_space) 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! { node! {
/// A base with an optional sub- and superscript in a formula: `a_1^2`. /// 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. /// The base of the script.
pub fn base(&self) -> Expr { 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. /// The subscript.
@ -705,26 +775,21 @@ impl Script {
node! { node! {
/// A fraction in a formula: `x/2` /// A fraction in a formula: `x/2`
Frac MathFrac
} }
impl Frac { impl MathFrac {
/// The numerator. /// The numerator.
pub fn num(&self) -> Expr { 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. /// The denominator.
pub fn denom(&self) -> Expr { 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! { node! {
/// An identifier: `it`. /// An identifier: `it`.
Ident Ident
@ -732,17 +797,13 @@ node! {
impl Ident { impl Ident {
/// Get the identifier. /// Get the identifier.
pub fn get(&self) -> &str { pub fn get(&self) -> &EcoString {
self.0.text().trim_start_matches('#') self.0.text()
} }
/// Take out the contained identifier. /// Take out the contained identifier.
pub fn take(self) -> EcoString { pub fn take(self) -> EcoString {
let text = self.0.into_text(); self.0.into_text()
match text.strip_prefix('#') {
Some(text) => text.into(),
Option::None => text,
}
} }
/// Get the identifier as a string slice. /// Get the identifier as a string slice.
@ -789,7 +850,7 @@ node! {
impl Int { impl Int {
/// Get the integer value. /// Get the integer value.
pub fn get(&self) -> i64 { 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 { impl Float {
/// Get the floating-point value. /// Get the floating-point value.
pub fn get(&self) -> f64 { 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(); .count();
let split = text.len() - 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..] { let unit = match &text[split..] {
"pt" => Unit::Length(AbsUnit::Pt), "pt" => Unit::Length(AbsUnit::Pt),
"mm" => Unit::Length(AbsUnit::Mm), "mm" => Unit::Length(AbsUnit::Mm),
@ -910,7 +971,19 @@ node! {
} }
impl CodeBlock { 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> + '_ { pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
self.0.children().filter_map(SyntaxNode::cast) self.0.children().filter_map(SyntaxNode::cast)
} }
@ -924,7 +997,7 @@ node! {
impl ContentBlock { impl ContentBlock {
/// The contained markup. /// The contained markup.
pub fn body(&self) -> 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 { impl Parenthesized {
/// The wrapped expression. /// The wrapped expression.
pub fn expr(&self) -> Expr { pub fn expr(&self) -> Expr {
self.0 self.0.cast_first_match().unwrap_or_default()
.cast_first_match()
.expect("parenthesized expression is missing expression")
} }
} }
@ -1029,12 +1100,12 @@ node! {
impl Named { impl Named {
/// The name: `thickness`. /// The name: `thickness`.
pub fn name(&self) -> Ident { 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`. /// The right-hand side of the pair: `3pt`.
pub fn expr(&self) -> Expr { 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 self.0
.children() .children()
.find_map(|node| node.cast::<Str>()) .find_map(|node| node.cast::<Str>())
.expect("keyed pair is missing key") .unwrap_or_default()
} }
/// The right-hand side of the pair: `true`. /// The right-hand side of the pair: `true`.
pub fn expr(&self) -> Expr { 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 self.0
.children() .children()
.find_map(|node| UnOp::from_kind(node.kind())) .find_map(|node| UnOp::from_kind(node.kind()))
.expect("unary operation is missing operator") .unwrap_or(UnOp::Pos)
} }
/// The expression to operate on: `x`. /// The expression to operate on: `x`.
pub fn expr(&self) -> Expr { 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), SyntaxKind::In if not => Some(BinOp::NotIn),
_ => BinOp::from_kind(node.kind()), _ => BinOp::from_kind(node.kind()),
}) })
.expect("binary operation is missing operator") .unwrap_or(BinOp::Add)
} }
/// The left-hand side of the operation: `a`. /// The left-hand side of the operation: `a`.
pub fn lhs(&self) -> Expr { pub fn lhs(&self) -> Expr {
self.0 self.0.cast_first_match().unwrap_or_default()
.cast_first_match()
.expect("binary operation is missing left-hand side")
} }
/// The right-hand side of the operation: `b`. /// The right-hand side of the operation: `b`.
pub fn rhs(&self) -> Expr { pub fn rhs(&self) -> Expr {
self.0 self.0.cast_last_match().unwrap_or_default()
.cast_last_match()
.expect("binary operation is missing right-hand side")
} }
} }
@ -1317,12 +1384,12 @@ node! {
impl FieldAccess { impl FieldAccess {
/// The expression to access the field on. /// The expression to access the field on.
pub fn target(&self) -> Expr { 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. /// The name of the field.
pub fn field(&self) -> Ident { 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 { impl FuncCall {
/// The function to call. /// The function to call.
pub fn callee(&self) -> Expr { 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. /// The arguments to the function.
pub fn args(&self) -> Args { pub fn args(&self) -> Args {
self.0 self.0.cast_last_match().unwrap_or_default()
.cast_last_match()
.expect("function call is missing argument list")
} }
} }
@ -1353,19 +1418,17 @@ node! {
impl MethodCall { impl MethodCall {
/// The expression to call the method on. /// The expression to call the method on.
pub fn target(&self) -> Expr { 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. /// The name of the method.
pub fn method(&self) -> Ident { 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. /// The arguments to the method.
pub fn args(&self) -> Args { pub fn args(&self) -> Args {
self.0 self.0.cast_last_match().unwrap_or_default()
.cast_last_match()
.expect("method call is missing argument list")
} }
} }
@ -1428,14 +1491,13 @@ impl Closure {
self.0 self.0
.children() .children()
.find(|x| x.kind() == SyntaxKind::Params) .find(|x| x.kind() == SyntaxKind::Params)
.expect("closure is missing parameter list") .map_or([].iter(), |params| params.children())
.children()
.filter_map(SyntaxNode::cast) .filter_map(SyntaxNode::cast)
} }
/// The body of the closure. /// The body of the closure.
pub fn body(&self) -> Expr { 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 { pub fn binding(&self) -> Ident {
match self.0.cast_first_match() { match self.0.cast_first_match() {
Some(Expr::Ident(binding)) => binding, Some(Expr::Ident(binding)) => binding,
Some(Expr::Closure(closure)) => { Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(),
closure.name().expect("let-bound closure is missing name") _ => Ident::default(),
}
_ => panic!("let is missing binding"),
} }
} }
@ -1506,12 +1566,12 @@ node! {
impl SetRule { impl SetRule {
/// The function to set style properties for. /// The function to set style properties for.
pub fn target(&self) -> Expr { 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. /// The style properties to set.
pub fn args(&self) -> Args { 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. /// A condition under which the set rule applies.
@ -1540,7 +1600,7 @@ impl ShowRule {
/// The transformation recipe. /// The transformation recipe.
pub fn transform(&self) -> Expr { 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 { impl Conditional {
/// The condition which selects the body to evaluate. /// The condition which selects the body to evaluate.
pub fn condition(&self) -> Expr { 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. /// The expression to evaluate if the condition is true.
@ -1561,7 +1621,7 @@ impl Conditional {
.children() .children()
.filter_map(SyntaxNode::cast) .filter_map(SyntaxNode::cast)
.nth(1) .nth(1)
.expect("conditional is missing body") .unwrap_or_default()
} }
/// The expression to evaluate if the condition is false. /// The expression to evaluate if the condition is false.
@ -1578,12 +1638,12 @@ node! {
impl WhileLoop { impl WhileLoop {
/// The condition which selects whether to evaluate the body. /// The condition which selects whether to evaluate the body.
pub fn condition(&self) -> Expr { 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. /// The expression to evaluate while the condition is true.
pub fn body(&self) -> Expr { 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 { impl ForLoop {
/// The pattern to assign to. /// The pattern to assign to.
pub fn pattern(&self) -> ForPattern { 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. /// The expression to iterate over.
pub fn iter(&self) -> Expr { 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. /// The expression to evaluate for each iteration.
pub fn body(&self) -> Expr { 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. /// The value part of the pattern.
pub fn value(&self) -> Ident { 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 { impl ModuleImport {
/// The module or path from which the items should be imported. /// The module or path from which the items should be imported.
pub fn source(&self) -> Expr { 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. /// The items to be imported.
@ -1673,7 +1733,7 @@ node! {
impl ModuleInclude { impl ModuleInclude {
/// The module or path from which the content should be included. /// The module or path from which the content should be included.
pub fn source(&self) -> Expr { pub fn source(&self) -> Expr {
self.0.cast_last_match().expect("module include is missing path") self.0.cast_last_match().unwrap_or_default()
} }
} }

View File

@ -58,19 +58,26 @@ pub enum SyntaxKind {
/// Introduces a term item: `/`. /// Introduces a term item: `/`.
TermMarker, TermMarker,
/// A mathematical formula: `$x$`, `$ x^2 $`. /// A mathematical formula: `$x$`, `$ x^2 $`.
Formula,
/// Mathematical markup.
Math, Math,
/// An atom in math: `x`, `+`, `12`. /// 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: /// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`. /// `[x + y]`.
Delimited, MathDelimited,
/// A base with optional sub- and superscripts in math: `a_1^2`. /// A base with optional sub- and superscripts in math: `a_1^2`.
Script, MathScript,
/// A fraction in math: `x/2`. /// A fraction in math: `x/2`.
Frac, MathFrac,
/// An alignment point in math: `&`.
AlignPoint,
/// A hashtag that switches into code mode: `#`.
Hashtag,
/// A left curly brace, starting a code block: `{`. /// A left curly brace, starting a code block: `{`.
LeftBrace, LeftBrace,
/// A right curly brace, terminating a code block: `}`. /// A right curly brace, terminating a code block: `}`.
@ -175,6 +182,8 @@ pub enum SyntaxKind {
/// The `as` keyword. /// The `as` keyword.
As, As,
/// Code.
Code,
/// An identifier: `it`. /// An identifier: `it`.
Ident, Ident,
/// A boolean: `true`, `false`. /// A boolean: `true`, `false`.
@ -338,12 +347,15 @@ impl SyntaxKind {
Self::EnumMarker => "enum marker", Self::EnumMarker => "enum marker",
Self::TermItem => "term list item", Self::TermItem => "term list item",
Self::TermMarker => "term marker", Self::TermMarker => "term marker",
Self::Math => "math formula", Self::Formula => "math formula",
Self::Delimited => "delimited math", Self::Math => "math",
Self::Atom => "math atom", Self::MathIdent => "math identifier",
Self::Script => "script", Self::MathAtom => "math atom",
Self::Frac => "fraction", Self::MathAlignPoint => "math alignment point",
Self::AlignPoint => "alignment point", Self::MathDelimited => "delimited math",
Self::MathScript => "math script",
Self::MathFrac => "math fraction",
Self::Hashtag => "hashtag",
Self::LeftBrace => "opening brace", Self::LeftBrace => "opening brace",
Self::RightBrace => "closing brace", Self::RightBrace => "closing brace",
Self::LeftBracket => "opening bracket", Self::LeftBracket => "opening bracket",
@ -394,6 +406,7 @@ impl SyntaxKind {
Self::Import => "keyword `import`", Self::Import => "keyword `import`",
Self::Include => "keyword `include`", Self::Include => "keyword `include`",
Self::As => "keyword `as`", Self::As => "keyword `as`",
Self::Code => "code",
Self::Ident => "identifier", Self::Ident => "identifier",
Self::Bool => "boolean", Self::Bool => "boolean",
Self::Int => "integer", Self::Int => "integer",

View File

@ -376,7 +376,7 @@ impl Lexer<'_> {
Some('-') if !s.at(['-', '?']) => {} Some('-') if !s.at(['-', '?']) => {}
Some('.') if !s.at("..") => {} Some('.') if !s.at("..") => {}
Some('h') if !s.at("ttp://") && !s.at("ttps://") => {} Some('h') if !s.at("ttp://") && !s.at("ttps://") => {}
Some('@' | '#') if !s.at(is_id_start) => {} Some('@') if !s.at(is_id_start) => {}
_ => break, _ => break,
} }
@ -410,15 +410,8 @@ impl Lexer<'_> {
'\\' => self.backslash(), '\\' => self.backslash(),
':' if self.s.at(is_id_start) => self.maybe_symbol(), ':' if self.s.at(is_id_start) => self.maybe_symbol(),
'"' => self.string(), '"' => 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, '<' 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, '[' 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::Underscore,
'$' => SyntaxKind::Dollar, '$' => SyntaxKind::Dollar,
'/' => SyntaxKind::Slash, '/' => SyntaxKind::Slash,
'^' => SyntaxKind::Hat, '^' => SyntaxKind::Hat,
'&' => SyntaxKind::AlignPoint, '&' => SyntaxKind::MathAlignPoint,
// Identifiers and symbol notation. // Identifiers and symbol notation.
c if is_math_id_start(c) && self.s.at(is_math_id_continue) => { 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); .map_or(0, str::len);
self.s.jump(start + len); self.s.jump(start + len);
} }
SyntaxKind::Atom SyntaxKind::MathAtom
} }
} }

View File

@ -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. /// Try to convert the node to a typed AST node.
pub fn cast<T: AstNode>(&self) -> Option<T> { pub fn cast<T: AstNode>(&self) -> Option<T> {
T::from_untyped(self) 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. /// Convert the child to an error.
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) { pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
let len = self.len(); let len = self.len();
@ -695,6 +710,14 @@ impl<'a> LinkedNode<'a> {
Some(next) 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. /// Access to leafs.
@ -865,8 +888,8 @@ mod tests {
// Go back to "#set". Skips the space. // Go back to "#set". Skips the space.
let prev = node.prev_sibling().unwrap(); let prev = node.prev_sibling().unwrap();
assert_eq!(prev.offset(), 0); assert_eq!(prev.offset(), 1);
assert_eq!(prev.text(), "#set"); assert_eq!(prev.text(), "set");
} }
#[test] #[test]
@ -875,7 +898,7 @@ mod tests {
let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap(); let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
let prev = leaf.prev_leaf().unwrap(); let prev = leaf.prev_leaf().unwrap();
assert_eq!(leaf.text(), "fun"); assert_eq!(leaf.text(), "fun");
assert_eq!(prev.text(), "#set"); assert_eq!(prev.text(), "set");
let source = Source::detached("#let x = 10"); let source = Source::detached("#let x = 10");
let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap(); let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap();

View File

@ -18,9 +18,7 @@ pub fn parse(text: &str) -> SyntaxNode {
/// This is only used for syntax highlighting. /// This is only used for syntax highlighting.
pub fn parse_code(text: &str) -> SyntaxNode { pub fn parse_code(text: &str) -> SyntaxNode {
let mut p = Parser::new(text, 0, LexMode::Code); let mut p = Parser::new(text, 0, LexMode::Code);
let m = p.marker();
code(&mut p, |_| false); code(&mut p, |_| false);
p.wrap(m, SyntaxKind::CodeBlock);
p.finish().into_iter().next().unwrap() p.finish().into_iter().next().unwrap()
} }
@ -31,7 +29,15 @@ fn markup(
mut stop: impl FnMut(SyntaxKind) -> bool, mut stop: impl FnMut(SyntaxKind) -> bool,
) { ) {
let m = p.marker(); 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() { if p.newline() {
at_start = true; at_start = true;
if min_indent > 0 && p.column(p.current_end()) < min_indent { if min_indent > 0 && p.column(p.current_end()) < min_indent {
@ -54,10 +60,18 @@ pub(super) fn reparse_markup(
text: &str, text: &str,
range: Range<usize>, range: Range<usize>,
at_start: &mut bool, at_start: &mut bool,
nesting: &mut usize,
mut stop: impl FnMut(SyntaxKind) -> bool, mut stop: impl FnMut(SyntaxKind) -> bool,
) -> Option<Vec<SyntaxNode>> { ) -> Option<Vec<SyntaxNode>> {
let mut p = Parser::new(text, range.start, LexMode::Markup); 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() { if p.newline() {
*at_start = true; *at_start = true;
p.eat(); p.eat();
@ -75,46 +89,6 @@ pub(super) fn reparse_markup(
fn markup_expr(p: &mut Parser, at_start: &mut bool) { fn markup_expr(p: &mut Parser, at_start: &mut bool) {
match p.current() { match p.current() {
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::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::Space
| SyntaxKind::Parbreak | SyntaxKind::Parbreak
| SyntaxKind::LineComment | SyntaxKind::LineComment
@ -122,6 +96,34 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
p.eat(); p.eat();
return; 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 => formula(p),
SyntaxKind::LeftBracket
| SyntaxKind::RightBracket
| SyntaxKind::HeadingMarker
| SyntaxKind::ListMarker
| SyntaxKind::EnumMarker
| SyntaxKind::TermMarker
| SyntaxKind::Colon => p.convert(SyntaxKind::Text),
_ => {} _ => {}
} }
@ -130,7 +132,7 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) {
fn strong(p: &mut Parser) { fn strong(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.expect(SyntaxKind::Star); p.assert(SyntaxKind::Star);
markup(p, false, 0, |kind| { markup(p, false, 0, |kind| {
kind == SyntaxKind::Star kind == SyntaxKind::Star
|| kind == SyntaxKind::Parbreak || kind == SyntaxKind::Parbreak
@ -142,7 +144,7 @@ fn strong(p: &mut Parser) {
fn emph(p: &mut Parser) { fn emph(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.expect(SyntaxKind::Underscore); p.assert(SyntaxKind::Underscore);
markup(p, false, 0, |kind| { markup(p, false, 0, |kind| {
kind == SyntaxKind::Underscore kind == SyntaxKind::Underscore
|| kind == SyntaxKind::Parbreak || kind == SyntaxKind::Parbreak
@ -154,7 +156,7 @@ fn emph(p: &mut Parser) {
fn heading(p: &mut Parser) { fn heading(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.expect(SyntaxKind::HeadingMarker); p.assert(SyntaxKind::HeadingMarker);
whitespace(p); whitespace(p);
markup(p, false, usize::MAX, |kind| { markup(p, false, usize::MAX, |kind| {
kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket
@ -164,7 +166,7 @@ fn heading(p: &mut Parser) {
fn list_item(p: &mut Parser) { fn list_item(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.expect(SyntaxKind::ListMarker); p.assert(SyntaxKind::ListMarker);
let min_indent = p.column(p.prev_end()); let min_indent = p.column(p.prev_end());
whitespace(p); whitespace(p);
markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket); 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) { fn enum_item(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.expect(SyntaxKind::EnumMarker); p.assert(SyntaxKind::EnumMarker);
let min_indent = p.column(p.prev_end()); let min_indent = p.column(p.prev_end());
whitespace(p); whitespace(p);
markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket); 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) { fn term_item(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.expect(SyntaxKind::TermMarker); p.assert(SyntaxKind::TermMarker);
let min_indent = p.column(p.prev_end()); let min_indent = p.column(p.prev_end());
whitespace(p); whitespace(p);
markup(p, false, usize::MAX, |kind| { 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(); let m = p.marker();
p.enter(LexMode::Math); p.enter(LexMode::Math);
p.expect(SyntaxKind::Dollar); p.assert(SyntaxKind::Dollar);
math(p, |kind| kind == SyntaxKind::Dollar); math(p, |kind| kind == SyntaxKind::Dollar);
p.expect(SyntaxKind::Dollar); p.expect(SyntaxKind::Dollar);
p.exit(); p.exit();
p.wrap(m, SyntaxKind::Math); p.wrap(m, SyntaxKind::Formula);
} }
fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
let m = p.marker();
while !p.eof() && !stop(p.current()) { while !p.eof() && !stop(p.current()) {
let prev = p.prev_end(); let prev = p.prev_end();
math_expr(p); math_expr(p);
@ -218,6 +221,7 @@ fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
p.unexpected(); p.unexpected();
} }
} }
p.wrap(m, SyntaxKind::Math);
} }
fn math_expr(p: &mut Parser) { 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) { fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
let m = p.marker(); let m = p.marker();
match p.current() { match p.current() {
SyntaxKind::Ident => { SyntaxKind::Hashtag => embedded_code_expr(p),
SyntaxKind::MathIdent => {
p.eat(); p.eat();
if p.directly_at(SyntaxKind::Atom) && p.current_text() == "(" { if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" {
math_args(p); math_args(p);
p.wrap(m, SyntaxKind::FuncCall); 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) => { SyntaxKind::MathAtom => {
if math_class(p.current_text()) == Some(MathClass::Fence) {
math_delimited(p, MathClass::Fence) math_delimited(p, MathClass::Fence)
} } else if math_class(p.current_text()) == Some(MathClass::Opening) {
SyntaxKind::Atom if math_class(p.current_text()) == Some(MathClass::Opening) => {
math_delimited(p, MathClass::Closing) math_delimited(p, MathClass::Closing)
} else {
p.eat()
}
} }
SyntaxKind::Let SyntaxKind::Linebreak
| 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::Escape | SyntaxKind::Escape
| SyntaxKind::Shorthand | SyntaxKind::Shorthand
| SyntaxKind::Symbol | SyntaxKind::MathAlignPoint
| SyntaxKind::AlignPoint
| SyntaxKind::Str => p.eat(), | SyntaxKind::Str => p.eat(),
_ => return, _ => p.expected("expression"),
} }
while !p.eof() && !p.at(stop) { 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 => {} ast::Assoc::Right => {}
} }
if kind == SyntaxKind::MathFrac {
math_unparen(p, m);
}
p.eat(); p.eat();
let m2 = p.marker();
math_expr_prec(p, prec, stop); math_expr_prec(p, prec, stop);
math_unparen(p, m2);
if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) {
let m3 = p.marker();
math_expr_prec(p, prec, SyntaxKind::Eof); math_expr_prec(p, prec, SyntaxKind::Eof);
math_unparen(p, m3);
} }
p.wrap(m, kind); 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) { fn math_delimited(p: &mut Parser, stop: MathClass) {
let m = p.marker(); let m = p.marker();
p.expect(SyntaxKind::Atom); p.assert(SyntaxKind::MathAtom);
let m2 = p.marker();
while !p.eof() && !p.at(SyntaxKind::Dollar) { while !p.eof() && !p.at(SyntaxKind::Dollar) {
if math_class(p.current_text()) == Some(stop) { if math_class(p.current_text()) == Some(stop) {
p.eat(); p.wrap(m2, SyntaxKind::Math);
p.wrap(m, SyntaxKind::Delimited); p.assert(SyntaxKind::MathAtom);
p.wrap(m, SyntaxKind::MathDelimited);
return; 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> { fn math_class(text: &str) -> Option<MathClass> {
let mut chars = text.chars(); let mut chars = text.chars();
chars chars
@ -321,20 +351,20 @@ fn math_class(text: &str) -> Option<MathClass> {
fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> { fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> {
match kind { match kind {
SyntaxKind::Underscore => { SyntaxKind::Underscore => {
Some((SyntaxKind::Script, SyntaxKind::Hat, ast::Assoc::Right, 2)) Some((SyntaxKind::MathScript, SyntaxKind::Hat, ast::Assoc::Right, 2))
} }
SyntaxKind::Hat => { SyntaxKind::Hat => {
Some((SyntaxKind::Script, SyntaxKind::Underscore, ast::Assoc::Right, 2)) Some((SyntaxKind::MathScript, SyntaxKind::Underscore, ast::Assoc::Right, 2))
} }
SyntaxKind::Slash => { SyntaxKind::Slash => {
Some((SyntaxKind::Frac, SyntaxKind::Eof, ast::Assoc::Left, 1)) Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1))
} }
_ => None, _ => None,
} }
} }
fn math_args(p: &mut Parser) { fn math_args(p: &mut Parser) {
p.expect(SyntaxKind::Atom); p.assert(SyntaxKind::MathAtom);
let m = p.marker(); let m = p.marker();
let mut m2 = p.marker(); let mut m2 = p.marker();
while !p.eof() && !p.at(SyntaxKind::Dollar) { while !p.eof() && !p.at(SyntaxKind::Dollar) {
@ -359,10 +389,11 @@ fn math_args(p: &mut Parser) {
p.wrap(m2, SyntaxKind::Math); p.wrap(m2, SyntaxKind::Math);
} }
p.wrap(m, SyntaxKind::Args); p.wrap(m, SyntaxKind::Args);
p.expect(SyntaxKind::Atom); p.expect(SyntaxKind::MathAtom);
} }
fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
let m = p.marker();
while !p.eof() && !stop(p.current()) { while !p.eof() && !stop(p.current()) {
p.stop_at_newline(true); p.stop_at_newline(true);
let prev = p.prev_end(); let prev = p.prev_end();
@ -379,6 +410,7 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) {
p.unexpected(); p.unexpected();
} }
} }
p.wrap(m, SyntaxKind::Code);
} }
fn code_expr(p: &mut Parser) { fn code_expr(p: &mut Parser) {
@ -386,6 +418,10 @@ fn code_expr(p: &mut Parser) {
} }
fn embedded_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!( let stmt = matches!(
p.current(), p.current(),
SyntaxKind::Let SyntaxKind::Let
@ -395,13 +431,12 @@ fn embedded_code_expr(p: &mut Parser) {
| SyntaxKind::Include | SyntaxKind::Include
); );
p.stop_at_newline(true);
p.enter(LexMode::Code);
code_expr_prec(p, true, 0); code_expr_prec(p, true, 0);
let semi = p.eat_if(SyntaxKind::Semicolon); let semi = p.eat_if(SyntaxKind::Semicolon);
if stmt && !semi && !p.eof() && !p.at(SyntaxKind::RightBracket) { if stmt && !semi && !p.eof() && !p.at(SyntaxKind::RightBracket) {
p.expected("semicolon or line break"); p.expected("semicolon or line break");
} }
p.exit(); p.exit();
p.unstop(); p.unstop();
} }
@ -424,7 +459,10 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) {
continue; 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; break;
} }
@ -480,7 +518,7 @@ fn code_primary(p: &mut Parser, atomic: bool) {
p.eat(); p.eat();
if !atomic && p.at(SyntaxKind::Arrow) { if !atomic && p.at(SyntaxKind::Arrow) {
p.wrap(m, SyntaxKind::Params); p.wrap(m, SyntaxKind::Params);
p.expect(SyntaxKind::Arrow); p.assert(SyntaxKind::Arrow);
code_expr(p); code_expr(p);
p.wrap(m, SyntaxKind::Closure); p.wrap(m, SyntaxKind::Closure);
} }
@ -489,7 +527,7 @@ fn code_primary(p: &mut Parser, atomic: bool) {
SyntaxKind::LeftBrace => code_block(p), SyntaxKind::LeftBrace => code_block(p),
SyntaxKind::LeftBracket => content_block(p), SyntaxKind::LeftBracket => content_block(p),
SyntaxKind::LeftParen => with_paren(p), SyntaxKind::LeftParen => with_paren(p),
SyntaxKind::Dollar => equation(p), SyntaxKind::Dollar => formula(p),
SyntaxKind::Let => let_binding(p), SyntaxKind::Let => let_binding(p),
SyntaxKind::Set => set_rule(p), SyntaxKind::Set => set_rule(p),
SyntaxKind::Show => show_rule(p), SyntaxKind::Show => show_rule(p),
@ -536,7 +574,7 @@ fn code_block(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.enter(LexMode::Code); p.enter(LexMode::Code);
p.stop_at_newline(false); p.stop_at_newline(false);
p.expect(SyntaxKind::LeftBrace); p.assert(SyntaxKind::LeftBrace);
code(p, |kind| kind == SyntaxKind::RightBrace); code(p, |kind| kind == SyntaxKind::RightBrace);
p.expect(SyntaxKind::RightBrace); p.expect(SyntaxKind::RightBrace);
p.exit(); p.exit();
@ -547,7 +585,7 @@ fn code_block(p: &mut Parser) {
fn content_block(p: &mut Parser) { fn content_block(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.enter(LexMode::Markup); p.enter(LexMode::Markup);
p.expect(SyntaxKind::LeftBracket); p.assert(SyntaxKind::LeftBracket);
markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket); markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket);
p.expect(SyntaxKind::RightBracket); p.expect(SyntaxKind::RightBracket);
p.exit(); p.exit();
@ -560,7 +598,7 @@ fn with_paren(p: &mut Parser) {
if p.at(SyntaxKind::Arrow) { if p.at(SyntaxKind::Arrow) {
validate_params(p, m); validate_params(p, m);
p.wrap(m, SyntaxKind::Params); p.wrap(m, SyntaxKind::Params);
p.expect(SyntaxKind::Arrow); p.assert(SyntaxKind::Arrow);
code_expr(p); code_expr(p);
kind = SyntaxKind::Closure; kind = SyntaxKind::Closure;
} }
@ -574,7 +612,7 @@ fn with_paren(p: &mut Parser) {
fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind {
p.stop_at_newline(false); p.stop_at_newline(false);
p.expect(SyntaxKind::LeftParen); p.assert(SyntaxKind::LeftParen);
let mut count = 0; let mut count = 0;
let mut parenthesized = true; let mut parenthesized = true;

View File

@ -114,20 +114,30 @@ fn try_reparse(
end += 1; 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 prefix_len = 0;
let mut nesting = 0;
let mut at_start = true; let mut at_start = true;
for child in &children[..start] { for child in &children[..start] {
prefix_len += child.len(); prefix_len += child.len();
next_at_start(child, &mut at_start); 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. // Determine what `at_start` will have to be at the end of the reparse.
let mut prev_len = 0; let mut prev_len = 0;
let mut prev_at_start_after = at_start; let mut prev_at_start_after = at_start;
let mut prev_nesting_after = nesting;
for child in &children[start..end] { for child in &children[start..end] {
prev_len += child.len(); prev_len += child.len();
next_at_start(child, &mut prev_at_start_after); next_at_start(child, &mut prev_at_start_after);
next_nesting(child, &mut prev_nesting_after);
} }
let shifted = offset + prefix_len; let shifted = offset + prefix_len;
@ -139,11 +149,11 @@ fn try_reparse(
}; };
if let Some(newborns) = 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 kind == stop_kind
}) })
{ {
if at_start == prev_at_start_after { if at_start == prev_at_start_after && nesting == prev_nesting_after {
return node return node
.replace_children(start..end, newborns) .replace_children(start..end, newborns)
.is_ok() .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)] #[cfg(test)]
mod tests { mod tests {
use std::ops::Range; use std::ops::Range;
@ -209,9 +230,13 @@ mod tests {
panic!("test failed"); panic!("test failed");
} }
if incremental { if incremental {
assert_ne!(source.len_bytes(), range.len()); assert_ne!(source.len_bytes(), range.len(), "should have been incremental");
} else { } 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("abc~def~ghi", 5..6, "+", true);
test("~~~~~~~", 3..4, "A", true); test("~~~~~~~", 3..4, "A", true);
test("abc~~", 1..2, "", true); test("abc~~", 1..2, "", true);
test("#var. hello", 5..6, " ", false);
test("#var;hello", 9..10, "a", false); test("#var;hello", 9..10, "a", false);
test("https:/world", 7..7, "/", false); test("https:/world", 7..7, "/", false);
test("hello world", 7..12, "walkers", 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("a d e", 1..3, " b c d", false);
test("~*~*~", 2..2, "*", false); test("~*~*~", 2..2, "*", false);
test("::1\n2. a\n3", 7..7, "4", true); test("::1\n2. a\n3", 7..7, "4", true);
test("* {1+2} *", 5..6, "3", true); test("* #{1+2} *", 6..7, "3", true);
test("{(0, 1, 2)}", 5..6, "11pt", false); test("#{(0, 1, 2)}", 6..7, "11pt", true);
test("\n= A heading", 4..4, "n evocative", false); test("\n= A heading", 4..4, "n evocative", false);
test("#call() abc~d", 7..7, "[]", true); test("#call() abc~d", 7..7, "[]", true);
test("a your thing a", 6..7, "a", false); test("a your thing a", 6..7, "a", false);
@ -239,24 +265,26 @@ mod tests {
test("#for", 4..4, "//", false); test("#for", 4..4, "//", false);
test("a\n#let \nb", 7..7, "i", true); test("a\n#let \nb", 7..7, "i", true);
test("#let x = (1, 2 + ;~ Five\r\n\r", 20..23, "2.", 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(r#"a ```typst hello```"#, 16..17, "", false);
} }
#[test] #[test]
fn test_reparse_block() { fn test_reparse_block() {
test("Hello { x + 1 }!", 8..9, "abc", true); test("Hello #{ x + 1 }!", 9..10, "abc", true);
test("A{}!", 2..2, "\"", false); test("A#{}!", 3..3, "\"", false);
test("{ [= x] }!", 4..4, "=", true); test("#{ [= x] }!", 5..5, "=", true);
test("[[]]", 2..2, "\\", false); test("#[[]]", 3..3, "\\", false);
test("[[ab]]", 3..4, "\\", false); test("#[[ab]]", 4..5, "\\", false);
test("{}}", 1..1, "{", false); test("#{}}", 2..2, "{", false);
test("A: [BC]", 5..5, "{", false); test("A: #[BC]", 6..6, "{", true);
test("A: [BC]", 5..5, "{}", true); test("A: #[BC]", 6..6, "#{", false);
test("{\"ab\"}A", 4..4, "c", true); test("A: #[BC]", 6..6, "#{}", true);
test("{\"ab\"}A", 4..5, "c", false); test("#{\"ab\"}A", 5..5, "c", true);
test("a[]b", 2..2, "{", false); test("#{\"ab\"}A", 5..6, "c", false);
test("a{call(); abc}b", 7..7, "[]", true); 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 #while x {\n g(x) \n} b", 12..12, "//", true);
test("a#[]b", 3..3, "[hey]", true);
} }
} }

View File

@ -21,7 +21,7 @@ No heading
// Parsed as headings if at start of the context. // Parsed as headings if at start of the context.
/**/ = Level 1 /**/ = Level 1
{[== Level 2]} #{[== Level 2]}
#box[=== Level 3] #box[=== Level 3]
// Not at the start of the context. // Not at the start of the context.
@ -33,7 +33,7 @@ No = heading
--- ---
// Blocks can continue the heading. // Blocks can continue the heading.
= [This = #[This
is is
multiline. multiline.
] ]

View File

@ -24,7 +24,7 @@ _Shopping list_
--- ---
- Level 1 - Level 1
- Level [ - Level #[
2 through content block 2 through content block
] ]

View File

@ -7,19 +7,19 @@
#set page(width: 150pt) #set page(width: 150pt)
// Empty. // Empty.
{()} #{()}
// Not an array, just a parenthesized expression. // Not an array, just a parenthesized expression.
{(1)} #{(1)}
// One item and trailing comma. // One item and trailing comma.
{(-1,)} #{(-1,)}
// No trailing comma. // No trailing comma.
{(true, false)} #{(true, false)}
// Multiple lines and items and trailing comma. // Multiple lines and items and trailing comma.
{("1" #{("1"
, rgb("002") , rgb("002")
,)} ,)}
@ -30,7 +30,7 @@
--- ---
// Test lvalue and rvalue access. // Test lvalue and rvalue access.
{ #{
let array = (1, 2) let array = (1, 2)
array.at(1) += 5 + array.at(0) array.at(1) += 5 + array.at(0)
test(array, (1, 8)) test(array, (1, 8))
@ -38,7 +38,7 @@
--- ---
// Test different lvalue method. // Test different lvalue method.
{ #{
let array = (1, 2, 3) let array = (1, 2, 3)
array.first() = 7 array.first() = 7
array.at(1) *= 8 array.at(1) *= 8
@ -47,12 +47,12 @@
--- ---
// Test rvalue out of bounds. // Test rvalue out of bounds.
// Error: 2-17 array index out of bounds (index: 5, len: 3) // Error: 3-18 array index out of bounds (index: 5, len: 3)
{(1, 2, 3).at(5)} #{(1, 2, 3).at(5)}
--- ---
// Test lvalue out of bounds. // Test lvalue out of bounds.
{ #{
let array = (1, 2, 3) let array = (1, 2, 3)
// Error: 3-14 array index out of bounds (index: 3, len: 3) // Error: 3-14 array index out of bounds (index: 3, len: 3)
array.at(3) = 5 array.at(3) = 5
@ -60,19 +60,19 @@
--- ---
// Test bad lvalue. // 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) #let array = (1, 2, 3)
{ array.len() = 4 } #{ array.len() = 4 }
--- ---
// Test bad lvalue. // 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) #let array = (1, 2, 3)
{ array.yolo() = 4 } #{ array.yolo() = 4 }
--- ---
// Test negative indices. // Test negative indices.
{ #{
let array = (1, 2, 3, 4) let array = (1, 2, 3, 4)
test(array.at(0), 1) test(array.at(0), 1)
test(array.at(-1), 4) test(array.at(-1), 4)
@ -89,16 +89,16 @@
#test((1, 2, 3).last(), 3) #test((1, 2, 3).last(), 3)
--- ---
// Error: 3-13 array is empty // Error: 4-14 array is empty
{ ().first() } #{ ().first() }
--- ---
// Error: 3-12 array is empty // Error: 4-13 array is empty
{ ().last() } #{ ().last() }
--- ---
// Test the `push` and `pop` methods. // Test the `push` and `pop` methods.
{ #{
let tasks = (a: (1, 2, 3), b: (4, 5, 6)) let tasks = (a: (1, 2, 3), b: (4, 5, 6))
tasks.at("a").pop() tasks.at("a").pop()
tasks.b.push(7) tasks.b.push(7)
@ -108,7 +108,7 @@
--- ---
// Test the `insert` and `remove` methods. // Test the `insert` and `remove` methods.
{ #{
let array = (0, 1, 2, 4, 5) let array = (0, 1, 2, 4, 5)
array.insert(3, 3) array.insert(3, 3)
test(array, range(6)) 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 = () #let numbers = ()
{ numbers.insert() } #{ numbers.insert() }
--- ---
// Test the `slice` method. // Test the `slice` method.
#test((1, 2, 3, 4).slice(2), (3, 4)) #test((1, 2, 3, 4).slice(2), (3, 4))
@ -132,12 +133,12 @@
#test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D") #test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D")
--- ---
// Error: 3-31 array index out of bounds (index: 12, len: 10) // Error: 4-32 array index out of bounds (index: 12, len: 10)
{ range(10).slice(9, count: 3) } #{ range(10).slice(9, count: 3) }
--- ---
// Error: 3-25 array index out of bounds (index: -4, len: 3) // Error: 4-26 array index out of bounds (index: -4, len: 3)
{ (1, 2, 3).slice(0, -4) } #{ (1, 2, 3).slice(0, -4) }
--- ---
// Test the `position` method. // Test the `position` method.
@ -162,8 +163,8 @@
#test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10) #test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10)
--- ---
// Error: 21-31 function must have exactly two parameters // Error: 22-32 function must have exactly two parameters
{ (1, 2, 3).fold(0, () => none) } #{ (1, 2, 3).fold(0, () => none) }
--- ---
// Test the `rev` method. // Test the `rev` method.
@ -177,17 +178,17 @@
#test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)") #test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)")
--- ---
// Error: 2-22 cannot join boolean with boolean // Error: 3-23 cannot join boolean with boolean
{(true, false).join()} #{(true, false).join()}
--- ---
// Error: 2-20 cannot join string with integer // Error: 3-21 cannot join string with integer
{("a", "b").join(1)} #{("a", "b").join(1)}
--- ---
// Test joining content. // Test joining content.
// Ref: true // Ref: true
{([One], [Two], [Three]).join([, ], last: [ and ])}. #{([One], [Two], [Three]).join([, ], last: [ and ])}.
--- ---
// Test the `sorted` method. // 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)) #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 // Error: 3-27 cannot order content and content
{([Hi], [There]).sorted()} #{([Hi], [There]).sorted()}
--- ---
// Error: 2-18 array index out of bounds (index: -4, len: 3) // Error: 3-19 array index out of bounds (index: -4, len: 3)
{(1, 2, 3).at(-4)} #{(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 // Error: 5-7 unexpected end of block comment
{(1*/2)} #{(1*/2)}
// Error: 6-8 invalid number suffix // Error: 7-9 invalid number suffix
{(1, 1u 2)} #{(1, 1u 2)}
// Error: 3-4 unexpected comma // Error: 4-5 unexpected comma
{(,1)} #{(,1)}
// Missing expression makes named pair incomplete, making this an empty array. // Missing expression makes named pair incomplete, making this an empty array.
// Error: 5 expected expression // Error: 6 expected expression
{(a:)} #{(a:)}
// Named pair after this is already identified as an array. // Named pair after this is already identified as an array.
// Error: 6-10 expected expression, found named pair // Error: 7-11 expected expression, found named pair
{(1, b: 2)} #{(1, b: 2)}
// Keyed pair after this is already identified as an array. // Keyed pair after this is already identified as an array.
// Error: 6-14 expected expression, found keyed pair // Error: 7-15 expected expression, found keyed pair
{(1, "key": 2)} #{(1, "key": 2)}

View File

@ -7,7 +7,7 @@
// ... but also "content" values. While these contain markup, // ... but also "content" values. While these contain markup,
// they are also values and can be summed, stored in arrays etc. // they are also values and can be summed, stored in arrays etc.
// There are also more standard control flow structures, like #if and #for. // 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*] #let faculty = [*Fakultät II, Institut for Mathematik*]
// The `box` function just places content into a rectangular container. When // The `box` function just places content into a rectangular container. When

View File

@ -5,14 +5,14 @@
// Ref: true // Ref: true
// Evaluates to join of none, [My ] and the two loop bodies. // Evaluates to join of none, [My ] and the two loop bodies.
{ #{
let parts = ("my fri", "end.") let parts = ("my fri", "end.")
[Hello, ] [Hello, ]
for s in parts [{s}] for s in parts [#s]
} }
// Evaluates to join of the content and strings. // Evaluates to join of the content and strings.
{ #{
[How] [How]
if true { if true {
" are" " are"
@ -50,7 +50,7 @@
--- ---
// Some things can't be joined. // Some things can't be joined.
{ #{
[A] [A]
// Error: 3-4 cannot join content with integer // Error: 3-4 cannot join content with integer
1 1
@ -59,7 +59,7 @@
--- ---
// Block directly in markup also creates a scope. // Block directly in markup also creates a scope.
{ let x = 1 } #{ let x = 1 }
// Error: 7-8 unknown variable // Error: 7-8 unknown variable
#test(x, 1) #test(x, 1)
@ -73,22 +73,22 @@
#test(a, 1) #test(a, 1)
// Error: 2-3 unknown variable // Error: 3-4 unknown variable
{b} #{b}
--- ---
// Double block creates a scope. // Double block creates a scope.
{{ #{{
import "module.typ": b import "module.typ": b
test(b, 1) test(b, 1)
}} }}
// Error: 2-3 unknown variable // Error: 3-4 unknown variable
{b} #{b}
--- ---
// Multiple nested scopes. // Multiple nested scopes.
{ #{
let a = "a1" let a = "a1"
{ {
let a = "a2" let a = "a2"
@ -104,28 +104,28 @@
--- ---
// Content blocks also create a scope. // Content blocks also create a scope.
[#let x = 1] #[#let x = 1]
// Error: 2-3 unknown variable // Error: 3-4 unknown variable
{x} #{x}
--- ---
// Multiple unseparated expressions in one line. // Multiple unseparated expressions in one line.
// Error: 2-4 invalid number suffix // Error: 3-5 invalid number suffix
{1u} #{1u}
// Should output `1`. // Should output `1`.
// Error: 3 expected semicolon or line break // Error: 4 expected semicolon or line break
{1 2} #{1 2}
// Should output `2`. // Should output `2`.
// Error: 12 expected semicolon or line break // Error: 13 expected semicolon or line break
// Error: 22 expected semicolon or line break // Error: 23 expected semicolon or line break
{let x = -1 let y = 3 x + y} #{let x = -1 let y = 3 x + y}
// Should output `3`. // Should output `3`.
{ #{
// Error: 6 expected identifier // Error: 6 expected identifier
// Error: 10 expected block // Error: 10 expected block
for "v" 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
} #}

View File

@ -96,7 +96,7 @@
#let x = { continue } #let x = { continue }
--- ---
// Error: 1-10 cannot continue outside of loop // Error: 2-10 cannot continue outside of loop
#continue #continue
--- ---
@ -104,7 +104,7 @@
// Should output `Hello World 🌎`. // Should output `Hello World 🌎`.
#for _ in range(10) { #for _ in range(10) {
[Hello ] [Hello ]
[World { [World #{
[🌎] [🌎]
break break
}] }]

View File

@ -6,7 +6,7 @@
// Ommitted space. // Ommitted space.
#let f() = {} #let f() = {}
[#f()*Bold*] #[#f()*Bold*]
// Call return value of function with body. // Call return value of function with body.
#let f(x, body) = (y) => [#x] + body + [#y] #let f(x, body) = (y) => [#x] + body + [#y]
@ -34,7 +34,7 @@
#test(alias(alias), "function") #test(alias(alias), "function")
// Callee expressions. // Callee expressions.
{ #{
// Wrapped in parens. // Wrapped in parens.
test((type)("hi"), "string") test((type)("hi"), "string")
@ -48,25 +48,25 @@
#set text(family: "Arial", family: "Helvetica") #set text(family: "Arial", family: "Helvetica")
--- ---
// Error: 2-6 expected function, found boolean // Error: 3-7 expected function, found boolean
{true()} #{true()}
--- ---
#let x = "x" #let x = "x"
// Error: 1-3 expected function, found string // Error: 2-3 expected function, found string
#x() #x()
--- ---
#let f(x) = x #let f(x) = x
// Error: 1-6 expected function, found integer // Error: 2-6 expected function, found integer
#f(1)(2) #f(1)(2)
--- ---
#let f(x) = x #let f(x) = x
// Error: 1-6 expected function, found content // Error: 2-6 expected function, found content
#f[1](2) #f[1](2)
--- ---
@ -90,16 +90,16 @@
// Error: 7-12 expected identifier, found string // Error: 7-12 expected identifier, found string
#func("abc": 2) #func("abc": 2)
// Error: 7-10 expected identifier, found group // Error: 8-11 expected identifier, found group
{func((x):1)} #{func((x):1)}
--- ---
// Error: 2:1 expected closing bracket // Error: 2:1 expected closing bracket
#func[`a]` #func[`a]`
--- ---
// Error: 7 expected closing paren // Error: 8 expected closing paren
{func(} #{func(}
--- ---
// Error: 2:1 expected quote // Error: 2:1 expected quote

View File

@ -12,7 +12,7 @@
--- ---
// Basic closure without captures. // Basic closure without captures.
{ #{
let adder = (x, y) => x + y let adder = (x, y) => x + y
test(adder(2, 3), 5) test(adder(2, 3), 5)
} }
@ -20,7 +20,7 @@
--- ---
// Pass closure as argument and return closure. // Pass closure as argument and return closure.
// Also uses shorthand syntax for a single argument. // Also uses shorthand syntax for a single argument.
{ #{
let chain = (f, g) => (x) => f(g(x)) let chain = (f, g) => (x) => f(g(x))
let f = x => x + 1 let f = x => x + 1
let g = x => 2 * x let g = x => 2 * x
@ -30,7 +30,7 @@
--- ---
// Capture environment. // Capture environment.
{ #{
let mark = "!" let mark = "!"
let greet = { let greet = {
let hi = "Hi" let hi = "Hi"
@ -48,7 +48,7 @@
--- ---
// Redefined variable. // Redefined variable.
{ #{
let x = 1 let x = 1
let f() = { let f() = {
let x = x + 2 let x = x + 2
@ -59,7 +59,7 @@
--- ---
// Import bindings. // Import bindings.
{ #{
let b = "module.typ" let b = "module.typ"
let f() = { let f() = {
import b: b import b: b
@ -70,7 +70,7 @@
--- ---
// For loop bindings. // For loop bindings.
{ #{
let v = (1, 2, 3) let v = (1, 2, 3)
let f() = { let f() = {
let s = 0 let s = 0
@ -82,7 +82,7 @@
--- ---
// Let + closure bindings. // Let + closure bindings.
{ #{
let g = "hi" let g = "hi"
let f() = { let f() = {
let g() = "bye" let g() = "bye"
@ -93,7 +93,7 @@
--- ---
// Parameter bindings. // Parameter bindings.
{ #{
let x = 5 let x = 5
let g() = { let g() = {
let f(x, y: x) = x + y let f(x, y: x) = x + y
@ -105,7 +105,7 @@
--- ---
// Don't leak environment. // Don't leak environment.
{ #{
// Error: 16-17 unknown variable // Error: 16-17 unknown variable
let func() = x let func() = x
let x = "hi" let x = "hi"
@ -114,7 +114,7 @@
--- ---
// Too few arguments. // Too few arguments.
{ #{
let types(x, y) = "[" + type(x) + ", " + type(y) + "]" let types(x, y) = "[" + type(x) + ", " + type(y) + "]"
test(types(14%, 12pt), "[ratio, length]") test(types(14%, 12pt), "[ratio, length]")
@ -124,7 +124,7 @@
--- ---
// Too many arguments. // Too many arguments.
{ #{
let f(x) = x + 1 let f(x) = x + 1
// Error: 8-13 unexpected argument // Error: 8-13 unexpected argument
@ -133,7 +133,7 @@
--- ---
// Named arguments. // Named arguments.
{ #{
let greet(name, birthday: false) = { let greet(name, birthday: false) = {
if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!" if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!"
} }
@ -156,8 +156,8 @@
#let f(a, b, a: none, b: none, c, b) = none #let f(a, b, a: none, b: none, c, b) = none
--- ---
// Error: 6-16 expected identifier, named pair or argument sink, found keyed pair // Error: 7-17 expected identifier, named pair or argument sink, found keyed pair
{(a, "named": b) => none} #{(a, "named": b) => none}
--- ---
// Error: 10-15 expected identifier, found string // Error: 10-15 expected identifier, found string

View File

@ -18,7 +18,7 @@
--- ---
// The inner rectangle should also be yellow here. // The inner rectangle should also be yellow here.
// (and therefore invisible) // (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. // The inner rectangle should not be yellow here.

View File

@ -5,7 +5,7 @@
// Ref: true // Ref: true
// Empty // Empty
{(:)} #{(:)}
// Two pairs and string key. // Two pairs and string key.
#let dict = (normal: 1, "spacy key": 2) #let dict = (normal: 1, "spacy key": 2)
@ -16,7 +16,7 @@
--- ---
// Test lvalue and rvalue access. // Test lvalue and rvalue access.
{ #{
let dict = (a: 1, "b b": 1) let dict = (a: 1, "b b": 1)
dict.at("b b") += 1 dict.at("b b") += 1
dict.state = (ok: true, err: false) dict.state = (ok: true, err: false)
@ -29,7 +29,7 @@
--- ---
// Test rvalue missing key. // Test rvalue missing key.
{ #{
let dict = (a: 1, b: 2) let dict = (a: 1, b: 2)
// Error: 11-23 dictionary does not contain key "c" // Error: 11-23 dictionary does not contain key "c"
let x = dict.at("c") let x = dict.at("c")
@ -37,7 +37,7 @@
--- ---
// Missing lvalue is not automatically none-initialized. // Missing lvalue is not automatically none-initialized.
{ #{
let dict = (:) let dict = (:)
// Error: 3-9 dictionary does not contain key "b" // Error: 3-9 dictionary does not contain key "b"
dict.b += 1 dict.b += 1
@ -51,39 +51,39 @@
#test(dict.values(), (3, 1, 2)) #test(dict.values(), (3, 1, 2))
#test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2") #test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2")
{ dict.remove("c") } #{ dict.remove("c") }
#test("c" in dict, false) #test("c" in dict, false)
#test(dict, (a: 3, b: 1)) #test(dict, (a: 3, b: 1))
--- ---
// Error: 24-29 duplicate key // Error: 25-30 duplicate key
{(first: 1, second: 2, first: 3)} #{(first: 1, second: 2, first: 3)}
--- ---
// Error: 17-20 duplicate key // Error: 18-21 duplicate key
{(a: 1, "b": 2, "a": 3)} #{(a: 1, "b": 2, "a": 3)}
--- ---
// Simple expression after already being identified as a dictionary. // Simple expression after already being identified as a dictionary.
// Error: 9-10 expected named or keyed pair, found identifier // Error: 10-11 expected named or keyed pair, found identifier
{(a: 1, b)} #{(a: 1, b)}
// Identified as dictionary due to initial colon. // Identified as dictionary due to initial colon.
// Error: 4-5 expected named or keyed pair, found integer // Error: 5-6 expected named or keyed pair, found integer
// Error: 5 expected comma // Error: 6 expected comma
// Error: 12-16 expected identifier or string, found boolean // Error: 13-17 expected identifier or string, found boolean
// Error: 17 expected expression // Error: 18 expected expression
{(:1 b:"", true:)} #{(:1 b:"", true:)}
// Error: 3-8 expected identifier or string, found binary expression // Error: 4-9 expected identifier or string, found binary expression
{(a + b: "hey")} #{(a + b: "hey")}
--- ---
// Error: 3-15 cannot mutate a temporary value // Error: 4-16 cannot mutate a temporary value
{ (key: "val").other = "some" } #{ (key: "val").other = "some" }
--- ---
{ #{
let object = none let object = none
// Error: 3-9 expected dictionary, found none // Error: 3-9 expected dictionary, found none
object.property = "value" object.property = "value"

View File

@ -5,7 +5,7 @@
// Test field on dictionary. // Test field on dictionary.
#let dict = (nothing: "ness", hello: "world") #let dict = (nothing: "ness", hello: "world")
#test(dict.nothing, "ness") #test(dict.nothing, "ness")
{ #{
let world = dict let world = dict
.hello .hello
@ -23,12 +23,12 @@
- C - C
--- ---
// Error: 6-13 dictionary does not contain key "invalid" // Error: 7-14 dictionary does not contain key "invalid"
{(:).invalid} #{(:).invalid}
--- ---
// Error: 2-7 expected dictionary or content, found boolean // Error: 9-11 cannot access fields on type boolean
{false.ok} #{false.ok}
--- ---
// Error: 29-32 unknown field `fun` // Error: 29-32 unknown field `fun`
@ -36,6 +36,6 @@
= A = A
--- ---
// Error: 8 expected identifier // Error: 9 expected identifier
// Error: 8 expected semicolon or line break // Error: 9 expected semicolon or line break
{false.true} #{false.true}

View File

@ -10,12 +10,12 @@
// Dictionary is not traversed in insertion order. // Dictionary is not traversed in insertion order.
// Should output `Age: 2. Name: Typst.`. // Should output `Age: 2. Name: Typst.`.
#for k, v in (Name: "Typst", Age: 2) [ #for k, v in (Name: "Typst", Age: 2) [
{k}: {v}. #k: #v.
] ]
// Block body. // Block body.
// Should output `[1st, 2nd, 3rd, 4th]`. // Should output `[1st, 2nd, 3rd, 4th]`.
{ #{
"[" "["
for v in (1, 2, 3, 4) { for v in (1, 2, 3, 4) {
if v > 1 [, ] if v > 1 [, ]
@ -97,8 +97,8 @@
// Error: 5 expected identifier // Error: 5 expected identifier
#for// #for//
// Error: 5 expected identifier // Error: 6 expected identifier
{for} #{for}
// Error: 7 expected keyword `in` // Error: 7 expected keyword `in`
#for v #for v

View File

@ -40,7 +40,7 @@
} }
// Content block can be argument or body depending on whitespace. // Content block can be argument or body depending on whitespace.
{ #{
if "content" == type[b] [Fi] else [Nope] if "content" == type[b] [Fi] else [Nope]
if "content" == type [Nope] else [ve.] if "content" == type [Nope] else [ve.]
} }
@ -76,7 +76,7 @@
// Value of if expressions. // Value of if expressions.
// Ref: false // Ref: false
{ #{
let x = 1 let x = 1
let y = 2 let y = 2
let z let z
@ -109,13 +109,13 @@
// Error: 4 expected expression // Error: 4 expected expression
#if #if
// Error: 4 expected expression // Error: 5 expected expression
{if} #{if}
// Error: 6 expected block // Error: 6 expected block
#if x #if x
// Error: 1-6 unexpected keyword `else` // Error: 2 expected expression
#else {} #else {}
// Should output `x`. // Should output `x`.

View File

@ -49,8 +49,8 @@
#test((module,).at(0).item(1, 2), 3) #test((module,).at(0).item(1, 2), 3)
// Doesn't work because of mutating name. // Doesn't work because of mutating name.
// Error: 2-11 cannot mutate a temporary value // Error: 3-12 cannot mutate a temporary value
{(module,).at(0).push()} #{(module,).at(0).push()}
--- ---
// Who needs whitespace anyways? // Who needs whitespace anyways?

View File

@ -15,7 +15,7 @@
#chap2 #chap2
--- ---
{ #{
// Error: 19-38 file not found (searched at typ/compiler/modules/chap3.typ) // Error: 19-38 file not found (searched at typ/compiler/modules/chap3.typ)
let x = include "modules/chap3.typ" let x = include "modules/chap3.typ"
} }
@ -24,7 +24,7 @@
#include "modules/chap1.typ" #include "modules/chap1.typ"
// The variables of the file should not appear in this scope. // The variables of the file should not appear in this scope.
// Error: 1-6 unknown variable // Error: 2-6 unknown variable
#name #name
--- ---

View File

@ -26,7 +26,7 @@ The end.
it it
} }
This is a thing [that <last>] happened. This is a thing #[that <last>] happened.
--- ---
// Test abusing dynamic labels for styling. // Test abusing dynamic labels for styling.
@ -50,7 +50,7 @@ _Visible_
--- ---
// Test that label only works within one content block. // Test that label only works within one content block.
#show <strike>: strike #show <strike>: strike
*This is* [<strike>] *protected.* *This is* #[<strike>] *protected.*
*This is not.* <strike> *This is not.* <strike>
--- ---

View File

@ -36,8 +36,8 @@ Three
// Error: 5 expected identifier // Error: 5 expected identifier
#let #let
// Error: 5 expected identifier // Error: 6 expected identifier
{let} #{let}
// Error: 5 expected identifier // Error: 5 expected identifier
// Error: 5 expected semicolon or line break // Error: 5 expected semicolon or line break

View File

@ -7,7 +7,7 @@
--- ---
// Test mutating indexed value. // Test mutating indexed value.
{ #{
let matrix = (((1,), (2,)), ((3,), (4,))) let matrix = (((1,), (2,)), ((3,), (4,)))
matrix.at(1).at(0).push(5) matrix.at(1).at(0).push(5)
test(matrix, (((1,), (2,)), ((3, 5), (4,)))) test(matrix, (((1,), (2,)), ((3, 5), (4,))))
@ -15,7 +15,7 @@
--- ---
// Test multiline chain in code block. // Test multiline chain in code block.
{ #{
let rewritten = "Hello. This is a sentence. And one more." let rewritten = "Hello. This is a sentence. And one more."
.split(".") .split(".")
.map(s => s.trim()) .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 = () #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) #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) #let numbers = (1, 2, 3)
{ numbers.sorted() = 1 } #{ numbers.sorted() = 1 }
--- ---
// Error: 3-6 cannot mutate a constant // Error: 4-7 cannot mutate a constant
{ box.push(1) } #{ box.push(1) }

View File

@ -2,8 +2,8 @@
// Ref: false // Ref: false
--- ---
// Error: 3 expected expression // Error: 4 expected expression
{-} #{-}
--- ---
// Error: 10 expected expression // Error: 10 expected expression
@ -14,115 +14,115 @@
#test({2*}, 2) #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 // Error: 3-9 cannot apply 'not' to array
{not ()} #{not ()}
--- ---
// Error: 2-18 cannot apply '<=' to relative length and ratio // Error: 3-19 cannot apply '<=' to relative length and ratio
{30% + 1pt <= 40%} #{30% + 1pt <= 40%}
--- ---
// Error: 2-13 cannot apply '<=' to length and length // Error: 3-14 cannot apply '<=' to length and length
{1em <= 10pt} #{1em <= 10pt}
--- ---
// Error: 2-11 cannot divide by zero // Error: 3-12 cannot divide by zero
{1.2 / 0.0} #{1.2 / 0.0}
--- ---
// Error: 2-7 cannot divide by zero // Error: 3-8 cannot divide by zero
{1 / 0} #{1 / 0}
--- ---
// Error: 2-14 cannot divide by zero // Error: 3-15 cannot divide by zero
{15deg / 0deg} #{15deg / 0deg}
--- ---
// Special messages for +, -, * and /. // Special messages for +, -, * and /.
// Error: 03-10 cannot add integer and string // Error: 4-11 cannot add integer and string
{(1 + "2", 40% - 1)} #{(1 + "2", 40% - 1)}
--- ---
// Error: 14-22 cannot add integer and string // Error: 15-23 cannot add integer and string
{ let x = 1; x += "2" } #{ let x = 1; x += "2" }
--- ---
// Error: 3-12 cannot divide ratio by length // Error: 4-13 cannot divide ratio by length
{ 10% / 5pt } #{ 10% / 5pt }
--- ---
// Error: 3-12 cannot divide these two lengths // Error: 4-13 cannot divide these two lengths
{ 1em / 5pt } #{ 1em / 5pt }
--- ---
// Error: 3-19 cannot divide relative length by ratio // Error: 4-20 cannot divide relative length by ratio
{ (10% + 1pt) / 5% } #{ (10% + 1pt) / 5% }
--- ---
// Error: 3-28 cannot divide these two relative lengths // Error: 4-29 cannot divide these two relative lengths
{ (10% + 1pt) / (20% + 1pt) } #{ (10% + 1pt) / (20% + 1pt) }
--- ---
// Error: 12-19 cannot subtract integer from ratio // Error: 13-20 cannot subtract integer from ratio
{(1234567, 40% - 1)} #{(1234567, 40% - 1)}
--- ---
// Error: 2-10 cannot multiply integer with boolean // Error: 3-11 cannot multiply integer with boolean
{2 * true} #{2 * true}
--- ---
// Error: 2-10 cannot divide integer by length // Error: 3-11 cannot divide integer by length
{3 / 12pt} #{3 / 12pt}
--- ---
// Error: 2-9 cannot repeat this string -1 times // Error: 3-10 cannot repeat this string -1 times
{-1 * ""} #{-1 * ""}
--- ---
{ #{
let x = 2 let x = 2
for _ in range(61) { for _ in range(61) {
(x) *= 2 (x) *= 2
} }
// Error: 4-18 cannot repeat this string 4611686018427387904 times // Error: 3-17 cannot repeat this string 4611686018427387904 times
{x * "abcdefgh"} x * "abcdefgh"
} }
--- ---
// Error: 4-5 unknown variable // Error: 5-6 unknown variable
{ (x) = "" } #{ (x) = "" }
--- ---
// Error: 3-8 cannot mutate a temporary value // Error: 4-9 cannot mutate a temporary value
{ 1 + 2 += 3 } #{ 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" #let x = "Hey"
{not x = "a"} #{not x = "a"}
--- ---
// Error: 7-8 unknown variable // Error: 8-9 unknown variable
{ 1 + x += 3 } #{ 1 + x += 3 }
--- ---
// Error: 3-4 unknown variable // Error: 4-5 unknown variable
{ z = 1 } #{ z = 1 }
--- ---
// Error: 3-7 cannot mutate a constant // Error: 4-8 cannot mutate a constant
{ rect = "hi" } #{ rect = "hi" }
--- ---
// Works if we define rect beforehand // Works if we define rect beforehand
// (since then it doesn't resolve to the standard library version anymore). // (since then it doesn't resolve to the standard library version anymore).
#let rect = "" #let rect = ""
{ rect = "hi" } #{ rect = "hi" }

View File

@ -14,9 +14,14 @@
--- ---
// Assignment binds stronger than boolean operations. // 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 #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. // Parentheses override precedence.
@ -25,8 +30,3 @@
// Error: 14 expected closing paren // Error: 14 expected closing paren
#test({(1 + 1}, 2) #test({(1 + 1}, 2)
---
// Precedence doesn't matter for chained unary operators.
// Error: 2-11 cannot apply '-' to boolean
{-not true}

View File

@ -4,7 +4,7 @@
--- ---
// Test adding content. // Test adding content.
// Ref: true // Ref: true
{[*Hello* ] + [world!]} #{[*Hello* ] + [world!]}
--- ---
// Test math operators. // Test math operators.
@ -176,17 +176,17 @@
// Test assignment operators. // Test assignment operators.
#let x = 0 #let x = 0
{ x = 10 } #test(x, 10) #{ x = 10 } #test(x, 10)
{ x -= 5 } #test(x, 5) #{ x -= 5 } #test(x, 5)
{ x += 1 } #test(x, 6) #{ x += 1 } #test(x, 6)
{ x *= x } #test(x, 36) #{ x *= x } #test(x, 36)
{ x /= 2.0 } #test(x, 18.0) #{ x /= 2.0 } #test(x, 18.0)
{ x = "some" } #test(x, "some") #{ x = "some" } #test(x, "some")
{ x += "thing" } #test(x, "something") #{ x += "thing" } #test(x, "something")
--- ---
// Error: 3-6 cannot mutate a constant // Error: 4-7 cannot mutate a constant
{ box = 1 } #{ box = 1 }
--- ---
// Test `in` operator. // Test `in` operator.
@ -204,8 +204,8 @@
/* fun comment? */ in "abc", false) /* fun comment? */ in "abc", false)
--- ---
// Error: 9 expected keyword `in` // Error: 10 expected keyword `in`
{"a" not} #{"a" not}
--- ---
// Test `with` method. // Test `with` method.

View File

@ -2,33 +2,33 @@
--- ---
// Literal values. // Literal values.
{auto} \ #auto \
{none} (empty) \ #none (empty) \
{true} \ #true \
{false} #false
--- ---
// Numerical values. // Numerical values.
{1} \ #1 \
{1.0e-4} \ #1.0e-4 \
{3.15} \ #3.15 \
{1e-10} \ #1e-10 \
{50.368%} \ #50.368% \
{0.0000012345pt} \ #0.0000012345pt \
{4.5cm} \ #4.5cm \
{12e1pt} \ #12e1pt \
{2.5rad} \ #2.5rad \
{45deg} \ #45deg \
{1.7em} \ #1.7em \
{1cm + 0em} \ #{1cm + 0em} \
{2em + 10pt} \ #{2em + 10pt} \
{2.3fr} #2.3fr
--- ---
// Colors and strokes. // Colors and strokes.
#set text(0.8em) #set text(0.8em)
#rgb("f7a205") \ #rgb("f7a205") \
{2pt + rgb("f7a205")} #{2pt + rgb("f7a205")}
--- ---
// Strings and escaping. // Strings and escaping.
@ -43,6 +43,6 @@
// Functions are invisible. // Functions are invisible.
Nothing Nothing
#let f(x) = x #let f(x) = x
{f} #f
{rect} #rect
{() => none} #{() => none}

View File

@ -3,18 +3,18 @@
--- ---
// Test that text is affected by instantiation-site bold. // Test that text is affected by instantiation-site bold.
#let x = [World] #let x = [World]
Hello *{x}* Hello *#x*
--- ---
// Test that lists are affected by correct indents. // Test that lists are affected by correct indents.
#let fruit = [ #let fruit = [
- Apple - Apple
- Orange - Orange
#list(body-indent: 20pt, [Pear]) #list(body-indent: 20pt)[Pear]
] ]
- Fruit - Fruit
[#set list(indent: 10pt) #[#set list(indent: 10pt)
#fruit] #fruit]
- No more fruit - No more fruit
@ -28,7 +28,7 @@ Hello *{x}*
--- ---
// Test that scoping works as expected. // Test that scoping works as expected.
{ #{
if true { if true {
set text(blue) set text(blue)
[Blue ] [Blue ]
@ -62,5 +62,5 @@ Hello *{x}*
#set text(red) if 1 + 2 #set text(red) if 1 + 2
--- ---
// Error: 11-25 set is only allowed directly in code and content blocks // Error: 12-26 set is only allowed directly in code and content blocks
{ let x = set text(blue) } #{ let x = set text(blue) }

View File

@ -17,4 +17,4 @@ a~b
--- ---
#set text("Roboto") #set text("Roboto")
A... vs {"A..."} A... vs #"A..."

View File

@ -16,7 +16,7 @@ in booklovers and the great fulfiller of human need.
--- ---
// Test bare show in content block. // Test bare show in content block.
A [_B #show c => [*#c*]; C_] D A #[_B #show c => [*#c*]; C_] D
--- ---
// Test style precedence. // Test style precedence.
@ -29,5 +29,5 @@ Forest
Ignored Ignored
--- ---
// Error: 4-18 show is only allowed directly in code and content blocks // Error: 5-19 show is only allowed directly in code and content blocks
{ (show body => 2) * body } #{ (show body => 2) * body }

View File

@ -14,7 +14,7 @@
// Test full reset. // Test full reset.
#show heading: [B] #show heading: [B]
#show heading: set text(size: 10pt, weight: 400) #show heading: set text(size: 10pt, weight: 400)
A [= Heading] C A #[= Heading] C
--- ---
// Test full removal. // Test full removal.
@ -58,7 +58,7 @@ Another text.
--- ---
// Test that scoping works as expected. // Test that scoping works as expected.
{ #{
let world = [ World ] let world = [ World ]
show "W": strong show "W": strong
world world
@ -100,5 +100,5 @@ Another text.
#show red: [] #show red: []
--- ---
// Error: 7-25 show is only allowed directly in code and content blocks // Error: 8-26 show is only allowed directly in code and content blocks
{ 1 + show heading: none } #{ 1 + show heading: none }

View File

@ -3,7 +3,7 @@
--- ---
// Test standard argument overriding. // Test standard argument overriding.
{ #{
let f(style: "normal", weight: "regular") = { let f(style: "normal", weight: "regular") = {
"(style: " + style + ", weight: " + weight + ")" "(style: " + style + ", weight: " + weight + ")"
} }
@ -16,7 +16,7 @@
--- ---
// Test multiple calls. // Test multiple calls.
{ #{
let f(b, c: "!") = b + c let f(b, c: "!") = b + c
let g(a, ..sink) = a + f(..sink) let g(a, ..sink) = a + f(..sink)
test(g("a", "b", c: "c"), "abc") test(g("a", "b", c: "c"), "abc")
@ -24,7 +24,7 @@
--- ---
// Test doing things with arguments. // Test doing things with arguments.
{ #{
let save(..args) = { let save(..args) = {
test(type(args), "arguments") test(type(args), "arguments")
test(repr(args), "(1, 2, three: true)") test(repr(args), "(1, 2, three: true)")
@ -35,14 +35,14 @@
--- ---
// Test spreading array and dictionary. // Test spreading array and dictionary.
{ #{
let more = (3, -3, 6, 10) let more = (3, -3, 6, 10)
test(min(1, 2, ..more), -3) test(min(1, 2, ..more), -3)
test(max(..more, 9), 10) test(max(..more, 9), 10)
test(max(..more, 11), 11) test(max(..more, 11), 11)
} }
{ #{
let more = (c: 3, d: 4) let more = (c: 3, d: 4)
let tostr(..args) = repr(args) let tostr(..args) = repr(args)
test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)") 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. // Test spreading into array and dictionary.
{ #{
let l = (1, 2, 3) let l = (1, 2, 3)
let r = (5, 6, 7) let r = (5, 6, 7)
test((..l, 4, ..r), range(1, 8)) test((..l, 4, ..r), range(1, 8))
test((..none), ()) test((..none), ())
} }
{ #{
let x = (a: 1) let x = (a: 1)
let y = (b: 2) let y = (b: 2)
let z = (a: 3) let z = (a: 3)
@ -85,9 +85,9 @@
} }
--- ---
// Error: 11-17 cannot spread dictionary into array // Error: 12-18 cannot spread dictionary into array
{(1, 2, ..(a: 1))} #{(1, 2, ..(a: 1))}
--- ---
// Error: 5-11 cannot spread array into dictionary // Error: 6-12 cannot spread array into dictionary
{(..(1, 2), a: 1)} #{(..(1, 2), a: 1)}

View File

@ -13,12 +13,12 @@
#test("🏳🌈A🏳".last(), "🏳️‍⚧️") #test("🏳🌈A🏳".last(), "🏳️‍⚧️")
--- ---
// Error: 3-13 string is empty // Error: 4-14 string is empty
{ "".first() } #{ "".first() }
--- ---
// Error: 3-12 string is empty // Error: 4-13 string is empty
{ "".last() } #{ "".last() }
--- ---
// Test the `at` method. // Test the `at` method.
@ -27,8 +27,8 @@
#test("Hey: 🏳️‍🌈 there!".at(5), "🏳️‍🌈") #test("Hey: 🏳️‍🌈 there!".at(5), "🏳️‍🌈")
--- ---
// Error: 3-16 string index out of bounds (index: 5, len: 5) // Error: 4-17 string index out of bounds (index: 5, len: 5)
{ "Hello".at(5) } #{ "Hello".at(5) }
--- ---
// Test the `slice` method. // Test the `slice` method.
@ -135,8 +135,8 @@
#test("hello world".trim(regex(".")), "") #test("hello world".trim(regex(".")), "")
--- ---
// Error: 17-21 expected either `start` or `end` // Error: 18-22 expected either `start` or `end`
{"abc".trim(at: left)} #{"abc".trim(at: left)}
--- ---
// Test the `split` method. // Test the `split` method.

View File

@ -4,7 +4,7 @@
// Should output `2 4 6 8 10`. // Should output `2 4 6 8 10`.
#let i = 0 #let i = 0
#while i < 10 [ #while i < 10 [
{ i += 2 } #{ i += 2 }
#i #i
] ]
@ -26,7 +26,7 @@
#test(while false {}, none) #test(while false {}, none)
#let i = 0 #let i = 0
#test(type(while i < 1 [{ i += 1 }]), "content") #test(type(while i < 1 [#{ i += 1 }]), "content")
--- ---
// Condition must be boolean. // Condition must be boolean.
@ -38,7 +38,7 @@
#while 2 < "hello".len() {} #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 #let i = 1
#while i > 0 { i += 1 } #while i > 0 { i += 1 }
@ -46,8 +46,8 @@
// Error: 7 expected expression // Error: 7 expected expression
#while #while
// Error: 7 expected expression // Error: 8 expected expression
{while} #{while}
// Error: 9 expected block // Error: 9 expected block
#while x #while x

View File

@ -43,8 +43,8 @@ Blue #move(dy: -0.15em)[🌊]
``` ```
--- ---
// Error: 7-19 cannot continue outside of loop // Error: 7-18 cannot continue outside of loop
#eval("{continue}") #eval("#continue")
--- ---
// Error: 7-33 cannot access file system from here // Error: 7-33 cannot access file system from here
@ -72,5 +72,5 @@ _No relative giraffe!_
``` ```
--- ---
// Error: 7-16 expected comma // Error: 7-15 expected comma
#eval("{(1 2)}") #eval("#(1 2)")

View File

@ -8,7 +8,7 @@
// Test custom paragraphs with user code. // Test custom paragraphs with user code.
#set text(8pt) #set text(8pt)
{ #{
let sentences = lorem(59) let sentences = lorem(59)
.split(".") .split(".")
.filter(s => s != "") .filter(s => s != "")

View File

@ -2,7 +2,7 @@
--- ---
// Set all margins at once. // Set all margins at once.
[ #[
#set page(height: 20pt, margin: 5pt) #set page(height: 20pt, margin: 5pt)
#place(top + left)[TL] #place(top + left)[TL]
#place(bottom + right)[BR] #place(bottom + right)[BR]
@ -11,10 +11,10 @@
--- ---
// Set individual margins. // Set individual margins.
#set page(height: 40pt) #set page(height: 40pt)
[#set page(margin: (left: 0pt)); #align(left)[Left]] #[#set page(margin: (left: 0pt)); #align(left)[Left]]
[#set page(margin: (right: 0pt)); #align(right)[Right]] #[#set page(margin: (right: 0pt)); #align(right)[Right]]
[#set page(margin: (top: 0pt)); #align(top)[Top]] #[#set page(margin: (top: 0pt)); #align(top)[Top]]
[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]] #[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]]
// Ensure that specific margins override general margins. // Ensure that specific margins override general margins.
[#set page(margin: (rest: 0pt, left: 20pt)); Overriden] #[#set page(margin: (rest: 0pt, left: 20pt)); Overriden]

View File

@ -14,11 +14,11 @@
// Set width and height. // Set width and height.
// Should result in one high and one wide page. // Should result in one high and one wide page.
#set page(width: 80pt, height: 80pt) #set page(width: 80pt, height: 80pt)
[#set page(width: 40pt);High] #[#set page(width: 40pt);High]
[#set page(height: 40pt);Wide] #[#set page(height: 40pt);Wide]
// Flipped predefined paper. // Flipped predefined paper.
[#set page(paper: "a11", flipped: true);Flipped A11] #[#set page(paper: "a11", flipped: true);Flipped A11]
--- ---
// Test page fill. // Test page fill.

View File

@ -26,12 +26,12 @@ Second
// Test a combination of pagebreaks, styled pages and pages with bodies. // Test a combination of pagebreaks, styled pages and pages with bodies.
// Should result in three five pages, with the fourth one being forest-colored. // Should result in three five pages, with the fourth one being forest-colored.
#set page(width: 80pt, height: 30pt) #set page(width: 80pt, height: 30pt)
[#set page(width: 60pt); First] #[#set page(width: 60pt); First]
#pagebreak() #pagebreak()
#pagebreak() #pagebreak()
Third Third
#page(height: 20pt, fill: forest)[] #page(height: 20pt, fill: forest)[]
Fif[#set page();th] Fif#[#set page();th]
--- ---
// Test hard and weak pagebreak followed by page with body. // Test hard and weak pagebreak followed by page with body.

View File

@ -47,7 +47,7 @@ Lריווח #h(1cm) R
--- ---
// Test whether L1 whitespace resetting destroys stuff. // Test whether L1 whitespace resetting destroys stuff.
الغالب #h(70pt) ن{" "}ة الغالب #h(70pt) ن#" "ة
--- ---
// Test setting a vertical direction. // Test setting a vertical direction.

View File

@ -30,4 +30,4 @@ D E F #linebreak(justify: true)
// Test that there are no hick-ups with justification enabled and // Test that there are no hick-ups with justification enabled and
// basically empty paragraph. // basically empty paragraph.
#set par(justify: true) #set par(justify: true)
{""} #""

View File

@ -12,7 +12,7 @@
) )
#for section in sections [ #for section in sections [
{section.at(0)} #repeat[.] {section.at(1)} \ #section.at(0) #repeat[.] #section.at(1) \
] ]
--- ---

View File

@ -3,13 +3,13 @@
--- ---
// Test creating the TeX and XeTeX logos. // Test creating the TeX and XeTeX logos.
#let size = 11pt #let size = 11pt
#let tex = [{ #let tex = {
[T] [T]
h(-0.14 * size) h(-0.14 * size)
move(dy: 0.22 * size)[E] move(dy: 0.22 * size)[E]
h(-0.12 * size) h(-0.12 * size)
[X] [X]
}] }
#let xetex = { #let xetex = {
[X] [X]

View File

@ -3,10 +3,10 @@
#let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v))) #let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v)))
#let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both]) #let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both])
#for k in kinds { #for kk in kinds {
cells.push(raw(repr(k).trim("<function ").trim(">"))) cells.push(raw(repr(kk).trim("<function ").trim(">")))
for m in modifiers { for mm in modifiers {
cells.push($ #m(#k(part)) $) cells.push($ mm(kk(part)) $)
} }
} }

View File

@ -8,23 +8,23 @@
--- ---
Hello Hello
// Error: 1-30 must appear before any content // Error: 2-30 must appear before any content
#set document(title: "Hello") #set document(title: "Hello")
--- ---
#box[ #box[
// Error: 3-32 not allowed here // Error: 4-32 not allowed here
#set document(title: "Hello") #set document(title: "Hello")
] ]
--- ---
#box[ #box[
// Error: 3-18 not allowed here // Error: 4-18 not allowed here
#set page("a4") #set page("a4")
] ]
--- ---
#box[ #box[
// Error: 3-15 not allowed here // Error: 4-15 not allowed here
#pagebreak() #pagebreak()
] ]

View File

@ -11,7 +11,7 @@
= Analyse = Analyse
#lorem(10) #lorem(10)
[ #[
#set heading(outlined: false) #set heading(outlined: false)
== Methodik == Methodik
#lorem(6) #lorem(6)

View File

@ -3,10 +3,10 @@
--- ---
#set text(size: 5pt) #set text(size: 5pt)
A // 5pt A // 5pt
[ #[
#set text(size: 2em) #set text(size: 2em)
B // 10pt B // 10pt
[ #[
#set text(size: 1.5em + 1pt) #set text(size: 1.5em + 1pt)
C // 16pt C // 16pt
#text(size: 2em)[D] // 32pt #text(size: 2em)[D] // 32pt

View File

@ -8,7 +8,7 @@ _Emphasized and *strong* words!_
hello_world Nutzer*innen hello_world Nutzer*innen
// Can contain paragraph in nested content block. // Can contain paragraph in nested content block.
_Still [ _Still #[
] emphasized._ ] emphasized._
@ -24,7 +24,7 @@ Normal
*Bold* *Bold*
#set strong(delta: 150) #set strong(delta: 150)
*Medium* and *[*Bold*]* *Medium* and *#[*Bold*]*
--- ---
// Error: 13 expected underscore // Error: 13 expected underscore
@ -38,6 +38,6 @@ _Hello
World World
--- ---
// Error: 25 expected star // Error: 26 expected star
// Error: 25 expected underscore // Error: 26 expected underscore
[_Cannot *be interleaved] #[_Cannot *be interleaved]

View File

@ -25,7 +25,7 @@
Emoji: 🐪, 🌋, 🏞 Emoji: 🐪, 🌋, 🏞
// Colors. // Colors.
[ #[
#set text(fill: eastern) #set text(fill: eastern)
This is #text(rgb("FA644B"))[way more] colorful. This is #text(rgb("FA644B"))[way more] colorful.
] ]

View File

@ -50,4 +50,4 @@ He's told some books contain questionable "example text".
// Test changing properties within text. // Test changing properties within text.
"She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me. "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."]

View File

@ -7,7 +7,7 @@ C #let x = 2;D #test(x, 2) \
E#if true [F]G \ E#if true [F]G \
H #if true{"I"} J \ H #if true{"I"} J \
K #if true [L] else []M \ 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 \ #let c = true; Q #while c { c = false; "R" } S \
T#for _ in (none,) {"U"}V T#for _ in (none,) {"U"}V
@ -19,11 +19,11 @@ A /**/B/**/ C
--- ---
// Test that a run consisting only of whitespace isn't trimmed. // 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. // Test font change after space.
Left [#set text("IBM Plex Serif");Right]. Left #text("IBM Plex Serif")[Right].
--- ---
// Test that linebreak consumed surrounding spaces. // 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. // 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. // Test that trailing space does not force a line break.

View File

@ -6,7 +6,7 @@
--- ---
// Test the `end` argument. // Test the `end` argument.
{ #{
line(end: (10pt, 0pt)) line(end: (10pt, 0pt))
line(start: (0pt, 10pt), end: (0pt, 0pt)) line(start: (0pt, 10pt), end: (0pt, 0pt))
line(end: (15pt, 15pt)) line(end: (15pt, 15pt))

View File

@ -16,7 +16,7 @@
variant(fill: forest, stroke: black + 2pt), variant(fill: forest, stroke: black + 2pt),
variant(fill: forest, stroke: conifer + 2pt), variant(fill: forest, stroke: conifer + 2pt),
) { ) {
(align(horizon)[{i + 1}.], item, []) (align(horizon)[#{i + 1}.], item, [])
} }
#grid( #grid(

View File

@ -24,9 +24,9 @@
#rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft] #rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft]
// These are inline with text. // These are inline with text.
\{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67")) {#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("edd466"))
#rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))\} #rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))}
// Rounded corners. // Rounded corners.
#rect(width: 2cm, radius: 60%) #rect(width: 2cm, radius: 60%)
@ -39,10 +39,8 @@
)) ))
// Different strokes. // Different strokes.
[
#set rect(stroke: (right: red)) #set rect(stroke: (right: red))
#rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt)) #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" // Error: 15-38 unexpected key "cake", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest"

View File

@ -24,21 +24,7 @@
}, },
"common": { "common": {
"patterns": [ "patterns": [
{ "include": "#comments" }, { "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" }]
}
] ]
}, },
"markup": { "markup": {
@ -213,14 +199,34 @@
}, },
{ {
"name": "entity.other.interpolated.typst", "name": "entity.other.interpolated.typst",
"match": "(#)[[:alpha:]_][[:alnum:]_-]*", "match": "(#)[[:alpha:]_][.[:alnum:]_-]*",
"captures": { "1": { "name": "punctuation.definition.variable.typst" } } "captures": { "1": { "name": "punctuation.definition.variable.typst" } }
},
{
"name": "meta.block.content.typst",
"begin": "#",
"end": "\\s",
"patterns": [{ "include": "#code" }]
} }
] ]
}, },
"code": { "code": {
"patterns": [ "patterns": [
{ "include": "#common" }, { "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", "name": "comment.line.double-slash.typst",
"begin": "//", "begin": "//",