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

Code before:
                if !positional {
                    maybe_array_start = p.marker();
                }

                // Parses an array: `a, b, c;`.
                // The semicolon merges preceding arguments separated by commas into an
                // array argument.
                p.wrap(maybe_array_start, SyntaxKind::Array);
                p.eat();
                maybe_array_start = p.marker();
                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);
                    // This is 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 false;
        }
    }


Review comment (suggestion):
We should add a test case for unclosed math function calls. (and also for if the call isn't actually a function, e.g. `$sin(x)$` vs. `$sin(x$`)

Code after:
            }
            SyntaxKind::Semicolon => {
                if !positional {
                    maybe_array_start = p.marker();
                }

                // Parses an array: `a, b, c;`.
                // The semicolon merges preceding arguments separated by commas
                // into an array argument.
                p.wrap(maybe_array_start, SyntaxKind::Array);
                p.eat();
                maybe_array_start = p.marker();
                has_arrays = true;
            }
            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;