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