1pub mod declaration;
2pub mod expr;
3pub mod module;
4pub mod pattern;
5pub mod type_annotation;
6
7use crate::comment::Comment;
8use crate::node::Spanned;
9use crate::span::{Position, Span};
10use crate::token::Token;
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct ParseError {
15 pub message: String,
16 pub span: Span,
17}
18
19impl std::fmt::Display for ParseError {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(
22 f,
23 "{}:{}: {}",
24 self.span.start.line, self.span.start.column, self.message
25 )
26 }
27}
28
29impl std::error::Error for ParseError {}
30
31pub type ParseResult<T> = Result<T, ParseError>;
32
33pub(crate) const MAX_EXPR_DEPTH: usize = 256;
44
45pub struct Parser {
46 tokens: Vec<Spanned<Token>>,
47 pos: usize,
48 paren_depth: u32,
52 pub(crate) app_context_col: Option<u32>,
58 collected_comments: Vec<Spanned<Comment>>,
62}
63
64impl Parser {
65 pub fn new(tokens: Vec<Spanned<Token>>) -> Self {
67 Self {
68 tokens,
69 pos: 0,
70 paren_depth: 0,
71 app_context_col: None,
72 collected_comments: Vec::new(),
73 }
74 }
75
76 pub fn drain_comments(&mut self) -> Vec<Spanned<Comment>> {
78 std::mem::take(&mut self.collected_comments)
79 }
80
81 pub fn take_pending_comments(&mut self) -> Vec<Spanned<Comment>> {
84 std::mem::take(&mut self.collected_comments)
85 }
86
87 pub fn pending_comments_snapshot(&self) -> usize {
91 self.collected_comments.len()
92 }
93
94 pub fn take_pending_comments_since(&mut self, snapshot: usize) -> Vec<Spanned<Comment>> {
97 if snapshot >= self.collected_comments.len() {
98 return Vec::new();
99 }
100 self.collected_comments.split_off(snapshot)
101 }
102
103 pub fn restore_pending_comments(&mut self, comments: Vec<Spanned<Comment>>) {
107 self.collected_comments.extend(comments);
108 }
109
110 pub fn in_paren_context(&self) -> bool {
113 self.paren_depth > 0
114 }
115
116 pub fn current(&self) -> &Spanned<Token> {
120 &self.tokens[self.pos.min(self.tokens.len() - 1)]
121 }
122
123 pub fn peek(&self) -> &Token {
125 &self.current().value
126 }
127
128 pub fn peek_span(&self) -> Span {
130 self.current().span
131 }
132
133 pub fn current_pos(&self) -> Position {
135 self.current().span.start
136 }
137
138 pub fn current_column(&self) -> u32 {
140 self.current().span.start.column
141 }
142
143 pub fn is_eof(&self) -> bool {
145 matches!(self.peek(), Token::Eof)
146 }
147
148 pub fn prev_token_end_offset(&self) -> usize {
153 if self.pos == 0 {
154 0
155 } else {
156 self.tokens[self.pos - 1].span.end.offset
157 }
158 }
159
160 pub fn peek_raw_next(&self) -> &Spanned<Token> {
163 let i = self.pos + 1;
164 if i < self.tokens.len() {
165 &self.tokens[i]
166 } else {
167 &self.tokens[self.tokens.len() - 1]
168 }
169 }
170
171 pub fn advance(&mut self) -> Spanned<Token> {
176 let tok = self.tokens[self.pos.min(self.tokens.len() - 1)].clone();
177 match &tok.value {
179 Token::LeftParen | Token::LeftBracket | Token::LeftBrace => {
180 self.paren_depth += 1;
181 }
182 Token::RightParen | Token::RightBracket | Token::RightBrace => {
183 self.paren_depth = self.paren_depth.saturating_sub(1);
184 }
185 _ => {}
186 }
187 if self.pos < self.tokens.len() - 1 {
188 self.pos += 1;
189 }
190 tok
191 }
192
193 pub fn skip_whitespace(&mut self) {
196 while matches!(
197 self.peek(),
198 Token::Newline | Token::LineComment(_) | Token::BlockComment(_)
199 ) {
200 let Spanned { span, value, .. } = self.advance();
201 match value {
202 Token::LineComment(text) => {
203 self.collected_comments
204 .push(Spanned::new(span, Comment::Line(text)));
205 }
206 Token::BlockComment(text) => {
207 self.collected_comments
208 .push(Spanned::new(span, Comment::Block(text)));
209 }
210 _ => {} }
212 }
213 }
214
215 pub fn skip_whitespace_before_doc(&mut self) {
219 while matches!(
220 self.peek(),
221 Token::Newline | Token::LineComment(_) | Token::BlockComment(_)
222 ) {
223 let Spanned { span, value, .. } = self.advance();
224 match value {
225 Token::LineComment(text) => {
226 self.collected_comments
227 .push(Spanned::new(span, Comment::Line(text)));
228 }
229 Token::BlockComment(text) => {
230 self.collected_comments
231 .push(Spanned::new(span, Comment::Block(text)));
232 }
233 _ => {} }
235 }
236 }
237
238 pub fn skip_newlines(&mut self) {
240 while matches!(self.peek(), Token::Newline) {
241 self.advance();
242 }
243 }
244
245 pub fn expect(&mut self, expected: &Token) -> ParseResult<Spanned<Token>> {
249 self.skip_whitespace();
250 if self.peek() == expected {
251 Ok(self.advance())
252 } else {
253 Err(self.error(format!(
254 "expected {}, found {}",
255 describe(expected),
256 describe(self.peek())
257 )))
258 }
259 }
260
261 pub fn expect_lower_name(&mut self) -> ParseResult<Spanned<String>> {
263 self.skip_whitespace();
264 if matches!(self.peek(), Token::LowerName(_)) {
265 let Spanned { span, value, .. } = self.advance();
266 let Token::LowerName(name) = value else {
267 unreachable!("matched LowerName above")
268 };
269 Ok(Spanned::new(span, name))
270 } else {
271 Err(self.error(format!(
272 "expected lowercase name, found {}",
273 describe(self.peek())
274 )))
275 }
276 }
277
278 pub fn expect_upper_name(&mut self) -> ParseResult<Spanned<String>> {
280 self.skip_whitespace();
281 if matches!(self.peek(), Token::UpperName(_)) {
282 let Spanned { span, value, .. } = self.advance();
283 let Token::UpperName(name) = value else {
284 unreachable!("matched UpperName above")
285 };
286 Ok(Spanned::new(span, name))
287 } else {
288 Err(self.error(format!(
289 "expected uppercase name, found {}",
290 describe(self.peek())
291 )))
292 }
293 }
294
295 pub fn check(&mut self, expected: &Token) -> bool {
300 self.skip_whitespace();
301 self.peek() == expected
302 }
303
304 pub fn eat(&mut self, expected: &Token) -> bool {
306 self.skip_whitespace();
307 if self.peek() == expected {
308 self.advance();
309 true
310 } else {
311 false
312 }
313 }
314
315 pub fn peek_past_whitespace_offset(&self) -> usize {
318 let mut i = self.pos;
319 while i < self.tokens.len() {
320 match &self.tokens[i].value {
321 Token::Newline
322 | Token::LineComment(_)
323 | Token::BlockComment(_)
324 | Token::DocComment(_) => i += 1,
325 _ => return self.tokens[i].span.start.offset,
326 }
327 }
328 self.tokens.last().map(|t| t.span.end.offset).unwrap_or(0)
329 }
330
331 pub fn peek_past_whitespace(&self) -> &Token {
334 let mut i = self.pos;
335 while i < self.tokens.len() {
336 match &self.tokens[i].value {
337 Token::Newline
338 | Token::LineComment(_)
339 | Token::BlockComment(_)
340 | Token::DocComment(_) => i += 1,
341 tok => return tok,
342 }
343 }
344 &Token::Eof
345 }
346
347 pub fn peek_nth_past_whitespace(&self, n: usize) -> &Token {
349 let mut i = self.pos;
350 let mut count = 0;
351 while i < self.tokens.len() {
352 match &self.tokens[i].value {
353 Token::Newline
354 | Token::LineComment(_)
355 | Token::BlockComment(_)
356 | Token::DocComment(_) => i += 1,
357 tok => {
358 if count == n {
359 return tok;
360 }
361 count += 1;
362 i += 1;
363 }
364 }
365 }
366 &Token::Eof
367 }
368
369 pub fn is_indented_past(&mut self, min_col: u32) -> bool {
374 self.skip_newlines();
375 !self.is_eof() && (self.in_paren_context() || self.current_column() > min_col)
376 }
377
378 pub fn is_at_or_past(&mut self, min_col: u32) -> bool {
381 self.skip_newlines();
382 !self.is_eof() && (self.in_paren_context() || self.current_column() >= min_col)
383 }
384
385 pub fn try_doc_comment(&mut self) -> Option<Spanned<String>> {
389 self.skip_whitespace_before_doc();
390 if matches!(self.peek(), Token::DocComment(_)) {
391 let Spanned { span, value, .. } = self.advance();
392 let Token::DocComment(text) = value else {
393 unreachable!("matched DocComment above")
394 };
395 Some(Spanned::new(span, text))
396 } else {
397 None
398 }
399 }
400
401 pub fn error(&self, message: impl Into<String>) -> ParseError {
404 ParseError {
405 message: message.into(),
406 span: self.peek_span(),
407 }
408 }
409
410 pub fn error_at(&self, span: Span, message: impl Into<String>) -> ParseError {
411 ParseError {
412 message: message.into(),
413 span,
414 }
415 }
416
417 pub fn span_from(&self, start: Position) -> Span {
426 let mut i = self.pos;
427 while i > 0
428 && matches!(
429 self.tokens[i - 1].value,
430 Token::Newline
431 | Token::LineComment(_)
432 | Token::BlockComment(_)
433 | Token::DocComment(_)
434 )
435 {
436 i -= 1;
437 }
438 let end = if i > 0 {
439 self.tokens[i - 1].span.end
440 } else {
441 start
442 };
443 Span::new(start, end)
444 }
445
446 pub fn spanned_from<T>(&self, start: Position, value: T) -> Spanned<T> {
448 Spanned::new(self.span_from(start), value)
449 }
450
451 pub fn skip_to_next_declaration(&mut self) {
457 loop {
458 self.skip_whitespace();
459 if self.is_eof() {
460 break;
461 }
462 let col = self.current_column();
463 let tok = self.peek();
464 if col == 1
466 && matches!(
467 tok,
468 Token::LowerName(_)
469 | Token::Type
470 | Token::Port
471 | Token::Infix
472 | Token::DocComment(_)
473 )
474 {
475 break;
476 }
477 self.advance();
478 }
479 }
480}
481
482fn describe(tok: &Token) -> String {
484 match tok {
485 Token::Module => "`module`".into(),
486 Token::Where => "`where`".into(),
487 Token::Import => "`import`".into(),
488 Token::As => "`as`".into(),
489 Token::Exposing => "`exposing`".into(),
490 Token::Type => "`type`".into(),
491 Token::Alias => "`alias`".into(),
492 Token::Port => "`port`".into(),
493 Token::If => "`if`".into(),
494 Token::Then => "`then`".into(),
495 Token::Else => "`else`".into(),
496 Token::Case => "`case`".into(),
497 Token::Of => "`of`".into(),
498 Token::Let => "`let`".into(),
499 Token::In => "`in`".into(),
500 Token::Infix => "`infix`".into(),
501 Token::LeftParen => "`(`".into(),
502 Token::RightParen => "`)`".into(),
503 Token::LeftBracket => "`[`".into(),
504 Token::RightBracket => "`]`".into(),
505 Token::LeftBrace => "`{`".into(),
506 Token::RightBrace => "`}`".into(),
507 Token::Comma => "`,`".into(),
508 Token::Pipe => "`|`".into(),
509 Token::Equals => "`=`".into(),
510 Token::Colon => "`:`".into(),
511 Token::Dot => "`.`".into(),
512 Token::DotDot => "`..`".into(),
513 Token::Backslash => "`\\`".into(),
514 Token::Underscore => "`_`".into(),
515 Token::Arrow => "`->`".into(),
516 Token::Operator(op) => format!("`{op}`"),
517 Token::Minus => "`-`".into(),
518 Token::LowerName(n) => format!("identifier `{n}`"),
519 Token::UpperName(n) => format!("type `{n}`"),
520 Token::Literal(_) => "literal".into(),
521 Token::LineComment(_) => "comment".into(),
522 Token::BlockComment(_) => "comment".into(),
523 Token::DocComment(_) => "doc comment".into(),
524 Token::Glsl(_) => "GLSL block".into(),
525 Token::Newline => "newline".into(),
526 Token::Eof => "end of file".into(),
527 }
528}
529
530pub fn parse(source: &str) -> Result<crate::file::ElmModule, Vec<ParseError>> {
536 let lexer = crate::lexer::Lexer::new(source);
537 let (tokens, lex_errors) = lexer.tokenize();
538
539 if !lex_errors.is_empty() {
540 return Err(lex_errors
541 .into_iter()
542 .map(|e| ParseError {
543 message: e.message,
544 span: e.span,
545 })
546 .collect());
547 }
548
549 let mut parser = Parser::new(tokens);
550 module::parse_module(&mut parser).map_err(|e| vec![e])
551}
552
553pub fn parse_recovering(source: &str) -> (Option<crate::file::ElmModule>, Vec<ParseError>) {
559 let lexer = crate::lexer::Lexer::new(source);
560 let (tokens, lex_errors) = lexer.tokenize();
561
562 if !lex_errors.is_empty() {
563 return (
564 None,
565 lex_errors
566 .into_iter()
567 .map(|e| ParseError {
568 message: e.message,
569 span: e.span,
570 })
571 .collect(),
572 );
573 }
574
575 let mut parser = Parser::new(tokens);
576 module::parse_module_recovering(&mut parser)
577}