use crate::parser::events::Event;
use crate::parser::expr::parse_stmt;
use crate::parser::lexer::{TokKind, Token, lex};
use crate::parser::tree_builder::{build_tree, syntax_kind_for};
use crate::syntax::{SyntaxKind, SyntaxNode};
pub use crate::parser::diagnostics::ParseDiagnostic;
use crate::parser::diagnostics::{DiagnosticKind, push_diagnostic};
#[derive(Debug, Clone)]
pub struct ParseOutput {
pub cst: SyntaxNode,
pub diagnostics: Vec<ParseDiagnostic>,
}
pub fn parse(text: &str) -> ParseOutput {
let tokens = lex(text);
let mut diagnostics = Vec::new();
let mut events = Vec::new();
let mut i = 0usize;
while i < tokens.len() {
if tokens[i].kind.is_trivia() {
events.push(Event::Tok(i));
i += 1;
continue;
}
let mut line = Vec::new();
let mut has_semicolon = false;
let mut leftover_mark: Option<usize> = None;
let mut first_is_doc_string = false;
let mut doc_no_target = false;
let line_has_semicolon = rest_of_line_has_semicolon(&tokens, i);
while i < tokens.len() {
match tokens[i].kind {
TokKind::Newline => break,
TokKind::Semicolon => {
has_semicolon = true;
line.push(Event::Tok(i));
i += 1;
}
k if k.is_trivia() => {
line.push(Event::Tok(i));
i += 1;
}
_ => {
if !line_has_semicolon
&& leftover_mark.is_some()
&& (!first_is_doc_string || doc_no_target)
{
line.push(Event::Tok(i));
i += 1;
} else if let Some(expr) = parse_stmt(&tokens, i, &mut diagnostics) {
if leftover_mark.is_none() {
first_is_doc_string = stmt_is_doc_string(&expr.events, &tokens);
}
line.extend(expr.events);
i = expr.end;
leftover_mark.get_or_insert(line.len());
} else if leftover_mark.is_none()
&& is_close_delimiter_tok(tokens[i].kind)
&& !rest_of_line_has_semicolon(&tokens, i)
{
line.push(Event::Start(SyntaxKind::ERROR));
line.push(Event::Finish);
let run_start = tokens[i].start;
line.push(Event::Start(SyntaxKind::ERROR));
while i < tokens.len() && tokens[i].kind != TokKind::Newline {
line.push(Event::Tok(i));
i += 1;
}
line.push(Event::Finish);
push_diagnostic(
&mut diagnostics,
DiagnosticKind::StrayCloser,
"stray closing delimiter",
run_start,
tokens[i - 1].end,
);
} else if leftover_mark.is_none() && is_stray_block_keyword_tok(tokens[i].kind)
{
line.push(Event::Start(SyntaxKind::ERROR));
line.push(Event::Tok(i));
line.push(Event::Finish);
push_diagnostic(
&mut diagnostics,
DiagnosticKind::StrayKeyword,
"unexpected block keyword",
tokens[i].start,
tokens[i].end,
);
i += 1;
leftover_mark = Some(line.len());
} else {
if first_is_doc_string && leftover_mark.is_some() {
doc_no_target = true;
}
line.push(Event::Tok(i));
i += 1;
}
}
}
}
if has_semicolon {
events.push(Event::Start(SyntaxKind::TOPLEVEL_SEMICOLON));
events.extend(line);
events.push(Event::Finish);
} else if let Some(mark) = leftover_mark.filter(|&m| {
let tail = &line[m..];
let defer_to_doc = first_is_doc_string && leftover_starts_with_subtree(tail, &tokens);
!defer_to_doc && tail.iter().any(|e| is_significant_event(e, &tokens))
}) {
events.extend(line[..mark].iter().cloned());
let tail = &line[mark..];
let lead = tail
.iter()
.take_while(|e| !is_significant_event(e, &tokens))
.count();
events.extend(tail[..lead].iter().cloned());
let first_junk = tail[lead..].iter().find_map(|e| match e {
Event::Tok(idx) => Some(*idx),
_ => None,
});
events.push(Event::Start(SyntaxKind::ERROR));
events.extend(tail[lead..].iter().cloned());
events.push(Event::Finish);
if let Some(idx) = first_junk {
push_diagnostic(
&mut diagnostics,
DiagnosticKind::TrailingJunk,
"trailing tokens after statement",
tokens[idx].start,
tokens[idx].end,
);
}
} else {
events.extend(line);
}
}
let events = fold_docstrings(&events, &tokens, true);
let cst = build_tree(&tokens, &events);
flag_invalid_const_decls(&cst, &mut diagnostics);
flag_invalid_function_signatures(&cst, &mut diagnostics);
flag_invalid_catch_vars(&cst, &mut diagnostics);
flag_invalid_export_items(&cst, &mut diagnostics);
ParseOutput { cst, diagnostics }
}
fn flag_invalid_export_items(cst: &SyntaxNode, diagnostics: &mut Vec<ParseDiagnostic>) {
for stmt in cst
.descendants()
.filter(|n| n.kind() == SyntaxKind::EXPORT_STMT)
{
for item in stmt
.children()
.filter(|c| matches!(c.kind(), SyntaxKind::PAREN_EXPR | SyntaxKind::TUPLE_EXPR))
{
if !export_paren_is_symbol(&item) {
let pos = usize::from(item.text_range().start());
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidExportItem,
"invalid `export` item: expected a single symbol",
pos,
pos,
);
}
}
}
}
fn export_paren_is_symbol(item: &SyntaxNode) -> bool {
if item.kind() != SyntaxKind::PAREN_EXPR {
return false;
}
let mut significant = item.children_with_tokens().filter(|el| {
!matches!(
el.kind(),
SyntaxKind::WHITESPACE
| SyntaxKind::NEWLINE
| SyntaxKind::COMMENT
| SyntaxKind::BLOCK_COMMENT
| SyntaxKind::LPAREN
| SyntaxKind::RPAREN
)
});
let Some(first) = significant.next() else {
return false;
};
if significant.next().is_some() {
return false;
}
match first {
rowan::NodeOrToken::Node(n) => matches!(
n.kind(),
SyntaxKind::NAME | SyntaxKind::NONSTANDARD_IDENTIFIER | SyntaxKind::INTERPOLATION
),
rowan::NodeOrToken::Token(_) => true,
}
}
fn flag_invalid_const_decls(cst: &SyntaxNode, diagnostics: &mut Vec<ParseDiagnostic>) {
for node in cst
.descendants()
.filter(|n| n.kind() == SyntaxKind::CONST_STMT)
{
if !const_decl_is_assignment(&node) && !is_struct_const_field(&node) {
let pos = usize::from(node.text_range().start());
push_diagnostic(
diagnostics,
DiagnosticKind::ConstNotAssignment,
"expected assignment after `const`",
pos,
pos,
);
}
}
}
fn flag_invalid_function_signatures(cst: &SyntaxNode, diagnostics: &mut Vec<ParseDiagnostic>) {
for node in cst
.descendants()
.filter(|n| matches!(n.kind(), SyntaxKind::FUNCTION_DEF | SyntaxKind::MACRO_DEF))
{
let Some(sig) = node.children().find(|c| c.kind() == SyntaxKind::SIGNATURE) else {
continue;
};
let sig_is_bare_name = sig.first_child().is_some_and(|inner| {
matches!(inner.kind(), SyntaxKind::NAME | SyntaxKind::INTERPOLATION)
});
if sig_is_bare_name && !function_body_is_empty(&node) {
let pos = usize::from(sig.text_range().start());
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidFunctionSignature,
"invalid function signature: expected a call, not a bare name",
pos,
pos,
);
}
}
}
fn flag_invalid_catch_vars(cst: &SyntaxNode, diagnostics: &mut Vec<ParseDiagnostic>) {
for node in cst
.descendants()
.filter(|n| n.kind() == SyntaxKind::CATCH_CLAUSE)
{
let Some(var) = node.children().find(|c| c.kind() != SyntaxKind::BLOCK) else {
continue;
};
if !matches!(
var.kind(),
SyntaxKind::NAME | SyntaxKind::INTERPOLATION | SyntaxKind::NONSTANDARD_IDENTIFIER
) {
let pos = usize::from(var.text_range().start());
push_diagnostic(
diagnostics,
DiagnosticKind::CatchVarNotIdentifier,
"catch variable must be an identifier",
pos,
pos,
);
}
}
}
fn function_body_is_empty(node: &SyntaxNode) -> bool {
match node.children().find(|c| c.kind() == SyntaxKind::BLOCK) {
Some(block) => {
block.first_child().is_none()
&& !block
.children_with_tokens()
.any(|el| el.kind() == SyntaxKind::SEMICOLON)
}
None => true,
}
}
fn is_struct_const_field(node: &SyntaxNode) -> bool {
node.parent()
.filter(|b| b.kind() == SyntaxKind::BLOCK)
.and_then(|b| b.parent())
.is_some_and(|p| p.kind() == SyntaxKind::STRUCT_DEF)
}
fn const_decl_is_assignment(node: &SyntaxNode) -> bool {
let mut body = node.first_child();
while let Some(n) = body {
match n.kind() {
SyntaxKind::GLOBAL_STMT | SyntaxKind::LOCAL_STMT => body = n.first_child(),
SyntaxKind::ASSIGNMENT_EXPR => {
return n
.children_with_tokens()
.filter_map(|el| el.into_token())
.any(|t| t.kind() == SyntaxKind::EQ);
}
_ => return false,
}
}
false
}
fn is_close_delimiter_tok(kind: TokKind) -> bool {
matches!(kind, TokKind::RParen | TokKind::RBracket | TokKind::RBrace)
}
fn is_stray_block_keyword_tok(kind: TokKind) -> bool {
matches!(
kind,
TokKind::EndKw
| TokKind::ElseKw
| TokKind::ElseifKw
| TokKind::CatchKw
| TokKind::FinallyKw
)
}
fn rest_of_line_has_semicolon(tokens: &[Token], start: usize) -> bool {
tokens[start..]
.iter()
.take_while(|t| t.kind != TokKind::Newline)
.any(|t| t.kind == TokKind::Semicolon)
}
fn is_significant_event(event: &Event, tokens: &[Token]) -> bool {
match event {
Event::Start(_) => true,
Event::Tok(idx) => !tokens[*idx].kind.is_trivia(),
Event::Finish => false,
}
}
fn leftover_starts_with_subtree(tail: &[Event], tokens: &[Token]) -> bool {
matches!(
tail.iter().find(|e| is_significant_event(e, tokens)),
Some(Event::Start(_))
)
}
fn stmt_is_doc_string(events: &[Event], tokens: &[Token]) -> bool {
matches!(
events.first(),
Some(Event::Start(SyntaxKind::STRING_LITERAL))
) && string_is_doc_eligible(&events[1..], tokens)
}
enum Item {
Leaf(usize),
Subtree(SyntaxKind, Vec<Event>),
}
fn is_doc_container(kind: SyntaxKind) -> bool {
matches!(kind, SyntaxKind::BLOCK | SyntaxKind::TOPLEVEL_SEMICOLON)
}
fn string_is_doc_eligible(inner: &[Event], tokens: &[Token]) -> bool {
match inner.first() {
Some(Event::Tok(idx)) => syntax_kind_for(tokens[*idx].kind) != SyntaxKind::STRING_PREFIX,
_ => true,
}
}
fn fold_docstrings(inner: &[Event], tokens: &[Token], is_container: bool) -> Vec<Event> {
let mut items: Vec<Item> = Vec::new();
let mut k = 0;
while k < inner.len() {
match inner[k] {
Event::Tok(idx) => {
items.push(Item::Leaf(idx));
k += 1;
}
Event::Start(kind) => {
let mut depth = 1usize;
let mut j = k + 1;
while j < inner.len() {
match inner[j] {
Event::Start(_) => depth += 1,
Event::Finish => {
depth -= 1;
if depth == 0 {
break;
}
}
Event::Tok(_) => {}
}
j += 1;
}
let child = fold_docstrings(&inner[k + 1..j], tokens, is_doc_container(kind));
items.push(Item::Subtree(kind, child));
k = j + 1;
}
Event::Finish => k += 1,
}
}
let mut out: Vec<Event> = Vec::new();
let mut idx = 0;
while idx < items.len() {
if is_container
&& let Item::Subtree(SyntaxKind::STRING_LITERAL, str_inner) = &items[idx]
&& string_is_doc_eligible(str_inner, tokens)
&& let Some(target) = doc_target(&items, idx, tokens)
{
out.push(Event::Start(SyntaxKind::DOC));
emit_items(&items[idx..=target], &mut out);
out.push(Event::Finish);
idx = target + 1;
continue;
}
emit_items(&items[idx..=idx], &mut out);
idx += 1;
}
out
}
fn doc_target(items: &[Item], start: usize, tokens: &[Token]) -> Option<usize> {
let mut newlines = 0;
let mut j = start + 1;
while j < items.len() {
match &items[j] {
Item::Subtree(SyntaxKind::ERROR, _) => return None,
Item::Subtree(..) => return Some(j),
Item::Leaf(t) => {
let kind = tokens[*t].kind;
if kind == TokKind::Newline {
newlines += 1;
if newlines > 1 {
return None;
}
} else if !kind.is_trivia() {
return None;
}
j += 1;
}
}
}
None
}
fn emit_items(items: &[Item], out: &mut Vec<Event>) {
for item in items {
match item {
Item::Leaf(idx) => out.push(Event::Tok(*idx)),
Item::Subtree(kind, inner) => {
out.push(Event::Start(*kind));
out.extend(inner.iter().cloned());
out.push(Event::Finish);
}
}
}
}
pub fn reconstruct(text: &str) -> String {
parse(text)
.cst
.descendants_with_tokens()
.filter_map(|el| el.into_token())
.map(|tok| tok.text().to_string())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_lossless(input: &str) {
assert_eq!(reconstruct(input), input, "not lossless: {input:?}");
}
#[test]
fn lossless_corpus() {
for input in [
"",
"x = 1\n",
"y = a + b * c\n",
"f(a, b, c)\n",
"v[1]\n",
"a[end]\n",
"a[end - 1]\n",
"a[2:end]\n",
"m[end, end]\n",
"z = (a + b) / 2\n",
"function g(x)\n x ^ 2\nend\n",
"if a >= b\n a\nelseif c\n c\nelse\n b\nend\n",
"begin\n a\n b\nend\n",
"map(xs) do x\n x + 1\nend\n",
"open(\"f\") do\n read()\nend\n",
"# a comment\nx = 1 # trailing\n",
"#= block =#\nq = 2\n",
"obj.field\n",
"x::Int\n",
"Vector{T}\n",
"foo(x::T) where {T<:Number} = x\n",
"f(a, b; c=1, d=2)\n",
"g(args...; kwargs...)\n",
"y = a .+ b .* c\n",
"z = .-x\n",
"a .= f.(b, c)\n",
"t = (a, b)\n",
"u = (a,)\n",
"e = ()\n",
"nt = (x = 1, y = 2)\n",
"y = a ? b : c\n",
"z = a == b ? x + 1 : x - 1\n",
"w = a ? b : c ? d : e\n",
"v = [1, 2, 3]\n",
"e = []\n",
"m = [1 2; 3 4]\n",
"s = [1 +2]\n",
"c = [1; 2; 3]\n",
"q = [x for x in xs]\n",
"r = [x^2 for x in 1:10 if x > 2]\n",
"g = (x for x in xs)\n",
"a; b\n",
"a;;;b;;\n",
";a\n",
] {
assert_lossless(input);
}
}
#[test]
fn groups_top_level_semicolon_line() {
let out = parse("a; b\nc\n");
let kinds: Vec<_> = out.cst.children().map(|n| n.kind()).collect();
use crate::syntax::SyntaxKind::{NAME, TOPLEVEL_SEMICOLON};
assert_eq!(kinds, vec![TOPLEVEL_SEMICOLON, NAME]);
}
#[test]
fn builds_expected_top_level_shape() {
let out = parse("x = 1 + 2\n");
let kinds: Vec<_> = out.cst.children().map(|n| n.kind()).collect();
assert_eq!(kinds, vec![crate::syntax::SyntaxKind::ASSIGNMENT_EXPR]);
assert!(out.diagnostics.is_empty());
}
}