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