mod expr;
mod items;
mod skeleton;
mod ty;
use wolf_ast::{File, NodeId};
use wolf_diag::{Diagnostic, codes};
use wolf_lexer::{Token, TokenKind};
use wolf_span::{FileId, Span};
pub const MAX_DEPTH: u32 = 256;
pub const STACK_SIZE: usize = 64 * 1024 * 1024;
#[derive(Debug)]
pub struct Parsed {
pub file: File,
pub diagnostics: Vec<Diagnostic>,
}
pub fn parse(file: FileId, text: &str, tokens: &[Token]) -> Parsed {
let mut parser = Parser::new(file, text, tokens);
let tree = parser.file();
let too_deep = parser.depth_reported;
let mut diagnostics = parser.diagnostics;
if !too_deep {
diagnostics.extend(skeleton::check(file, &tree));
}
Parsed {
file: tree,
diagnostics,
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Lines {
Significant,
Ignored,
}
pub(crate) struct Parser<'a> {
file: FileId,
text: &'a str,
tokens: Vec<Token>,
pos: usize,
lines: Vec<Lines>,
pub(crate) diagnostics: Vec<Diagnostic>,
next_id: u32,
depth: u32,
depth_reported: bool,
pub(crate) resync_to_top: bool,
poisoned: bool,
pub(crate) line_errors: usize,
pub(crate) labels: Vec<String>,
}
impl<'a> Parser<'a> {
fn new(file: FileId, text: &'a str, tokens: &[Token]) -> Parser<'a> {
let mut tokens = tokens.to_vec();
let ends_with_eof = tokens.last().is_some_and(|t| t.kind == TokenKind::Eof);
if !ends_with_eof {
let end = u32::try_from(text.len()).unwrap_or(u32::MAX);
tokens.push(Token {
kind: TokenKind::Eof,
span: Span::new(file, end, end),
});
}
Parser {
file,
text,
tokens,
pos: 0,
lines: vec![Lines::Significant],
diagnostics: Vec::new(),
next_id: 0,
depth: 0,
depth_reported: false,
resync_to_top: false,
poisoned: false,
line_errors: 0,
labels: Vec::new(),
}
}
fn raw(&self, n: usize) -> &Token {
let index = (self.pos + n).min(self.tokens.len().saturating_sub(1));
match self.tokens.get(index) {
Some(token) => token,
None => &EOF_TOKEN,
}
}
fn ignoring_lines(&self) -> bool {
self.lines.last() == Some(&Lines::Ignored)
}
fn visible_index(&self, n: usize) -> usize {
let mut index = self.pos;
let mut seen = 0;
loop {
let token = self.tokens.get(index);
let skip = self.ignoring_lines() && token.is_some_and(|t| t.kind == TokenKind::Newline);
if !skip {
if seen == n || token.is_none_or(|t| t.kind == TokenKind::Eof) {
return index.min(self.tokens.len().saturating_sub(1));
}
seen += 1;
}
index += 1;
}
}
pub(crate) fn nth(&self, n: usize) -> &Token {
match self.tokens.get(self.visible_index(n)) {
Some(token) => token,
None => &EOF_TOKEN,
}
}
pub(crate) fn peek(&self) -> &TokenKind {
&self.nth(0).kind
}
pub(crate) fn peek_span(&self) -> Span {
self.nth(0).span
}
pub(crate) fn at(&self, kind: &TokenKind) -> bool {
same_kind(self.peek(), kind)
}
pub(crate) fn nth_is(&self, n: usize, kind: &TokenKind) -> bool {
same_kind(&self.nth(n).kind, kind)
}
pub(crate) fn at_word(&self, word: &str) -> bool {
self.nth_word(0, word)
}
pub(crate) fn nth_word(&self, n: usize, word: &str) -> bool {
let token = self.nth(n);
token.kind == TokenKind::Ident && self.source(token.span) == word
}
pub(crate) fn bump(&mut self) -> Token {
let index = self.visible_index(0);
let token = self.tokens.get(index).cloned().unwrap_or(EOF_TOKEN.clone());
if token.kind == TokenKind::Error {
self.poisoned = true;
}
if token.kind != TokenKind::Eof {
self.pos = index + 1;
} else {
self.pos = index;
}
token
}
pub(crate) fn eat(&mut self, kind: &TokenKind) -> bool {
if self.at(kind) {
self.bump();
true
} else {
false
}
}
pub(crate) fn eat_then_lines(&mut self, kind: &TokenKind) -> bool {
if self.eat(kind) {
self.skip_newlines();
true
} else {
false
}
}
pub(crate) fn expect_then_lines(&mut self, kind: &TokenKind, what: &str) -> Option<Span> {
let span = self.expect(kind, what)?;
self.skip_newlines();
Some(span)
}
pub(crate) fn eat_word(&mut self, word: &str) -> bool {
if self.at_word(word) {
self.bump();
true
} else {
false
}
}
pub(crate) fn expect(&mut self, kind: &TokenKind, what: &str) -> Option<Span> {
if self.at(kind) {
return Some(self.bump().span);
}
self.unexpected(what);
None
}
pub(crate) fn skip_newlines(&mut self) {
while self.raw(0).kind == TokenKind::Newline {
self.pos += 1;
}
}
pub(crate) fn at_line_end(&self) -> bool {
matches!(self.raw(0).kind, TokenKind::Newline | TokenKind::Eof)
}
pub(crate) fn source(&self, span: Span) -> &'a str {
self.text.get(span.range()).unwrap_or("")
}
pub(crate) fn prev_span(&self) -> Span {
let index = self.pos.saturating_sub(1);
self.tokens
.get(index)
.map(|t| t.span)
.unwrap_or(Span::new(self.file, 0, 0))
}
pub(crate) fn span_from(&self, start: Span) -> Span {
let end = self.prev_span();
if end.end < start.start {
start
} else {
start.to(end)
}
}
pub(crate) fn with_lines<T>(&mut self, mode: Lines, f: impl FnOnce(&mut Self) -> T) -> T {
self.lines.push(mode);
let result = f(self);
self.lines.pop();
result
}
pub(crate) fn ignore_lines<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.with_lines(Lines::Ignored, f)
}
pub(crate) fn significant_lines<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.with_lines(Lines::Significant, f)
}
pub(crate) fn id(&mut self) -> NodeId {
let id = NodeId(self.next_id);
self.next_id += 1;
id
}
pub(crate) fn report(&mut self, diagnostic: Diagnostic) {
if self.depth_reported && diagnostic.code != codes::E0314 {
return;
}
self.diagnostics.push(diagnostic);
}
pub(crate) fn unexpected(&mut self, expected: &str) {
let token = self.nth(0).clone();
if token.kind == TokenKind::Error || self.depth_reported || self.poisoned {
return;
}
let found = describe(&token, self.source(token.span));
let span = if token.kind == TokenKind::Newline {
Span::new(token.span.file, token.span.start, token.span.start)
} else {
token.span
};
self.report(
Diagnostic::new(codes::E0301, format!("expected {expected}, found {found}"))
.primary(span, format!("expected {expected}")),
);
}
pub(crate) fn end_line_if_poisoned(&mut self) -> bool {
if !self.poisoned {
return false;
}
self.recover();
self.poisoned = false;
true
}
pub(crate) fn start_line(&mut self) -> usize {
self.poisoned = false;
self.diagnostics.len()
}
pub(crate) fn end_line_after_error(&mut self, errors_before: usize) -> bool {
if self.resync_to_top {
return true;
}
if self.end_line_if_poisoned() {
return true;
}
if matches!(self.raw(0).kind, TokenKind::DotDot | TokenKind::DotDotEq) {
let span = self.raw(0).span;
self.report(
Diagnostic::new(codes::E0307, "range outside `for` or index brackets").primary(
span,
"ranges are allowed only in `for i in a..b` and `items[a..b]`",
),
);
self.recover();
return true;
}
if self.diagnostics.len() > errors_before {
self.recover();
return true;
}
false
}
pub(crate) fn recover(&mut self) {
let mut depth = 0u32;
loop {
let kind = self.raw(0).kind.clone();
match kind {
TokenKind::Eof => return,
TokenKind::Newline if depth == 0 && !self.ignoring_lines() => return,
TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => depth += 1,
TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
if depth == 0 {
return;
}
depth -= 1;
}
_ => {}
}
self.pos += 1;
}
}
pub(crate) fn enter(&mut self) -> bool {
self.depth += 1;
if self.depth <= MAX_DEPTH {
return true;
}
if !self.depth_reported {
self.depth_reported = true;
let span = self.peek_span();
self.report(
Diagnostic::new(codes::E0314, "nesting too deep")
.primary(span, format!("more than {MAX_DEPTH} nested levels"))
.help("split the expression or the code into smaller parts"),
);
}
false
}
pub(crate) fn leave(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
pub(crate) fn give_up(&mut self) {
self.pos = self.tokens.len().saturating_sub(1);
}
pub(crate) fn too_deep(&self) -> bool {
self.depth_reported
}
}
static EOF_TOKEN: Token = Token {
kind: TokenKind::Eof,
span: Span {
file: FILE_ZERO,
start: 0,
end: 0,
},
};
const FILE_ZERO: FileId = wolf_span::FileId::ZERO;
pub(crate) fn same_kind(a: &TokenKind, b: &TokenKind) -> bool {
std::mem::discriminant(a) == std::mem::discriminant(b)
}
pub(crate) fn describe(token: &Token, source: &str) -> String {
match token.kind {
TokenKind::Ident => format!("identifier `{source}`"),
TokenKind::Int(_) | TokenKind::Float(_) | TokenKind::Str(_) => format!("`{source}`"),
_ => token.kind.to_string(),
}
}