Skip to main content

arity_parser/parser/
core.rs

1use crate::parser::bracket_balancer::rebalance_brackets;
2use crate::parser::context::StatementTracker;
3use crate::parser::events::Event;
4use crate::parser::expr::parse_expr;
5use crate::parser::lexer::{TokKind, lex};
6use crate::parser::tree_builder::build_tree;
7use crate::syntax::SyntaxNode;
8
9pub use crate::parser::diagnostics::ParseDiagnostic;
10
11#[derive(Debug, Clone)]
12pub struct ParseOutput {
13    pub cst: SyntaxNode,
14    pub diagnostics: Vec<ParseDiagnostic>,
15}
16
17pub fn parse(text: &str) -> ParseOutput {
18    let tokens = rebalance_brackets(lex(text));
19    let mut diagnostics = Vec::new();
20    let mut root_events = Vec::new();
21
22    let mut i = 0usize;
23    let mut statements = StatementTracker::default();
24    while i < tokens.len() {
25        if matches!(
26            tokens[i].kind,
27            TokKind::Whitespace | TokKind::Newline | TokKind::Semicolon
28        ) {
29            root_events.push(Event::Tok(i));
30            i += 1;
31            continue;
32        }
33
34        if tokens[i].kind == TokKind::RoxygenMarker {
35            i = crate::parser::roxygen::emit_roxygen_block(&tokens, i, &mut root_events);
36            continue;
37        }
38
39        let before = diagnostics.len();
40        if let Some(expr) = parse_expr(&tokens, i, 0, &mut diagnostics) {
41            // A comment is parsed as an atom but is not a statement: it needs no
42            // separator from the code it trails, and it must not become the
43            // "previous statement" for the next one's check.
44            if !tokens[expr.start].kind.is_comment_like() {
45                let recovered = diagnostics.len() > before;
46                statements.record(&tokens, expr.start, expr.end, recovered, &mut diagnostics);
47            }
48            root_events.extend(expr.events);
49            i = expr.end;
50        } else {
51            // A token that can't start an expression at the top level (a stray
52            // closing delimiter like the second `)` in `f(1))`, or lexer junk).
53            // R rejects these outright; flag one so the leniency isn't silent,
54            // but keep the token in the tree to preserve losslessness (Tenet 4).
55            crate::parser::diagnostics::push_token_diagnostic(
56                &mut diagnostics,
57                &format!("unexpected '{}'", tokens[i].text),
58                &tokens[i],
59            );
60            root_events.push(Event::Tok(i));
61            i += 1;
62            statements.record_recovery(i);
63        }
64    }
65
66    let cst = build_tree(&tokens, &root_events);
67    ParseOutput { cst, diagnostics }
68}
69
70pub fn reconstruct(text: &str) -> String {
71    parse(text)
72        .cst
73        .descendants_with_tokens()
74        .filter_map(|el| el.into_token())
75        .map(|tok| tok.text().to_string())
76        .collect::<String>()
77}