Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Error Default trait with extra information from the Lexer as parameters #462

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/internal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::source::Chunk;
use crate::{Filter, FilterResult, Lexer, Logos, Skip};
use crate::{utils, Filter, FilterResult, Lexer, Logos, Skip};

/// Trait used by the functions contained in the `Lexicon`.
///
Expand Down Expand Up @@ -72,7 +72,7 @@ impl<'s, T: Logos<'s>> CallbackResult<'s, (), T> for bool {
{
match self {
true => lex.set(Ok(c(()))),
false => lex.set(Err(T::Error::default())),
false => lex.set(Err(utils::error_from_lexer(lex))),
}
}
}
Expand All @@ -85,7 +85,7 @@ impl<'s, P, T: Logos<'s>> CallbackResult<'s, P, T> for Option<P> {
{
match self {
Some(product) => lex.set(Ok(c(product))),
None => lex.set(Err(T::Error::default())),
None => lex.set(Err(utils::error_from_lexer(lex))),
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/lexer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::internal::LexerInternal;
use super::Logos;
use crate::source::{self, Source};
use crate::utils;

use core::fmt::{self, Debug};
use core::ops::{Deref, DerefMut};
Expand Down Expand Up @@ -378,11 +379,11 @@ where
self.token_end = self.source.find_boundary(self.token_end);
#[cfg(not(feature = "forbid_unsafe"))]
{
self.token = core::mem::ManuallyDrop::new(Some(Err(Token::Error::default())));
self.token = core::mem::ManuallyDrop::new(Some(Err(utils::error_from_lexer(self))));
}
#[cfg(feature = "forbid_unsafe")]
{
self.token = Some(Err(Token::Error::default()));
self.token = Some(Err(utils::error_from_lexer(self)));
}
}

Expand Down
16 changes: 15 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub mod source;

#[doc(hidden)]
pub mod internal;
pub(crate) mod utils;

pub use crate::lexer::{Lexer, Span, SpannedIter};
pub use crate::source::Source;
Expand All @@ -53,7 +54,7 @@ pub trait Logos<'source>: Sized {

/// Error type returned by the lexer. This can be set using
/// `#[logos(error = MyError)]`. Defaults to `()` if not set.
type Error: Default + Clone + PartialEq + Debug + 'source;
type Error: DefaultLexerError<'source, Self::Source, Self::Extras>;

/// The heart of Logos. Called by the `Lexer`. The implementation for this function
/// is generated by the `logos-derive` crate.
Expand All @@ -78,6 +79,19 @@ pub trait Logos<'source>: Sized {
}
}

/// Trait used to construct the default error type for a `Logos` implementation.
/// More information is communicated than simply using [`Default`].
pub trait DefaultLexerError<'source, Source: ?Sized, Extras = ()>:
Clone + PartialEq + Debug
{
/// Constructs a default error, given contextual information from the Lexer.
fn from_lexer<'e>(source: &'source Source, span: Span, extras: &'e Extras) -> Self;
}

impl<'source, Source: ?Sized, Extras> DefaultLexerError<'source, Source, Extras> for () {
fn from_lexer<'e>(_: &'source Source, _: Span, _: &'e Extras) -> Self {}
}

/// Type that can be returned from a callback, informing the `Lexer`, to skip
/// current token match. See also [`logos::skip`](./fn.skip.html).
///
Expand Down
13 changes: 13 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pub(crate) fn error_from_lexer<
'source,
Token: super::Logos<'source>,
Error: super::DefaultLexerError<'source, Token::Source, Token::Extras>,
>(
lex: &crate::Lexer<'source, Token>,
) -> Error {
let source = lex.source();
let span = lex.span();
let extras = &lex.extras;

Error::from_lexer(source, span, extras)
}
9 changes: 3 additions & 6 deletions tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ publish = false
version = "0.1.0"

[dependencies]
logos-derive = {path = "../logos-derive"}
logos = {path = "../", default-features = false, features = ["std"]}
logos-derive = { path = "../logos-derive" }
logos = { path = "../", default-features = false, features = ["std"] }

[features]
forbid_unsafe = [
"logos-derive/forbid_unsafe",
"logos/forbid_unsafe"
]
forbid_unsafe = ["logos-derive/forbid_unsafe", "logos/forbid_unsafe"]

[dev-dependencies]
criterion = "0.4"
Expand Down
25 changes: 19 additions & 6 deletions tests/tests/callbacks.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use logos::{Lexer, Logos as _, Skip};
use logos::{DefaultLexerError, Lexer, Logos as _, Skip};
use logos_derive::Logos;
use tests::assert_lex;

#[derive(Default, Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
enum LexingError {
ParseNumberError,
#[default]
Other,
}

