From dd331f007cb9c9968605f8d3eaef8fb498c21322 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 20 Jan 2023 14:05:17 +0100 Subject: [PATCH] Rewrite parser --- src/ide/complete.rs | 8 +- src/ide/highlight.rs | 86 +- src/model/eval.rs | 28 +- src/syntax/ast.rs | 110 +- src/syntax/kind.rs | 268 ++-- src/syntax/lexer.rs | 456 +++---- src/syntax/mod.rs | 3 +- src/syntax/node.rs | 36 +- src/syntax/parser.rs | 2348 +++++++++++++------------------- src/syntax/reparse.rs | 525 ------- src/syntax/reparser.rs | 262 ++++ src/syntax/source.rs | 3 +- tests/ref/basics/heading.png | Bin 24767 -> 24759 bytes tests/ref/basics/list.png | Bin 20656 -> 20300 bytes tests/typ/basics/list.typ | 1 + tests/typ/compiler/array.typ | 5 +- tests/typ/compiler/block.typ | 4 +- tests/typ/compiler/call.typ | 6 +- tests/typ/compiler/closure.typ | 10 + tests/typ/compiler/dict.typ | 11 +- tests/typ/compiler/field.typ | 3 +- tests/typ/compiler/for.typ | 9 +- tests/typ/compiler/if.typ | 6 +- tests/typ/compiler/import.typ | 20 +- tests/typ/compiler/let.typ | 8 +- tests/typ/compiler/spread.typ | 2 +- tests/typ/compiler/while.typ | 4 +- tests/typ/text/emphasis.typ | 6 +- tests/typ/text/raw.typ | 3 +- 29 files changed, 1729 insertions(+), 2502 deletions(-) delete mode 100644 src/syntax/reparse.rs create mode 100644 src/syntax/reparser.rs diff --git a/src/ide/complete.rs b/src/ide/complete.rs index 9e13fc8dd..f0808b21f 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -141,7 +141,7 @@ fn complete_params(ctx: &mut CompletionContext) -> bool { if let Some(param) = before_colon.cast::(); then { ctx.from = match ctx.leaf.kind() { - SyntaxKind::Colon | SyntaxKind::Space { .. } => ctx.cursor, + SyntaxKind::Colon | SyntaxKind::Space => ctx.cursor, _ => ctx.leaf.offset(), }; ctx.named_param_value_completions(&callee, ¶m); @@ -246,7 +246,7 @@ fn complete_symbols(ctx: &mut CompletionContext) -> bool { /// Complete in markup mode. fn complete_markup(ctx: &mut CompletionContext) -> bool { // Bail if we aren't even in markup. - if !matches!(ctx.leaf.parent_kind(), None | Some(SyntaxKind::Markup { .. })) { + if !matches!(ctx.leaf.parent_kind(), None | Some(SyntaxKind::Markup)) { return false; } @@ -325,7 +325,7 @@ fn complete_math(ctx: &mut CompletionContext) -> bool { fn complete_code(ctx: &mut CompletionContext) -> bool { if matches!( ctx.leaf.parent_kind(), - None | Some(SyntaxKind::Markup { .. }) | Some(SyntaxKind::Math) + None | Some(SyntaxKind::Markup) | Some(SyntaxKind::Math) ) { return false; } @@ -887,7 +887,7 @@ impl<'a> CompletionContext<'a> { self.snippet_completion( "import", - "import ${items} from \"${file.typ}\"", + "import \"${file.typ}\": ${items}", "Imports variables from another file.", ); diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index cc502537e..42c050028 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -83,9 +83,41 @@ impl Category { /// highlighted. pub fn highlight(node: &LinkedNode) -> Option { match node.kind() { - SyntaxKind::LineComment => Some(Category::Comment), - SyntaxKind::BlockComment => Some(Category::Comment), - SyntaxKind::Space { .. } => None, + SyntaxKind::Markup + if node.parent_kind() == Some(SyntaxKind::TermItem) + && node.next_sibling().as_ref().map(|v| v.kind()) + == Some(SyntaxKind::Colon) => + { + Some(Category::ListTerm) + } + SyntaxKind::Markup => None, + SyntaxKind::Text => None, + SyntaxKind::Space => None, + SyntaxKind::Linebreak => Some(Category::Escape), + 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), + SyntaxKind::Raw => Some(Category::Raw), + SyntaxKind::Link => Some(Category::Link), + SyntaxKind::Label => Some(Category::Label), + SyntaxKind::Ref => Some(Category::Ref), + SyntaxKind::Heading => Some(Category::Heading), + SyntaxKind::HeadingMarker => None, + SyntaxKind::ListItem => None, + SyntaxKind::ListMarker => Some(Category::ListMarker), + SyntaxKind::EnumItem => None, + SyntaxKind::EnumMarker => Some(Category::ListMarker), + SyntaxKind::TermItem => None, + SyntaxKind::TermMarker => Some(Category::ListMarker), + SyntaxKind::Math => None, + SyntaxKind::Atom => None, + SyntaxKind::Script => None, + SyntaxKind::Frac => None, + SyntaxKind::AlignPoint => Some(Category::MathOperator), SyntaxKind::LeftBrace => Some(Category::Punctuation), SyntaxKind::RightBrace => Some(Category::Punctuation), @@ -105,16 +137,9 @@ pub fn highlight(node: &LinkedNode) -> Option { _ => None, }, SyntaxKind::Dollar => Some(Category::MathDelimiter), - SyntaxKind::Plus => Some(match node.parent_kind() { - Some(SyntaxKind::EnumItem) => Category::ListMarker, - _ => Category::Operator, - }), - SyntaxKind::Minus => Some(match node.parent_kind() { - Some(SyntaxKind::ListItem) => Category::ListMarker, - _ => Category::Operator, - }), + SyntaxKind::Plus => Some(Category::Operator), + SyntaxKind::Minus => Some(Category::Operator), SyntaxKind::Slash => Some(match node.parent_kind() { - Some(SyntaxKind::TermItem) => Category::ListMarker, Some(SyntaxKind::Frac) => Category::MathOperator, _ => Category::Operator, }), @@ -157,41 +182,9 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::Include => Some(Category::Keyword), SyntaxKind::As => Some(Category::Keyword), - SyntaxKind::Markup { .. } - if node.parent_kind() == Some(SyntaxKind::TermItem) - && node.next_sibling().as_ref().map(|v| v.kind()) - == Some(SyntaxKind::Colon) => - { - Some(Category::ListTerm) - } - SyntaxKind::Markup { .. } => None, - - SyntaxKind::Text => None, - SyntaxKind::Linebreak => Some(Category::Escape), - 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), - SyntaxKind::Raw { .. } => Some(Category::Raw), - SyntaxKind::Link => Some(Category::Link), - SyntaxKind::Label => Some(Category::Label), - SyntaxKind::Ref => Some(Category::Ref), - SyntaxKind::Heading => Some(Category::Heading), - SyntaxKind::ListItem => None, - SyntaxKind::EnumItem => None, - SyntaxKind::EnumNumbering => Some(Category::ListMarker), - SyntaxKind::TermItem => None, - SyntaxKind::Math => None, - SyntaxKind::Atom => None, - SyntaxKind::Script => None, - SyntaxKind::Frac => None, - SyntaxKind::AlignPoint => Some(Category::MathOperator), - SyntaxKind::Ident => match node.parent_kind() { Some( - SyntaxKind::Markup { .. } + SyntaxKind::Markup | SyntaxKind::Math | SyntaxKind::Script | SyntaxKind::Frac, @@ -258,7 +251,10 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::LoopContinue => None, SyntaxKind::FuncReturn => None, + SyntaxKind::LineComment => Some(Category::Comment), + SyntaxKind::BlockComment => Some(Category::Comment), SyntaxKind::Error => Some(Category::Error), + SyntaxKind::Eof => None, } } diff --git a/src/model/eval.rs b/src/model/eval.rs index 8e8c93c5c..0469649bc 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -261,9 +261,10 @@ impl Eval for ast::Expr { }; match self { + Self::Text(v) => v.eval(vm).map(Value::Content), Self::Space(v) => v.eval(vm).map(Value::Content), Self::Linebreak(v) => v.eval(vm).map(Value::Content), - Self::Text(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), @@ -330,14 +331,19 @@ impl ast::Expr { } } +impl Eval for ast::Text { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.text)(self.get().clone())) + } +} + impl Eval for ast::Space { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok(match self.newlines() { - 0..=1 => (vm.items.space)(), - _ => (vm.items.parbreak)(), - }) + Ok((vm.items.space)()) } } @@ -349,11 +355,11 @@ impl Eval for ast::Linebreak { } } -impl Eval for ast::Text { +impl Eval for ast::Parbreak { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.text)(self.get().clone())) + Ok((vm.items.parbreak)()) } } @@ -438,7 +444,7 @@ impl Eval for ast::Link { type Output = Content; fn eval(&self, vm: &mut Vm) -> SourceResult { - Ok((vm.items.link)(self.url().clone())) + Ok((vm.items.link)(self.get().clone())) } } @@ -1231,13 +1237,17 @@ impl Eval for ast::ModuleImport { } } Some(ast::Imports::Items(idents)) => { + let mut errors = vec![]; for ident in idents { if let Some(value) = module.scope().get(&ident) { vm.scopes.top.define(ident.take(), value.clone()); } else { - bail!(ident.span(), "unresolved import"); + errors.push(error!(ident.span(), "unresolved import")); } } + if errors.len() > 0 { + return Err(Box::new(errors)); + } } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index bf4b37bca..169b0276a 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -29,9 +29,6 @@ pub trait AstNode: Sized { macro_rules! node { ($(#[$attr:meta])* $name:ident) => { - node!{ $(#[$attr])* $name: SyntaxKind::$name { .. } } - }; - ($(#[$attr:meta])* $name:ident: $variants:pat) => { #[derive(Debug, Clone, PartialEq, Hash)] #[repr(transparent)] $(#[$attr])* @@ -39,7 +36,7 @@ macro_rules! node { impl AstNode for $name { fn from_untyped(node: &SyntaxNode) -> Option { - if matches!(node.kind(), $variants) { + if matches!(node.kind(), SyntaxKind::$name) { Some(Self(node.clone())) } else { Option::None @@ -67,8 +64,7 @@ impl Markup { .filter(move |node| { // Ignore newline directly after statements without semicolons. let kind = node.kind(); - let keep = - !was_stmt || !matches!(kind, SyntaxKind::Space { newlines: 1 }); + let keep = !was_stmt || node.kind() != SyntaxKind::Space; was_stmt = kind.is_stmt(); keep }) @@ -79,12 +75,15 @@ impl Markup { /// An expression in markup, math or code. #[derive(Debug, Clone, PartialEq, Hash)] pub enum Expr { - /// Whitespace. + /// Plain text without markup. + Text(Text), + /// Whitespace in markup or math. Has at most one newline in markup, as more + /// indicate a paragraph break. Space(Space), /// A forced line break: `\`. Linebreak(Linebreak), - /// Plain text without markup. - Text(Text), + /// A paragraph break, indicated by one or multiple blank lines. + Parbreak(Parbreak), /// An escape sequence: `\#`, `\u{1F5FA}`. Escape(Escape), /// A shorthand for a unicode codepoint. For example, `~` for non-breaking @@ -189,7 +188,7 @@ pub enum Expr { impl Expr { fn cast_with_space(node: &SyntaxNode) -> Option { match node.kind() { - SyntaxKind::Space { .. } => node.cast().map(Self::Space), + SyntaxKind::Space => node.cast().map(Self::Space), _ => Self::from_untyped(node), } } @@ -199,14 +198,15 @@ impl AstNode for Expr { fn from_untyped(node: &SyntaxNode) -> Option { match node.kind() { SyntaxKind::Linebreak => node.cast().map(Self::Linebreak), + SyntaxKind::Parbreak => node.cast().map(Self::Parbreak), 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::SmartQuote => node.cast().map(Self::SmartQuote), SyntaxKind::Strong => node.cast().map(Self::Strong), SyntaxKind::Emph => node.cast().map(Self::Emph), - SyntaxKind::Raw { .. } => node.cast().map(Self::Raw), + SyntaxKind::Raw => node.cast().map(Self::Raw), SyntaxKind::Link => node.cast().map(Self::Link), SyntaxKind::Label => node.cast().map(Self::Label), SyntaxKind::Ref => node.cast().map(Self::Ref), @@ -255,9 +255,10 @@ impl AstNode for Expr { fn as_untyped(&self) -> &SyntaxNode { match self { + Self::Text(v) => v.as_untyped(), Self::Space(v) => v.as_untyped(), Self::Linebreak(v) => v.as_untyped(), - Self::Text(v) => v.as_untyped(), + Self::Parbreak(v) => v.as_untyped(), Self::Escape(v) => v.as_untyped(), Self::Shorthand(v) => v.as_untyped(), Self::Symbol(v) => v.as_untyped(), @@ -311,26 +312,6 @@ impl AstNode for Expr { } } -node! { - /// Whitespace. - Space -} - -impl Space { - /// Get the number of newlines. - pub fn newlines(&self) -> usize { - match self.0.kind() { - SyntaxKind::Space { newlines } => newlines, - _ => panic!("space is of wrong kind"), - } - } -} - -node! { - /// A forced line break: `\`. - Linebreak -} - node! { /// Plain text without markup. Text @@ -343,6 +324,22 @@ impl Text { } } +node! { + /// Whitespace in markup or math. Has at most one newline in markup, as more + /// indicate a paragraph break. + Space +} + +node! { + /// A forced line break: `\`. + Linebreak +} + +node! { + /// A paragraph break, indicated by one or multiple blank lines. + Parbreak +} + node! { /// An escape sequence: `\#`, `\u{1F5FA}`. Escape @@ -454,10 +451,6 @@ node! { impl Raw { /// The trimmed raw text. pub fn text(&self) -> EcoString { - let SyntaxKind::Raw { column } = self.0.kind() else { - panic!("raw node is of wrong kind"); - }; - let mut text = self.0.text().as_str(); let blocky = text.starts_with("```"); text = text.trim_matches('`'); @@ -480,14 +473,16 @@ impl Raw { let mut lines = split_newlines(text); if blocky { + let dedent = lines + .iter() + .skip(1) + .map(|line| line.chars().take_while(|c| c.is_whitespace()).count()) + .min() + .unwrap_or(0); + // Dedent based on column, but not for the first line. for line in lines.iter_mut().skip(1) { - let offset = line - .chars() - .take(column) - .take_while(|c| c.is_whitespace()) - .map(char::len_utf8) - .sum(); + let offset = line.chars().take(dedent).map(char::len_utf8).sum(); *line = &line[offset..]; } @@ -531,7 +526,7 @@ node! { impl Link { /// Get the URL. - pub fn url(&self) -> &EcoString { + pub fn get(&self) -> &EcoString { self.0.text() } } @@ -575,10 +570,9 @@ impl Heading { pub fn level(&self) -> NonZeroUsize { self.0 .children() - .filter(|n| n.kind() == SyntaxKind::Eq) - .count() - .try_into() - .expect("heading is missing equals sign") + .find(|node| node.kind() == SyntaxKind::HeadingMarker) + .and_then(|node| node.len().try_into().ok()) + .expect("heading is missing marker") } } @@ -603,7 +597,7 @@ impl EnumItem { /// The explicit numbering, if any: `23.`. pub fn number(&self) -> Option { self.0.children().find_map(|node| match node.kind() { - SyntaxKind::EnumNumbering => node.text().trim_end_matches('.').parse().ok(), + SyntaxKind::EnumMarker => node.text().trim_end_matches('.').parse().ok(), _ => Option::None, }) } @@ -765,7 +759,7 @@ node! { } impl Bool { - /// Get the value. + /// Get the boolean value. pub fn get(&self) -> bool { self.0.text() == "true" } @@ -777,7 +771,7 @@ node! { } impl Int { - /// Get the value. + /// Get the integer value. pub fn get(&self) -> i64 { self.0.text().parse().expect("integer is invalid") } @@ -789,7 +783,7 @@ node! { } impl Float { - /// Get the value. + /// Get the floating-point value. pub fn get(&self) -> f64 { self.0.text().parse().expect("float is invalid") } @@ -801,7 +795,7 @@ node! { } impl Numeric { - /// Get the value and unit. + /// Get the numeric value and unit. pub fn get(&self) -> (f64, Unit) { let text = self.0.text(); let count = text @@ -850,7 +844,7 @@ node! { } impl Str { - /// Get the value. + /// Get the string value with resolved escape sequences. pub fn get(&self) -> EcoString { let text = self.0.text(); let unquoted = &text[1..text.len() - 1]; @@ -1058,7 +1052,7 @@ impl Unary { pub fn op(&self) -> UnOp { self.0 .children() - .find_map(|node| UnOp::from_token(node.kind())) + .find_map(|node| UnOp::from_kind(node.kind())) .expect("unary operation is missing operator") } @@ -1081,7 +1075,7 @@ pub enum UnOp { impl UnOp { /// Try to convert the token into a unary operation. - pub fn from_token(token: SyntaxKind) -> Option { + pub fn from_kind(token: SyntaxKind) -> Option { Some(match token { SyntaxKind::Plus => Self::Pos, SyntaxKind::Minus => Self::Neg, @@ -1125,7 +1119,7 @@ impl Binary { Option::None } SyntaxKind::In if not => Some(BinOp::NotIn), - _ => BinOp::from_token(node.kind()), + _ => BinOp::from_kind(node.kind()), }) .expect("binary operation is missing operator") } @@ -1190,7 +1184,7 @@ pub enum BinOp { impl BinOp { /// Try to convert the token into a binary operation. - pub fn from_token(token: SyntaxKind) -> Option { + pub fn from_kind(token: SyntaxKind) -> Option { Some(match token { SyntaxKind::Plus => Self::Add, SyntaxKind::Minus => Self::Sub, diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index 26e92b930..5928fa0a6 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -1,17 +1,72 @@ -/// All syntactical building blocks that can be part of a Typst document. +/// A syntactical building block of a Typst file. /// /// Can be created by the lexer or by the parser. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(u8)] pub enum SyntaxKind { - /// A line comment: `// ...`. - LineComment, - /// A block comment: `/* ... */`. - BlockComment, - /// One or more whitespace characters. Single spaces are collapsed into text - /// nodes if they would otherwise be surrounded by text nodes. + /// Markup of which all lines must have a minimal indentation. /// - /// Also stores how many newlines are contained. - Space { newlines: usize }, + /// Notably, the number does not determine in which column the markup + /// started, but to the right of which column all markup elements must be, + /// so it is zero except inside indent-aware constructs like lists. + Markup, + /// Plain text without markup. + Text, + /// Whitespace. Contains at most one newline in markup, as more indicate a + /// paragraph break. + Space, + /// A forced line break: `\`. + Linebreak, + /// A paragraph break, indicated by one or multiple blank lines. + Parbreak, + /// An escape sequence: `\#`, `\u{1F5FA}`. + Escape, + /// A shorthand for a unicode codepoint. For example, `~` for non-breaking + /// space or `-?` for a soft hyphen. + Shorthand, + /// Symbol notation: `:arrow:l:`. The string only contains the inner part + /// without leading and trailing dot. + Symbol, + /// A smart quote: `'` or `"`. + SmartQuote, + /// Strong content: `*Strong*`. + Strong, + /// Emphasized content: `_Emphasized_`. + Emph, + /// Raw text with optional syntax highlighting: `` `...` ``. + Raw, + /// A hyperlink: `https://typst.org`. + Link, + /// A label: ``. + Label, + /// A reference: `@target`. + Ref, + /// A section heading: `= Introduction`. + Heading, + /// Introduces a section heading: `=`, `==`, ... + HeadingMarker, + /// An item in a bullet list: `- ...`. + ListItem, + /// Introduces a list item: `-`. + ListMarker, + /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. + EnumItem, + /// Introduces an enumeration item: `+`, `1.`. + EnumMarker, + /// An item in a term list: `/ Term: Details`. + TermItem, + /// Introduces a term item: `/`. + TermMarker, + /// A mathematical formula: `$x$`, `$ x^2 $`. + Math, + /// An atom in math: `x`, `+`, `12`. + Atom, + /// A base with optional sub- and superscripts in math: `a_1^2`. + Script, + /// A fraction in math: `x/2`. + Frac, + /// An alignment point in math: `&`. + AlignPoint, /// A left curly brace, starting a code block: `{`. LeftBrace, @@ -37,19 +92,17 @@ pub enum SyntaxKind { /// The strong text toggle, multiplication operator, and wildcard import /// symbol: `*`. Star, - /// Toggles emphasized text and indicates a subscript in a formula: `_`. + /// Toggles emphasized text and indicates a subscript in math: `_`. Underscore, /// Starts and ends a math formula: `$`. Dollar, - /// The unary plus, binary addition operator, and start of enum items: `+`. + /// The unary plus and binary addition operator: `+`. Plus, - /// The unary negation, binary subtraction operator, and start of list - /// items: `-`. + /// The unary negation and binary subtraction operator: `-`. Minus, - /// The division operator, start of term list items, and fraction operator - /// in a formula: `/`. + /// The division operator and fraction operator in math: `/`. Slash, - /// The superscript operator in a formula: `^`. + /// The superscript operator in math: `^`. Hat, /// The field access and method call operator: `.`. Dot, @@ -119,59 +172,6 @@ pub enum SyntaxKind { /// The `as` keyword. As, - /// Markup of which all lines must have a minimal indentation. - /// - /// Notably, the number does not determine in which column the markup - /// started, but to the right of which column all markup elements must be, - /// so it is zero except inside indent-aware constructs like lists. - Markup { min_indent: usize }, - /// Plain text without markup. - Text, - /// A forced line break: `\`. - Linebreak, - /// An escape sequence: `\#`, `\u{1F5FA}`. - Escape, - /// A shorthand for a unicode codepoint. For example, `~` for non-breaking - /// space or `-?` for a soft hyphen. - Shorthand, - /// Symbol notation: `:arrow:l:`. The string only contains the inner part - /// without leading and trailing dot. - Symbol, - /// A smart quote: `'` or `"`. - SmartQuote, - /// Strong content: `*Strong*`. - Strong, - /// Emphasized content: `_Emphasized_`. - Emph, - /// Raw text with optional syntax highlighting: `` `...` ``. - Raw { column: usize }, - /// A hyperlink: `https://typst.org`. - Link, - /// A label: ``. - Label, - /// A reference: `@target`. - Ref, - /// A section heading: `= Introduction`. - Heading, - /// An item in a bullet list: `- ...`. - ListItem, - /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. - EnumItem, - /// An explicit enumeration numbering: `23.`. - EnumNumbering, - /// An item in a term list: `/ Term: Details`. - TermItem, - /// A mathematical formula: `$x$`, `$ x^2 $`. - Math, - /// An atom in a formula: `x`, `+`, `12`. - Atom, - /// A base with optional sub- and superscripts in a formula: `a_1^2`. - Script, - /// A fraction in a formula: `x/2`. - Frac, - /// An alignment point in a formula: `&`. - AlignPoint, - /// An identifier: `it`. Ident, /// A boolean: `true`, `false`. @@ -243,54 +243,103 @@ pub enum SyntaxKind { /// A return from a function: `return`, `return x + 1`. FuncReturn, + /// A line comment: `// ...`. + LineComment, + /// A block comment: `/* ... */`. + BlockComment, /// An invalid sequence of characters. Error, + /// The end of the file. + Eof, } impl SyntaxKind { - /// Whether this is trivia. - pub fn is_trivia(self) -> bool { - self.is_space() || self.is_comment() || self.is_error() + /// Is this a bracket, brace, or parenthesis? + pub fn is_grouping(self) -> bool { + matches!( + self, + Self::LeftBracket + | Self::LeftBrace + | Self::LeftParen + | Self::RightBracket + | Self::RightBrace + | Self::RightParen + ) } - /// Whether this is a space. - pub fn is_space(self) -> bool { - matches!(self, Self::Space { .. }) + /// Does this node terminate a preceding expression? + pub fn is_terminator(self) -> bool { + matches!( + self, + Self::Eof + | Self::Semicolon + | Self::RightBrace + | Self::RightParen + | Self::RightBracket + ) } - /// Whether this is a comment. - pub fn is_comment(self) -> bool { - matches!(self, Self::LineComment | Self::BlockComment) - } - - /// Whether this is an error. - pub fn is_error(self) -> bool { - matches!(self, SyntaxKind::Error) - } - - /// Whether this is a left or right parenthesis. - pub fn is_paren(self) -> bool { - matches!(self, Self::LeftParen | Self::RightParen) + /// Is this a code or content block. + pub fn is_block(self) -> bool { + matches!(self, Self::CodeBlock | Self::ContentBlock) } /// Does this node need termination through a semicolon or linebreak? pub fn is_stmt(self) -> bool { matches!( self, - SyntaxKind::LetBinding - | SyntaxKind::SetRule - | SyntaxKind::ShowRule - | SyntaxKind::ModuleImport - | SyntaxKind::ModuleInclude + Self::LetBinding + | Self::SetRule + | Self::ShowRule + | Self::ModuleImport + | Self::ModuleInclude ) } + /// Whether this kind of node is automatically skipped by the parser in + /// code and math mode. + pub fn is_trivia(self) -> bool { + matches!( + self, + Self::Space | Self::Parbreak | Self::LineComment | Self::BlockComment + ) + } + + /// Whether this is an error. + pub fn is_error(self) -> bool { + self == Self::Error + } + /// A human-readable name for the kind. pub fn name(self) -> &'static str { match self { - Self::LineComment => "line comment", - Self::BlockComment => "block comment", - Self::Space { .. } => "space", + Self::Markup => "markup", + Self::Text => "text", + Self::Space => "space", + Self::Linebreak => "line break", + Self::Parbreak => "paragraph break", + Self::Escape => "escape sequence", + Self::Shorthand => "shorthand", + Self::Symbol => "symbol notation", + Self::Strong => "strong content", + Self::Emph => "emphasized content", + Self::Raw => "raw block", + Self::Link => "link", + Self::Label => "label", + Self::Ref => "reference", + Self::Heading => "heading", + Self::HeadingMarker => "heading marker", + Self::ListItem => "list item", + Self::ListMarker => "list marker", + Self::EnumItem => "enum item", + Self::EnumMarker => "enum marker", + Self::TermItem => "term list item", + Self::TermMarker => "term marker", + Self::Math => "math formula", + Self::Atom => "math atom", + Self::Script => "script", + Self::Frac => "fraction", + Self::AlignPoint => "alignment point", Self::LeftBrace => "opening brace", Self::RightBrace => "closing brace", Self::LeftBracket => "opening bracket", @@ -309,7 +358,7 @@ impl SyntaxKind { Self::Slash => "slash", Self::Hat => "hat", Self::Dot => "dot", - Self::Eq => "assignment operator", + Self::Eq => "equals sign", Self::EqEq => "equality operator", Self::ExclEq => "inequality operator", Self::Lt => "less-than operator", @@ -341,28 +390,6 @@ impl SyntaxKind { Self::Import => "keyword `import`", Self::Include => "keyword `include`", Self::As => "keyword `as`", - Self::Markup { .. } => "markup", - Self::Text => "text", - Self::Linebreak => "linebreak", - Self::Escape => "escape sequence", - Self::Shorthand => "shorthand", - Self::Symbol => "symbol notation", - Self::Strong => "strong content", - Self::Emph => "emphasized content", - Self::Raw { .. } => "raw block", - Self::Link => "link", - Self::Label => "label", - Self::Ref => "reference", - Self::Heading => "heading", - Self::ListItem => "list item", - Self::EnumItem => "enumeration item", - Self::EnumNumbering => "enumeration item numbering", - Self::TermItem => "term list item", - Self::Math => "math formula", - Self::Atom => "math atom", - Self::Script => "script", - Self::Frac => "fraction", - Self::AlignPoint => "alignment point", Self::Ident => "identifier", Self::Bool => "boolean", Self::Int => "integer", @@ -398,7 +425,10 @@ impl SyntaxKind { Self::LoopBreak => "`break` expression", Self::LoopContinue => "`continue` expression", Self::FuncReturn => "`return` expression", + Self::LineComment => "line comment", + Self::BlockComment => "block comment", Self::Error => "syntax error", + Self::Eof => "end of file", } } } diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index f082bd285..e3c291509 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -9,12 +9,11 @@ use crate::util::{format_eco, EcoString}; pub(super) struct Lexer<'s> { /// The underlying scanner. s: Scanner<'s>, - /// The mode the lexer is in. This determines what tokens it recognizes. + /// The mode the lexer is in. This determines which kinds of tokens it + /// produces. mode: LexMode, - /// Whether the last token has been terminated. - terminated: bool, - /// Offsets the indentation on the first line of the source. - column_offset: usize, + /// Whether the last token contained a newline. + newline: bool, /// An error for the last token. error: Option<(EcoString, ErrorPos)>, } @@ -33,12 +32,11 @@ pub(super) enum LexMode { impl<'s> Lexer<'s> { /// Create a new lexer with the given mode and a prefix to offset column /// calculations. - pub fn with_prefix(prefix: &str, text: &'s str, mode: LexMode) -> Self { + pub fn new(text: &'s str, mode: LexMode) -> Self { Self { s: Scanner::new(text), mode, - terminated: true, - column_offset: column(prefix, prefix.len(), 0), + newline: false, error: None, } } @@ -64,26 +62,18 @@ impl<'s> Lexer<'s> { self.s.jump(index); } - /// The underlying scanner. - pub fn scanner(&self) -> Scanner<'s> { - self.s + /// Whether the last token contained a newline. + pub fn newline(&self) -> bool { + self.newline } - /// Whether the last token was terminated. - pub fn terminated(&self) -> bool { - self.terminated - } - - /// The column index of a given index in the source string. - pub fn column(&self, index: usize) -> usize { - column(self.s.string(), index, self.column_offset) - } - - /// Take out the last error. - pub fn last_error(&mut self) -> Option<(EcoString, ErrorPos)> { + /// Take out the last error, if any. + pub fn take_error(&mut self) -> Option<(EcoString, ErrorPos)> { self.error.take() } +} +impl Lexer<'_> { /// Construct a full-positioned syntax error. fn error(&mut self, message: impl Into) -> SyntaxKind { self.error = Some((message.into(), ErrorPos::Full)); @@ -97,45 +87,53 @@ impl<'s> Lexer<'s> { } } -impl Iterator for Lexer<'_> { - type Item = SyntaxKind; - - /// Produce the next token. - fn next(&mut self) -> Option { +/// Shared. +impl Lexer<'_> { + pub fn next(&mut self) -> SyntaxKind { + self.newline = false; self.error = None; let start = self.s.cursor(); - let c = self.s.eat()?; - Some(match c { - // Trivia. - c if c.is_whitespace() => self.whitespace(c), - '/' if self.s.eat_if('/') => self.line_comment(), - '/' if self.s.eat_if('*') => self.block_comment(), - '*' if self.s.eat_if('/') => self.error("unexpected end of block comment"), + match self.s.eat() { + Some(c) if c.is_whitespace() => self.whitespace(start, c), + Some('/') if self.s.eat_if('/') => self.line_comment(), + Some('/') if self.s.eat_if('*') => self.block_comment(), + Some('*') if self.s.eat_if('/') => { + self.error("unexpected end of block comment") + } - // Other things. - _ => match self.mode { + Some(c) => match self.mode { LexMode::Markup => self.markup(start, c), LexMode::Math => self.math(c), LexMode::Code => self.code(start, c), }, - }) - } -} -/// Shared. -impl Lexer<'_> { + None => SyntaxKind::Eof, + } + } + + fn whitespace(&mut self, start: usize, c: char) -> SyntaxKind { + let more = self.s.eat_while(char::is_whitespace); + let newlines = match c { + ' ' if more.is_empty() => 0, + _ => count_newlines(self.s.from(start)), + }; + + self.newline = newlines > 0; + if self.mode == LexMode::Markup && newlines >= 2 { + SyntaxKind::Parbreak + } else { + SyntaxKind::Space + } + } + fn line_comment(&mut self) -> SyntaxKind { self.s.eat_until(is_newline); - if self.s.done() { - self.terminated = false; - } SyntaxKind::LineComment } fn block_comment(&mut self) -> SyntaxKind { let mut state = '_'; let mut depth = 1; - self.terminated = false; // Find the first `*/` that does not correspond to a nested `/*`. while let Some(c) = self.s.eat() { @@ -143,7 +141,6 @@ impl Lexer<'_> { ('*', '/') => { depth -= 1; if depth == 0 { - self.terminated = true; break; } '_' @@ -162,32 +159,6 @@ impl Lexer<'_> { SyntaxKind::BlockComment } - - fn whitespace(&mut self, c: char) -> SyntaxKind { - if c == ' ' && !self.s.at(char::is_whitespace) { - return SyntaxKind::Space { newlines: 0 }; - } - - self.s.uneat(); - - // Count the number of newlines. - let mut newlines = 0; - while let Some(c) = self.s.eat() { - if !c.is_whitespace() { - self.s.uneat(); - break; - } - - if is_newline(c) { - if c == '\r' { - self.s.eat_if('\n'); - } - newlines += 1; - } - } - - SyntaxKind::Space { newlines } - } } /// Markup. @@ -199,9 +170,9 @@ impl Lexer<'_> { '`' => self.raw(), 'h' if self.s.eat_if("ttp://") => self.link(), 'h' if self.s.eat_if("ttps://") => self.link(), + '0'..='9' => self.numbering(start), '<' if self.s.at(is_id_continue) => self.label(), '@' if self.s.at(is_id_continue) => self.reference(), - '0'..='9' => self.numbering(start), '#' if self.s.eat_if('{') => SyntaxKind::LeftBrace, '#' if self.s.eat_if('[') => SyntaxKind::LeftBracket, '#' if self.s.at(is_id_start) => { @@ -225,17 +196,154 @@ impl Lexer<'_> { '\'' => SyntaxKind::SmartQuote, '"' => SyntaxKind::SmartQuote, '$' => SyntaxKind::Dollar, - '=' => SyntaxKind::Eq, - '+' => SyntaxKind::Plus, - '/' => SyntaxKind::Slash, '~' => SyntaxKind::Shorthand, ':' => SyntaxKind::Colon, - '-' => SyntaxKind::Minus, + '=' => { + self.s.eat_while('='); + if self.space_and_more() { + SyntaxKind::HeadingMarker + } else { + self.text() + } + } + '-' if self.space_and_more() => SyntaxKind::ListMarker, + '+' if self.space_and_more() => SyntaxKind::EnumMarker, + '/' if self.space_and_more() => SyntaxKind::TermMarker, _ => self.text(), } } + fn backslash(&mut self) -> SyntaxKind { + if self.s.eat_if("u{") { + let hex = self.s.eat_while(char::is_ascii_alphanumeric); + if !self.s.eat_if('}') { + return self.error_at_end("expected closing brace"); + } + + if u32::from_str_radix(hex, 16) + .ok() + .and_then(std::char::from_u32) + .is_none() + { + return self.error("invalid unicode escape sequence"); + } + + return SyntaxKind::Escape; + } + + if self.s.done() || self.s.at(char::is_whitespace) { + SyntaxKind::Linebreak + } else { + self.s.eat(); + SyntaxKind::Escape + } + } + + fn maybe_symbol(&mut self) -> SyntaxKind { + let start = self.s.cursor(); + let mut end = start; + while !self.s.eat_while(is_id_continue).is_empty() && self.s.at(':') { + end = self.s.cursor(); + self.s.eat(); + } + + self.s.jump(end); + + if start < end { + self.s.expect(':'); + SyntaxKind::Symbol + } else if self.mode == LexMode::Markup { + SyntaxKind::Colon + } else { + SyntaxKind::Atom + } + } + + fn raw(&mut self) -> SyntaxKind { + let mut backticks = 1; + while self.s.eat_if('`') { + backticks += 1; + } + + if backticks == 2 { + return SyntaxKind::Raw; + } + + let mut found = 0; + while found < backticks { + match self.s.eat() { + Some('`') => found += 1, + Some(_) => found = 0, + None => break, + } + } + + if found != backticks { + let remaining = backticks - found; + let noun = if remaining == 1 { "backtick" } else { "backticks" }; + return self.error_at_end(if found == 0 { + format_eco!("expected {} {}", remaining, noun) + } else { + format_eco!("expected {} more {}", remaining, noun) + }); + } + + SyntaxKind::Raw + } + + fn link(&mut self) -> SyntaxKind { + #[rustfmt::skip] + self.s.eat_while(|c: char| matches!(c, + | '0' ..= '9' + | 'a' ..= 'z' + | 'A' ..= 'Z' + | '~' | '/' | '%' | '?' | '#' | '&' | '+' | '=' + | '\'' | '.' | ',' | ';' + )); + + if self.s.scout(-1) == Some('.') { + self.s.uneat(); + } + + SyntaxKind::Link + } + + fn numbering(&mut self, start: usize) -> SyntaxKind { + self.s.eat_while(char::is_ascii_digit); + + let read = self.s.from(start); + if self.s.eat_if('.') { + if let Ok(number) = read.parse::() { + if number == 0 { + return self.error("must be positive"); + } + + return SyntaxKind::EnumMarker; + } + } + + self.text() + } + + fn label(&mut self) -> SyntaxKind { + let label = self.s.eat_while(is_id_continue); + if label.is_empty() { + return self.error("label cannot be empty"); + } + + if !self.s.eat_if('>') { + return self.error_at_end("expected closing angle bracket"); + } + + SyntaxKind::Label + } + + fn reference(&mut self) -> SyntaxKind { + self.s.eat_while(is_id_continue); + SyntaxKind::Ref + } + fn text(&mut self) -> SyntaxKind { macro_rules! table { ($(|$c:literal)*) => { @@ -277,132 +385,20 @@ impl Lexer<'_> { SyntaxKind::Text } - fn backslash(&mut self) -> SyntaxKind { - if self.s.eat_if("u{") { - let hex = self.s.eat_while(char::is_ascii_alphanumeric); - if !self.s.eat_if('}') { - self.terminated = false; - return self.error_at_end("expected closing brace"); - } - - if u32::from_str_radix(hex, 16) - .ok() - .and_then(std::char::from_u32) - .is_none() - { - return self.error("invalid unicode escape sequence"); - } - - return SyntaxKind::Escape; - } - - if self.s.done() || self.s.at(char::is_whitespace) { - SyntaxKind::Linebreak - } else { - self.s.eat(); - SyntaxKind::Escape - } - } - - fn maybe_symbol(&mut self) -> SyntaxKind { - let start = self.s.cursor(); - let mut end = start; - while !self.s.eat_while(is_id_continue).is_empty() && self.s.at(':') { - end = self.s.cursor(); - self.s.eat(); - } - - self.s.jump(end); - - if start < end { - self.s.expect(':'); - SyntaxKind::Symbol - } else if self.mode == LexMode::Markup { - SyntaxKind::Colon - } else { - SyntaxKind::Atom - } - } - - fn link(&mut self) -> SyntaxKind { - #[rustfmt::skip] - self.s.eat_while(|c: char| matches!(c, - | '0' ..= '9' - | 'a' ..= 'z' - | 'A' ..= 'Z' - | '~' | '/' | '%' | '?' | '#' | '&' | '+' | '=' - | '\'' | '.' | ',' | ';' - )); - - if self.s.scout(-1) == Some('.') { - self.s.uneat(); - } - - SyntaxKind::Link - } - - fn raw(&mut self) -> SyntaxKind { - let column = self.column(self.s.cursor() - 1); - - let mut backticks = 1; - while self.s.eat_if('`') { - backticks += 1; - } - - if backticks == 2 { - return SyntaxKind::Raw { column }; - } - - let mut found = 0; - while found < backticks { - match self.s.eat() { - Some('`') => found += 1, - Some(_) => found = 0, - None => break, - } - } - - if found != backticks { - self.terminated = false; - let remaining = backticks - found; - let noun = if remaining == 1 { "backtick" } else { "backticks" }; - return self.error_at_end(if found == 0 { - format_eco!("expected {} {}", remaining, noun) - } else { - format_eco!("expected {} more {}", remaining, noun) - }); - } - - SyntaxKind::Raw { column } - } - - fn numbering(&mut self, start: usize) -> SyntaxKind { - self.s.eat_while(char::is_ascii_digit); - - let read = self.s.from(start); - if self.s.eat_if('.') { - if let Ok(number) = read.parse::() { - if number == 0 { - return self.error("must be positive"); - } - - return SyntaxKind::EnumNumbering; - } - } - - self.text() - } - - fn reference(&mut self) -> SyntaxKind { - self.s.eat_while(is_id_continue); - SyntaxKind::Ref - } - fn in_word(&self) -> bool { - let alphanumeric = |c: Option| c.map_or(false, |c| c.is_alphanumeric()); + let alphanum = |c: Option| c.map_or(false, |c| c.is_alphanumeric()); let prev = self.s.scout(-2); let next = self.s.peek(); - alphanumeric(prev) && alphanumeric(next) + alphanum(prev) && alphanum(next) + } + + fn space_and_more(&self) -> bool { + let mut s = self.s; + if !s.at(char::is_whitespace) { + return false; + } + s.eat_while(|c: char| c.is_whitespace() && !is_newline(c)); + !s.done() && !s.at(is_newline) } } @@ -586,26 +582,11 @@ impl Lexer<'_> { }); if !self.s.eat_if('"') { - self.terminated = false; return self.error_at_end("expected quote"); } SyntaxKind::Str } - - fn label(&mut self) -> SyntaxKind { - let label = self.s.eat_while(is_id_continue); - if label.is_empty() { - return self.error("label cannot be empty"); - } - - if !self.s.eat_if('>') { - self.terminated = false; - return self.error_at_end("expected closing angle bracket"); - } - - SyntaxKind::Label - } } /// Try to parse an identifier into a keyword. @@ -632,34 +613,6 @@ fn keyword(ident: &str) -> Option { }) } -/// The column index of a given index in the source string, given a column -/// offset for the first line. -fn column(string: &str, index: usize, offset: usize) -> usize { - let mut apply_offset = false; - let res = string[..index] - .char_indices() - .rev() - .take_while(|&(_, c)| !is_newline(c)) - .inspect(|&(i, _)| { - if i == 0 { - apply_offset = true - } - }) - .count(); - - // The loop is never executed if the slice is empty, but we are of - // course still at the start of the first line. - if index == 0 { - apply_offset = true; - } - - if apply_offset { - res + offset - } else { - res - } -} - /// Whether this character denotes a newline. #[inline] pub fn is_newline(character: char) -> bool { @@ -695,6 +648,21 @@ pub(super) fn split_newlines(text: &str) -> Vec<&str> { lines } +/// Count the number of newlines in text. +fn count_newlines(text: &str) -> usize { + let mut newlines = 0; + let mut s = Scanner::new(text); + while let Some(c) = s.eat() { + if is_newline(c) { + if c == '\r' { + s.eat_if('\n'); + } + newlines += 1; + } + } + newlines +} + /// Whether a string is a valid unicode identifier. /// /// In addition to what is specified in the [Unicode Standard][uax31], we allow: diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index a2bb57662..ae12e818c 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -6,7 +6,7 @@ mod kind; mod lexer; mod node; mod parser; -mod reparse; +mod reparser; mod source; mod span; @@ -14,5 +14,6 @@ pub use self::kind::*; pub use self::lexer::*; pub use self::node::*; pub use self::parser::*; +pub use self::reparser::*; pub use self::source::*; pub use self::span::*; diff --git a/src/syntax/node.rs b/src/syntax/node.rs index 283d55b4c..3465f73fd 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -12,15 +12,15 @@ use crate::util::EcoString; #[derive(Clone, PartialEq, Hash)] pub struct SyntaxNode(Repr); -/// The two internal representations. +/// The three internal representations. #[derive(Clone, PartialEq, Hash)] enum Repr { /// A leaf node. Leaf(LeafNode), /// A reference-counted inner node. Inner(Arc), - /// An error. - Error(ErrorNode), + /// An error node. + Error(Arc), } impl SyntaxNode { @@ -36,7 +36,7 @@ impl SyntaxNode { /// Create a new error node. pub fn error(message: impl Into, pos: ErrorPos, len: usize) -> Self { - Self(Repr::Error(ErrorNode::new(message, pos, len))) + Self(Repr::Error(Arc::new(ErrorNode::new(message, pos, len)))) } /// The type of the node. @@ -134,17 +134,13 @@ impl SyntaxNode { .collect() } } +} - /// Change the type of the node. - pub(super) fn convert_to(&mut self, kind: SyntaxKind) { - debug_assert!(!kind.is_error()); - match &mut self.0 { - Repr::Leaf(leaf) => leaf.kind = kind, - Repr::Inner(inner) => { - let node = Arc::make_mut(inner); - node.kind = kind; - } - Repr::Error(_) => {} +impl SyntaxNode { + /// Mark this node as erroneous. + pub(super) fn make_erroneous(&mut self) { + if let Repr::Inner(inner) = &mut self.0 { + Arc::make_mut(inner).erroneous = true; } } @@ -159,7 +155,7 @@ impl SyntaxNode { match &mut self.0 { Repr::Leaf(leaf) => leaf.span = span, Repr::Inner(inner) => Arc::make_mut(inner).synthesize(span), - Repr::Error(error) => error.span = span, + Repr::Error(error) => Arc::make_mut(error).span = span, } } @@ -177,7 +173,7 @@ impl SyntaxNode { match &mut self.0 { Repr::Leaf(leaf) => leaf.span = mid, Repr::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within)?, - Repr::Error(error) => error.span = mid, + Repr::Error(error) => Arc::make_mut(error).span = mid, } Ok(()) @@ -245,7 +241,7 @@ impl SyntaxNode { } /// The upper bound of assigned numbers in this subtree. - fn upper(&self) -> u64 { + pub(super) fn upper(&self) -> u64 { match &self.0 { Repr::Inner(inner) => inner.upper, Repr::Leaf(leaf) => leaf.span.number() + 1, @@ -297,7 +293,7 @@ impl LeafNode { impl Debug for LeafNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{:?}: {}", self.kind, self.len()) + write!(f, "{:?}: {:?}", self.kind, self.text) } } @@ -588,7 +584,7 @@ impl ErrorNode { impl Debug for ErrorNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "({}): {}", self.message, self.len) + write!(f, "Error: {} ({})", self.len, self.message) } } @@ -888,7 +884,7 @@ mod tests { let prev = leaf.prev_leaf().unwrap(); let next = leaf.next_leaf().unwrap(); assert_eq!(prev.kind(), SyntaxKind::Eq); - assert_eq!(leaf.kind(), SyntaxKind::Space { newlines: 0 }); + assert_eq!(leaf.kind(), SyntaxKind::Space); assert_eq!(next.kind(), SyntaxKind::Int); } } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 1584e59b8..0e1b52b1a 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -1,262 +1,93 @@ use std::collections::HashSet; -use std::fmt::{self, Display, Formatter}; -use std::mem; +use std::ops::Range; -use super::ast::{self, Assoc, BinOp, UnOp}; -use super::{ErrorPos, LexMode, Lexer, SyntaxKind, SyntaxNode}; +use super::{ast, is_newline, ErrorPos, LexMode, Lexer, SyntaxKind, SyntaxNode}; use crate::util::{format_eco, EcoString}; /// Parse a source file. pub fn parse(text: &str) -> SyntaxNode { - let mut p = Parser::new(text, LexMode::Markup); - markup(&mut p, true); + let mut p = Parser::new(text, 0, LexMode::Markup); + markup(&mut p, true, 0, |_| false); p.finish().into_iter().next().unwrap() } -/// Parse code directly, only used for syntax highlighting. +/// Parse code directly. +/// +/// This is only used for syntax highlighting. pub fn parse_code(text: &str) -> SyntaxNode { - let mut p = Parser::new(text, LexMode::Code); - p.perform(SyntaxKind::CodeBlock, code); + 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() } -/// Reparse a code block. -/// -/// Returns `Some` if all of the input was consumed. -pub(super) fn reparse_code_block( - prefix: &str, - text: &str, - end_pos: usize, -) -> Option<(Vec, bool, usize)> { - let mut p = Parser::with_prefix(prefix, text, LexMode::Code); - if !p.at(SyntaxKind::LeftBrace) { - return None; - } - - code_block(&mut p); - - let (mut node, terminated) = p.consume()?; - let first = node.remove(0); - if first.len() != end_pos { - return None; - } - - Some((vec![first], terminated, 1)) -} - -/// Reparse a content block. -/// -/// Returns `Some` if all of the input was consumed. -pub(super) fn reparse_content_block( - prefix: &str, - text: &str, - end_pos: usize, -) -> Option<(Vec, bool, usize)> { - let mut p = Parser::with_prefix(prefix, text, LexMode::Code); - if !p.at(SyntaxKind::LeftBracket) { - return None; - } - - content_block(&mut p); - - let (mut node, terminated) = p.consume()?; - let first = node.remove(0); - if first.len() != end_pos { - return None; - } - - Some((vec![first], terminated, 1)) -} - -/// Reparse a sequence markup elements without the topmost node. -/// -/// Returns `Some` if all of the input was consumed. -pub(super) fn reparse_markup_elements( - prefix: &str, - text: &str, - end_pos: usize, - differential: isize, - reference: &[SyntaxNode], +fn markup( + p: &mut Parser, mut at_start: bool, min_indent: usize, -) -> Option<(Vec, bool, usize)> { - let mut p = Parser::with_prefix(prefix, text, LexMode::Markup); - - let mut node: Option<&SyntaxNode> = None; - let mut iter = reference.iter(); - let mut offset = differential; - let mut replaced = 0; - let mut stopped = false; - - 'outer: while !p.eof() { - if let Some(SyntaxKind::Space { newlines: (1..) }) = p.peek() { - if p.column(p.current_end()) < min_indent { - return None; + mut stop: impl FnMut(SyntaxKind) -> bool, +) { + let m = p.marker(); + while !p.eof() && !stop(p.current) { + if p.newline() { + at_start = true; + if min_indent > 0 && p.column(p.current_end()) < min_indent { + break; } - } - - markup_node(&mut p, &mut at_start); - - if p.prev_end() <= end_pos { + p.eat(); continue; } - let recent = p.marker().before(&p).unwrap(); - let recent_start = p.prev_end() - recent.len(); - - while offset <= recent_start as isize { - if let Some(node) = node { - // The nodes are equal, at the same position and have the - // same content. The parsing trees have converged again, so - // the reparse may stop here. - if offset == recent_start as isize && node == recent { - replaced -= 1; - stopped = true; - break 'outer; - } - } - - if let Some(node) = node { - offset += node.len() as isize; - } - - node = iter.next(); - if node.is_none() { - break; - } - - replaced += 1; + let prev = p.prev_end(); + markup_expr(p, &mut at_start); + if !p.progress(prev) { + p.unexpected(); } } - - if p.eof() && !stopped { - replaced = reference.len(); - } - - let (mut res, terminated) = p.consume()?; - if stopped { - res.pop().unwrap(); - } - - Some((res, terminated, replaced)) + p.wrap(m, SyntaxKind::Markup); } -/// Parse markup. -/// -/// If `at_start` is true, things like headings that may only appear at the -/// beginning of a line or content block are initially allowed. -fn markup(p: &mut Parser, mut at_start: bool) { - p.perform(SyntaxKind::Markup { min_indent: 0 }, |p| { - while !p.eof() { - markup_node(p, &mut at_start); - } - }); -} - -/// Parse markup that stays right of the given `column`. -fn markup_indented(p: &mut Parser, min_indent: usize) { - p.eat_while(|t| match t { - SyntaxKind::Space { newlines } => newlines == 0, - SyntaxKind::LineComment | SyntaxKind::BlockComment => true, - _ => false, - }); - - let marker = p.marker(); - let mut at_start = false; - - while !p.eof() { - match p.peek() { - Some(SyntaxKind::Space { newlines: (1..) }) - if p.column(p.current_end()) < min_indent => - { - break; - } - _ => {} - } - - markup_node(p, &mut at_start); - } - - marker.end(p, SyntaxKind::Markup { min_indent }); -} - -/// Parse a line of markup that can prematurely end if `f` returns true. -fn markup_line(p: &mut Parser, mut f: F) -where - F: FnMut(SyntaxKind) -> bool, -{ - p.eat_while(|t| match t { - SyntaxKind::Space { newlines } => newlines == 0, - SyntaxKind::LineComment | SyntaxKind::BlockComment => true, - _ => false, - }); - - p.perform(SyntaxKind::Markup { min_indent: usize::MAX }, |p| { - let mut at_start = false; - while let Some(kind) = p.peek() { - if let SyntaxKind::Space { newlines: (1..) } = kind { - break; - } - - if f(kind) { - break; - } - - markup_node(p, &mut at_start); - } - }); -} - -fn markup_node(p: &mut Parser, at_start: &mut bool) { - let Some(token) = p.peek() else { return }; - match token { - // Whitespace. - SyntaxKind::Space { newlines } => { - *at_start |= newlines > 0; +pub(super) fn reparse_markup( + text: &str, + range: Range, + at_start: &mut bool, + mut stop: impl FnMut(SyntaxKind) -> bool, +) -> Option> { + let mut p = Parser::new(&text, range.start, LexMode::Markup); + while !p.eof() && !stop(p.current) && p.current_start() < range.end { + if p.newline() { + *at_start = true; p.eat(); - return; + continue; } - // Comments. - SyntaxKind::LineComment | SyntaxKind::BlockComment => { - p.eat(); - return; + let prev = p.prev_end(); + markup_expr(&mut p, at_start); + if !p.progress(prev) { + p.unexpected(); } + } + (p.balanced && p.current_start() == range.end).then(|| p.finish()) +} - // Text and markup. - SyntaxKind::Text - | SyntaxKind::Linebreak - | SyntaxKind::SmartQuote { .. } - | SyntaxKind::Escape - | SyntaxKind::Shorthand - | SyntaxKind::Symbol - | SyntaxKind::Link - | SyntaxKind::Raw { .. } - | SyntaxKind::Ref => p.eat(), - - // Math. - SyntaxKind::Dollar => math(p), - - // Strong, emph, heading. +fn markup_expr(p: &mut Parser, at_start: &mut bool) { + match p.current() { SyntaxKind::Star => strong(p), SyntaxKind::Underscore => emph(p), - SyntaxKind::Eq => heading(p, *at_start), + 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), - // Lists. - SyntaxKind::Minus => list_item(p, *at_start), - SyntaxKind::Plus | SyntaxKind::EnumNumbering => enum_item(p, *at_start), - SyntaxKind::Slash => { - term_item(p, *at_start).ok(); - } - SyntaxKind::Colon => { - let marker = p.marker(); - p.eat(); - marker.convert(p, SyntaxKind::Text); - } + SyntaxKind::HeadingMarker + | SyntaxKind::ListMarker + | SyntaxKind::EnumMarker + | SyntaxKind::TermMarker + | SyntaxKind::Colon => p.convert(SyntaxKind::Text), - // Hashtag + keyword / identifier. SyntaxKind::Ident - | SyntaxKind::Label | SyntaxKind::Let | SyntaxKind::Set | SyntaxKind::Show @@ -267,204 +98,148 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { | SyntaxKind::Include | SyntaxKind::Break | SyntaxKind::Continue - | SyntaxKind::Return => embedded_expr(p), + | SyntaxKind::Return + | SyntaxKind::LeftBrace + | SyntaxKind::LeftBracket => embedded_code_expr(p), - // Code and content block. - SyntaxKind::LeftBrace => code_block(p), - SyntaxKind::LeftBracket => content_block(p), + SyntaxKind::Text + | SyntaxKind::Linebreak + | SyntaxKind::Escape + | SyntaxKind::Shorthand + | SyntaxKind::Symbol + | SyntaxKind::SmartQuote + | SyntaxKind::Raw + | SyntaxKind::Link + | SyntaxKind::Label + | SyntaxKind::Ref => p.eat(), - SyntaxKind::Error => p.eat(), - _ => p.unexpected(), - }; + SyntaxKind::Space + | SyntaxKind::Parbreak + | SyntaxKind::LineComment + | SyntaxKind::BlockComment => { + p.eat(); + return; + } + _ => {} + } *at_start = false; } fn strong(p: &mut Parser) { - p.perform(SyntaxKind::Strong, |p| { - p.start_group(Group::Strong); - markup(p, false); - p.end_group(); - }) + let m = p.marker(); + p.expect(SyntaxKind::Star); + markup(p, false, 0, |kind| { + kind == SyntaxKind::Star + || kind == SyntaxKind::Parbreak + || kind == SyntaxKind::RightBracket + }); + p.expect(SyntaxKind::Star); + p.wrap(m, SyntaxKind::Strong); } fn emph(p: &mut Parser) { - p.perform(SyntaxKind::Emph, |p| { - p.start_group(Group::Emph); - markup(p, false); - p.end_group(); - }) -} - -fn heading(p: &mut Parser, at_start: bool) { - let marker = p.marker(); - let mut markers = vec![]; - while p.at(SyntaxKind::Eq) { - markers.push(p.marker()); - p.eat(); - } - - if at_start && p.peek().map_or(true, |kind| kind.is_space()) { - p.eat_while(|kind| kind == SyntaxKind::Space { newlines: 0 }); - markup_line(p, |kind| matches!(kind, SyntaxKind::Label)); - marker.end(p, SyntaxKind::Heading); - } else { - for marker in markers { - marker.convert(p, SyntaxKind::Text); - } - } -} - -fn list_item(p: &mut Parser, at_start: bool) { - let marker = p.marker(); - p.assert(SyntaxKind::Minus); - - let min_indent = p.column(p.prev_end()); - if at_start && p.eat_if(SyntaxKind::Space { newlines: 0 }) && !p.eof() { - markup_indented(p, min_indent); - marker.end(p, SyntaxKind::ListItem); - } else { - marker.convert(p, SyntaxKind::Text); - } -} - -fn enum_item(p: &mut Parser, at_start: bool) { - let marker = p.marker(); - p.eat(); - - let min_indent = p.column(p.prev_end()); - if at_start && p.eat_if(SyntaxKind::Space { newlines: 0 }) && !p.eof() { - markup_indented(p, min_indent); - marker.end(p, SyntaxKind::EnumItem); - } else { - marker.convert(p, SyntaxKind::Text); - } -} - -fn term_item(p: &mut Parser, at_start: bool) -> ParseResult { - let marker = p.marker(); - p.eat(); - - let min_indent = p.column(p.prev_end()); - if at_start && p.eat_if(SyntaxKind::Space { newlines: 0 }) && !p.eof() { - markup_line(p, |node| matches!(node, SyntaxKind::Colon)); - p.expect(SyntaxKind::Colon)?; - markup_indented(p, min_indent); - marker.end(p, SyntaxKind::TermItem); - } else { - marker.convert(p, SyntaxKind::Text); - } - - Ok(()) -} - -fn embedded_expr(p: &mut Parser) { - // Does the expression need termination or can content follow directly? - let stmt = matches!( - p.peek(), - Some( - SyntaxKind::Let - | SyntaxKind::Set - | SyntaxKind::Show - | SyntaxKind::Import - | SyntaxKind::Include - ) - ); - - p.start_group(Group::Expr); - let res = expr_prec(p, true, 0); - if stmt && res.is_ok() && !p.eof() { - p.expected("semicolon or line break"); - } - p.end_group(); -} - -fn math(p: &mut Parser) { - p.perform(SyntaxKind::Math, |p| { - p.start_group(Group::Math); - while !p.eof() { - math_node(p); - } - p.end_group(); + let m = p.marker(); + p.expect(SyntaxKind::Underscore); + markup(p, false, 0, |kind| { + kind == SyntaxKind::Underscore + || kind == SyntaxKind::Parbreak + || kind == SyntaxKind::RightBracket }); + p.expect(SyntaxKind::Underscore); + p.wrap(m, SyntaxKind::Emph); } -fn math_node(p: &mut Parser) { - math_node_prec(p, 0, None) +fn heading(p: &mut Parser) { + let m = p.marker(); + p.expect(SyntaxKind::HeadingMarker); + whitespace(p); + markup(p, false, usize::MAX, |kind| { + kind == SyntaxKind::Label || kind == SyntaxKind::RightBracket + }); + p.wrap(m, SyntaxKind::Heading); } -fn math_node_prec(p: &mut Parser, min_prec: usize, stop: Option) { - let marker = p.marker(); - math_primary(p); +fn list_item(p: &mut Parser) { + let m = p.marker(); + p.expect(SyntaxKind::ListMarker); + let min_indent = p.column(p.prev_end()); + whitespace(p); + markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket); + p.wrap(m, SyntaxKind::ListItem); +} - loop { - let (kind, mut prec, assoc, stop) = match p.peek() { - v if v == stop => break, - Some(SyntaxKind::Underscore) => { - (SyntaxKind::Script, 2, Assoc::Right, Some(SyntaxKind::Hat)) - } - Some(SyntaxKind::Hat) => { - (SyntaxKind::Script, 2, Assoc::Right, Some(SyntaxKind::Underscore)) - } - Some(SyntaxKind::Slash) => (SyntaxKind::Frac, 1, Assoc::Left, None), - _ => break, - }; +fn enum_item(p: &mut Parser) { + let m = p.marker(); + p.expect(SyntaxKind::EnumMarker); + let min_indent = p.column(p.prev_end()); + whitespace(p); + markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket); + p.wrap(m, SyntaxKind::EnumItem); +} - if prec < min_prec { - break; - } - - match assoc { - Assoc::Left => prec += 1, - Assoc::Right => {} - } +fn term_item(p: &mut Parser) { + let m = p.marker(); + p.expect(SyntaxKind::TermMarker); + let min_indent = p.column(p.prev_end()); + whitespace(p); + markup(p, false, usize::MAX, |kind| { + kind == SyntaxKind::Colon || kind == SyntaxKind::RightBracket + }); + p.expect(SyntaxKind::Colon); + whitespace(p); + markup(p, false, min_indent, |kind| kind == SyntaxKind::RightBracket); + p.wrap(m, SyntaxKind::TermItem); +} +fn whitespace(p: &mut Parser) { + while p.current().is_trivia() { p.eat(); - math_node_prec(p, prec, stop); - - // Allow up to two different scripts. We do not risk encountering the - // previous script kind again here due to right-associativity. - if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { - math_node_prec(p, prec, None); - } - - marker.end(p, kind); } } -/// Parse a primary math node. -fn math_primary(p: &mut Parser) { - let Some(token) = p.peek() else { return }; - match token { - // Spaces and expressions. - SyntaxKind::Space { .. } - | SyntaxKind::Linebreak - | SyntaxKind::Escape - | SyntaxKind::Str - | SyntaxKind::Shorthand - | SyntaxKind::AlignPoint - | SyntaxKind::Symbol => p.eat(), +fn equation(p: &mut Parser) { + let m = p.marker(); + p.enter(LexMode::Math); + p.expect(SyntaxKind::Dollar); + math(p, |kind| kind == SyntaxKind::Dollar); + p.expect(SyntaxKind::Dollar); + p.exit(); + p.wrap(m, SyntaxKind::Math); +} - // Atoms. - SyntaxKind::Atom => match p.peek_src() { - "(" => math_group(p, Group::MathRow('(', ')')), - "{" => math_group(p, Group::MathRow('{', '}')), - "[" => math_group(p, Group::MathRow('[', ']')), +fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { + while !p.eof() && !stop(p.current()) { + let prev = p.prev_end(); + math_expr(p); + if !p.progress(prev) { + p.unexpected(); + } + } +} + +fn math_expr(p: &mut Parser) { + math_expr_prec(p, 0, SyntaxKind::Eof) +} + +fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { + let m = p.marker(); + match p.current() { + SyntaxKind::Ident => { + p.eat(); + if p.directly_at(SyntaxKind::Atom) && p.current_text() == "(" { + math_args(p); + p.wrap(m, SyntaxKind::FuncCall); + } + } + + SyntaxKind::Atom => match p.current_text() { + "(" => math_delimited(p, ")"), + "{" => math_delimited(p, "}"), + "[" => math_delimited(p, "]"), _ => p.eat(), }, - // Identifiers and math calls. - SyntaxKind::Ident => { - let marker = p.marker(); - p.eat(); - - // Parenthesis or bracket means this is a function call. - if matches!(p.peek_direct(), Some(SyntaxKind::Atom) if p.peek_src() == "(") { - marker.perform(p, SyntaxKind::FuncCall, math_args); - } - } - - // Hashtag + keyword / identifier. SyntaxKind::Let | SyntaxKind::Set | SyntaxKind::Show @@ -475,55 +250,164 @@ fn math_primary(p: &mut Parser) { | SyntaxKind::Include | SyntaxKind::Break | SyntaxKind::Continue - | SyntaxKind::Return => embedded_expr(p), + | SyntaxKind::Return + | SyntaxKind::LeftBrace + | SyntaxKind::LeftBracket => embedded_code_expr(p), - // Code and content block. - SyntaxKind::LeftBrace => code_block(p), - SyntaxKind::LeftBracket => content_block(p), + SyntaxKind::Linebreak + | SyntaxKind::Escape + | SyntaxKind::Shorthand + | SyntaxKind::Symbol + | SyntaxKind::AlignPoint + | SyntaxKind::Str => p.eat(), - _ => p.unexpected(), + _ => return, + } + + while !p.eof() && !p.at(stop) { + let Some((kind, stop, assoc, mut prec)) = math_op(p.current()) else { + break; + }; + + if prec < min_prec { + break; + } + + match assoc { + ast::Assoc::Left => prec += 1, + ast::Assoc::Right => {} + } + + p.eat(); + math_expr_prec(p, prec, stop); + if p.eat_if(SyntaxKind::Underscore) || p.eat_if(SyntaxKind::Hat) { + math_expr_prec(p, prec, SyntaxKind::Eof); + } + + p.wrap(m, kind); } } -fn math_group(p: &mut Parser, group: Group) { - p.perform(SyntaxKind::Math, |p| { - p.start_group(group); - while !p.eof() { - math_node(p); +fn math_delimited(p: &mut Parser, closing: &str) { + let m = p.marker(); + p.expect(SyntaxKind::Atom); + while !p.eof() + && !p.at(SyntaxKind::Dollar) + && (!p.at(SyntaxKind::Atom) || p.current_text() != closing) + { + let prev = p.prev_end(); + math_expr(p); + if !p.progress(prev) { + p.unexpected(); } - p.end_group(); - }) + } + p.expect(SyntaxKind::Atom); + p.wrap(m, SyntaxKind::Math); } -fn expr(p: &mut Parser) -> ParseResult { - expr_prec(p, false, 0) +fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> { + match kind { + SyntaxKind::Underscore => { + Some((SyntaxKind::Script, SyntaxKind::Hat, ast::Assoc::Right, 2)) + } + SyntaxKind::Hat => { + Some((SyntaxKind::Script, SyntaxKind::Underscore, ast::Assoc::Right, 2)) + } + SyntaxKind::Slash => { + Some((SyntaxKind::Frac, SyntaxKind::Eof, ast::Assoc::Left, 1)) + } + _ => None, + } } -/// Parse an expression with operators having at least the minimum precedence. -/// -/// If `atomic` is true, this does not parse binary operations and arrow -/// functions, which is exactly what we want in a shorthand expression directly -/// in markup. -/// -/// Stops parsing at operations with lower precedence than `min_prec`, -fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { - let marker = p.marker(); - - // Start the unary expression. - match p.peek().and_then(UnOp::from_token) { - Some(op) if !atomic => { - p.eat(); - let prec = op.precedence(); - expr_prec(p, atomic, prec)?; - marker.end(p, SyntaxKind::Unary); +fn math_args(p: &mut Parser) { + p.expect(SyntaxKind::Atom); + let m = p.marker(); + let mut m2 = p.marker(); + while !p.eof() { + match p.current_text() { + ")" => break, + "," => { + p.wrap(m2, SyntaxKind::Math); + p.convert(SyntaxKind::Comma); + m2 = p.marker(); + continue; + } + _ => {} } - _ => primary(p, atomic)?, - }; + + let prev = p.prev_end(); + math_expr(p); + if !p.progress(prev) { + p.unexpected(); + } + } + if m2 != p.marker() { + p.wrap(m2, SyntaxKind::Math); + } + p.wrap(m, SyntaxKind::Args); + p.expect(SyntaxKind::Atom); +} + +fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { + while !p.eof() && !stop(p.current()) { + p.stop_at_newline(true); + let prev = p.prev_end(); + code_expr(p); + if p.progress(prev) + && !p.eof() + && !stop(p.current()) + && !p.eat_if(SyntaxKind::Semicolon) + { + p.expected("semicolon or line break"); + } + p.unstop(); + if !p.progress(prev) && !p.eof() { + p.unexpected(); + } + } +} + +fn code_expr(p: &mut Parser) { + code_expr_prec(p, false, 0) +} + +fn embedded_code_expr(p: &mut Parser) { + let stmt = matches!( + p.current(), + SyntaxKind::Let + | SyntaxKind::Set + | SyntaxKind::Show + | SyntaxKind::Import + | 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(); +} + +fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) { + let m = p.marker(); + if let Some(op) = ast::UnOp::from_kind(p.current()) { + p.eat(); + code_expr_prec(p, atomic, op.precedence()); + p.wrap(m, SyntaxKind::Unary); + } else { + code_primary(p, atomic); + } loop { - // Parenthesis or bracket means this is a function call. - if let Some(SyntaxKind::LeftParen | SyntaxKind::LeftBracket) = p.peek_direct() { - marker.perform(p, SyntaxKind::FuncCall, args)?; + if p.directly_at(SyntaxKind::LeftParen) || p.directly_at(SyntaxKind::LeftBracket) + { + args(p); + p.wrap(m, SyntaxKind::FuncCall); continue; } @@ -531,711 +415,571 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { break; } - // Method call or field access. if p.eat_if(SyntaxKind::Dot) { - ident(p)?; - if let Some(SyntaxKind::LeftParen | SyntaxKind::LeftBracket) = p.peek_direct() + p.expect(SyntaxKind::Ident); + if p.directly_at(SyntaxKind::LeftParen) + || p.directly_at(SyntaxKind::LeftBracket) { - marker.perform(p, SyntaxKind::MethodCall, args)?; + args(p); + p.wrap(m, SyntaxKind::MethodCall); } else { - marker.end(p, SyntaxKind::FieldAccess); + p.wrap(m, SyntaxKind::FieldAccess) } continue; } - let op = if p.eat_if(SyntaxKind::Not) { + let binop = if p.eat_if(SyntaxKind::Not) { if p.at(SyntaxKind::In) { - BinOp::NotIn + Some(ast::BinOp::NotIn) } else { p.expected("keyword `in`"); - return Err(ParseError); + break; } } else { - match p.peek().and_then(BinOp::from_token) { - Some(binop) => binop, - None => break, - } + ast::BinOp::from_kind(p.current()) }; - let mut prec = op.precedence(); - if prec < min_prec { - break; - } - - p.eat(); - - match op.assoc() { - Assoc::Left => prec += 1, - Assoc::Right => {} - } - - marker.perform(p, SyntaxKind::Binary, |p| expr_prec(p, atomic, prec))?; - } - - Ok(()) -} - -fn primary(p: &mut Parser, atomic: bool) -> ParseResult { - match p.peek() { - // Literals and few other things. - Some( - SyntaxKind::None - | SyntaxKind::Auto - | SyntaxKind::Int - | SyntaxKind::Float - | SyntaxKind::Bool - | SyntaxKind::Numeric - | SyntaxKind::Str - | SyntaxKind::Label - | SyntaxKind::Raw { .. }, - ) => { - p.eat(); - Ok(()) - } - - // Things that start with an identifier. - Some(SyntaxKind::Ident) => { - let marker = p.marker(); - p.eat(); - - // Arrow means this is a closure's lone parameter. - if !atomic && p.at(SyntaxKind::Arrow) { - marker.end(p, SyntaxKind::Params); - p.assert(SyntaxKind::Arrow); - marker.perform(p, SyntaxKind::Closure, expr) - } else { - Ok(()) + if let Some(op) = binop { + let mut prec = op.precedence(); + if prec < min_prec { + break; } - } - // Structures. - Some(SyntaxKind::LeftParen) => parenthesized(p, atomic), - Some(SyntaxKind::LeftBrace) => Ok(code_block(p)), - Some(SyntaxKind::LeftBracket) => Ok(content_block(p)), - Some(SyntaxKind::Dollar) => Ok(math(p)), + match op.assoc() { + ast::Assoc::Left => prec += 1, + ast::Assoc::Right => {} + } - // Keywords. - Some(SyntaxKind::Let) => let_binding(p), - Some(SyntaxKind::Set) => set_rule(p), - Some(SyntaxKind::Show) => show_rule(p), - Some(SyntaxKind::If) => conditional(p), - Some(SyntaxKind::While) => while_loop(p), - Some(SyntaxKind::For) => for_loop(p), - Some(SyntaxKind::Import) => module_import(p), - Some(SyntaxKind::Include) => module_include(p), - Some(SyntaxKind::Break) => break_stmt(p), - Some(SyntaxKind::Continue) => continue_stmt(p), - Some(SyntaxKind::Return) => return_stmt(p), - - Some(SyntaxKind::Error) => { p.eat(); - Err(ParseError) - } - - // Nothing. - _ => { - p.expected_found("expression"); - Err(ParseError) - } - } -} - -fn ident(p: &mut Parser) -> ParseResult { - match p.peek() { - Some(SyntaxKind::Ident) => { - p.eat(); - Ok(()) - } - _ => { - p.expected_found("identifier"); - Err(ParseError) - } - } -} - -/// Parse something that starts with a parenthesis, which can be either of: -/// - Array literal -/// - Dictionary literal -/// - Parenthesized expression -/// - Parameter list of closure expression -fn parenthesized(p: &mut Parser, atomic: bool) -> ParseResult { - let marker = p.marker(); - - p.start_group(Group::Paren); - let colon = p.eat_if(SyntaxKind::Colon); - let kind = collection(p, true).0; - p.end_group(); - - // Leading colon makes this a dictionary. - if colon { - dict(p, marker); - return Ok(()); - } - - // Arrow means this is a closure's parameter list. - if !atomic && p.at(SyntaxKind::Arrow) { - params(p, marker); - p.assert(SyntaxKind::Arrow); - return marker.perform(p, SyntaxKind::Closure, expr); - } - - // Transform into the identified collection. - match kind { - CollectionKind::Group => marker.end(p, SyntaxKind::Parenthesized), - CollectionKind::Positional => array(p, marker), - CollectionKind::Named => dict(p, marker), - } - - Ok(()) -} - -/// The type of a collection. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum CollectionKind { - /// The collection is only one item and has no comma. - Group, - /// The collection starts with a positional item and has multiple items or a - /// trailing comma. - Positional, - /// The collection starts with a colon or named item. - Named, -} - -/// Parse a collection. -/// -/// Returns the length of the collection and whether the literal contained any -/// commas. -fn collection(p: &mut Parser, keyed: bool) -> (CollectionKind, usize) { - let mut collection_kind = None; - let mut items = 0; - let mut can_group = true; - let mut missing_coma: Option = None; - - while !p.eof() { - let Ok(item_kind) = item(p, keyed) else { - p.eat_if(SyntaxKind::Comma); - collection_kind = Some(CollectionKind::Group); + code_expr_prec(p, false, prec); + p.wrap(m, SyntaxKind::Binary); continue; - }; - - match item_kind { - SyntaxKind::Spread => can_group = false, - SyntaxKind::Named if collection_kind.is_none() => { - collection_kind = Some(CollectionKind::Named); - can_group = false; - } - _ if collection_kind.is_none() => { - collection_kind = Some(CollectionKind::Positional); - } - _ => {} } - items += 1; - - if let Some(marker) = missing_coma.take() { - p.expected_at(marker, "comma"); - } - - if p.eof() { - break; - } - - if p.eat_if(SyntaxKind::Comma) { - can_group = false; - } else { - missing_coma = Some(p.trivia_start()); - } - } - - let kind = if can_group && items == 1 { - CollectionKind::Group - } else { - collection_kind.unwrap_or(CollectionKind::Positional) - }; - - (kind, items) -} - -fn item(p: &mut Parser, keyed: bool) -> ParseResult { - let marker = p.marker(); - if p.eat_if(SyntaxKind::Dots) { - marker.perform(p, SyntaxKind::Spread, expr)?; - return Ok(SyntaxKind::Spread); - } - - expr(p)?; - - if p.at(SyntaxKind::Colon) { - match marker.after(p).map(|c| c.kind()) { - Some(SyntaxKind::Ident) => { - p.eat(); - marker.perform(p, SyntaxKind::Named, expr)?; - } - Some(SyntaxKind::Str) if keyed => { - p.eat(); - marker.perform(p, SyntaxKind::Keyed, expr)?; - } - kind => { - let mut msg = EcoString::from("expected identifier"); - if keyed { - msg.push_str(" or string"); - } - if let Some(kind) = kind { - msg.push_str(", found "); - msg.push_str(kind.name()); - } - marker.to_error(p, msg); - p.eat(); - marker.perform(p, SyntaxKind::Named, expr).ok(); - return Err(ParseError); - } - } - - Ok(SyntaxKind::Named) - } else { - Ok(SyntaxKind::None) + break; } } -fn array(p: &mut Parser, marker: Marker) { - marker.filter_children(p, |x| match x.kind() { - SyntaxKind::Named | SyntaxKind::Keyed => Err("expected expression"), - _ => Ok(()), - }); - marker.end(p, SyntaxKind::Array); -} - -fn dict(p: &mut Parser, marker: Marker) { - let mut used = HashSet::new(); - marker.filter_children(p, |x| match x.kind() { - kind if kind.is_paren() => Ok(()), - SyntaxKind::Named | SyntaxKind::Keyed => { - if let Some(child) = x.children().next() { - let key = match child.cast::() { - Some(str) => str.get(), - None => child.text().clone(), - }; - - if !used.insert(key) { - return Err("pair has duplicate key"); - } +fn code_primary(p: &mut Parser, atomic: bool) { + let m = p.marker(); + match p.current() { + SyntaxKind::Ident => { + p.eat(); + if !atomic && p.at(SyntaxKind::Arrow) { + p.wrap(m, SyntaxKind::Params); + p.expect(SyntaxKind::Arrow); + code_expr(p); + p.wrap(m, SyntaxKind::Closure); } - Ok(()) } - SyntaxKind::Spread | SyntaxKind::Comma | SyntaxKind::Colon => Ok(()), - _ => Err("expected named or keyed pair"), - }); - marker.end(p, SyntaxKind::Dict); + + SyntaxKind::LeftBrace => code_block(p), + SyntaxKind::LeftBracket => content_block(p), + SyntaxKind::LeftParen => with_paren(p), + SyntaxKind::Dollar => equation(p), + SyntaxKind::Let => let_binding(p), + SyntaxKind::Set => set_rule(p), + SyntaxKind::Show => show_rule(p), + SyntaxKind::If => conditional(p), + SyntaxKind::While => while_loop(p), + SyntaxKind::For => for_loop(p), + SyntaxKind::Import => module_import(p), + SyntaxKind::Include => module_include(p), + SyntaxKind::Break => break_stmt(p), + SyntaxKind::Continue => continue_stmt(p), + SyntaxKind::Return => return_stmt(p), + + SyntaxKind::None + | SyntaxKind::Auto + | SyntaxKind::Int + | SyntaxKind::Float + | SyntaxKind::Bool + | SyntaxKind::Numeric + | SyntaxKind::Str + | SyntaxKind::Label + | SyntaxKind::Raw => p.eat(), + + _ => p.expected("expression"), + } } -fn params(p: &mut Parser, marker: Marker) { - marker.filter_children(p, |x| match x.kind() { - kind if kind.is_paren() => Ok(()), - SyntaxKind::Named | SyntaxKind::Ident | SyntaxKind::Comma => Ok(()), - SyntaxKind::Spread - if matches!( - x.children().last().map(|child| child.kind()), - Some(SyntaxKind::Ident) - ) => - { - Ok(()) - } - _ => Err("expected identifier, named pair or argument sink"), - }); - marker.end(p, SyntaxKind::Params); +fn block(p: &mut Parser) { + match p.current() { + SyntaxKind::LeftBracket => content_block(p), + SyntaxKind::LeftBrace => code_block(p), + _ => p.expected("block"), + } +} + +pub(super) fn reparse_block(text: &str, range: Range) -> Option { + let mut p = Parser::new(&text, range.start, LexMode::Code); + assert!(p.at(SyntaxKind::LeftBracket) || p.at(SyntaxKind::LeftBrace)); + block(&mut p); + (p.balanced && p.prev_end() == range.end) + .then(|| p.finish().into_iter().next().unwrap()) } -/// Parse a code block: `{...}`. fn code_block(p: &mut Parser) { - p.perform(SyntaxKind::CodeBlock, |p| { - p.start_group(Group::Brace); - code(p); - p.end_group(); - }); -} - -fn code(p: &mut Parser) { - while !p.eof() { - p.start_group(Group::Expr); - if expr(p).is_ok() && !p.eof() { - p.expected("semicolon or line break"); - } - p.end_group(); - - // Forcefully skip over newlines since the group's contents can't. - p.eat_while(SyntaxKind::is_space); - } + let m = p.marker(); + p.enter(LexMode::Code); + p.stop_at_newline(false); + p.expect(SyntaxKind::LeftBrace); + code(p, |kind| kind == SyntaxKind::RightBrace); + p.expect(SyntaxKind::RightBrace); + p.exit(); + p.unstop(); + p.wrap(m, SyntaxKind::CodeBlock); } fn content_block(p: &mut Parser) { - p.perform(SyntaxKind::ContentBlock, |p| { - p.start_group(Group::Bracket); - markup(p, true); - p.end_group(); - }); + let m = p.marker(); + p.enter(LexMode::Markup); + p.expect(SyntaxKind::LeftBracket); + markup(p, true, 0, |kind| kind == SyntaxKind::RightBracket); + p.expect(SyntaxKind::RightBracket); + p.exit(); + p.wrap(m, SyntaxKind::ContentBlock); } -fn args(p: &mut Parser) -> ParseResult { - match p.peek_direct() { - Some(SyntaxKind::LeftParen) => {} - Some(SyntaxKind::LeftBracket) => {} - _ => { - p.expected_found("argument list"); - return Err(ParseError); +fn with_paren(p: &mut Parser) { + let m = p.marker(); + let mut kind = collection(p, true); + if p.at(SyntaxKind::Arrow) { + validate_params(p, m); + p.wrap(m, SyntaxKind::Params); + p.expect(SyntaxKind::Arrow); + code_expr(p); + kind = SyntaxKind::Closure; + } + match kind { + SyntaxKind::Array => validate_array(p, m), + SyntaxKind::Dict => validate_dict(p, m), + _ => {} + } + p.wrap(m, kind); +} + +fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { + p.stop_at_newline(false); + p.expect(SyntaxKind::LeftParen); + + let mut count = 0; + let mut parenthesized = true; + let mut kind = None; + if keyed && p.eat_if(SyntaxKind::Colon) { + kind = Some(SyntaxKind::Dict); + parenthesized = false; + } + + while !p.current().is_terminator() { + let prev = p.prev_end(); + match item(p, keyed) { + SyntaxKind::Spread => parenthesized = false, + SyntaxKind::Named | SyntaxKind::Keyed if kind.is_none() => { + kind = Some(SyntaxKind::Dict); + parenthesized = false; + } + _ if kind.is_none() => kind = Some(SyntaxKind::Array), + _ => {} + } + + if !p.progress(prev) { + p.unexpected(); + continue; + } + + count += 1; + + if p.current().is_terminator() { + break; + } + + if p.expect(SyntaxKind::Comma) { + parenthesized = false; } } - p.perform(SyntaxKind::Args, |p| { - if p.at(SyntaxKind::LeftParen) { - let marker = p.marker(); - p.start_group(Group::Paren); - collection(p, false); - p.end_group(); + p.expect(SyntaxKind::RightParen); + p.unstop(); - let mut used = HashSet::new(); - marker.filter_children(p, |x| match x.kind() { - SyntaxKind::Named => { - if let Some(ident) = - x.children().next().and_then(|child| child.cast::()) - { - if !used.insert(ident.take()) { - return Err("duplicate argument"); - } - } - Ok(()) + if parenthesized && count == 1 { + SyntaxKind::Parenthesized + } else { + kind.unwrap_or(SyntaxKind::Array) + } +} + +fn item(p: &mut Parser, keyed: bool) -> SyntaxKind { + let m = p.marker(); + + if p.eat_if(SyntaxKind::Dots) { + code_expr(p); + p.wrap(m, SyntaxKind::Spread); + return SyntaxKind::Spread; + } + + code_expr(p); + + if !p.eat_if(SyntaxKind::Colon) { + return SyntaxKind::Int; + } + + code_expr(p); + + let kind = match p.node(m).map(SyntaxNode::kind) { + Some(SyntaxKind::Ident) => SyntaxKind::Named, + Some(SyntaxKind::Str) if keyed => SyntaxKind::Keyed, + _ => { + for child in p.post_process(m).next() { + if child.kind() == SyntaxKind::Colon { + break; } - _ => Ok(()), - }); - } - while p.peek_direct() == Some(SyntaxKind::LeftBracket) { - content_block(p); - } - }); - - Ok(()) -} - -fn math_args(p: &mut Parser) { - p.start_group(Group::MathRow('(', ')')); - p.perform(SyntaxKind::Args, |p| { - let mut marker = p.marker(); - while !p.eof() { - if matches!(p.peek(), Some(SyntaxKind::Atom) if p.peek_src() == ",") { - marker.end(p, SyntaxKind::Math); - let comma = p.marker(); - p.eat(); - comma.convert(p, SyntaxKind::Comma); - marker = p.marker(); - } else { - math_node(p); + let mut message = EcoString::from("expected identifier"); + if keyed { + message.push_str(" or string"); + } + message.push_str(", found "); + message.push_str(child.kind().name()); + child.convert_to_error(message); } + SyntaxKind::Named } - if marker != p.marker() { - marker.end(p, SyntaxKind::Math); - } - }); - p.end_group(); + }; + + p.wrap(m, kind); + kind } -fn let_binding(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::LetBinding, |p| { - p.assert(SyntaxKind::Let); +fn args(p: &mut Parser) { + if !p.at(SyntaxKind::LeftParen) && !p.at(SyntaxKind::LeftBracket) { + p.expected("argument list"); + } - let marker = p.marker(); - ident(p)?; + let m = p.marker(); + if p.at(SyntaxKind::LeftParen) { + collection(p, false); + validate_args(p, m); + } - // If a parenthesis follows, this is a function definition. - let has_params = p.peek_direct() == Some(SyntaxKind::LeftParen); - if has_params { - let marker = p.marker(); - p.start_group(Group::Paren); - collection(p, false); - p.end_group(); - params(p, marker); - } + while p.directly_at(SyntaxKind::LeftBracket) { + content_block(p); + } - if p.eat_if(SyntaxKind::Eq) { - expr(p)?; - } else if has_params { - // Function definitions must have a body. - p.expected("body"); - } - - // Rewrite into a closure expression if it's a function definition. - if has_params { - marker.end(p, SyntaxKind::Closure); - } - - Ok(()) - }) + p.wrap(m, SyntaxKind::Args); } -fn set_rule(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::SetRule, |p| { - p.assert(SyntaxKind::Set); - ident(p)?; - args(p)?; - if p.eat_if(SyntaxKind::If) { - expr(p)?; +fn let_binding(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Let); + + let m2 = p.marker(); + p.expect(SyntaxKind::Ident); + + let closure = p.directly_at(SyntaxKind::LeftParen); + if closure { + let m3 = p.marker(); + collection(p, false); + validate_params(p, m3); + p.wrap(m3, SyntaxKind::Params); + } + + let f = if closure { Parser::expect } else { Parser::eat_if }; + if f(p, SyntaxKind::Eq) { + code_expr(p); + } + + if closure { + p.wrap(m2, SyntaxKind::Closure); + } + + p.wrap(m, SyntaxKind::LetBinding); +} + +fn set_rule(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Set); + p.expect(SyntaxKind::Ident); + args(p); + if p.eat_if(SyntaxKind::If) { + code_expr(p); + } + p.wrap(m, SyntaxKind::SetRule); +} + +fn show_rule(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Show); + code_expr(p); + if p.eat_if(SyntaxKind::Colon) { + code_expr(p); + } + p.wrap(m, SyntaxKind::ShowRule); +} + +fn conditional(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::If); + code_expr(p); + block(p); + if p.eat_if(SyntaxKind::Else) { + if p.at(SyntaxKind::If) { + conditional(p); + } else { + block(p); } - Ok(()) - }) + } + p.wrap(m, SyntaxKind::Conditional); } -fn show_rule(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::ShowRule, |p| { - p.assert(SyntaxKind::Show); - expr(p)?; - if p.eat_if(SyntaxKind::Colon) { - expr(p)?; - } - Ok(()) - }) +fn while_loop(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::While); + code_expr(p); + block(p); + p.wrap(m, SyntaxKind::WhileLoop); } -fn conditional(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::Conditional, |p| { - p.assert(SyntaxKind::If); - - expr(p)?; - body(p)?; - - if p.eat_if(SyntaxKind::Else) { - if p.at(SyntaxKind::If) { - conditional(p)?; - } else { - body(p)?; - } - } - - Ok(()) - }) +fn for_loop(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::For); + for_pattern(p); + p.expect(SyntaxKind::In); + code_expr(p); + block(p); + p.wrap(m, SyntaxKind::ForLoop); } -fn while_loop(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::WhileLoop, |p| { - p.assert(SyntaxKind::While); - expr(p)?; - body(p) - }) -} - -fn for_loop(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::ForLoop, |p| { - p.assert(SyntaxKind::For); - for_pattern(p)?; - p.expect(SyntaxKind::In)?; - expr(p)?; - body(p) - }) -} - -fn for_pattern(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::ForPattern, |p| { - ident(p)?; +fn for_pattern(p: &mut Parser) { + let m = p.marker(); + if p.expect(SyntaxKind::Ident) { if p.eat_if(SyntaxKind::Comma) { - ident(p)?; + p.expect(SyntaxKind::Ident); } - Ok(()) - }) + p.wrap(m, SyntaxKind::ForPattern); + } } -fn module_import(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::ModuleImport, |p| { - p.assert(SyntaxKind::Import); - expr(p)?; +fn module_import(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Import); + code_expr(p); + if p.eat_if(SyntaxKind::Colon) && !p.eat_if(SyntaxKind::Star) { + import_items(p); + } + p.wrap(m, SyntaxKind::ModuleImport); +} - if !p.eat_if(SyntaxKind::Colon) || p.eat_if(SyntaxKind::Star) { - return Ok(()); +fn import_items(p: &mut Parser) { + let m = p.marker(); + while !p.eof() && !p.at(SyntaxKind::Semicolon) { + if !p.eat_if(SyntaxKind::Ident) { + p.unexpected(); } - - // This is the list of identifiers scenario. - p.perform(SyntaxKind::ImportItems, |p| { - let marker = p.marker(); - let items = collection(p, false).1; - if items == 0 { - p.expected("import items"); - } - marker.filter_children(p, |n| match n.kind() { - SyntaxKind::Ident | SyntaxKind::Comma => Ok(()), - _ => Err("expected identifier"), - }); - }); - - Ok(()) - }) -} - -fn module_include(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::ModuleInclude, |p| { - p.assert(SyntaxKind::Include); - expr(p) - }) -} - -fn break_stmt(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::LoopBreak, |p| { - p.assert(SyntaxKind::Break); - Ok(()) - }) -} - -fn continue_stmt(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::LoopContinue, |p| { - p.assert(SyntaxKind::Continue); - Ok(()) - }) -} - -fn return_stmt(p: &mut Parser) -> ParseResult { - p.perform(SyntaxKind::FuncReturn, |p| { - p.assert(SyntaxKind::Return); - if !p.at(SyntaxKind::Comma) && !p.eof() { - expr(p)?; + if p.current().is_terminator() { + break; } - Ok(()) - }) + p.expect(SyntaxKind::Comma); + } + p.wrap(m, SyntaxKind::ImportItems); } -fn body(p: &mut Parser) -> ParseResult { - match p.peek() { - Some(SyntaxKind::LeftBracket) => Ok(content_block(p)), - Some(SyntaxKind::LeftBrace) => Ok(code_block(p)), - _ => { - p.expected("body"); - Err(ParseError) +fn module_include(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Include); + code_expr(p); + p.wrap(m, SyntaxKind::ModuleInclude); +} + +fn break_stmt(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Break); + p.wrap(m, SyntaxKind::LoopBreak); +} + +fn continue_stmt(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Continue); + p.wrap(m, SyntaxKind::LoopContinue); +} + +fn return_stmt(p: &mut Parser) { + let m = p.marker(); + p.assert(SyntaxKind::Return); + if !p.current().is_terminator() && !p.at(SyntaxKind::Comma) { + code_expr(p); + } + p.wrap(m, SyntaxKind::FuncReturn); +} + +fn validate_array(p: &mut Parser, m: Marker) { + for child in p.post_process(m) { + let kind = child.kind(); + if kind == SyntaxKind::Named || kind == SyntaxKind::Keyed { + child.convert_to_error(format_eco!( + "expected expression, found {}", + kind.name() + )); } } } -/// A convenient token-based parser. -struct Parser<'s> { - /// An iterator over the source tokens. - lexer: Lexer<'s>, - /// Whether we are at the end of the file or of a group. - eof: bool, - /// The current token. - current: Option, - /// The end byte index of the last non-trivia token. - prev_end: usize, - /// The start byte index of the peeked token. - current_start: usize, - /// The stack of open groups. - groups: Vec, - /// The children of the currently built node. - children: Vec, - /// Whether the last group was not correctly terminated. - unterminated_group: bool, - /// Whether a group terminator was found that did not close a group. - stray_terminator: bool, +fn validate_dict(p: &mut Parser, m: Marker) { + let mut used = HashSet::new(); + for child in p.post_process(m) { + match child.kind() { + SyntaxKind::Named | SyntaxKind::Keyed => { + let Some(first) = child.children_mut().first_mut() else { continue }; + let key = match first.cast::() { + Some(str) => str.get(), + None => first.text().clone(), + }; + + if !used.insert(key) { + first.convert_to_error("duplicate key"); + child.make_erroneous(); + } + } + SyntaxKind::Spread => {} + SyntaxKind::LeftParen + | SyntaxKind::RightParen + | SyntaxKind::Comma + | SyntaxKind::Colon => {} + kind => { + child.convert_to_error(format_eco!( + "expected named or keyed pair, found {}", + kind.name() + )); + } + } + } } +fn validate_params(p: &mut Parser, m: Marker) { + let mut used = HashSet::new(); + for child in p.post_process(m) { + match child.kind() { + SyntaxKind::Ident => { + if !used.insert(child.text().clone()) { + child.convert_to_error("duplicate parameter"); + } + } + SyntaxKind::Named => { + let Some(within) = child.children_mut().first_mut() else { return }; + if !used.insert(within.text().clone()) { + within.convert_to_error("duplicate parameter"); + child.make_erroneous(); + } + } + SyntaxKind::Spread => { + let Some(within) = child.children_mut().last_mut() else { continue }; + if within.kind() != SyntaxKind::Ident { + within.convert_to_error(format_eco!( + "expected identifier, found {}", + within.kind().name(), + )); + child.make_erroneous(); + } + } + SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {} + kind => { + child.convert_to_error(format_eco!( + "expected identifier, named pair or argument sink, found {}", + kind.name() + )); + } + } + } +} + +fn validate_args(p: &mut Parser, m: Marker) { + let mut used = HashSet::new(); + for child in p.post_process(m) { + if child.kind() == SyntaxKind::Named { + let Some(within) = child.children_mut().first_mut() else { return }; + if !used.insert(within.text().clone()) { + within.convert_to_error("duplicate argument"); + child.make_erroneous(); + } + } + } +} + +/// Manages parsing of a stream of tokens. +struct Parser<'s> { + text: &'s str, + lexer: Lexer<'s>, + prev_end: usize, + current_start: usize, + current: SyntaxKind, + modes: Vec, + nodes: Vec, + stop_at_newline: Vec, + balanced: bool, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +struct Marker(usize); + impl<'s> Parser<'s> { - /// Create a new parser for the source string. - fn new(text: &'s str, mode: LexMode) -> Self { - Self::with_prefix("", text, mode) - } - - /// Create a new parser for the source string that is prefixed by some text - /// that does not need to be parsed but taken into account for column - /// calculation. - fn with_prefix(prefix: &str, text: &'s str, mode: LexMode) -> Self { - let mut lexer = Lexer::with_prefix(prefix, text, mode); + fn new(text: &'s str, offset: usize, mode: LexMode) -> Self { + let mut lexer = Lexer::new(text, mode); + lexer.jump(offset); let current = lexer.next(); Self { lexer, - eof: current.is_none(), + text, + prev_end: offset, + current_start: offset, current, - prev_end: 0, - current_start: 0, - groups: vec![], - children: vec![], - unterminated_group: false, - stray_terminator: false, + modes: vec![], + nodes: vec![], + stop_at_newline: vec![], + balanced: true, } } - /// End the parsing process and return the parsed children. fn finish(self) -> Vec { - self.children + self.nodes } - /// End the parsing process and return - /// - the parsed children and whether the last token was terminated, if all - /// groups were terminated correctly, or - /// - `None` otherwise. - fn consume(self) -> Option<(Vec, bool)> { - self.terminated().then(|| (self.children, self.lexer.terminated())) + fn prev_end(&self) -> usize { + self.prev_end } - /// Create a new marker. - fn marker(&mut self) -> Marker { - Marker(self.children.len()) + fn current(&self) -> SyntaxKind { + self.current } - /// Create a marker right before the trailing trivia. - fn trivia_start(&self) -> Marker { - let count = self - .children - .iter() - .rev() - .take_while(|node| self.is_trivia(node.kind())) - .count(); - Marker(self.children.len() - count) + fn current_start(&self) -> usize { + self.current_start } - /// Perform a subparse that wraps its result in a node with the given kind. - fn perform(&mut self, kind: SyntaxKind, f: F) -> T - where - F: FnOnce(&mut Self) -> T, - { - let prev = mem::take(&mut self.children); - let output = f(self); - let until = self.trivia_start(); - let mut children = mem::replace(&mut self.children, prev); - - if self.lexer.mode() == LexMode::Markup { - self.children.push(SyntaxNode::inner(kind, children)); - } else { - // Trailing trivia should not be wrapped into the new node. - let idx = self.children.len(); - self.children.push(SyntaxNode::default()); - self.children.extend(children.drain(until.0..)); - self.children[idx] = SyntaxNode::inner(kind, children); - } - - output + fn current_end(&self) -> usize { + self.lexer.cursor() + } + + fn current_text(&self) -> &'s str { + &self.text[self.current_start..self.current_end()] + } + + fn at(&self, kind: SyntaxKind) -> bool { + self.current == kind + } + + fn assert(&mut self, kind: SyntaxKind) { + assert_eq!(self.current, kind); + self.eat(); } - /// Whether the end of the source string or group is reached. fn eof(&self) -> bool { - self.eof + self.at(SyntaxKind::Eof) } - /// Consume the current token and also trailing trivia. - fn eat(&mut self) { - self.stray_terminator |= match self.current { - Some(SyntaxKind::RightParen) => !self.inside(Group::Paren), - Some(SyntaxKind::RightBracket) => !self.inside(Group::Bracket), - Some(SyntaxKind::RightBrace) => !self.inside(Group::Brace), - _ => false, - }; - - self.prev_end = self.lexer.cursor(); - self.bump(); - - if self.lexer.mode() != LexMode::Markup { - // Skip whitespace and comments. - while self.current.map_or(false, |kind| self.is_trivia(kind)) { - self.bump(); - } - } - - self.repeek(); + fn directly_at(&self, kind: SyntaxKind) -> bool { + self.current == kind && self.prev_end == self.current_start } - /// Consume the current token if it is the given one. fn eat_if(&mut self, kind: SyntaxKind) -> bool { let at = self.at(kind); if at { @@ -1244,437 +988,169 @@ impl<'s> Parser<'s> { at } - /// Eat tokens while the condition is true. - fn eat_while(&mut self, mut f: F) - where - F: FnMut(SyntaxKind) -> bool, - { - while self.peek().map_or(false, |t| f(t)) { - self.eat(); - } - } - - /// Consume the current token if it is the given one and produce an error if - /// not. - fn expect(&mut self, kind: SyntaxKind) -> ParseResult { - let at = self.peek() == Some(kind); - if at { - self.eat(); - Ok(()) - } else { - self.expected(kind.name()); - Err(ParseError) - } - } - - /// Consume the current token, debug-asserting that it is the given one. - #[track_caller] - fn assert(&mut self, kind: SyntaxKind) { - debug_assert_eq!(self.peek(), Some(kind)); + fn convert(&mut self, kind: SyntaxKind) { + self.current = kind; self.eat(); } - /// Whether the current token is of the given type. - fn at(&self, kind: SyntaxKind) -> bool { - self.peek() == Some(kind) + fn newline(&mut self) -> bool { + self.lexer.newline() } - /// Peek at the current token without consuming it. - fn peek(&self) -> Option { - if self.eof { - None - } else { - self.current + fn column(&self, at: usize) -> usize { + self.text[..at].chars().rev().take_while(|&c| !is_newline(c)).count() + } + + fn marker(&self) -> Marker { + Marker(self.nodes.len()) + } + + fn node(&self, m: Marker) -> Option<&SyntaxNode> { + self.nodes.get(m.0) + } + + fn post_process(&mut self, m: Marker) -> impl Iterator { + self.nodes[m.0..] + .iter_mut() + .filter(|child| !child.kind().is_error() && !child.kind().is_trivia()) + } + + fn wrap(&mut self, m: Marker, kind: SyntaxKind) { + self.unskip(); + let from = m.0.min(self.nodes.len()); + let children = self.nodes.drain(from..).collect(); + self.nodes.push(SyntaxNode::inner(kind, children)); + self.skip(); + } + + fn progress(&self, offset: usize) -> bool { + offset < self.prev_end + } + + fn enter(&mut self, mode: LexMode) { + self.modes.push(self.lexer.mode()); + self.lexer.set_mode(mode); + } + + fn exit(&mut self) { + let mode = self.modes.pop().unwrap(); + if mode != self.lexer.mode() { + self.unskip(); + self.lexer.set_mode(mode); + self.lexer.jump(self.current_start); + self.lex(); + self.skip(); } } - /// Peek at the current token, but only if it follows immediately after the - /// last one without any trivia in between. - fn peek_direct(&self) -> Option { - if self.prev_end() == self.current_start() { - self.peek() - } else { - None - } + fn stop_at_newline(&mut self, stop: bool) { + self.stop_at_newline.push(stop); } - /// The byte index at which the last non-trivia token ended. - fn prev_end(&self) -> usize { - self.prev_end + fn unstop(&mut self) { + self.unskip(); + self.stop_at_newline.pop(); + self.lexer.jump(self.prev_end); + self.lex(); + self.skip(); } - /// The byte index at which the current token starts. - fn current_start(&self) -> usize { - self.current_start + fn eat(&mut self) { + self.save(); + self.lex(); + self.skip(); } - /// The byte index at which the current token ends. - fn current_end(&self) -> usize { - self.lexer.cursor() - } - - /// The byte length of the current token. - fn current_len(&self) -> usize { - self.current_end() - self.current_start() - } - - /// The text of the current node. - fn peek_src(&self) -> &str { - self.lexer.scanner().from(self.current_start) - } - - /// Determine the column index for the given byte index. - fn column(&self, index: usize) -> usize { - self.lexer.column(index) - } - - /// Continue parsing in a group. - /// - /// When the end delimiter of the group is reached, all subsequent calls to - /// `peek()` return `None`. Parsing can only continue with a matching call - /// to `end_group`. - /// - /// This panics if the current token does not start the given group. - #[track_caller] - fn start_group(&mut self, kind: Group) { - self.groups.push(GroupEntry { kind, prev_mode: self.lexer.mode() }); - self.lexer.set_mode(match kind { - Group::Bracket | Group::Strong | Group::Emph => LexMode::Markup, - Group::Math | Group::MathRow(_, _) => LexMode::Math, - Group::Brace | Group::Paren | Group::Expr => LexMode::Code, - }); - - match kind { - Group::Brace => self.assert(SyntaxKind::LeftBrace), - Group::Bracket => self.assert(SyntaxKind::LeftBracket), - Group::Paren => self.assert(SyntaxKind::LeftParen), - Group::Strong => self.assert(SyntaxKind::Star), - Group::Emph => self.assert(SyntaxKind::Underscore), - Group::Math => self.assert(SyntaxKind::Dollar), - Group::MathRow(..) => self.assert(SyntaxKind::Atom), - Group::Expr => self.repeek(), - } - } - - /// End the parsing of a group. - /// - /// This panics if no group was started. - #[track_caller] - fn end_group(&mut self) { - let group_mode = self.lexer.mode(); - let group = self.groups.pop().expect("no started group"); - self.lexer.set_mode(group.prev_mode); - - let mut rescan = self.lexer.mode() != group_mode; - - // Eat the end delimiter if there is one. - if let Some((end, required)) = match group.kind { - Group::Brace => Some((SyntaxKind::RightBrace, true)), - Group::Bracket => Some((SyntaxKind::RightBracket, true)), - Group::Paren => Some((SyntaxKind::RightParen, true)), - Group::Strong => Some((SyntaxKind::Star, true)), - Group::Emph => Some((SyntaxKind::Underscore, true)), - Group::Math => Some((SyntaxKind::Dollar, true)), - Group::MathRow(..) => Some((SyntaxKind::Atom, true)), - Group::Expr => Some((SyntaxKind::Semicolon, false)), - } { - if self.current.as_ref() == Some(&end) { - // If another group closes after a group with the missing - // terminator, its scope of influence ends here and no longer - // taints the rest of the reparse. - self.unterminated_group = false; - - // Bump the delimeter and return. No need to rescan in this - // case. Also, we know that the delimiter is not stray even - // though we already removed the group. - let s = self.stray_terminator; - self.eat(); - self.stray_terminator = s; - rescan = false; - } else if required { - self.expected(end.name()); - self.unterminated_group = true; + fn skip(&mut self) { + if self.lexer.mode() != LexMode::Markup { + while self.current.is_trivia() { + self.save(); + self.lex(); } } + } - // Rescan the peeked token if the mode changed. - if rescan { - let mut target = self.prev_end(); - if group_mode != LexMode::Markup { - let start = self.trivia_start().0; - target = self.current_start - - self.children[start..].iter().map(SyntaxNode::len).sum::(); - self.children.truncate(start); + fn unskip(&mut self) { + if self.lexer.mode() != LexMode::Markup && self.prev_end != self.current_start { + while self.nodes.last().map_or(false, |last| last.kind().is_trivia()) { + self.nodes.pop(); } - self.lexer.jump(target); - self.prev_end = self.lexer.cursor(); - self.current_start = self.lexer.cursor(); - self.current = self.lexer.next(); + self.lexer.jump(self.prev_end); + self.lex(); } - - self.repeek(); } - /// Checks if all groups were correctly terminated. - fn terminated(&self) -> bool { - self.groups.is_empty() && !self.unterminated_group && !self.stray_terminator - } - - /// Low-level bump that consumes exactly one token without special trivia - /// handling. - fn bump(&mut self) { - if let Some((message, pos)) = self.lexer.last_error() { - let len = self.current_len(); - self.children.push(SyntaxNode::error(message, pos, len)) + fn save(&mut self) { + if self.at(SyntaxKind::Error) { + let (message, pos) = self.lexer.take_error().unwrap(); + let len = self.current_end() - self.current_start; + self.nodes.push(SyntaxNode::error(message, pos, len)); } else { - let kind = self.current.unwrap(); - let text = self.peek_src(); - self.children.push(SyntaxNode::leaf(kind, text)); + let text = self.current_text(); + self.nodes.push(SyntaxNode::leaf(self.current, text)); } + + if self.lexer.mode() == LexMode::Markup || !self.current.is_trivia() { + self.prev_end = self.current_end(); + } + } + + fn lex(&mut self) { self.current_start = self.lexer.cursor(); self.current = self.lexer.next(); - } - - /// Take another look at the current token to recheck whether it ends a - /// group. - fn repeek(&mut self) { - self.eof = match &self.current { - Some(SyntaxKind::RightBrace) => self.inside(Group::Brace), - Some(SyntaxKind::RightBracket) => self.inside(Group::Bracket), - Some(SyntaxKind::RightParen) => self.inside(Group::Paren), - Some(SyntaxKind::Star) => self.inside(Group::Strong), - Some(SyntaxKind::Underscore) => self.inside(Group::Emph), - Some(SyntaxKind::Dollar) => self - .groups - .iter() - .rev() - .skip_while(|group| matches!(group.kind, Group::MathRow(..))) - .next() - .map_or(false, |group| group.kind == Group::Math), - Some(SyntaxKind::Semicolon) => self.inside(Group::Expr), - Some(SyntaxKind::Atom) => match self.peek_src() { - ")" => self.inside(Group::MathRow('(', ')')), - "}" => self.inside(Group::MathRow('{', '}')), - "]" => self.inside(Group::MathRow('[', ']')), - _ => false, - }, - Some(SyntaxKind::Space { newlines }) => self.space_ends_group(*newlines), - Some(_) => false, - None => true, - }; - } - - /// Returns whether the given type can be skipped over. - fn is_trivia(&self, token: SyntaxKind) -> bool { - match token { - SyntaxKind::Space { newlines } => !self.space_ends_group(newlines), - SyntaxKind::LineComment => true, - SyntaxKind::BlockComment => true, - _ => false, + if self.lexer.mode() == LexMode::Code + && self.lexer.newline() + && self.stop_at_newline.last().copied().unwrap_or(false) + && !matches!(self.lexer.clone().next(), SyntaxKind::Else | SyntaxKind::Dot) + { + self.current = SyntaxKind::Eof; } } - /// Whether a space with the given number of newlines ends the current group. - fn space_ends_group(&self, n: usize) -> bool { - if n == 0 { - return false; - } - - match self.groups.last().map(|group| group.kind) { - Some(Group::Strong | Group::Emph) => n >= 2, - Some(Group::Expr) if n >= 1 => { - // Allow else and method call to continue on next line. - self.groups.iter().nth_back(1).map(|group| group.kind) - != Some(Group::Brace) - || !matches!( - self.lexer.clone().next(), - Some(SyntaxKind::Else | SyntaxKind::Dot) - ) - } - _ => false, - } - } - - /// Whether we are inside the given group (can be nested). - fn inside(&self, kind: Group) -> bool { - self.groups - .iter() - .rev() - .take_while(|g| !kind.is_weak() || g.kind.is_weak()) - .any(|g| g.kind == kind) - } -} - -/// Error handling. -impl Parser<'_> { - /// Eat the current token and add an error that it is unexpected. - fn unexpected(&mut self) { - if let Some(found) = self.peek() { - let marker = self.marker(); - let msg = format_eco!("unexpected {}", found.name()); + fn expect(&mut self, kind: SyntaxKind) -> bool { + let at = self.at(kind); + if at { self.eat(); - marker.to_error(self, msg); + } else { + self.balanced &= !kind.is_grouping(); + self.expected(kind.name()); } + at } - /// Add an error that the `thing` was expected at the end of the last - /// non-trivia token. fn expected(&mut self, thing: &str) { - self.expected_at(self.trivia_start(), thing); + self.unskip(); + if self + .nodes + .last() + .map_or(true, |child| child.kind() != SyntaxKind::Error) + { + let message = format_eco!("expected {}", thing); + self.nodes.push(SyntaxNode::error(message, ErrorPos::Full, 0)); + } + self.skip(); } - /// Insert an error message that `what` was expected at the marker position. - fn expected_at(&mut self, marker: Marker, what: &str) { - let msg = format_eco!("expected {}", what); - self.children - .insert(marker.0, SyntaxNode::error(msg, ErrorPos::Full, 0)); - } + fn unexpected(&mut self) { + self.unskip(); + while self + .nodes + .last() + .map_or(false, |child| child.kind() == SyntaxKind::Error && child.len() == 0) + { + self.nodes.pop(); + } + self.skip(); - /// Eat the current token and add an error that it is not the expected - /// `thing`. - fn expected_found(&mut self, thing: &str) { - match self.peek() { - Some(found) => { - let marker = self.marker(); - let msg = format_eco!("expected {}, found {}", thing, found.name()); - self.eat(); - marker.to_error(self, msg); - } - None => self.expected(thing), + let kind = self.current; + let offset = self.nodes.len(); + self.eat(); + self.balanced &= !kind.is_grouping(); + + if !kind.is_error() { + self.nodes[offset] + .convert_to_error(format_eco!("unexpected {}", kind.name())); } } } - -/// Marks a location in a parser's child list. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct Marker(usize); - -impl Marker { - /// Peek at the child directly before the marker. - fn before<'a>(self, p: &'a Parser) -> Option<&'a SyntaxNode> { - p.children.get(self.0.checked_sub(1)?) - } - - /// Peek at the child directly after the marker. - fn after<'a>(self, p: &'a Parser) -> Option<&'a SyntaxNode> { - p.children.get(self.0) - } - - /// Convert the child directly after marker. - fn convert(self, p: &mut Parser, kind: SyntaxKind) { - if let Some(child) = p.children.get_mut(self.0) { - child.convert_to(kind); - } - } - - /// Convert the child directly after marker. - fn to_error(self, p: &mut Parser, message: impl Into) { - if let Some(child) = p.children.get_mut(self.0) { - child.convert_to_error(message); - } - } - - /// Perform a subparse that wraps all children after the marker in a node - /// with the given kind. - fn perform(self, p: &mut Parser, kind: SyntaxKind, f: F) -> T - where - F: FnOnce(&mut Parser) -> T, - { - let success = f(p); - self.end(p, kind); - success - } - - /// Wrap all children after the marker (excluding trailing trivia) in a node - /// with the given `kind`. - fn end(self, p: &mut Parser, kind: SyntaxKind) { - let until = p.trivia_start().0.max(self.0); - let children = p.children.drain(self.0..until).collect(); - p.children.insert(self.0, SyntaxNode::inner(kind, children)); - } - - /// Wrap all children that do not fulfill the predicate in error nodes. - fn filter_children(self, p: &mut Parser, mut f: F) - where - F: FnMut(&SyntaxNode) -> Result<(), &'static str>, - { - for child in &mut p.children[self.0..] { - // Don't expose errors. - if child.kind().is_error() { - continue; - } - - // Don't expose trivia in code. - if p.lexer.mode() != LexMode::Markup && child.kind().is_trivia() { - continue; - } - - if let Err(msg) = f(child) { - let mut msg = EcoString::from(msg); - if msg.starts_with("expected") { - msg.push_str(", found "); - msg.push_str(child.kind().name()); - } - let len = child.len(); - *child = SyntaxNode::error(msg, ErrorPos::Full, len); - } - } - } -} - -/// A logical group of tokens, e.g. `[...]`. -#[derive(Debug)] -struct GroupEntry { - /// The kind of group this is. This decides which token(s) will end the - /// group. For example, a [`Group::Paren`] will be ended by - /// [`Token::RightParen`]. - kind: Group, - /// The mode the parser was in _before_ the group started (to which we go - /// back once the group ends). - prev_mode: LexMode, -} - -/// A group, confined by optional start and end delimiters. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -enum Group { - /// A curly-braced group: `{...}`. - Brace, - /// A bracketed group: `[...]`. - Bracket, - /// A parenthesized group: `(...)`. - Paren, - /// A group surrounded with stars: `*...*`. - Strong, - /// A group surrounded with underscore: `_..._`. - Emph, - /// A group surrounded by dollar signs: `$...$`. - Math, - /// A group surrounded by math delimiters. - MathRow(char, char), - /// A group ended by a semicolon or a line break: `;`, `\n`. - Expr, -} - -impl Group { - /// Whether the group can only force other weak groups to end. - fn is_weak(self) -> bool { - matches!(self, Group::Strong | Group::Emph) - } -} - -/// Allows parser methods to use the try operator. Never returned top-level -/// because the parser recovers from all errors. -type ParseResult = Result; - -/// The error type for parsing. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct ParseError; - -impl Display for ParseError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad("failed to parse") - } -} - -impl std::error::Error for ParseError {} diff --git a/src/syntax/reparse.rs b/src/syntax/reparse.rs deleted file mode 100644 index e72192fff..000000000 --- a/src/syntax/reparse.rs +++ /dev/null @@ -1,525 +0,0 @@ -use std::ops::Range; - -use super::{ - is_newline, parse, reparse_code_block, reparse_content_block, - reparse_markup_elements, Span, SyntaxKind, SyntaxNode, -}; - -/// Refresh the given syntax node with as little parsing as possible. -/// -/// Takes the new source, the range in the old source that was replaced and the -/// length of the replacement. -/// -/// Returns the range in the new source that was ultimately reparsed. -pub fn reparse( - root: &mut SyntaxNode, - text: &str, - replaced: Range, - replacement_len: usize, -) -> Range { - let change = Change { text, replaced, replacement_len }; - if let Some(range) = try_reparse(&change, root, 0, true, true) { - return range; - } - - let id = root.span().source(); - *root = parse(text); - root.numberize(id, Span::FULL).unwrap(); - 0..text.len() -} - -/// Try to reparse inside the given node. -fn try_reparse( - change: &Change, - node: &mut SyntaxNode, - mut offset: usize, - outermost: bool, - safe_to_replace: bool, -) -> Option> { - let is_markup = matches!(node.kind(), SyntaxKind::Markup { .. }); - let original_count = node.children().len(); - let original_offset = offset; - - let mut search = SearchState::default(); - let mut ahead: Option = None; - - // Whether the first node that should be replaced is at start. - let mut at_start = true; - - // Whether the last searched child is the outermost child. - let mut child_outermost = false; - - // Find the the first child in the range of children to reparse. - for (i, child) in node.children().enumerate() { - let pos = NodePos { idx: i, offset }; - let child_span = offset..offset + child.len(); - child_outermost = outermost && i + 1 == original_count; - - match search { - SearchState::NoneFound => { - // The edit is contained within the span of the current element. - if child_span.contains(&change.replaced.start) - && child_span.end >= change.replaced.end - { - // In Markup mode, we want to consider a non-whitespace - // neighbor if the edit is on the node boundary. - search = if is_markup && child_span.end == change.replaced.end { - SearchState::RequireNonTrivia(pos) - } else { - SearchState::Contained(pos) - }; - } else if child_span.contains(&change.replaced.start) { - search = SearchState::Inside(pos); - } else if child_span.end == change.replaced.start - && change.replaced.start == change.replaced.end - && child_outermost - { - search = SearchState::SpanFound(pos, pos); - } else { - // Update compulsary state of `ahead_nontrivia`. - if let Some(ahead_nontrivia) = ahead.as_mut() { - if let SyntaxKind::Space { newlines: (1..) } = child.kind() { - ahead_nontrivia.newline(); - } - } - - // We look only for non spaces, non-semicolon and also - // reject text that points to the special case for URL - // evasion and line comments. - if !child.kind().is_space() - && child.kind() != SyntaxKind::Semicolon - && (child.kind() != SyntaxKind::Text || child.text() != "/") - && (ahead.is_none() || change.replaced.start > child_span.end) - && !ahead.map_or(false, Ahead::is_compulsory) - { - ahead = Some(Ahead::new(pos, at_start, is_bounded(child.kind()))); - } - - at_start = next_at_start(child.kind(), at_start); - } - } - SearchState::Inside(start) => { - if child_span.end == change.replaced.end { - search = SearchState::RequireNonTrivia(start); - } else if child_span.end > change.replaced.end { - search = SearchState::SpanFound(start, pos); - } - } - SearchState::RequireNonTrivia(start) => { - if !child.kind().is_trivia() { - search = SearchState::SpanFound(start, pos); - } - } - _ => unreachable!(), - } - - offset += child.len(); - - if search.done().is_some() { - break; - } - } - - // If we were looking for a non-whitespace element and hit the end of - // the file here, we instead use EOF as the end of the span. - if let SearchState::RequireNonTrivia(start) = search { - search = SearchState::SpanFound( - start, - NodePos { - idx: node.children().len() - 1, - offset: offset - node.children().last().unwrap().len(), - }, - ) - } - - if let SearchState::Contained(pos) = search { - // Do not allow replacement of elements inside of constructs whose - // opening and closing brackets look the same. - let safe_inside = is_bounded(node.kind()); - let child = &mut node.children_mut()[pos.idx]; - let prev_len = child.len(); - let prev_descendants = child.descendants(); - - if !child.is_leaf() { - if let Some(range) = - try_reparse(change, child, pos.offset, child_outermost, safe_inside) - { - let new_len = child.len(); - let new_descendants = child.descendants(); - node.update_parent(prev_len, new_len, prev_descendants, new_descendants); - return Some(range); - } - } - - let superseded_span = pos.offset..pos.offset + prev_len; - let func: Option = match child.kind() { - SyntaxKind::CodeBlock => Some(ReparseMode::Code), - SyntaxKind::ContentBlock => Some(ReparseMode::Content), - _ => None, - }; - - // Return if the element was reparsable on its own, otherwise try to - // treat it as a markup element. - if let Some(func) = func { - if let Some(result) = replace( - change, - node, - func, - pos.idx..pos.idx + 1, - superseded_span, - outermost, - ) { - return Some(result); - } - } - } - - // Make sure this is a markup node and that we may replace. If so, save - // the current indent. - let min_indent = match node.kind() { - SyntaxKind::Markup { min_indent } if safe_to_replace => min_indent, - _ => return None, - }; - - let (mut start, end) = search.done()?; - if let Some(ahead) = ahead { - if start.offset == change.replaced.start || ahead.is_compulsory() { - start = ahead.pos; - at_start = ahead.at_start; - } - } else { - start = NodePos { idx: 0, offset: original_offset }; - } - - let superseded_span = - start.offset..end.offset + node.children().as_slice()[end.idx].len(); - - replace( - change, - node, - ReparseMode::MarkupElements { at_start, min_indent }, - start.idx..end.idx + 1, - superseded_span, - outermost, - ) -} - -/// Reparse the superseded nodes and replace them. -fn replace( - change: &Change, - node: &mut SyntaxNode, - mode: ReparseMode, - superseded_idx: Range, - superseded_span: Range, - outermost: bool, -) -> Option> { - let superseded_start = superseded_idx.start; - - let differential: isize = - change.replacement_len as isize - change.replaced.len() as isize; - let newborn_end = (superseded_span.end as isize + differential) as usize; - let newborn_span = superseded_span.start..newborn_end; - - let mut prefix = ""; - for (i, c) in change.text[..newborn_span.start].char_indices().rev() { - if is_newline(c) { - break; - } - prefix = &change.text[i..newborn_span.start]; - } - - let (newborns, terminated, amount) = match mode { - ReparseMode::Code => reparse_code_block( - prefix, - &change.text[newborn_span.start..], - newborn_span.len(), - ), - ReparseMode::Content => reparse_content_block( - prefix, - &change.text[newborn_span.start..], - newborn_span.len(), - ), - ReparseMode::MarkupElements { at_start, min_indent } => reparse_markup_elements( - prefix, - &change.text[newborn_span.start..], - newborn_span.len(), - differential, - &node.children().as_slice()[superseded_start..], - at_start, - min_indent, - ), - }?; - - // Do not accept unclosed nodes if the old node wasn't at the right edge - // of the tree. - if !outermost && !terminated { - return None; - } - - node.replace_children(superseded_start..superseded_start + amount, newborns) - .ok()?; - - Some(newborn_span) -} - -/// A description of a change. -struct Change<'a> { - /// The new source code, with the change applied. - text: &'a str, - /// Which range in the old source file was changed. - replaced: Range, - /// How many characters replaced the text in `replaced`. - replacement_len: usize, -} - -/// Encodes the state machine of the search for the nodes are pending for -/// replacement. -#[derive(Clone, Copy, Debug, PartialEq)] -enum SearchState { - /// Neither an end nor a start have been found as of now. - /// The latest non-trivia child is continually saved. - NoneFound, - /// The search has concluded by finding a node that fully contains the - /// modifications. - Contained(NodePos), - /// The search has found the start of the modified nodes. - Inside(NodePos), - /// The search has found the end of the modified nodes but the change - /// touched its boundries so another non-trivia node is needed. - RequireNonTrivia(NodePos), - /// The search has concluded by finding a start and an end index for nodes - /// with a pending reparse. - SpanFound(NodePos, NodePos), -} - -impl Default for SearchState { - fn default() -> Self { - Self::NoneFound - } -} - -impl SearchState { - fn done(self) -> Option<(NodePos, NodePos)> { - match self { - Self::NoneFound => None, - Self::Contained(s) => Some((s, s)), - Self::Inside(_) => None, - Self::RequireNonTrivia(_) => None, - Self::SpanFound(s, e) => Some((s, e)), - } - } -} - -/// The position of a syntax node. -#[derive(Clone, Copy, Debug, PartialEq)] -struct NodePos { - /// The index in the parent node. - idx: usize, - /// The byte offset in the string. - offset: usize, -} - -/// An ahead node with an index and whether it is `at_start`. -#[derive(Clone, Copy, Debug, PartialEq)] -struct Ahead { - /// The position of the node. - pos: NodePos, - /// The `at_start` before this node. - at_start: bool, - /// The kind of ahead node. - kind: AheadKind, -} - -/// The kind of ahead node. -#[derive(Clone, Copy, Debug, PartialEq)] -enum AheadKind { - /// A normal non-trivia child has been found. - Normal, - /// An unbounded child has been found. The boolean indicates whether it was - /// on the current line, in which case adding it to the reparsing range is - /// compulsory. - Unbounded(bool), -} - -impl Ahead { - fn new(pos: NodePos, at_start: bool, bounded: bool) -> Self { - Self { - pos, - at_start, - kind: if bounded { AheadKind::Normal } else { AheadKind::Unbounded(true) }, - } - } - - fn newline(&mut self) { - if let AheadKind::Unbounded(current_line) = &mut self.kind { - *current_line = false; - } - } - - fn is_compulsory(self) -> bool { - matches!(self.kind, AheadKind::Unbounded(true)) - } -} - -/// Which reparse function to choose for a span of elements. -#[derive(Clone, Copy, Debug, PartialEq)] -enum ReparseMode { - /// Reparse a code block, including its braces. - Code, - /// Reparse a content block, including its square brackets. - Content, - /// Reparse elements of the markup. Also specified the initial `at_start` - /// state for the reparse and the minimum indent of the reparsed nodes. - MarkupElements { at_start: bool, min_indent: usize }, -} - -/// Whether changes _inside_ this node are safely encapsulated, so that only -/// this node must be reparsed. -fn is_bounded(kind: SyntaxKind) -> bool { - matches!( - kind, - SyntaxKind::CodeBlock - | SyntaxKind::ContentBlock - | SyntaxKind::Linebreak - | SyntaxKind::SmartQuote - | SyntaxKind::BlockComment - | SyntaxKind::Space { .. } - | SyntaxKind::Escape - | SyntaxKind::Shorthand - ) -} - -/// Whether `at_start` would still be true after this node given the -/// previous value of the property. -fn next_at_start(kind: SyntaxKind, prev: bool) -> bool { - match kind { - SyntaxKind::Space { newlines: (1..) } => true, - SyntaxKind::Space { .. } | SyntaxKind::LineComment | SyntaxKind::BlockComment => { - prev - } - _ => false, - } -} - -#[cfg(test)] -#[rustfmt::skip] -mod tests { - use std::fmt::Debug; - - use super::*; - use super::super::{parse, Source}; - - #[track_caller] - fn check(text: &str, found: T, expected: T) - where - T: Debug + PartialEq, - { - if found != expected { - println!("source: {text:?}"); - println!("expected: {expected:#?}"); - println!("found: {found:#?}"); - panic!("test failed"); - } - } - - #[track_caller] - fn test(prev: &str, range: Range, with: &str, goal: Range) { - let mut source = Source::detached(prev); - let range = source.edit(range, with); - check(source.text(), source.root(), &parse(source.text())); - assert_eq!(range, goal); - } - - #[test] - fn test_parse_incremental_simple_replacements() { - test("hello world", 7 .. 12, "walkers", 0 .. 14); - test("some content", 0..12, "", 0..0); - test("", 0..0, "do it", 0..5); - test("a d e", 1 .. 3, " b c d", 0 .. 9); - test("*~ *", 2..2, "*", 0..5); - test("_1_\n2a\n3", 5..5, "4", 4..7); - test("_1_\n2a\n3~", 8..8, "4", 4..10); - test("_1_ 2 3a\n4", 7..7, "5", 0..9); - test("* {1+2} *", 5..6, "3", 2..7); - test("a #f() e", 1 .. 6, " b c d", 0 .. 9); - test("a\nb\nc\nd\ne\n", 5 .. 5, "c", 2 .. 7); - test("a\n\nb\n\nc\n\nd\n\ne\n", 7 .. 7, "c", 3 .. 10); - test("a\nb\nc *hel a b lo* d\nd\ne", 13..13, "c ", 4..20); - test("~~ {a} ~~", 4 .. 5, "b", 3 .. 6); - test("{(0, 1, 2)}", 5 .. 6, "11pt", 0..14); - test("\n= A heading", 4 .. 4, "n evocative", 0 .. 23); - test("for~your~thing", 9 .. 9, "a", 0 .. 15); - test("a your thing a", 6 .. 7, "a", 0 .. 14); - test("{call(); abc}", 7 .. 7, "[]", 0 .. 15); - test("#call() abc", 7 .. 7, "[]", 0 .. 10); - test("hi[\n- item\n- item 2\n - item 3]", 11 .. 11, " ", 2 .. 35); - test("hi\n- item\nno item\n - item 3", 10 .. 10, "- ", 3..19); - test("#grid(columns: (auto, 1fr, 40%), [*plonk*], rect(width: 100%, height: 1pt, fill: conifer), [thing])", 16 .. 20, "none", 0..99); - test("#grid(columns: (auto, 1fr, 40%), [*plonk*], rect(width: 100%, height: 1pt, fill: conifer), [thing])", 33 .. 42, "[_gronk_]", 33..42); - test("#grid(columns: (auto, 1fr, 40%), [*plonk*], rect(width: 100%, height: 1pt, fill: conifer), [thing])", 34 .. 41, "_bar_", 33 .. 40); - test("{let i=1; for x in range(5) {i}}", 6 .. 6, " ", 0 .. 33); - test("{let i=1; for x in range(5) {i}}", 13 .. 14, " ", 0 .. 33); - test("hello~~{x}", 7 .. 10, "#f()", 0 .. 11); - test("this~is -- in my opinion -- spectacular", 8 .. 10, "---", 0 .. 25); - test("understanding `code` is complicated", 15 .. 15, "C ", 0 .. 22); - test("{ let x = g() }", 10 .. 12, "f(54", 0 .. 17); - test(r#"a ```typst hello``` b"#, 16 .. 17, "", 0 .. 18); - test(r#"a ```typst hello```"#, 16 .. 17, "", 0 .. 18); - test("#for", 4 .. 4, "//", 0 .. 6); - test("#show f: a => b..", 16..16, "c", 0..18); - test("a\n#let \nb", 7 .. 7, "i", 2 .. 9); - test("a\n#for i \nb", 9 .. 9, "in", 2 .. 12); - test("a~https://fun/html", 13..14, "n", 0..18); - } - - #[test] - fn test_parse_incremental_whitespace_invariants() { - test("hello \\ world", 7 .. 8, "a ", 0 .. 14); - test("hello \\ world", 7 .. 8, " a", 0 .. 14); - test("x = y", 1 .. 1, " + y", 0 .. 6); - test("x = y", 1 .. 1, " + y\n", 0 .. 7); - test("abc\n= a heading\njoke", 3 .. 4, "\nmore\n\n", 0 .. 21); - test("abc\n= a heading\njoke", 3 .. 4, "\nnot ", 0 .. 19); - test("#let x = (1, 2 + ;~ Five\r\n\r", 20 .. 23, "2.", 0 .. 23); - test("hey #myfriend", 4 .. 4, "\\", 0 .. 14); - test("hey #myfriend", 4 .. 4, "\\", 0 .. 6); - test("= foo\nbar\n - a\n - b", 6 .. 9, "", 0 .. 11); - test("= foo\n bar\n baz", 6 .. 8, "", 0 .. 9); - test(" // hi", 1 .. 1, " ", 0 .. 7); - test("- \nA", 2..3, "", 0..3); - } - - #[test] - fn test_parse_incremental_type_invariants() { - test("a #for x in array {x}", 18 .. 21, "[#x]", 0 .. 22); - test("a #let x = 1 {5}", 3 .. 6, "if", 0 .. 11); - test("a {let x = 1 {5}} b", 3 .. 6, "if", 2 .. 16); - test("#let x = 1 {5}", 4 .. 4, " if", 0 .. 13); - test("{let x = 1 {5}}", 4 .. 4, " if", 0 .. 18); - test("a // b c #f()", 3 .. 4, "", 0 .. 12); - test("{\nf()\n//g(a)\n}", 6 .. 8, "", 0 .. 12); - test("a{\nf()\n//g(a)\n}b", 7 .. 9, "", 1 .. 13); - test("a #while x {\n g(x) \n} b", 11 .. 11, "//", 0 .. 26); - test("{(1, 2)}", 1 .. 1, "while ", 0 .. 14); - test("a b c", 1 .. 1, "{[}", 0 .. 8); - } - - #[test] - fn test_parse_incremental_wrongly_or_unclosed_things() { - test(r#"{"hi"}"#, 4 .. 5, "c", 0 .. 6); - test(r"this \u{abcd}", 8 .. 9, "", 0 .. 12); - test(r"this \u{abcd} that", 12 .. 13, "", 0 .. 17); - test(r"{{let x = z}; a = 1} b", 6 .. 6, "//", 0 .. 24); - test("a b c", 1 .. 1, " /* letters */", 0 .. 19); - test("a b c", 1 .. 1, " /* letters", 0 .. 16); - test("{if i==1 {a} else [b]; b()}", 12 .. 12, " /* letters */", 0 .. 41); - test("{if i==1 {a} else [b]; b()}", 12 .. 12, " /* letters", 0 .. 38); - test("~~~~", 2 .. 2, "[]", 0 .. 5); - test("a[]b", 2 .. 2, "{", 1 .. 4); - test("[hello]", 2 .. 3, "]", 0 .. 7); - test("{a}", 1 .. 2, "b", 0 .. 3); - test("{ a; b; c }", 5 .. 6, "[}]", 0 .. 13); - test("#a()\n~", 3..4, "{}", 0..7); - test("[]\n~", 1..2, "#if i==0 {true}", 0..18); - } -} diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs new file mode 100644 index 000000000..9404055d8 --- /dev/null +++ b/src/syntax/reparser.rs @@ -0,0 +1,262 @@ +use std::ops::Range; + +use super::{ + is_newline, parse, reparse_block, reparse_markup, Span, SyntaxKind, SyntaxNode, +}; + +/// Refresh the given syntax node with as little parsing as possible. +/// +/// Takes the new text, the range in the old text that was replaced and the +/// length of the replacement and returns the range in the new text that was +/// ultimately reparsed. +/// +/// The high-level API for this function is +/// [`Source::edit`](super::Source::edit). +pub fn reparse( + root: &mut SyntaxNode, + text: &str, + replaced: Range, + replacement_len: usize, +) -> Range { + try_reparse(text, replaced, replacement_len, None, root, 0).unwrap_or_else(|| { + let id = root.span().source(); + *root = parse(text); + root.numberize(id, Span::FULL).unwrap(); + 0..text.len() + }) +} + +/// Try to reparse inside the given node. +fn try_reparse( + text: &str, + replaced: Range, + replacement_len: usize, + parent_kind: Option, + node: &mut SyntaxNode, + offset: usize, +) -> Option> { + // The range of children which overlap with the edit. + let mut overlap = usize::MAX..0; + let mut cursor = offset; + let node_kind = node.kind(); + + for (i, child) in node.children_mut().iter_mut().enumerate() { + let prev_range = cursor..cursor + child.len(); + let prev_len = child.len(); + let prev_desc = child.descendants(); + + // Does the child surround the edit? + // If so, try to reparse within it or itself. + if !child.is_leaf() && includes(&prev_range, &replaced) { + let new_len = prev_len + replacement_len - replaced.len(); + let new_range = cursor..cursor + new_len; + + // Try to reparse within the child. + if let Some(range) = try_reparse( + text, + replaced.clone(), + replacement_len, + Some(node_kind), + child, + cursor, + ) { + assert_eq!(child.len(), new_len); + let new_desc = child.descendants(); + node.update_parent(prev_len, new_len, prev_desc, new_desc); + return Some(range); + } + + // If the child is a block, try to reparse the block. + if child.kind().is_block() { + if let Some(newborn) = reparse_block(text, new_range.clone()) { + return node + .replace_children(i..i + 1, vec![newborn]) + .is_ok() + .then(|| new_range); + } + } + } + + // Does the child overlap with the edit? + if overlaps(&prev_range, &replaced) { + overlap.start = overlap.start.min(i); + overlap.end = i + 1; + } + + // Is the child beyond the edit? + if replaced.end < cursor { + break; + } + + cursor += child.len(); + } + + // Try to reparse a range of markup expressions within markup. This is only + // possible if the markup is top-level or contained in a block, not if it is + // contained in things like headings or lists because too much can go wrong + // with indent and line breaks. + if node.kind() == SyntaxKind::Markup + && (parent_kind == None || parent_kind == Some(SyntaxKind::ContentBlock)) + && !overlap.is_empty() + { + // Add one node of slack in both directions. + let children = node.children_mut(); + let mut start = overlap.start.saturating_sub(1); + let mut end = (overlap.end + 1).min(children.len()); + + // Expand to the left. + while start > 0 && expand(&children[start]) { + start -= 1; + } + + // Expand to the right. + while end < children.len() && expand(&children[end]) { + end += 1; + } + + // Synthesize what `at_start` would be at the start of the reparse. + let mut prefix_len = 0; + let mut at_start = true; + for child in &children[..start] { + prefix_len += child.len(); + next_at_start(child, &mut at_start); + } + + // 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; + for child in &children[start..end] { + prev_len += child.len(); + next_at_start(child, &mut prev_at_start_after); + } + + let shifted = offset + prefix_len; + let new_len = prev_len + replacement_len - replaced.len(); + let new_range = shifted..shifted + new_len; + let stop_kind = match parent_kind { + Some(_) => SyntaxKind::RightBracket, + None => SyntaxKind::Eof, + }; + + if let Some(newborns) = + reparse_markup(text, new_range.clone(), &mut at_start, |kind| { + kind == stop_kind + }) + { + if at_start == prev_at_start_after { + return node + .replace_children(start..end, newborns) + .is_ok() + .then(|| new_range); + } + } + } + + None +} + +/// Whether the inner range is fully contained in the outer one (no touching). +fn includes(outer: &Range, inner: &Range) -> bool { + outer.start < inner.start && outer.end > inner.end +} + +/// Whether the first and second range overlap or touch. +fn overlaps(first: &Range, second: &Range) -> bool { + (first.start <= second.start && second.start <= first.end) + || (second.start <= first.start && first.start <= second.end) +} + +/// Whether the selection should be expanded beyond a node of this kind. +fn expand(node: &SyntaxNode) -> bool { + let kind = node.kind(); + kind.is_trivia() + || kind.is_error() + || kind == SyntaxKind::Semicolon + || node.text() == "/" + || node.text() == ":" +} + +/// Whether `at_start` would still be true after this node given the +/// previous value of the property. +fn next_at_start(node: &SyntaxNode, at_start: &mut bool) { + if node.kind().is_trivia() { + if node.text().chars().any(is_newline) { + *at_start = true; + } + } else { + *at_start = false; + } +} + +#[cfg(test)] +mod tests { + use std::ops::Range; + + use super::super::{parse, Source}; + + #[track_caller] + fn test(prev: &str, range: Range, with: &str, incremental: bool) { + let mut source = Source::detached(prev); + let prev = source.root().clone(); + let range = source.edit(range, with); + let found = source.root(); + let expected = parse(source.text()); + if found != &expected { + eprintln!("source: {:?}", source.text()); + eprintln!("previous: {prev:#?}"); + eprintln!("expected: {expected:#?}"); + eprintln!("found: {found:#?}"); + panic!("test failed"); + } + if incremental { + assert_ne!(source.len_bytes(), range.len()); + } else { + assert_eq!(source.len_bytes(), range.len()); + } + } + + #[test] + fn test_reparse_markup() { + test("abc~def~ghi", 5..6, "+", true); + test("~~~~~~~", 3..4, "A", true); + test("abc~~", 1..2, "", true); + test("#var;hello", 9..10, "a", false); + test("https:/world", 7..7, "/", false); + test("hello world", 7..12, "walkers", false); + test("some content", 0..12, "", false); + test("", 0..0, "do it", false); + 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("\n= A heading", 4..4, "n evocative", false); + test("#call() abc~d", 7..7, "[]", true); + test("a your thing a", 6..7, "a", false); + test("#grid(columns: (auto, 1fr, 40%))", 16..20, "4pt", false); + test("abc\n= a heading\njoke", 3..4, "\nmore\n\n", true); + 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", 6..6, "//", 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("a #while x {\n g(x) \n} b", 12..12, "//", true); + } +} diff --git a/src/syntax/source.rs b/src/syntax/source.rs index 41805a604..472e8c6ca 100644 --- a/src/syntax/source.rs +++ b/src/syntax/source.rs @@ -9,8 +9,7 @@ use comemo::Prehashed; use unscanny::Scanner; use super::ast::Markup; -use super::reparse::reparse; -use super::{is_newline, parse, Span, SyntaxNode}; +use super::{is_newline, parse, reparse, Span, SyntaxNode}; use crate::diag::SourceResult; use crate::util::{PathExt, StrExt}; diff --git a/tests/ref/basics/heading.png b/tests/ref/basics/heading.png index 96ffcb80a84d543d4312a475472bdf9b879134a0..9cb4d0985f632213cd969c3127fd5a545bdb90bc 100644 GIT binary patch literal 24759 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 BB3l3e literal 24767 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 diff --git a/tests/ref/basics/list.png b/tests/ref/basics/list.png index 5d0f03c00c264128911ec363e67d0c2f114a3df3..b6b8ed3e689241e6c9eaccaccfac9eefdd109a9f 100644 GIT binary patch delta 3529 zcmV;)4L0(yp#jXE0gx9CxCj6M007ie>3;wKPZO~vEkS=(z5j9i`}ocbBQT~Y4Vp`s zOUPAGG?%o{#F8v>xwrgP=1)^o%O%_rCvT;ZSCmk5X*3rKm#gV@E4MW5W_V4^4K;=6 zilU;R?6S=3{xJ&-VBW{IbVk3Qzs~vcox|{i^F8N$hx0zKy6z!zvfOBIdZkDI0g$Qv zRI~dQ2qk|5G_$FUa6rZYSE_{q?W&yz618u`ryBqs8sdTo@|lQ;h|yi;Q;w$PhT5xu zE)Bg39S8$`qoOM#ESAk9A|fIpA{-ABYSSWW1qBWLqW7)n64oRZ+BNGHyJ33GTIm#p zX-BT#{hHkFdI&p=Mx)Va40@;RS}@J%mkG=^_617VYTN_-*uwZQ>@e;C=p1hAxzuPY z$1!2;BgSXp$2L;aX4lc6=Fp=@N++GJ%Nd|#+yg>1SaJW%CtS-czheJjuH@VmHL0Rz zmXpB^7#E-r6oN}4s8Xd$l`2)LRH;&>V$9XGgOiaB8-H+dgj>Xz=O6j^J%Ml=G3Mzn zX@u}waStH;?=j|iL0*xYmIR0|-5=q9k1?Mu)&dv2Tf05NZN!-W`Y`~V^^RL?Pkc1y z!;=8(_Lb~-H`aP5bm8X{rmVExHM@6a#KP+U?3)?6E+2rz568?ndv_6TBgWiT9jn`y z=4cwU)_>_21IE738yadn7694pgnwfhkOAbqENa#j#>0^Rs%+f0T8q=bH0j?;>%HpE zA^iR^=5WEYhi$1qKhQ$%n_~f^&&S22ch?+;Sq+cDSK47fr0#P_G`50wJU2mdleQKz zG@~GU;M6;Y@CU@0W9cpJF+)jZ>EOmL02jRm*?)s*I<|!ChSvf9l}d)!M1Zwo6Z^%+ zJ3A))0Ws!`6BF%=H)^!nXuGf0c8hG=VEfm?>p_B8W?S1OwgIGy56JRr|5eeQMEC<@ z%uRBb#j)~r9ieL-nO%kuS__KJ-I~<$@u&wY#Rgl)MfSHj2-ejW9_`-Gzhel$dyKhd zgnx5VK>qfq=QWR&IuM>}2t#xe{;_-ay=S@G)Bo^+ z@pq0fFM3H6Q|Wt5634*U8-T_k6nG4>?+!^K*4F2s_`>-`!^OYv-XZd~V$AcN)~_tT zv8>5CK)g5!#`<}{b{Wkz`36wZtR7few0|iD=Jvf&641#4Z0ViNr6~o#e35?F5N zT!{J$E(9B{!Z!g@c#bRt_B5Bm^Yv_CcYqM``LugzJ|X=?w&M_aF2_FlmWz|RcFKMn z$U1FbI-it#Pfn^B`>B~Fg#hsI%L+kFv?>IJpb!*-LQn|z0D>hyKR-YJH>S4^w=WCH zJM6!<;+OLh5B=tx(7?`afRJDfEq{MFvfw5i*2`Gn7we}Hwx~u&6^!)|LN~UI;aFmY zmxuYg1;Pulv9U8k#Z8kTF9N*pQzgRxX@-BbsgD@pXy_N>o(RzZbduM8tJ}L(B783< z0JPIMVg_GuYlJbv_J_PT4&Z0=H6YJ?nv3SMoc*M^x7mD!1hM?w)Zz0SPk%YfkGl*o zS$t<*KM>>F%xtzm*1QqpKQFc+>@h`J19=OUj2^Kl*M$*YleUT0Sa|_>OT+=&D z@x)6hrA#D2#58%dmI%mp-o|+GlXYQ|lv2uKE;QD(3Y0Cb17ZG?ss|dxpc2Sz=Gmo* zY?*S`5spU3{9FY>;b!llwtt2bVm0uH$6tVVd@Xz@;(`9Q-68l4mJh{y&Ig!4Uq_Ou zHLNu<=mM;l6M-~S`lG6KnJG?_LVZUv;u;)9uF!T7~jwqu|SSYw73AmLMf$OSJjxXTzqP4 ziIbP*)2GatM<|GNr?`(mQnRT}Q}lA!Ubx zsu0q|09(rixh}8TW`Eue!1M^WL|CAGuKXFJ6(P(pV^=~#%W??gMFwzw&6Gg7{H_m- zrOBz5Dvu7omYprN7`??apPllTZbNU0@PQoc^s!!G2ykA$1JEf52(vy=`p@?NAHu=81b3CX>99JJ>f?6Io@k-g)mx9ao${D2#_o%1G&CIK!2D_0+PK3!0P1yEql8n z!jlmZ5o4Z_Z5+SREt>jsf!+-@84iSTo>5UHl@K-ve`{!YwEeX`8bvLHz?xCtu6&|z z-6#mF+h^v6nSO>-So+IBYxce+<6RHokTrGuPh-kPx174hv%oTASAYcLI^cA$(OAGC z<0^n~`<3BFdp2mf;~5t_ni)I5$;YK^@ny3L`fE4w%6*Ms0-(E0QwZ;hJwVawiId|B z0S?BKyKN7Ux@5dLr98qn6W{wD_{N&HoD+YVr1;#Fg_GA8SrJO(BWBt{$&Y<9A?^~u zk@y=wMo!u0>Ih@Re;FtUkqw$@8l3}1i6lP}25|bLe0;5FO zoSnxxq8)mJqSv)Y>-9usec!Odz8gFZx>7K2#dub4%**ID=>*G zz;!XnwFkx=@dAv~j9R~YcaJiJ68&GSY2%5N5$MyHefb2n}P>GfoJ}HJW_3XNJGuC6`AyZOtKX_}XRQ zTz4Vr&dlssJ%w=wYpBGPzMevMJYerWO&!zzH8?FM`29sM{N@pflYAQaJag)w@e_CsXd$dTVFtw^41t+iH>ys*L#9;@* zeLA!Bb8JW349k6t@MVpl1nZLRi3e(qQ6VSV_+NjU2>;(kD0IGJTrRHPgJZ(zTOd@Z zeTDCdTs?UX=}GKJh004&{1i- zWWJ}clz%_hJa8D$}N zfX8X)fc`$cvLQ+?tAub#j)%e#Gk{61x;vB>7Z(?oRzhg?tW&S1!2oZ`%eE+T^8H?R zM#wJ!ZfJ*BLij=a!`{bb5e5q(oF0Q~MR;8wReo;BI%7Of9wy70z)X+ERXwV~f2V&X zgzJXZ>vCNjVQzSMc=-HE2$`W$*6w@28Nnk1h- z>AEW;++<9EQ_IKJ)g7*ca7KOvz@KY4{UziFJ~V$%LPGT_5GedMrmv>6TOt6*9BJHT z2qpSq0JLrIgs{u{WB{M45CBGre_!1X;luBNeBHE42n?*90VHVVI{lDaWE_xO#qN+c zHW!#DFS#8;)?=O%w{GuM?Pz6$v;#@??V zY(4e{2yOcV|7>p0GoM`lVdx(W><<<~*Z(4Lxr@As#K{`RYm;=W0ONfaNxpV00000NkvXXu0mjf DC!Ndm delta 3887 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 diff --git a/tests/typ/basics/list.typ b/tests/typ/basics/list.typ index fc3e5ca72..b8bd59eab 100644 --- a/tests/typ/basics/list.typ +++ b/tests/typ/basics/list.typ @@ -43,6 +43,7 @@ _Shopping list_ - A with 1 tab - B with 2 tabs +--- // This doesn't work because of mixed tabs and spaces. - A with 2 spaces - B with 2 tabs diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ index ccde8598e..e01f88966 100644 --- a/tests/typ/compiler/array.typ +++ b/tests/typ/compiler/array.typ @@ -208,17 +208,16 @@ // Error: 3 expected closing paren {(} -// Error: 2-3 expected expression, found closing paren +// Error: 2-3 unexpected closing paren {)} -// Error: 4 expected comma // Error: 4-6 unexpected end of block comment {(1*/2)} // Error: 6-8 invalid number suffix {(1, 1u 2)} -// Error: 3-4 expected expression, found comma +// Error: 3-4 unexpected comma {(,1)} // Missing expression makes named pair incomplete, making this an empty array. diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ index 7cf1f8bec..7fb7738b2 100644 --- a/tests/typ/compiler/block.typ +++ b/tests/typ/compiler/block.typ @@ -126,10 +126,12 @@ // Should output `3`. { - // Error: 7-10 expected identifier, found string + // Error: 6 expected identifier + // Error: 10 expected block for "v" // Error: 8 expected keyword `in` + // Error: 22 expected block for v let z = 1 + 2 z diff --git a/tests/typ/compiler/call.typ b/tests/typ/compiler/call.typ index 7ea0a998f..087e46941 100644 --- a/tests/typ/compiler/call.typ +++ b/tests/typ/compiler/call.typ @@ -44,7 +44,7 @@ } --- -// Error: 28-47 duplicate argument +// Error: 28-34 duplicate argument #set text(family: "Arial", family: "Helvetica") --- @@ -70,7 +70,8 @@ #f[1](2) --- -// Error: 7-8 expected expression, found colon +// Error: 7 expected expression +// Error: 8 expected expression #func(:) // Error: 10-12 unexpected end of block comment @@ -102,5 +103,4 @@ --- // Error: 2:1 expected quote -// Error: 2:1 expected closing paren #func("] diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ index c73212044..f1604b19d 100644 --- a/tests/typ/compiler/closure.typ +++ b/tests/typ/compiler/closure.typ @@ -145,6 +145,16 @@ test(greet("Typst", whatever: 10)) } +--- +// Error: 11-12 duplicate parameter +#let f(x, x) = none + +--- +// Error: 14-15 duplicate parameter +// Error: 23-24 duplicate parameter +// Error: 35-36 duplicate parameter +#let f(a, b, a: none, b: none, c, b) = none + --- // Error: 6-16 expected identifier, named pair or argument sink, found keyed pair {(a, "named": b) => none} diff --git a/tests/typ/compiler/dict.typ b/tests/typ/compiler/dict.typ index 0170cb8b3..f9a0b3695 100644 --- a/tests/typ/compiler/dict.typ +++ b/tests/typ/compiler/dict.typ @@ -56,11 +56,11 @@ #test(dict, (a: 3, b: 1)) --- -// Error: 24-32 pair has duplicate key +// Error: 24-29 duplicate key {(first: 1, second: 2, first: 3)} --- -// Error: 17-23 pair has duplicate key +// Error: 17-20 duplicate key {(a: 1, "b": 2, "a": 3)} --- @@ -72,8 +72,11 @@ // Error: 4-5 expected named or keyed pair, found integer // Error: 5 expected comma // Error: 12-16 expected identifier or string, found boolean -// Error: 17-18 expected expression, found colon -{(:1 b:"", true::)} +// Error: 17 expected expression +{(:1 b:"", true:)} + +// Error: 3-8 expected identifier or string, found binary expression +{(a + b: "hey")} --- // Error: 3-15 cannot mutate a temporary value diff --git a/tests/typ/compiler/field.typ b/tests/typ/compiler/field.typ index 78439ae08..0195c6d8d 100644 --- a/tests/typ/compiler/field.typ +++ b/tests/typ/compiler/field.typ @@ -36,5 +36,6 @@ = A --- -// Error: 8-12 expected identifier, found boolean +// Error: 8 expected identifier +// Error: 8 expected semicolon or line break {false.true} diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ index f63b870e1..7a530b73a 100644 --- a/tests/typ/compiler/for.typ +++ b/tests/typ/compiler/for.typ @@ -94,7 +94,7 @@ // Error: 5 expected identifier #for -// Error: 7 expected identifier +// Error: 5 expected identifier #for// // Error: 5 expected identifier @@ -106,17 +106,18 @@ // Error: 10 expected expression #for v in -// Error: 15 expected body +// Error: 15 expected block #for v in iter // Error: 5 expected identifier #for v in iter {} -// Error: 7-10 expected identifier, found string +// Error: 6 expected identifier +// Error: 10 expected block A#for "v" thing -// Error: 6-9 expected identifier, found string +// Error: 5 expected identifier #for "v" in iter {} // Error: 7 expected keyword `in` diff --git a/tests/typ/compiler/if.typ b/tests/typ/compiler/if.typ index 0d87c689b..3b35ebd89 100644 --- a/tests/typ/compiler/if.typ +++ b/tests/typ/compiler/if.typ @@ -112,7 +112,7 @@ // Error: 4 expected expression {if} -// Error: 6 expected body +// Error: 6 expected block #if x // Error: 1-6 unexpected keyword `else` @@ -124,11 +124,11 @@ x {} // Should output `something`. -// Error: 6 expected body +// Error: 6 expected block #if x something // Should output `A thing.` -// Error: 19 expected body +// Error: 19 expected block A#if false {} else thing #if a []else [b] diff --git a/tests/typ/compiler/import.typ b/tests/typ/compiler/import.typ index 6f2ac459c..6b2d80750 100644 --- a/tests/typ/compiler/import.typ +++ b/tests/typ/compiler/import.typ @@ -81,21 +81,16 @@ This is never reached. #import --- -// Error: 26-29 expected identifier, found string +// Error: 26-29 unexpected string #import "module.typ": a, "b", c --- -// Error: 22 expected import items -#import "module.typ": - ---- -// Error: 23-24 expected expression, found assignment operator -// Error: 24 expected import items +// Error: 23-24 unexpected equals sign #import "module.typ": = --- // An additional trailing comma. -// Error: 31-32 expected expression, found comma +// Error: 31-32 unexpected comma #import "module.typ": a, b, c,, --- @@ -105,7 +100,7 @@ This is never reached. --- // A star in the list. -// Error: 26-27 expected expression, found star +// Error: 26-27 unexpected star #import "module.typ": a, *, b --- @@ -114,5 +109,10 @@ This is never reached. #import "module.typ": *, a --- -// Error: 13-17 expected identifier, found named pair +// Error: 14-15 unexpected colon +// Error: 16-17 unexpected integer #import "": a: 1 + +--- +// Error: 14 expected comma +#import "": a b diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ index d4f9510ab..3a879ce7a 100644 --- a/tests/typ/compiler/let.typ +++ b/tests/typ/compiler/let.typ @@ -39,7 +39,8 @@ Three // Error: 5 expected identifier {let} -// Error: 6-9 expected identifier, found string +// Error: 5 expected identifier +// Error: 5 expected semicolon or line break #let "v" // Error: 7 expected semicolon or line break @@ -48,7 +49,8 @@ Three // Error: 9 expected expression #let v = -// Error: 6-9 expected identifier, found string +// Error: 5 expected identifier +// Error: 5 expected semicolon or line break #let "v" = 1 // Terminated because expression ends. @@ -61,7 +63,7 @@ Three #let v5 = (1, 2 + ; Five --- -// Error: 13 expected body +// Error: 13 expected equals sign #let func(x) // Error: 15 expected expression diff --git a/tests/typ/compiler/spread.typ b/tests/typ/compiler/spread.typ index ff661eadb..244e9fb9c 100644 --- a/tests/typ/compiler/spread.typ +++ b/tests/typ/compiler/spread.typ @@ -60,7 +60,7 @@ #min(.."nope") --- -// Error: 8-14 expected identifier, named pair or argument sink, found spread +// Error: 10-14 expected identifier, found boolean #let f(..true) = none --- diff --git a/tests/typ/compiler/while.typ b/tests/typ/compiler/while.typ index 3c28a32a1..d495a84a1 100644 --- a/tests/typ/compiler/while.typ +++ b/tests/typ/compiler/while.typ @@ -49,12 +49,12 @@ // Error: 7 expected expression {while} -// Error: 9 expected body +// Error: 9 expected block #while x // Error: 7 expected expression #while x {} -// Error: 9 expected body +// Error: 9 expected block #while x something diff --git a/tests/typ/text/emphasis.typ b/tests/typ/text/emphasis.typ index 27e3b9776..f45700310 100644 --- a/tests/typ/text/emphasis.typ +++ b/tests/typ/text/emphasis.typ @@ -38,6 +38,6 @@ _Hello World --- -// Error: 1:12 expected star -// Error: 2:1 expected star -_Cannot *be_ interleaved* +// Error: 25 expected star +// Error: 25 expected underscore +[_Cannot *be interleaved] diff --git a/tests/typ/text/raw.typ b/tests/typ/text/raw.typ index c17c8fecf..96a23b66d 100644 --- a/tests/typ/text/raw.typ +++ b/tests/typ/text/raw.typ @@ -41,7 +41,8 @@ The keyword ```rust let```. // First line is not dedented and leading space is still possible. ``` A B - C``` + C + ``` --- // Unterminated.