use std::fmt::{Display, Formatter, Result as FmtResult};
use crate::identifier::Identifier;
#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Id(
Identifier,
),
Number(
f64,
),
String(String),
End,
Punct(
Punct,
),
}
impl Token {
pub fn id(&self) -> Option<&Identifier> {
match self {
Self::Id(identifier) => Some(identifier),
_ => None,
}
}
pub fn matches_keyword(&self, keyword: &str) -> bool {
self.id().is_some_and(|id| id.matches_keyword(keyword))
}
pub fn as_number(&self) -> Option<f64> {
if let Self::Number(number) = self {
Some(*number)
} else {
None
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
Self::Number(number)
if *number >= i64::MIN as f64
&& *number <= i64::MAX as f64
&& *number == number.floor() =>
{
Some(*number as i64)
}
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
Self::String(string) => Some(string.as_str()),
_ => None,
}
}
}
fn is_printable(c: char) -> bool {
!c.is_control() || ['\t', '\r', '\n'].contains(&c)
}
fn string_representation(s: &str, quote: char, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{quote}")?;
for section in s.split_inclusive(quote) {
if let Some(rest) = section.strip_suffix(quote) {
write!(f, "{rest}{quote}{quote}")?;
} else {
write!(f, "{section}")?;
}
}
write!(f, "{quote}")
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
Token::Id(s) => write!(f, "{s}"),
Token::Number(number) => {
if number.is_sign_negative() {
write!(f, "-{}", number.abs())
} else {
write!(f, "{number}")
}
}
Token::String(s) => {
if s.chars().all(is_printable) {
if s.contains('"') {
string_representation(s, '\'', f)
} else {
string_representation(s, '"', f)
}
} else {
write!(f, "X\"")?;
for byte in s.bytes() {
let c1 = char::from_digit((byte >> 4) as u32, 16)
.unwrap()
.to_ascii_uppercase();
let c2 = char::from_digit((byte & 0xf) as u32, 16)
.unwrap()
.to_ascii_uppercase()
.to_ascii_lowercase();
write!(f, "{c1}{c2}")?;
}
write!(f, "\"")
}
}
Token::End => write!(f, "."),
Token::Punct(punct) => punct.fmt(f),
}
}
}
#[cfg(test)]
mod tests {
use crate::lex::token::Token;
#[test]
fn test_string() {
assert_eq!(Token::String(String::from("abc")).to_string(), "\"abc\"");
assert_eq!(
Token::String(String::from("\u{0080}")).to_string(),
"X\"C280\""
);
}
#[test]
fn test_neg0() {
assert_eq!(Token::Number(-0.0).to_string(), "-0");
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Punct {
Plus,
Dash,
Asterisk,
Slash,
Equals,
LParen,
RParen,
LSquare,
RSquare,
LCurly,
RCurly,
Comma,
Semicolon,
Colon,
And,
Or,
Not,
Eq,
Ge,
Gt,
Le,
Lt,
Ne,
All,
By,
To,
With,
Exp,
Bang,
Percent,
Question,
Backtick,
Dot,
Underscore,
BangAsterisk,
}
impl Punct {
pub fn as_str(&self) -> &'static str {
match self {
Self::Plus => "+",
Self::Dash => "-",
Self::Asterisk => "*",
Self::Slash => "/",
Self::Equals => "=",
Self::LParen => "(",
Self::RParen => ")",
Self::LSquare => "[",
Self::RSquare => "]",
Self::LCurly => "{",
Self::RCurly => "}",
Self::Comma => ",",
Self::Semicolon => ";",
Self::Colon => ":",
Self::And => "AND",
Self::Or => "OR",
Self::Not => "NOT",
Self::Eq => "EQ",
Self::Ge => ">=",
Self::Gt => ">",
Self::Le => "<=",
Self::Lt => "<",
Self::Ne => "~=",
Self::All => "ALL",
Self::By => "BY",
Self::To => "TO",
Self::With => "WITH",
Self::Exp => "**",
Self::Bang => "!",
Self::Percent => "%",
Self::Question => "?",
Self::Backtick => "`",
Self::Dot => ".",
Self::Underscore => "_",
Self::BangAsterisk => "!*",
}
}
}
impl Display for Punct {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.as_str())
}
}