use std::cmp::min; use codespan_reporting::diagnostic::{Diagnostic, Label}; use crate::source::{SourceFile, SourceSpan}; // TODO Add warnings for things like trailing whitespace #[derive(Debug)] pub struct ParseError(SourceSpan, String); impl From<&ParseError> for Diagnostic { fn from(e: &ParseError) -> Self { Self::error() .with_message(&e.1) .with_labels(vec![Label::primary(e.0.file_id(), e.0.range())]) } } pub type ParseResult = Result; #[derive(Debug)] pub struct Parser<'a> { file: SourceFile, content: &'a str, offset: usize, errors: Vec, } impl<'a> Parser<'a> { pub fn new(file: SourceFile, content: &'a str) -> Self { Self { file, content, offset: 0, errors: vec![], } } pub fn peek(&self, amount: usize) -> &'a str { // self.offset is always a valid start since take() ensures it is in the // range 0..self.content.len() let end = min(self.content.len(), self.offset + amount); &self.content[self.offset..end] } pub fn peek_rest(&self) -> &'a str { // self.offset is always a valid start since take() ensures it is in the // range 0..self.content.len() &self.content[self.offset..] } pub fn peek_line(&self) -> &'a str { if let Some(i) = self.peek_rest().find('\n') { self.peek(i) } else { self.peek_rest() } } pub fn at(&self) -> usize { self.offset } pub fn at_eof(&self) -> bool { self.offset >= self.content.len() } pub fn take(&mut self, amount: usize) { // Ensure the offset always stays in the range 0..self.content.len() self.offset = min(self.content.len(), self.offset + amount); } pub fn take_line(&mut self) { self.take(self.peek_line().len() + 1); } fn error(&self, at: usize, error: impl ToString) -> ParseError { ParseError(self.file.span(at..at), error.to_string()) } pub fn uncritical(&mut self, at: usize, error: impl ToString) { self.errors.push(self.error(at, error)); } pub fn critical(&self, at: usize, error: impl ToString) -> ParseResult { Err(self.error(at, error)) } pub fn parse( &mut self, f: impl FnOnce(&mut Self) -> ParseResult, ) -> Result> { match f(self) { Ok(result) => { if self.errors.is_empty() { return Ok(result); } } Err(error) => { self.errors.push(error); } }; Err(self.errors.split_off(0)) } }