summaryrefslogtreecommitdiff
path: root/src/syntax
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-01-27 11:54:30 +0100
committerLaurenz <laurmaedje@gmail.com>2023-01-27 11:54:30 +0100
commita8fd64f9289b92614b9e6c16e909ec0c45429027 (patch)
tree76ce8797a6fe9c8b8c0bb1783910ba4b9e22da8b /src/syntax
parent33585d9a3fbab8a76d3fd8e9c2560f929202a518 (diff)
Hashtags everywhere!
Diffstat (limited to 'src/syntax')
-rw-r--r--src/syntax/ast.rs292
-rw-r--r--src/syntax/kind.rs37
-rw-r--r--src/syntax/lexer.rs21
-rw-r--r--src/syntax/node.rs29
-rw-r--r--src/syntax/parser.rs222
-rw-r--r--src/syntax/reparser.rs68
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);
}
}