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| {
layout::ListItem::Term(basics::TermItem { term, description }).pack()
},
math_formula: |body, block| math::FormulaNode { body, block }.pack(),
formula: |body, block| math::FormulaNode { body, block }.pack(),
math_atom: |atom| math::AtomNode(atom).pack(),
math_delimited: |body| math::LrNode(body).pack(),
math_align_point: || math::AlignPointNode.pack(),
math_delimited: |open, body, close| math::LrNode(open + body + close).pack(),
math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(),
math_frac: |num, denom| math::FracNode { num, denom }.pack(),
math_align_point: || math::AlignPointNode.pack(),
}
}

View File

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

View File

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

View File

@ -7,7 +7,7 @@ pub enum Category {
Comment,
/// Punctuation in code.
Punctuation,
/// An escape sequence, shorthand or symbol notation.
/// An escape sequence or shorthand.
Escape,
/// Strong markup.
Strong,
@ -97,7 +97,6 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Parbreak => None,
SyntaxKind::Escape => Some(Category::Escape),
SyntaxKind::Shorthand => Some(Category::Escape),
SyntaxKind::Symbol => Some(Category::Escape),
SyntaxKind::SmartQuote => None,
SyntaxKind::Strong => Some(Category::Strong),
SyntaxKind::Emph => Some(Category::Emph),
@ -113,12 +112,22 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::EnumMarker => Some(Category::ListMarker),
SyntaxKind::TermItem => None,
SyntaxKind::TermMarker => Some(Category::ListMarker),
SyntaxKind::Formula => None,
SyntaxKind::Math => None,
SyntaxKind::Atom => None,
SyntaxKind::Delimited => None,
SyntaxKind::Script => None,
SyntaxKind::Frac => None,
SyntaxKind::AlignPoint => Some(Category::MathOperator),
SyntaxKind::MathAtom => None,
SyntaxKind::MathIdent => highlight_ident(node),
SyntaxKind::MathDelimited => None,
SyntaxKind::MathScript => None,
SyntaxKind::MathFrac => None,
SyntaxKind::MathAlignPoint => Some(Category::MathOperator),
SyntaxKind::Hashtag if node.before_error() => None,
SyntaxKind::Hashtag => node
.next_leaf()
.filter(|node| node.kind() != SyntaxKind::Dollar)
.as_ref()
.and_then(highlight),
SyntaxKind::LeftBrace => Some(Category::Punctuation),
SyntaxKind::RightBrace => Some(Category::Punctuation),
@ -134,14 +143,14 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
_ => Some(Category::Operator),
},
SyntaxKind::Underscore => match node.parent_kind() {
Some(SyntaxKind::Script) => Some(Category::MathOperator),
Some(SyntaxKind::MathScript) => Some(Category::MathOperator),
_ => None,
},
SyntaxKind::Dollar => Some(Category::MathDelimiter),
SyntaxKind::Plus => Some(Category::Operator),
SyntaxKind::Minus => Some(Category::Operator),
SyntaxKind::Slash => Some(match node.parent_kind() {
Some(SyntaxKind::Frac) => Category::MathOperator,
Some(SyntaxKind::MathFrac) => Category::MathOperator,
_ => Category::Operator,
}),
SyntaxKind::Hat => Some(Category::MathOperator),
@ -183,47 +192,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Include => Some(Category::Keyword),
SyntaxKind::As => Some(Category::Keyword),
SyntaxKind::Ident => match node.parent_kind() {
Some(
SyntaxKind::Markup
| SyntaxKind::Math
| SyntaxKind::Script
| SyntaxKind::Frac,
) => Some(Category::Interpolated),
Some(SyntaxKind::FuncCall) => Some(Category::Function),
Some(SyntaxKind::FieldAccess)
if node.parent().and_then(|p| p.parent_kind())
== Some(SyntaxKind::SetRule)
&& node.next_sibling().is_none() =>
{
Some(Category::Function)
}
Some(SyntaxKind::FieldAccess)
if node
.parent()
.and_then(|p| p.parent())
.filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
.and_then(|gp| gp.parent())
.map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
&& node.next_sibling().is_none() =>
{
Some(Category::Function)
}
Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
Some(Category::Function)
}
Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => {
Some(Category::Function)
}
Some(SyntaxKind::SetRule) => Some(Category::Function),
Some(SyntaxKind::ShowRule)
if node.prev_sibling().as_ref().map(|v| v.kind())
== Some(SyntaxKind::Show) =>
{
Some(Category::Function)
}
_ => None,
},
SyntaxKind::Code => None,
SyntaxKind::Ident => highlight_ident(node),
SyntaxKind::Bool => Some(Category::Keyword),
SyntaxKind::Int => Some(Category::Number),
SyntaxKind::Float => Some(Category::Number),
@ -238,7 +208,11 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
SyntaxKind::Keyed => None,
SyntaxKind::Unary => None,
SyntaxKind::Binary => None,
SyntaxKind::FieldAccess => None,
SyntaxKind::FieldAccess => match node.parent_kind() {
Some(SyntaxKind::Markup | SyntaxKind::Math) => Some(Category::Interpolated),
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
_ => None,
},
SyntaxKind::FuncCall => None,
SyntaxKind::MethodCall => None,
SyntaxKind::Args => None,
@ -266,6 +240,52 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> {
}
}
/// Highlight an identifier based on context.
fn highlight_ident(node: &LinkedNode) -> Option<Category> {
match node.parent_kind() {
Some(
SyntaxKind::Markup
| SyntaxKind::Math
| SyntaxKind::MathFrac
| SyntaxKind::MathScript,
) => Some(Category::Interpolated),
Some(SyntaxKind::FuncCall) => Some(Category::Function),
Some(SyntaxKind::FieldAccess)
if node.parent().and_then(|p| p.parent_kind())
== Some(SyntaxKind::SetRule)
&& node.next_sibling().is_none() =>
{
Some(Category::Function)
}
Some(SyntaxKind::FieldAccess)
if node
.parent()
.and_then(|p| p.parent())
.filter(|gp| gp.kind() == SyntaxKind::Parenthesized)
.and_then(|gp| gp.parent())
.map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall)
&& node.next_sibling().is_none() =>
{
Some(Category::Function)
}
Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight),
Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => {
Some(Category::Function)
}
Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => {
Some(Category::Function)
}
Some(SyntaxKind::SetRule) => Some(Category::Function),
Some(SyntaxKind::ShowRule)
if node.prev_sibling().as_ref().map(|v| v.kind())
== Some(SyntaxKind::Show) =>
{
Some(Category::Function)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use std::ops::Range;
@ -300,7 +320,8 @@ mod tests {
test(
"#f(x + 1)",
&[
(0..2, Function),
(0..1, Function),
(1..2, Function),
(2..3, Punctuation),
(5..6, Operator),
(7..8, Number),
@ -311,7 +332,8 @@ mod tests {
test(
"#let f(x) = x",
&[
(0..4, Keyword),
(0..1, Keyword),
(1..4, Keyword),
(5..6, Function),
(6..7, Punctuation),
(8..9, Punctuation),

View File

@ -234,7 +234,7 @@ fn eval_markup(
*node = mem::take(node).labelled(label);
}
}
value => seq.push(value.display().spanned(expr.span())),
value => seq.push(value.display()),
},
}
@ -254,11 +254,9 @@ impl Eval for ast::Expr {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let span = self.span();
let forbidden = |name| {
error!(
self.span(),
"{} is only allowed directly in code and content blocks", name
)
error!(span, "{} is only allowed directly in code and content blocks", name)
};
match self {
@ -266,9 +264,9 @@ impl Eval for ast::Expr {
Self::Space(v) => v.eval(vm).map(Value::Content),
Self::Linebreak(v) => v.eval(vm).map(Value::Content),
Self::Parbreak(v) => v.eval(vm).map(Value::Content),
Self::Escape(v) => v.eval(vm).map(Value::Content),
Self::Shorthand(v) => v.eval(vm).map(Value::Content),
Self::Symbol(v) => v.eval(vm).map(Value::Content),
Self::Escape(v) => v.eval(vm),
Self::Shorthand(v) => v.eval(vm),
Self::SmartQuote(v) => v.eval(vm).map(Value::Content),
Self::Strong(v) => v.eval(vm).map(Value::Content),
Self::Emph(v) => v.eval(vm).map(Value::Content),
@ -280,11 +278,14 @@ impl Eval for ast::Expr {
Self::List(v) => v.eval(vm).map(Value::Content),
Self::Enum(v) => v.eval(vm).map(Value::Content),
Self::Term(v) => v.eval(vm).map(Value::Content),
Self::Atom(v) => v.eval(vm).map(Value::Content),
Self::Delimited(v) => v.eval(vm).map(Value::Content),
Self::Script(v) => v.eval(vm).map(Value::Content),
Self::Frac(v) => v.eval(vm).map(Value::Content),
Self::AlignPoint(v) => v.eval(vm).map(Value::Content),
Self::Formula(v) => v.eval(vm).map(Value::Content),
Self::Math(v) => v.eval(vm).map(Value::Content),
Self::MathAtom(v) => v.eval(vm).map(Value::Content),
Self::MathIdent(v) => v.eval(vm),
Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content),
Self::MathDelimited(v) => v.eval(vm).map(Value::Content),
Self::MathScript(v) => v.eval(vm).map(Value::Content),
Self::MathFrac(v) => v.eval(vm).map(Value::Content),
Self::Ident(v) => v.eval(vm),
Self::None(v) => v.eval(vm),
Self::Auto(v) => v.eval(vm),
@ -295,7 +296,6 @@ impl Eval for ast::Expr {
Self::Str(v) => v.eval(vm),
Self::Code(v) => v.eval(vm),
Self::Content(v) => v.eval(vm).map(Value::Content),
Self::Math(v) => v.eval(vm).map(Value::Content),
Self::Array(v) => v.eval(vm).map(Value::Array),
Self::Dict(v) => v.eval(vm).map(Value::Dict),
Self::Parenthesized(v) => v.eval(vm),
@ -316,29 +316,12 @@ impl Eval for ast::Expr {
Self::Break(v) => v.eval(vm),
Self::Continue(v) => v.eval(vm),
Self::Return(v) => v.eval(vm),
}
}
}
}?
.spanned(span);
impl ast::Expr {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok(match self {
Self::Escape(v) => v.eval_in_math(vm)?,
Self::Shorthand(v) => v.eval_in_math(vm)?,
Self::Symbol(v) => v.eval_in_math(vm)?,
Self::Ident(v) => v.eval_in_math(vm)?,
Self::FuncCall(v) => v.eval_in_math(vm)?,
_ => self.eval(vm)?.display_in_math(),
}
.spanned(self.span()))
}
fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok(match self {
Self::Delimited(v) => v.eval_without_parens(vm)?,
_ => self.eval_in_math(vm)?,
}
.spanned(self.span()))
Ok(v)
}
}
@ -375,44 +358,22 @@ impl Eval for ast::Parbreak {
}
impl Eval for ast::Escape {
type Output = Content;
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.text)(self.get().into()))
}
}
impl ast::Escape {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok((vm.items.math_atom)(self.get().into()))
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
// This can be in markup and math, going through a string ensure
// that either text or atom is picked.
Ok(Value::Str(self.get().into()))
}
}
impl Eval for ast::Shorthand {
type Output = Content;
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.text)(self.get().into()))
}
}
impl ast::Shorthand {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok((vm.items.math_atom)(self.get().into()))
}
}
impl Eval for ast::Symbol {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
Ok((vm.items.symbol)(self.get().into()))
}
}
impl ast::Symbol {
fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> {
Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op:square".into()))
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
// This can be in markup and math, going through a string ensure
// that either text or atom is picked.
Ok(Value::Str(self.get().into()))
}
}
@ -513,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 {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let seq = self
.exprs()
.map(|expr| expr.eval_in_math(vm))
.collect::<SourceResult<_>>()?;
let block = self.block();
Ok((vm.items.math_formula)(Content::sequence(seq), block))
Ok(Content::sequence(
self.exprs()
.map(|expr| Ok(expr.eval(vm)?.display_in_math()))
.collect::<SourceResult<_>>()?,
))
}
}
impl Eval for ast::Atom {
impl Eval for ast::MathAtom {
type Output = Content;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
@ -534,59 +504,15 @@ impl Eval for ast::Atom {
}
}
impl Eval for ast::Delimited {
type Output = Content;
impl Eval for ast::MathIdent {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let seq = self
.exprs()
.map(|expr| expr.eval_in_math(vm))
.collect::<SourceResult<_>>()?;
Ok((vm.items.math_delimited)(Content::sequence(seq)))
Ok(vm.scopes.get_in_math(self).cloned().at(self.span())?)
}
}
impl ast::Delimited {
fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> {
let exprs: Vec<_> = self.exprs().collect();
let mut slice = exprs.as_slice();
if let (Some(ast::Expr::Atom(first)), Some(ast::Expr::Atom(last))) =
(exprs.first(), exprs.last())
{
if first.get() == "(" && last.get() == ")" {
slice = &exprs[1..exprs.len() - 1];
}
}
let seq = slice
.iter()
.map(|expr| expr.eval_in_math(vm))
.collect::<SourceResult<_>>()?;
Ok((vm.items.math_delimited)(Content::sequence(seq)))
}
}
impl Eval for ast::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 {
impl Eval for ast::MathAlignPoint {
type Output = Content;
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 {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let value = vm.scopes.get(self).cloned().at(self.span())?;
Ok(match value {
Value::Func(func) => Value::Func(func.spanned(self.span())),
value => value,
})
}
}
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())
}
Ok(vm.scopes.get(self).cloned().at(self.span())?)
}
}
@ -686,12 +634,20 @@ impl Eval for ast::CodeBlock {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
vm.scopes.enter();
let output = eval_code(vm, &mut self.exprs())?;
let output = self.body().eval(vm)?;
vm.scopes.exit();
Ok(output)
}
}
impl Eval for ast::Code {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
eval_code(vm, &mut self.exprs())
}
}
/// Evaluate a stream of expressions.
fn eval_code(
vm: &mut Vm,
@ -901,23 +857,9 @@ impl Eval for ast::FieldAccess {
type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let object = self.target().eval(vm)?;
let span = self.field().span();
let field = self.field().take();
Ok(match object {
Value::Dict(dict) => dict.at(&field).at(span)?.clone(),
Value::Content(content) => content
.field(&field)
.ok_or_else(|| format!("unknown field `{field}`"))
.at(span)?,
Value::Module(module) => module.get(&field).cloned().at(span)?,
v => bail!(
self.target().span(),
"expected dictionary or content, found {}",
v.type_name()
),
})
let value = self.target().eval(vm)?;
let field = self.field();
value.field(&field).at(field.span())
}
}
@ -926,27 +868,13 @@ impl Eval for ast::FuncCall {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let callee = self.callee();
let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?;
let args = self.args().eval(vm)?;
Self::eval_call(vm, &callee, args, self.span())
}
}
let callee_span = callee.span();
let in_math = matches!(callee, ast::Expr::MathIdent(_));
let callee = callee.eval(vm)?;
let mut args = self.args().eval(vm)?;
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 {
if in_math && !matches!(callee, Value::Func(_)) {
let mut body = (vm.items.math_atom)('('.into());
let mut args = self.args().eval(vm)?;
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
if i > 0 {
body += (vm.items.math_atom)(','.into());
@ -954,25 +882,28 @@ impl ast::FuncCall {
body += arg;
}
body += (vm.items.math_atom)(')'.into());
Ok(callee.display_in_math() + body)
}
}
fn eval_call(
vm: &mut Vm,
callee: &Func,
args: Args,
span: Span,
) -> SourceResult<Value> {
if vm.depth >= MAX_CALL_DEPTH {
bail!(span, "maximum function call depth exceeded");
return Ok(Value::Content(callee.display_in_math() + body));
}
let point = || Tracepoint::Call(callee.name().map(Into::into));
callee.call(vm, args).trace(vm.world, point, span)
let callee = callee.cast::<Func>().at(callee_span)?;
complete_call(vm, &callee, args, self.span())
}
}
fn complete_call(
vm: &mut Vm,
callee: &Func,
args: Args,
span: Span,
) -> SourceResult<Value> {
if vm.depth >= MAX_CALL_DEPTH {
bail!(span, "maximum function call depth exceeded");
}
let point = || Tracepoint::Call(callee.name().map(Into::into));
callee.call(vm, args).trace(vm.world, point, span)
}
impl Eval for ast::MethodCall {
type Output = Value;
@ -988,7 +919,7 @@ impl Eval for ast::MethodCall {
if let Value::Func(callee) =
module.get(&method).cloned().at(method.span())?
{
return ast::FuncCall::eval_call(vm, &callee, args, self.span());
return complete_call(vm, &callee, args, self.span());
}
}
@ -999,7 +930,7 @@ impl Eval for ast::MethodCall {
if let Value::Module(module) = &value {
if let Value::Func(callee) = module.get(&method).at(method.span())? {
return ast::FuncCall::eval_call(vm, callee, args, self.span());
return complete_call(vm, callee, args, self.span());
}
}
@ -1367,7 +1298,7 @@ impl Eval for ast::ModuleInclude {
}
/// Process an import of a module relative to the current location.
fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> {
fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult<Module> {
let path = match source {
Value::Str(path) => path,
Value::Module(module) => return Ok(module),
@ -1386,7 +1317,8 @@ fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> {
// Evaluate the file.
let source = vm.world.source(id);
let point = || Tracepoint::Import;
eval(vm.world, vm.route, source).trace(vm.world, point, span)
eval(vm.world, vm.route, TrackedMut::reborrow_mut(&mut vm.tracer), source)
.trace(vm.world, point, span)
}
impl Eval for ast::LoopBreak {
@ -1446,7 +1378,12 @@ impl Access for ast::Expr {
impl Access for ast::Ident {
fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> {
vm.scopes.get_mut(self).at(self.span())
let span = self.span();
let value = vm.scopes.get_mut(self).at(span)?;
if vm.traced == Some(span) {
vm.tracer.trace(value.clone());
}
Ok(value)
}
}

View File

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

View File

@ -68,19 +68,19 @@ pub struct LangItems {
/// An item in a term list: `/ Term: Details`.
pub term_item: fn(term: Content, description: Content) -> Content,
/// A mathematical formula: `$x$`, `$ x^2 $`.
pub math_formula: fn(body: Content, block: bool) -> Content,
/// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`.
pub math_delimited: fn(body: Content) -> Content,
pub formula: fn(body: Content, block: bool) -> Content,
/// An atom in a formula: `x`, `+`, `12`.
pub math_atom: fn(atom: EcoString) -> Content,
/// An alignment point in a formula: `&`.
pub math_align_point: fn() -> Content,
/// A subsection in a math formula that is surrounded by matched delimiters:
/// `[x + y]`.
pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content,
/// A base with optional sub- and superscripts in a formula: `a_1^2`.
pub math_script:
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
/// A fraction in a formula: `x/2`.
pub math_frac: fn(num: Content, denom: Content) -> Content,
/// An alignment point in a formula: `&`.
pub math_align_point: fn() -> Content,
}
impl Debug for LangItems {
@ -108,7 +108,7 @@ impl Hash for LangItems {
self.list_item.hash(state);
self.enum_item.hash(state);
self.term_item.hash(state);
self.math_formula.hash(state);
self.formula.hash(state);
self.math_atom.hash(state);
self.math_script.hash(state);
self.math_frac.hash(state);

View File

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

View File

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

View File

@ -376,7 +376,7 @@ impl Lexer<'_> {
Some('-') if !s.at(['-', '?']) => {}
Some('.') if !s.at("..") => {}
Some('h') if !s.at("ttp://") && !s.at("ttps://") => {}
Some('@' | '#') if !s.at(is_id_start) => {}
Some('@') if !s.at(is_id_start) => {}
_ => break,
}
@ -410,15 +410,8 @@ impl Lexer<'_> {
'\\' => self.backslash(),
':' if self.s.at(is_id_start) => self.maybe_symbol(),
'"' => self.string(),
'#' if self.s.eat_if('{') => SyntaxKind::LeftBrace,
'#' if self.s.eat_if('[') => SyntaxKind::LeftBracket,
'#' if self.s.at(is_id_start) => {
match keyword(self.s.eat_while(is_id_continue)) {
Some(keyword) => keyword,
None => SyntaxKind::Ident,
}
}
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'|' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'<' if self.s.eat_if("->") => SyntaxKind::Shorthand,
'<' if self.s.eat_if("=>") => SyntaxKind::Shorthand,
@ -429,13 +422,17 @@ impl Lexer<'_> {
'-' if self.s.eat_if('>') => SyntaxKind::Shorthand,
'=' if self.s.eat_if('>') => SyntaxKind::Shorthand,
':' if self.s.eat_if('=') => SyntaxKind::Shorthand,
'.' if self.s.eat_if("..") => SyntaxKind::Shorthand,
'[' if self.s.eat_if('|') => SyntaxKind::Shorthand,
'|' if self.s.eat_if(']') => SyntaxKind::Shorthand,
'|' if self.s.eat_if('|') => SyntaxKind::Shorthand,
'*' => SyntaxKind::Shorthand,
'#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag,
'_' => SyntaxKind::Underscore,
'$' => SyntaxKind::Dollar,
'/' => SyntaxKind::Slash,
'^' => SyntaxKind::Hat,
'&' => SyntaxKind::AlignPoint,
'&' => SyntaxKind::MathAlignPoint,
// Identifiers and symbol notation.
c if is_math_id_start(c) && self.s.at(is_math_id_continue) => {
@ -479,7 +476,7 @@ impl Lexer<'_> {
.map_or(0, str::len);
self.s.jump(start + len);
}
SyntaxKind::Atom
SyntaxKind::MathAtom
}
}

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.
pub fn cast<T: AstNode>(&self) -> Option<T> {
T::from_untyped(self)
@ -144,6 +149,16 @@ impl SyntaxNode {
}
}
/// Convert the child to another kind.
pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) {
debug_assert!(!kind.is_error());
match &mut self.0 {
Repr::Leaf(leaf) => leaf.kind = kind,
Repr::Inner(inner) => Arc::make_mut(inner).kind = kind,
Repr::Error(_) => panic!("cannot convert error"),
}
}
/// Convert the child to an error.
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
let len = self.len();
@ -695,6 +710,14 @@ impl<'a> LinkedNode<'a> {
Some(next)
}
}
/// Whether an error follows directly after the node.
pub fn before_error(&self) -> bool {
let Some(parent) = self.parent() else { return false };
let Some(index) = self.index.checked_add(1) else { return false };
let Some(node) = parent.node.children().nth(index) else { return false };
node.kind().is_error()
}
}
/// Access to leafs.
@ -865,8 +888,8 @@ mod tests {
// Go back to "#set". Skips the space.
let prev = node.prev_sibling().unwrap();
assert_eq!(prev.offset(), 0);
assert_eq!(prev.text(), "#set");
assert_eq!(prev.offset(), 1);
assert_eq!(prev.text(), "set");
}
#[test]
@ -875,7 +898,7 @@ mod tests {
let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap();
let prev = leaf.prev_leaf().unwrap();
assert_eq!(leaf.text(), "fun");
assert_eq!(prev.text(), "#set");
assert_eq!(prev.text(), "set");
let source = Source::detached("#let x = 10");
let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@
---
// The inner rectangle should also be yellow here.
// (and therefore invisible)
[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
#[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))]
---
// The inner rectangle should not be yellow here.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@
---
// Test mutating indexed value.
{
#{
let matrix = (((1,), (2,)), ((3,), (4,)))
matrix.at(1).at(0).push(5)
test(matrix, (((1,), (2,)), ((3, 5), (4,))))
@ -15,7 +15,7 @@
---
// Test multiline chain in code block.
{
#{
let rewritten = "Hello. This is a sentence. And one more."
.split(".")
.map(s => s.trim())
@ -27,20 +27,20 @@
}
---
// Error: 2:3-2:16 type array has no method `fun`
// Error: 2:4-2:17 type array has no method `fun`
#let numbers = ()
{ numbers.fun() }
#{ numbers.fun() }
---
// Error: 2:3-2:44 cannot mutate a temporary value
// Error: 2:4-2:45 cannot mutate a temporary value
#let numbers = (1, 2, 3)
{ numbers.map(v => v / 2).sorted().map(str).remove(4) }
#{ numbers.map(v => v / 2).sorted().map(str).remove(4) }
---
// Error: 2:3-2:19 cannot mutate a temporary value
// Error: 2:4-2:20 cannot mutate a temporary value
#let numbers = (1, 2, 3)
{ numbers.sorted() = 1 }
#{ numbers.sorted() = 1 }
---
// Error: 3-6 cannot mutate a constant
{ box.push(1) }
// Error: 4-7 cannot mutate a constant
#{ box.push(1) }

View File

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

View File

@ -14,9 +14,14 @@
---
// Assignment binds stronger than boolean operations.
// Error: 2:2-2:7 cannot mutate a temporary value
// Error: 2:3-2:8 cannot mutate a temporary value
#let x = false
{not x = "a"}
#{not x = "a"}
---
// Precedence doesn't matter for chained unary operators.
// Error: 3-12 cannot apply '-' to boolean
#{-not true}
---
// Parentheses override precedence.
@ -25,8 +30,3 @@
// Error: 14 expected closing paren
#test({(1 + 1}, 2)
---
// Precedence doesn't matter for chained unary operators.
// Error: 2-11 cannot apply '-' to boolean
{-not true}

View File

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

View File

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

View File

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

View File

@ -17,4 +17,4 @@ a~b
---
#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.
A [_B #show c => [*#c*]; C_] D
A #[_B #show c => [*#c*]; C_] D
---
// Test style precedence.
@ -29,5 +29,5 @@ Forest
Ignored
---
// Error: 4-18 show is only allowed directly in code and content blocks
{ (show body => 2) * body }
// Error: 5-19 show is only allowed directly in code and content blocks
#{ (show body => 2) * body }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,7 +47,7 @@ Lריווח #h(1cm) R
---
// Test whether L1 whitespace resetting destroys stuff.
الغالب #h(70pt) ن{" "}ة
الغالب #h(70pt) ن#" "ة
---
// 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
// basically empty paragraph.
#set par(justify: true)
{""}
#""

View File

@ -12,7 +12,7 @@
)
#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.
#let size = 11pt
#let tex = [{
#let tex = {
[T]
h(-0.14 * size)
move(dy: 0.22 * size)[E]
h(-0.12 * size)
[X]
}]
}
#let xetex = {
[X]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@
Emoji: 🐪, 🌋, 🏞
// Colors.
[
#[
#set text(fill: eastern)
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.
"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 \
H #if true{"I"} J \
K #if true [L] else []M \
#let c = true; N#while c [{c = false}O] P \
#let c = true; N#while c [#{c = false}O] P \
#let c = true; Q #while c { c = false; "R" } S \
T#for _ in (none,) {"U"}V
@ -19,11 +19,11 @@ A /**/B/**/ C
---
// Test that a run consisting only of whitespace isn't trimmed.
A[#set text("IBM Plex Serif"); ]B
A#text("IBM Plex Serif")[ ]B
---
// Test font change after space.
Left [#set text("IBM Plex Serif");Right].
Left #text("IBM Plex Serif")[Right].
---
// Test that linebreak consumed surrounding spaces.
@ -31,7 +31,7 @@ Left [#set text("IBM Plex Serif");Right].
---
// Test that space at start of non-backslash-linebreak line isn't trimmed.
A{"\n"} B
A#"\n" B
---
// Test that trailing space does not force a line break.

View File

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

View File

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

View File

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

View File

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