diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index c170186fe..65d9ded87 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -7,9 +7,7 @@ use crate::World; /// Try to determine a set of possible values for an expression. pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec { match node.cast::() { - Some( - ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::MethodCall(_), - ) => { + Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::FuncCall(_)) => { if let Some(parent) = node.parent() { if parent.kind() == SyntaxKind::FieldAccess && node.index() > 0 { return analyze(world, parent); diff --git a/src/ide/complete.rs b/src/ide/complete.rs index 83202e30f..143e22b95 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -4,7 +4,7 @@ use if_chain::if_chain; use super::{analyze, plain_docs_sentence, summarize_font_family}; use crate::model::{methods_on, CastInfo, Scope, Value}; -use crate::syntax::{ast, LinkedNode, Source, SyntaxKind, SyntaxNode}; +use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; use crate::util::{format_eco, EcoString}; use crate::World; @@ -936,9 +936,7 @@ impl<'a> CompletionContext<'a> { if let Some(parent) = node.parent() { if let Some(v) = parent.cast::() { - if node.prev_sibling().as_deref().map(SyntaxNode::kind) - != Some(SyntaxKind::In) - { + if node.prev_sibling_kind() != Some(SyntaxKind::In) { let pattern = v.pattern(); if let Some(key) = pattern.key() { defined.insert(key.take()); diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index d8f15f000..ede13d7f6 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -85,8 +85,7 @@ pub fn highlight(node: &LinkedNode) -> Option { match node.kind() { SyntaxKind::Markup if node.parent_kind() == Some(SyntaxKind::TermItem) - && node.next_sibling().as_ref().map(|v| v.kind()) - == Some(SyntaxKind::Colon) => + && node.next_sibling_kind() == Some(SyntaxKind::Colon) => { Some(Category::ListTerm) } @@ -116,17 +115,12 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::Math => None, SyntaxKind::MathIdent => highlight_ident(node), + SyntaxKind::MathAlignPoint => Some(Category::MathOperator), SyntaxKind::MathDelimited => None, SyntaxKind::MathAttach => None, SyntaxKind::MathFrac => None, - SyntaxKind::MathAlignPoint => Some(Category::MathOperator), - - SyntaxKind::Hashtag => node - .next_sibling() - .filter(|node| node.cast::().map_or(false, |e| e.hashtag())) - .and_then(|node| node.leftmost_leaf()) - .and_then(|node| highlight(&node)), + SyntaxKind::Hashtag => highlight_hashtag(node), SyntaxKind::LeftBrace => Some(Category::Punctuation), SyntaxKind::RightBrace => Some(Category::Punctuation), SyntaxKind::LeftBracket => Some(Category::Punctuation), @@ -206,18 +200,8 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::Keyed => None, SyntaxKind::Unary => None, SyntaxKind::Binary => None, - SyntaxKind::FieldAccess => match node.parent_kind() { - Some( - SyntaxKind::Markup - | SyntaxKind::Math - | SyntaxKind::MathFrac - | SyntaxKind::MathAttach, - ) => Some(Category::Interpolated), - Some(SyntaxKind::FieldAccess) => node.parent().and_then(highlight), - _ => None, - }, + SyntaxKind::FieldAccess => None, SyntaxKind::FuncCall => None, - SyntaxKind::MethodCall => None, SyntaxKind::Args => None, SyntaxKind::Spread => None, SyntaxKind::Closure => None, @@ -245,49 +229,60 @@ pub fn highlight(node: &LinkedNode) -> Option { /// Highlight an identifier based on context. fn highlight_ident(node: &LinkedNode) -> Option { - match node.parent_kind() { - 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) - } - Some( - SyntaxKind::Markup - | SyntaxKind::Math - | SyntaxKind::MathFrac - | SyntaxKind::MathAttach, - ) => Some(Category::Interpolated), - _ if node.kind() == SyntaxKind::MathIdent => Some(Category::Interpolated), - _ => None, + // Are we directly before an argument list? + let next_leaf_kind = node.next_leaf().map(|leaf| leaf.kind()); + if matches!(next_leaf_kind, Some(SyntaxKind::LeftParen | SyntaxKind::LeftBracket)) { + return Some(Category::Function); } + + // Are we in math? + if node.kind() == SyntaxKind::MathIdent { + return Some(Category::Interpolated); + } + + // Find the first non-field access ancestor. + let mut ancestor = node; + while ancestor.parent_kind() == Some(SyntaxKind::FieldAccess) { + ancestor = ancestor.parent()?; + } + + // Are we directly before a show rule colon? + if next_leaf_kind == Some(SyntaxKind::Colon) + && ancestor.parent_kind() == Some(SyntaxKind::ShowRule) + { + return Some(Category::Function); + } + + // Are we (or an ancestor field access) directly after a hashtag. + if ancestor.prev_leaf().map(|leaf| leaf.kind()) == Some(SyntaxKind::Hashtag) { + return Some(Category::Interpolated); + } + + // Are we behind a dot, that is behind another identifier? + let prev = node.prev_leaf()?; + if prev.kind() == SyntaxKind::Dot { + let prev_prev = prev.prev_leaf()?; + if is_ident(&prev_prev) { + return highlight_ident(&prev_prev); + } + } + + None +} + +/// Highlight a hashtag based on context. +fn highlight_hashtag(node: &LinkedNode) -> Option { + let next = node.next_sibling()?; + let expr = next.cast::()?; + if !expr.hashtag() { + return None; + } + highlight(&next.leftmost_leaf()?) +} + +/// Whether the node is one of the two identifier nodes. +fn is_ident(node: &LinkedNode) -> bool { + matches!(node.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent) } #[cfg(test)] diff --git a/src/model/eval.rs b/src/model/eval.rs index b63069bfe..d0751a1f6 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -364,7 +364,6 @@ impl Eval for ast::Expr { Self::Parenthesized(v) => v.eval(vm), Self::FieldAccess(v) => v.eval(vm), Self::FuncCall(v) => v.eval(vm), - Self::MethodCall(v) => v.eval(vm), Self::Closure(v) => v.eval(vm), Self::Unary(v) => v.eval(vm), Self::Binary(v) => v.eval(vm), @@ -918,12 +917,51 @@ impl Eval for ast::FuncCall { type Output = Value; fn eval(&self, vm: &mut Vm) -> SourceResult { - let callee_expr = self.callee(); - let callee_span = callee_expr.span(); - let callee = callee_expr.eval(vm)?; - let mut args = self.args().eval(vm)?; + let span = self.span(); + let callee = self.callee(); + let in_math = in_math(&callee); + let callee_span = callee.span(); + let args = self.args(); - if in_math(&callee_expr) && !matches!(callee, Value::Func(_)) { + // Try to evaluate as a method call. This is possible if the callee is a + // field access and does not evaluate to a module. + let (callee, mut args) = if let ast::Expr::FieldAccess(access) = callee { + let target = access.target(); + let method = access.field(); + let method_span = method.span(); + let method = method.take(); + let point = || Tracepoint::Call(Some(method.clone())); + if methods::is_mutating(&method) { + let args = args.eval(vm)?; + let value = target.access(vm)?; + + let value = if let Value::Module(module) = &value { + module.get(&method).cloned().at(method_span)? + } else { + return methods::call_mut(value, &method, args, span) + .trace(vm.world, point, span); + }; + + (value, args) + } else { + let target = target.eval(vm)?; + let args = args.eval(vm)?; + let value = if let Value::Module(module) = &target { + module.get(&method).cloned().at(method_span)? + } else { + return methods::call(vm, target, &method, args, span) + .trace(vm.world, point, span); + }; + (value, args) + } + } else { + (callee.eval(vm)?, args.eval(vm)?) + }; + + // Handle math special cases for non-functions: + // Combining accent symbols apply themselves while everything else + // simply displays the arguments verbatim. + if in_math && !matches!(callee, Value::Func(_)) { if let Value::Symbol(sym) = &callee { let c = sym.get(); if let Some(accent) = combining_accent(c) { @@ -932,7 +970,6 @@ impl Eval for ast::FuncCall { return Ok(Value::Content((vm.items.math_accent)(base, accent))); } } - let mut body = (vm.items.text)('('.into()); for (i, arg) in args.all::()?.into_iter().enumerate() { if i > 0 { @@ -944,8 +981,14 @@ impl Eval for ast::FuncCall { return Ok(Value::Content(callee.display() + body)); } + // Finally, just a normal function call! + if vm.depth >= MAX_CALL_DEPTH { + bail!(span, "maximum function call depth exceeded"); + } + let callee = callee.cast::().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) } } @@ -957,59 +1000,6 @@ fn in_math(expr: &ast::Expr) -> bool { } } -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; - - fn eval(&self, vm: &mut Vm) -> SourceResult { - let span = self.span(); - let method = self.method(); - - let result = if methods::is_mutating(&method) { - let args = self.args().eval(vm)?; - let value = self.target().access(vm)?; - - if let Value::Module(module) = &value { - if let Value::Func(callee) = - module.get(&method).cloned().at(method.span())? - { - return complete_call(vm, &callee, args, self.span()); - } - } - - methods::call_mut(value, &method, args, span) - } else { - let value = self.target().eval(vm)?; - let args = self.args().eval(vm)?; - - if let Value::Module(module) = &value { - if let Value::Func(callee) = module.get(&method).at(method.span())? { - return complete_call(vm, callee, args, self.span()); - } - } - - methods::call(vm, value, &method, args, span) - }; - - let method = method.take(); - let point = || Tracepoint::Call(Some(method.clone())); - result.trace(vm.world, point, span) - } -} - impl Eval for ast::Args { type Output = Args; @@ -1223,8 +1213,12 @@ impl Eval for ast::WhileLoop { fn is_invariant(expr: &SyntaxNode) -> bool { match expr.cast() { Some(ast::Expr::Ident(_)) => false, - Some(ast::Expr::MethodCall(call)) => { - is_invariant(call.target().as_untyped()) + Some(ast::Expr::MathIdent(_)) => false, + Some(ast::Expr::FieldAccess(access)) => { + is_invariant(access.target().as_untyped()) + } + Some(ast::Expr::FuncCall(call)) => { + is_invariant(call.callee().as_untyped()) && is_invariant(call.args().as_untyped()) } _ => expr.children().all(is_invariant), @@ -1434,7 +1428,7 @@ impl Access for ast::Expr { Self::Ident(v) => v.access(vm), Self::Parenthesized(v) => v.access(vm), Self::FieldAccess(v) => v.access(vm), - Self::MethodCall(v) => v.access(vm), + Self::FuncCall(v) => v.access(vm), _ => { let _ = self.eval(vm)?; bail!(self.span(), "cannot mutate a temporary value"); @@ -1479,22 +1473,22 @@ impl ast::FieldAccess { } } -impl Access for ast::MethodCall { +impl Access for ast::FuncCall { fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { - let span = self.span(); - let method = self.method().take(); - let world = vm.world(); - - if !methods::is_accessor(&method) { - let _ = self.eval(vm)?; - bail!(span, "cannot mutate a temporary value"); + if let ast::Expr::FieldAccess(access) = self.callee() { + let method = access.field().take(); + if methods::is_accessor(&method) { + let span = self.span(); + let world = vm.world(); + let args = self.args().eval(vm)?; + let value = access.target().access(vm)?; + let result = methods::call_access(value, &method, args, span); + let point = || Tracepoint::Call(Some(method.clone())); + return result.trace(world, point, span); + } } - let args = self.args().eval(vm)?; - let value = self.target().access(vm)?; - let result = methods::call_access(value, &method, args, span); - - let point = || Tracepoint::Call(Some(method.clone())); - result.trace(world, point, span) + let _ = self.eval(vm)?; + bail!(self.span(), "cannot mutate a temporary value"); } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 5704f1712..78d895fff 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -157,10 +157,8 @@ pub enum Expr { Binary(Binary), /// A field access: `properties.age`. FieldAccess(FieldAccess), - /// An invocation of a function: `f(x, y)`. + /// An invocation of a function or method: `f(x, y)`. FuncCall(FuncCall), - /// An invocation of a method: `array.push(v)`. - MethodCall(MethodCall), /// A closure: `(x, y) => z`. Closure(Closure), /// A let binding: `let x = 1`. @@ -239,7 +237,6 @@ impl AstNode for Expr { SyntaxKind::Binary => node.cast().map(Self::Binary), SyntaxKind::FieldAccess => node.cast().map(Self::FieldAccess), SyntaxKind::FuncCall => node.cast().map(Self::FuncCall), - SyntaxKind::MethodCall => node.cast().map(Self::MethodCall), SyntaxKind::Closure => node.cast().map(Self::Closure), SyntaxKind::LetBinding => node.cast().map(Self::Let), SyntaxKind::SetRule => node.cast().map(Self::Set), @@ -299,7 +296,6 @@ impl AstNode for Expr { Self::Binary(v) => v.as_untyped(), Self::FieldAccess(v) => v.as_untyped(), Self::FuncCall(v) => v.as_untyped(), - Self::MethodCall(v) => v.as_untyped(), Self::Closure(v) => v.as_untyped(), Self::Let(v) => v.as_untyped(), Self::Set(v) => v.as_untyped(), @@ -335,7 +331,6 @@ impl Expr { Self::Parenthesized(_) => true, Self::FieldAccess(_) => true, Self::FuncCall(_) => true, - Self::MethodCall(_) => true, Self::Let(_) => true, Self::Set(_) => true, Self::Show(_) => true, @@ -1403,7 +1398,7 @@ impl FieldAccess { } node! { - /// An invocation of a function: `f(x, y)`. + /// An invocation of a function or method: `f(x, y)`. FuncCall } @@ -1419,28 +1414,6 @@ impl FuncCall { } } -node! { - /// An invocation of a method: `array.push(v)`. - MethodCall -} - -impl MethodCall { - /// The expression to call the method on. - pub fn target(&self) -> Expr { - self.0.cast_first_match().unwrap_or_default() - } - - /// The name of the method. - pub fn method(&self) -> Ident { - self.0.cast_last_match().unwrap_or_default() - } - - /// The arguments to the method. - pub fn args(&self) -> Args { - self.0.cast_last_match().unwrap_or_default() - } -} - node! { /// A function call's argument list: `(12pt, y)`. Args diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index b2b65a626..cf973e6ad 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -210,10 +210,8 @@ pub enum SyntaxKind { Binary, /// A field access: `properties.age`. FieldAccess, - /// An invocation of a function: `f(x, y)`. + /// An invocation of a function or method: `f(x, y)`. FuncCall, - /// An invocation of a method: `array.push(v)`. - MethodCall, /// A function call's argument list: `(12pt, y)`. Args, /// Spreaded arguments or an argument sink: `..x`. @@ -416,7 +414,6 @@ impl SyntaxKind { Self::Binary => "binary expression", Self::FieldAccess => "field access", Self::FuncCall => "function call", - Self::MethodCall => "method call", Self::Args => "call arguments", Self::Spread => "spread", Self::Closure => "closure", diff --git a/src/syntax/node.rs b/src/syntax/node.rs index ed0007882..049275ed0 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -681,11 +681,6 @@ impl<'a> LinkedNode<'a> { self.parent.as_deref() } - /// Get the kind of this node's parent. - pub fn parent_kind(&self) -> Option { - Some(self.parent()?.node.kind()) - } - /// Get the first previous non-trivia sibling node. pub fn prev_sibling(&self) -> Option { let parent = self.parent()?; @@ -713,6 +708,21 @@ impl<'a> LinkedNode<'a> { Some(next) } } + + /// Get the kind of this node's parent. + pub fn parent_kind(&self) -> Option { + Some(self.parent()?.node.kind()) + } + + /// Get the kind of this node's first previous non-trivia sibling. + pub fn prev_sibling_kind(&self) -> Option { + Some(self.prev_sibling()?.node.kind()) + } + + /// Get the kind of this node's next non-trivia sibling. + pub fn next_sibling_kind(&self) -> Option { + Some(self.next_sibling()?.node.kind()) + } } /// Access to leafs. diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 1b5c10a3f..602d9f2cc 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -374,12 +374,12 @@ fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usiz } fn math_args(p: &mut Parser) { - p.assert(SyntaxKind::Text); - let m = p.marker(); - let mut arg = p.marker(); + p.convert(SyntaxKind::LeftParen); + let mut namable = true; let mut named = None; + let mut arg = p.marker(); while !p.eof() && !p.at(SyntaxKind::Dollar) { if namable @@ -418,11 +418,14 @@ fn math_args(p: &mut Parser) { maybe_wrap_in_math(p, arg, named); } - p.wrap(m, SyntaxKind::Args); - if !p.eat_if(SyntaxKind::Text) { + if p.at(SyntaxKind::Text) && p.current_text() == ")" { + p.convert(SyntaxKind::RightParen); + } else { p.expected("closing paren"); p.balanced = false; } + + p.wrap(m, SyntaxKind::Args); } fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option) { @@ -512,14 +515,7 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) { if p.eat_if(SyntaxKind::Dot) { p.expect(SyntaxKind::Ident); - if p.directly_at(SyntaxKind::LeftParen) - || p.directly_at(SyntaxKind::LeftBracket) - { - args(p); - p.wrap(m, SyntaxKind::MethodCall); - } else { - p.wrap(m, SyntaxKind::FieldAccess) - } + p.wrap(m, SyntaxKind::FieldAccess); continue; } diff --git a/tests/ref/compiler/highlight.png b/tests/ref/compiler/highlight.png new file mode 100644 index 000000000..317a2128f Binary files /dev/null and b/tests/ref/compiler/highlight.png differ diff --git a/tests/ref/math/syntax.png b/tests/ref/math/syntax.png index 0b7385117..ce83f9f28 100644 Binary files a/tests/ref/math/syntax.png and b/tests/ref/math/syntax.png differ diff --git a/tests/typ/compiler/highlight.typ b/tests/typ/compiler/highlight.typ new file mode 100644 index 000000000..6c6ec802e --- /dev/null +++ b/tests/typ/compiler/highlight.typ @@ -0,0 +1,63 @@ +#set page(width: auto) + +```typ +#set hello() +#set hello() +#set hello.world() +#set hello.my.world() + +#show heading: func +#show module.func: func +#show module.func: it => {} +#foo(ident: ident) + +#hello +#hello() +#hello.world +#hello.world() +#hello().world() +#hello.my.world +#hello.my.world() +#hello.my().world +#hello.my().world() + +$ hello $ +$ hello() $ +$ hello.world $ +$ hello.world() $ +$ hello().world() $ +$ hello.my.world $ +$ hello.my.world() $ +$ hello.my().world $ +$ hello.my().world() $ + +$ emph(hello) $ +$ emph(hello()) $ +$ emph(hello.world) $ +$ emph(hello.world()) $ +$ emph(hello().world()) $ +$ emph(hello.my.world) $ +$ emph(hello.my.world()) $ +$ emph(hello.my().world) $ +$ emph(hello.my().world()) $ + +$ #hello $ +$ #hello() $ +$ #hello.world $ +$ #hello.world() $ +$ #hello().world() $ +$ #hello.my.world $ +$ #hello.my.world() $ +$ #hello.my().world $ +$ #hello.my().world() $ + +#{ hello } +#{ hello() } +#{ hello.world } +#{ hello.world() } +#{ hello().world() } +#{ hello.my.world } +#{ hello.my.world() } +#{ hello.my().world } +#{ hello.my().world() } +``` diff --git a/tests/typ/math/matrix.typ b/tests/typ/math/matrix.typ index aa99bb1b5..3f65a6838 100644 --- a/tests/typ/math/matrix.typ +++ b/tests/typ/math/matrix.typ @@ -23,5 +23,5 @@ $ f(x, y) := cases( #set math.vec(delim: "%") --- -// Error: 9-12 missing argument: lower index +// Error: 8-13 missing argument: lower index $ binom(x^2) $