use std::{
fmt::{self, Display, Write},
ops::Deref,
};
use logos::Logos;
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Logos)]
pub enum Token {
#[regex("[a-zA-Z]+", |lex| lex.slice().chars().next().unwrap())]
Ident(char),
#[token("(")]
OpenParen,
#[token(")")]
CloseParen,
#[token("not")]
#[token("!")]
Not,
#[token("->")]
Implication,
#[token("and")]
#[token("&")]
#[token("&&")]
And,
#[token("or")]
#[token("|")]
#[token("||")]
Or,
#[token("=")]
#[token("==")]
Equals,
#[token("!=")]
NotEquals,
#[token("0")]
False,
#[token("1")]
True,
#[error]
#[regex(r"[ \t\n]", logos::skip)]
Error,
}
#[derive(Debug)]
pub struct Tokens {
tokens: Vec<Token>,
}
impl Tokens {
pub fn from_text(text: &str) -> Self {
Self {
tokens: Token::lexer(text).collect(),
}
}
}
impl Deref for Tokens {
type Target = [Token];
fn deref(&self) -> &Self::Target {
&self.tokens
}
}
impl Display for Tokens {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for token in self.tokens.iter() {
match token {
Token::Ident(c) => f.write_char(*c),
Token::OpenParen => f.write_char('('),
Token::CloseParen => f.write_char(')'),
Token::Not => f.write_char('¬'),
Token::Implication => f.write_str(" → "),
Token::And => f.write_str(" ∧ "),
Token::Or => f.write_str(" ∨ "),
Token::Equals => f.write_str(" = "),
Token::NotEquals => f.write_str(" ≠ "),
Token::False => f.write_char('0'),
Token::True => f.write_char('1'),
Token::Error => continue,
}?
}
Ok(())
}
}
pub fn rposition_if_not_in_paren(
tokens: &[Token],
needle: Token,
) -> Option<usize> {
debug_assert!(!matches!(needle, Token::OpenParen | Token::CloseParen));
let mut open_parens = 0;
for (i, token) in tokens.iter().enumerate().rev() {
match token {
Token::OpenParen => open_parens += 1,
Token::CloseParen => open_parens -= 1,
_ if open_parens == 0 && token == &needle => return Some(i),
_ => (),
}
}
assert_eq!(open_parens, 0, "Non matching parentheses");
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn implication() {
let s = "a -> b";
assert_eq!(
Tokens::from_text(s).tokens,
vec![Token::Ident('a'), Token::Implication, Token::Ident('b')]
);
}
#[test]
fn equals() {
let s = "a == b";
assert_eq!(
Tokens::from_text(s).tokens,
vec![Token::Ident('a'), Token::Equals, Token::Ident('b')]
);
}
#[test]
fn not_equals() {
let s = "a != b";
assert_eq!(
Tokens::from_text(s).tokens,
vec![Token::Ident('a'), Token::NotEquals, Token::Ident('b')]
);
}
}