Skip to main content

arity_parser/parser/
expr.rs

1use crate::parser::context::{
2    ParserCtx, StatementTracker, push_token_diagnostic_ctx as push_token_diagnostic,
3};
4use crate::parser::diagnostics::ParseDiagnostic;
5use crate::parser::events::{Event, ExprParse};
6use crate::parser::lexer::{TokKind, Token};
7use crate::parser::recovery::error_expr_to_line_end;
8use crate::parser::structural::{
9    parse_for_expr, parse_function_expr, parse_if_expr, parse_repeat_expr, parse_while_expr,
10};
11use crate::syntax::SyntaxKind;
12
13fn is_assignment_operator(kind: &TokKind) -> bool {
14    matches!(
15        kind,
16        TokKind::AssignLeft
17            | TokKind::SuperAssign
18            | TokKind::AssignRight
19            | TokKind::SuperAssignRight
20            | TokKind::AssignEq
21            | TokKind::Walrus
22    )
23}
24
25fn is_infix_operator(kind: &TokKind) -> bool {
26    infix_binding_power(kind).is_some()
27}
28
29fn next_operator<'a>(
30    ctx: &ParserCtx<'a>,
31    start: usize,
32    inside_brackets: bool,
33) -> Option<(usize, &'a Token<'a>)> {
34    let op_idx = ctx.skip_ws(start);
35    let op = ctx.token(op_idx)?;
36    if matches!(op.kind, TokKind::Newline | TokKind::Comment) {
37        // Outside brackets, a newline after a complete operand terminates the
38        // expression — R only continues across the newline if the prior line is
39        // incomplete (i.e. ended in an operator). Inside `(`, `[`, `[[` (and
40        // the conditions/sequences of `if`/`while`/`for`), newlines are not
41        // statement separators, so peek past them for a continuation operator.
42        //
43        // A trailing comment before the (possible) newline behaves the same way:
44        // it is trivia, not a terminator, so `(a # note\n || b)` continues just
45        // like `(a\n || b)`. `skip_ws` above stops at the comment, so peek past
46        // comments too (not just newlines) when looking for the operator.
47        if !inside_brackets {
48            return None;
49        }
50        let next_idx = ctx.skip_ws_newlines_comments(start);
51        let next = ctx.token(next_idx)?;
52        if is_assignment_operator(&next.kind) || is_infix_operator(&next.kind) {
53            return Some((next_idx, next));
54        }
55        return None;
56    }
57    if is_assignment_operator(&op.kind) || is_infix_operator(&op.kind) {
58        return Some((op_idx, op));
59    }
60    None
61}
62
63pub(crate) fn parse_expr(
64    tokens: &[Token],
65    start: usize,
66    min_bp: u8,
67    diagnostics: &mut Vec<ParseDiagnostic>,
68    md_default: bool,
69) -> Option<ExprParse> {
70    parse_expr_with_mode(tokens, start, min_bp, diagnostics, false, false, md_default)
71}
72
73pub(crate) fn parse_expr_in_brackets(
74    tokens: &[Token],
75    start: usize,
76    min_bp: u8,
77    diagnostics: &mut Vec<ParseDiagnostic>,
78    md_default: bool,
79) -> Option<ExprParse> {
80    parse_expr_with_mode(tokens, start, min_bp, diagnostics, true, true, md_default)
81}
82
83fn parse_expr_with_mode(
84    tokens: &[Token],
85    start: usize,
86    min_bp: u8,
87    diagnostics: &mut Vec<ParseDiagnostic>,
88    allow_newline_prefix: bool,
89    inside_brackets: bool,
90    md_default: bool,
91) -> Option<ExprParse> {
92    let ctx = ParserCtx::new(tokens, md_default);
93    let start_non_ws = if allow_newline_prefix {
94        ctx.skip_ws_and_newlines(start)
95    } else {
96        ctx.skip_ws(start)
97    };
98    if matches!(
99        tokens.get(start_non_ws).map(|t| &t.kind),
100        Some(TokKind::IfKw)
101    ) {
102        return parse_if_expr(tokens, start_non_ws, diagnostics, md_default);
103    }
104    if matches!(
105        tokens.get(start_non_ws).map(|t| &t.kind),
106        Some(TokKind::ForKw)
107    ) {
108        return parse_for_expr(tokens, start_non_ws, diagnostics, md_default);
109    }
110    if matches!(
111        tokens.get(start_non_ws).map(|t| &t.kind),
112        Some(TokKind::WhileKw)
113    ) {
114        return parse_while_expr(tokens, start_non_ws, diagnostics, md_default);
115    }
116    if matches!(
117        tokens.get(start_non_ws).map(|t| &t.kind),
118        Some(TokKind::RepeatKw)
119    ) {
120        return parse_repeat_expr(tokens, start_non_ws, diagnostics, md_default);
121    }
122    if matches!(
123        tokens.get(start_non_ws).map(|t| &t.kind),
124        Some(TokKind::FunctionKw)
125    ) {
126        return parse_function_expr(
127            tokens,
128            start_non_ws,
129            inside_brackets,
130            diagnostics,
131            md_default,
132        );
133    }
134    if matches!(
135        tokens.get(start_non_ws).map(|t| &t.kind),
136        Some(TokKind::LambdaFn)
137    ) {
138        return parse_function_expr(
139            tokens,
140            start_non_ws,
141            inside_brackets,
142            diagnostics,
143            md_default,
144        );
145    }
146
147    let mut lhs = parse_prefix(
148        &ctx,
149        start,
150        diagnostics,
151        allow_newline_prefix,
152        inside_brackets,
153    )?;
154
155    // A bare comment parses as an atom (so a comment-only argument such as
156    // `c(1, # note\n)` has something to hang on), but it can never be the
157    // operand of an operator. Do not let the infix loop bind a following
158    // operator to it: inside brackets `next_operator` peeks across newlines,
159    // so in `c(-1, # a\n -2)` the `-` that starts the next argument would
160    // otherwise be misread as a binary minus on the comment.
161    if lhs.end == lhs.start + 1 && tokens[lhs.start].kind.is_comment_like() {
162        return Some(lhs);
163    }
164
165    loop {
166        lhs = parse_postfix_chain(&ctx, lhs, inside_brackets, diagnostics);
167
168        let Some((op_idx, op)) = next_operator(&ctx, lhs.end, inside_brackets) else {
169            break;
170        };
171
172        if is_assignment_operator(&op.kind) {
173            let (l_bp, r_bp) = (1, 1);
174            if l_bp < min_bp {
175                break;
176            }
177
178            let rhs_start = op_idx + 1;
179            // A pending operator means the line is incomplete, so newlines and
180            // comments before the RHS operand are not statement terminators or
181            // operands — skip past them to the operand (R does the same). The
182            // skipped tokens stay in the tree as trivia between the operator and
183            // the operand, emitted by the event builder.
184            let rhs_operand = ctx.skip_ws_newlines_comments(rhs_start);
185            let Some(rhs) = parse_expr_with_mode(
186                tokens,
187                rhs_operand,
188                r_bp,
189                diagnostics,
190                true,
191                inside_brackets,
192                ctx.md_default(),
193            ) else {
194                push_token_diagnostic(diagnostics, "expected assignment right-hand side", op);
195                return Some(error_expr_to_line_end(tokens, lhs.start, rhs_start));
196            };
197
198            lhs = build_assignment_expr(lhs, op_idx, rhs);
199            continue;
200        }
201
202        let Some((l_bp, r_bp)) = infix_binding_power(&op.kind) else {
203            break;
204        };
205        if l_bp < min_bp {
206            break;
207        }
208
209        let rhs_start = op_idx + 1;
210        // A pending operator means the line is incomplete, so newlines and
211        // comments before the RHS operand are not statement terminators or
212        // operands — skip past them to the operand (the skipped tokens remain
213        // in the tree as trivia between the operator and the operand).
214        let rhs_operand = ctx.skip_ws_newlines_comments(rhs_start);
215        // The extract/namespace operators (`$`, `@`, `::`, `:::`) share the
216        // highest precedence tier with the postfix operators (`[`, `[[`, `(`)
217        // and are all left-associative, so `a$b[c]` is `(a$b)[c]` and
218        // `pkg::fn(x)` is `(pkg::fn)(x)` (R's grammar). Their RHS is a bare
219        // member name, not a full expression: parse only the prefix operand so
220        // any trailing postfix binds to the *whole* extract expression via the
221        // outer loop's `parse_postfix_chain`, rather than being swallowed into
222        // the RHS.
223        let rhs = if is_extract_operator(&op.kind) {
224            parse_prefix(&ctx, rhs_operand, diagnostics, true, inside_brackets)
225        } else {
226            parse_expr_with_mode(
227                tokens,
228                rhs_operand,
229                r_bp,
230                diagnostics,
231                true,
232                inside_brackets,
233                ctx.md_default(),
234            )
235        };
236        let Some(rhs) = rhs else {
237            push_token_diagnostic(
238                diagnostics,
239                "expected right-hand side for binary operator",
240                op,
241            );
242            return Some(error_expr_to_line_end(tokens, lhs.start, rhs_start));
243        };
244
245        if matches!(op.kind, TokKind::Colon2 | TokKind::Colon3)
246            && lhs
247                .events
248                .iter()
249                .any(|event| matches!(event, Event::Start(SyntaxKind::CALL_EXPR)))
250        {
251            push_token_diagnostic(
252                diagnostics,
253                "namespace operator requires a package name on the left-hand side",
254                op,
255            );
256        }
257
258        if matches!(op.kind, TokKind::Colon2 | TokKind::Colon3)
259            && matches!(expr_root_kind(&rhs), Some(SyntaxKind::BLOCK_EXPR))
260        {
261            push_token_diagnostic(
262                diagnostics,
263                "namespace operator requires an identifier on the right-hand side",
264                op,
265            );
266        }
267
268        if matches!(op.kind, TokKind::Colon2 | TokKind::Colon3)
269            && matches!(
270                expr_root_kind(&rhs),
271                Some(SyntaxKind::INT | SyntaxKind::FLOAT | SyntaxKind::COMPLEX)
272            )
273        {
274            push_token_diagnostic(
275                diagnostics,
276                "namespace operator requires an identifier on the right-hand side",
277                op,
278            );
279        }
280
281        if matches!(op.kind, TokKind::Colon2 | TokKind::Colon3)
282            && matches!(expr_root_kind(&rhs), Some(SyntaxKind::IDENT))
283        {
284            let rhs_tok_idx = ctx.skip_ws(rhs.start);
285            if let Some(tok) = tokens.get(rhs_tok_idx)
286                && ident_is_special_constant(tok.text)
287            {
288                push_token_diagnostic(
289                    diagnostics,
290                    "namespace operator requires an identifier on the right-hand side",
291                    op,
292                );
293            }
294        }
295
296        if matches!(op.kind, TokKind::Colon2 | TokKind::Colon3)
297            && matches!(expr_root_kind(&lhs), Some(SyntaxKind::BINARY_EXPR))
298            && lhs
299                .events
300                .iter()
301                .any(|event| matches!(event, Event::Tok(idx) if matches!(tokens[*idx].kind, TokKind::Colon2 | TokKind::Colon3)))
302        {
303            push_token_diagnostic(diagnostics, "namespace operator chaining is not allowed", op);
304        }
305
306        lhs = build_binary_expr(lhs, op_idx, rhs);
307    }
308
309    Some(lhs)
310}
311
312fn parse_prefix(
313    ctx: &ParserCtx<'_>,
314    start: usize,
315    diagnostics: &mut Vec<ParseDiagnostic>,
316    allow_newline_prefix: bool,
317    inside_brackets: bool,
318) -> Option<ExprParse> {
319    let tokens = ctx.tokens();
320    let i = if allow_newline_prefix {
321        ctx.skip_ws_and_newlines(start)
322    } else {
323        ctx.skip_ws(start)
324    };
325    let tok = tokens.get(i)?;
326
327    match tok.kind {
328        TokKind::Plus | TokKind::Minus | TokKind::Bang | TokKind::Tilde | TokKind::Question => {
329            // Unary `~` (R formula) and unary `?` (help) sit at the same low
330            // precedence tier as their binary counterparts, so `~ x + y` must
331            // parse as `~(x + y)` and `? x + y` as `?(x + y)`. Match the infix
332            // right-binding power so every higher-precedence infix folds into
333            // the operand.
334            //
335            // Unary `!` binds *looser* than the comparison and arithmetic
336            // operators but tighter than `&&`/`||` (R's precedence table): `!a ==
337            // b` is `!(a == b)` and `!a + b` is `!(a + b)`, while `!a & b` is
338            // `(!a) & b`. Its right-binding power sits between the And tier
339            // (60/61) and the Relational tier (80/81) so relational and tighter
340            // infixes fold into the operand and the logical operators do not.
341            // Unary `+`/`-`, by contrast, bind tightly (just below `^`), so they
342            // keep the high right-binding power.
343            let rbp = match tok.kind {
344                TokKind::Tilde => 41,
345                TokKind::Question => 1,
346                TokKind::Bang => 70,
347                _ => 130,
348            };
349            let operand_start = i + 1;
350            let Some(operand) = parse_expr_with_mode(
351                tokens,
352                operand_start,
353                rbp,
354                diagnostics,
355                true,
356                inside_brackets,
357                ctx.md_default(),
358            ) else {
359                push_token_diagnostic(diagnostics, "expected operand for unary operator", tok);
360                return Some(error_expr_to_line_end(tokens, i, operand_start));
361            };
362            let mut events = Vec::new();
363            events.push(Event::Start(SyntaxKind::UNARY_EXPR));
364            events.push(Event::Tok(i));
365            for idx in (i + 1)..operand.start {
366                events.push(Event::Tok(idx));
367            }
368            events.extend(operand.events);
369            events.push(Event::Finish);
370            Some(ExprParse {
371                start: i,
372                end: operand.end,
373                events,
374            })
375        }
376        TokKind::Ident
377        | TokKind::Int
378        | TokKind::Float
379        | TokKind::Complex
380        | TokKind::String
381        | TokKind::Comment
382        | TokKind::UserOp => Some(ExprParse {
383            start: i,
384            end: i + 1,
385            events: vec![Event::Tok(i)],
386        }),
387        TokKind::LParen => {
388            let inner_start = i + 1;
389            let mut expr_start = inner_start;
390            while matches!(
391                tokens.get(expr_start).map(|t| &t.kind),
392                Some(TokKind::Whitespace | TokKind::Newline | TokKind::Comment)
393            ) {
394                expr_start += 1;
395            }
396            let Some(inner) = parse_expr_with_mode(
397                tokens,
398                expr_start,
399                0,
400                diagnostics,
401                true,
402                true,
403                ctx.md_default(),
404            ) else {
405                push_token_diagnostic(diagnostics, "expected expression after '('", tok);
406                return Some(error_expr_to_line_end(tokens, i, inner_start));
407            };
408            let mut close_idx = inner.end;
409            while matches!(
410                tokens.get(close_idx).map(|t| &t.kind),
411                Some(TokKind::Whitespace | TokKind::Newline | TokKind::Comment)
412            ) {
413                close_idx += 1;
414            }
415            if !matches!(
416                tokens.get(close_idx).map(|t| &t.kind),
417                Some(TokKind::RParen)
418            ) {
419                push_token_diagnostic(diagnostics, "expected ')'", tok);
420                let mut events = Vec::new();
421                events.push(Event::Start(SyntaxKind::PAREN_EXPR));
422                events.push(Event::Tok(i));
423                for idx in inner_start..inner.start {
424                    events.push(Event::Tok(idx));
425                }
426                events.extend(inner.events);
427                for idx in inner.end..close_idx {
428                    events.push(Event::Tok(idx));
429                }
430                events.push(Event::Finish);
431                return Some(ExprParse {
432                    start: i,
433                    end: close_idx,
434                    events,
435                });
436            }
437
438            let mut events = Vec::new();
439            events.push(Event::Start(SyntaxKind::PAREN_EXPR));
440            events.push(Event::Tok(i));
441            for idx in inner_start..inner.start {
442                events.push(Event::Tok(idx));
443            }
444            events.extend(inner.events);
445            for idx in inner.end..close_idx {
446                events.push(Event::Tok(idx));
447            }
448            events.push(Event::Tok(close_idx));
449            events.push(Event::Finish);
450
451            Some(ExprParse {
452                start: i,
453                end: close_idx + 1,
454                events,
455            })
456        }
457        TokKind::LBrace => parse_block_expr(ctx, i, diagnostics),
458        TokKind::Star
459        | TokKind::Slash
460        | TokKind::Caret
461        | TokKind::AssignLeft
462        | TokKind::SuperAssign
463        | TokKind::AssignRight
464        | TokKind::SuperAssignRight
465        | TokKind::AssignEq
466        | TokKind::Walrus
467        | TokKind::Colon
468        | TokKind::Or
469        | TokKind::Or2
470        | TokKind::And
471        | TokKind::And2
472        | TokKind::Equal2
473        | TokKind::NotEqual
474        | TokKind::LessThan
475        | TokKind::LessThanOrEqual
476        | TokKind::GreaterThan
477        | TokKind::GreaterThanOrEqual
478        | TokKind::Colon2
479        | TokKind::Colon3
480        | TokKind::Dollar
481        | TokKind::At
482        | TokKind::Semicolon
483        | TokKind::Comma
484        | TokKind::Pipe => {
485            push_token_diagnostic(diagnostics, "unexpected operator at expression start", tok);
486            Some(error_expr_to_line_end(tokens, i, i + 1))
487        }
488        TokKind::Whitespace
489        | TokKind::Newline
490        | TokKind::RParen
491        | TokKind::RBrack
492        | TokKind::RBrack2
493        | TokKind::RBrace
494        | TokKind::IfKw
495        | TokKind::ElseKw
496        | TokKind::ForKw
497        | TokKind::WhileKw
498        | TokKind::RepeatKw
499        | TokKind::FunctionKw
500        | TokKind::LambdaFn
501        | TokKind::InKw
502        | TokKind::LBrack
503        | TokKind::LBrack2
504        | TokKind::RoxygenMarker
505        | TokKind::RoxygenAt
506        | TokKind::RoxygenTagName
507        | TokKind::RoxygenTagArg
508        | TokKind::RoxygenText
509        | TokKind::RoxygenCode
510        | TokKind::RoxygenRdMacro
511        | TokKind::RoxygenMdLink
512        | TokKind::RoxygenMdBracket
513        | TokKind::RoxygenMdImage
514        | TokKind::RoxygenMdDelim
515        | TokKind::RoxygenMdCode
516        | TokKind::RoxygenMdListMarker
517        | TokKind::RoxygenMdFence
518        | TokKind::RoxygenMdHtml
519        | TokKind::RoxygenMdHtmlBlock
520        | TokKind::RoxygenMdTableDelim
521        | TokKind::RoxygenMdHeading
522        | TokKind::RoxygenMdSetextUnderline
523        | TokKind::RoxygenMdBlockQuote
524        | TokKind::RoxygenMdThematicBreak
525        | TokKind::Unknown => None,
526    }
527}
528
529fn parse_block_expr(
530    ctx: &ParserCtx<'_>,
531    start: usize,
532    diagnostics: &mut Vec<ParseDiagnostic>,
533) -> Option<ExprParse> {
534    let tokens = ctx.tokens();
535    let mut i = start + 1;
536    let mut events = vec![Event::Start(SyntaxKind::BLOCK_EXPR), Event::Tok(start)];
537    let mut statements = StatementTracker::default();
538
539    loop {
540        let next = ctx.skip_ws(i);
541        let Some(tok) = tokens.get(next) else {
542            push_token_diagnostic(diagnostics, "expected '}' to close block", &tokens[start]);
543            for idx in i..tokens.len() {
544                events.push(Event::Tok(idx));
545            }
546            events.push(Event::Finish);
547            return Some(ExprParse {
548                start,
549                end: tokens.len(),
550                events,
551            });
552        };
553
554        if tok.kind == TokKind::RBrace {
555            for idx in i..next {
556                events.push(Event::Tok(idx));
557            }
558            events.push(Event::Tok(next));
559            events.push(Event::Finish);
560            return Some(ExprParse {
561                start,
562                end: next + 1,
563                events,
564            });
565        }
566
567        // Consume newlines and semicolons as statement separators.
568        if matches!(tok.kind, TokKind::Newline | TokKind::Semicolon) {
569            for idx in i..=next {
570                events.push(Event::Tok(idx));
571            }
572            i = next + 1;
573            continue;
574        }
575
576        if tok.kind == TokKind::RoxygenMarker {
577            // Emit the leading whitespace gap, then the grouped block.
578            for idx in i..next {
579                events.push(Event::Tok(idx));
580            }
581            i = crate::parser::roxygen::emit_roxygen_block(
582                tokens,
583                next,
584                &mut events,
585                ctx.md_default(),
586            );
587            continue;
588        }
589
590        let before = diagnostics.len();
591        if let Some(expr) = parse_expr(tokens, i, 0, diagnostics, ctx.md_default()) {
592            // As at the root: a comment is an atom, not a statement.
593            if !tokens[expr.start].kind.is_comment_like() {
594                let recovered = diagnostics.len() > before;
595                statements.record(tokens, expr.start, expr.end, recovered, diagnostics);
596            }
597            for idx in i..expr.start {
598                events.push(Event::Tok(idx));
599            }
600            events.extend(expr.events);
601            i = expr.end;
602        } else {
603            events.push(Event::Tok(i));
604            i += 1;
605            statements.record_recovery(i);
606        }
607    }
608}
609
610fn parse_postfix_chain(
611    ctx: &ParserCtx<'_>,
612    mut lhs: ExprParse,
613    inside_brackets: bool,
614    diagnostics: &mut Vec<ParseDiagnostic>,
615) -> ExprParse {
616    let tokens = ctx.tokens();
617    loop {
618        // At top level no newline is allowed between the callee and `(` — `f\n(x)`
619        // is two statements in R. Inside brackets the newline is insignificant, so
620        // `structure(list\n(h = 1))` is `structure(list(h = 1))` and `f(x\n[i])`
621        // is `f(x[i])`; skip across it to find the postfix operator.
622        let after_lhs = if inside_brackets {
623            ctx.skip_ws_and_newlines(lhs.end)
624        } else {
625            ctx.skip_ws(lhs.end)
626        };
627        if matches!(
628            tokens.get(after_lhs).map(|t| &t.kind),
629            Some(TokKind::LParen)
630        ) {
631            lhs = parse_call_expr(ctx, lhs, after_lhs, diagnostics);
632            continue;
633        }
634        if matches!(
635            tokens.get(after_lhs).map(|t| &t.kind),
636            Some(TokKind::LBrack)
637        ) {
638            lhs = parse_subset_expr(ctx, lhs, after_lhs, diagnostics);
639            continue;
640        }
641        if matches!(
642            tokens.get(after_lhs).map(|t| &t.kind),
643            Some(TokKind::LBrack2)
644        ) {
645            lhs = parse_subset2_expr(ctx, lhs, after_lhs, diagnostics);
646            continue;
647        }
648        break;
649    }
650    lhs
651}
652
653fn build_binary_like_expr(
654    kind: SyntaxKind,
655    lhs: ExprParse,
656    op_idx: usize,
657    rhs: ExprParse,
658) -> ExprParse {
659    let mut events = Vec::new();
660    events.push(Event::Start(kind));
661    events.extend(lhs.events);
662    for idx in lhs.end..op_idx {
663        events.push(Event::Tok(idx));
664    }
665    events.push(Event::Tok(op_idx));
666    for idx in (op_idx + 1)..rhs.start {
667        events.push(Event::Tok(idx));
668    }
669    events.extend(rhs.events);
670    events.push(Event::Finish);
671
672    ExprParse {
673        start: lhs.start,
674        end: rhs.end,
675        events,
676    }
677}
678
679fn build_binary_expr(lhs: ExprParse, op_idx: usize, rhs: ExprParse) -> ExprParse {
680    build_binary_like_expr(SyntaxKind::BINARY_EXPR, lhs, op_idx, rhs)
681}
682
683fn build_assignment_expr(lhs: ExprParse, op_idx: usize, rhs: ExprParse) -> ExprParse {
684    build_binary_like_expr(SyntaxKind::ASSIGNMENT_EXPR, lhs, op_idx, rhs)
685}
686
687/// Returns true if tokens starting at `i` match the pattern `name =` (named
688/// argument), where `=` is not `==`. The name may be an identifier or a string
689/// literal: inside an argument list `=` is always named-argument syntax (never
690/// assignment), and R accepts a string as the name (`c("SE" = 13)`).
691fn is_named_arg(ctx: &ParserCtx<'_>, i: usize) -> bool {
692    let tokens = ctx.tokens();
693    if !matches!(
694        tokens.get(i).map(|t| &t.kind),
695        Some(TokKind::Ident | TokKind::String)
696    ) {
697        return false;
698    }
699    let mut next = i + 1;
700    while matches!(
701        tokens.get(next).map(|t| &t.kind),
702        Some(TokKind::Whitespace | TokKind::Newline | TokKind::Comment)
703    ) {
704        next += 1;
705    }
706    matches!(tokens.get(next).map(|t| &t.kind), Some(TokKind::AssignEq))
707}
708
709fn has_newline(tokens: &[Token], start: usize, end: usize) -> bool {
710    tokens[start..end]
711        .iter()
712        .any(|t| t.kind == TokKind::Newline)
713}
714
715fn expr_root_kind(expr: &ExprParse) -> Option<SyntaxKind> {
716    expr.events.iter().find_map(|event| match event {
717        Event::Start(kind) => Some(*kind),
718        _ => None,
719    })
720}
721
722pub fn ident_is_special_constant(text: &str) -> bool {
723    matches!(
724        text,
725        "NA" | "NULL"
726            | "TRUE"
727            | "FALSE"
728            | "Inf"
729            | "NaN"
730            | "NA_integer_"
731            | "NA_real_"
732            | "NA_complex_"
733            | "NA_character_"
734    )
735}
736
737fn parse_call_expr(
738    ctx: &ParserCtx<'_>,
739    callee: ExprParse,
740    lparen_idx: usize,
741    diagnostics: &mut Vec<ParseDiagnostic>,
742) -> ExprParse {
743    let tokens = ctx.tokens();
744    let mut events = vec![Event::Start(SyntaxKind::CALL_EXPR)];
745    events.extend(callee.events);
746    // Whitespace between callee end and `(`
747    for idx in callee.end..lparen_idx {
748        events.push(Event::Tok(idx));
749    }
750    events.push(Event::Tok(lparen_idx)); // (
751
752    events.push(Event::Start(SyntaxKind::ARG_LIST));
753    let mut i = lparen_idx + 1;
754
755    let mut expect_delimiter = false;
756    let mut last_arg_was_comment_only = false;
757    loop {
758        // Skip whitespace and newlines within the argument list.
759        let next_i = ctx.skip_ws_and_newlines(i);
760        let had_newline_gap = has_newline(tokens, i, next_i);
761        for idx in i..next_i {
762            events.push(Event::Tok(idx));
763        }
764        i = next_i;
765
766        // End of argument list.
767        if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::RParen) | None) {
768            break;
769        }
770
771        if expect_delimiter
772            && !had_newline_gap
773            && let Some(tok) = tokens.get(i)
774        {
775            let current_is_comment = tok.kind == TokKind::Comment;
776            if !current_is_comment && !last_arg_was_comment_only {
777                push_token_diagnostic(diagnostics, "expected ',' between arguments", tok);
778            }
779        }
780
781        // Empty argument (leading or consecutive comma).
782        if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::Comma)) {
783            events.push(Event::Start(SyntaxKind::ARG));
784            events.push(Event::Finish);
785            events.push(Event::Tok(i)); // ,
786            i += 1;
787            expect_delimiter = false;
788            last_arg_was_comment_only = false;
789            continue;
790        }
791
792        events.push(Event::Start(SyntaxKind::ARG));
793        last_arg_was_comment_only = false;
794
795        if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::RoxygenMarker)) {
796            // A `#'` line inside an argument list is a comment, not
797            // documentation, but the lexer classifies it as roxygen regardless
798            // of context. Group the whole roxygen block into this ARG (as the
799            // block/root parsers do) so its sub-tokens are one comment-only
800            // argument rather than a cascade of stray operands.
801            i = crate::parser::roxygen::emit_roxygen_block(
802                tokens,
803                i,
804                &mut events,
805                ctx.md_default(),
806            );
807            last_arg_was_comment_only = true;
808        } else if is_named_arg(ctx, i) {
809            // Named argument: ident = expr
810            events.push(Event::Tok(i)); // ident
811            let mut eq_idx = i + 1;
812            while matches!(
813                tokens.get(eq_idx).map(|t| &t.kind),
814                Some(TokKind::Whitespace | TokKind::Newline | TokKind::Comment)
815            ) {
816                eq_idx += 1;
817            }
818            for idx in (i + 1)..eq_idx {
819                events.push(Event::Tok(idx));
820            }
821            if !matches!(tokens.get(eq_idx).map(|t| &t.kind), Some(TokKind::AssignEq)) {
822                events.push(Event::Finish); // ARG
823                expect_delimiter = true;
824                i = eq_idx;
825                continue;
826            }
827            events.push(Event::Tok(eq_idx)); // =
828            let val_start = eq_idx + 1;
829            let mut value_idx = val_start;
830            while matches!(
831                tokens.get(value_idx).map(|t| &t.kind),
832                Some(TokKind::Whitespace | TokKind::Newline | TokKind::Comment)
833            ) {
834                value_idx += 1;
835            }
836            if matches!(
837                tokens.get(value_idx).map(|t| &t.kind),
838                Some(TokKind::Comma | TokKind::RParen) | None
839            ) {
840                for idx in val_start..value_idx {
841                    events.push(Event::Tok(idx));
842                }
843                i = value_idx;
844            } else if let Some(val) =
845                parse_expr_in_brackets(tokens, value_idx, 0, diagnostics, ctx.md_default())
846            {
847                for idx in val_start..val.start {
848                    events.push(Event::Tok(idx));
849                }
850                events.extend(val.events);
851                i = val.end;
852            } else {
853                i = val_start;
854            }
855        } else {
856            // Positional argument.
857            if let Some(arg) = parse_expr_in_brackets(tokens, i, 0, diagnostics, ctx.md_default()) {
858                last_arg_was_comment_only = arg.end == arg.start + 1
859                    && matches!(
860                        tokens.get(arg.start).map(|t| &t.kind),
861                        Some(TokKind::Comment)
862                    );
863                for idx in i..arg.start {
864                    events.push(Event::Tok(idx));
865                }
866                events.extend(arg.events);
867                i = arg.end;
868            } else {
869                last_arg_was_comment_only = false;
870                events.push(Event::Tok(i));
871                i += 1;
872            }
873        }
874
875        events.push(Event::Finish); // ARG
876        expect_delimiter = true;
877
878        // Skip whitespace/newlines after arg, then consume optional comma.
879        let next_i = ctx.skip_ws_and_newlines(i);
880        for idx in i..next_i {
881            events.push(Event::Tok(idx));
882        }
883        i = next_i;
884
885        if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::Comma)) {
886            events.push(Event::Tok(i)); // ,
887            i += 1;
888            expect_delimiter = false;
889            last_arg_was_comment_only = false;
890        }
891    }
892
893    events.push(Event::Finish); // ARG_LIST
894
895    // Closing paren (may have trailing whitespace/newlines before it).
896    let next_i = ctx.skip_ws_and_newlines(i);
897    for idx in i..next_i {
898        events.push(Event::Tok(idx));
899    }
900    i = next_i;
901
902    if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::RParen)) {
903        events.push(Event::Tok(i));
904        i += 1;
905    } else if let Some(tok) = tokens.get(lparen_idx) {
906        push_token_diagnostic(diagnostics, "expected ')' to close function call", tok);
907    }
908
909    events.push(Event::Finish); // CALL_EXPR
910
911    ExprParse {
912        start: callee.start,
913        end: i,
914        events,
915    }
916}
917
918fn parse_subset_expr(
919    ctx: &ParserCtx<'_>,
920    target: ExprParse,
921    lbrack_idx: usize,
922    diagnostics: &mut Vec<ParseDiagnostic>,
923) -> ExprParse {
924    parse_bracket_expr(
925        ctx,
926        target,
927        lbrack_idx,
928        SyntaxKind::SUBSET_EXPR,
929        diagnostics,
930    )
931}
932
933fn parse_subset2_expr(
934    ctx: &ParserCtx<'_>,
935    target: ExprParse,
936    lbrack2_idx: usize,
937    diagnostics: &mut Vec<ParseDiagnostic>,
938) -> ExprParse {
939    parse_bracket_expr(
940        ctx,
941        target,
942        lbrack2_idx,
943        SyntaxKind::SUBSET2_EXPR,
944        diagnostics,
945    )
946}
947
948fn parse_bracket_expr(
949    ctx: &ParserCtx<'_>,
950    target: ExprParse,
951    open_idx: usize,
952    node_kind: SyntaxKind,
953    diagnostics: &mut Vec<ParseDiagnostic>,
954) -> ExprParse {
955    let tokens = ctx.tokens();
956    let mut events = vec![Event::Start(node_kind)];
957    events.extend(target.events);
958    for idx in target.end..open_idx {
959        events.push(Event::Tok(idx));
960    }
961    events.push(Event::Tok(open_idx));
962
963    events.push(Event::Start(SyntaxKind::ARG_LIST));
964    let mut i = open_idx + 1;
965
966    let mut expect_delimiter = false;
967    let mut last_arg_was_comment_only = false;
968    loop {
969        let next_i = ctx.skip_ws_and_newlines(i);
970        let had_newline_gap = has_newline(tokens, i, next_i);
971        for idx in i..next_i {
972            events.push(Event::Tok(idx));
973        }
974        i = next_i;
975
976        if is_matching_subset_close(tokens, i, node_kind) || tokens.get(i).is_none() {
977            break;
978        }
979
980        if expect_delimiter
981            && !had_newline_gap
982            && let Some(tok) = tokens.get(i)
983        {
984            let current_is_comment = tok.kind == TokKind::Comment;
985            if !current_is_comment && !last_arg_was_comment_only {
986                push_token_diagnostic(diagnostics, "expected ',' between subset arguments", tok);
987            }
988        }
989
990        if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::Comma)) {
991            events.push(Event::Start(SyntaxKind::ARG));
992            events.push(Event::Finish);
993            events.push(Event::Tok(i));
994            i += 1;
995            expect_delimiter = false;
996            last_arg_was_comment_only = false;
997            continue;
998        }
999
1000        events.push(Event::Start(SyntaxKind::ARG));
1001        last_arg_was_comment_only = false;
1002
1003        if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::RoxygenMarker)) {
1004            // See `parse_call_expr`: a `#'` line inside `[`/`[[` is a comment,
1005            // grouped into a single comment-only ARG.
1006            i = crate::parser::roxygen::emit_roxygen_block(
1007                tokens,
1008                i,
1009                &mut events,
1010                ctx.md_default(),
1011            );
1012            last_arg_was_comment_only = true;
1013        } else if is_named_arg(ctx, i) {
1014            // Named subscript argument: `ident = expr` (e.g. `drop = FALSE`).
1015            // `=` inside `[`/`[[` is named-argument syntax, never an assignment,
1016            // so this parses flat (like a call argument), not as an
1017            // ASSIGNMENT_EXPR.
1018            events.push(Event::Tok(i)); // ident
1019            let mut eq_idx = i + 1;
1020            while matches!(
1021                tokens.get(eq_idx).map(|t| &t.kind),
1022                Some(TokKind::Whitespace | TokKind::Newline | TokKind::Comment)
1023            ) {
1024                eq_idx += 1;
1025            }
1026            for idx in (i + 1)..eq_idx {
1027                events.push(Event::Tok(idx));
1028            }
1029            // `is_named_arg` guarantees an `=` at `eq_idx`.
1030            events.push(Event::Tok(eq_idx)); // =
1031            let val_start = eq_idx + 1;
1032            let mut value_idx = val_start;
1033            while matches!(
1034                tokens.get(value_idx).map(|t| &t.kind),
1035                Some(TokKind::Whitespace | TokKind::Newline | TokKind::Comment)
1036            ) {
1037                value_idx += 1;
1038            }
1039            if matches!(
1040                tokens.get(value_idx).map(|t| &t.kind),
1041                Some(TokKind::Comma) | None
1042            ) || is_matching_subset_close(tokens, value_idx, node_kind)
1043            {
1044                // Empty value, e.g. `x[drop = ]`.
1045                for idx in val_start..value_idx {
1046                    events.push(Event::Tok(idx));
1047                }
1048                i = value_idx;
1049            } else if let Some(val) =
1050                parse_expr_in_brackets(tokens, value_idx, 0, diagnostics, ctx.md_default())
1051            {
1052                for idx in val_start..val.start {
1053                    events.push(Event::Tok(idx));
1054                }
1055                events.extend(val.events);
1056                i = val.end;
1057            } else {
1058                i = val_start;
1059            }
1060        } else if let Some(arg) =
1061            parse_expr_in_brackets(tokens, i, 0, diagnostics, ctx.md_default())
1062        {
1063            last_arg_was_comment_only = arg.end == arg.start + 1
1064                && matches!(
1065                    tokens.get(arg.start).map(|t| &t.kind),
1066                    Some(TokKind::Comment)
1067                );
1068            for idx in i..arg.start {
1069                events.push(Event::Tok(idx));
1070            }
1071            events.extend(arg.events);
1072            i = arg.end;
1073        } else {
1074            last_arg_was_comment_only = false;
1075            events.push(Event::Tok(i));
1076            i += 1;
1077        }
1078        events.push(Event::Finish);
1079        expect_delimiter = true;
1080
1081        let next_i = ctx.skip_ws_and_newlines(i);
1082        for idx in i..next_i {
1083            events.push(Event::Tok(idx));
1084        }
1085        i = next_i;
1086        if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::Comma)) {
1087            events.push(Event::Tok(i));
1088            i += 1;
1089            expect_delimiter = false;
1090            last_arg_was_comment_only = false;
1091        }
1092    }
1093
1094    events.push(Event::Finish); // ARG_LIST
1095    let next_i = ctx.skip_ws_and_newlines(i);
1096    for idx in i..next_i {
1097        events.push(Event::Tok(idx));
1098    }
1099    i = next_i;
1100
1101    if is_matching_subset_close(tokens, i, node_kind) {
1102        match node_kind {
1103            SyntaxKind::SUBSET_EXPR => {
1104                if matches!(tokens.get(i).map(|t| &t.kind), Some(TokKind::RBrack2)) {
1105                    events.push(Event::Tok(i)); // consume one ] out of ]]
1106                    i += 1;
1107                } else {
1108                    events.push(Event::Tok(i));
1109                    i += 1;
1110                }
1111            }
1112            SyntaxKind::SUBSET2_EXPR => {
1113                events.push(Event::Tok(i));
1114                i += 1;
1115            }
1116            _ => {}
1117        }
1118    } else if node_kind == SyntaxKind::SUBSET_EXPR
1119        && matches!(previous_non_trivia_kind(tokens, i), Some(TokKind::RBrack2))
1120    {
1121        // `]]` may have been consumed as a single token by an inner subset node.
1122        // In that case, treat the outer subset close as satisfied.
1123    } else if let Some(tok) = tokens.get(open_idx) {
1124        push_token_diagnostic(
1125            diagnostics,
1126            "expected closing bracket in subset expression",
1127            tok,
1128        );
1129    }
1130
1131    events.push(Event::Finish);
1132    ExprParse {
1133        start: target.start,
1134        end: i,
1135        events,
1136    }
1137}
1138
1139fn is_matching_subset_close(tokens: &[Token], idx: usize, node_kind: SyntaxKind) -> bool {
1140    match node_kind {
1141        SyntaxKind::SUBSET_EXPR => matches!(
1142            tokens.get(idx).map(|t| &t.kind),
1143            Some(TokKind::RBrack | TokKind::RBrack2)
1144        ),
1145        SyntaxKind::SUBSET2_EXPR => {
1146            matches!(tokens.get(idx).map(|t| &t.kind), Some(TokKind::RBrack2))
1147        }
1148        _ => false,
1149    }
1150}
1151
1152fn previous_non_trivia_kind(tokens: &[Token], mut idx: usize) -> Option<TokKind> {
1153    if idx == 0 {
1154        return None;
1155    }
1156    idx -= 1;
1157    loop {
1158        let tok = tokens.get(idx)?;
1159        if !matches!(tok.kind, TokKind::Whitespace | TokKind::Newline) {
1160            return Some(tok.kind.clone());
1161        }
1162        if idx == 0 {
1163            return None;
1164        }
1165        idx -= 1;
1166    }
1167}
1168
1169/// The extract/namespace operators (`$`, `@`, `::`, `:::`). They tie with the
1170/// postfix operators (`[`, `[[`, `(`) for the highest precedence and are
1171/// left-associative, so their RHS is parsed as a bare member (see the infix
1172/// loop) to keep `a$b[c]` == `(a$b)[c]`.
1173fn is_extract_operator(kind: &TokKind) -> bool {
1174    matches!(
1175        kind,
1176        TokKind::Dollar | TokKind::At | TokKind::Colon2 | TokKind::Colon3
1177    )
1178}
1179
1180fn infix_binding_power(kind: &TokKind) -> Option<(u8, u8)> {
1181    // Binding powers are aligned to AIR's operator precedence tiers:
1182    // LogicalOr (5), LogicalAnd (6), Relational (8), Additive (9),
1183    // Multiplicative (10), Special (11), Colon (12), Tilde (4), Exponential (14).
1184    // Help (`?`) sits below assignment as in R, so `x <- 1 ? 2` parses as
1185    // `(x <- 1) ? 2`. Namespace/extract operators (`::`, `:::`, `$`, `@`) bind
1186    // tighter than exponentiation and are treated as left-associative.
1187    match kind {
1188        TokKind::Question => Some((0, 1)),
1189        TokKind::Or | TokKind::Or2 => Some((50, 51)),
1190        TokKind::And | TokKind::And2 => Some((60, 61)),
1191        TokKind::Equal2
1192        | TokKind::NotEqual
1193        | TokKind::LessThan
1194        | TokKind::LessThanOrEqual
1195        | TokKind::GreaterThan
1196        | TokKind::GreaterThanOrEqual => Some((80, 81)),
1197        TokKind::Plus | TokKind::Minus => Some((90, 91)),
1198        TokKind::Star | TokKind::Slash => Some((100, 101)),
1199        TokKind::Pipe | TokKind::UserOp => Some((110, 111)),
1200        TokKind::Colon => Some((120, 121)),
1201        TokKind::Tilde => Some((40, 41)),
1202        TokKind::Caret => Some((140, 140)),
1203        TokKind::Colon2 | TokKind::Colon3 | TokKind::Dollar | TokKind::At => Some((150, 151)),
1204        _ => None,
1205    }
1206}