diff --git a/library/src/lib.rs b/library/src/lib.rs index c4b6710f4..bdb2cc907 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -189,11 +189,11 @@ fn items() -> LangItems { term_item: |term, description| { layout::ListItem::Term(basics::TermItem { term, description }).pack() }, - math_formula: |body, block| math::FormulaNode { body, block }.pack(), + formula: |body, block| math::FormulaNode { body, block }.pack(), math_atom: |atom| math::AtomNode(atom).pack(), - math_delimited: |body| math::LrNode(body).pack(), + math_align_point: || math::AlignPointNode.pack(), + math_delimited: |open, body, close| math::LrNode(open + body + close).pack(), math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), math_frac: |num, denom| math::FracNode { num, denom }.pack(), - math_align_point: || math::AlignPointNode.pack(), } } diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs index a15a60204..6829554cd 100644 --- a/library/src/math/accent.rs +++ b/library/src/math/accent.rs @@ -133,7 +133,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs { /// Extract a single character from content. fn extract(accent: &Content) -> Option { - let atom = accent.to::()?.body.to::()?; + let atom = accent.to::()?; let mut chars = atom.0.chars(); let c = chars.next().filter(|_| chars.next().is_none())?; Some(combining(c)) @@ -166,6 +166,8 @@ fn combining(c: char) -> char { '\u{2190}' => '\u{20d6}', '\u{2192}' => '\u{20d7}', '\u{2212}' => '\u{0305}', + '\u{223C}' => '\u{0303}', + '\u{22C5}' => '\u{0307}', '\u{27f6}' => '\u{20d7}', _ => c, } diff --git a/library/src/math/root.rs b/library/src/math/root.rs index 105730668..bae3bbb67 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -155,7 +155,7 @@ fn layout( /// Select a precomposed radical, if the font has it. fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option { - let node = index?.to::()?.body.to::()?; + let node = index?.to::()?; let c = match node.0.as_str() { "3" => '∛', "4" => '∜', diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index 75201d93e..b1df94d1f 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -7,7 +7,7 @@ pub enum Category { Comment, /// Punctuation in code. Punctuation, - /// An escape sequence, shorthand or symbol notation. + /// An escape sequence or shorthand. Escape, /// Strong markup. Strong, @@ -97,7 +97,6 @@ pub fn highlight(node: &LinkedNode) -> Option { 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 { 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 { _ => 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 { 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 { 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 { } } +/// Highlight an identifier based on context. +fn highlight_ident(node: &LinkedNode) -> Option { + match node.parent_kind() { + Some( + SyntaxKind::Markup + | SyntaxKind::Math + | SyntaxKind::MathFrac + | SyntaxKind::MathScript, + ) => Some(Category::Interpolated), + Some(SyntaxKind::FuncCall) => Some(Category::Function), + Some(SyntaxKind::FieldAccess) + if node.parent().and_then(|p| p.parent_kind()) + == Some(SyntaxKind::SetRule) + && node.next_sibling().is_none() => + { + Some(Category::Function) + } + Some(SyntaxKind::FieldAccess) + if node + .parent() + .and_then(|p| p.parent()) + .filter(|gp| gp.kind() == SyntaxKind::Parenthesized) + .and_then(|gp| gp.parent()) + .map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall) + && node.next_sibling().is_none() => + { + Some(Category::Function) + } + Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight), + Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => { + Some(Category::Function) + } + Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => { + Some(Category::Function) + } + Some(SyntaxKind::SetRule) => Some(Category::Function), + Some(SyntaxKind::ShowRule) + if node.prev_sibling().as_ref().map(|v| v.kind()) + == Some(SyntaxKind::Show) => + { + Some(Category::Function) + } + _ => None, + } +} + #[cfg(test)] mod tests { use std::ops::Range; @@ -300,7 +320,8 @@ mod tests { test( "#f(x + 1)", &[ - (0..2, Function), + (0..1, Function), + (1..2, Function), (2..3, Punctuation), (5..6, Operator), (7..8, Number), @@ -311,7 +332,8 @@ mod tests { test( "#let f(x) = x", &[ - (0..4, Keyword), + (0..1, Keyword), + (1..4, Keyword), (5..6, Function), (6..7, Punctuation), (8..9, Punctuation), diff --git a/src/model/eval.rs b/src/model/eval.rs index 6de328bc2..6a1884eb4 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -234,7 +234,7 @@ fn eval_markup( *node = mem::take(node).labelled(label); } } - value => seq.push(value.display().spanned(expr.span())), + value => seq.push(value.display()), }, } @@ -254,11 +254,9 @@ impl Eval for ast::Expr { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { - 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 { - 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 { - Ok((vm.items.text)(self.get().into())) - } -} - -impl ast::Escape { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.math_atom)(self.get().into())) + fn eval(&self, _: &mut Vm) -> SourceResult { + // 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 { - Ok((vm.items.text)(self.get().into())) - } -} - -impl ast::Shorthand { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.math_atom)(self.get().into())) - } -} - -impl Eval for ast::Symbol { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.symbol)(self.get().into())) - } -} - -impl ast::Symbol { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op:square".into())) + fn eval(&self, _: &mut Vm) -> SourceResult { + // 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 { + 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 { - let seq = self - .exprs() - .map(|expr| expr.eval_in_math(vm)) - .collect::>()?; - 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::>()?, + )) } } -impl Eval for ast::Atom { +impl Eval for ast::MathAtom { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { @@ -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 { - let seq = self - .exprs() - .map(|expr| expr.eval_in_math(vm)) - .collect::>()?; - 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 { - 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::>()?; - Ok((vm.items.math_delimited)(Content::sequence(seq))) - } -} - -impl Eval for ast::Script { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - 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 { - 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 { @@ -594,27 +520,49 @@ impl Eval for ast::AlignPoint { } } +impl Eval for ast::MathDelimited { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + 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 { + 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 { + 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 { - 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 { - 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 { 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 { + 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 { - 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 { let callee = self.callee(); - let callee = callee.eval(vm)?.cast::().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 { - 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::()?.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 { - 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::().at(callee_span)?; + complete_call(vm, &callee, args, self.span()) } } +fn complete_call( + vm: &mut Vm, + callee: &Func, + args: Args, + span: Span, +) -> SourceResult { + 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 { +fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult { 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 { // Evaluate the file. let source = vm.world.source(id); let point = || Tracepoint::Import; - eval(vm.world, vm.route, source).trace(vm.world, point, span) + eval(vm.world, vm.route, TrackedMut::reborrow_mut(&mut vm.tracer), source) + .trace(vm.world, point, span) } impl Eval for ast::LoopBreak { @@ -1446,7 +1378,12 @@ impl Access for ast::Expr { impl Access for ast::Ident { fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - vm.scopes.get_mut(self).at(self.span()) + let span = self.span(); + let value = vm.scopes.get_mut(self).at(span)?; + if vm.traced == Some(span) { + vm.tracer.trace(value.clone()); + } + Ok(value) } } diff --git a/src/model/func.rs b/src/model/func.rs index 73b7f1c79..00e59bbd7 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -505,17 +505,17 @@ mod tests { fn test_captures() { // Let binding and function definition. test("#let x = x", &["x"]); - test("#let x; {x + y}", &["y"]); + test("#let x; #{x + y}", &["y"]); test("#let f(x, y) = x + y", &[]); test("#let f(x, y) = f", &[]); test("#let f = (x, y) => f", &["f"]); // Closure with different kinds of params. - test("{(x, y) => x + z}", &["z"]); - test("{(x: y, z) => x + z}", &["y"]); - test("{(..x) => x + y}", &["y"]); - test("{(x, y: x + z) => x + y}", &["x", "z"]); - test("{x => x; x}", &["x"]); + test("#{(x, y) => x + z}", &["z"]); + test("#{(x: y, z) => x + z}", &["y"]); + test("#{(..x) => x + y}", &["y"]); + test("#{(x, y: x + z) => x + y}", &["x", "z"]); + test("#{x => x; x}", &["x"]); // Show rule. test("#show y: x => x", &["y"]); @@ -532,7 +532,7 @@ mod tests { test("#import x + y: x, y, z", &["x", "y"]); // Blocks. - test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); - test("[#let x = 1]#x", &["x"]); + test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]); + test("#[#let x = 1]#x", &["x"]); } } diff --git a/src/model/library.rs b/src/model/library.rs index a64b02636..cd9db10f2 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -68,19 +68,19 @@ pub struct LangItems { /// An item in a term list: `/ Term: Details`. pub term_item: fn(term: Content, description: Content) -> Content, /// A mathematical formula: `$x$`, `$ x^2 $`. - pub math_formula: fn(body: Content, block: bool) -> Content, - /// A subsection in a math formula that is surrounded by matched delimiters: - /// `[x + y]`. - pub math_delimited: fn(body: Content) -> Content, + pub formula: fn(body: Content, block: bool) -> Content, /// An atom in a formula: `x`, `+`, `12`. pub math_atom: fn(atom: EcoString) -> Content, + /// An alignment point in a formula: `&`. + pub math_align_point: fn() -> Content, + /// A subsection in a math formula that is surrounded by matched delimiters: + /// `[x + y]`. + pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content, /// A base with optional sub- and superscripts in a formula: `a_1^2`. pub math_script: fn(base: Content, sub: Option, sup: Option) -> Content, /// A fraction in a formula: `x/2`. pub math_frac: fn(num: Content, denom: Content) -> Content, - /// An alignment point in a formula: `&`. - pub math_align_point: fn() -> Content, } impl Debug for LangItems { @@ -108,7 +108,7 @@ impl Hash for LangItems { self.list_item.hash(state); self.enum_item.hash(state); self.term_item.hash(state); - self.math_formula.hash(state); + self.formula.hash(state); self.math_atom.hash(state); self.math_script.hash(state); self.math_frac.hash(state); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 4bab0c429..b91867874 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -29,7 +29,7 @@ pub trait AstNode: Sized { macro_rules! node { ($(#[$attr:meta])* $name:ident) => { - #[derive(Debug, Clone, PartialEq, Hash)] + #[derive(Debug, Default, Clone, PartialEq, Hash)] #[repr(transparent)] $(#[$attr])* pub struct $name(SyntaxNode); @@ -114,18 +114,22 @@ pub enum Expr { /// An item in a term list: `/ Term: Details`. Term(TermItem), /// A math formula: `$x$`, `$ x^2 $`. + Formula(Formula), + /// A math formula: `$x$`, `$ x^2 $`. Math(Math), /// An atom in a math formula: `x`, `+`, `12`. - Atom(Atom), + MathAtom(MathAtom), + /// An identifier in a math formula: `pi`. + MathIdent(MathIdent), + /// An alignment point in a math formula: `&`. + MathAlignPoint(MathAlignPoint), /// A subsection in a math formula that is surrounded by matched delimiters: /// `[x + y]`. - Delimited(Delimited), + MathDelimited(MathDelimited), /// A base with optional sub- and superscripts in a math formula: `a_1^2`. - Script(Script), + MathScript(MathScript), /// A fraction in a math formula: `x/2`. - Frac(Frac), - /// An alignment point in a math formula: `&`. - AlignPoint(AlignPoint), + MathFrac(MathFrac), /// An identifier: `left`. Ident(Ident), /// The `none` literal. @@ -205,7 +209,6 @@ impl AstNode for Expr { SyntaxKind::Text => node.cast().map(Self::Text), SyntaxKind::Escape => node.cast().map(Self::Escape), SyntaxKind::Shorthand => node.cast().map(Self::Shorthand), - SyntaxKind::Symbol => node.cast().map(Self::Symbol), SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote), SyntaxKind::Strong => node.cast().map(Self::Strong), SyntaxKind::Emph => node.cast().map(Self::Emph), @@ -217,12 +220,14 @@ impl AstNode for Expr { SyntaxKind::ListItem => node.cast().map(Self::List), SyntaxKind::EnumItem => node.cast().map(Self::Enum), SyntaxKind::TermItem => node.cast().map(Self::Term), + SyntaxKind::Formula => node.cast().map(Self::Formula), SyntaxKind::Math => node.cast().map(Self::Math), - SyntaxKind::Atom => node.cast().map(Self::Atom), - SyntaxKind::Delimited => node.cast().map(Self::Delimited), - SyntaxKind::Script => node.cast().map(Self::Script), - SyntaxKind::Frac => node.cast().map(Self::Frac), - SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint), + SyntaxKind::MathAtom => node.cast().map(Self::MathAtom), + SyntaxKind::MathIdent => node.cast().map(Self::MathIdent), + SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint), + SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited), + SyntaxKind::MathScript => node.cast().map(Self::MathScript), + SyntaxKind::MathFrac => node.cast().map(Self::MathFrac), SyntaxKind::Ident => node.cast().map(Self::Ident), SyntaxKind::None => node.cast().map(Self::None), SyntaxKind::Auto => node.cast().map(Self::Auto), @@ -265,7 +270,6 @@ impl AstNode for Expr { Self::Parbreak(v) => v.as_untyped(), Self::Escape(v) => v.as_untyped(), Self::Shorthand(v) => v.as_untyped(), - Self::Symbol(v) => v.as_untyped(), Self::SmartQuote(v) => v.as_untyped(), Self::Strong(v) => v.as_untyped(), Self::Emph(v) => v.as_untyped(), @@ -277,12 +281,14 @@ impl AstNode for Expr { Self::List(v) => v.as_untyped(), Self::Enum(v) => v.as_untyped(), Self::Term(v) => v.as_untyped(), + Self::Formula(v) => v.as_untyped(), Self::Math(v) => v.as_untyped(), - Self::Atom(v) => v.as_untyped(), - Self::Delimited(v) => v.as_untyped(), - Self::Script(v) => v.as_untyped(), - Self::Frac(v) => v.as_untyped(), - Self::AlignPoint(v) => v.as_untyped(), + Self::MathAtom(v) => v.as_untyped(), + Self::MathIdent(v) => v.as_untyped(), + Self::MathAlignPoint(v) => v.as_untyped(), + Self::MathDelimited(v) => v.as_untyped(), + Self::MathScript(v) => v.as_untyped(), + Self::MathFrac(v) => v.as_untyped(), Self::Ident(v) => v.as_untyped(), Self::None(v) => v.as_untyped(), Self::Auto(v) => v.as_untyped(), @@ -317,6 +323,12 @@ impl AstNode for Expr { } } +impl Default for Expr { + fn default() -> Self { + Expr::Space(Space::default()) + } +} + node! { /// Plain text without markup. Text @@ -360,9 +372,9 @@ impl Escape { u32::from_str_radix(hex, 16) .ok() .and_then(std::char::from_u32) - .expect("unicode escape is invalid") + .unwrap_or_default() } else { - s.eat().expect("escape is missing escaped character") + s.eat().unwrap_or_default() } } } @@ -378,10 +390,11 @@ impl Shorthand { pub fn get(&self) -> char { match self.0.text().as_str() { "~" => '\u{00A0}', - "..." => '\u{2026}', "--" => '\u{2013}', "---" => '\u{2014}', "-?" => '\u{00AD}', + "..." => '…', + "*" => '∗', "!=" => '≠', "<=" => '≤', ">=" => '≥', @@ -432,7 +445,7 @@ node! { impl Strong { /// The contents of the strong node. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("strong emphasis is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -444,7 +457,7 @@ node! { impl Emph { /// The contents of the emphasis node. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("emphasis is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -568,7 +581,7 @@ node! { impl Heading { /// The contents of the heading. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("heading is missing markup body") + self.0.cast_first_match().unwrap_or_default() } /// The section depth (numer of equals signs). @@ -577,7 +590,7 @@ impl Heading { .children() .find(|node| node.kind() == SyntaxKind::HeadingMarker) .and_then(|node| node.len().try_into().ok()) - .expect("heading is missing marker") + .unwrap_or(NonZeroUsize::new(1).unwrap()) } } @@ -589,7 +602,7 @@ node! { impl ListItem { /// The contents of the list item. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("list item is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -609,7 +622,7 @@ impl EnumItem { /// The contents of the list item. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("enum item is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -621,41 +634,53 @@ node! { impl TermItem { /// The term described by the item. pub fn term(&self) -> Markup { - self.0.cast_first_match().expect("term list item is missing term") + self.0.cast_first_match().unwrap_or_default() } /// The description of the term. pub fn description(&self) -> Markup { - self.0 - .cast_last_match() - .expect("term list item is missing description") + self.0.cast_last_match().unwrap_or_default() } } node! { /// A math formula: `$x$`, `$ x^2 $`. - Math + Formula } -impl Math { - /// The expressions the formula consists of. - pub fn exprs(&self) -> impl DoubleEndedIterator + '_ { - 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 + '_ { + 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 + '_ { - 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 + '_ { 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::()) - .expect("keyed pair is missing key") + .unwrap_or_default() } /// The right-hand side of the pair: `true`. pub fn expr(&self) -> Expr { - self.0.cast_last_match().expect("keyed pair is missing expression") + self.0.cast_last_match().unwrap_or_default() } } @@ -1069,12 +1140,12 @@ impl Unary { self.0 .children() .find_map(|node| UnOp::from_kind(node.kind())) - .expect("unary operation is missing operator") + .unwrap_or(UnOp::Pos) } /// The expression to operate on: `x`. pub fn expr(&self) -> Expr { - self.0.cast_last_match().expect("unary operation is missing child") + self.0.cast_last_match().unwrap_or_default() } } @@ -1137,21 +1208,17 @@ impl Binary { SyntaxKind::In if not => Some(BinOp::NotIn), _ => BinOp::from_kind(node.kind()), }) - .expect("binary operation is missing operator") + .unwrap_or(BinOp::Add) } /// The left-hand side of the operation: `a`. pub fn lhs(&self) -> Expr { - self.0 - .cast_first_match() - .expect("binary operation is missing left-hand side") + self.0.cast_first_match().unwrap_or_default() } /// The right-hand side of the operation: `b`. pub fn rhs(&self) -> Expr { - self.0 - .cast_last_match() - .expect("binary operation is missing right-hand side") + self.0.cast_last_match().unwrap_or_default() } } @@ -1317,12 +1384,12 @@ node! { impl FieldAccess { /// The expression to access the field on. pub fn target(&self) -> Expr { - self.0.cast_first_match().expect("field access is missing object") + self.0.cast_first_match().unwrap_or_default() } /// The name of the field. pub fn field(&self) -> Ident { - self.0.cast_last_match().expect("field access is missing name") + self.0.cast_last_match().unwrap_or_default() } } @@ -1334,14 +1401,12 @@ node! { impl FuncCall { /// The function to call. pub fn callee(&self) -> Expr { - self.0.cast_first_match().expect("function call is missing callee") + self.0.cast_first_match().unwrap_or_default() } /// The arguments to the function. pub fn args(&self) -> Args { - self.0 - .cast_last_match() - .expect("function call is missing argument list") + self.0.cast_last_match().unwrap_or_default() } } @@ -1353,19 +1418,17 @@ node! { impl MethodCall { /// The expression to call the method on. pub fn target(&self) -> Expr { - self.0.cast_first_match().expect("method call is missing target") + self.0.cast_first_match().unwrap_or_default() } /// The name of the method. pub fn method(&self) -> Ident { - self.0.cast_last_match().expect("method call is missing name") + self.0.cast_last_match().unwrap_or_default() } /// The arguments to the method. pub fn args(&self) -> Args { - self.0 - .cast_last_match() - .expect("method call is missing argument list") + self.0.cast_last_match().unwrap_or_default() } } @@ -1428,14 +1491,13 @@ impl Closure { self.0 .children() .find(|x| x.kind() == SyntaxKind::Params) - .expect("closure is missing parameter list") - .children() + .map_or([].iter(), |params| params.children()) .filter_map(SyntaxNode::cast) } /// The body of the closure. pub fn body(&self) -> Expr { - self.0.cast_last_match().expect("closure is missing body") + self.0.cast_last_match().unwrap_or_default() } } @@ -1479,10 +1541,8 @@ impl LetBinding { pub fn binding(&self) -> Ident { match self.0.cast_first_match() { Some(Expr::Ident(binding)) => binding, - Some(Expr::Closure(closure)) => { - closure.name().expect("let-bound closure is missing name") - } - _ => panic!("let is missing binding"), + Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(), + _ => Ident::default(), } } @@ -1506,12 +1566,12 @@ node! { impl SetRule { /// The function to set style properties for. pub fn target(&self) -> Expr { - self.0.cast_first_match().expect("set rule is missing target") + self.0.cast_first_match().unwrap_or_default() } /// The style properties to set. pub fn args(&self) -> Args { - self.0.cast_last_match().expect("set rule is missing argument list") + self.0.cast_last_match().unwrap_or_default() } /// A condition under which the set rule applies. @@ -1540,7 +1600,7 @@ impl ShowRule { /// The transformation recipe. pub fn transform(&self) -> Expr { - self.0.cast_last_match().expect("show rule is missing transform") + self.0.cast_last_match().unwrap_or_default() } } @@ -1552,7 +1612,7 @@ node! { impl Conditional { /// The condition which selects the body to evaluate. pub fn condition(&self) -> Expr { - self.0.cast_first_match().expect("conditional is missing condition") + self.0.cast_first_match().unwrap_or_default() } /// The expression to evaluate if the condition is true. @@ -1561,7 +1621,7 @@ impl Conditional { .children() .filter_map(SyntaxNode::cast) .nth(1) - .expect("conditional is missing body") + .unwrap_or_default() } /// The expression to evaluate if the condition is false. @@ -1578,12 +1638,12 @@ node! { impl WhileLoop { /// The condition which selects whether to evaluate the body. pub fn condition(&self) -> Expr { - self.0.cast_first_match().expect("while loop is missing condition") + self.0.cast_first_match().unwrap_or_default() } /// The expression to evaluate while the condition is true. pub fn body(&self) -> Expr { - self.0.cast_last_match().expect("while loop is missing body") + self.0.cast_last_match().unwrap_or_default() } } @@ -1595,17 +1655,17 @@ node! { impl ForLoop { /// The pattern to assign to. pub fn pattern(&self) -> ForPattern { - self.0.cast_first_match().expect("for loop is missing pattern") + self.0.cast_first_match().unwrap_or_default() } /// The expression to iterate over. pub fn iter(&self) -> Expr { - self.0.cast_first_match().expect("for loop is missing iterable") + self.0.cast_first_match().unwrap_or_default() } /// The expression to evaluate for each iteration. pub fn body(&self) -> Expr { - self.0.cast_last_match().expect("for loop is missing body") + self.0.cast_last_match().unwrap_or_default() } } @@ -1628,7 +1688,7 @@ impl ForPattern { /// The value part of the pattern. pub fn value(&self) -> Ident { - self.0.cast_last_match().expect("for loop pattern is missing value") + self.0.cast_last_match().unwrap_or_default() } } @@ -1640,7 +1700,7 @@ node! { impl ModuleImport { /// The module or path from which the items should be imported. pub fn source(&self) -> Expr { - self.0.cast_last_match().expect("module import is missing source") + self.0.cast_last_match().unwrap_or_default() } /// The items to be imported. @@ -1673,7 +1733,7 @@ node! { impl ModuleInclude { /// The module or path from which the content should be included. pub fn source(&self) -> Expr { - self.0.cast_last_match().expect("module include is missing path") + self.0.cast_last_match().unwrap_or_default() } } diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 206df9113..34e2fce7e 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -58,19 +58,26 @@ pub enum SyntaxKind { /// Introduces a term item: `/`. TermMarker, /// A mathematical formula: `$x$`, `$ x^2 $`. + Formula, + + /// Mathematical markup. Math, /// An atom in math: `x`, `+`, `12`. - Atom, + MathAtom, + /// An identifier in math: `pi`. + MathIdent, + /// An alignment point in math: `&`. + MathAlignPoint, /// A subsection in a math formula that is surrounded by matched delimiters: /// `[x + y]`. - Delimited, + MathDelimited, /// A base with optional sub- and superscripts in math: `a_1^2`. - Script, + MathScript, /// A fraction in math: `x/2`. - Frac, - /// An alignment point in math: `&`. - AlignPoint, + MathFrac, + /// A hashtag that switches into code mode: `#`. + Hashtag, /// A left curly brace, starting a code block: `{`. LeftBrace, /// A right curly brace, terminating a code block: `}`. @@ -175,6 +182,8 @@ pub enum SyntaxKind { /// The `as` keyword. As, + /// Code. + Code, /// An identifier: `it`. Ident, /// A boolean: `true`, `false`. @@ -338,12 +347,15 @@ impl SyntaxKind { Self::EnumMarker => "enum marker", Self::TermItem => "term list item", Self::TermMarker => "term marker", - Self::Math => "math formula", - Self::Delimited => "delimited math", - Self::Atom => "math atom", - Self::Script => "script", - Self::Frac => "fraction", - Self::AlignPoint => "alignment point", + Self::Formula => "math formula", + Self::Math => "math", + Self::MathIdent => "math identifier", + Self::MathAtom => "math atom", + Self::MathAlignPoint => "math alignment point", + Self::MathDelimited => "delimited math", + Self::MathScript => "math script", + Self::MathFrac => "math fraction", + Self::Hashtag => "hashtag", Self::LeftBrace => "opening brace", Self::RightBrace => "closing brace", Self::LeftBracket => "opening bracket", @@ -394,6 +406,7 @@ impl SyntaxKind { Self::Import => "keyword `import`", Self::Include => "keyword `include`", Self::As => "keyword `as`", + Self::Code => "code", Self::Ident => "identifier", Self::Bool => "boolean", Self::Int => "integer", diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index 1064939de..0735270b5 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -376,7 +376,7 @@ impl Lexer<'_> { Some('-') if !s.at(['-', '?']) => {} Some('.') if !s.at("..") => {} Some('h') if !s.at("ttp://") && !s.at("ttps://") => {} - Some('@' | '#') if !s.at(is_id_start) => {} + Some('@') if !s.at(is_id_start) => {} _ => break, } @@ -410,15 +410,8 @@ impl Lexer<'_> { '\\' => self.backslash(), ':' if self.s.at(is_id_start) => self.maybe_symbol(), '"' => self.string(), - '#' if self.s.eat_if('{') => SyntaxKind::LeftBrace, - '#' if self.s.eat_if('[') => SyntaxKind::LeftBracket, - '#' if self.s.at(is_id_start) => { - match keyword(self.s.eat_while(is_id_continue)) { - Some(keyword) => keyword, - None => SyntaxKind::Ident, - } - } + '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, '|' if self.s.eat_if("->") => SyntaxKind::Shorthand, '<' if self.s.eat_if("->") => SyntaxKind::Shorthand, '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, @@ -429,13 +422,17 @@ impl Lexer<'_> { '-' if self.s.eat_if('>') => SyntaxKind::Shorthand, '=' if self.s.eat_if('>') => SyntaxKind::Shorthand, ':' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, + '[' if self.s.eat_if('|') => SyntaxKind::Shorthand, + '|' if self.s.eat_if(']') => SyntaxKind::Shorthand, + '|' if self.s.eat_if('|') => SyntaxKind::Shorthand, + '*' => SyntaxKind::Shorthand, + '#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag, '_' => SyntaxKind::Underscore, '$' => SyntaxKind::Dollar, '/' => SyntaxKind::Slash, '^' => SyntaxKind::Hat, - '&' => SyntaxKind::AlignPoint, + '&' => SyntaxKind::MathAlignPoint, // Identifiers and symbol notation. c if is_math_id_start(c) && self.s.at(is_math_id_continue) => { @@ -479,7 +476,7 @@ impl Lexer<'_> { .map_or(0, str::len); self.s.jump(start + len); } - SyntaxKind::Atom + SyntaxKind::MathAtom } } diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 038d13bcb..d133fc5d6 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -95,6 +95,11 @@ impl SyntaxNode { } } + /// Whether the node can be cast to the given AST node. + pub fn is(&self) -> bool { + self.cast::().is_some() + } + /// Try to convert the node to a typed AST node. pub fn cast(&self) -> Option { 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) { let len = self.len(); @@ -695,6 +710,14 @@ impl<'a> LinkedNode<'a> { Some(next) } } + + /// Whether an error follows directly after the node. + pub fn before_error(&self) -> bool { + let Some(parent) = self.parent() else { return false }; + let Some(index) = self.index.checked_add(1) else { return false }; + let Some(node) = parent.node.children().nth(index) else { return false }; + node.kind().is_error() + } } /// Access to leafs. @@ -865,8 +888,8 @@ mod tests { // Go back to "#set". Skips the space. let prev = node.prev_sibling().unwrap(); - assert_eq!(prev.offset(), 0); - assert_eq!(prev.text(), "#set"); + assert_eq!(prev.offset(), 1); + assert_eq!(prev.text(), "set"); } #[test] @@ -875,7 +898,7 @@ mod tests { let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap(); let prev = leaf.prev_leaf().unwrap(); assert_eq!(leaf.text(), "fun"); - assert_eq!(prev.text(), "#set"); + assert_eq!(prev.text(), "set"); let source = Source::detached("#let x = 10"); let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap(); diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 07f533728..a046b6858 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -18,9 +18,7 @@ pub fn parse(text: &str) -> SyntaxNode { /// This is only used for syntax highlighting. pub fn parse_code(text: &str) -> SyntaxNode { let mut p = Parser::new(text, 0, LexMode::Code); - let m = p.marker(); code(&mut p, |_| false); - p.wrap(m, SyntaxKind::CodeBlock); p.finish().into_iter().next().unwrap() } @@ -31,7 +29,15 @@ fn markup( mut stop: impl FnMut(SyntaxKind) -> bool, ) { let m = p.marker(); - while !p.eof() && !stop(p.current) { + let mut nesting: usize = 0; + while !p.eof() { + match p.current() { + SyntaxKind::LeftBracket => nesting += 1, + SyntaxKind::RightBracket if nesting > 0 => nesting -= 1, + _ if stop(p.current) => break, + _ => {} + } + if p.newline() { at_start = true; if min_indent > 0 && p.column(p.current_end()) < min_indent { @@ -54,10 +60,18 @@ pub(super) fn reparse_markup( text: &str, range: Range, at_start: &mut bool, + nesting: &mut usize, mut stop: impl FnMut(SyntaxKind) -> bool, ) -> Option> { 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 { let mut chars = text.chars(); chars @@ -321,20 +351,20 @@ fn math_class(text: &str) -> Option { fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> { match kind { SyntaxKind::Underscore => { - Some((SyntaxKind::Script, SyntaxKind::Hat, ast::Assoc::Right, 2)) + Some((SyntaxKind::MathScript, SyntaxKind::Hat, ast::Assoc::Right, 2)) } SyntaxKind::Hat => { - Some((SyntaxKind::Script, SyntaxKind::Underscore, ast::Assoc::Right, 2)) + Some((SyntaxKind::MathScript, SyntaxKind::Underscore, ast::Assoc::Right, 2)) } SyntaxKind::Slash => { - Some((SyntaxKind::Frac, SyntaxKind::Eof, ast::Assoc::Left, 1)) + Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1)) } _ => None, } } fn math_args(p: &mut Parser) { - p.expect(SyntaxKind::Atom); + p.assert(SyntaxKind::MathAtom); let m = p.marker(); let mut m2 = p.marker(); while !p.eof() && !p.at(SyntaxKind::Dollar) { @@ -359,10 +389,11 @@ fn math_args(p: &mut Parser) { p.wrap(m2, SyntaxKind::Math); } p.wrap(m, SyntaxKind::Args); - p.expect(SyntaxKind::Atom); + p.expect(SyntaxKind::MathAtom); } fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { + let m = p.marker(); while !p.eof() && !stop(p.current()) { p.stop_at_newline(true); let prev = p.prev_end(); @@ -379,6 +410,7 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { p.unexpected(); } } + p.wrap(m, SyntaxKind::Code); } fn code_expr(p: &mut Parser) { @@ -386,6 +418,10 @@ fn code_expr(p: &mut Parser) { } fn embedded_code_expr(p: &mut Parser) { + p.stop_at_newline(true); + p.enter(LexMode::Code); + p.assert(SyntaxKind::Hashtag); + let stmt = matches!( p.current(), SyntaxKind::Let @@ -395,13 +431,12 @@ fn embedded_code_expr(p: &mut Parser) { | SyntaxKind::Include ); - p.stop_at_newline(true); - p.enter(LexMode::Code); code_expr_prec(p, true, 0); let semi = p.eat_if(SyntaxKind::Semicolon); if stmt && !semi && !p.eof() && !p.at(SyntaxKind::RightBracket) { p.expected("semicolon or line break"); } + p.exit(); p.unstop(); } @@ -424,7 +459,10 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) { continue; } - if atomic { + let at_field_or_method = + p.directly_at(SyntaxKind::Dot) && p.lexer.clone().next() == SyntaxKind::Ident; + + if atomic && !at_field_or_method { break; } @@ -480,7 +518,7 @@ fn code_primary(p: &mut Parser, atomic: bool) { p.eat(); if !atomic && p.at(SyntaxKind::Arrow) { p.wrap(m, SyntaxKind::Params); - p.expect(SyntaxKind::Arrow); + p.assert(SyntaxKind::Arrow); code_expr(p); p.wrap(m, SyntaxKind::Closure); } @@ -489,7 +527,7 @@ fn code_primary(p: &mut Parser, atomic: bool) { SyntaxKind::LeftBrace => code_block(p), SyntaxKind::LeftBracket => content_block(p), SyntaxKind::LeftParen => with_paren(p), - SyntaxKind::Dollar => equation(p), + SyntaxKind::Dollar => formula(p), SyntaxKind::Let => let_binding(p), SyntaxKind::Set => set_rule(p), SyntaxKind::Show => show_rule(p), @@ -536,7 +574,7 @@ fn code_block(p: &mut Parser) { let m = p.marker(); p.enter(LexMode::Code); p.stop_at_newline(false); - p.expect(SyntaxKind::LeftBrace); + p.assert(SyntaxKind::LeftBrace); code(p, |kind| kind == SyntaxKind::RightBrace); p.expect(SyntaxKind::RightBrace); p.exit(); @@ -547,7 +585,7 @@ fn code_block(p: &mut Parser) { fn content_block(p: &mut Parser) { let m = p.marker(); p.enter(LexMode::Markup); - p.expect(SyntaxKind::LeftBracket); + p.assert(SyntaxKind::LeftBracket); markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket); p.expect(SyntaxKind::RightBracket); p.exit(); @@ -560,7 +598,7 @@ fn with_paren(p: &mut Parser) { if p.at(SyntaxKind::Arrow) { validate_params(p, m); p.wrap(m, SyntaxKind::Params); - p.expect(SyntaxKind::Arrow); + p.assert(SyntaxKind::Arrow); code_expr(p); kind = SyntaxKind::Closure; } @@ -574,7 +612,7 @@ fn with_paren(p: &mut Parser) { fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { p.stop_at_newline(false); - p.expect(SyntaxKind::LeftParen); + p.assert(SyntaxKind::LeftParen); let mut count = 0; let mut parenthesized = true; diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs index 4c01e4ccc..de845abf1 100644 --- a/src/syntax/reparser.rs +++ b/src/syntax/reparser.rs @@ -114,20 +114,30 @@ fn try_reparse( end += 1; } - // Synthesize what `at_start` would be at the start of the reparse. + // Also take hashtag. + if start > 0 && children[start - 1].kind() == SyntaxKind::Hashtag { + start -= 1; + } + + // Synthesize what `at_start` and `nesting` would be at the start of the + // reparse. let mut prefix_len = 0; + let mut nesting = 0; let mut at_start = true; for child in &children[..start] { prefix_len += child.len(); next_at_start(child, &mut at_start); + next_nesting(child, &mut nesting); } // Determine what `at_start` will have to be at the end of the reparse. let mut prev_len = 0; let mut prev_at_start_after = at_start; + let mut prev_nesting_after = nesting; for child in &children[start..end] { prev_len += child.len(); next_at_start(child, &mut prev_at_start_after); + next_nesting(child, &mut prev_nesting_after); } let shifted = offset + prefix_len; @@ -139,11 +149,11 @@ fn try_reparse( }; if let Some(newborns) = - reparse_markup(text, new_range.clone(), &mut at_start, |kind| { + reparse_markup(text, new_range.clone(), &mut at_start, &mut nesting, |kind| { kind == stop_kind }) { - if at_start == prev_at_start_after { + if at_start == prev_at_start_after && nesting == prev_nesting_after { return node .replace_children(start..end, newborns) .is_ok() @@ -188,6 +198,17 @@ fn next_at_start(node: &SyntaxNode, at_start: &mut bool) { } } +/// Update `nesting` based on the node. +fn next_nesting(node: &SyntaxNode, nesting: &mut usize) { + if node.kind() == SyntaxKind::Text { + match node.text().as_str() { + "[" => *nesting += 1, + "]" if *nesting > 0 => *nesting -= 1, + _ => {} + } + } +} + #[cfg(test)] mod tests { use std::ops::Range; @@ -209,9 +230,13 @@ mod tests { panic!("test failed"); } if incremental { - assert_ne!(source.len_bytes(), range.len()); + assert_ne!(source.len_bytes(), range.len(), "should have been incremental"); } else { - assert_eq!(source.len_bytes(), range.len()); + assert_eq!( + source.len_bytes(), + range.len(), + "shouldn't have been incremental" + ); } } @@ -220,6 +245,7 @@ mod tests { test("abc~def~ghi", 5..6, "+", true); test("~~~~~~~", 3..4, "A", true); test("abc~~", 1..2, "", true); + test("#var. hello", 5..6, " ", false); test("#var;hello", 9..10, "a", false); test("https:/world", 7..7, "/", false); test("hello world", 7..12, "walkers", false); @@ -228,8 +254,8 @@ mod tests { test("a d e", 1..3, " b c d", false); test("~*~*~", 2..2, "*", false); test("::1\n2. a\n3", 7..7, "4", true); - test("* {1+2} *", 5..6, "3", true); - test("{(0, 1, 2)}", 5..6, "11pt", false); + test("* #{1+2} *", 6..7, "3", true); + test("#{(0, 1, 2)}", 6..7, "11pt", true); test("\n= A heading", 4..4, "n evocative", false); test("#call() abc~d", 7..7, "[]", true); test("a your thing a", 6..7, "a", false); @@ -239,24 +265,26 @@ mod tests { test("#for", 4..4, "//", false); test("a\n#let \nb", 7..7, "i", true); test("#let x = (1, 2 + ;~ Five\r\n\r", 20..23, "2.", true); - test(r"{{let x = z}; a = 1} b", 6..6, "//", false); + test(r"#{{let x = z}; a = 1} b", 7..7, "//", false); test(r#"a ```typst hello```"#, 16..17, "", false); } #[test] fn test_reparse_block() { - test("Hello { x + 1 }!", 8..9, "abc", true); - test("A{}!", 2..2, "\"", false); - test("{ [= x] }!", 4..4, "=", true); - test("[[]]", 2..2, "\\", false); - test("[[ab]]", 3..4, "\\", false); - test("{}}", 1..1, "{", false); - test("A: [BC]", 5..5, "{", false); - test("A: [BC]", 5..5, "{}", true); - test("{\"ab\"}A", 4..4, "c", true); - test("{\"ab\"}A", 4..5, "c", false); - test("a[]b", 2..2, "{", false); - test("a{call(); abc}b", 7..7, "[]", true); + test("Hello #{ x + 1 }!", 9..10, "abc", true); + test("A#{}!", 3..3, "\"", false); + test("#{ [= x] }!", 5..5, "=", true); + test("#[[]]", 3..3, "\\", false); + test("#[[ab]]", 4..5, "\\", false); + test("#{}}", 2..2, "{", false); + test("A: #[BC]", 6..6, "{", true); + test("A: #[BC]", 6..6, "#{", false); + test("A: #[BC]", 6..6, "#{}", true); + test("#{\"ab\"}A", 5..5, "c", true); + test("#{\"ab\"}A", 5..6, "c", false); + test("a#[]b", 3..3, "#{", false); + test("a#{call(); abc}b", 8..8, "[]", true); test("a #while x {\n g(x) \n} b", 12..12, "//", true); + test("a#[]b", 3..3, "[hey]", true); } } diff --git a/tests/typ/basics/heading.typ b/tests/typ/basics/heading.typ index 4e9550b97..d843a2e82 100644 --- a/tests/typ/basics/heading.typ +++ b/tests/typ/basics/heading.typ @@ -21,7 +21,7 @@ No heading // Parsed as headings if at start of the context. /**/ = Level 1 -{[== Level 2]} +#{[== Level 2]} #box[=== Level 3] // Not at the start of the context. @@ -33,7 +33,7 @@ No = heading --- // Blocks can continue the heading. -= [This += #[This is multiline. ] diff --git a/tests/typ/basics/list.typ b/tests/typ/basics/list.typ index b8bd59eab..1c111dcb8 100644 --- a/tests/typ/basics/list.typ +++ b/tests/typ/basics/list.typ @@ -24,7 +24,7 @@ _Shopping list_ --- - Level 1 - - Level [ + - Level #[ 2 through content block ] diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ index e01f88966..3d4d6106c 100644 --- a/tests/typ/compiler/array.typ +++ b/tests/typ/compiler/array.typ @@ -7,19 +7,19 @@ #set page(width: 150pt) // Empty. -{()} +#{()} // Not an array, just a parenthesized expression. -{(1)} +#{(1)} // One item and trailing comma. -{(-1,)} +#{(-1,)} // No trailing comma. -{(true, false)} +#{(true, false)} // Multiple lines and items and trailing comma. -{("1" +#{("1" , rgb("002") ,)} @@ -30,7 +30,7 @@ --- // Test lvalue and rvalue access. -{ +#{ let array = (1, 2) array.at(1) += 5 + array.at(0) test(array, (1, 8)) @@ -38,7 +38,7 @@ --- // Test different lvalue method. -{ +#{ let array = (1, 2, 3) array.first() = 7 array.at(1) *= 8 @@ -47,12 +47,12 @@ --- // Test rvalue out of bounds. -// Error: 2-17 array index out of bounds (index: 5, len: 3) -{(1, 2, 3).at(5)} +// Error: 3-18 array index out of bounds (index: 5, len: 3) +#{(1, 2, 3).at(5)} --- // Test lvalue out of bounds. -{ +#{ let array = (1, 2, 3) // Error: 3-14 array index out of bounds (index: 3, len: 3) array.at(3) = 5 @@ -60,19 +60,19 @@ --- // Test bad lvalue. -// Error: 2:3-2:14 cannot mutate a temporary value +// Error: 2:4-2:15 cannot mutate a temporary value #let array = (1, 2, 3) -{ array.len() = 4 } +#{ array.len() = 4 } --- // Test bad lvalue. -// Error: 2:3-2:15 type array has no method `yolo` +// Error: 2:4-2:16 type array has no method `yolo` #let array = (1, 2, 3) -{ array.yolo() = 4 } +#{ array.yolo() = 4 } --- // Test negative indices. -{ +#{ let array = (1, 2, 3, 4) test(array.at(0), 1) test(array.at(-1), 4) @@ -89,16 +89,16 @@ #test((1, 2, 3).last(), 3) --- -// Error: 3-13 array is empty -{ ().first() } +// Error: 4-14 array is empty +#{ ().first() } --- -// Error: 3-12 array is empty -{ ().last() } +// Error: 4-13 array is empty +#{ ().last() } --- // Test the `push` and `pop` methods. -{ +#{ let tasks = (a: (1, 2, 3), b: (4, 5, 6)) tasks.at("a").pop() tasks.b.push(7) @@ -108,7 +108,7 @@ --- // Test the `insert` and `remove` methods. -{ +#{ let array = (0, 1, 2, 4, 5) array.insert(3, 3) test(array, range(6)) @@ -117,9 +117,10 @@ } --- -// Error: 2:17-2:19 missing argument: index +// Error: 2:18-2:20 missing argument: index #let numbers = () -{ numbers.insert() } +#{ numbers.insert() } + --- // Test the `slice` method. #test((1, 2, 3, 4).slice(2), (3, 4)) @@ -132,12 +133,12 @@ #test("ABCD".split("").slice(1, -1).join("-"), "A-B-C-D") --- -// Error: 3-31 array index out of bounds (index: 12, len: 10) -{ range(10).slice(9, count: 3) } +// Error: 4-32 array index out of bounds (index: 12, len: 10) +#{ range(10).slice(9, count: 3) } --- -// Error: 3-25 array index out of bounds (index: -4, len: 3) -{ (1, 2, 3).slice(0, -4) } +// Error: 4-26 array index out of bounds (index: -4, len: 3) +#{ (1, 2, 3).slice(0, -4) } --- // Test the `position` method. @@ -162,8 +163,8 @@ #test((1, 2, 3, 4).fold(0, (s, x) => s + x), 10) --- -// Error: 21-31 function must have exactly two parameters -{ (1, 2, 3).fold(0, () => none) } +// Error: 22-32 function must have exactly two parameters +#{ (1, 2, 3).fold(0, () => none) } --- // Test the `rev` method. @@ -177,17 +178,17 @@ #test("(" + ("a", "b", "c").join(", ") + ")", "(a, b, c)") --- -// Error: 2-22 cannot join boolean with boolean -{(true, false).join()} +// Error: 3-23 cannot join boolean with boolean +#{(true, false).join()} --- -// Error: 2-20 cannot join string with integer -{("a", "b").join(1)} +// Error: 3-21 cannot join string with integer +#{("a", "b").join(1)} --- // Test joining content. // Ref: true -{([One], [Two], [Three]).join([, ], last: [ and ])}. +#{([One], [Two], [Three]).join([, ], last: [ and ])}. --- // Test the `sorted` method. @@ -197,37 +198,37 @@ #test((2, 1, 3, 10, 5, 8, 6, -7, 2).sorted(), (-7, 1, 2, 2, 3, 5, 6, 8, 10)) --- -// Error: 2-26 cannot order content and content -{([Hi], [There]).sorted()} +// Error: 3-27 cannot order content and content +#{([Hi], [There]).sorted()} --- -// Error: 2-18 array index out of bounds (index: -4, len: 3) -{(1, 2, 3).at(-4)} +// Error: 3-19 array index out of bounds (index: -4, len: 3) +#{(1, 2, 3).at(-4)} --- -// Error: 3 expected closing paren -{(} +// Error: 4 expected closing paren +#{(} -// Error: 2-3 unexpected closing paren -{)} +// Error: 3-4 unexpected closing paren +#{)} -// Error: 4-6 unexpected end of block comment -{(1*/2)} +// Error: 5-7 unexpected end of block comment +#{(1*/2)} -// Error: 6-8 invalid number suffix -{(1, 1u 2)} +// Error: 7-9 invalid number suffix +#{(1, 1u 2)} -// Error: 3-4 unexpected comma -{(,1)} +// Error: 4-5 unexpected comma +#{(,1)} // Missing expression makes named pair incomplete, making this an empty array. -// Error: 5 expected expression -{(a:)} +// Error: 6 expected expression +#{(a:)} // Named pair after this is already identified as an array. -// Error: 6-10 expected expression, found named pair -{(1, b: 2)} +// Error: 7-11 expected expression, found named pair +#{(1, b: 2)} // Keyed pair after this is already identified as an array. -// Error: 6-14 expected expression, found keyed pair -{(1, "key": 2)} +// Error: 7-15 expected expression, found keyed pair +#{(1, "key": 2)} diff --git a/tests/typ/compiler/bench.typ b/tests/typ/compiler/bench.typ index 6aff1ac2f..227959663 100644 --- a/tests/typ/compiler/bench.typ +++ b/tests/typ/compiler/bench.typ @@ -7,7 +7,7 @@ // ... but also "content" values. While these contain markup, // they are also values and can be summed, stored in arrays etc. // There are also more standard control flow structures, like #if and #for. -#let university = [*Technische Universität {city}*] +#let university = [*Technische Universität #{city}*] #let faculty = [*Fakultät II, Institut for Mathematik*] // The `box` function just places content into a rectangular container. When diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ index 7fb7738b2..204cab1ec 100644 --- a/tests/typ/compiler/block.typ +++ b/tests/typ/compiler/block.typ @@ -5,14 +5,14 @@ // Ref: true // Evaluates to join of none, [My ] and the two loop bodies. -{ +#{ let parts = ("my fri", "end.") [Hello, ] - for s in parts [{s}] + for s in parts [#s] } // Evaluates to join of the content and strings. -{ +#{ [How] if true { " are" @@ -50,7 +50,7 @@ --- // Some things can't be joined. -{ +#{ [A] // Error: 3-4 cannot join content with integer 1 @@ -59,7 +59,7 @@ --- // Block directly in markup also creates a scope. -{ let x = 1 } +#{ let x = 1 } // Error: 7-8 unknown variable #test(x, 1) @@ -73,22 +73,22 @@ #test(a, 1) -// Error: 2-3 unknown variable -{b} +// Error: 3-4 unknown variable +#{b} --- // Double block creates a scope. -{{ +#{{ import "module.typ": b test(b, 1) }} -// Error: 2-3 unknown variable -{b} +// Error: 3-4 unknown variable +#{b} --- // Multiple nested scopes. -{ +#{ let a = "a1" { let a = "a2" @@ -104,28 +104,28 @@ --- // Content blocks also create a scope. -[#let x = 1] +#[#let x = 1] -// Error: 2-3 unknown variable -{x} +// Error: 3-4 unknown variable +#{x} --- // Multiple unseparated expressions in one line. -// Error: 2-4 invalid number suffix -{1u} +// Error: 3-5 invalid number suffix +#{1u} // Should output `1`. -// Error: 3 expected semicolon or line break -{1 2} +// Error: 4 expected semicolon or line break +#{1 2} // Should output `2`. -// Error: 12 expected semicolon or line break -// Error: 22 expected semicolon or line break -{let x = -1 let y = 3 x + y} +// Error: 13 expected semicolon or line break +// Error: 23 expected semicolon or line break +#{let x = -1 let y = 3 x + y} // Should output `3`. -{ +#{ // Error: 6 expected identifier // Error: 10 expected block for "v" @@ -138,9 +138,9 @@ } --- -// Error: 2 expected closing brace -{ +// Error: 3 expected closing brace +#{ --- -// Error: 1-2 unexpected closing brace -} +// Error: 2 expected expression +#} diff --git a/tests/typ/compiler/break-continue.typ b/tests/typ/compiler/break-continue.typ index fb3222fa2..566234a7a 100644 --- a/tests/typ/compiler/break-continue.typ +++ b/tests/typ/compiler/break-continue.typ @@ -96,7 +96,7 @@ #let x = { continue } --- -// Error: 1-10 cannot continue outside of loop +// Error: 2-10 cannot continue outside of loop #continue --- @@ -104,7 +104,7 @@ // Should output `Hello World 🌎`. #for _ in range(10) { [Hello ] - [World { + [World #{ [🌎] break }] diff --git a/tests/typ/compiler/call.typ b/tests/typ/compiler/call.typ index 087e46941..690d55d31 100644 --- a/tests/typ/compiler/call.typ +++ b/tests/typ/compiler/call.typ @@ -6,7 +6,7 @@ // Ommitted space. #let f() = {} -[#f()*Bold*] +#[#f()*Bold*] // Call return value of function with body. #let f(x, body) = (y) => [#x] + body + [#y] @@ -34,7 +34,7 @@ #test(alias(alias), "function") // Callee expressions. -{ +#{ // Wrapped in parens. test((type)("hi"), "string") @@ -48,25 +48,25 @@ #set text(family: "Arial", family: "Helvetica") --- -// Error: 2-6 expected function, found boolean -{true()} +// Error: 3-7 expected function, found boolean +#{true()} --- #let x = "x" -// Error: 1-3 expected function, found string +// Error: 2-3 expected function, found string #x() --- #let f(x) = x -// Error: 1-6 expected function, found integer +// Error: 2-6 expected function, found integer #f(1)(2) --- #let f(x) = x -// Error: 1-6 expected function, found content +// Error: 2-6 expected function, found content #f[1](2) --- @@ -90,16 +90,16 @@ // Error: 7-12 expected identifier, found string #func("abc": 2) -// Error: 7-10 expected identifier, found group -{func((x):1)} +// Error: 8-11 expected identifier, found group +#{func((x):1)} --- // Error: 2:1 expected closing bracket #func[`a]` --- -// Error: 7 expected closing paren -{func(} +// Error: 8 expected closing paren +#{func(} --- // Error: 2:1 expected quote diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ index f1604b19d..1551a66dd 100644 --- a/tests/typ/compiler/closure.typ +++ b/tests/typ/compiler/closure.typ @@ -12,7 +12,7 @@ --- // Basic closure without captures. -{ +#{ let adder = (x, y) => x + y test(adder(2, 3), 5) } @@ -20,7 +20,7 @@ --- // Pass closure as argument and return closure. // Also uses shorthand syntax for a single argument. -{ +#{ let chain = (f, g) => (x) => f(g(x)) let f = x => x + 1 let g = x => 2 * x @@ -30,7 +30,7 @@ --- // Capture environment. -{ +#{ let mark = "!" let greet = { let hi = "Hi" @@ -48,7 +48,7 @@ --- // Redefined variable. -{ +#{ let x = 1 let f() = { let x = x + 2 @@ -59,7 +59,7 @@ --- // Import bindings. -{ +#{ let b = "module.typ" let f() = { import b: b @@ -70,7 +70,7 @@ --- // For loop bindings. -{ +#{ let v = (1, 2, 3) let f() = { let s = 0 @@ -82,7 +82,7 @@ --- // Let + closure bindings. -{ +#{ let g = "hi" let f() = { let g() = "bye" @@ -93,7 +93,7 @@ --- // Parameter bindings. -{ +#{ let x = 5 let g() = { let f(x, y: x) = x + y @@ -105,7 +105,7 @@ --- // Don't leak environment. -{ +#{ // Error: 16-17 unknown variable let func() = x let x = "hi" @@ -114,7 +114,7 @@ --- // Too few arguments. -{ +#{ let types(x, y) = "[" + type(x) + ", " + type(y) + "]" test(types(14%, 12pt), "[ratio, length]") @@ -124,7 +124,7 @@ --- // Too many arguments. -{ +#{ let f(x) = x + 1 // Error: 8-13 unexpected argument @@ -133,7 +133,7 @@ --- // Named arguments. -{ +#{ let greet(name, birthday: false) = { if birthday { "Happy Birthday, " } else { "Hey, " } + name + "!" } @@ -156,8 +156,8 @@ #let f(a, b, a: none, b: none, c, b) = none --- -// Error: 6-16 expected identifier, named pair or argument sink, found keyed pair -{(a, "named": b) => none} +// Error: 7-17 expected identifier, named pair or argument sink, found keyed pair +#{(a, "named": b) => none} --- // Error: 10-15 expected identifier, found string diff --git a/tests/typ/compiler/construct.typ b/tests/typ/compiler/construct.typ index 3ed8fed31..161827cb4 100644 --- a/tests/typ/compiler/construct.typ +++ b/tests/typ/compiler/construct.typ @@ -18,7 +18,7 @@ --- // The inner rectangle should also be yellow here. // (and therefore invisible) -[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))] +#[#set rect(fill: yellow);#text(1em, rect(inset: 5pt, rect()))] --- // The inner rectangle should not be yellow here. diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ index 5f00ef3ae..c34b54786 100644 --- a/tests/typ/compiler/dict.typ +++ b/tests/typ/compiler/dict.typ @@ -5,7 +5,7 @@ // Ref: true // Empty -{(:)} +#{(:)} // Two pairs and string key. #let dict = (normal: 1, "spacy key": 2) @@ -16,7 +16,7 @@ --- // Test lvalue and rvalue access. -{ +#{ let dict = (a: 1, "b b": 1) dict.at("b b") += 1 dict.state = (ok: true, err: false) @@ -29,7 +29,7 @@ --- // Test rvalue missing key. -{ +#{ let dict = (a: 1, b: 2) // Error: 11-23 dictionary does not contain key "c" let x = dict.at("c") @@ -37,7 +37,7 @@ --- // Missing lvalue is not automatically none-initialized. -{ +#{ let dict = (:) // Error: 3-9 dictionary does not contain key "b" dict.b += 1 @@ -51,39 +51,39 @@ #test(dict.values(), (3, 1, 2)) #test(dict.pairs((k, v) => k + str(v)).join(), "a3b1c2") -{ dict.remove("c") } +#{ dict.remove("c") } #test("c" in dict, false) #test(dict, (a: 3, b: 1)) --- -// Error: 24-29 duplicate key -{(first: 1, second: 2, first: 3)} +// Error: 25-30 duplicate key +#{(first: 1, second: 2, first: 3)} --- -// Error: 17-20 duplicate key -{(a: 1, "b": 2, "a": 3)} +// Error: 18-21 duplicate key +#{(a: 1, "b": 2, "a": 3)} --- // Simple expression after already being identified as a dictionary. -// Error: 9-10 expected named or keyed pair, found identifier -{(a: 1, b)} +// Error: 10-11 expected named or keyed pair, found identifier +#{(a: 1, b)} // Identified as dictionary due to initial colon. -// Error: 4-5 expected named or keyed pair, found integer -// Error: 5 expected comma -// Error: 12-16 expected identifier or string, found boolean -// Error: 17 expected expression -{(:1 b:"", true:)} +// Error: 5-6 expected named or keyed pair, found integer +// Error: 6 expected comma +// Error: 13-17 expected identifier or string, found boolean +// Error: 18 expected expression +#{(:1 b:"", true:)} -// Error: 3-8 expected identifier or string, found binary expression -{(a + b: "hey")} +// Error: 4-9 expected identifier or string, found binary expression +#{(a + b: "hey")} --- -// Error: 3-15 cannot mutate a temporary value -{ (key: "val").other = "some" } +// Error: 4-16 cannot mutate a temporary value +#{ (key: "val").other = "some" } --- -{ +#{ let object = none // Error: 3-9 expected dictionary, found none object.property = "value" diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ index 0195c6d8d..c99d910d3 100644 --- a/tests/typ/compiler/field.typ +++ b/tests/typ/compiler/field.typ @@ -5,7 +5,7 @@ // Test field on dictionary. #let dict = (nothing: "ness", hello: "world") #test(dict.nothing, "ness") -{ +#{ let world = dict .hello @@ -23,12 +23,12 @@ - C --- -// Error: 6-13 dictionary does not contain key "invalid" -{(:).invalid} +// Error: 7-14 dictionary does not contain key "invalid" +#{(:).invalid} --- -// Error: 2-7 expected dictionary or content, found boolean -{false.ok} +// Error: 9-11 cannot access fields on type boolean +#{false.ok} --- // Error: 29-32 unknown field `fun` @@ -36,6 +36,6 @@ = A --- -// Error: 8 expected identifier -// Error: 8 expected semicolon or line break -{false.true} +// Error: 9 expected identifier +// Error: 9 expected semicolon or line break +#{false.true} diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ index 7a530b73a..cf7174111 100644 --- a/tests/typ/compiler/for.typ +++ b/tests/typ/compiler/for.typ @@ -10,12 +10,12 @@ // Dictionary is not traversed in insertion order. // Should output `Age: 2. Name: Typst.`. #for k, v in (Name: "Typst", Age: 2) [ - {k}: {v}. + #k: #v. ] // Block body. // Should output `[1st, 2nd, 3rd, 4th]`. -{ +#{ "[" for v in (1, 2, 3, 4) { if v > 1 [, ] @@ -97,8 +97,8 @@ // Error: 5 expected identifier #for// -// Error: 5 expected identifier -{for} +// Error: 6 expected identifier +#{for} // Error: 7 expected keyword `in` #for v diff --git a/tests/typ/compiler/if.typ b/tests/typ/compiler/if.typ index 3b35ebd89..23ed92598 100644 --- a/tests/typ/compiler/if.typ +++ b/tests/typ/compiler/if.typ @@ -40,7 +40,7 @@ } // Content block can be argument or body depending on whitespace. -{ +#{ if "content" == type[b] [Fi] else [Nope] if "content" == type [Nope] else [ve.] } @@ -76,7 +76,7 @@ // Value of if expressions. // Ref: false -{ +#{ let x = 1 let y = 2 let z @@ -109,13 +109,13 @@ // Error: 4 expected expression #if -// Error: 4 expected expression -{if} +// Error: 5 expected expression +#{if} // Error: 6 expected block #if x -// Error: 1-6 unexpected keyword `else` +// Error: 2 expected expression #else {} // Should output `x`. diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ index a657be503..f97b1ae0f 100644 --- a/tests/typ/compiler/import.typ +++ b/tests/typ/compiler/import.typ @@ -49,8 +49,8 @@ #test((module,).at(0).item(1, 2), 3) // Doesn't work because of mutating name. -// Error: 2-11 cannot mutate a temporary value -{(module,).at(0).push()} +// Error: 3-12 cannot mutate a temporary value +#{(module,).at(0).push()} --- // Who needs whitespace anyways? diff --git a/tests/typ/compiler/include.typ b/tests/typ/compiler/include.typ index 289fea217..a655e9f27 100644 --- a/tests/typ/compiler/include.typ +++ b/tests/typ/compiler/include.typ @@ -15,7 +15,7 @@ #chap2 --- -{ +#{ // Error: 19-38 file not found (searched at typ/compiler/modules/chap3.typ) let x = include "modules/chap3.typ" } @@ -24,7 +24,7 @@ #include "modules/chap1.typ" // The variables of the file should not appear in this scope. -// Error: 1-6 unknown variable +// Error: 2-6 unknown variable #name --- diff --git a/tests/typ/compiler/label.typ b/tests/typ/compiler/label.typ index 795c0435a..d4ff15d70 100644 --- a/tests/typ/compiler/label.typ +++ b/tests/typ/compiler/label.typ @@ -26,7 +26,7 @@ The end. it } -This is a thing [that ] happened. +This is a thing #[that ] happened. --- // Test abusing dynamic labels for styling. @@ -50,7 +50,7 @@ _Visible_ --- // Test that label only works within one content block. #show : strike -*This is* [] *protected.* +*This is* #[] *protected.* *This is not.* --- diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ index 3a879ce7a..aa1132b91 100644 --- a/tests/typ/compiler/let.typ +++ b/tests/typ/compiler/let.typ @@ -36,8 +36,8 @@ Three // Error: 5 expected identifier #let -// Error: 5 expected identifier -{let} +// Error: 6 expected identifier +#{let} // Error: 5 expected identifier // Error: 5 expected semicolon or line break diff --git a/tests/typ/compiler/methods.typ b/tests/typ/compiler/methods.typ index f468320b9..ae0945df1 100644 --- a/tests/typ/compiler/methods.typ +++ b/tests/typ/compiler/methods.typ @@ -7,7 +7,7 @@ --- // Test mutating indexed value. -{ +#{ let matrix = (((1,), (2,)), ((3,), (4,))) matrix.at(1).at(0).push(5) test(matrix, (((1,), (2,)), ((3, 5), (4,)))) @@ -15,7 +15,7 @@ --- // Test multiline chain in code block. -{ +#{ let rewritten = "Hello. This is a sentence. And one more." .split(".") .map(s => s.trim()) @@ -27,20 +27,20 @@ } --- -// Error: 2:3-2:16 type array has no method `fun` +// Error: 2:4-2:17 type array has no method `fun` #let numbers = () -{ numbers.fun() } +#{ numbers.fun() } --- -// Error: 2:3-2:44 cannot mutate a temporary value +// Error: 2:4-2:45 cannot mutate a temporary value #let numbers = (1, 2, 3) -{ numbers.map(v => v / 2).sorted().map(str).remove(4) } +#{ numbers.map(v => v / 2).sorted().map(str).remove(4) } --- -// Error: 2:3-2:19 cannot mutate a temporary value +// Error: 2:4-2:20 cannot mutate a temporary value #let numbers = (1, 2, 3) -{ numbers.sorted() = 1 } +#{ numbers.sorted() = 1 } --- -// Error: 3-6 cannot mutate a constant -{ box.push(1) } +// Error: 4-7 cannot mutate a constant +#{ box.push(1) } diff --git a/tests/typ/compiler/ops-invalid.typ b/tests/typ/compiler/ops-invalid.typ index d51e42fb5..3356a24c9 100644 --- a/tests/typ/compiler/ops-invalid.typ +++ b/tests/typ/compiler/ops-invalid.typ @@ -2,8 +2,8 @@ // Ref: false --- -// Error: 3 expected expression -{-} +// Error: 4 expected expression +#{-} --- // Error: 10 expected expression @@ -14,115 +14,115 @@ #test({2*}, 2) --- -// Error: 2-12 cannot apply '+' to content -{+([] + [])} +// Error: 3-13 cannot apply '+' to content +#{+([] + [])} --- -// Error: 2-5 cannot apply '-' to string -{-""} +// Error: 3-6 cannot apply '-' to string +#{-""} --- -// Error: 2-8 cannot apply 'not' to array -{not ()} +// Error: 3-9 cannot apply 'not' to array +#{not ()} --- -// Error: 2-18 cannot apply '<=' to relative length and ratio -{30% + 1pt <= 40%} +// Error: 3-19 cannot apply '<=' to relative length and ratio +#{30% + 1pt <= 40%} --- -// Error: 2-13 cannot apply '<=' to length and length -{1em <= 10pt} +// Error: 3-14 cannot apply '<=' to length and length +#{1em <= 10pt} --- -// Error: 2-11 cannot divide by zero -{1.2 / 0.0} +// Error: 3-12 cannot divide by zero +#{1.2 / 0.0} --- -// Error: 2-7 cannot divide by zero -{1 / 0} +// Error: 3-8 cannot divide by zero +#{1 / 0} --- -// Error: 2-14 cannot divide by zero -{15deg / 0deg} +// Error: 3-15 cannot divide by zero +#{15deg / 0deg} --- // Special messages for +, -, * and /. -// Error: 03-10 cannot add integer and string -{(1 + "2", 40% - 1)} +// Error: 4-11 cannot add integer and string +#{(1 + "2", 40% - 1)} --- -// Error: 14-22 cannot add integer and string -{ let x = 1; x += "2" } +// Error: 15-23 cannot add integer and string +#{ let x = 1; x += "2" } --- -// Error: 3-12 cannot divide ratio by length -{ 10% / 5pt } +// Error: 4-13 cannot divide ratio by length +#{ 10% / 5pt } --- -// Error: 3-12 cannot divide these two lengths -{ 1em / 5pt } +// Error: 4-13 cannot divide these two lengths +#{ 1em / 5pt } --- -// Error: 3-19 cannot divide relative length by ratio -{ (10% + 1pt) / 5% } +// Error: 4-20 cannot divide relative length by ratio +#{ (10% + 1pt) / 5% } --- -// Error: 3-28 cannot divide these two relative lengths -{ (10% + 1pt) / (20% + 1pt) } +// Error: 4-29 cannot divide these two relative lengths +#{ (10% + 1pt) / (20% + 1pt) } --- -// Error: 12-19 cannot subtract integer from ratio -{(1234567, 40% - 1)} +// Error: 13-20 cannot subtract integer from ratio +#{(1234567, 40% - 1)} --- -// Error: 2-10 cannot multiply integer with boolean -{2 * true} +// Error: 3-11 cannot multiply integer with boolean +#{2 * true} --- -// Error: 2-10 cannot divide integer by length -{3 / 12pt} +// Error: 3-11 cannot divide integer by length +#{3 / 12pt} --- -// Error: 2-9 cannot repeat this string -1 times -{-1 * ""} +// Error: 3-10 cannot repeat this string -1 times +#{-1 * ""} --- -{ +#{ let x = 2 for _ in range(61) { (x) *= 2 } - // Error: 4-18 cannot repeat this string 4611686018427387904 times - {x * "abcdefgh"} + // Error: 3-17 cannot repeat this string 4611686018427387904 times + x * "abcdefgh" } --- -// Error: 4-5 unknown variable -{ (x) = "" } +// Error: 5-6 unknown variable +#{ (x) = "" } --- -// Error: 3-8 cannot mutate a temporary value -{ 1 + 2 += 3 } +// Error: 4-9 cannot mutate a temporary value +#{ 1 + 2 += 3 } --- -// Error: 2:2-2:7 cannot apply 'not' to string +// Error: 2:3-2:8 cannot apply 'not' to string #let x = "Hey" -{not x = "a"} +#{not x = "a"} --- -// Error: 7-8 unknown variable -{ 1 + x += 3 } +// Error: 8-9 unknown variable +#{ 1 + x += 3 } --- -// Error: 3-4 unknown variable -{ z = 1 } +// Error: 4-5 unknown variable +#{ z = 1 } --- -// Error: 3-7 cannot mutate a constant -{ rect = "hi" } +// Error: 4-8 cannot mutate a constant +#{ rect = "hi" } --- // Works if we define rect beforehand // (since then it doesn't resolve to the standard library version anymore). #let rect = "" -{ rect = "hi" } +#{ rect = "hi" } diff --git a/tests/typ/compiler/ops-prec.typ b/tests/typ/compiler/ops-prec.typ index eba0c8a97..2fa90382d 100644 --- a/tests/typ/compiler/ops-prec.typ +++ b/tests/typ/compiler/ops-prec.typ @@ -14,9 +14,14 @@ --- // Assignment binds stronger than boolean operations. -// Error: 2:2-2:7 cannot mutate a temporary value +// Error: 2:3-2:8 cannot mutate a temporary value #let x = false -{not x = "a"} +#{not x = "a"} + +--- +// Precedence doesn't matter for chained unary operators. +// Error: 3-12 cannot apply '-' to boolean +#{-not true} --- // Parentheses override precedence. @@ -25,8 +30,3 @@ // Error: 14 expected closing paren #test({(1 + 1}, 2) - ---- -// Precedence doesn't matter for chained unary operators. -// Error: 2-11 cannot apply '-' to boolean -{-not true} diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ index 722a9c8d4..3e72476c1 100644 --- a/tests/typ/compiler/ops.typ +++ b/tests/typ/compiler/ops.typ @@ -4,7 +4,7 @@ --- // Test adding content. // Ref: true -{[*Hello* ] + [world!]} +#{[*Hello* ] + [world!]} --- // Test math operators. @@ -176,17 +176,17 @@ // Test assignment operators. #let x = 0 -{ x = 10 } #test(x, 10) -{ x -= 5 } #test(x, 5) -{ x += 1 } #test(x, 6) -{ x *= x } #test(x, 36) -{ x /= 2.0 } #test(x, 18.0) -{ x = "some" } #test(x, "some") -{ x += "thing" } #test(x, "something") +#{ x = 10 } #test(x, 10) +#{ x -= 5 } #test(x, 5) +#{ x += 1 } #test(x, 6) +#{ x *= x } #test(x, 36) +#{ x /= 2.0 } #test(x, 18.0) +#{ x = "some" } #test(x, "some") +#{ x += "thing" } #test(x, "something") --- -// Error: 3-6 cannot mutate a constant -{ box = 1 } +// Error: 4-7 cannot mutate a constant +#{ box = 1 } --- // Test `in` operator. @@ -204,8 +204,8 @@ /* fun comment? */ in "abc", false) --- -// Error: 9 expected keyword `in` -{"a" not} +// Error: 10 expected keyword `in` +#{"a" not} --- // Test `with` method. diff --git a/tests/typ/compiler/repr.typ b/tests/typ/compiler/repr.typ index bdea9c825..a22fdd490 100644 --- a/tests/typ/compiler/repr.typ +++ b/tests/typ/compiler/repr.typ @@ -2,33 +2,33 @@ --- // Literal values. -{auto} \ -{none} (empty) \ -{true} \ -{false} +#auto \ +#none (empty) \ +#true \ +#false --- // Numerical values. -{1} \ -{1.0e-4} \ -{3.15} \ -{1e-10} \ -{50.368%} \ -{0.0000012345pt} \ -{4.5cm} \ -{12e1pt} \ -{2.5rad} \ -{45deg} \ -{1.7em} \ -{1cm + 0em} \ -{2em + 10pt} \ -{2.3fr} +#1 \ +#1.0e-4 \ +#3.15 \ +#1e-10 \ +#50.368% \ +#0.0000012345pt \ +#4.5cm \ +#12e1pt \ +#2.5rad \ +#45deg \ +#1.7em \ +#{1cm + 0em} \ +#{2em + 10pt} \ +#2.3fr --- // Colors and strokes. #set text(0.8em) #rgb("f7a205") \ -{2pt + rgb("f7a205")} +#{2pt + rgb("f7a205")} --- // Strings and escaping. @@ -43,6 +43,6 @@ // Functions are invisible. Nothing #let f(x) = x -{f} -{rect} -{() => none} +#f +#rect +#{() => none} diff --git a/tests/typ/compiler/set.typ b/tests/typ/compiler/set.typ index 034bfab8a..39c3e613d 100644 --- a/tests/typ/compiler/set.typ +++ b/tests/typ/compiler/set.typ @@ -3,18 +3,18 @@ --- // Test that text is affected by instantiation-site bold. #let x = [World] -Hello *{x}* +Hello *#x* --- // Test that lists are affected by correct indents. #let fruit = [ - Apple - Orange - #list(body-indent: 20pt, [Pear]) + #list(body-indent: 20pt)[Pear] ] - Fruit -[#set list(indent: 10pt) +#[#set list(indent: 10pt) #fruit] - No more fruit @@ -28,7 +28,7 @@ Hello *{x}* --- // Test that scoping works as expected. -{ +#{ if true { set text(blue) [Blue ] @@ -62,5 +62,5 @@ Hello *{x}* #set text(red) if 1 + 2 --- -// Error: 11-25 set is only allowed directly in code and content blocks -{ let x = set text(blue) } +// Error: 12-26 set is only allowed directly in code and content blocks +#{ let x = set text(blue) } diff --git a/tests/typ/compiler/shorthand.typ b/tests/typ/compiler/shorthand.typ index 5c94dab02..02c42ab06 100644 --- a/tests/typ/compiler/shorthand.typ +++ b/tests/typ/compiler/shorthand.typ @@ -17,4 +17,4 @@ a~b --- #set text("Roboto") -A... vs {"A..."} +A... vs #"A..." diff --git a/tests/typ/compiler/show-bare.typ b/tests/typ/compiler/show-bare.typ index 8b8d0852a..2f076adec 100644 --- a/tests/typ/compiler/show-bare.typ +++ b/tests/typ/compiler/show-bare.typ @@ -16,7 +16,7 @@ in booklovers and the great fulfiller of human need. --- // Test bare show in content block. -A [_B #show c => [*#c*]; C_] D +A #[_B #show c => [*#c*]; C_] D --- // Test style precedence. @@ -29,5 +29,5 @@ Forest Ignored --- -// Error: 4-18 show is only allowed directly in code and content blocks -{ (show body => 2) * body } +// Error: 5-19 show is only allowed directly in code and content blocks +#{ (show body => 2) * body } diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ index 46663c683..4aba4e9be 100644 --- a/tests/typ/compiler/show-node.typ +++ b/tests/typ/compiler/show-node.typ @@ -14,7 +14,7 @@ // Test full reset. #show heading: [B] #show heading: set text(size: 10pt, weight: 400) -A [= Heading] C +A #[= Heading] C --- // Test full removal. @@ -58,7 +58,7 @@ Another text. --- // Test that scoping works as expected. -{ +#{ let world = [ World ] show "W": strong world @@ -100,5 +100,5 @@ Another text. #show red: [] --- -// Error: 7-25 show is only allowed directly in code and content blocks -{ 1 + show heading: none } +// Error: 8-26 show is only allowed directly in code and content blocks +#{ 1 + show heading: none } diff --git a/tests/typ/compiler/spread.typ b/tests/typ/compiler/spread.typ index 244e9fb9c..ce3d8cea3 100644 --- a/tests/typ/compiler/spread.typ +++ b/tests/typ/compiler/spread.typ @@ -3,7 +3,7 @@ --- // Test standard argument overriding. -{ +#{ let f(style: "normal", weight: "regular") = { "(style: " + style + ", weight: " + weight + ")" } @@ -16,7 +16,7 @@ --- // Test multiple calls. -{ +#{ let f(b, c: "!") = b + c let g(a, ..sink) = a + f(..sink) test(g("a", "b", c: "c"), "abc") @@ -24,7 +24,7 @@ --- // Test doing things with arguments. -{ +#{ let save(..args) = { test(type(args), "arguments") test(repr(args), "(1, 2, three: true)") @@ -35,14 +35,14 @@ --- // Test spreading array and dictionary. -{ +#{ let more = (3, -3, 6, 10) test(min(1, 2, ..more), -3) test(max(..more, 9), 10) test(max(..more, 11), 11) } -{ +#{ let more = (c: 3, d: 4) let tostr(..args) = repr(args) test(tostr(a: 1, ..more, b: 2), "(a: 1, c: 3, d: 4, b: 2)") @@ -69,14 +69,14 @@ --- // Test spreading into array and dictionary. -{ +#{ let l = (1, 2, 3) let r = (5, 6, 7) test((..l, 4, ..r), range(1, 8)) test((..none), ()) } -{ +#{ let x = (a: 1) let y = (b: 2) let z = (a: 3) @@ -85,9 +85,9 @@ } --- -// Error: 11-17 cannot spread dictionary into array -{(1, 2, ..(a: 1))} +// Error: 12-18 cannot spread dictionary into array +#{(1, 2, ..(a: 1))} --- -// Error: 5-11 cannot spread array into dictionary -{(..(1, 2), a: 1)} +// Error: 6-12 cannot spread array into dictionary +#{(..(1, 2), a: 1)} diff --git a/tests/typ/compiler/string.typ b/tests/typ/compiler/string.typ index 8ac515a51..576990749 100644 --- a/tests/typ/compiler/string.typ +++ b/tests/typ/compiler/string.typ @@ -13,12 +13,12 @@ #test("🏳️‍🌈A🏳️‍⚧️".last(), "🏳️‍⚧️") --- -// Error: 3-13 string is empty -{ "".first() } +// Error: 4-14 string is empty +#{ "".first() } --- -// Error: 3-12 string is empty -{ "".last() } +// Error: 4-13 string is empty +#{ "".last() } --- // Test the `at` method. @@ -27,8 +27,8 @@ #test("Hey: 🏳️‍🌈 there!".at(5), "🏳️‍🌈") --- -// Error: 3-16 string index out of bounds (index: 5, len: 5) -{ "Hello".at(5) } +// Error: 4-17 string index out of bounds (index: 5, len: 5) +#{ "Hello".at(5) } --- // Test the `slice` method. @@ -135,8 +135,8 @@ #test("hello world".trim(regex(".")), "") --- -// Error: 17-21 expected either `start` or `end` -{"abc".trim(at: left)} +// Error: 18-22 expected either `start` or `end` +#{"abc".trim(at: left)} --- // Test the `split` method. diff --git a/tests/typ/compiler/while.typ b/tests/typ/compiler/while.typ index d495a84a1..8070d2e07 100644 --- a/tests/typ/compiler/while.typ +++ b/tests/typ/compiler/while.typ @@ -4,7 +4,7 @@ // Should output `2 4 6 8 10`. #let i = 0 #while i < 10 [ - { i += 2 } + #{ i += 2 } #i ] @@ -26,7 +26,7 @@ #test(while false {}, none) #let i = 0 -#test(type(while i < 1 [{ i += 1 }]), "content") +#test(type(while i < 1 [#{ i += 1 }]), "content") --- // Condition must be boolean. @@ -38,7 +38,7 @@ #while 2 < "hello".len() {} --- -// Error: 2:1-2:24 loop seems to be infinite +// Error: 2:2-2:24 loop seems to be infinite #let i = 1 #while i > 0 { i += 1 } @@ -46,8 +46,8 @@ // Error: 7 expected expression #while -// Error: 7 expected expression -{while} +// Error: 8 expected expression +#{while} // Error: 9 expected block #while x diff --git a/tests/typ/compute/foundations.typ b/tests/typ/compute/foundations.typ index 6fc93a750..1b2ab473f 100644 --- a/tests/typ/compute/foundations.typ +++ b/tests/typ/compute/foundations.typ @@ -43,8 +43,8 @@ Blue #move(dy: -0.15em)[🌊] ``` --- -// Error: 7-19 cannot continue outside of loop -#eval("{continue}") +// Error: 7-18 cannot continue outside of loop +#eval("#continue") --- // Error: 7-33 cannot access file system from here @@ -72,5 +72,5 @@ _No relative giraffe!_ ``` --- -// Error: 7-16 expected comma -#eval("{(1 2)}") +// Error: 7-15 expected comma +#eval("#(1 2)") diff --git a/tests/typ/compute/utility.typ b/tests/typ/compute/utility.typ index c99c0858a..73bb2e2d9 100644 --- a/tests/typ/compute/utility.typ +++ b/tests/typ/compute/utility.typ @@ -8,7 +8,7 @@ // Test custom paragraphs with user code. #set text(8pt) -{ +#{ let sentences = lorem(59) .split(".") .filter(s => s != "") diff --git a/tests/typ/layout/page-margin.typ b/tests/typ/layout/page-margin.typ index 9be2484f7..53bc27ce0 100644 --- a/tests/typ/layout/page-margin.typ +++ b/tests/typ/layout/page-margin.typ @@ -2,7 +2,7 @@ --- // Set all margins at once. -[ +#[ #set page(height: 20pt, margin: 5pt) #place(top + left)[TL] #place(bottom + right)[BR] @@ -11,10 +11,10 @@ --- // Set individual margins. #set page(height: 40pt) -[#set page(margin: (left: 0pt)); #align(left)[Left]] -[#set page(margin: (right: 0pt)); #align(right)[Right]] -[#set page(margin: (top: 0pt)); #align(top)[Top]] -[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]] +#[#set page(margin: (left: 0pt)); #align(left)[Left]] +#[#set page(margin: (right: 0pt)); #align(right)[Right]] +#[#set page(margin: (top: 0pt)); #align(top)[Top]] +#[#set page(margin: (bottom: 0pt)); #align(bottom)[Bottom]] // Ensure that specific margins override general margins. -[#set page(margin: (rest: 0pt, left: 20pt)); Overriden] +#[#set page(margin: (rest: 0pt, left: 20pt)); Overriden] diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ index 50b6a3945..2815e230b 100644 --- a/tests/typ/layout/page.typ +++ b/tests/typ/layout/page.typ @@ -14,11 +14,11 @@ // Set width and height. // Should result in one high and one wide page. #set page(width: 80pt, height: 80pt) -[#set page(width: 40pt);High] -[#set page(height: 40pt);Wide] +#[#set page(width: 40pt);High] +#[#set page(height: 40pt);Wide] // Flipped predefined paper. -[#set page(paper: "a11", flipped: true);Flipped A11] +#[#set page(paper: "a11", flipped: true);Flipped A11] --- // Test page fill. diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ index 349a2fc20..f5921191d 100644 --- a/tests/typ/layout/pagebreak.typ +++ b/tests/typ/layout/pagebreak.typ @@ -26,12 +26,12 @@ Second // Test a combination of pagebreaks, styled pages and pages with bodies. // Should result in three five pages, with the fourth one being forest-colored. #set page(width: 80pt, height: 30pt) -[#set page(width: 60pt); First] +#[#set page(width: 60pt); First] #pagebreak() #pagebreak() Third #page(height: 20pt, fill: forest)[] -Fif[#set page();th] +Fif#[#set page();th] --- // Test hard and weak pagebreak followed by page with body. diff --git a/tests/typ/layout/par-bidi.typ b/tests/typ/layout/par-bidi.typ index 11c0cafa1..4baefcd3a 100644 --- a/tests/typ/layout/par-bidi.typ +++ b/tests/typ/layout/par-bidi.typ @@ -47,7 +47,7 @@ Lריווח #h(1cm) R --- // Test whether L1 whitespace resetting destroys stuff. -الغالب #h(70pt) ن{" "}ة +الغالب #h(70pt) ن#" "ة --- // Test setting a vertical direction. diff --git a/tests/typ/layout/par-justify.typ b/tests/typ/layout/par-justify.typ index e3d61322a..5a9012d1d 100644 --- a/tests/typ/layout/par-justify.typ +++ b/tests/typ/layout/par-justify.typ @@ -30,4 +30,4 @@ D E F #linebreak(justify: true) // Test that there are no hick-ups with justification enabled and // basically empty paragraph. #set par(justify: true) -{""} +#"" diff --git a/tests/typ/layout/repeat.typ b/tests/typ/layout/repeat.typ index 82d64b941..05b055afc 100644 --- a/tests/typ/layout/repeat.typ +++ b/tests/typ/layout/repeat.typ @@ -12,7 +12,7 @@ ) #for section in sections [ - {section.at(0)} #repeat[.] {section.at(1)} \ + #section.at(0) #repeat[.] #section.at(1) \ ] --- diff --git a/tests/typ/layout/transform.typ b/tests/typ/layout/transform.typ index 2dde626b1..58a053e99 100644 --- a/tests/typ/layout/transform.typ +++ b/tests/typ/layout/transform.typ @@ -3,13 +3,13 @@ --- // Test creating the TeX and XeTeX logos. #let size = 11pt -#let tex = [{ +#let tex = { [T] h(-0.14 * size) move(dy: 0.22 * size)[E] h(-0.12 * size) [X] -}] +} #let xetex = { [X] diff --git a/tests/typ/math/style.typ b/tests/typ/math/style.typ index a8b9c7a50..5089610b1 100644 --- a/tests/typ/math/style.typ +++ b/tests/typ/math/style.typ @@ -3,10 +3,10 @@ #let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v))) #let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both]) -#for k in kinds { - cells.push(raw(repr(k).trim(""))) - for m in modifiers { - cells.push($ #m(#k(part)) $) +#for kk in kinds { + cells.push(raw(repr(kk).trim(""))) + for mm in modifiers { + cells.push($ mm(kk(part)) $) } } diff --git a/tests/typ/meta/document.typ b/tests/typ/meta/document.typ index 1fcb8109e..bbe8dafa7 100644 --- a/tests/typ/meta/document.typ +++ b/tests/typ/meta/document.typ @@ -8,23 +8,23 @@ --- Hello -// Error: 1-30 must appear before any content +// Error: 2-30 must appear before any content #set document(title: "Hello") --- #box[ - // Error: 3-32 not allowed here + // Error: 4-32 not allowed here #set document(title: "Hello") ] --- #box[ - // Error: 3-18 not allowed here + // Error: 4-18 not allowed here #set page("a4") ] --- #box[ - // Error: 3-15 not allowed here + // Error: 4-15 not allowed here #pagebreak() ] diff --git a/tests/typ/meta/outline.typ b/tests/typ/meta/outline.typ index 96629f8c1..1f882cecb 100644 --- a/tests/typ/meta/outline.typ +++ b/tests/typ/meta/outline.typ @@ -11,7 +11,7 @@ = Analyse #lorem(10) -[ +#[ #set heading(outlined: false) == Methodik #lorem(6) diff --git a/tests/typ/text/em.typ b/tests/typ/text/em.typ index dd0a436e2..f901aae4b 100644 --- a/tests/typ/text/em.typ +++ b/tests/typ/text/em.typ @@ -3,10 +3,10 @@ --- #set text(size: 5pt) A // 5pt -[ +#[ #set text(size: 2em) B // 10pt - [ + #[ #set text(size: 1.5em + 1pt) C // 16pt #text(size: 2em)[D] // 32pt diff --git a/tests/typ/text/emphasis.typ b/tests/typ/text/emphasis.typ index f45700310..0191ac87e 100644 --- a/tests/typ/text/emphasis.typ +++ b/tests/typ/text/emphasis.typ @@ -8,7 +8,7 @@ _Emphasized and *strong* words!_ hello_world Nutzer*innen // Can contain paragraph in nested content block. -_Still [ +_Still #[ ] emphasized._ @@ -24,7 +24,7 @@ Normal *Bold* #set strong(delta: 150) -*Medium* and *[*Bold*]* +*Medium* and *#[*Bold*]* --- // Error: 13 expected underscore @@ -38,6 +38,6 @@ _Hello World --- -// Error: 25 expected star -// Error: 25 expected underscore -[_Cannot *be interleaved] +// Error: 26 expected star +// Error: 26 expected underscore +#[_Cannot *be interleaved] diff --git a/tests/typ/text/font.typ b/tests/typ/text/font.typ index 0889a0f36..d2add0f18 100644 --- a/tests/typ/text/font.typ +++ b/tests/typ/text/font.typ @@ -25,7 +25,7 @@ Emoji: 🐪, 🌋, 🏞 // Colors. -[ +#[ #set text(fill: eastern) This is #text(rgb("FA644B"))[way more] colorful. ] diff --git a/tests/typ/text/quotes.typ b/tests/typ/text/quotes.typ index 0c64867af..c63caa747 100644 --- a/tests/typ/text/quotes.typ +++ b/tests/typ/text/quotes.typ @@ -50,4 +50,4 @@ He's told some books contain questionable "example text". // Test changing properties within text. "She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me. -Some people's thought on this would be [#set smartquote(enabled: false); "strange."] +Some people's thought on this would be #[#set smartquote(enabled: false); "strange."] diff --git a/tests/typ/text/space.typ b/tests/typ/text/space.typ index 8dcc59e36..51970a486 100644 --- a/tests/typ/text/space.typ +++ b/tests/typ/text/space.typ @@ -7,7 +7,7 @@ C #let x = 2;D #test(x, 2) \ E#if true [F]G \ H #if true{"I"} J \ K #if true [L] else []M \ -#let c = true; N#while c [{c = false}O] P \ +#let c = true; N#while c [#{c = false}O] P \ #let c = true; Q #while c { c = false; "R" } S \ T#for _ in (none,) {"U"}V @@ -19,11 +19,11 @@ A /**/B/**/ C --- // Test that a run consisting only of whitespace isn't trimmed. -A[#set text("IBM Plex Serif"); ]B +A#text("IBM Plex Serif")[ ]B --- // Test font change after space. -Left [#set text("IBM Plex Serif");Right]. +Left #text("IBM Plex Serif")[Right]. --- // Test that linebreak consumed surrounding spaces. @@ -31,7 +31,7 @@ Left [#set text("IBM Plex Serif");Right]. --- // Test that space at start of non-backslash-linebreak line isn't trimmed. -A{"\n"} B +A#"\n" B --- // Test that trailing space does not force a line break. diff --git a/tests/typ/visualize/line.typ b/tests/typ/visualize/line.typ index 9cc9b8ccb..2085daa13 100644 --- a/tests/typ/visualize/line.typ +++ b/tests/typ/visualize/line.typ @@ -6,10 +6,10 @@ --- // Test the `end` argument. -{ - line(end: (10pt, 0pt)) - line(start: (0pt, 10pt), end: (0pt, 0pt)) - line(end: (15pt, 15pt)) +#{ + line(end: (10pt, 0pt)) + line(start: (0pt, 10pt), end: (0pt, 0pt)) + line(end: (15pt, 15pt)) } #v(.5cm) diff --git a/tests/typ/visualize/shape-fill-stroke.typ b/tests/typ/visualize/shape-fill-stroke.typ index d14d0981b..8820a9fd7 100644 --- a/tests/typ/visualize/shape-fill-stroke.typ +++ b/tests/typ/visualize/shape-fill-stroke.typ @@ -16,7 +16,7 @@ variant(fill: forest, stroke: black + 2pt), variant(fill: forest, stroke: conifer + 2pt), ) { - (align(horizon)[{i + 1}.], item, []) + (align(horizon)[#{i + 1}.], item, []) } #grid( diff --git a/tests/typ/visualize/shape-rect.typ b/tests/typ/visualize/shape-rect.typ index c8518bbd4..ff80dfb93 100644 --- a/tests/typ/visualize/shape-rect.typ +++ b/tests/typ/visualize/shape-rect.typ @@ -24,9 +24,9 @@ #rect(height: 1cm, width: 100%, fill: rgb("734ced"))[Topleft] // These are inline with text. -\{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67")) - #rect(width: 0.5in, height: 7pt, fill: rgb("edd466")) - #rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))\} +{#rect(width: 0.5in, height: 7pt, fill: rgb("d6cd67")) + #rect(width: 0.5in, height: 7pt, fill: rgb("edd466")) + #rect(width: 0.5in, height: 7pt, fill: rgb("e3be62"))} // Rounded corners. #rect(width: 2cm, radius: 60%) @@ -39,10 +39,8 @@ )) // Different strokes. -[ - #set rect(stroke: (right: red)) - #rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt)) -] +#set rect(stroke: (right: red)) +#rect(width: 100%, fill: lime, stroke: (x: 5pt, y: 1pt)) --- // Error: 15-38 unexpected key "cake", valid keys are "top-left", "top-right", "bottom-right", "bottom-left", "left", "top", "right", "bottom", and "rest" diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 692fe652d..d546eccb5 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -24,21 +24,7 @@ }, "common": { "patterns": [ - { "include": "#comments" }, - { - "name": "meta.block.code.typst", - "begin": "{", - "end": "}", - "captures": { "0": { "name": "punctuation.definition.block.code.typst" } }, - "patterns": [{ "include": "#code" }] - }, - { - "name": "meta.block.content.typst", - "begin": "\\[", - "end": "\\]", - "captures": { "0": { "name": "punctuation.definition.block.content.typst" } }, - "patterns": [{ "include": "#markup" }] - } + { "include": "#comments" } ] }, "markup": { @@ -213,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": "//",