add item_at to Scanner trait and migrate to thiserror create (#17)

This commit is contained in:
Josh Thomas 2024-10-15 05:49:46 -05:00 committed by GitHub
parent 1a34ac124e
commit 52eea54ed7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 109 additions and 95 deletions

58
Cargo.lock generated
View file

@ -5,3 +5,61 @@ version = 3
[[package]]
name = "django-template-ast"
version = "0.1.0"
dependencies = [
"thiserror",
]
[[package]]
name = "proc-macro2"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"

View file

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
thiserror = "1.0.64"

View file

@ -1,64 +1,16 @@
use std::fmt;
use thiserror::Error;
pub trait ErrorMessage {
fn message(&self) -> &str;
}
#[derive(Debug)]
#[derive(Error, Debug)]
pub enum LexerError {
EmptyToken {
line: usize,
message: String,
},
UnexpectedCharacter {
character: char,
line: usize,
message: String,
},
LexicalError {
position: usize,
message: String,
},
#[error("Empty token at line {line:?}")]
EmptyToken { line: usize },
#[error("Unexpected character '{character}' at line {line}")]
UnexpectedCharacter { character: char, line: usize },
#[error("At beginning of input")]
AtBeginningOfInput,
#[error("At end of input")]
AtEndOfInput,
#[error("Invalid character access")]
InvalidCharacterAccess,
}
impl LexerError {
pub fn empty_token<T>(line: usize) -> Result<T, Self> {
Err(LexerError::EmptyToken {
line,
message: format!("Empty token at line {}", line),
})
}
pub fn unexpected_character<T>(character: char, line: usize) -> Result<T, Self> {
Err(LexerError::UnexpectedCharacter {
character,
line,
message: format!("Unexpected character '{}' at line {}", character, line),
})
}
pub fn lexical_error<T>(message: &str, position: usize) -> Result<T, Self> {
Err(LexerError::LexicalError {
position,
message: format!("Lexical error at position {}: {}", position, message),
})
}
}
impl ErrorMessage for LexerError {
fn message(&self) -> &str {
match self {
LexerError::EmptyToken { message, .. }
| LexerError::UnexpectedCharacter { message, .. }
| LexerError::LexicalError { message, .. } => message,
}
}
}
impl fmt::Display for LexerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message())
}
}
impl std::error::Error for LexerError {}

View file

@ -52,7 +52,12 @@ impl<'a> Lexer<'a> {
'|' => TokenType::Pipe,
'\'' => TokenType::SingleQuote,
'"' => TokenType::DoubleQuote,
_ => return LexerError::unexpected_character(c, self.state.line),
_ => {
return Err(LexerError::UnexpectedCharacter {
character: c,
line: self.state.line,
})
}
};
Ok(token_type)
}
@ -156,7 +161,12 @@ impl<'a> Lexer<'a> {
self.state.line += 1;
}
' ' | '\t' | '\r' => {}
_ => return LexerError::unexpected_character(c, self.state.line),
_ => {
return Err(LexerError::UnexpectedCharacter {
character: c,
line: self.state.line,
})
}
}
c = self.advance()?;
}
@ -167,7 +177,9 @@ impl<'a> Lexer<'a> {
self.advance_while(|c| !Self::is_token_boundary(c))?;
if self.state.start == self.state.current {
LexerError::empty_token(self.state.line)
Err(LexerError::EmptyToken {
line: self.state.line,
})
} else {
Ok(TokenType::Text)
}
@ -220,45 +232,35 @@ impl<'a> Scanner for Lexer<'a> {
}
fn peek(&self) -> Result<Self::Item, Self::Error> {
self.source[self.state.current..]
.chars()
.next()
.ok_or_else(|| {
LexerError::lexical_error::<Self::Item>(
"Unexpected end of input",
self.state.current,
)
.unwrap_err()
})
self.item_at(self.state.current)
}
fn peek_next(&self) -> Result<Self::Item, Self::Error> {
self.source[self.state.current..]
.chars()
.nth(1)
.ok_or_else(|| {
LexerError::lexical_error::<Self::Item>(
"Unexpected end of input",
self.state.current + 1,
)
.unwrap_err()
})
self.item_at(self.state.current + 1)
}
fn previous(&self) -> Result<Self::Item, Self::Error> {
if self.state.current > 0 {
self.source[..self.state.current]
.chars()
.last()
.ok_or_else(|| {
LexerError::lexical_error::<Self::Item>(
"No previous character",
self.state.current - 1,
)
.unwrap_err()
})
self.item_at(self.state.current - 1)
} else {
LexerError::lexical_error("No previous character", 0)
Err(LexerError::AtBeginningOfInput)
}
}
fn item_at(&self, index: usize) -> Result<Self::Item, Self::Error> {
match self.source.chars().nth(index) {
Some(ch) => Ok(ch),
None => {
let error = if self.source.is_empty() || index == 0 {
LexerError::AtBeginningOfInput
} else if index >= self.source.len() {
LexerError::AtEndOfInput
} else {
LexerError::InvalidCharacterAccess
};
Err(error)
}
}
}

View file

@ -24,5 +24,6 @@ pub trait Scanner {
fn peek(&self) -> Result<Self::Item, Self::Error>;
fn peek_next(&self) -> Result<Self::Item, Self::Error>;
fn previous(&self) -> Result<Self::Item, Self::Error>;
fn item_at(&self, index: usize) -> Result<Self::Item, Self::Error>;
fn is_at_end(&self) -> bool;
}