diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/diag.rs | 1 | ||||
| -rw-r--r-- | src/ide/analyze.rs | 4 | ||||
| -rw-r--r-- | src/ide/complete.rs | 12 | ||||
| -rw-r--r-- | src/ide/highlight.rs | 25 | ||||
| -rw-r--r-- | src/model/content.rs | 10 | ||||
| -rw-r--r-- | src/model/dict.rs | 21 | ||||
| -rw-r--r-- | src/model/eval.rs | 40 | ||||
| -rw-r--r-- | src/model/func.rs | 10 | ||||
| -rw-r--r-- | src/model/library.rs | 3 | ||||
| -rw-r--r-- | src/model/methods.rs | 76 | ||||
| -rw-r--r-- | src/model/value.rs | 14 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 63 | ||||
| -rw-r--r-- | src/syntax/kind.rs | 3 | ||||
| -rw-r--r-- | src/syntax/lexer.rs | 19 | ||||
| -rw-r--r-- | src/syntax/node.rs | 8 | ||||
| -rw-r--r-- | src/syntax/parser.rs | 56 | ||||
| -rw-r--r-- | src/syntax/reparser.rs | 7 |
17 files changed, 221 insertions, 151 deletions
diff --git a/src/diag.rs b/src/diag.rs index 054a7b03..1cf2f85b 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -68,7 +68,6 @@ impl SourceError { /// Create a new, bare error. #[track_caller] pub fn new(span: Span, message: impl Into<EcoString>) -> Self { - assert!(!span.is_detached()); Self { span, pos: ErrorPos::Full, diff --git a/src/ide/analyze.rs b/src/ide/analyze.rs index a1ac5778..c170186f 100644 --- a/src/ide/analyze.rs +++ b/src/ide/analyze.rs @@ -7,7 +7,9 @@ use crate::World; /// Try to determine a set of possible values for an expression. pub fn analyze(world: &(dyn World + 'static), node: &LinkedNode) -> Vec<Value> { match node.cast::<ast::Expr>() { - Some(ast::Expr::Ident(_) | ast::Expr::MathIdent(_)) => { + Some( + ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::MethodCall(_), + ) => { 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 83d0ca9c..83202e30 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -229,7 +229,7 @@ fn complete_math(ctx: &mut CompletionContext) -> bool { } // Behind existing atom or identifier: "$a|$" or "$abc|$". - if matches!(ctx.leaf.kind(), SyntaxKind::MathAtom | SyntaxKind::MathIdent) { + if matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::MathIdent) { ctx.from = ctx.leaf.offset(); math_completions(ctx); return true; @@ -274,7 +274,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { // Behind an expression plus dot: "emoji.|". if_chain! { if ctx.leaf.kind() == SyntaxKind::Dot - || (matches!(ctx.leaf.kind(), SyntaxKind::Text | SyntaxKind::MathAtom) + || (ctx.leaf.kind() == SyntaxKind::Text && ctx.leaf.text() == "."); if ctx.leaf.range().end == ctx.cursor; if let Some(prev) = ctx.leaf.prev_sibling(); @@ -326,11 +326,15 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) { } } _ => { - for &method in methods_on(value.type_name()) { + for &(method, args) in methods_on(value.type_name()) { ctx.completions.push(Completion { kind: CompletionKind::Func, label: method.into(), - apply: Some(format_eco!("{method}(${{}})")), + apply: Some(if args { + format_eco!("{method}(${{}})") + } else { + format_eco!("{method}()${{}}") + }), detail: None, }) } diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index 7f7ad6ee..d8f15f00 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -1,4 +1,4 @@ -use crate::syntax::{LinkedNode, SyntaxKind}; +use crate::syntax::{ast, LinkedNode, SyntaxKind}; /// Syntax highlighting categories. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] @@ -115,19 +115,17 @@ pub fn highlight(node: &LinkedNode) -> Option<Category> { SyntaxKind::Formula => None, SyntaxKind::Math => None, - SyntaxKind::MathAtom => None, SyntaxKind::MathIdent => highlight_ident(node), SyntaxKind::MathDelimited => None, SyntaxKind::MathAttach => 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), + .next_sibling() + .filter(|node| node.cast::<ast::Expr>().map_or(false, |e| e.hashtag())) + .and_then(|node| node.leftmost_leaf()) + .and_then(|node| highlight(&node)), SyntaxKind::LeftBrace => Some(Category::Punctuation), SyntaxKind::RightBrace => Some(Category::Punctuation), @@ -248,12 +246,6 @@ 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::MathAttach, - ) => Some(Category::Interpolated), Some(SyntaxKind::FuncCall) => Some(Category::Function), Some(SyntaxKind::FieldAccess) if node.parent().and_then(|p| p.parent_kind()) @@ -287,6 +279,13 @@ fn highlight_ident(node: &LinkedNode) -> Option<Category> { { Some(Category::Function) } + Some( + SyntaxKind::Markup + | SyntaxKind::Math + | SyntaxKind::MathFrac + | SyntaxKind::MathAttach, + ) => Some(Category::Interpolated), + _ if node.kind() == SyntaxKind::MathIdent => Some(Category::Interpolated), _ => None, } } diff --git a/src/model/content.rs b/src/model/content.rs index df910a58..143f97aa 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -183,12 +183,18 @@ impl Content { } /// Whether the contained node is of type `T`. - pub fn is<T: 'static>(&self) -> bool { + pub fn is<T>(&self) -> bool + where + T: Capable + 'static, + { (*self.obj).as_any().is::<T>() } /// Cast to `T` if the contained node is of type `T`. - pub fn to<T: 'static>(&self) -> Option<&T> { + pub fn to<T>(&self) -> Option<&T> + where + T: Capable + 'static, + { (*self.obj).as_any().downcast_ref::<T>() } diff --git a/src/model/dict.rs b/src/model/dict.rs index 76d194a8..7165fbbe 100644 --- a/src/model/dict.rs +++ b/src/model/dict.rs @@ -3,8 +3,8 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::ops::{Add, AddAssign}; use std::sync::Arc; -use super::{Args, Array, Func, Str, Value, Vm}; -use crate::diag::{bail, SourceResult, StrResult}; +use super::{array, Array, Str, Value}; +use crate::diag::StrResult; use crate::syntax::is_ident; use crate::util::{format_eco, ArcExt, EcoString}; @@ -104,17 +104,12 @@ impl Dict { self.0.values().cloned().collect() } - /// Transform each pair in the dictionary with a function. - pub fn map(&self, vm: &mut Vm, func: Func) -> SourceResult<Array> { - if func.argc().map_or(false, |count| count != 2) { - bail!(func.span(), "function must have exactly two parameters"); - } - self.iter() - .map(|(key, value)| { - let args = - Args::new(func.span(), [Value::Str(key.clone()), value.clone()]); - func.call(vm, args) - }) + /// Return the values of the dictionary as an array of pairs (arrays of + /// length two). + pub fn pairs(&self) -> Array { + self.0 + .iter() + .map(|(k, v)| Value::Array(array![k.clone(), v.clone()])) .collect() } diff --git a/src/model/eval.rs b/src/model/eval.rs index 44f08a76..b63069bf 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -344,7 +344,6 @@ impl Eval for ast::Expr { Self::Term(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), @@ -552,21 +551,13 @@ impl Eval for ast::Math { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { Ok(Content::sequence( self.exprs() - .map(|expr| Ok(expr.eval(vm)?.display_in_math())) + .map(|expr| Ok(expr.eval(vm)?.display())) .collect::<SourceResult<_>>()?, ) .spanned(self.span())) } } -impl Eval for ast::MathAtom { - type Output = Content; - - fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - Ok((vm.items.math_atom)(self.get().clone())) - } -} - impl Eval for ast::MathIdent { type Output = Value; @@ -587,9 +578,9 @@ impl Eval for ast::MathDelimited { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let open = self.open().eval(vm)?.display_in_math(); + let open = self.open().eval(vm)?.display(); let body = self.body().eval(vm)?; - let close = self.close().eval(vm)?.display_in_math(); + let close = self.close().eval(vm)?.display(); Ok((vm.items.math_delimited)(open, body, close)) } } @@ -598,16 +589,13 @@ impl Eval for ast::MathAttach { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let base = self.base().eval(vm)?.display_in_math(); - let sub = self + let base = self.base().eval(vm)?.display(); + let bottom = self .bottom() - .map(|expr| expr.eval(vm).map(Value::display_in_math)) - .transpose()?; - let sup = self - .top() - .map(|expr| expr.eval(vm).map(Value::display_in_math)) + .map(|expr| expr.eval(vm).map(Value::display)) .transpose()?; - Ok((vm.items.math_attach)(base, sub, sup)) + let top = self.top().map(|expr| expr.eval(vm).map(Value::display)).transpose()?; + Ok((vm.items.math_attach)(base, bottom, top)) } } @@ -615,8 +603,8 @@ impl Eval for ast::MathFrac { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { - let num = self.num().eval(vm)?.display_in_math(); - let denom = self.denom().eval(vm)?.display_in_math(); + let num = self.num().eval(vm)?.display(); + let denom = self.denom().eval(vm)?.display(); Ok((vm.items.math_frac)(num, denom)) } } @@ -945,15 +933,15 @@ impl Eval for ast::FuncCall { } } - let mut body = (vm.items.math_atom)('('.into()); + let mut body = (vm.items.text)('('.into()); for (i, arg) in args.all::<Content>()?.into_iter().enumerate() { if i > 0 { - body += (vm.items.math_atom)(','.into()); + body += (vm.items.text)(','.into()); } body += arg; } - body += (vm.items.math_atom)(')'.into()); - return Ok(Value::Content(callee.display_in_math() + body)); + body += (vm.items.text)(')'.into()); + return Ok(Value::Content(callee.display() + body)); } let callee = callee.cast::<Func>().at(callee_span)?; diff --git a/src/model/func.rs b/src/model/func.rs index 8cf3ea99..1ccb0107 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -389,6 +389,7 @@ impl<'a> CapturesVisitor<'a> { // actually bind a new name are handled below (individually through // the expressions that contain them). Some(ast::Expr::Ident(ident)) => self.capture(ident), + Some(ast::Expr::MathIdent(ident)) => self.capture_in_math(ident), // Code and content blocks create a scope. Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => { @@ -483,6 +484,15 @@ impl<'a> CapturesVisitor<'a> { } } } + + /// Capture a variable in math mode if it isn't internal. + fn capture_in_math(&mut self, ident: ast::MathIdent) { + if self.internal.get(&ident).is_err() { + if let Ok(value) = self.external.get_in_math(&ident) { + self.captures.define_captured(ident.take(), value.clone()); + } + } + } } #[cfg(test)] diff --git a/src/model/library.rs b/src/model/library.rs index 4208a4c7..c87ca095 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -67,8 +67,6 @@ pub struct LangItems { pub term_item: fn(term: Content, description: Content) -> Content, /// A mathematical formula: `$x$`, `$ x^2 $`. 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, /// Matched delimiters surrounding math in a formula: `[x + y]`. @@ -110,7 +108,6 @@ impl Hash for LangItems { self.enum_item.hash(state); self.term_item.hash(state); self.formula.hash(state); - self.math_atom.hash(state); self.math_align_point.hash(state); self.math_delimited.hash(state); self.math_attach.hash(state); diff --git a/src/model/methods.rs b/src/model/methods.rs index 1671a5c4..5da64fa2 100644 --- a/src/model/methods.rs +++ b/src/model/methods.rs @@ -107,7 +107,7 @@ pub fn call( "at" => dict.at(&args.expect::<Str>("key")?).cloned().at(span)?, "keys" => Value::Array(dict.keys()), "values" => Value::Array(dict.values()), - "pairs" => Value::Array(dict.map(vm, args.expect("function")?)?), + "pairs" => Value::Array(dict.pairs()), _ => return missing(), }, @@ -211,35 +211,61 @@ fn missing_method(type_name: &str, method: &str) -> String { format!("type {type_name} has no method `{method}`") } -/// List the available methods for a type. -pub fn methods_on(type_name: &str) -> &[&'static str] { +/// List the available methods for a type and whether they take arguments. +pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] { match type_name { - "color" => &["lighten", "darken", "negate"], + "color" => &[("lighten", true), ("darken", true), ("negate", false)], "string" => &[ - "len", - "at", - "contains", - "ends-with", - "find", - "first", - "last", - "match", - "matches", - "position", - "replace", - "slice", - "split", - "starts-with", - "trim", + ("len", false), + ("at", true), + ("contains", true), + ("ends-with", true), + ("find", true), + ("first", false), + ("last", false), + ("match", true), + ("matches", true), + ("position", true), + ("replace", true), + ("slice", true), + ("split", true), + ("starts-with", true), + ("trim", true), ], "array" => &[ - "all", "any", "at", "contains", "filter", "find", "first", "flatten", "fold", - "insert", "join", "last", "len", "map", "pop", "position", "push", "remove", - "rev", "slice", "sorted", + ("all", true), + ("any", true), + ("at", true), + ("contains", true), + ("filter", true), + ("find", true), + ("first", false), + ("flatten", false), + ("fold", true), + ("insert", true), + ("join", true), + ("last", false), + ("len", false), + ("map", true), + ("pop", false), + ("position", true), + ("push", true), + ("remove", true), + ("rev", false), + ("slice", true), + ("sorted", false), ], - "dictionary" => &["at", "insert", "keys", "len", "pairs", "remove", "values"], - "function" => &["where", "with"], - "arguments" => &["named", "pos"], + "dictionary" => &[ + ("at", true), + ("insert", true), + ("keys", false), + ("len", false), + ("pairs", false), + ("remove", true), + ("values", false), + ], + "function" => &[("where", true), ("with", true)], + "arguments" => &[("named", false), ("pos", false)], _ => &[], } } diff --git a/src/model/value.rs b/src/model/value.rs index 4b9fa5f7..ea17349e 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -145,16 +145,6 @@ impl Value { } } - /// Return the display representation of the value in math mode. - pub fn display_in_math(self) -> Content { - match self { - Self::Int(v) => item!(math_atom)(format_eco!("{}", v)), - Self::Float(v) => item!(math_atom)(format_eco!("{}", v)), - Self::Symbol(v) => item!(math_atom)(v.get().into()), - _ => self.display(), - } - } - /// Try to extract documentation for the value. pub fn docs(&self) -> Option<&'static str> { match self { @@ -447,8 +437,8 @@ primitive! { Label: "label", Label } primitive! { Content: "content", Content, None => Content::empty(), - Symbol(symbol) => item!(text)(symbol.get().into()), - Str(text) => item!(text)(text.into()) + Symbol(v) => item!(text)(v.get().into()), + Str(v) => item!(text)(v.into()) } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 45f79685..5704f171 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -115,8 +115,6 @@ pub enum Expr { Formula(Formula), /// A math formula: `$x$`, `$ x^2 $`. Math(Math), - /// An atom in a math formula: `x`, `+`, `12`. - MathAtom(MathAtom), /// An identifier in a math formula: `pi`. MathIdent(MathIdent), /// An alignment point in a math formula: `&`. @@ -219,7 +217,6 @@ impl AstNode for Expr { SyntaxKind::TermItem => node.cast().map(Self::Term), SyntaxKind::Formula => node.cast().map(Self::Formula), SyntaxKind::Math => node.cast().map(Self::Math), - 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), @@ -280,7 +277,6 @@ impl AstNode for Expr { Self::Term(v) => v.as_untyped(), Self::Formula(v) => v.as_untyped(), Self::Math(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(), @@ -320,6 +316,42 @@ impl AstNode for Expr { } } +impl Expr { + /// Can this expression be embedded into markup with a hashtag? + pub fn hashtag(&self) -> bool { + match self { + Self::Ident(_) => true, + Self::None(_) => true, + Self::Auto(_) => true, + Self::Bool(_) => true, + Self::Int(_) => true, + Self::Float(_) => true, + Self::Numeric(_) => true, + Self::Str(_) => true, + Self::Code(_) => true, + Self::Content(_) => true, + Self::Array(_) => true, + Self::Dict(_) => true, + Self::Parenthesized(_) => true, + Self::FieldAccess(_) => true, + Self::FuncCall(_) => true, + Self::MethodCall(_) => true, + Self::Let(_) => true, + Self::Set(_) => true, + Self::Show(_) => true, + Self::Conditional(_) => true, + Self::While(_) => true, + Self::For(_) => true, + Self::Import(_) => true, + Self::Include(_) => true, + Self::Break(_) => true, + Self::Continue(_) => true, + Self::Return(_) => true, + _ => false, + } + } +} + impl Default for Expr { fn default() -> Self { Expr::Space(Space::default()) @@ -393,18 +425,23 @@ impl Shorthand { "..." => '…', "*" => '∗', "!=" => '≠', + "<<" => '≪', + "<<<" => '⋘', + ">>" => '≫', + ">>>" => '⋙', "<=" => '≤', ">=" => '≥', "<-" => '←', "->" => '→', "=>" => '⇒', + "|->" => '↦', + "|=>" => '⤇', + "<->" => '↔', + "<=>" => '⇔', ":=" => '≔', "[|" => '⟦', "|]" => '⟧', "||" => '‖', - "|->" => '↦', - "<->" => '↔', - "<=>" => '⇔', _ => char::default(), } } @@ -661,18 +698,6 @@ impl Math { } node! { - /// A atom in a formula: `x`, `+`, `12`. - MathAtom -} - -impl MathAtom { - /// Get the atom's text. - pub fn get(&self) -> &EcoString { - self.0.text() - } -} - -node! { /// An identifier in a math formula: `pi`. MathIdent } diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index aa4a5cfe..b2b65a62 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -59,8 +59,6 @@ pub enum SyntaxKind { /// Mathematical markup. Math, - /// An atom in math: `x`, `+`, `12`. - MathAtom, /// An identifier in math: `pi`. MathIdent, /// An alignment point in math: `&`. @@ -345,7 +343,6 @@ impl SyntaxKind { 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::MathAttach => "math attachments", diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index d4548b8b..d267a05b 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -380,21 +380,28 @@ impl Lexer<'_> { '\\' => self.backslash(), '"' => self.string(), + '*' => SyntaxKind::Shorthand, '.' if self.s.eat_if("..") => SyntaxKind::Shorthand, '|' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("->") => SyntaxKind::Shorthand, - '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, + '|' if self.s.eat_if("=>") => SyntaxKind::Shorthand, '!' if self.s.eat_if('=') => SyntaxKind::Shorthand, + '<' if self.s.eat_if("<<") => SyntaxKind::Shorthand, + '<' if self.s.eat_if('<') => SyntaxKind::Shorthand, + '>' if self.s.eat_if(">>") => SyntaxKind::Shorthand, + '>' if self.s.eat_if('>') => SyntaxKind::Shorthand, + + '<' if self.s.eat_if("=>") => SyntaxKind::Shorthand, + '<' if self.s.eat_if("->") => SyntaxKind::Shorthand, '<' if self.s.eat_if('=') => SyntaxKind::Shorthand, '>' if self.s.eat_if('=') => SyntaxKind::Shorthand, '<' if self.s.eat_if('-') => SyntaxKind::Shorthand, '-' if self.s.eat_if('>') => SyntaxKind::Shorthand, '=' if self.s.eat_if('>') => SyntaxKind::Shorthand, + ':' if self.s.eat_if('=') => SyntaxKind::Shorthand, '[' if self.s.eat_if('|') => SyntaxKind::Shorthand, '|' 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, @@ -410,11 +417,11 @@ impl Lexer<'_> { } // Other math atoms. - _ => self.atom(start, c), + _ => self.math_text(start, c), } } - fn atom(&mut self, start: usize, c: char) -> SyntaxKind { + fn math_text(&mut self, start: usize, c: char) -> SyntaxKind { // Keep numbers and grapheme clusters together. if c.is_numeric() { self.s.eat_while(char::is_numeric); @@ -427,7 +434,7 @@ impl Lexer<'_> { .map_or(0, str::len); self.s.jump(start + len); } - SyntaxKind::MathAtom + SyntaxKind::Text } } diff --git a/src/syntax/node.rs b/src/syntax/node.rs index a0fa5e1e..ed000788 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -713,14 +713,6 @@ 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. diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 07730533..1b5c10a3 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -234,24 +234,24 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { SyntaxKind::Hashtag => embedded_code_expr(p), SyntaxKind::MathIdent => { p.eat(); - while p.directly_at(SyntaxKind::MathAtom) + while p.directly_at(SyntaxKind::Text) && p.current_text() == "." && matches!( p.lexer.clone().next(), - SyntaxKind::MathIdent | SyntaxKind::MathAtom + SyntaxKind::MathIdent | SyntaxKind::Text ) { p.convert(SyntaxKind::Dot); p.convert(SyntaxKind::Ident); p.wrap(m, SyntaxKind::FieldAccess); } - if p.directly_at(SyntaxKind::MathAtom) && p.current_text() == "(" { + if p.directly_at(SyntaxKind::Text) && p.current_text() == "(" { math_args(p); p.wrap(m, SyntaxKind::FuncCall); } } - SyntaxKind::MathAtom | SyntaxKind::Shorthand => { + SyntaxKind::Text | SyntaxKind::Shorthand => { if math_class(p.current_text()) == Some(MathClass::Fence) { math_delimited(p, MathClass::Fence) } else if math_class(p.current_text()) == Some(MathClass::Opening) { @@ -374,16 +374,32 @@ fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usiz } fn math_args(p: &mut Parser) { - p.assert(SyntaxKind::MathAtom); + p.assert(SyntaxKind::Text); + let m = p.marker(); - let mut m2 = p.marker(); + let mut arg = p.marker(); + let mut namable = true; + let mut named = None; + while !p.eof() && !p.at(SyntaxKind::Dollar) { + if namable + && (p.at(SyntaxKind::MathIdent) || p.at(SyntaxKind::Text)) + && p.text[p.current_end()..].starts_with(':') + { + p.convert(SyntaxKind::Ident); + p.convert(SyntaxKind::Colon); + named = Some(arg); + arg = p.marker(); + } + match p.current_text() { ")" => break, "," => { - p.wrap(m2, SyntaxKind::Math); + maybe_wrap_in_math(p, arg, named); p.convert(SyntaxKind::Comma); - m2 = p.marker(); + arg = p.marker(); + namable = true; + named = None; continue; } _ => {} @@ -394,12 +410,30 @@ fn math_args(p: &mut Parser) { if !p.progress(prev) { p.unexpected(); } + + namable = false; } - if m2 != p.marker() { - p.wrap(m2, SyntaxKind::Math); + + if arg != p.marker() { + maybe_wrap_in_math(p, arg, named); } + p.wrap(m, SyntaxKind::Args); - p.expect(SyntaxKind::MathAtom); + if !p.eat_if(SyntaxKind::Text) { + p.expected("closing paren"); + p.balanced = false; + } +} + +fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option<Marker>) { + let exprs = p.post_process(arg).filter(|node| node.is::<ast::Expr>()).count(); + if exprs != 1 { + p.wrap(arg, SyntaxKind::Math); + } + + if let Some(m) = named { + p.wrap(m, SyntaxKind::Named); + } } fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs index de845abf..a876e86b 100644 --- a/src/syntax/reparser.rs +++ b/src/syntax/reparser.rs @@ -99,9 +99,9 @@ fn try_reparse( && (parent_kind.is_none() || parent_kind == Some(SyntaxKind::ContentBlock)) && !overlap.is_empty() { - // Add one node of slack in both directions. + // Add slack in both directions. let children = node.children_mut(); - let mut start = overlap.start.saturating_sub(1); + let mut start = overlap.start.saturating_sub(2); let mut end = (overlap.end + 1).min(children.len()); // Expand to the left. @@ -242,7 +242,7 @@ mod tests { #[test] fn test_reparse_markup() { - test("abc~def~ghi", 5..6, "+", true); + test("abc~def~gh~", 5..6, "+", true); test("~~~~~~~", 3..4, "A", true); test("abc~~", 1..2, "", true); test("#var. hello", 5..6, " ", false); @@ -264,7 +264,6 @@ mod tests { test("#show f: a => b..", 16..16, "c", false); 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", 7..7, "//", false); test(r#"a ```typst hello```"#, 16..17, "", false); } |
