Repository: typst/typst
File: crates/typst-syntax/src/ast.rs (Rust)

Code before:
node! {
    /// A mathematical equation: `$x$`, `$ x^2 $`.
    struct Equation
}

impl<'a> Equation<'a> {
    /// The contained math.
    pub fn body(self) -> Math<'a> {
        self.0.cast_first()
    }

    /// Whether the equation should be displayed as a separate block.
    pub fn block(self) -> bool {
        // The `.unwrap()` calls in this function won't panic because there will
        // always be an extra dollar-sign node on the opposing side.
        let mut front_iter = self.0.children().enumerate().skip(1);
        let (mut front_idx, mut front) = front_iter.next().unwrap();
        // The parser likes to group adjacent trivia, so if the equation's body
        // is an empty Math node, e.g. in `$ /**/ $`, the CST will look like:
        // `Eqn [ Math(<empty>) Space(" ") Trivia("/**/") Space(" ") ]`.
        // Yet we still want to treat this as a block, so we check the following
        // node by skipping an empty math node at the start.
        if front.is_empty() && front.kind() == SyntaxKind::Math {
            (front_idx, front) = front_iter.next().unwrap();
        }
        let (back_idx, back) = self.0.children().enumerate().nth_back(1).unwrap();

        front.kind() == SyntaxKind::Space
            && back.kind() == SyntaxKind::Space
            // If the front and back spaces are the same one, require that it
            // has multiple characters or, if one char, that it is a newline.
            && (front_idx != back_idx || {
                match front.text().char_indices().next_back() {
                    Some((0, c)) => is_newline(c), // A single char must be a newline.
                    _ => true
                }
            })
    }
}

node! {
    /// The contents of a mathematical equation: `x^2 + 1`.
    struct Math
}

impl<'a> Math<'a> {
    /// The expressions the mathematical content consists of.
    pub fn exprs(self) -> impl DoubleEndedIterator<Item = Expr<'a>> {
        self.0.children().filter_map(Expr::cast_with_space)
    }


Review comment (suggestion):
I would find this grouping more readable because it emphasizes the general parallelity of front & back
```rust
// The `.unwrap()` calls in this function won't panic because there will
// always be an extra dollar-sign node on the opposing side.

// The parser likes to group adjacent trivia, so if the equation's body
// is an empty Math node, e.g. in `$ /**/ $`, the CST will look like:
// `Eqn [ Math(<empty>) Space(" ") Trivia("/**/") Space(" ") ]`.
// Yet we still want to treat this as a block, so we check the following
// node by skipping an empty math node at the start.
let mut front_iter = self.0.children().enumerate().skip(1);
let (mut front_idx, mut front) = front_iter.next().unwrap();
if front.is_empty() && front.kind() == SyntaxKind::Math {
    (front_idx, front) = front_iter.next().unwrap();
}

let (back_idx, back) = self.0.children().enumerate().nth_back(1).unwrap();
```


Code after:
node! {
    /// A mathematical equation: `$x$`, `$ x^2 $`.
    struct Equation
}

impl<'a> Equation<'a> {
    /// The contained math.
    pub fn body(self) -> Math<'a> {
        self.0.cast_first()
    }

    /// Whether the equation should be displayed as a separate block.
    pub fn block(self) -> bool {
        // The parser likes to group adjacent trivia, so if the equation's body
        // is an empty Math node, e.g. in `$ /**/ $`, the CST will look like:
        // `Eqn [ Math(<empty>) Space(" ") Trivia("/**/") Space(" ") ]`.
        // Yet we still want to treat this as a block, so we check the following
        // node by skipping an empty math node at the start.
        let mut front_iter = self.0.children().enumerate().skip(1);
        let mut front_pair = front_iter.next();
        if let Some((_, front)) = front_pair
            && front.is_empty()
            && front.kind() == SyntaxKind::Math
        {
            front_pair = front_iter.next();
        }

        if let Some((front_idx, front)) = front_pair
            && let Some((back_idx, back)) = self.0.children().enumerate().nth_back(1)
        {
            front.kind() == SyntaxKind::Space
                && back.kind() == SyntaxKind::Space
                // If the front and back spaces are the same one, require that
                // it has multiple chars or, if one char, that it is a newline.
                // `parse::<char>` is `Ok` if there was only a single char.
                && (front_idx != back_idx
                    || front.text().parse::<char>().map_or(true, is_newline))
        } else {
            false
        }
    }
}

node! {
    /// The contents of a mathematical equation: `x^2 + 1`.
    struct Math
}

impl<'a> Math<'a> {
    /// The expressions the mathematical content consists of.
    pub fn exprs(self) -> impl DoubleEndedIterator<Item = Expr<'a>> {