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

Code before:
            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}"));
            }
            positional = false;
        }
    }

    // Parses a normal position argument.
    let arg = p.marker();
    let count = math_exprs(p, syntax_set!(End, Comma, Semicolon, RightParen));
    let named = if positional { None } else { Some(m) };
    maybe_wrap_in_math(p, arg, count, named);

    positional
}

/// Wrap math function arguments to join adjacent math content or create an
/// empty 'Math' node for when we have 0 args.
///
/// We don't wrap when `count == 1`, since wrapping would change the type of the
/// expression from potentially non-content to content. Ex: `$ func(#12pt) $`
/// would change the type from size to content if wrapped.
fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, count: usize, named: Option<Marker>) {
    if count == 0 {
        // Flush trivia so that the new empty Math node will be wrapped _inside_
        // any `SyntaxKind::Array` elements created in `math_args`.
        // (And if we don't follow by wrapping in an array, it has no effect.)
        // The difference in node layout without this would look like:
        // Expression: `$ mat( ;) $`
        // - Correct:   [ .., Space(" "), Array[Math[], ], Semicolon(";"), .. ]
        // - Incorrect: [ .., Math[], Array[], Space(" "), Semicolon(";"), .. ]
        p.flush_trivia();
    }

    if count != 1 {
        p.wrap(arg, SyntaxKind::Math);
    }

Review comment (refactor):
I feel like `maybe_wrap_in_math` doesn't really pull its weight as an abstraction anymore. Perhaps it should be inlined into `fn arg`?

Code after:
        // 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));
    if count == 0 {
        // Named argument requires a value.
        if !positional {
            p.expected("expression");
        }

        // Flush trivia so that the new empty Math node will be wrapped _inside_
        // any `SyntaxKind::Array` elements created in `math_args`.
        // (And if we don't follow by wrapping in an array, it has no effect.)
        // The difference in node layout without this would look like:
        // Expression: `$ mat( ;) $`
        // - Correct:   [ .., Space(" "), Array[Math[], ], Semicolon(";"), .. ]
        // - Incorrect: [ .., Math[], Array[], Space(" "), Semicolon(";"), .. ]
        p.flush_trivia();
    }

    // Wrap math function arguments to join adjacent math content or create an
    // empty 'Math' node for when we have 0 args. We don't wrap when
    // `count == 1`, since wrapping would change the type of the expression
    // from potentially non-content to content. Ex: `$ func(#12pt) $` would
    // change the type from size to content if wrapped.
    if count != 1 {
        p.wrap(arg, SyntaxKind::Math);
    }

    if !positional {
        p.wrap(m, SyntaxKind::Named);
    }
    positional
}

/// Parses the contents of a code block.
fn code(p: &mut Parser, stop_set: SyntaxSet) {
    let m = p.marker();
    code_exprs(p, stop_set);