diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ide/highlight.rs | 128 | ||||
| -rw-r--r-- | src/model/eval.rs | 291 | ||||
| -rw-r--r-- | src/model/func.rs | 16 | ||||
| -rw-r--r-- | src/model/library.rs | 14 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 292 | ||||
| -rw-r--r-- | src/syntax/kind.rs | 37 | ||||
| -rw-r--r-- | src/syntax/lexer.rs | 21 | ||||
| -rw-r--r-- | src/syntax/node.rs | 29 | ||||
| -rw-r--r-- | src/syntax/parser.rs | 222 | ||||
| -rw-r--r-- | src/syntax/reparser.rs | 68 |
10 files changed, 618 insertions, 500 deletions
diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index 75201d93..b1df94d1 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -7,7 +7,7 @@ pub enum Category { Comment, /// Punctuation in code. Punctuation, - /// An escape sequence, shorthand or symbol notation. + /// An escape sequence or shorthand. Escape, /// Strong markup. Strong, @@ -97,7 +97,6 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> { SyntaxKind::Parbreak => None, SyntaxKind::Escape => Some(Category::Escape), SyntaxKind::Shorthand => Some(Category::Escape), - SyntaxKind::Symbol => Some(Category::Escape), SyntaxKind::SmartQuote => None, SyntaxKind::Strong => Some(Category::Strong), SyntaxKind::Emph => Some(Category::Emph), @@ -113,12 +112,22 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> { SyntaxKind::EnumMarker => Some(Category::ListMarker), SyntaxKind::TermItem => None, SyntaxKind::TermMarker => Some(Category::ListMarker), + SyntaxKind::Formula => None, + SyntaxKind::Math => None, - SyntaxKind::Atom => None, - SyntaxKind::Delimited => None, - SyntaxKind::Script => None, - SyntaxKind::Frac => None, - SyntaxKind::AlignPoint => Some(Category::MathOperator), + SyntaxKind::MathAtom => None, + SyntaxKind::MathIdent => highlight_ident(node), + SyntaxKind::MathDelimited => None, + SyntaxKind::MathScript => None, + SyntaxKind::MathFrac => None, + SyntaxKind::MathAlignPoint => Some(Category::MathOperator), + + SyntaxKind::Hashtag if node.before_error() => None, + SyntaxKind::Hashtag => node + .next_leaf() + .filter(|node| node.kind() != SyntaxKind::Dollar) + .as_ref() + .and_then(highlight), SyntaxKind::LeftBrace => Some(Category::Punctuation), SyntaxKind::RightBrace => Some(Category::Punctuation), @@ -134,14 +143,14 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> { _ => Some(Category::Operator), }, SyntaxKind::Underscore => match node.parent_kind() { - Some(SyntaxKind::Script) => Some(Category::MathOperator), + Some(SyntaxKind::MathScript) => Some(Category::MathOperator), _ => None, }, SyntaxKind::Dollar => Some(Category::MathDelimiter), SyntaxKind::Plus => Some(Category::Operator), SyntaxKind::Minus => Some(Category::Operator), SyntaxKind::Slash => Some(match node.parent_kind() { - Some(SyntaxKind::Frac) => Category::MathOperator, + Some(SyntaxKind::MathFrac) => Category::MathOperator, _ => Category::Operator, }), SyntaxKind::Hat => Some(Category::MathOperator), @@ -183,47 +192,8 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> { SyntaxKind::Include => Some(Category::Keyword), SyntaxKind::As => Some(Category::Keyword), - SyntaxKind::Ident => match node.parent_kind() { - Some( - SyntaxKind::Markup - | SyntaxKind::Math - | SyntaxKind::Script - | SyntaxKind::Frac, - ) => Some(Category::Interpolated), - Some(SyntaxKind::FuncCall) => Some(Category::Function), - Some(SyntaxKind::FieldAccess) - if node.parent().and_then(|p| p.parent_kind()) - == Some(SyntaxKind::SetRule) - && node.next_sibling().is_none() => - { - Some(Category::Function) - } - Some(SyntaxKind::FieldAccess) - if node - .parent() - .and_then(|p| p.parent()) - .filter(|gp| gp.kind() == SyntaxKind::Parenthesized) - .and_then(|gp| gp.parent()) - .map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall) - && node.next_sibling().is_none() => - { - Some(Category::Function) - } - Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => { - Some(Category::Function) - } - Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => { - Some(Category::Function) - } - Some(SyntaxKind::SetRule) => Some(Category::Function), - Some(SyntaxKind::ShowRule) - if node.prev_sibling().as_ref().map(|v| v.kind()) - == Some(SyntaxKind::Show) => - { - Some(Category::Function) - } - _ => None, - }, + SyntaxKind::Code => None, + SyntaxKind::Ident => highlight_ident(node), SyntaxKind::Bool => Some(Category::Keyword), SyntaxKind::Int => Some(Category::Number), SyntaxKind::Float => Some(Category::Number), @@ -238,7 +208,11 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> { SyntaxKind::Keyed => None, SyntaxKind::Unary => None, SyntaxKind::Binary => None, - SyntaxKind::FieldAccess => None, + SyntaxKind::FieldAccess => match node.parent_kind() { + Some(SyntaxKind::Markup | SyntaxKind::Math) => Some(Category::Interpolated), + Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight), + _ => None, + }, SyntaxKind::FuncCall => None, SyntaxKind::MethodCall => None, SyntaxKind::Args => None, @@ -266,6 +240,52 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> { } } +/// Highlight an identifier based on context. +fn highlight_ident(node: &LinkedNode) -> Option<Category> { + match node.parent_kind() { + Some( + SyntaxKind::Markup + | SyntaxKind::Math + | SyntaxKind::MathFrac + | SyntaxKind::MathScript, + ) => Some(Category::Interpolated), + Some(SyntaxKind::FuncCall) => Some(Category::Function), + Some(SyntaxKind::FieldAccess) + if node.parent().and_then(|p| p.parent_kind()) + == Some(SyntaxKind::SetRule) + && node.next_sibling().is_none() => + { + Some(Category::Function) + } + Some(SyntaxKind::FieldAccess) + if node + .parent() + .and_then(|p| p.parent()) + .filter(|gp| gp.kind() == SyntaxKind::Parenthesized) + .and_then(|gp| gp.parent()) + .map_or(false, |ggp| ggp.kind() == SyntaxKind::FuncCall) + && node.next_sibling().is_none() => + { + Some(Category::Function) + } + Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight), + Some(SyntaxKind::MethodCall) if node.prev_sibling().is_some() => { + Some(Category::Function) + } + Some(SyntaxKind::Closure) if node.prev_sibling().is_none() => { + Some(Category::Function) + } + Some(SyntaxKind::SetRule) => Some(Category::Function), + Some(SyntaxKind::ShowRule) + if node.prev_sibling().as_ref().map(|v| v.kind()) + == Some(SyntaxKind::Show) => + { + Some(Category::Function) + } + _ => None, + } +} + #[cfg(test)] mod tests { use std::ops::Range; @@ -300,7 +320,8 @@ mod tests { test( "#f(x + 1)", &[ - (0..2, Function), + (0..1, Function), + (1..2, Function), (2..3, Punctuation), (5..6, Operator), (7..8, Number), @@ -311,7 +332,8 @@ mod tests { test( "#let f(x) = x", &[ - (0..4, Keyword), + (0..1, Keyword), + (1..4, Keyword), (5..6, Function), (6..7, Punctuation), (8..9, Punctuation), diff --git a/src/model/eval.rs b/src/model/eval.rs index 6de328bc..6a1884eb 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -234,7 +234,7 @@ fn eval_markup( *node = mem::take(node).labelled(label); } } - value => seq.push(value.display().spanned(expr.span())), + value => seq.push(value.display()), }, } @@ -254,11 +254,9 @@ impl Eval for ast::Expr { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + let span = self.span(); let forbidden = |name| { - error!( - self.span(), - "{} is only allowed directly in code and content blocks", name - ) + error!(span, "{} is only allowed directly in code and content blocks", name) }; match self { @@ -266,9 +264,9 @@ impl Eval for ast::Expr { Self::Space(v) => v.eval(vm).map(Value::Content), Self::Linebreak(v) => v.eval(vm).map(Value::Content), Self::Parbreak(v) => v.eval(vm).map(Value::Content), - Self::Escape(v) => v.eval(vm).map(Value::Content), - Self::Shorthand(v) => v.eval(vm).map(Value::Content), Self::Symbol(v) => v.eval(vm).map(Value::Content), + Self::Escape(v) => v.eval(vm), + Self::Shorthand(v) => v.eval(vm), Self::SmartQuote(v) => v.eval(vm).map(Value::Content), Self::Strong(v) => v.eval(vm).map(Value::Content), Self::Emph(v) => v.eval(vm).map(Value::Content), @@ -280,11 +278,14 @@ impl Eval for ast::Expr { Self::List(v) => v.eval(vm).map(Value::Content), Self::Enum(v) => v.eval(vm).map(Value::Content), Self::Term(v) => v.eval(vm).map(Value::Content), - Self::Atom(v) => v.eval(vm).map(Value::Content), - Self::Delimited(v) => v.eval(vm).map(Value::Content), - Self::Script(v) => v.eval(vm).map(Value::Content), - Self::Frac(v) => v.eval(vm).map(Value::Content), - Self::AlignPoint(v) => v.eval(vm).map(Value::Content), + Self::Formula(v) => v.eval(vm).map(Value::Content), + Self::Math(v) => v.eval(vm).map(Value::Content), + Self::MathAtom(v) => v.eval(vm).map(Value::Content), + Self::MathIdent(v) => v.eval(vm), + Self::MathAlignPoint(v) => v.eval(vm).map(Value::Content), + Self::MathDelimited(v) => v.eval(vm).map(Value::Content), + Self::MathScript(v) => v.eval(vm).map(Value::Content), + Self::MathFrac(v) => v.eval(vm).map(Value::Content), Self::Ident(v) => v.eval(vm), Self::None(v) => v.eval(vm), Self::Auto(v) => v.eval(vm), @@ -295,7 +296,6 @@ impl Eval for ast::Expr { Self::Str(v) => v.eval(vm), Self::Code(v) => v.eval(vm), Self::Content(v) => v.eval(vm).map(Value::Content), - Self::Math(v) => v.eval(vm).map(Value::Content), Self::Array(v) => v.eval(vm).map(Value::Array), Self::Dict(v) => v.eval(vm).map(Value::Dict), Self::Parenthesized(v) => v.eval(vm), @@ -316,29 +316,12 @@ impl Eval for ast::Expr { Self::Break(v) => v.eval(vm), Self::Continue(v) => v.eval(vm), Self::Return(v) => v.eval(vm), - } - } -} + }? + .spanned(span); -impl ast::Expr { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { - Ok(match self { - Self::Escape(v) => v.eval_in_math(vm)?, - Self::Shorthand(v) => v.eval_in_math(vm)?, - Self::Symbol(v) => v.eval_in_math(vm)?, - Self::Ident(v) => v.eval_in_math(vm)?, - Self::FuncCall(v) => v.eval_in_math(vm)?, - _ => self.eval(vm)?.display_in_math(), } - .spanned(self.span())) - } - fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> { - Ok(match self { - Self::Delimited(v) => v.eval_without_parens(vm)?, - _ => self.eval_in_math(vm)?, - } - .spanned(self.span())) + Ok(v) } } @@ -375,44 +358,22 @@ impl Eval for ast::Parbreak { } impl Eval for ast::Escape { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.text)(self.get().into())) - } -} + type Output = Value; -impl ast::Escape { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { - Ok((vm.items.math_atom)(self.get().into())) + fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { + // This can be in markup and math, going through a string ensure + // that either text or atom is picked. + Ok(Value::Str(self.get().into())) } } impl Eval for ast::Shorthand { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.text)(self.get().into())) - } -} - -impl ast::Shorthand { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { - Ok((vm.items.math_atom)(self.get().into())) - } -} - -impl Eval for ast::Symbol { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.symbol)(self.get().into())) - } -} + type Output = Value; -impl ast::Symbol { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { - Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op:square".into())) + fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> { + // This can be in markup and math, going through a string ensure + // that either text or atom is picked. + Ok(Value::Str(self.get().into())) } } @@ -513,108 +474,95 @@ impl Eval for ast::TermItem { } } -impl Eval for ast::Math { +impl Eval for ast::Formula { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let seq = self - .exprs() - .map(|expr| expr.eval_in_math(vm)) - .collect::<SourceResult<_>>()?; + let body = self.body().eval(vm)?; let block = self.block(); - Ok((vm.items.math_formula)(Content::sequence(seq), block)) + Ok((vm.items.formula)(body, block)) } } -impl Eval for ast::Atom { +impl Eval for ast::Math { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.math_atom)(self.get().clone())) + Ok(Content::sequence( + self.exprs() + .map(|expr| Ok(expr.eval(vm)?.display_in_math())) + .collect::<SourceResult<_>>()?, + )) } } -impl Eval for ast::Delimited { +impl Eval for ast::MathAtom { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let seq = self - .exprs() - .map(|expr| expr.eval_in_math(vm)) - .collect::<SourceResult<_>>()?; - Ok((vm.items.math_delimited)(Content::sequence(seq))) + Ok((vm.items.math_atom)(self.get().clone())) } } -impl ast::Delimited { - fn eval_without_parens(&self, vm: &mut Vm) -> SourceResult<Content> { - let exprs: Vec<_> = self.exprs().collect(); - let mut slice = exprs.as_slice(); - if let (Some(ast::Expr::Atom(first)), Some(ast::Expr::Atom(last))) = - (exprs.first(), exprs.last()) - { - if first.get() == "(" && last.get() == ")" { - slice = &exprs[1..exprs.len() - 1]; - } - } - let seq = slice - .iter() - .map(|expr| expr.eval_in_math(vm)) - .collect::<SourceResult<_>>()?; - Ok((vm.items.math_delimited)(Content::sequence(seq))) +impl Eval for ast::MathIdent { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok(vm.scopes.get_in_math(self).cloned().at(self.span())?) } } -impl Eval for ast::Script { +impl Eval for ast::MathAlignPoint { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let base = self.base().eval_in_math(vm)?; - let sub = self.sub().map(|expr| expr.eval_without_parens(vm)).transpose()?; - let sup = self.sup().map(|expr| expr.eval_without_parens(vm)).transpose()?; - Ok((vm.items.math_script)(base, sub, sup)) + Ok((vm.items.math_align_point)()) } } -impl Eval for ast::Frac { +impl Eval for ast::MathDelimited { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let num = self.num().eval_without_parens(vm)?; - let denom = self.denom().eval_without_parens(vm)?; - Ok((vm.items.math_frac)(num, denom)) + let open = self.open().eval(vm)?; + let body = self.body().eval(vm)?; + let close = self.close().eval(vm)?; + Ok((vm.items.math_delimited)(open, body, close)) } } -impl Eval for ast::AlignPoint { +impl Eval for ast::MathScript { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.math_align_point)()) + let base = self.base().eval(vm)?.display_in_math(); + let sub = self + .sub() + .map(|expr| expr.eval(vm).map(Value::display_in_math)) + .transpose()?; + let sup = self + .sup() + .map(|expr| expr.eval(vm).map(Value::display_in_math)) + .transpose()?; + Ok((vm.items.math_script)(base, sub, sup)) } } -impl Eval for ast::Ident { - type Output = Value; +impl Eval for ast::MathFrac { + type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let value = vm.scopes.get(self).cloned().at(self.span())?; - Ok(match value { - Value::Func(func) => Value::Func(func.spanned(self.span())), - value => value, - }) + let num = self.num().eval(vm)?.display_in_math(); + let denom = self.denom().eval(vm)?.display_in_math(); + Ok((vm.items.math_frac)(num, denom)) } } -impl ast::Ident { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { - if self.as_untyped().len() == self.len() - && matches!(vm.scopes.get_in_math(self), Ok(Value::Func(_)) | Err(_)) - { - Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into())) - } else { - Ok(vm.scopes.get_in_math(self).at(self.span())?.clone().display_in_math()) - } +impl Eval for ast::Ident { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + Ok(vm.scopes.get(self).cloned().at(self.span())?) } } @@ -686,12 +634,20 @@ impl Eval for ast::CodeBlock { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { vm.scopes.enter(); - let output = eval_code(vm, &mut self.exprs())?; + let output = self.body().eval(vm)?; vm.scopes.exit(); Ok(output) } } +impl Eval for ast::Code { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { + eval_code(vm, &mut self.exprs()) + } +} + /// Evaluate a stream of expressions. fn eval_code( vm: &mut Vm, @@ -901,23 +857,9 @@ impl Eval for ast::FieldAccess { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let object = self.target().eval(vm)?; - let span = self.field().span(); - let field = self.field().take(); - - Ok(match object { - Value::Dict(dict) => dict.at(&field).at(span)?.clone(), - Value::Content(content) => content - .field(&field) - .ok_or_else(|| format!("unknown field `{field}`")) - .at(span)?, - Value::Module(module) => module.get(&field).cloned().at(span)?, - v => bail!( - self.target().span(), - "expected dictionary or content, found {}", - v.type_name() - ), - }) + let value = self.target().eval(vm)?; + let field = self.field(); + value.field(&field).at(field.span()) } } @@ -926,27 +868,13 @@ impl Eval for ast::FuncCall { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { let callee = self.callee(); - let callee = callee.eval(vm)?.cast::<Func>().at(callee.span())?; - let args = self.args().eval(vm)?; - Self::eval_call(vm, &callee, args, self.span()) - } -} - -impl ast::FuncCall { - fn eval_in_math(&self, vm: &mut Vm) -> SourceResult<Content> { - let callee = match self.callee() { - ast::Expr::Ident(ident) => { - vm.scopes.get_in_math(&ident).at(ident.span())?.clone() - } - expr => expr.eval(vm)?, - }; + let callee_span = callee.span(); + let in_math = matches!(callee, ast::Expr::MathIdent(_)); + let callee = callee.eval(vm)?; + let mut args = self.args().eval(vm)?; - if let Value::Func(callee) = callee { - let args = self.args().eval(vm)?; - Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math()) - } else { + if in_math && !matches!(callee, Value::Func(_)) { let mut body = (vm.items.math_atom)('('.into()); - let mut args = self.args().eval(vm)?; for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { if i > 0 { body += (vm.items.math_atom)(','.into()); @@ -954,23 +882,26 @@ impl ast::FuncCall { body += arg; } body += (vm.items.math_atom)(')'.into()); - Ok(callee.display_in_math() + body) + return Ok(Value::Content(callee.display_in_math() + body)); } - } - fn eval_call( - vm: &mut Vm, - callee: &Func, - args: Args, - span: Span, - ) -> SourceResult<Value> { - if vm.depth >= MAX_CALL_DEPTH { - bail!(span, "maximum function call depth exceeded"); - } + let callee = callee.cast::<Func>().at(callee_span)?; + complete_call(vm, &callee, args, self.span()) + } +} - let point = || Tracepoint::Call(callee.name().map(Into::into)); - callee.call(vm, args).trace(vm.world, point, span) +fn complete_call( + vm: &mut Vm, + callee: &Func, + args: Args, + span: Span, +) -> SourceResult<Value> { + if vm.depth >= MAX_CALL_DEPTH { + bail!(span, "maximum function call depth exceeded"); } + + let point = || Tracepoint::Call(callee.name().map(Into::into)); + callee.call(vm, args).trace(vm.world, point, span) } impl Eval for ast::MethodCall { @@ -988,7 +919,7 @@ impl Eval for ast::MethodCall { if let Value::Func(callee) = module.get(&method).cloned().at(method.span())? { - return ast::FuncCall::eval_call(vm, &callee, args, self.span()); + return complete_call(vm, &callee, args, self.span()); } } @@ -999,7 +930,7 @@ impl Eval for ast::MethodCall { if let Value::Module(module) = &value { if let Value::Func(callee) = module.get(&method).at(method.span())? { - return ast::FuncCall::eval_call(vm, callee, args, self.span()); + return complete_call(vm, callee, args, self.span()); } } @@ -1367,7 +1298,7 @@ impl Eval for ast::ModuleInclude { } /// Process an import of a module relative to the current location. -fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> { +fn import(vm: &mut Vm, source: Value, span: Span) -> SourceResult<Module> { let path = match source { Value::Str(path) => path, Value::Module(module) => return Ok(module), @@ -1386,7 +1317,8 @@ fn import(vm: &Vm, source: Value, span: Span) -> SourceResult<Module> { // Evaluate the file. let source = vm.world.source(id); let point = || Tracepoint::Import; - eval(vm.world, vm.route, source).trace(vm.world, point, span) + eval(vm.world, vm.route, TrackedMut::reborrow_mut(&mut vm.tracer), source) + .trace(vm.world, point, span) } impl Eval for ast::LoopBreak { @@ -1446,7 +1378,12 @@ impl Access for ast::Expr { impl Access for ast::Ident { fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - vm.scopes.get_mut(self).at(self.span()) + let span = self.span(); + let value = vm.scopes.get_mut(self).at(span)?; + if vm.traced == Some(span) { + vm.tracer.trace(value.clone()); + } + Ok(value) } } diff --git a/src/model/func.rs b/src/model/func.rs index 73b7f1c7..00e59bbd 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -505,17 +505,17 @@ mod tests { fn test_captures() { // Let binding and function definition. test("#let x = x", &["x"]); - test("#let x; {x + y}", &["y"]); + test("#let x; #{x + y}", &["y"]); test("#let f(x, y) = x + y", &[]); test("#let f(x, y) = f", &[]); test("#let f = (x, y) => f", &["f"]); // Closure with different kinds of params. - test("{(x, y) => x + z}", &["z"]); - test("{(x: y, z) => x + z}", &["y"]); - test("{(..x) => x + y}", &["y"]); - test("{(x, y: x + z) => x + y}", &["x", "z"]); - test("{x => x; x}", &["x"]); + test("#{(x, y) => x + z}", &["z"]); + test("#{(x: y, z) => x + z}", &["y"]); + test("#{(..x) => x + y}", &["y"]); + test("#{(x, y: x + z) => x + y}", &["x", "z"]); + test("#{x => x; x}", &["x"]); // Show rule. test("#show y: x => x", &["y"]); @@ -532,7 +532,7 @@ mod tests { test("#import x + y: x, y, z", &["x", "y"]); // Blocks. - test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]); - test("[#let x = 1]#x", &["x"]); + test("#{ let x = 1; { let y = 2; y }; x + y }", &["y"]); + test("#[#let x = 1]#x", &["x"]); } } diff --git a/src/model/library.rs b/src/model/library.rs index a64b0263..cd9db10f 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -68,19 +68,19 @@ pub struct LangItems { /// An item in a term list: `/ Term: Details`. pub term_item: fn(term: Content, description: Content) -> Content, /// A mathematical formula: `$x$`, `$ x^2 $`. - pub math_formula: fn(body: Content, block: bool) -> Content, - /// A subsection in a math formula that is surrounded by matched delimiters: - /// `[x + y]`. - pub math_delimited: fn(body: Content) -> Content, + pub formula: fn(body: Content, block: bool) -> Content, /// An atom in a formula: `x`, `+`, `12`. pub math_atom: fn(atom: EcoString) -> Content, + /// An alignment point in a formula: `&`. + pub math_align_point: fn() -> Content, + /// A subsection in a math formula that is surrounded by matched delimiters: + /// `[x + y]`. + pub math_delimited: fn(open: Content, body: Content, close: Content) -> Content, /// A base with optional sub- and superscripts in a formula: `a_1^2`. pub math_script: fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content, /// A fraction in a formula: `x/2`. pub math_frac: fn(num: Content, denom: Content) -> Content, - /// An alignment point in a formula: `&`. - pub math_align_point: fn() -> Content, } impl Debug for LangItems { @@ -108,7 +108,7 @@ impl Hash for LangItems { self.list_item.hash(state); self.enum_item.hash(state); self.term_item.hash(state); - self.math_formula.hash(state); + self.formula.hash(state); self.math_atom.hash(state); self.math_script.hash(state); self.math_frac.hash(state); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 4bab0c42..b9186787 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -29,7 +29,7 @@ pub trait AstNode: Sized { macro_rules! node { ($(#[$attr:meta])* $name:ident) => { - #[derive(Debug, Clone, PartialEq, Hash)] + #[derive(Debug, Default, Clone, PartialEq, Hash)] #[repr(transparent)] $(#[$attr])* pub struct $name(SyntaxNode); @@ -114,18 +114,22 @@ pub enum Expr { /// An item in a term list: `/ Term: Details`. Term(TermItem), /// A math formula: `$x$`, `$ x^2 $`. + Formula(Formula), + /// A math formula: `$x$`, `$ x^2 $`. Math(Math), /// An atom in a math formula: `x`, `+`, `12`. - Atom(Atom), + MathAtom(MathAtom), + /// An identifier in a math formula: `pi`. + MathIdent(MathIdent), + /// An alignment point in a math formula: `&`. + MathAlignPoint(MathAlignPoint), /// A subsection in a math formula that is surrounded by matched delimiters: /// `[x + y]`. - Delimited(Delimited), + MathDelimited(MathDelimited), /// A base with optional sub- and superscripts in a math formula: `a_1^2`. - Script(Script), + MathScript(MathScript), /// A fraction in a math formula: `x/2`. - Frac(Frac), - /// An alignment point in a math formula: `&`. - AlignPoint(AlignPoint), + MathFrac(MathFrac), /// An identifier: `left`. Ident(Ident), /// The `none` literal. @@ -205,7 +209,6 @@ impl AstNode for Expr { SyntaxKind::Text => node.cast().map(Self::Text), SyntaxKind::Escape => node.cast().map(Self::Escape), SyntaxKind::Shorthand => node.cast().map(Self::Shorthand), - SyntaxKind::Symbol => node.cast().map(Self::Symbol), SyntaxKind::SmartQuote => node.cast().map(Self::SmartQuote), SyntaxKind::Strong => node.cast().map(Self::Strong), SyntaxKind::Emph => node.cast().map(Self::Emph), @@ -217,12 +220,14 @@ impl AstNode for Expr { SyntaxKind::ListItem => node.cast().map(Self::List), SyntaxKind::EnumItem => node.cast().map(Self::Enum), SyntaxKind::TermItem => node.cast().map(Self::Term), + SyntaxKind::Formula => node.cast().map(Self::Formula), SyntaxKind::Math => node.cast().map(Self::Math), - SyntaxKind::Atom => node.cast().map(Self::Atom), - SyntaxKind::Delimited => node.cast().map(Self::Delimited), - SyntaxKind::Script => node.cast().map(Self::Script), - SyntaxKind::Frac => node.cast().map(Self::Frac), - SyntaxKind::AlignPoint => node.cast().map(Self::AlignPoint), + SyntaxKind::MathAtom => node.cast().map(Self::MathAtom), + SyntaxKind::MathIdent => node.cast().map(Self::MathIdent), + SyntaxKind::MathAlignPoint => node.cast().map(Self::MathAlignPoint), + SyntaxKind::MathDelimited => node.cast().map(Self::MathDelimited), + SyntaxKind::MathScript => node.cast().map(Self::MathScript), + SyntaxKind::MathFrac => node.cast().map(Self::MathFrac), SyntaxKind::Ident => node.cast().map(Self::Ident), SyntaxKind::None => node.cast().map(Self::None), SyntaxKind::Auto => node.cast().map(Self::Auto), @@ -265,7 +270,6 @@ impl AstNode for Expr { Self::Parbreak(v) => v.as_untyped(), Self::Escape(v) => v.as_untyped(), Self::Shorthand(v) => v.as_untyped(), - Self::Symbol(v) => v.as_untyped(), Self::SmartQuote(v) => v.as_untyped(), Self::Strong(v) => v.as_untyped(), Self::Emph(v) => v.as_untyped(), @@ -277,12 +281,14 @@ impl AstNode for Expr { Self::List(v) => v.as_untyped(), Self::Enum(v) => v.as_untyped(), Self::Term(v) => v.as_untyped(), + Self::Formula(v) => v.as_untyped(), Self::Math(v) => v.as_untyped(), - Self::Atom(v) => v.as_untyped(), - Self::Delimited(v) => v.as_untyped(), - Self::Script(v) => v.as_untyped(), - Self::Frac(v) => v.as_untyped(), - Self::AlignPoint(v) => v.as_untyped(), + Self::MathAtom(v) => v.as_untyped(), + Self::MathIdent(v) => v.as_untyped(), + Self::MathAlignPoint(v) => v.as_untyped(), + Self::MathDelimited(v) => v.as_untyped(), + Self::MathScript(v) => v.as_untyped(), + Self::MathFrac(v) => v.as_untyped(), Self::Ident(v) => v.as_untyped(), Self::None(v) => v.as_untyped(), Self::Auto(v) => v.as_untyped(), @@ -317,6 +323,12 @@ impl AstNode for Expr { } } +impl Default for Expr { + fn default() -> Self { + Expr::Space(Space::default()) + } +} + node! { /// Plain text without markup. Text @@ -360,9 +372,9 @@ impl Escape { u32::from_str_radix(hex, 16) .ok() .and_then(std::char::from_u32) - .expect("unicode escape is invalid") + .unwrap_or_default() } else { - s.eat().expect("escape is missing escaped character") + s.eat().unwrap_or_default() } } } @@ -378,10 +390,11 @@ impl Shorthand { pub fn get(&self) -> char { match self.0.text().as_str() { "~" => '\u{00A0}', - "..." => '\u{2026}', "--" => '\u{2013}', "---" => '\u{2014}', "-?" => '\u{00AD}', + "..." => '…', + "*" => '∗', "!=" => '≠', "<=" => '≤', ">=" => '≥', @@ -432,7 +445,7 @@ node! { impl Strong { /// The contents of the strong node. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("strong emphasis is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -444,7 +457,7 @@ node! { impl Emph { /// The contents of the emphasis node. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("emphasis is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -568,7 +581,7 @@ node! { impl Heading { /// The contents of the heading. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("heading is missing markup body") + self.0.cast_first_match().unwrap_or_default() } /// The section depth (numer of equals signs). @@ -577,7 +590,7 @@ impl Heading { .children() .find(|node| node.kind() == SyntaxKind::HeadingMarker) .and_then(|node| node.len().try_into().ok()) - .expect("heading is missing marker") + .unwrap_or(NonZeroUsize::new(1).unwrap()) } } @@ -589,7 +602,7 @@ node! { impl ListItem { /// The contents of the list item. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("list item is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -609,7 +622,7 @@ impl EnumItem { /// The contents of the list item. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("enum item is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -621,41 +634,53 @@ node! { impl TermItem { /// The term described by the item. pub fn term(&self) -> Markup { - self.0.cast_first_match().expect("term list item is missing term") + self.0.cast_first_match().unwrap_or_default() } /// The description of the term. pub fn description(&self) -> Markup { - self.0 - .cast_last_match() - .expect("term list item is missing description") + self.0.cast_last_match().unwrap_or_default() } } node! { /// A math formula: `$x$`, `$ x^2 $`. - Math + Formula } -impl Math { - /// The expressions the formula consists of. - pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { - self.0.children().filter_map(Expr::cast_with_space) +impl Formula { + /// The contained math. + pub fn body(&self) -> Math { + self.0.cast_first_match().unwrap_or_default() } /// Whether the formula should be displayed as a separate block. pub fn block(&self) -> bool { - matches!(self.exprs().next(), Some(Expr::Space(_))) - && matches!(self.exprs().last(), Some(Expr::Space(_))) + let is_space = |node: Option<&SyntaxNode>| { + node.map(SyntaxNode::kind) == Some(SyntaxKind::Space) + }; + is_space(self.0.children().nth(1)) && is_space(self.0.children().nth_back(1)) + } +} + +node! { + /// Math markup. + Math +} + +impl Math { + /// The expressions the mathematical content consists of. + pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { + self.0.children().filter_map(Expr::cast_with_space) } } node! { /// A atom in a formula: `x`, `+`, `12`. - Atom + MathAtom } -impl Atom { +impl MathAtom { /// Get the atom's text. pub fn get(&self) -> &EcoString { self.0.text() @@ -663,27 +688,72 @@ impl Atom { } node! { + /// An identifier in a math formula: `pi`. + MathIdent +} + +impl MathIdent { + /// Get the identifier. + pub fn get(&self) -> &EcoString { + self.0.text() + } + + /// Take out the contained identifier. + pub fn take(self) -> EcoString { + self.0.into_text() + } + + /// Get the identifier as a string slice. + pub fn as_str(&self) -> &str { + self.get() + } +} + +impl Deref for MathIdent { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +node! { + /// An alignment point in a formula: `&`. + MathAlignPoint +} + +node! { /// A subsection in a math formula that is surrounded by matched delimiters: /// `[x + y]`. - Delimited + MathDelimited } -impl Delimited { +impl MathDelimited { + /// The opening delimiter. + pub fn open(&self) -> MathAtom { + self.0.cast_first_match().unwrap_or_default() + } + /// The contents, including the delimiters. - pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { - self.0.children().filter_map(Expr::cast_with_space) + pub fn body(&self) -> Math { + self.0.cast_first_match().unwrap_or_default() + } + + /// The closing delimiter. + pub fn close(&self) -> MathAtom { + self.0.cast_last_match().unwrap_or_default() } } node! { /// A base with an optional sub- and superscript in a formula: `a_1^2`. - Script + MathScript } -impl Script { +impl MathScript { /// The base of the script. pub fn base(&self) -> Expr { - self.0.cast_first_match().expect("script node is missing base") + self.0.cast_first_match().unwrap_or_default() } /// The subscript. @@ -705,44 +775,35 @@ impl Script { node! { /// A fraction in a formula: `x/2` - Frac + MathFrac } -impl Frac { +impl MathFrac { /// The numerator. pub fn num(&self) -> Expr { - self.0.cast_first_match().expect("fraction is missing numerator") + self.0.cast_first_match().unwrap_or_default() } /// The denominator. pub fn denom(&self) -> Expr { - self.0.cast_last_match().expect("fraction is missing denominator") + self.0.cast_last_match().unwrap_or_default() } } node! { - /// An alignment point in a formula: `&`. - AlignPoint -} - -node! { /// An identifier: `it`. Ident } impl Ident { /// Get the identifier. - pub fn get(&self) -> &str { - self.0.text().trim_start_matches('#') + pub fn get(&self) -> &EcoString { + self.0.text() } /// Take out the contained identifier. pub fn take(self) -> EcoString { - let text = self.0.into_text(); - match text.strip_prefix('#') { - Some(text) => text.into(), - Option::None => text, - } + self.0.into_text() } /// Get the identifier as a string slice. @@ -789,7 +850,7 @@ node! { impl Int { /// Get the integer value. pub fn get(&self) -> i64 { - self.0.text().parse().expect("integer is invalid") + self.0.text().parse().unwrap_or_default() } } @@ -801,7 +862,7 @@ node! { impl Float { /// Get the floating-point value. pub fn get(&self) -> f64 { - self.0.text().parse().expect("float is invalid") + self.0.text().parse().unwrap_or_default() } } @@ -821,7 +882,7 @@ impl Numeric { .count(); let split = text.len() - count; - let value = text[..split].parse().expect("number is invalid"); + let value = text[..split].parse().unwrap_or_default(); let unit = match &text[split..] { "pt" => Unit::Length(AbsUnit::Pt), "mm" => Unit::Length(AbsUnit::Mm), @@ -910,7 +971,19 @@ node! { } impl CodeBlock { - /// The list of expressions contained in the block. + /// The contained code. + pub fn body(&self) -> Code { + self.0.cast_first_match().unwrap_or_default() + } +} + +node! { + /// Code. + Code +} + +impl Code { + /// The list of expressions contained in the code. pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ { self.0.children().filter_map(SyntaxNode::cast) } @@ -924,7 +997,7 @@ node! { impl ContentBlock { /// The contained markup. pub fn body(&self) -> Markup { - self.0.cast_first_match().expect("content block is missing body") + self.0.cast_first_match().unwrap_or_default() } } @@ -936,9 +1009,7 @@ node! { impl Parenthesized { /// The wrapped expression. pub fn expr(&self) -> Expr { - self.0 - .cast_first_match() - .expect("parenthesized expression is missing expression") + self.0.cast_first_match().unwrap_or_default() } } @@ -1029,12 +1100,12 @@ node! { impl Named { /// The name: `thickness`. pub fn name(&self) -> Ident { - self.0.cast_first_match().expect("named pair is missing name") + self.0.cast_first_match().unwrap_or_default() } /// The right-hand side of the pair: `3pt`. pub fn expr(&self) -> Expr { - self.0.cast_last_match().expect("named pair is missing expression") + self.0.cast_last_match().unwrap_or_default() } } @@ -1049,12 +1120,12 @@ impl Keyed { self.0 .children() .find_map(|node| node.cast::<Str>()) - .expect("keyed pair is missing key") + .unwrap_or_default() } /// The right-hand side of the pair: `true`. pub fn expr(&self) -> Expr { - self.0.cast_last_match().expect("keyed pair is missing expression") + self.0.cast_last_match().unwrap_or_default() } } @@ -1069,12 +1140,12 @@ impl Unary { self.0 .children() .find_map(|node| UnOp::from_kind(node.kind())) - .expect("unary operation is missing operator") + .unwrap_or(UnOp::Pos) } /// The expression to operate on: `x`. pub fn expr(&self) -> Expr { - self.0.cast_last_match().expect("unary operation is missing child") + self.0.cast_last_match().unwrap_or_default() } } @@ -1137,21 +1208,17 @@ impl Binary { SyntaxKind::In if not => Some(BinOp::NotIn), _ => BinOp::from_kind(node.kind()), }) - .expect("binary operation is missing operator") + .unwrap_or(BinOp::Add) } /// The left-hand side of the operation: `a`. pub fn lhs(&self) -> Expr { - self.0 - .cast_first_match() - .expect("binary operation is missing left-hand side") + self.0.cast_first_match().unwrap_or_default() } /// The right-hand side of the operation: `b`. pub fn rhs(&self) -> Expr { - self.0 - .cast_last_match() - .expect("binary operation is missing right-hand side") + self.0.cast_last_match().unwrap_or_default() } } @@ -1317,12 +1384,12 @@ node! { impl FieldAccess { /// The expression to access the field on. pub fn target(&self) -> Expr { - self.0.cast_first_match().expect("field access is missing object") + self.0.cast_first_match().unwrap_or_default() } /// The name of the field. pub fn field(&self) -> Ident { - self.0.cast_last_match().expect("field access is missing name") + self.0.cast_last_match().unwrap_or_default() } } @@ -1334,14 +1401,12 @@ node! { impl FuncCall { /// The function to call. pub fn callee(&self) -> Expr { - self.0.cast_first_match().expect("function call is missing callee") + self.0.cast_first_match().unwrap_or_default() } /// The arguments to the function. pub fn args(&self) -> Args { - self.0 - .cast_last_match() - .expect("function call is missing argument list") + self.0.cast_last_match().unwrap_or_default() } } @@ -1353,19 +1418,17 @@ node! { impl MethodCall { /// The expression to call the method on. pub fn target(&self) -> Expr { - self.0.cast_first_match().expect("method call is missing target") + self.0.cast_first_match().unwrap_or_default() } /// The name of the method. pub fn method(&self) -> Ident { - self.0.cast_last_match().expect("method call is missing name") + self.0.cast_last_match().unwrap_or_default() } /// The arguments to the method. pub fn args(&self) -> Args { - self.0 - .cast_last_match() - .expect("method call is missing argument list") + self.0.cast_last_match().unwrap_or_default() } } @@ -1428,14 +1491,13 @@ impl Closure { self.0 .children() .find(|x| x.kind() == SyntaxKind::Params) - .expect("closure is missing parameter list") - .children() + .map_or([].iter(), |params| params.children()) .filter_map(SyntaxNode::cast) } /// The body of the closure. pub fn body(&self) -> Expr { - self.0.cast_last_match().expect("closure is missing body") + self.0.cast_last_match().unwrap_or_default() } } @@ -1479,10 +1541,8 @@ impl LetBinding { pub fn binding(&self) -> Ident { match self.0.cast_first_match() { Some(Expr::Ident(binding)) => binding, - Some(Expr::Closure(closure)) => { - closure.name().expect("let-bound closure is missing name") - } - _ => panic!("let is missing binding"), + Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(), + _ => Ident::default(), } } @@ -1506,12 +1566,12 @@ node! { impl SetRule { /// The function to set style properties for. pub fn target(&self) -> Expr { - self.0.cast_first_match().expect("set rule is missing target") + self.0.cast_first_match().unwrap_or_default() } /// The style properties to set. pub fn args(&self) -> Args { - self.0.cast_last_match().expect("set rule is missing argument list") + self.0.cast_last_match().unwrap_or_default() } /// A condition under which the set rule applies. @@ -1540,7 +1600,7 @@ impl ShowRule { /// The transformation recipe. pub fn transform(&self) -> Expr { - self.0.cast_last_match().expect("show rule is missing transform") + self.0.cast_last_match().unwrap_or_default() } } @@ -1552,7 +1612,7 @@ node! { impl Conditional { /// The condition which selects the body to evaluate. pub fn condition(&self) -> Expr { - self.0.cast_first_match().expect("conditional is missing condition") + self.0.cast_first_match().unwrap_or_default() } /// The expression to evaluate if the condition is true. @@ -1561,7 +1621,7 @@ impl Conditional { .children() .filter_map(SyntaxNode::cast) .nth(1) - .expect("conditional is missing body") + .unwrap_or_default() } /// The expression to evaluate if the condition is false. @@ -1578,12 +1638,12 @@ node! { impl WhileLoop { /// The condition which selects whether to evaluate the body. pub fn condition(&self) -> Expr { - self.0.cast_first_match().expect("while loop is missing condition") + self.0.cast_first_match().unwrap_or_default() } /// The expression to evaluate while the condition is true. pub fn body(&self) -> Expr { - self.0.cast_last_match().expect("while loop is missing body") + self.0.cast_last_match().unwrap_or_default() } } @@ -1595,17 +1655,17 @@ node! { impl ForLoop { /// The pattern to assign to. pub fn pattern(&self) -> ForPattern { - self.0.cast_first_match().expect("for loop is missing pattern") + self.0.cast_first_match().unwrap_or_default() } /// The expression to iterate over. pub fn iter(&self) -> Expr { - self.0.cast_first_match().expect("for loop is missing iterable") + self.0.cast_first_match().unwrap_or_default() } /// The expression to evaluate for each iteration. pub fn body(&self) -> Expr { - self.0.cast_last_match().expect("for loop is missing body") + self.0.cast_last_match().unwrap_or_default() } } @@ -1628,7 +1688,7 @@ impl ForPattern { /// The value part of the pattern. pub fn value(&self) -> Ident { - self.0.cast_last_match().expect("for loop pattern is missing value") + self.0.cast_last_match().unwrap_or_default() } } @@ -1640,7 +1700,7 @@ node! { impl ModuleImport { /// The module or path from which the items should be imported. pub fn source(&self) -> Expr { - self.0.cast_last_match().expect("module import is missing source") + self.0.cast_last_match().unwrap_or_default() } /// The items to be imported. @@ -1673,7 +1733,7 @@ node! { impl ModuleInclude { /// The module or path from which the content should be included. pub fn source(&self) -> Expr { - self.0.cast_last_match().expect("module include is missing path") + self.0.cast_last_match().unwrap_or_default() } } diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 206df911..34e2fce7 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -58,19 +58,26 @@ pub enum SyntaxKind { /// Introduces a term item: `/`. TermMarker, /// A mathematical formula: `$x$`, `$ x^2 $`. + Formula, + + /// Mathematical markup. Math, /// An atom in math: `x`, `+`, `12`. - Atom, + MathAtom, + /// An identifier in math: `pi`. + MathIdent, + /// An alignment point in math: `&`. + MathAlignPoint, /// A subsection in a math formula that is surrounded by matched delimiters: /// `[x + y]`. - Delimited, + MathDelimited, /// A base with optional sub- and superscripts in math: `a_1^2`. - Script, + MathScript, /// A fraction in math: `x/2`. - Frac, - /// An alignment point in math: `&`. - AlignPoint, + MathFrac, + /// A hashtag that switches into code mode: `#`. + Hashtag, /// A left curly brace, starting a code block: `{`. LeftBrace, /// A right curly brace, terminating a code block: `}`. @@ -175,6 +182,8 @@ pub enum SyntaxKind { /// The `as` keyword. As, + /// Code. + Code, /// An identifier: `it`. Ident, /// A boolean: `true`, `false`. @@ -338,12 +347,15 @@ impl SyntaxKind { Self::EnumMarker => "enum marker", Self::TermItem => "term list item", Self::TermMarker => "term marker", - Self::Math => "math formula", - Self::Delimited => "delimited math", - Self::Atom => "math atom", - Self::Script => "script", - Self::Frac => "fraction", - Self::AlignPoint => "alignment point", + Self::Formula => "math formula", + Self::Math => "math", + Self::MathIdent => "math identifier", + Self::MathAtom => "math atom", + Self::MathAlignPoint => "math alignment point", + Self::MathDelimited => "delimited math", + Self::MathScript => "math script", + Self::MathFrac => "math fraction", + Self::Hashtag => "hashtag", Self::LeftBrace => "opening brace", Self::RightBrace => "closing brace", Self::LeftBracket => "opening bracket", @@ -394,6 +406,7 @@ impl SyntaxKind { Self::Import => "keyword `import`", Self::Include => "keyword `include`", Self::As => "keyword `as`", + Self::Code => "code", Self::Ident => "identifier", Self::Bool => "boolean", Self::Int => "integer", diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index 1064939d..0735270b 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -376,7 +376,7 @@ impl Lexer<'_> { Some('-') if !s.at(['-', '?']) => {} Some('.') if !s.at("..") => {} Some('h') if !s.at("ttp://") && !s.at("ttps://") => {} - Some('@' | '#') if !s.at(is_id_start) => {} + Some('@') if !s.at(is_id_start) => {} _ => break, } @@ -410,15 +410,8 @@ impl Lexer<'_> { '\\' => self.backslash(), ':' if self.s.at(is_id_start) => self.maybe_symbol(), '"' => self.string(), - '#' if self.s.eat_if('{') => SyntaxKind::LeftBrace, - '#' if self.s.eat_if('[') => SyntaxKind::LeftBracket, - '#' if self.s.at(is_id_start) => { - match keyword(self.s.eat_while(is_id_continue)) { - Some(keyword) => keyword, - None => SyntaxKind::Ident, - } - } + '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, '|' if self.s.eat_if("->") => SyntaxKind::Shorthand, '<' if self.s.eat_if("->") => SyntaxKind::Shorthand, '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, @@ -429,13 +422,17 @@ impl Lexer<'_> { '-' if self.s.eat_if('>') => SyntaxKind::Shorthand, '=' if self.s.eat_if('>') => SyntaxKind::Shorthand, ':' if self.s.eat_if('=') => SyntaxKind::Shorthand, - '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, + '[' if self.s.eat_if('|') => SyntaxKind::Shorthand, + '|' if self.s.eat_if(']') => SyntaxKind::Shorthand, + '|' if self.s.eat_if('|') => SyntaxKind::Shorthand, + '*' => SyntaxKind::Shorthand, + '#' if !self.s.at(char::is_whitespace) => SyntaxKind::Hashtag, '_' => SyntaxKind::Underscore, '$' => SyntaxKind::Dollar, '/' => SyntaxKind::Slash, '^' => SyntaxKind::Hat, - '&' => SyntaxKind::AlignPoint, + '&' => SyntaxKind::MathAlignPoint, // Identifiers and symbol notation. c if is_math_id_start(c) && self.s.at(is_math_id_continue) => { @@ -479,7 +476,7 @@ impl Lexer<'_> { .map_or(0, str::len); self.s.jump(start + len); } - SyntaxKind::Atom + SyntaxKind::MathAtom } } diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 038d13bc..d133fc5d 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -95,6 +95,11 @@ impl SyntaxNode { } } + /// Whether the node can be cast to the given AST node. + pub fn is<T: AstNode>(&self) -> bool { + self.cast::<T>().is_some() + } + /// Try to convert the node to a typed AST node. pub fn cast<T: AstNode>(&self) -> Option<T> { T::from_untyped(self) @@ -144,6 +149,16 @@ impl SyntaxNode { } } + /// Convert the child to another kind. + pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) { + debug_assert!(!kind.is_error()); + match &mut self.0 { + Repr::Leaf(leaf) => leaf.kind = kind, + Repr::Inner(inner) => Arc::make_mut(inner).kind = kind, + Repr::Error(_) => panic!("cannot convert error"), + } + } + /// Convert the child to an error. pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) { let len = self.len(); @@ -695,6 +710,14 @@ impl<'a> LinkedNode<'a> { Some(next) } } + + /// Whether an error follows directly after the node. + pub fn before_error(&self) -> bool { + let Some(parent) = self.parent() else { return false }; + let Some(index) = self.index.checked_add(1) else { return false }; + let Some(node) = parent.node.children().nth(index) else { return false }; + node.kind().is_error() + } } /// Access to leafs. @@ -865,8 +888,8 @@ mod tests { // Go back to "#set". Skips the space. let prev = node.prev_sibling().unwrap(); - assert_eq!(prev.offset(), 0); - assert_eq!(prev.text(), "#set"); + assert_eq!(prev.offset(), 1); + assert_eq!(prev.text(), "set"); } #[test] @@ -875,7 +898,7 @@ mod tests { let leaf = LinkedNode::new(source.root()).leaf_at(6).unwrap(); let prev = leaf.prev_leaf().unwrap(); assert_eq!(leaf.text(), "fun"); - assert_eq!(prev.text(), "#set"); + assert_eq!(prev.text(), "set"); let source = Source::detached("#let x = 10"); let leaf = LinkedNode::new(source.root()).leaf_at(9).unwrap(); diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 07f53372..a046b685 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -18,9 +18,7 @@ pub fn parse(text: &str) -> SyntaxNode { /// This is only used for syntax highlighting. pub fn parse_code(text: &str) -> SyntaxNode { let mut p = Parser::new(text, 0, LexMode::Code); - let m = p.marker(); code(&mut p, |_| false); - p.wrap(m, SyntaxKind::CodeBlock); p.finish().into_iter().next().unwrap() } @@ -31,7 +29,15 @@ fn markup( mut stop: impl FnMut(SyntaxKind) -> bool, ) { let m = p.marker(); - while !p.eof() && !stop(p.current) { + let mut nesting: usize = 0; + while !p.eof() { + match p.current() { + SyntaxKind::LeftBracket => nesting += 1, + SyntaxKind::RightBracket if nesting > 0 => nesting -= 1, + _ if stop(p.current) => break, + _ => {} + } + if p.newline() { at_start = true; if min_indent > 0 && p.column(p.current_end()) < min_indent { @@ -54,10 +60,18 @@ pub(super) fn reparse_markup( text: &str, range: Range<usize>, at_start: &mut bool, + nesting: &mut usize, mut stop: impl FnMut(SyntaxKind) -> bool, ) -> Option<Vec<SyntaxNode>> { let mut p = Parser::new(text, range.start, LexMode::Markup); - while !p.eof() && !stop(p.current) && p.current_start() < range.end { + while !p.eof() && p.current_start() < range.end { + match p.current() { + SyntaxKind::LeftBracket => *nesting += 1, + SyntaxKind::RightBracket if *nesting > 0 => *nesting -= 1, + _ if stop(p.current) => break, + _ => {} + } + if p.newline() { *at_start = true; p.eat(); @@ -75,53 +89,41 @@ pub(super) fn reparse_markup( fn markup_expr(p: &mut Parser, at_start: &mut bool) { match p.current() { + SyntaxKind::Space + | SyntaxKind::Parbreak + | SyntaxKind::LineComment + | SyntaxKind::BlockComment => { + p.eat(); + return; + } + + SyntaxKind::Text + | SyntaxKind::Linebreak + | SyntaxKind::Escape + | SyntaxKind::Shorthand + | SyntaxKind::SmartQuote + | SyntaxKind::Raw + | SyntaxKind::Link + | SyntaxKind::Label + | SyntaxKind::Ref => p.eat(), + + SyntaxKind::Hashtag => embedded_code_expr(p), SyntaxKind::Star => strong(p), SyntaxKind::Underscore => emph(p), SyntaxKind::HeadingMarker if *at_start => heading(p), SyntaxKind::ListMarker if *at_start => list_item(p), SyntaxKind::EnumMarker if *at_start => enum_item(p), SyntaxKind::TermMarker if *at_start => term_item(p), - SyntaxKind::Dollar => equation(p), + SyntaxKind::Dollar => formula(p), - SyntaxKind::HeadingMarker + SyntaxKind::LeftBracket + | SyntaxKind::RightBracket + | SyntaxKind::HeadingMarker | SyntaxKind::ListMarker | SyntaxKind::EnumMarker | SyntaxKind::TermMarker | SyntaxKind::Colon => p.convert(SyntaxKind::Text), - SyntaxKind::Ident - | SyntaxKind::Let - | SyntaxKind::Set - | SyntaxKind::Show - | SyntaxKind::If - | SyntaxKind::While - | SyntaxKind::For - | SyntaxKind::Import - | SyntaxKind::Include - | SyntaxKind::Break - | SyntaxKind::Continue - | SyntaxKind::Return - | SyntaxKind::LeftBrace - | SyntaxKind::LeftBracket => embedded_code_expr(p), - - SyntaxKind::Text - | SyntaxKind::Linebreak - | SyntaxKind::Escape - | SyntaxKind::Shorthand - | SyntaxKind::Symbol - | SyntaxKind::SmartQuote - | SyntaxKind::Raw - | SyntaxKind::Link - | SyntaxKind::Label - | SyntaxKind::Ref => p.eat(), - - SyntaxKind::Space - | SyntaxKind::Parbreak - | SyntaxKind::LineComment - | SyntaxKind::BlockComment => { - p.eat(); - return; - } _ => {} } @@ -130,7 +132,7 @@ fn markup_expr(p: &mut Parser, at_start: &mut bool) { fn strong(p: &mut Parser) { let m = p.marker(); - p.expect(SyntaxKind::Star); + p.assert(SyntaxKind::Star); markup(p, false, 0, |kind| { kind == SyntaxKind::Star || kind == SyntaxKind::Parbreak @@ -142,7 +144,7 @@ fn strong(p: &mut Parser) { fn emph(p: &mut Parser) { let m = p.marker(); - p.expect(SyntaxKind::Underscore); + p.assert(SyntaxKind::Underscore); markup(p, false, 0, |kind| { kind == SyntaxKind::Underscore || kind == SyntaxKind::Parbreak @@ -154,7 +156,7 @@ fn emph(p: &mut Parser) { fn heading(p: &mut Parser) { let m = p.marker(); - p.expect(SyntaxKind::HeadingMarker); + p.assert(SyntaxKind::HeadingMarker); whitespace(p); markup(p, false, usize::MAX, |kind| { kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket @@ -164,7 +166,7 @@ fn heading(p: &mut Parser) { fn list_item(p: &mut Parser) { let m = p.marker(); - p.expect(SyntaxKind::ListMarker); + p.assert(SyntaxKind::ListMarker); let min_indent = p.column(p.prev_end()); whitespace(p); markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket); @@ -173,7 +175,7 @@ fn list_item(p: &mut Parser) { fn enum_item(p: &mut Parser) { let m = p.marker(); - p.expect(SyntaxKind::EnumMarker); + p.assert(SyntaxKind::EnumMarker); let min_indent = p.column(p.prev_end()); whitespace(p); markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket); @@ -182,7 +184,7 @@ fn enum_item(p: &mut Parser) { fn term_item(p: &mut Parser) { let m = p.marker(); - p.expect(SyntaxKind::TermMarker); + p.assert(SyntaxKind::TermMarker); let min_indent = p.column(p.prev_end()); whitespace(p); markup(p, false, usize::MAX, |kind| { @@ -200,17 +202,18 @@ fn whitespace(p: &mut Parser) { } } -fn equation(p: &mut Parser) { +fn formula(p: &mut Parser) { let m = p.marker(); p.enter(LexMode::Math); - p.expect(SyntaxKind::Dollar); + p.assert(SyntaxKind::Dollar); math(p, |kind| kind == SyntaxKind::Dollar); p.expect(SyntaxKind::Dollar); p.exit(); - p.wrap(m, SyntaxKind::Math); + p.wrap(m, SyntaxKind::Formula); } fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { + let m = p.marker(); while !p.eof() && !stop(p.current()) { let prev = p.prev_end(); math_expr(p); @@ -218,6 +221,7 @@ fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { p.unexpected(); } } + p.wrap(m, SyntaxKind::Math); } fn math_expr(p: &mut Parser) { @@ -227,45 +231,44 @@ fn math_expr(p: &mut Parser) { fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { let m = p.marker(); match p.current() { - SyntaxKind::Ident => { + SyntaxKind::Hashtag => embedded_code_expr(p), + SyntaxKind::MathIdent => { p.eat(); - if p.directly_at(SyntaxKind::Atom) && p.current_text() == "(" { + if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" { math_args(p); p.wrap(m, SyntaxKind::FuncCall); + } else { + while p.directly_at(SyntaxKind::MathAtom) + && p.current_text() == "." + && matches!( + p.lexer.clone().next(), + SyntaxKind::MathIdent | SyntaxKind::MathAtom + ) + { + p.convert(SyntaxKind::Dot); + p.convert(SyntaxKind::Ident); + p.wrap(m, SyntaxKind::FieldAccess); + } } } - SyntaxKind::Atom if math_class(p.current_text()) == Some(MathClass::Fence) => { - math_delimited(p, MathClass::Fence) - } - - SyntaxKind::Atom if math_class(p.current_text()) == Some(MathClass::Opening) => { - math_delimited(p, MathClass::Closing) + SyntaxKind::MathAtom => { + if math_class(p.current_text()) == Some(MathClass::Fence) { + math_delimited(p, MathClass::Fence) + } else if math_class(p.current_text()) == Some(MathClass::Opening) { + math_delimited(p, MathClass::Closing) + } else { + p.eat() + } } - SyntaxKind::Let - | SyntaxKind::Set - | SyntaxKind::Show - | SyntaxKind::If - | SyntaxKind::While - | SyntaxKind::For - | SyntaxKind::Import - | SyntaxKind::Include - | SyntaxKind::Break - | SyntaxKind::Continue - | SyntaxKind::Return - | SyntaxKind::LeftBrace - | SyntaxKind::LeftBracket => embedded_code_expr(p), - - SyntaxKind::Atom - | SyntaxKind::Linebreak + SyntaxKind::Linebreak | SyntaxKind::Escape | SyntaxKind::Shorthand - | SyntaxKind::Symbol - | SyntaxKind::AlignPoint + | SyntaxKind::MathAlignPoint | SyntaxKind::Str => p.eat(), - _ => return, + _ => p.expected("expression"), } while !p.eof() && !p.at(stop) { @@ -282,10 +285,19 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { ast::Assoc::Right => {} } + if kind == SyntaxKind::MathFrac { + math_unparen(p, m); + } + p.eat(); + let m2 = p.marker(); math_expr_prec(p, prec, stop); + math_unparen(p, m2); + if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { + let m3 = p.marker(); math_expr_prec(p, prec, SyntaxKind::Eof); + math_unparen(p, m3); } p.wrap(m, kind); @@ -294,11 +306,13 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { fn math_delimited(p: &mut Parser, stop: MathClass) { let m = p.marker(); - p.expect(SyntaxKind::Atom); + p.assert(SyntaxKind::MathAtom); + let m2 = p.marker(); while !p.eof() && !p.at(SyntaxKind::Dollar) { if math_class(p.current_text()) == Some(stop) { - p.eat(); - p.wrap(m, SyntaxKind::Delimited); + p.wrap(m2, SyntaxKind::Math); + p.assert(SyntaxKind::MathAtom); + p.wrap(m, SyntaxKind::MathDelimited); return; } @@ -310,6 +324,22 @@ fn math_delimited(p: &mut Parser, stop: MathClass) { } } +fn math_unparen(p: &mut Parser, m: Marker) { + let Some(node) = p.nodes.get_mut(m.0) else { return }; + if node.kind() != SyntaxKind::MathDelimited { + return; + } + + if let [first, .., last] = node.children_mut() { + if first.text() == "(" && last.text() == ")" { + first.convert_to_kind(SyntaxKind::LeftParen); + last.convert_to_kind(SyntaxKind::RightParen); + } + } + + node.convert_to_kind(SyntaxKind::Math); +} + fn math_class(text: &str) -> Option<MathClass> { let mut chars = text.chars(); chars @@ -321,20 +351,20 @@ fn math_class(text: &str) -> Option<MathClass> { fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> { match kind { SyntaxKind::Underscore => { - Some((SyntaxKind::Script, SyntaxKind::Hat, ast::Assoc::Right, 2)) + Some((SyntaxKind::MathScript, SyntaxKind::Hat, ast::Assoc::Right, 2)) } SyntaxKind::Hat => { - Some((SyntaxKind::Script, SyntaxKind::Underscore, ast::Assoc::Right, 2)) + Some((SyntaxKind::MathScript, SyntaxKind::Underscore, ast::Assoc::Right, 2)) } SyntaxKind::Slash => { - Some((SyntaxKind::Frac, SyntaxKind::Eof, ast::Assoc::Left, 1)) + Some((SyntaxKind::MathFrac, SyntaxKind::Eof, ast::Assoc::Left, 1)) } _ => None, } } fn math_args(p: &mut Parser) { - p.expect(SyntaxKind::Atom); + p.assert(SyntaxKind::MathAtom); let m = p.marker(); let mut m2 = p.marker(); while !p.eof() && !p.at(SyntaxKind::Dollar) { @@ -359,10 +389,11 @@ fn math_args(p: &mut Parser) { p.wrap(m2, SyntaxKind::Math); } p.wrap(m, SyntaxKind::Args); - p.expect(SyntaxKind::Atom); + p.expect(SyntaxKind::MathAtom); } fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { + let m = p.marker(); while !p.eof() && !stop(p.current()) { p.stop_at_newline(true); let prev = p.prev_end(); @@ -379,6 +410,7 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { p.unexpected(); } } + p.wrap(m, SyntaxKind::Code); } fn code_expr(p: &mut Parser) { @@ -386,6 +418,10 @@ fn code_expr(p: &mut Parser) { } fn embedded_code_expr(p: &mut Parser) { + p.stop_at_newline(true); + p.enter(LexMode::Code); + p.assert(SyntaxKind::Hashtag); + let stmt = matches!( p.current(), SyntaxKind::Let @@ -395,13 +431,12 @@ fn embedded_code_expr(p: &mut Parser) { | SyntaxKind::Include ); - p.stop_at_newline(true); - p.enter(LexMode::Code); code_expr_prec(p, true, 0); let semi = p.eat_if(SyntaxKind::Semicolon); if stmt && !semi && !p.eof() && !p.at(SyntaxKind::RightBracket) { p.expected("semicolon or line break"); } + p.exit(); p.unstop(); } @@ -424,7 +459,10 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) { continue; } - if atomic { + let at_field_or_method = + p.directly_at(SyntaxKind::Dot) && p.lexer.clone().next() == SyntaxKind::Ident; + + if atomic && !at_field_or_method { break; } @@ -480,7 +518,7 @@ fn code_primary(p: &mut Parser, atomic: bool) { p.eat(); if !atomic && p.at(SyntaxKind::Arrow) { p.wrap(m, SyntaxKind::Params); - p.expect(SyntaxKind::Arrow); + p.assert(SyntaxKind::Arrow); code_expr(p); p.wrap(m, SyntaxKind::Closure); } @@ -489,7 +527,7 @@ fn code_primary(p: &mut Parser, atomic: bool) { SyntaxKind::LeftBrace => code_block(p), SyntaxKind::LeftBracket => content_block(p), SyntaxKind::LeftParen => with_paren(p), - SyntaxKind::Dollar => equation(p), + SyntaxKind::Dollar => formula(p), SyntaxKind::Let => let_binding(p), SyntaxKind::Set => set_rule(p), SyntaxKind::Show => show_rule(p), @@ -536,7 +574,7 @@ fn code_block(p: &mut Parser) { let m = p.marker(); p.enter(LexMode::Code); p.stop_at_newline(false); - p.expect(SyntaxKind::LeftBrace); + p.assert(SyntaxKind::LeftBrace); code(p, |kind| kind == SyntaxKind::RightBrace); p.expect(SyntaxKind::RightBrace); p.exit(); @@ -547,7 +585,7 @@ fn code_block(p: &mut Parser) { fn content_block(p: &mut Parser) { let m = p.marker(); p.enter(LexMode::Markup); - p.expect(SyntaxKind::LeftBracket); + p.assert(SyntaxKind::LeftBracket); markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket); p.expect(SyntaxKind::RightBracket); p.exit(); @@ -560,7 +598,7 @@ fn with_paren(p: &mut Parser) { if p.at(SyntaxKind::Arrow) { validate_params(p, m); p.wrap(m, SyntaxKind::Params); - p.expect(SyntaxKind::Arrow); + p.assert(SyntaxKind::Arrow); code_expr(p); kind = SyntaxKind::Closure; } @@ -574,7 +612,7 @@ fn with_paren(p: &mut Parser) { fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { p.stop_at_newline(false); - p.expect(SyntaxKind::LeftParen); + p.assert(SyntaxKind::LeftParen); let mut count = 0; let mut parenthesized = true; diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs index 4c01e4cc..de845abf 100644 --- a/src/syntax/reparser.rs +++ b/src/syntax/reparser.rs @@ -114,20 +114,30 @@ fn try_reparse( end += 1; } - // Synthesize what `at_start` would be at the start of the reparse. + // Also take hashtag. + if start > 0 && children[start - 1].kind() == SyntaxKind::Hashtag { + start -= 1; + } + + // Synthesize what `at_start` and `nesting` would be at the start of the + // reparse. let mut prefix_len = 0; + let mut nesting = 0; let mut at_start = true; for child in &children[..start] { prefix_len += child.len(); next_at_start(child, &mut at_start); + next_nesting(child, &mut nesting); } // Determine what `at_start` will have to be at the end of the reparse. let mut prev_len = 0; let mut prev_at_start_after = at_start; + let mut prev_nesting_after = nesting; for child in &children[start..end] { prev_len += child.len(); next_at_start(child, &mut prev_at_start_after); + next_nesting(child, &mut prev_nesting_after); } let shifted = offset + prefix_len; @@ -139,11 +149,11 @@ fn try_reparse( }; if let Some(newborns) = - reparse_markup(text, new_range.clone(), &mut at_start, |kind| { + reparse_markup(text, new_range.clone(), &mut at_start, &mut nesting, |kind| { kind == stop_kind }) { - if at_start == prev_at_start_after { + if at_start == prev_at_start_after && nesting == prev_nesting_after { return node .replace_children(start..end, newborns) .is_ok() @@ -188,6 +198,17 @@ fn next_at_start(node: &SyntaxNode, at_start: &mut bool) { } } +/// Update `nesting` based on the node. +fn next_nesting(node: &SyntaxNode, nesting: &mut usize) { + if node.kind() == SyntaxKind::Text { + match node.text().as_str() { + "[" => *nesting += 1, + "]" if *nesting > 0 => *nesting -= 1, + _ => {} + } + } +} + #[cfg(test)] mod tests { use std::ops::Range; @@ -209,9 +230,13 @@ mod tests { panic!("test failed"); } if incremental { - assert_ne!(source.len_bytes(), range.len()); + assert_ne!(source.len_bytes(), range.len(), "should have been incremental"); } else { - assert_eq!(source.len_bytes(), range.len()); + assert_eq!( + source.len_bytes(), + range.len(), + "shouldn't have been incremental" + ); } } @@ -220,6 +245,7 @@ mod tests { test("abc~def~ghi", 5..6, "+", true); test("~~~~~~~", 3..4, "A", true); test("abc~~", 1..2, "", true); + test("#var. hello", 5..6, " ", false); test("#var;hello", 9..10, "a", false); test("https:/world", 7..7, "/", false); test("hello world", 7..12, "walkers", false); @@ -228,8 +254,8 @@ mod tests { test("a d e", 1..3, " b c d", false); test("~*~*~", 2..2, "*", false); test("::1\n2. a\n3", 7..7, "4", true); - test("* {1+2} *", 5..6, "3", true); - test("{(0, 1, 2)}", 5..6, "11pt", false); + test("* #{1+2} *", 6..7, "3", true); + test("#{(0, 1, 2)}", 6..7, "11pt", true); test("\n= A heading", 4..4, "n evocative", false); test("#call() abc~d", 7..7, "[]", true); test("a your thing a", 6..7, "a", false); @@ -239,24 +265,26 @@ mod tests { test("#for", 4..4, "//", false); test("a\n#let \nb", 7..7, "i", true); test("#let x = (1, 2 + ;~ Five\r\n\r", 20..23, "2.", true); - test(r"{{let x = z}; a = 1} b", 6..6, "//", false); + test(r"#{{let x = z}; a = 1} b", 7..7, "//", false); test(r#"a ```typst hello```"#, 16..17, "", false); } #[test] fn test_reparse_block() { - test("Hello { x + 1 }!", 8..9, "abc", true); - test("A{}!", 2..2, "\"", false); - test("{ [= x] }!", 4..4, "=", true); - test("[[]]", 2..2, "\\", false); - test("[[ab]]", 3..4, "\\", false); - test("{}}", 1..1, "{", false); - test("A: [BC]", 5..5, "{", false); - test("A: [BC]", 5..5, "{}", true); - test("{\"ab\"}A", 4..4, "c", true); - test("{\"ab\"}A", 4..5, "c", false); - test("a[]b", 2..2, "{", false); - test("a{call(); abc}b", 7..7, "[]", true); + test("Hello #{ x + 1 }!", 9..10, "abc", true); + test("A#{}!", 3..3, "\"", false); + test("#{ [= x] }!", 5..5, "=", true); + test("#[[]]", 3..3, "\\", false); + test("#[[ab]]", 4..5, "\\", false); + test("#{}}", 2..2, "{", false); + test("A: #[BC]", 6..6, "{", true); + test("A: #[BC]", 6..6, "#{", false); + test("A: #[BC]", 6..6, "#{}", true); + test("#{\"ab\"}A", 5..5, "c", true); + test("#{\"ab\"}A", 5..6, "c", false); + test("a#[]b", 3..3, "#{", false); + test("a#{call(); abc}b", 8..8, "[]", true); test("a #while x {\n g(x) \n} b", 12..12, "//", true); + test("a#[]b", 3..3, "[hey]", true); } } |
