Skip to main content

arity_parser/ast/
nodes.rs

1use rowan::ast::support;
2use rowan::{SyntaxElement, SyntaxToken, TextRange, TextSize};
3use smol_str::SmolStr;
4
5use crate::ast::AstNode;
6use crate::ast::kinds::{is_binary_operator, is_trivia, is_unary_operator};
7use crate::ast::tokens::{AstToken, StringLit, token_name};
8use crate::syntax::{RLanguage, SyntaxKind, SyntaxNode};
9
10macro_rules! ast_node {
11    ($name:ident, $kind:expr) => {
12        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
13        pub struct $name(SyntaxNode);
14
15        impl AstNode for $name {
16            type Language = RLanguage;
17
18            fn can_cast(kind: SyntaxKind) -> bool {
19                kind == $kind
20            }
21
22            fn cast(syntax: SyntaxNode) -> Option<Self> {
23                Self::can_cast(syntax.kind()).then(|| Self(syntax))
24            }
25
26            fn syntax(&self) -> &SyntaxNode {
27                &self.0
28            }
29        }
30    };
31}
32
33ast_node!(Root, SyntaxKind::ROOT);
34ast_node!(AssignmentExpr, SyntaxKind::ASSIGNMENT_EXPR);
35ast_node!(BinaryExpr, SyntaxKind::BINARY_EXPR);
36ast_node!(UnaryExpr, SyntaxKind::UNARY_EXPR);
37ast_node!(ParenExpr, SyntaxKind::PAREN_EXPR);
38ast_node!(CallExpr, SyntaxKind::CALL_EXPR);
39ast_node!(SubsetExpr, SyntaxKind::SUBSET_EXPR);
40ast_node!(Subset2Expr, SyntaxKind::SUBSET2_EXPR);
41ast_node!(ArgList, SyntaxKind::ARG_LIST);
42ast_node!(Arg, SyntaxKind::ARG);
43ast_node!(IfExpr, SyntaxKind::IF_EXPR);
44ast_node!(ForExpr, SyntaxKind::FOR_EXPR);
45ast_node!(WhileExpr, SyntaxKind::WHILE_EXPR);
46ast_node!(RepeatExpr, SyntaxKind::REPEAT_EXPR);
47ast_node!(FunctionExpr, SyntaxKind::FUNCTION_EXPR);
48ast_node!(BlockExpr, SyntaxKind::BLOCK_EXPR);
49ast_node!(RoxygenBlock, SyntaxKind::ROXYGEN_BLOCK);
50ast_node!(RoxygenSection, SyntaxKind::ROXYGEN_SECTION);
51ast_node!(RoxygenParagraph, SyntaxKind::ROXYGEN_PARAGRAPH);
52ast_node!(RoxygenTag, SyntaxKind::ROXYGEN_TAG);
53
54/// The first direct child token of `node` with the given `kind`.
55fn first_child_token(node: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken<RLanguage>> {
56    node.children_with_tokens()
57        .find_map(|element| match element {
58            SyntaxElement::Token(token) if token.kind() == kind => Some(token),
59            _ => None,
60        })
61}
62
63impl RoxygenBlock {
64    /// The logical sections of this block, in source order: the intro section
65    /// (untagged leading prose) and one section per `@tag`.
66    pub fn sections(&self) -> impl Iterator<Item = RoxygenSection> + '_ {
67        self.0.children().filter_map(RoxygenSection::cast)
68    }
69
70    /// The tags of this block, in source order (`@param`, `@export`, …).
71    pub fn tags(&self) -> impl Iterator<Item = RoxygenTag> + '_ {
72        self.sections().filter_map(|section| section.tag())
73    }
74
75    /// Whether any tag of this block is named `name` (without the leading `@`).
76    pub fn has_tag(&self, name: &str) -> bool {
77        self.tags().any(|tag| tag.name().as_deref() == Some(name))
78    }
79
80    /// The intro section — the untagged leading prose whose first paragraph is
81    /// the topic title. `None` when the block starts with a tag.
82    pub fn intro(&self) -> Option<RoxygenSection> {
83        self.sections().find(|section| section.tag().is_none())
84    }
85}
86
87impl RoxygenSection {
88    /// The tag heading this section, if it is a tag section (`@details`, …); the
89    /// intro section has none.
90    pub fn tag(&self) -> Option<RoxygenTag> {
91        self.0.children().find_map(RoxygenTag::cast)
92    }
93
94    /// The prose paragraphs of this section, in source order.
95    pub fn paragraphs(&self) -> impl Iterator<Item = RoxygenParagraph> + '_ {
96        self.0.children().filter_map(RoxygenParagraph::cast)
97    }
98
99    /// Whether the section carries any prose beyond the tag head: text folded
100    /// onto the tag line (`@param x A number.`) or a continuation paragraph.
101    pub fn has_prose(&self) -> bool {
102        self.tag().is_some_and(|tag| tag.text().is_some()) || self.paragraphs().next().is_some()
103    }
104}
105
106impl RoxygenTag {
107    /// The `@` token.
108    pub fn at(&self) -> Option<SyntaxToken<RLanguage>> {
109        first_child_token(&self.0, SyntaxKind::ROXYGEN_AT)
110    }
111
112    /// The tag name (e.g. `param`, `examples`), without the leading `@`.
113    pub fn name(&self) -> Option<SmolStr> {
114        first_child_token(&self.0, SyntaxKind::ROXYGEN_TAG_NAME).map(|t| SmolStr::new(t.text()))
115    }
116
117    /// The name argument for arg-bearing tags (e.g. `x` in `@param x ...`).
118    pub fn arg(&self) -> Option<SyntaxToken<RLanguage>> {
119        first_child_token(&self.0, SyntaxKind::ROXYGEN_TAG_ARG)
120    }
121
122    /// The remaining prose content of the tag, if any.
123    pub fn text(&self) -> Option<SyntaxToken<RLanguage>> {
124        first_child_token(&self.0, SyntaxKind::ROXYGEN_TEXT)
125    }
126
127    /// Whether this is an `@examples`/`@examplesIf` tag (embedded R code).
128    pub fn is_examples(&self) -> bool {
129        matches!(self.name().as_deref(), Some("examples" | "examplesIf"))
130    }
131
132    /// The comma-separated names in the tag's arg (`@param a,b` documents both
133    /// `a` and `b`), each with the sub-range of its own text inside the arg
134    /// token. Empty for a tag with no arg.
135    pub fn arg_names(&self) -> Vec<(SmolStr, TextRange)> {
136        let Some(arg) = self.arg() else {
137            return Vec::new();
138        };
139        let arg_start = arg.text_range().start();
140        let mut names = Vec::new();
141        let mut offset = 0usize;
142        for piece in arg.text().split(',') {
143            let trimmed = piece.trim();
144            if !trimmed.is_empty() {
145                let lead = piece.len() - piece.trim_start().len();
146                let start = arg_start + TextSize::from((offset + lead) as u32);
147                names.push((
148                    SmolStr::new(trimmed),
149                    TextRange::at(start, TextSize::of(trimmed)),
150                ));
151            }
152            offset += piece.len() + 1;
153        }
154        names
155    }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct ForExprParts {
160    pub leading_comments: Vec<SyntaxToken<RLanguage>>,
161    pub variable_elements: Vec<SyntaxElement<RLanguage>>,
162    pub sequence_elements: Vec<SyntaxElement<RLanguage>>,
163    pub post_clause_comments: Vec<SyntaxToken<RLanguage>>,
164    pub body: Option<SyntaxElement<RLanguage>>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct WhileExprParts {
169    pub leading_comments: Vec<SyntaxToken<RLanguage>>,
170    pub condition_elements: Vec<SyntaxElement<RLanguage>>,
171    pub post_clause_comments: Vec<SyntaxToken<RLanguage>>,
172    pub body: Option<SyntaxElement<RLanguage>>,
173}
174
175impl Root {
176    pub fn expressions(&self) -> impl Iterator<Item = SyntaxNode> {
177        self.syntax().children()
178    }
179}
180
181impl AssignmentExpr {
182    /// The assignment operator kind. Returns one of `ASSIGN_LEFT` (`<-`),
183    /// `ASSIGN_RIGHT` (`->`), `SUPER_ASSIGN` (`<<-`), `SUPER_ASSIGN_RIGHT`
184    /// (`->>`), `ASSIGN_EQ` (`=`), or `WALRUS` (`:=`).
185    pub fn op_kind(&self) -> Option<SyntaxKind> {
186        self.syntax()
187            .children_with_tokens()
188            .find_map(|element| match element {
189                SyntaxElement::Token(token) if is_assignment_op(token.kind()) => Some(token.kind()),
190                _ => None,
191            })
192    }
193
194    /// The operator token, if present.
195    pub fn op_token(&self) -> Option<SyntaxToken<RLanguage>> {
196        self.syntax()
197            .children_with_tokens()
198            .find_map(|element| match element {
199                SyntaxElement::Token(token) if is_assignment_op(token.kind()) => Some(token),
200                _ => None,
201            })
202    }
203
204    /// The element on the *target* side of the operator (the side that becomes
205    /// the binding). For `<-`/`=`/`<<-`/`:=` this is the left element; for
206    /// `->`/`->>` it is the right element.
207    pub fn target_element(&self) -> Option<SyntaxElement<RLanguage>> {
208        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
209        let op_idx = assignment_op_index(&elements)?;
210        let kind = element_kind(&elements[op_idx])?;
211        let (start, end) = if is_right_assign(kind) {
212            (op_idx + 1, elements.len())
213        } else {
214            (0, op_idx)
215        };
216        elements[start..end]
217            .iter()
218            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
219            .cloned()
220    }
221
222    /// The element on the *value* side of the operator.
223    pub fn value_element(&self) -> Option<SyntaxElement<RLanguage>> {
224        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
225        let op_idx = assignment_op_index(&elements)?;
226        let kind = element_kind(&elements[op_idx])?;
227        let (start, end) = if is_right_assign(kind) {
228            (0, op_idx)
229        } else {
230            (op_idx + 1, elements.len())
231        };
232        elements[start..end]
233            .iter()
234            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
235            .cloned()
236    }
237
238    /// If the target is a simple `IDENT` (or backtick-quoted identifier), the
239    /// bound name as a `SmolStr`. Returns `None` for complex LHS like
240    /// `dim(x) <- ...`, `x[1] <- ...`, `x$y <- ...`.
241    pub fn target_name(&self) -> Option<SmolStr> {
242        match self.target_element()? {
243            SyntaxElement::Token(token) if token.kind() == SyntaxKind::IDENT => {
244                Some(SmolStr::new(token.text()))
245            }
246            SyntaxElement::Token(token) if token.kind() == SyntaxKind::STRING => {
247                StringLit::cast(token).and_then(|s| s.unquote().map(SmolStr::new))
248            }
249            _ => None,
250        }
251    }
252
253    /// The target name token, useful when the binding site's range is needed.
254    pub fn target_name_token(&self) -> Option<SyntaxToken<RLanguage>> {
255        match self.target_element()? {
256            SyntaxElement::Token(token)
257                if token.kind() == SyntaxKind::IDENT || token.kind() == SyntaxKind::STRING =>
258            {
259                Some(token)
260            }
261            _ => None,
262        }
263    }
264}
265
266impl FunctionExpr {
267    pub fn lparen_index(&self) -> Option<usize> {
268        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
269        elements.iter().position(|e| e.kind() == SyntaxKind::LPAREN)
270    }
271
272    pub fn rparen_index(&self) -> Option<usize> {
273        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
274        let lparen_idx = self.lparen_index()?;
275        let mut depth = 0usize;
276        elements
277            .iter()
278            .enumerate()
279            .skip(lparen_idx)
280            .find_map(|(i, el)| match el.kind() {
281                SyntaxKind::LPAREN => {
282                    depth += 1;
283                    None
284                }
285                SyntaxKind::RPAREN => {
286                    depth = depth.saturating_sub(1);
287                    if depth == 0 { Some(i) } else { None }
288                }
289                _ => None,
290            })
291    }
292
293    /// Iterate the parameters of the function. Each entry yields the parameter
294    /// name and the range of its name token. Default-value tokens are skipped
295    /// (function param defaults are raw tokens in the param list, not nodes).
296    pub fn params(&self) -> Vec<Param> {
297        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
298        let Some(lparen_idx) = self.lparen_index() else {
299            return Vec::new();
300        };
301        let Some(rparen_idx) = self.rparen_index() else {
302            return Vec::new();
303        };
304        let mut params = Vec::new();
305        let mut depth = 0usize;
306        let mut at_param_start = true;
307        let mut i = lparen_idx + 1;
308        while i < rparen_idx {
309            let element = &elements[i];
310            match element.kind() {
311                SyntaxKind::LPAREN
312                | SyntaxKind::LBRACK
313                | SyntaxKind::LBRACK2
314                | SyntaxKind::LBRACE => {
315                    depth += 1;
316                    at_param_start = false;
317                }
318                SyntaxKind::RPAREN
319                | SyntaxKind::RBRACK
320                | SyntaxKind::RBRACK2
321                | SyntaxKind::RBRACE => {
322                    depth = depth.saturating_sub(1);
323                    at_param_start = false;
324                }
325                SyntaxKind::COMMA if depth == 0 => {
326                    at_param_start = true;
327                }
328                SyntaxKind::IDENT if depth == 0 && at_param_start => {
329                    if let SyntaxElement::Token(token) = element {
330                        params.push(Param {
331                            name: SmolStr::new(token.text()),
332                            name_token: token.clone(),
333                        });
334                    }
335                    at_param_start = false;
336                }
337                SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE | SyntaxKind::COMMENT => {}
338                _ => {
339                    at_param_start = false;
340                }
341            }
342            i += 1;
343        }
344        params
345    }
346
347    /// The function body — the expression that follows the `)`. May be any
348    /// expression node (a `BLOCK_EXPR` for `function(x) { ... }`, or any other
349    /// expression for `function(x) x + 1`). Returns `None` if the body was
350    /// missing or replaced by an error-recovery placeholder.
351    pub fn body(&self) -> Option<SyntaxElement<RLanguage>> {
352        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
353        let rparen_idx = self.rparen_index()?;
354        elements[rparen_idx + 1..]
355            .iter()
356            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
357            .cloned()
358    }
359}
360
361#[derive(Debug, Clone)]
362pub struct Param {
363    pub name: SmolStr,
364    pub name_token: SyntaxToken<RLanguage>,
365}
366
367fn is_assignment_op(kind: SyntaxKind) -> bool {
368    matches!(
369        kind,
370        SyntaxKind::ASSIGN_LEFT
371            | SyntaxKind::ASSIGN_RIGHT
372            | SyntaxKind::SUPER_ASSIGN
373            | SyntaxKind::SUPER_ASSIGN_RIGHT
374            | SyntaxKind::ASSIGN_EQ
375            | SyntaxKind::WALRUS
376    )
377}
378
379fn is_right_assign(kind: SyntaxKind) -> bool {
380    matches!(
381        kind,
382        SyntaxKind::ASSIGN_RIGHT | SyntaxKind::SUPER_ASSIGN_RIGHT
383    )
384}
385
386fn assignment_op_index(elements: &[SyntaxElement<RLanguage>]) -> Option<usize> {
387    elements
388        .iter()
389        .position(|e| matches!(e, SyntaxElement::Token(t) if is_assignment_op(t.kind())))
390}
391
392fn element_kind(element: &SyntaxElement<RLanguage>) -> Option<SyntaxKind> {
393    Some(element.kind())
394}
395
396/// The RHS member `IDENT` of a `pkg::name` / `pkg:::name` `BINARY_EXPR`, if that
397/// is its shape. `None` for any other binary expression (a different operator,
398/// or a non-`IDENT` member).
399fn namespace_member_token(node: &SyntaxNode) -> Option<SyntaxToken<RLanguage>> {
400    let mut seen_ns_op = false;
401    for element in node.children_with_tokens() {
402        match element {
403            SyntaxElement::Token(t)
404                if matches!(t.kind(), SyntaxKind::COLON2 | SyntaxKind::COLON3) =>
405            {
406                seen_ns_op = true;
407            }
408            SyntaxElement::Token(t) if seen_ns_op && t.kind() == SyntaxKind::IDENT => {
409                return Some(t);
410            }
411            _ => {}
412        }
413    }
414    None
415}
416
417impl CallExpr {
418    pub fn arg_list(&self) -> Option<ArgList> {
419        support::child(self.syntax())
420    }
421
422    /// The callee name token — the `IDENT`/`STRING` immediately preceding the
423    /// argument list, when the function being called is a simple name (`foo(…)`,
424    /// `` `+`(…) ``) or a namespace-qualified name (`pkg::fn(…)`, where the token
425    /// is the `fn` member). Returns `None` for a genuinely computed callee
426    /// (`(g())(…)`, `x$f(…)`).
427    pub fn callee_token(&self) -> Option<SyntaxToken<RLanguage>> {
428        for element in self.syntax().children_with_tokens() {
429            match element {
430                SyntaxElement::Token(token)
431                    if is_trivia(token.kind()) || token.kind() == SyntaxKind::COMMENT =>
432                {
433                    continue;
434                }
435                SyntaxElement::Token(token)
436                    if matches!(token.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
437                {
438                    return Some(token);
439                }
440                // `pkg::fn(…)` / `pkg:::fn(…)` parses with the whole call wrapping
441                // a `pkg::fn` `BINARY_EXPR` callee. The callee *name* is that
442                // binary's RHS member, so the qualified form resolves like the
443                // bare `fn(…)` for every consumer (linter matchers, signature
444                // help, call hierarchy). Other binary callees (`obj$m(…)`) stay
445                // computed.
446                SyntaxElement::Node(node) if node.kind() == SyntaxKind::BINARY_EXPR => {
447                    return namespace_member_token(&node);
448                }
449                _ => return None,
450            }
451        }
452        None
453    }
454
455    /// The callee name (`foo` in `foo(…)`, `` + `` in `` `+`(…) ``), unquoted,
456    /// when the callee is a simple name. `None` for a computed callee.
457    pub fn callee_name(&self) -> Option<SmolStr> {
458        self.callee_token().as_ref().map(token_name)
459    }
460
461    /// The callee element — everything before the argument list's `(`. Generalizes
462    /// [`CallExpr::callee_token`] to the computed-callee case (`(g())(…)`,
463    /// `x$f(…)`), where it returns the callee *node* instead of a name token.
464    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
465        last_element_before(self.syntax(), SyntaxKind::LPAREN)
466    }
467}
468
469impl SubsetExpr {
470    /// The base being indexed — everything before the `[`.
471    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
472        last_element_before(self.syntax(), SyntaxKind::LBRACK)
473    }
474
475    pub fn open_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
476        first_child_token(self.syntax(), SyntaxKind::LBRACK)
477    }
478
479    pub fn close_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
480        first_child_token(self.syntax(), SyntaxKind::RBRACK)
481    }
482
483    pub fn arg_list(&self) -> Option<ArgList> {
484        support::child(self.syntax())
485    }
486
487    pub fn args(&self) -> impl Iterator<Item = Arg> {
488        self.arg_list()
489            .map(|list| list.args().collect::<Vec<_>>())
490            .unwrap_or_default()
491            .into_iter()
492    }
493}
494
495impl Subset2Expr {
496    /// The base being indexed — everything before the `[[`.
497    pub fn base(&self) -> Option<SyntaxElement<RLanguage>> {
498        last_element_before(self.syntax(), SyntaxKind::LBRACK2)
499    }
500
501    pub fn open_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
502        first_child_token(self.syntax(), SyntaxKind::LBRACK2)
503    }
504
505    pub fn close_bracket(&self) -> Option<SyntaxToken<RLanguage>> {
506        first_child_token(self.syntax(), SyntaxKind::RBRACK2)
507    }
508
509    pub fn arg_list(&self) -> Option<ArgList> {
510        support::child(self.syntax())
511    }
512
513    pub fn args(&self) -> impl Iterator<Item = Arg> {
514        self.arg_list()
515            .map(|list| list.args().collect::<Vec<_>>())
516            .unwrap_or_default()
517            .into_iter()
518    }
519}
520
521/// The last non-trivia, non-comment element before the first direct-child token
522/// of `boundary` kind (e.g. the callee before `(`, the base before `[`).
523fn last_element_before(
524    node: &SyntaxNode,
525    boundary: SyntaxKind,
526) -> Option<SyntaxElement<RLanguage>> {
527    let elements: Vec<_> = node.children_with_tokens().collect();
528    let idx = elements.iter().position(|e| e.kind() == boundary)?;
529    elements[..idx]
530        .iter()
531        .rev()
532        .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
533        .cloned()
534}
535
536/// A resolved namespace access (`pkg::name` / `pkg:::name`), as extracted from a
537/// [`BinaryExpr`].
538#[derive(Debug, Clone, PartialEq, Eq)]
539pub struct NamespaceAccess {
540    pub package: SmolStr,
541    pub package_token: SyntaxToken<RLanguage>,
542    /// `true` for the internal `:::` operator, `false` for `::`.
543    pub internal: bool,
544    pub name: SmolStr,
545    pub name_token: SyntaxToken<RLanguage>,
546}
547
548impl BinaryExpr {
549    /// If this binary expression is a namespace access — `pkg::name`,
550    /// `pkg:::name`, or the call form `pkg::name(args)` — the resolved package
551    /// and accessed name with their tokens. The package and name may be written
552    /// as backtick/quoted strings, which are unquoted.
553    pub fn namespace_access(&self) -> Option<NamespaceAccess> {
554        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
555        let (op_idx, internal) = elements
556            .iter()
557            .enumerate()
558            .find_map(|(i, e)| match e.kind() {
559                SyntaxKind::COLON2 => Some((i, false)),
560                SyntaxKind::COLON3 => Some((i, true)),
561                _ => None,
562            })?;
563        // Package: the last name token before the operator.
564        let package_token = elements[..op_idx].iter().rev().find_map(|e| match e {
565            SyntaxElement::Token(t)
566                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
567            {
568                Some(t.clone())
569            }
570            _ => None,
571        })?;
572        // Name: the first non-trivia element after the operator — either a bare
573        // name token or the callee of a `pkg::name(args)` call.
574        let rhs = elements[op_idx + 1..]
575            .iter()
576            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?;
577        let name_token = match rhs {
578            SyntaxElement::Token(t)
579                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
580            {
581                t.clone()
582            }
583            SyntaxElement::Node(n) if n.kind() == SyntaxKind::CALL_EXPR => {
584                CallExpr::cast(n.clone())?.callee_token()?
585            }
586            _ => return None,
587        };
588        Some(NamespaceAccess {
589            package: token_name(&package_token),
590            package_token,
591            internal,
592            name: token_name(&name_token),
593            name_token,
594        })
595    }
596}
597
598impl ArgList {
599    pub fn args(&self) -> impl Iterator<Item = Arg> {
600        support::children(self.syntax())
601    }
602}
603
604impl IfExpr {
605    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
606        self.syntax().children_with_tokens().collect()
607    }
608
609    pub fn if_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
610        self.elements()
611            .into_iter()
612            .find_map(|element| match element {
613                SyntaxElement::Token(token) if token.kind() == SyntaxKind::IF_KW => Some(token),
614                _ => None,
615            })
616    }
617
618    pub fn condition_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
619        let elements = self.elements();
620        let lparen_idx = self.lparen_index()?;
621        let rparen_idx = self.rparen_index()?;
622        Some(elements[lparen_idx + 1..rparen_idx].to_vec())
623    }
624
625    pub fn then_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
626        let elements = self.elements();
627        let rparen_idx = self.rparen_index()?;
628        let else_idx = find_token_after_index(&elements, rparen_idx, SyntaxKind::ELSE_KW);
629        let then_end = else_idx.unwrap_or(elements.len());
630        Some(elements[rparen_idx + 1..then_end].to_vec())
631    }
632
633    pub fn else_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
634        self.elements()
635            .into_iter()
636            .find_map(|element| match element {
637                SyntaxElement::Token(token) if token.kind() == SyntaxKind::ELSE_KW => Some(token),
638                _ => None,
639            })
640    }
641
642    pub fn else_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
643        let elements = self.elements();
644        let else_idx = find_token_index(&elements, SyntaxKind::ELSE_KW)?;
645        Some(elements[else_idx + 1..].to_vec())
646    }
647
648    pub fn lparen_index(&self) -> Option<usize> {
649        find_token_index(&self.elements(), SyntaxKind::LPAREN)
650    }
651
652    pub fn rparen_index(&self) -> Option<usize> {
653        let elements = self.elements();
654        let lparen_idx = self.lparen_index()?;
655        find_token_after_index(&elements, lparen_idx, SyntaxKind::RPAREN)
656    }
657}
658
659impl ForExpr {
660    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
661        self.syntax().children_with_tokens().collect()
662    }
663
664    pub fn for_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
665        self.elements()
666            .into_iter()
667            .find_map(|element| match element {
668                SyntaxElement::Token(token) if token.kind() == SyntaxKind::FOR_KW => Some(token),
669                _ => None,
670            })
671    }
672
673    pub fn clause_bounds(&self) -> Option<(usize, usize)> {
674        let elements = self.elements();
675        let lparen_idx = self.lparen_index()?;
676
677        let mut depth = 0usize;
678        let rparen_idx = elements.iter().enumerate().skip(lparen_idx).find_map(
679            |(idx, element)| match element {
680                SyntaxElement::Token(token) if token.kind() == SyntaxKind::LPAREN => {
681                    depth += 1;
682                    None
683                }
684                SyntaxElement::Token(token) if token.kind() == SyntaxKind::RPAREN => {
685                    depth = depth.saturating_sub(1);
686                    if depth == 0 { Some(idx) } else { None }
687                }
688                _ => None,
689            },
690        )?;
691
692        Some((lparen_idx, rparen_idx))
693    }
694
695    pub fn lparen_index(&self) -> Option<usize> {
696        let elements = self.elements();
697        let for_idx = find_token_index(&elements, SyntaxKind::FOR_KW)?;
698        find_token_after_index(&elements, for_idx, SyntaxKind::LPAREN)
699    }
700
701    pub fn leading_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
702        let elements = self.elements();
703        let for_idx = find_token_index(&elements, SyntaxKind::FOR_KW)?;
704        let (_, rparen_idx) = self.clause_bounds()?;
705        Some(
706            elements[for_idx + 1..rparen_idx]
707                .iter()
708                .filter_map(|element| match element {
709                    SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT => {
710                        Some(token.clone())
711                    }
712                    _ => None,
713                })
714                .collect(),
715        )
716    }
717
718    pub fn clause_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
719        let elements = self.elements();
720        let (lparen_idx, rparen_idx) = self.clause_bounds()?;
721        Some(
722            elements[lparen_idx + 1..rparen_idx]
723                .iter()
724                .filter(|element| {
725                    !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT
726                })
727                .cloned()
728                .collect(),
729        )
730    }
731
732    pub fn post_clause_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
733        let elements = self.elements();
734        let (_, rparen_idx) = self.clause_bounds()?;
735        let mut comments = Vec::new();
736        for element in &elements[rparen_idx + 1..] {
737            if is_trivia(element.kind()) {
738                continue;
739            }
740            if let SyntaxElement::Token(token) = element
741                && token.kind() == SyntaxKind::COMMENT
742            {
743                comments.push(token.clone());
744                continue;
745            }
746            break;
747        }
748        Some(comments)
749    }
750
751    pub fn body_element(&self) -> Option<SyntaxElement<RLanguage>> {
752        let elements = self.elements();
753        let (_, rparen_idx) = self.clause_bounds()?;
754        for element in &elements[rparen_idx + 1..] {
755            if is_trivia(element.kind()) {
756                continue;
757            }
758            if matches!(element, SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT)
759            {
760                continue;
761            }
762            return Some(element.clone());
763        }
764        None
765    }
766
767    pub fn parts(&self) -> Option<ForExprParts> {
768        self.for_keyword()?;
769        self.lparen_index()?;
770        self.clause_bounds()?;
771
772        let clause_elements = self.clause_elements()?;
773        let in_idx = clause_elements.iter().position(
774            |el| matches!(el, SyntaxElement::Token(tok) if tok.kind() == SyntaxKind::IN_KW),
775        )?;
776
777        Some(ForExprParts {
778            leading_comments: self.leading_comments()?,
779            variable_elements: clause_elements[..in_idx].to_vec(),
780            sequence_elements: clause_elements[in_idx + 1..].to_vec(),
781            post_clause_comments: self.post_clause_comments()?,
782            body: self.body_element(),
783        })
784    }
785}
786
787impl WhileExpr {
788    pub fn elements(&self) -> Vec<SyntaxElement<RLanguage>> {
789        self.syntax().children_with_tokens().collect()
790    }
791
792    pub fn while_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
793        self.elements()
794            .into_iter()
795            .find_map(|element| match element {
796                SyntaxElement::Token(token) if token.kind() == SyntaxKind::WHILE_KW => Some(token),
797                _ => None,
798            })
799    }
800
801    pub fn clause_bounds(&self) -> Option<(usize, usize)> {
802        let elements = self.elements();
803        let lparen_idx = self.lparen_index()?;
804
805        let mut depth = 0usize;
806        let rparen_idx = elements.iter().enumerate().skip(lparen_idx).find_map(
807            |(idx, element)| match element {
808                SyntaxElement::Token(token) if token.kind() == SyntaxKind::LPAREN => {
809                    depth += 1;
810                    None
811                }
812                SyntaxElement::Token(token) if token.kind() == SyntaxKind::RPAREN => {
813                    depth = depth.saturating_sub(1);
814                    if depth == 0 { Some(idx) } else { None }
815                }
816                _ => None,
817            },
818        )?;
819
820        Some((lparen_idx, rparen_idx))
821    }
822
823    pub fn lparen_index(&self) -> Option<usize> {
824        let elements = self.elements();
825        let while_idx = find_token_index(&elements, SyntaxKind::WHILE_KW)?;
826        find_token_after_index(&elements, while_idx, SyntaxKind::LPAREN)
827    }
828
829    pub fn leading_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
830        let elements = self.elements();
831        let while_idx = find_token_index(&elements, SyntaxKind::WHILE_KW)?;
832        let (_, rparen_idx) = self.clause_bounds()?;
833        Some(
834            elements[while_idx + 1..rparen_idx]
835                .iter()
836                .filter_map(|element| match element {
837                    SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT => {
838                        Some(token.clone())
839                    }
840                    _ => None,
841                })
842                .collect(),
843        )
844    }
845
846    pub fn condition_elements(&self) -> Option<Vec<SyntaxElement<RLanguage>>> {
847        let elements = self.elements();
848        let (lparen_idx, rparen_idx) = self.clause_bounds()?;
849        Some(
850            elements[lparen_idx + 1..rparen_idx]
851                .iter()
852                .filter(|element| {
853                    !is_trivia(element.kind()) && element.kind() != SyntaxKind::COMMENT
854                })
855                .cloned()
856                .collect(),
857        )
858    }
859
860    pub fn post_clause_comments(&self) -> Option<Vec<SyntaxToken<RLanguage>>> {
861        let elements = self.elements();
862        let (_, rparen_idx) = self.clause_bounds()?;
863        let mut comments = Vec::new();
864        for element in &elements[rparen_idx + 1..] {
865            if is_trivia(element.kind()) {
866                continue;
867            }
868            if let SyntaxElement::Token(token) = element
869                && token.kind() == SyntaxKind::COMMENT
870            {
871                comments.push(token.clone());
872                continue;
873            }
874            break;
875        }
876        Some(comments)
877    }
878
879    pub fn body_element(&self) -> Option<SyntaxElement<RLanguage>> {
880        let elements = self.elements();
881        let (_, rparen_idx) = self.clause_bounds()?;
882        for element in &elements[rparen_idx + 1..] {
883            if is_trivia(element.kind()) {
884                continue;
885            }
886            if matches!(element, SyntaxElement::Token(token) if token.kind() == SyntaxKind::COMMENT)
887            {
888                continue;
889            }
890            return Some(element.clone());
891        }
892        None
893    }
894
895    pub fn parts(&self) -> Option<WhileExprParts> {
896        self.while_keyword()?;
897        self.lparen_index()?;
898        self.clause_bounds()?;
899
900        Some(WhileExprParts {
901            leading_comments: self.leading_comments()?,
902            condition_elements: self.condition_elements()?,
903            post_clause_comments: self.post_clause_comments()?,
904            body: self.body_element(),
905        })
906    }
907}
908
909impl BinaryExpr {
910    /// The left-hand operand — an element, since a leaf operand is a bare token
911    /// (`x`, `1`, `TRUE`) while a compound operand is a node (`a + b`).
912    pub fn lhs(&self) -> Option<SyntaxElement<RLanguage>> {
913        self.parts().map(|(lhs, _, _)| lhs)
914    }
915
916    /// The operator token (`==`, `+`, `%in%`, `::`, …).
917    pub fn op(&self) -> Option<SyntaxToken<RLanguage>> {
918        self.parts().map(|(_, op, _)| op)
919    }
920
921    pub fn op_kind(&self) -> Option<SyntaxKind> {
922        self.op().map(|op| op.kind())
923    }
924
925    /// The right-hand operand element.
926    pub fn rhs(&self) -> Option<SyntaxElement<RLanguage>> {
927        self.parts().map(|(_, _, rhs)| rhs)
928    }
929
930    /// Split into `(lhs, operator, rhs)` at the top-level operator token.
931    pub fn parts(
932        &self,
933    ) -> Option<(
934        SyntaxElement<RLanguage>,
935        SyntaxToken<RLanguage>,
936        SyntaxElement<RLanguage>,
937    )> {
938        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
939        let op_idx = elements
940            .iter()
941            .position(|e| matches!(e, SyntaxElement::Token(t) if is_binary_operator(t.kind())))?;
942        let op = elements[op_idx].as_token()?.clone();
943        let lhs = elements[..op_idx]
944            .iter()
945            .rev()
946            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?
947            .clone();
948        let rhs = elements[op_idx + 1..]
949            .iter()
950            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)?
951            .clone();
952        Some((lhs, op, rhs))
953    }
954}
955
956impl UnaryExpr {
957    /// The prefix operator token (`!`, `-`, `+`, `~`, `?`).
958    pub fn op(&self) -> Option<SyntaxToken<RLanguage>> {
959        self.syntax().children_with_tokens().find_map(|e| match e {
960            SyntaxElement::Token(t) if is_unary_operator(t.kind()) => Some(t),
961            _ => None,
962        })
963    }
964
965    pub fn op_kind(&self) -> Option<SyntaxKind> {
966        self.op().map(|op| op.kind())
967    }
968
969    /// The operand element following the operator.
970    pub fn operand(&self) -> Option<SyntaxElement<RLanguage>> {
971        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
972        let op_idx = elements
973            .iter()
974            .position(|e| matches!(e, SyntaxElement::Token(t) if is_unary_operator(t.kind())))?;
975        elements[op_idx + 1..]
976            .iter()
977            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
978            .cloned()
979    }
980}
981
982impl ParenExpr {
983    pub fn lparen(&self) -> Option<SyntaxToken<RLanguage>> {
984        first_child_token(self.syntax(), SyntaxKind::LPAREN)
985    }
986
987    pub fn rparen(&self) -> Option<SyntaxToken<RLanguage>> {
988        first_child_token(self.syntax(), SyntaxKind::RPAREN)
989    }
990
991    /// The parenthesized inner expression element.
992    pub fn inner(&self) -> Option<SyntaxElement<RLanguage>> {
993        self.syntax().children_with_tokens().find(|e| {
994            !is_trivia(e.kind())
995                && !matches!(
996                    e.kind(),
997                    SyntaxKind::COMMENT | SyntaxKind::LPAREN | SyntaxKind::RPAREN
998                )
999        })
1000    }
1001}
1002
1003impl BlockExpr {
1004    pub fn lbrace(&self) -> Option<SyntaxToken<RLanguage>> {
1005        first_child_token(self.syntax(), SyntaxKind::LBRACE)
1006    }
1007
1008    pub fn rbrace(&self) -> Option<SyntaxToken<RLanguage>> {
1009        first_child_token(self.syntax(), SyntaxKind::RBRACE)
1010    }
1011
1012    /// The statement elements between the braces, in source order — the
1013    /// non-trivia children that are not the braces, comments, or `;` separators.
1014    /// Each is a bare token (`x`) or an expression node.
1015    pub fn statements(&self) -> impl Iterator<Item = SyntaxElement<RLanguage>> + '_ {
1016        self.syntax().children_with_tokens().filter(|e| {
1017            !is_trivia(e.kind())
1018                && !matches!(
1019                    e.kind(),
1020                    SyntaxKind::COMMENT
1021                        | SyntaxKind::LBRACE
1022                        | SyntaxKind::RBRACE
1023                        | SyntaxKind::SEMICOLON
1024                )
1025        })
1026    }
1027}
1028
1029impl Arg {
1030    /// The `=` token of a named argument (`name = value`), if any.
1031    pub fn eq_token(&self) -> Option<SyntaxToken<RLanguage>> {
1032        first_child_token(self.syntax(), SyntaxKind::ASSIGN_EQ)
1033    }
1034
1035    /// The name token of a named argument — the `IDENT`/`STRING` immediately
1036    /// before `=`. `None` for a positional argument.
1037    pub fn name_token(&self) -> Option<SyntaxToken<RLanguage>> {
1038        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1039        let eq = elements
1040            .iter()
1041            .position(|e| e.kind() == SyntaxKind::ASSIGN_EQ)?;
1042        elements[..eq].iter().rev().find_map(|e| match e {
1043            SyntaxElement::Token(t)
1044                if matches!(t.kind(), SyntaxKind::IDENT | SyntaxKind::STRING) =>
1045            {
1046                Some(t.clone())
1047            }
1048            _ => None,
1049        })
1050    }
1051
1052    /// The argument name, unquoted. `None` for a positional argument.
1053    pub fn name(&self) -> Option<SmolStr> {
1054        self.name_token().as_ref().map(token_name)
1055    }
1056
1057    pub fn is_named(&self) -> bool {
1058        self.name_token().is_some()
1059    }
1060
1061    /// The value element: for `name = value`, the first non-trivia element after
1062    /// `=`; for a positional argument, its first non-trivia element.
1063    pub fn value(&self) -> Option<SyntaxElement<RLanguage>> {
1064        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1065        let start = match elements
1066            .iter()
1067            .position(|e| e.kind() == SyntaxKind::ASSIGN_EQ)
1068        {
1069            Some(eq) => eq + 1,
1070            None => 0,
1071        };
1072        elements[start..]
1073            .iter()
1074            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1075            .cloned()
1076    }
1077}
1078
1079impl RepeatExpr {
1080    pub fn repeat_keyword(&self) -> Option<SyntaxToken<RLanguage>> {
1081        first_child_token(self.syntax(), SyntaxKind::REPEAT_KW)
1082    }
1083
1084    /// The loop body element following the `repeat` keyword.
1085    pub fn body(&self) -> Option<SyntaxElement<RLanguage>> {
1086        let elements: Vec<_> = self.syntax().children_with_tokens().collect();
1087        let kw_idx = elements
1088            .iter()
1089            .position(|e| e.kind() == SyntaxKind::REPEAT_KW)?;
1090        elements[kw_idx + 1..]
1091            .iter()
1092            .find(|e| !is_trivia(e.kind()) && e.kind() != SyntaxKind::COMMENT)
1093            .cloned()
1094    }
1095}
1096
1097fn find_token_index(elements: &[SyntaxElement<RLanguage>], kind: SyntaxKind) -> Option<usize> {
1098    elements
1099        .iter()
1100        .position(|element| matches!(element, SyntaxElement::Token(token) if token.kind() == kind))
1101}
1102
1103fn find_token_after_index(
1104    elements: &[SyntaxElement<RLanguage>],
1105    start_idx: usize,
1106    kind: SyntaxKind,
1107) -> Option<usize> {
1108    elements
1109        .iter()
1110        .enumerate()
1111        .skip(start_idx + 1)
1112        .find_map(|(idx, element)| match element {
1113            SyntaxElement::Token(token) if token.kind() == kind => Some(idx),
1114            _ => None,
1115        })
1116}