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