mirror of
https://github.com/joshuadavidthomas/django-template-ast.git
synced 2025-07-07 19:35:32 +00:00
add item_at
to Scanner trait and migrate to thiserror create (#17)
This commit is contained in:
parent
1a34ac124e
commit
52eea54ed7
5 changed files with 109 additions and 95 deletions
58
Cargo.lock
generated
58
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -4,3 +4,4 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.64"
|
||||
|
|
74
src/error.rs
74
src/error.rs
|
@ -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 {}
|
||||
|
|
70
src/lexer.rs
70
src/lexer.rs
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue