use crate::common;
use token::token;
use winnow::{
ascii::{multispace0, space0},
combinator::{preceded, repeat, terminated},
stream::{AsChar, Compare, FindSlice, SliceLen, Stream, StreamIsPartial},
Parser,
};
pub use common::lexer::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum TimeSpec {
Absolute,
Relative { from_start: bool, positive: bool },
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Token<T> {
Identifier(T),
Value(Value<T>),
Comment(T),
TupleOpen,
TupleClose,
ElementSeparator,
TimeSymbol(TimeSpec),
Directive(T),
Separator,
}
impl<T> From<GenericToken<T>> for Token<T> {
fn from(value: GenericToken<T>) -> Self {
match value {
GenericToken::Identifier(i) => Self::Identifier(i),
GenericToken::Value(v) => Self::Value(v),
GenericToken::Comment(c) => Self::Comment(c),
GenericToken::TupleOpen => Self::TupleOpen,
GenericToken::TupleClose => Self::TupleClose,
GenericToken::ElementSeparator => Self::ElementSeparator,
}
}
}
impl<T> From<Token<T>> for Option<GenericToken<T>> {
fn from(value: Token<T>) -> Self {
match value {
Token::Identifier(i) => Some(GenericToken::Identifier(i)),
Token::Value(v) => Some(GenericToken::Value(v)),
Token::Comment(c) => Some(GenericToken::Comment(c)),
Token::TupleOpen => Some(GenericToken::TupleOpen),
Token::TupleClose => Some(GenericToken::TupleClose),
Token::ElementSeparator => Some(GenericToken::ElementSeparator),
_ => None,
}
}
}
pub fn lex<
I: Stream
+ StreamIsPartial
+ Compare<&'static str>
+ FindSlice<&'static str>
+ FindSlice<(char, char)>
+ Copy,
>(
input: I,
) -> Result<
Vec<Token<<I as Stream>::Slice>>,
winnow::error::ParseError<I, winnow::error::ContextError>,
>
where
<I as Stream>::Token: AsChar + Clone,
I::Slice: SliceLen,
{
preceded(multispace0, repeat(0.., terminated(token, space0)))
.parse(input)
.map(|mut tokens: Vec<_>| {
match tokens.last() {
Some(Token::Separator) => { }
_ => tokens.push(Token::Separator),
}
tokens
})
}
pub mod token {
use super::*;
use winnow::{
ascii::{line_ending, multispace0},
combinator::{alt, opt, terminated},
stream::{AsChar, Compare, FindSlice, SliceLen, Stream, StreamIsPartial},
token::take_till,
PResult, Parser,
};
pub use common::lexer::token::*;
pub fn time_symbol<I: Stream + StreamIsPartial + Compare<&'static str>>(
input: &mut I,
) -> PResult<Token<<I as Stream>::Slice>> {
(
"@",
opt("=".void()).map(|o| o.is_some()),
opt(alt(["+".value(true), "-".value(false)])),
)
.map(|(_, from_start, sign)| {
Token::TimeSymbol(match sign {
Some(positive) => TimeSpec::Relative {
from_start,
positive,
},
None => {
if from_start {
TimeSpec::Relative {
from_start: true,
positive: true,
}
} else {
TimeSpec::Absolute
}
}
})
})
.parse_next(input)
}
pub fn directive<I: Stream + StreamIsPartial + Compare<&'static str>>(
input: &mut I,
) -> PResult<Token<<I as Stream>::Slice>>
where
I::Token: AsChar + Clone,
{
(
"#",
take_till(0.., |x: I::Token| x.clone().is_newline() || x.is_space()),
)
.map(|(_, dir)| Token::Directive(dir))
.parse_next(input)
}
pub fn separator<I: Stream + StreamIsPartial + Compare<&'static str>>(
input: &mut I,
) -> PResult<Token<<I as Stream>::Slice>>
where
I::Token: AsChar + Clone,
{
terminated(line_ending, multispace0)
.map(|_| Token::Separator)
.parse_next(input)
}
pub fn token<
I: Stream
+ StreamIsPartial
+ Compare<&'static str>
+ FindSlice<&'static str>
+ FindSlice<(char, char)>
+ Copy,
>(
input: &mut I,
) -> PResult<Token<<I as Stream>::Slice>>
where
<I as Stream>::Token: AsChar + Clone,
I::Slice: SliceLen,
{
alt((
value,
identifier,
comment,
tuple_open,
tuple_close,
element_separator,
time_symbol,
directive,
separator,
))
.parse_next(input)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn simple_example() {
let input = r#"
#directive value
#other_directive "string"
instruction argument "argument" ^argument$
@0 timed_instruction arg
@-1 negative_timed_instruction arg
@=2 positive_start_timed_instruction arg"#;
let expected = vec![
Token::Directive("directive"),
Token::Identifier("value"),
Token::Separator,
Token::Directive("other_directive"),
Token::Value(Value::String("string")),
Token::Separator,
Token::Identifier("instruction"),
Token::Identifier("argument"),
Token::Value(Value::String("argument")),
Token::Value(Value::Regex("argument")),
Token::Separator,
Token::TimeSymbol(TimeSpec::Absolute),
Token::Value(Value::Number("0")),
Token::Identifier("timed_instruction"),
Token::Identifier("arg"),
Token::Separator,
Token::TimeSymbol(TimeSpec::Relative {
from_start: false,
positive: false,
}),
Token::Value(Value::Number("1")),
Token::Identifier("negative_timed_instruction"),
Token::Identifier("arg"),
Token::Separator,
Token::TimeSymbol(TimeSpec::Relative {
from_start: true,
positive: true,
}),
Token::Value(Value::Number("2")),
Token::Identifier("positive_start_timed_instruction"),
Token::Identifier("arg"),
Token::Separator,
];
let actual = lex(input).expect("Failed to lex");
assert_eq!(actual, expected);
}
}