mod expressions;
mod items;
use crate::ast::SourceFile;
use crate::diagnostic::Diagnostic;
use crate::lexer::lex;
use crate::span::Span;
use crate::syntax::{Builder, NodeKind, SyntaxTree};
use crate::token::{Token, TokenKind};
pub const MAX_DEPTH: usize = 256;
#[derive(Debug, Clone)]
pub struct Parsed {
pub tree: SyntaxTree,
pub diagnostics: Vec<Diagnostic>,
}
impl Parsed {
pub fn file(&self) -> SourceFile<'_> {
SourceFile::from_root(self.tree.root())
}
}
pub fn parse(source: &str) -> Parsed {
let lexed = lex(source);
let mut parser = Parser::new(source, lexed.tokens);
parser.file();
let Parser {
mut tokens,
builder,
diagnostics: syntax_diagnostics,
..
} = parser;
tokens.pop();
let tree = builder.finish(source, tokens);
let mut diagnostics = lexed.diagnostics;
diagnostics.extend(syntax_diagnostics);
diagnostics.sort_by_key(|d| d.span.start);
Parsed { tree, diagnostics }
}
pub(crate) struct Parser<'s> {
source: &'s str,
tokens: Vec<Token>,
layout: Layout,
pos: usize,
limit: usize,
builder: Builder,
diagnostics: Vec<Diagnostic>,
recovering: bool,
depth: usize,
delims: Vec<usize>,
past_imports: bool,
}
impl<'s> Parser<'s> {
fn new(source: &'s str, tokens: Vec<Token>) -> Self {
let layout = Layout::new(&tokens);
let limit = tokens.len() - 1;
Self {
source,
builder: Builder::new(tokens.len()),
tokens,
layout,
pos: 0,
limit,
diagnostics: Vec::new(),
recovering: false,
depth: 0,
delims: Vec::new(),
past_imports: false,
}
}
fn is_skipped(&self, index: usize) -> bool {
let kind = self.tokens[index].kind;
kind.is_trivia() || (kind == TokenKind::Newline && self.layout.ignored[index])
}
fn next_index(&self, from: usize) -> usize {
let mut index = from;
while index < self.limit && self.is_skipped(index) {
index += 1;
}
index
}
fn end_of_string(&self) -> usize {
self.layout.ends[self.pos].max(self.pos + 1).min(self.limit)
}
fn nth_index(&self, n: usize) -> usize {
let mut index = self.next_index(self.pos);
for _ in 0..n {
if index >= self.limit {
break;
}
index = self.next_index(index + 1);
}
index
}
fn nth(&self, n: usize) -> TokenKind {
let index = self.nth_index(n);
if index >= self.limit {
TokenKind::Eof
} else {
self.tokens[index].kind
}
}
fn current(&self) -> TokenKind {
self.nth(0)
}
fn at(&self, kind: TokenKind) -> bool {
self.current() == kind
}
fn current_text(&self) -> &'s str {
self.text(self.nth_index(0))
}
fn text(&self, index: usize) -> &'s str {
self.source
.get(self.tokens[index].span.range())
.unwrap_or_default()
}
fn current_span(&self) -> Span {
self.tokens[self.nth_index(0)].span
}
fn flush(&mut self) {
let next = self.next_index(self.pos);
for index in self.pos..next {
self.builder.token(index);
}
self.pos = next;
}
fn bump(&mut self) {
self.flush();
if self.pos >= self.limit {
return;
}
if is_string_start(self.tokens[self.pos].kind) {
let end = self.end_of_string();
while self.pos < end {
self.builder.token(self.pos);
self.pos += 1;
}
} else {
self.builder.token(self.pos);
self.pos += 1;
}
}
fn bump_raw(&mut self) {
if self.pos < self.limit {
self.builder.token(self.pos);
self.pos += 1;
}
}
fn eat(&mut self, kind: TokenKind) -> bool {
let found = self.at(kind);
if found {
self.bump();
}
found
}
fn start_node(&mut self, kind: NodeKind) {
self.flush();
self.builder.start_node(kind);
}
fn checkpoint(&mut self) -> usize {
self.flush();
self.builder.checkpoint()
}
fn start_node_at(&mut self, checkpoint: usize, kind: NodeKind) {
self.builder.start_node_at(checkpoint, kind);
}
fn finish_node(&mut self) {
let empty_at = self.tokens[self.pos.min(self.tokens.len() - 1)].span.start;
self.builder.finish_node(&self.tokens, empty_at);
}
fn open(&mut self) -> usize {
let index = self.nth_index(0);
self.bump();
self.delims.push(index);
index
}
fn close(&mut self, opener: usize, closer: TokenKind) {
if !self.eat(closer) {
let open = self.tokens[opener];
let close = closer_text(closer);
if self.at(TokenKind::Eof) || self.closes_outer(self.current()) {
self.error(
"E0210",
open.span,
format!("unclosed `{}`", opener_text(open.kind)),
Some(format!("add `{close}` to close it")),
);
} else {
self.expected(&format!("`{close}`"));
}
}
if self.delims.last() == Some(&opener) {
self.delims.pop();
}
}
fn closes_outer(&self, kind: TokenKind) -> bool {
let Some(opener) = opener_of(kind) else {
return false;
};
let outer = self.delims.len().saturating_sub(1);
self.delims[..outer]
.iter()
.any(|&index| self.tokens[index].kind == opener)
}
fn enter(&mut self) -> bool {
if self.depth >= MAX_DEPTH {
self.too_deep();
return false;
}
self.depth += 1;
true
}
fn leave(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
fn too_deep(&mut self) {
self.error(
"E0211",
self.current_span(),
format!("nesting is deeper than the limit of {MAX_DEPTH} levels"),
Some("split the code into smaller parts with `let` bindings".to_owned()),
);
self.skip_to_separator();
}
fn skip_to_separator(&mut self) {
use TokenKind as T;
let mut balance = 0usize;
let mut started = false;
loop {
match self.current() {
T::Eof => break,
T::LBrace | T::LBracket | T::LParen => balance += 1,
T::RBrace | T::RBracket | T::RParen => {
if balance == 0 {
break;
}
balance -= 1;
}
T::Newline | T::Comma if balance == 0 => break,
_ => {}
}
if !started {
self.start_node(NodeKind::Error);
started = true;
}
self.bump();
}
if started {
self.finish_node();
}
}
fn bump_error(&mut self) {
if self.at(TokenKind::Eof) {
return;
}
self.start_node(NodeKind::Error);
self.bump();
self.finish_node();
}
fn sequence(
&mut self,
opener: usize,
closer: TokenKind,
what: &str,
mut element: impl FnMut(&mut Self),
) {
let mut after_element = false;
loop {
self.separators(after_element, what);
let kind = self.current();
if kind == closer || kind == TokenKind::Eof || self.closes_outer(kind) {
break;
}
after_element = true;
if is_closer(kind) {
self.expected(what);
self.bump_error();
continue;
}
self.element(&mut element);
let kind = self.current();
let separated = matches!(kind, TokenKind::Newline | TokenKind::Comma | TokenKind::Eof);
if !separated && kind != closer && !self.closes_outer(kind) {
self.expected(&format!("a line break, `,` or `{}`", closer_text(closer)));
self.skip_to_separator();
}
}
self.close(opener, closer);
}
fn comma_list(
&mut self,
opener: usize,
closer: TokenKind,
what: &str,
mut element: impl FnMut(&mut Self),
) {
loop {
let kind = self.current();
if kind == closer || kind == TokenKind::Eof || self.closes_outer(kind) {
break;
}
if is_closer(kind) || kind == TokenKind::Comma {
self.expected(what);
self.bump_error();
continue;
}
self.element(&mut element);
let kind = self.current();
if kind == TokenKind::Comma {
self.bump();
self.recovering = false;
} else if kind != closer && kind != TokenKind::Eof && !self.closes_outer(kind) {
self.expected(&format!("`,` or `{}`", closer_text(closer)));
self.skip_to_separator();
}
}
self.close(opener, closer);
}
fn element(&mut self, element: &mut impl FnMut(&mut Self)) {
let start = self.next_index(self.pos);
element(self);
if self.next_index(self.pos) == start {
self.bump_error();
}
}
fn separators(&mut self, after_element: bool, what: &str) {
let mut comma = !after_element;
loop {
match self.current() {
TokenKind::Comma if comma => {
self.expected(what);
self.bump_error();
}
TokenKind::Newline | TokenKind::Comma => {
comma |= self.at(TokenKind::Comma);
self.bump();
self.recovering = false;
}
_ => break,
}
}
}
fn skip_newlines_before(&mut self, kinds: &[TokenKind]) {
let mut index = self.next_index(self.pos);
let mut count = 0;
while index < self.limit && self.tokens[index].kind == TokenKind::Newline {
index = self.next_index(index + 1);
count += 1;
}
if count > 0 && index < self.limit && kinds.contains(&self.tokens[index].kind) {
for _ in 0..count {
self.bump();
}
}
}
fn error(&mut self, code: &'static str, span: Span, message: String, help: Option<String>) {
if !self.recovering {
self.diagnostics
.push(Diagnostic::error(code, span, message).with_optional_help(help));
}
self.recovering = true;
}
fn soft_error(
&mut self,
code: &'static str,
span: Span,
message: String,
help: Option<String>,
) {
if !self.recovering {
self.diagnostics
.push(Diagnostic::error(code, span, message).with_optional_help(help));
}
}
fn expected(&mut self, what: &str) {
self.unexpected(
"E0210",
|found| format!("expected {what}, found {found}"),
None,
);
}
fn unexpected(
&mut self,
code: &'static str,
message: impl FnOnce(String) -> String,
help: Option<String>,
) {
let index = self.real_next();
let token = self.tokens[index];
if token.kind == TokenKind::Error {
self.recovering = true;
return;
}
let found = self.describe(index);
self.error(code, token.span, message(found), help);
}
fn name(&mut self, what: &str) {
if is_name(self.current()) {
self.bump();
} else {
self.expected(what);
}
}
fn real_next(&self) -> usize {
let last = self.tokens.len() - 1;
let mut index = self.next_index(self.pos);
while index < last && self.is_skipped(index) {
index += 1;
}
index.min(last)
}
fn describe(&self, index: usize) -> String {
use TokenKind as T;
let token = self.tokens[index];
match token.kind {
T::Newline => "a line break".to_owned(),
T::Eof => "the end of the file".to_owned(),
T::StrStart | T::MStrStart | T::RawStrStart | T::RawMStrStart => "a string".to_owned(),
T::InterpEnd => "`}`".to_owned(),
_ => format!("`{}`", self.text(index)),
}
}
}
struct Layout {
ignored: Vec<bool>,
ends: Vec<usize>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Open {
Paren,
Brace,
Bracket,
Interp(usize),
Str { start: usize, single_line: bool },
}
impl Open {
fn bracket_slot(self) -> Option<usize> {
match self {
Self::Paren => Some(0),
Self::Brace => Some(1),
Self::Bracket => Some(2),
_ => None,
}
}
}
impl Layout {
fn new(tokens: &[Token]) -> Self {
use TokenKind as T;
let mut layout = Self {
ignored: vec![false; tokens.len()],
ends: vec![0; tokens.len()],
};
let mut stack: Vec<(Open, [usize; 3])> = Vec::new();
let mut single_line_strings = 0usize;
let mut previous: Option<TokenKind> = None;
let mut pending: Vec<usize> = Vec::new();
for (index, token) in tokens.iter().enumerate() {
let kind = token.kind;
if kind.is_trivia() {
continue;
}
if kind == T::Newline {
while single_line_strings > 0 {
layout.pop(&mut stack, &mut single_line_strings, index);
}
let in_parens = matches!(stack.last(), Some((Open::Paren | Open::Interp(_), _)));
if in_parens || previous.is_some_and(continues_after) {
layout.ignored[index] = true;
} else {
pending.push(index);
}
continue;
}
let continues = continues_before(kind);
for newline in pending.drain(..) {
layout.ignored[newline] = continues;
}
let push = |stack: &mut Vec<(Open, [usize; 3])>, open: Open| {
let mut counts = match (open, stack.last()) {
(Open::Interp(_) | Open::Str { .. }, _) | (_, None) => [0; 3],
(_, Some(&(_, counts))) => counts,
};
if let Some(slot) = open.bracket_slot() {
counts[slot] += 1;
}
stack.push((open, counts));
};
match kind {
T::LParen => push(&mut stack, Open::Paren),
T::LBrace => push(&mut stack, Open::Brace),
T::LBracket => push(&mut stack, Open::Bracket),
T::RParen | T::RBrace | T::RBracket => {
let slot = match kind {
T::RParen => 0,
T::RBrace => 1,
_ => 2,
};
if stack.last().is_some_and(|&(_, counts)| counts[slot] > 0) {
while let Some((open, _)) = stack.pop() {
if open.bracket_slot() == Some(slot) {
break;
}
}
}
}
T::InterpStart => push(&mut stack, Open::Interp(index)),
T::InterpEnd => {
if stack
.iter()
.any(|&(open, _)| matches!(open, Open::Interp(_)))
{
while let Some(open) =
layout.pop(&mut stack, &mut single_line_strings, index)
{
if matches!(open, Open::Interp(_)) {
break;
}
}
}
}
T::StrStart | T::RawStrStart => {
single_line_strings += 1;
push(
&mut stack,
Open::Str {
start: index,
single_line: true,
},
);
}
T::MStrStart | T::RawMStrStart => push(
&mut stack,
Open::Str {
start: index,
single_line: false,
},
),
T::StrEnd | T::MStrEnd => {
if stack
.iter()
.any(|&(open, _)| matches!(open, Open::Str { .. }))
{
while let Some(open) =
layout.pop(&mut stack, &mut single_line_strings, index + 1)
{
if matches!(open, Open::Str { .. }) {
break;
}
}
}
}
T::Eof => {
while layout
.pop(&mut stack, &mut single_line_strings, index)
.is_some()
{}
}
_ => {}
}
previous = Some(kind);
}
layout
}
fn pop(
&mut self,
stack: &mut Vec<(Open, [usize; 3])>,
single_line_strings: &mut usize,
end: usize,
) -> Option<Open> {
let (open, _) = stack.pop()?;
match open {
Open::Interp(start) => self.ends[start] = end,
Open::Str { start, single_line } => {
self.ends[start] = end;
if single_line {
*single_line_strings -= 1;
}
}
_ => {}
}
Some(open)
}
}
fn continues_after(kind: TokenKind) -> bool {
use TokenKind as T;
matches!(
kind,
T::LBrace
| T::LBracket
| T::LParen
| T::Comma
| T::Eq
| T::Dot
| T::QuestionDot
| T::QuestionQuestion
| T::Colon
| T::Arrow
| T::FatArrow
| T::Pipe
| T::Plus
| T::Minus
| T::Star
| T::Slash
| T::Percent
| T::PlusPlus
| T::EqEq
| T::NotEq
| T::Lt
| T::LtEq
| T::GtEq
| T::AndAnd
| T::OrOr
| T::Bang
| T::InKw
| T::ElseKw
| T::WhereKw
| T::ImportKw
| T::IncludeKw
| T::AsKw
)
}
fn continues_before(kind: TokenKind) -> bool {
use TokenKind as T;
matches!(
kind,
T::Dot
| T::QuestionDot
| T::QuestionQuestion
| T::AndAnd
| T::OrOr
| T::PlusPlus
| T::ElseKw
| T::Pipe
| T::FatArrow
)
}
fn is_string_start(kind: TokenKind) -> bool {
use TokenKind as T;
matches!(
kind,
T::StrStart | T::MStrStart | T::RawStrStart | T::RawMStrStart
)
}
fn is_name(kind: TokenKind) -> bool {
matches!(kind, TokenKind::Ident | TokenKind::TypeIdent)
}
fn opener_of(closer: TokenKind) -> Option<TokenKind> {
use TokenKind as T;
match closer {
T::RParen => Some(T::LParen),
T::RBracket => Some(T::LBracket),
T::RBrace => Some(T::LBrace),
_ => None,
}
}
fn is_closer(kind: TokenKind) -> bool {
opener_of(kind).is_some()
}
fn opener_text(kind: TokenKind) -> &'static str {
use TokenKind as T;
match kind {
T::LParen => "(",
T::LBracket => "[",
_ => "{",
}
}
fn closer_text(kind: TokenKind) -> &'static str {
use TokenKind as T;
match kind {
T::RParen => ")",
T::RBracket => "]",
_ => "}",
}
}
#[cfg(test)]
#[path = "parser_tests.rs"]
mod tests;