pub mod declaration;
pub mod expr;
pub mod module;
pub mod pattern;
pub mod type_annotation;
use crate::comment::Comment;
use crate::node::Spanned;
use crate::span::{Position, Span};
use crate::token::Token;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseError {
pub message: String,
pub span: Span,
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}: {}",
self.span.start.line, self.span.start.column, self.message
)
}
}
impl std::error::Error for ParseError {}
pub type ParseResult<T> = Result<T, ParseError>;
pub(crate) const MAX_EXPR_DEPTH: usize = 256;
pub struct Parser {
tokens: Vec<Spanned<Token>>,
pos: usize,
paren_depth: u32,
pub(crate) app_context_col: Option<u32>,
collected_comments: Vec<Spanned<Comment>>,
}
impl Parser {
pub fn new(tokens: Vec<Spanned<Token>>) -> Self {
Self {
tokens,
pos: 0,
paren_depth: 0,
app_context_col: None,
collected_comments: Vec::new(),
}
}
pub fn drain_comments(&mut self) -> Vec<Spanned<Comment>> {
std::mem::take(&mut self.collected_comments)
}
pub fn take_pending_comments(&mut self) -> Vec<Spanned<Comment>> {
std::mem::take(&mut self.collected_comments)
}
pub fn in_paren_context(&self) -> bool {
self.paren_depth > 0
}
pub fn current(&self) -> &Spanned<Token> {
&self.tokens[self.pos.min(self.tokens.len() - 1)]
}
pub fn peek(&self) -> &Token {
&self.current().value
}
pub fn peek_span(&self) -> Span {
self.current().span
}
pub fn current_pos(&self) -> Position {
self.current().span.start
}
pub fn current_column(&self) -> u32 {
self.current().span.start.column
}
pub fn is_eof(&self) -> bool {
matches!(self.peek(), Token::Eof)
}
pub fn advance(&mut self) -> Spanned<Token> {
let tok = self.tokens[self.pos.min(self.tokens.len() - 1)].clone();
match &tok.value {
Token::LeftParen | Token::LeftBracket | Token::LeftBrace => {
self.paren_depth += 1;
}
Token::RightParen | Token::RightBracket | Token::RightBrace => {
self.paren_depth = self.paren_depth.saturating_sub(1);
}
_ => {}
}
if self.pos < self.tokens.len() - 1 {
self.pos += 1;
}
tok
}
pub fn skip_whitespace(&mut self) {
while matches!(
self.peek(),
Token::Newline | Token::LineComment(_) | Token::BlockComment(_) | Token::DocComment(_)
) {
let tok = self.peek().clone();
let spanned_tok = self.advance();
match tok {
Token::LineComment(text) => {
self.collected_comments
.push(Spanned::new(spanned_tok.span, Comment::Line(text)));
}
Token::BlockComment(text) => {
self.collected_comments
.push(Spanned::new(spanned_tok.span, Comment::Block(text)));
}
_ => {} }
}
}
pub fn skip_whitespace_before_doc(&mut self) {
while matches!(
self.peek(),
Token::Newline | Token::LineComment(_) | Token::BlockComment(_)
) {
let tok = self.peek().clone();
let spanned_tok = self.advance();
match tok {
Token::LineComment(text) => {
self.collected_comments
.push(Spanned::new(spanned_tok.span, Comment::Line(text)));
}
Token::BlockComment(text) => {
self.collected_comments
.push(Spanned::new(spanned_tok.span, Comment::Block(text)));
}
_ => {} }
}
}
pub fn skip_newlines(&mut self) {
while matches!(self.peek(), Token::Newline) {
self.advance();
}
}
pub fn expect(&mut self, expected: &Token) -> ParseResult<Spanned<Token>> {
self.skip_whitespace();
if self.peek() == expected {
Ok(self.advance())
} else {
Err(self.error(format!(
"expected {}, found {}",
describe(expected),
describe(self.peek())
)))
}
}
pub fn expect_lower_name(&mut self) -> ParseResult<Spanned<String>> {
self.skip_whitespace();
match self.peek().clone() {
Token::LowerName(name) => {
let tok = self.advance();
Ok(Spanned::new(tok.span, name))
}
_ => Err(self.error(format!(
"expected lowercase name, found {}",
describe(self.peek())
))),
}
}
pub fn expect_upper_name(&mut self) -> ParseResult<Spanned<String>> {
self.skip_whitespace();
match self.peek().clone() {
Token::UpperName(name) => {
let tok = self.advance();
Ok(Spanned::new(tok.span, name))
}
_ => Err(self.error(format!(
"expected uppercase name, found {}",
describe(self.peek())
))),
}
}
pub fn check(&mut self, expected: &Token) -> bool {
self.skip_whitespace();
self.peek() == expected
}
pub fn eat(&mut self, expected: &Token) -> bool {
self.skip_whitespace();
if self.peek() == expected {
self.advance();
true
} else {
false
}
}
pub fn peek_past_whitespace(&self) -> &Token {
let mut i = self.pos;
while i < self.tokens.len() {
match &self.tokens[i].value {
Token::Newline
| Token::LineComment(_)
| Token::BlockComment(_)
| Token::DocComment(_) => i += 1,
tok => return tok,
}
}
&Token::Eof
}
pub fn peek_nth_past_whitespace(&self, n: usize) -> &Token {
let mut i = self.pos;
let mut count = 0;
while i < self.tokens.len() {
match &self.tokens[i].value {
Token::Newline
| Token::LineComment(_)
| Token::BlockComment(_)
| Token::DocComment(_) => i += 1,
tok => {
if count == n {
return tok;
}
count += 1;
i += 1;
}
}
}
&Token::Eof
}
pub fn is_indented_past(&mut self, min_col: u32) -> bool {
self.skip_newlines();
!self.is_eof() && (self.in_paren_context() || self.current_column() > min_col)
}
pub fn is_at_or_past(&mut self, min_col: u32) -> bool {
self.skip_newlines();
!self.is_eof() && (self.in_paren_context() || self.current_column() >= min_col)
}
pub fn try_doc_comment(&mut self) -> Option<Spanned<String>> {
self.skip_whitespace_before_doc();
if let Token::DocComment(text) = self.peek().clone() {
let tok = self.advance();
Some(Spanned::new(tok.span, text))
} else {
None
}
}
pub fn error(&self, message: impl Into<String>) -> ParseError {
ParseError {
message: message.into(),
span: self.peek_span(),
}
}
pub fn error_at(&self, span: Span, message: impl Into<String>) -> ParseError {
ParseError {
message: message.into(),
span,
}
}
pub fn span_from(&self, start: Position) -> Span {
let end = if self.pos > 0 {
self.tokens[self.pos - 1].span.end
} else {
start
};
Span::new(start, end)
}
pub fn spanned_from<T>(&self, start: Position, value: T) -> Spanned<T> {
Spanned::new(self.span_from(start), value)
}
pub fn skip_to_next_declaration(&mut self) {
loop {
self.skip_whitespace();
if self.is_eof() {
break;
}
let col = self.current_column();
let tok = self.peek();
if col == 1
&& matches!(
tok,
Token::LowerName(_)
| Token::Type
| Token::Port
| Token::Infix
| Token::DocComment(_)
)
{
break;
}
self.advance();
}
}
}
fn describe(tok: &Token) -> String {
match tok {
Token::Module => "`module`".into(),
Token::Where => "`where`".into(),
Token::Import => "`import`".into(),
Token::As => "`as`".into(),
Token::Exposing => "`exposing`".into(),
Token::Type => "`type`".into(),
Token::Alias => "`alias`".into(),
Token::Port => "`port`".into(),
Token::If => "`if`".into(),
Token::Then => "`then`".into(),
Token::Else => "`else`".into(),
Token::Case => "`case`".into(),
Token::Of => "`of`".into(),
Token::Let => "`let`".into(),
Token::In => "`in`".into(),
Token::Infix => "`infix`".into(),
Token::LeftParen => "`(`".into(),
Token::RightParen => "`)`".into(),
Token::LeftBracket => "`[`".into(),
Token::RightBracket => "`]`".into(),
Token::LeftBrace => "`{`".into(),
Token::RightBrace => "`}`".into(),
Token::Comma => "`,`".into(),
Token::Pipe => "`|`".into(),
Token::Equals => "`=`".into(),
Token::Colon => "`:`".into(),
Token::Dot => "`.`".into(),
Token::DotDot => "`..`".into(),
Token::Backslash => "`\\`".into(),
Token::Underscore => "`_`".into(),
Token::Arrow => "`->`".into(),
Token::Operator(op) => format!("`{op}`"),
Token::Minus => "`-`".into(),
Token::LowerName(n) => format!("identifier `{n}`"),
Token::UpperName(n) => format!("type `{n}`"),
Token::Literal(_) => "literal".into(),
Token::LineComment(_) => "comment".into(),
Token::BlockComment(_) => "comment".into(),
Token::DocComment(_) => "doc comment".into(),
Token::Glsl(_) => "GLSL block".into(),
Token::Newline => "newline".into(),
Token::Eof => "end of file".into(),
}
}
pub fn parse(source: &str) -> Result<crate::file::ElmModule, Vec<ParseError>> {
let lexer = crate::lexer::Lexer::new(source);
let (tokens, lex_errors) = lexer.tokenize();
if !lex_errors.is_empty() {
return Err(lex_errors
.into_iter()
.map(|e| ParseError {
message: e.message,
span: e.span,
})
.collect());
}
let mut parser = Parser::new(tokens);
module::parse_module(&mut parser).map_err(|e| vec![e])
}
pub fn parse_recovering(source: &str) -> (Option<crate::file::ElmModule>, Vec<ParseError>) {
let lexer = crate::lexer::Lexer::new(source);
let (tokens, lex_errors) = lexer.tokenize();
if !lex_errors.is_empty() {
return (
None,
lex_errors
.into_iter()
.map(|e| ParseError {
message: e.message,
span: e.span,
})
.collect(),
);
}
let mut parser = Parser::new(tokens);
module::parse_module_recovering(&mut parser)
}