Skip to content

Commit

Permalink
Use thiserror for error handlings
Browse files Browse the repository at this point in the history
  • Loading branch information
sile committed Oct 17, 2021
1 parent 4a37c2f commit d810733
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 160 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Expand Up @@ -16,7 +16,8 @@ coveralls = {repository = "sile/erl_tokenize"}

[dependencies]
num = "0.4"
trackable = "0.2"
thiserror = "1"

[dev-dependencies]
anyhow = "1"
structopt = "0.3"
8 changes: 3 additions & 5 deletions examples/tokenize.rs
@@ -1,6 +1,3 @@
#[macro_use]
extern crate trackable;

use erl_tokenize::{PositionRange, Tokenizer};
use std::fs::File;
use std::io::Read;
Expand All @@ -15,7 +12,7 @@ struct Opt {
silent: bool,
}

fn main() {
fn main() -> anyhow::Result<()> {
let opt = Opt::from_args();

let mut src = String::new();
Expand All @@ -26,7 +23,7 @@ fn main() {
let mut count = 0;
let tokenizer = Tokenizer::new(&src);
for result in tokenizer {
let token = track_try_unwrap!(result);
let token = result?;
if !opt.silent {
println!("[{:?}] {:?}", token.start_position(), token.text());
}
Expand All @@ -37,6 +34,7 @@ fn main() {
"ELAPSED: {:?} seconds",
to_seconds(Instant::now() - start_time)
);
Ok(())
}

fn to_seconds(duration: Duration) -> f64 {
Expand Down
137 changes: 107 additions & 30 deletions src/error.rs
@@ -1,36 +1,113 @@
use num;
use std;
use trackable::error::TrackableError;
use trackable::error::{ErrorKind as TrackableErrorKind, ErrorKindExt};

/// This crate specific error type.
#[derive(Debug, Clone)]
pub struct Error(TrackableError<ErrorKind>);
derive_traits_for_trackable_error_newtype!(Error, ErrorKind);
impl From<std::num::ParseIntError> for Error {
fn from(f: std::num::ParseIntError) -> Self {
ErrorKind::InvalidInput.cause(f).into()
}
use crate::Position;

/// Possible errors.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum Error {
/// No closing quotation.
#[error("no closing quotation ({position})")]
NoClosingQuotation { position: Position },

/// Invalid escaped character.
#[error("cannnot parse a escaped character ({position})")]
InvalidEscapedChar { position: Position },

/// A token was expected, but not found.
#[error("a token was expected, but not found ({position})")]
MissingToken { position: Position },

/// Unknown keyword.
#[error("unknown keyword {keyword:?} ({position})")]
UnknownKeyword { position: Position, keyword: String },

/// Invalid atom token.
#[error("Canot parse an atom token ({position})")]
InvalidAtomToken { position: Position },

/// Invalid character token.
#[error("cannnot parse a character token ({position})")]
InvalidCharToken { position: Position },

/// Invalid comment token.
#[error("cannnot parse a comment token ({position})")]
InvalidCommentToken { position: Position },

/// Invalid float token.
#[error("cannnot parse a float token ({position})")]
InvalidFloatToken { position: Position },

/// Invalid integer token.
#[error("cannnot parse a integer token ({position})")]
InvalidIntegerToken { position: Position },

/// Invalid string token.
#[error("cannnot parse a string token ({position})")]
InvalidStringToken { position: Position },

/// Invalid symbol token.
#[error("cannnot parse a symbol token ({position})")]
InvalidSymbolToken { position: Position },

/// Invalid variable token.
#[error("cannnot parse a variable token ({position})")]
InvalidVariableToken { position: Position },

/// Invalid whitespace token.
#[error("cannnot parse a whitespace token ({position})")]
InvalidWhitespaceToken { position: Position },
}
impl From<std::num::ParseFloatError> for Error {
fn from(f: std::num::ParseFloatError) -> Self {
ErrorKind::InvalidInput.cause(f).into()

impl Error {
pub(crate) fn no_closing_quotation(position: Position) -> Self {
Self::NoClosingQuotation { position }
}
}
impl From<num::bigint::ParseBigIntError> for Error {
fn from(f: num::bigint::ParseBigIntError) -> Self {
ErrorKind::InvalidInput.cause(f).into()

pub(crate) fn invalid_escaped_char(position: Position) -> Self {
Self::InvalidEscapedChar { position }
}
}

/// The list of the possible error kinds
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
/// Input text is invalid.
InvalidInput,
pub(crate) fn missing_token(position: Position) -> Self {
Self::MissingToken { position }
}

/// Unexpected End-Of-String.
UnexpectedEos,
}
pub(crate) fn unknown_keyword(position: Position, keyword: String) -> Self {
Self::UnknownKeyword { position, keyword }
}

pub(crate) fn invalid_atom_token(position: Position) -> Self {
Self::InvalidAtomToken { position }
}

pub(crate) fn invalid_char_token(position: Position) -> Self {
Self::InvalidCharToken { position }
}

impl TrackableErrorKind for ErrorKind {}
pub(crate) fn invalid_comment_token(position: Position) -> Self {
Self::InvalidCommentToken { position }
}

pub(crate) fn invalid_float_token(position: Position) -> Self {
Self::InvalidFloatToken { position }
}

pub(crate) fn invalid_integer_token(position: Position) -> Self {
Self::InvalidIntegerToken { position }
}

pub(crate) fn invalid_string_token(position: Position) -> Self {
Self::InvalidStringToken { position }
}

pub(crate) fn invalid_symbol_token(position: Position) -> Self {
Self::InvalidSymbolToken { position }
}

pub(crate) fn invalid_variable_token(position: Position) -> Self {
Self::InvalidVariableToken { position }
}

pub(crate) fn invalid_whitespace_token(position: Position) -> Self {
Self::InvalidWhitespaceToken { position }
}
}
2 changes: 1 addition & 1 deletion src/lexer.rs
Expand Up @@ -65,7 +65,7 @@ where
{
type Item = Result<LexicalToken>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(token) = self.0.next() {
for token in &mut self.0 {
match token {
Err(e) => return Some(Err(e)),
Ok(token) => {
Expand Down
6 changes: 1 addition & 5 deletions src/lib.rs
Expand Up @@ -23,11 +23,7 @@
//! [erl_scan]: http://erlang.org/doc/man/erl_scan.html
//! [Data Types]: http://erlang.org/doc/reference_manual/data_types.html
#![warn(missing_docs)]

#[macro_use]
extern crate trackable;

pub use crate::error::{Error, ErrorKind};
pub use crate::error::Error;
pub use crate::hidden_token::HiddenToken;
pub use crate::lexer::Lexer;
pub use crate::lexical_token::LexicalToken;
Expand Down
28 changes: 26 additions & 2 deletions src/position.rs
Expand Up @@ -45,14 +45,14 @@ impl Position {
self.filepath = Some(Arc::new(path.as_ref().to_path_buf()));
}

/// Step a position by the given width.
/// Steps a position by the given width.
pub(crate) fn step_by_width(mut self, witdh: usize) -> Position {
self.offset += witdh;
self.column += witdh;
self
}

/// Step a position by the given text.
/// Steps a position by the given text.
pub(crate) fn step_by_text(mut self, mut text: &str) -> Position {
while let Some(i) = text.find('\n') {
self.offset += i + 1;
Expand All @@ -65,12 +65,36 @@ impl Position {
self
}
}

impl Default for Position {
fn default() -> Self {
Self::new()
}
}

impl std::fmt::Display for Position {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}:{}:{}",
self.filepath
.as_ref()
.and_then(|f| f.to_str())
.unwrap_or("<unknown>"),
self.line,
self.column
)
}
}

impl std::ops::Add<usize> for Position {
type Output = Self;

fn add(self, rhs: usize) -> Self {
self.step_by_width(rhs)
}
}

/// This trait allows to get a (half-open) range where the subject is located.
pub trait PositionRange {
/// Returns the (inclusive) start position of this.
Expand Down
32 changes: 16 additions & 16 deletions src/token.rs
Expand Up @@ -4,7 +4,7 @@ use crate::tokens::{
AtomToken, CharToken, CommentToken, FloatToken, IntegerToken, KeywordToken, StringToken,
SymbolToken, VariableToken, WhitespaceToken,
};
use crate::{ErrorKind, HiddenToken, LexicalToken, Position, PositionRange};
use crate::{Error, HiddenToken, LexicalToken, Position, PositionRange};

/// Token.
#[allow(missing_docs)]
Expand Down Expand Up @@ -41,12 +41,15 @@ impl Token {
/// assert_eq!(token.as_symbol_token().map(|t| t.value()), Some(Symbol::OpenSquare));
/// ```
pub fn from_text(text: &str, pos: Position) -> crate::Result<Self> {
let head = track_assert_some!(text.chars().nth(0), ErrorKind::UnexpectedEos);
let head = text
.chars()
.next()
.ok_or_else(|| Error::missing_token(pos.clone()))?;
match head {
' ' | '\t' | '\r' | '\n' | '\u{A0}' => {
track!(WhitespaceToken::from_text(text, pos)).map(Token::from)
WhitespaceToken::from_text(text, pos).map(Token::from)
}
'A'..='Z' | '_' => track!(VariableToken::from_text(text, pos)).map(Token::from),
'A'..='Z' | '_' => VariableToken::from_text(text, pos).map(Token::from),
'0'..='9' => {
let maybe_float = if let Some(i) = text.find(|c: char| !c.is_digit(10)) {
text.as_bytes()[i] == b'.'
Expand All @@ -58,25 +61,25 @@ impl Token {
false
};
if maybe_float {
track!(FloatToken::from_text(text, pos)).map(Token::from)
FloatToken::from_text(text, pos).map(Token::from)
} else {
track!(IntegerToken::from_text(text, pos)).map(Token::from)
IntegerToken::from_text(text, pos).map(Token::from)
}
}
'$' => track!(CharToken::from_text(text, pos)).map(Token::from),
'"' => track!(StringToken::from_text(text, pos)).map(Token::from),
'\'' => track!(AtomToken::from_text(text, pos)).map(Token::from),
'%' => track!(CommentToken::from_text(text, pos)).map(Token::from),
'$' => CharToken::from_text(text, pos).map(Token::from),
'"' => StringToken::from_text(text, pos).map(Token::from),
'\'' => AtomToken::from_text(text, pos).map(Token::from),
'%' => CommentToken::from_text(text, pos).map(Token::from),
_ => {
if head.is_alphabetic() {
let atom = track!(AtomToken::from_text(text, pos.clone()))?;
let atom = AtomToken::from_text(text, pos.clone())?;
if let Ok(keyword) = KeywordToken::from_text(atom.text(), pos) {
Ok(Token::from(keyword))
} else {
Ok(Token::from(atom))
}
} else {
track!(SymbolToken::from_text(text, pos)).map(Token::from)
SymbolToken::from_text(text, pos).map(Token::from)
}
}
}
Expand Down Expand Up @@ -119,10 +122,7 @@ impl Token {

/// Returns `true` if this is a hidden token, otherwise `false`.
pub fn is_hidden_token(&self) -> bool {
match *self {
Token::Whitespace(_) | Token::Comment(_) => true,
_ => false,
}
return matches!(self, Token::Whitespace(_) | Token::Comment(_));
}

/// Tries to convert into `LexicalToken`.
Expand Down
4 changes: 2 additions & 2 deletions src/tokenizer.rs
Expand Up @@ -31,7 +31,7 @@ where
let init_pos = Position::new();
Tokenizer {
text,
next_pos: init_pos.clone(),
next_pos: init_pos,
}
}

Expand Down Expand Up @@ -96,7 +96,7 @@ where
.get_unchecked(self.next_pos.offset()..self.text.as_ref().len())
};
let cur_pos = self.next_pos.clone();
match track!(Token::from_text(text, cur_pos)) {
match Token::from_text(text, cur_pos) {
Err(e) => Some(Err(e)),
Ok(t) => {
self.next_pos = t.end_position();
Expand Down

0 comments on commit d810733

Please sign in to comment.