impl<'source> DefaultLexerError<'source, str, ()> for LexingError {
fn from_lexer<'e>(_: &'source str, _: logos::Span, _: &'e ()) -> Self {
LexingError::Other
}
}

impl From<std::num::ParseIntError> for LexingError {
fn from(_: std::num::ParseIntError) -> Self {
LexingError::ParseNumberError
Expand Down Expand Up @@ -176,11 +181,19 @@ mod any_token_callback {
mod return_result_skip {
use super::*;

#[derive(Debug, Default, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone)]
enum LexerError {
UnterminatedComment,
#[default]
Other,
Other { source: String, span: logos::Span },
}

impl<'source, Extras> DefaultLexerError<'source, str, Extras> for LexerError {
fn from_lexer<'e>(source: &'source str, span: logos::Span, _: &'e Extras) -> Self {
LexerError::Other {
source: source.to_owned(),
span,
}
}
}

#[derive(Logos, Debug, PartialEq)]
Expand Down
48 changes: 33 additions & 15 deletions tests/tests/custom_error.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
use logos::DefaultLexerError;
use logos_derive::Logos;
use std::num::{IntErrorKind, ParseIntError};
use tests::assert_lex;

#[derive(Debug, Clone, PartialEq, Default)]
enum LexingError {
#[derive(Debug, Clone, PartialEq)]
enum CustomError {
NumberTooLong,
NumberNotEven(u32),
#[default]
Other,
Unknown,
Generic { source: String, span: logos::Span },
}

impl From<ParseIntError> for LexingError {
impl<'source, Extras> DefaultLexerError<'source, str, Extras> for CustomError {
fn from_lexer<'e>(source: &'source str, span: logos::Span, _: &'e Extras) -> Self {
CustomError::Generic {
source: source.to_owned(),
span,
}
}
}

impl From<ParseIntError> for CustomError {
fn from(value: ParseIntError) -> Self {
match value.kind() {
IntErrorKind::PosOverflow => LexingError::NumberTooLong,
_ => LexingError::Other,
IntErrorKind::PosOverflow => CustomError::NumberTooLong,
_ => CustomError::Unknown,
}
}
}

fn parse_number(input: &str) -> Result<u32, LexingError> {
fn parse_number(input: &str) -> Result<u32, CustomError> {
let num = input.parse::<u32>()?;
if num % 2 == 0 {
if input.parse::<u32>()? % 2 == 0 {
Ok(num)
} else {
Err(LexingError::NumberNotEven(num))
Err(CustomError::NumberNotEven(num))
}
}

#[derive(Logos, Debug, Clone, Copy, PartialEq)]
#[logos(error = LexingError)]
#[logos(error = CustomError)]
enum Token<'a> {
#[regex(r"[0-9]+", |lex| parse_number(lex.slice()))]
Number(u32),
Expand All @@ -39,19 +49,27 @@ enum Token<'a> {

#[test]
fn test() {
let source = "123abc1234xyz1111111111111111111111111111111111111111111111111111111,";
assert_lex(
"123abc1234xyz1111111111111111111111111111111111111111111111111111111,",
source,
&[
(Err(LexingError::NumberNotEven(123)), "123", 0..3),
(Err(CustomError::NumberNotEven(123)), "123", 0..3),
(Ok(Token::Identifier("abc")), "abc", 3..6),
(Ok(Token::Number(1234)), "1234", 6..10),
(Ok(Token::Identifier("xyz")), "xyz", 10..13),
(
Err(LexingError::NumberTooLong),
Err(CustomError::NumberTooLong),
"1111111111111111111111111111111111111111111111111111111",
13..68,
),
(Err(LexingError::Other), ",", 68..69),
(
Err(CustomError::Generic {
source: source.into(),
span: 68..69,
}),
",",
68..69,
),
],
);
}
20 changes: 16 additions & 4 deletions tests/tests/edgecase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,12 +324,24 @@ mod type_params {
use super::*;
use std::num::ParseIntError;

#[derive(Debug, Clone, PartialEq, Default)]
struct LexingError;
#[derive(Debug, Clone, PartialEq)]
enum LexingError {
ParseIntError(ParseIntError),
Other { source: String, span: logos::Span },
}

impl<'source, Extras> logos::DefaultLexerError<'source, str, Extras> for LexingError {
fn from_lexer<'e>(source: &'source str, span: logos::Span, _: &'e Extras) -> Self {
LexingError::Other {
source: source.to_owned(),
span,
}
}
}

impl From<ParseIntError> for LexingError {
fn from(_: ParseIntError) -> Self {
LexingError
fn from(e: ParseIntError) -> Self {
LexingError::ParseIntError(e)
}
}

Expand Down
Loading