Skip to main content

arity_parser/parser/
validate.rs

1//! Judging a parse against R's own acceptance rules.
2//!
3//! arity's parser is deliberately more lenient than R's: it recovers from
4//! errors, and its lexer accepts name shapes R rejects. Consumers that need to
5//! answer "would R have parsed this?" — rather than "did arity produce a usable
6//! tree?" — go through the predicates here.
7
8use crate::parser::core::ParseOutput;
9use crate::syntax::{SyntaxKind, SyntaxNode};
10
11/// Whether the parsed text is exactly one complete R expression, the way R's
12/// own parser judges it — the predicate behind `rlang::parse_expr()` (and so
13/// roxygen2's `can_parse()`).
14///
15/// This takes the whole [`ParseOutput`] rather than just the CST because a
16/// recovered tree alone cannot answer the question: `f(` yields a well-shaped
17/// `CALL_EXPR` and reports the missing `)` only as a diagnostic.
18///
19/// Three things have to hold:
20///
21/// - the parse is diagnostic-free;
22/// - the root holds one expression, optionally followed by a single `;`. R's
23///   `;` is a statement *terminator*, not a separator that may stand alone: it
24///   must follow an expression on the same line, and a second one opens an
25///   empty statement, which is a syntax error (`x;` parses, `;x`, `x;;`,
26///   `x\n;` and `x; y` do not);
27/// - no name in the tree is one R's lexer rejects but arity's accepts (see
28///   [`has_r_invalid_name`]).
29pub fn is_single_expression(out: &ParseOutput) -> bool {
30    out.diagnostics.is_empty()
31        && root_holds_one_expression(&out.cst)
32        && !has_r_invalid_name(&out.cst)
33}
34
35/// The positional `expr` / `expr ;` rule over the root's children.
36fn root_holds_one_expression(cst: &SyntaxNode) -> bool {
37    let mut expr = false;
38    let mut semicolon = false;
39    let mut line_broke = false;
40    for el in cst.children_with_tokens() {
41        match el.kind() {
42            SyntaxKind::WHITESPACE | SyntaxKind::COMMENT => {}
43            SyntaxKind::NEWLINE => line_broke = true,
44            SyntaxKind::SEMICOLON => {
45                if !expr || semicolon || line_broke {
46                    return false;
47                }
48                semicolon = true;
49            }
50            _ => {
51                if expr {
52                    return false;
53                }
54                expr = true;
55                line_broke = false;
56            }
57        }
58    }
59    expr
60}
61
62/// Whether the tree contains a name R's lexer rejects but arity's more lenient
63/// one accepts as an ordinary identifier.
64///
65/// A `_`-leading name errors in R's lexer, with one exception: a lone `_` used
66/// as the native-pipe placeholder, valid only inside a `|>` pipeline (a
67/// `_`-leading name of length ≥ 2 is never valid). A zero-length backquoted
68/// name `` `` `` errors at parse time ("attempt to use zero-length variable
69/// name"); any non-empty backquoted name is valid.
70pub fn has_r_invalid_name(cst: &SyntaxNode) -> bool {
71    let has_pipe = cst
72        .descendants_with_tokens()
73        .any(|el| el.kind() == SyntaxKind::PIPE);
74    cst.descendants_with_tokens()
75        .filter_map(|el| el.into_token())
76        .filter(|t| t.kind() == SyntaxKind::IDENT)
77        .any(|t| {
78            let text = t.text();
79            text == "``" || (text.starts_with('_') && (text.len() > 1 || !has_pipe))
80        })
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::parser::core::parse;
87
88    fn single(text: &str) -> bool {
89        is_single_expression(&parse(text))
90    }
91
92    #[test]
93    fn accepts_one_expression() {
94        assert!(single("x"));
95        assert!(single("f(a, b)"));
96        assert!(single("function(x) x + 1"));
97        assert!(single("  x  "));
98        assert!(single("\nx\n"));
99        assert!(single("x # trailing comment"));
100    }
101
102    #[test]
103    fn rejects_zero_or_several_expressions() {
104        assert!(!single(""));
105        assert!(!single("   "));
106        assert!(!single("# just a comment"));
107        assert!(!single("inline code"));
108        assert!(!single("x\ny"));
109    }
110
111    #[test]
112    fn rejects_an_incomplete_expression() {
113        assert!(!single("f("));
114        assert!(!single("x +"));
115    }
116
117    #[test]
118    fn semicolon_terminates_but_never_stands_alone() {
119        // Cross-checked against `rlang::parse_expr()` under R 4.6.1.
120        assert!(single("x;"));
121        assert!(single("x ;"));
122        assert!(single("f(a);"));
123        assert!(single("x;\n"));
124        assert!(single("x; # trailing comment"));
125        assert!(!single(";"));
126        assert!(!single(";x"));
127        assert!(!single("x;;"));
128        assert!(!single("x; ;"));
129        assert!(!single("x; y"));
130        assert!(!single("x;\ny"));
131        // A `;` on its own line opens an empty statement, so it is an error
132        // even though the expression before it is complete.
133        assert!(!single("x\n;"));
134    }
135
136    #[test]
137    fn rejects_names_r_lexes_as_errors() {
138        assert!(!single("_"));
139        assert!(!single("_x"));
140        assert!(!single("``"));
141        assert!(single("x |> _$col"));
142        assert!(single("a_b"));
143        assert!(single("`x`"));
144    }
145}