use crate::parser::core::ParseOutput;
use crate::syntax::{SyntaxKind, SyntaxNode};
pub fn is_single_expression(out: &ParseOutput) -> bool {
out.diagnostics.is_empty()
&& root_holds_one_expression(&out.cst)
&& !has_r_invalid_name(&out.cst)
}
fn root_holds_one_expression(cst: &SyntaxNode) -> bool {
let mut expr = false;
let mut semicolon = false;
let mut line_broke = false;
for el in cst.children_with_tokens() {
match el.kind() {
SyntaxKind::WHITESPACE | SyntaxKind::COMMENT => {}
SyntaxKind::NEWLINE => line_broke = true,
SyntaxKind::SEMICOLON => {
if !expr || semicolon || line_broke {
return false;
}
semicolon = true;
}
_ => {
if expr {
return false;
}
expr = true;
line_broke = false;
}
}
}
expr
}
pub fn has_r_invalid_name(cst: &SyntaxNode) -> bool {
let has_pipe = cst
.descendants_with_tokens()
.any(|el| el.kind() == SyntaxKind::PIPE);
cst.descendants_with_tokens()
.filter_map(|el| el.into_token())
.filter(|t| t.kind() == SyntaxKind::IDENT)
.any(|t| {
let text = t.text();
text == "``" || (text.starts_with('_') && (text.len() > 1 || !has_pipe))
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::core::parse;
fn single(text: &str) -> bool {
is_single_expression(&parse(text))
}
#[test]
fn accepts_one_expression() {
assert!(single("x"));
assert!(single("f(a, b)"));
assert!(single("function(x) x + 1"));
assert!(single(" x "));
assert!(single("\nx\n"));
assert!(single("x # trailing comment"));
}
#[test]
fn rejects_zero_or_several_expressions() {
assert!(!single(""));
assert!(!single(" "));
assert!(!single("# just a comment"));
assert!(!single("inline code"));
assert!(!single("x\ny"));
}
#[test]
fn rejects_an_incomplete_expression() {
assert!(!single("f("));
assert!(!single("x +"));
}
#[test]
fn semicolon_terminates_but_never_stands_alone() {
assert!(single("x;"));
assert!(single("x ;"));
assert!(single("f(a);"));
assert!(single("x;\n"));
assert!(single("x; # trailing comment"));
assert!(!single(";"));
assert!(!single(";x"));
assert!(!single("x;;"));
assert!(!single("x; ;"));
assert!(!single("x; y"));
assert!(!single("x;\ny"));
assert!(!single("x\n;"));
}
#[test]
fn rejects_names_r_lexes_as_errors() {
assert!(!single("_"));
assert!(!single("_x"));
assert!(!single("``"));
assert!(single("x |> _$col"));
assert!(single("a_b"));
assert!(single("`x`"));
}
}