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

Code before:
                has_arrays = true;
            }
            SyntaxKind::End | SyntaxKind::Dollar | SyntaxKind::RightParen => {
                // Check if we have reached the end, after the last argument.
                if has_arrays && positional {
                    p.wrap(maybe_array_start, SyntaxKind::Array);
                    // So we don't wrap as an array twice after the loop.
                    array_last = false;
                }
            }
            _ => p.unexpected(),
        }
    }

    // Check if we need to wrap the preceding arguments in an array.
    if array_last && maybe_array_start != p.marker() && has_arrays && positional {
        p.wrap(maybe_array_start, SyntaxKind::Array);
    }

    p.expect_closing_delimiter(m, SyntaxKind::RightParen);
    p.wrap(m, SyntaxKind::Args);
}

/// Parses a single argument in a math argument list.
///
/// Returns whether the argument parsed was positional or not.
fn math_arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) -> bool {
    let m = p.marker();
    let start = p.current_start();

    if p.at(SyntaxKind::Dot) {
        // Parses a spread argument: `..args`.
        if let Some(spread) = p.lexer.is_math_spread_arg(start) {
            p.token.node = spread;
            p.eat();
            math_expr(p);
            p.wrap(m, SyntaxKind::Spread);
            return true;
        }
    }

    let mut positional = true;
    if p.at_set(syntax_set!(Text, MathIdent, Underscore)) {
        // Parses a named argument: `thickness: #12pt`.
        if let Some(named) = p.lexer.is_math_named_arg(start) {
            p.token.node = named;
            let text = p.current_text();
            p.eat();
            p.convert_and_eat(SyntaxKind::Colon);
            if !seen.insert(text) {
                p[m].convert_to_error(eco_format!("duplicate argument: {text}"));

Review comment (suggestion):
```suggestion
/// Returns whether the parsed argument was positional or not.
```

Code after:
            }
            SyntaxKind::End | SyntaxKind::Dollar | SyntaxKind::RightParen => {}
            _ => p.expected("comma or semicolon"),
        }
    }

    // Check if we need to wrap the preceding arguments in an array.
    if maybe_array_start != p.marker() && has_arrays && positional {
        p.wrap(maybe_array_start, SyntaxKind::Array);
    }

    p.expect_closing_delimiter(m, SyntaxKind::RightParen);
    p.wrap(m, SyntaxKind::Args);
}

/// Parses a single argument in a math argument list.
///
/// Returns whether the parsed argument was positional or not.
fn math_arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) -> bool {
    let m = p.marker();
    let start = p.current_start();

    if p.at(SyntaxKind::Dot) {
        // Parses a spread argument: `..args`.
        if let Some(spread) = p.lexer.maybe_math_spread_arg(start) {
            p.token.node = spread;
            p.eat();
            math_expr(p);
            p.wrap(m, SyntaxKind::Spread);
            return true;
        }
    }

    let mut positional = true;
    if p.at_set(syntax_set!(Text, MathIdent, Underscore)) {
        // Parses a named argument: `thickness: #12pt`.
        if let Some(named) = p.lexer.maybe_math_named_arg(start) {
            p.token.node = named;
            let text = p.current_text();
            p.eat();
            p.convert_and_eat(SyntaxKind::Colon);
            if !seen.insert(text) {
                p[m].convert_to_error(eco_format!("duplicate argument: {text}"));
            }
            positional = false;
        }
    }

    // Parses a normal positional argument.
    let arg = p.marker();
    let count = math_exprs(p, syntax_set!(End, Dollar, Comma, Semicolon, RightParen));