use anyhow::{bail, Result}; use itertools::Itertools; use std::fmt; use crate::assembler; #[derive(Debug, PartialEq, Eq)] pub enum Token { Comment(String), CharLiteral(String), StringLiteral(String), MacroLiteral(String), Literal(String), Number(String), Comma, Colon, LParen, RParen, Not, And, Nand, Or, Nor, Xor, Xnor, Lsh, Rsh, Add, Sub, Mul, Div, Pow, Cmp, Eq, Neq, Lt, Gt, Leq, Geq, Bol, Inv, Rnd, Newline(String), Whitespace(String), } impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Token::Comment(x) => write!(f, ";{}", x), Token::CharLiteral(x) => write!(f, "'{}'", x), Token::StringLiteral(x) => write!(f, "\"{}\"", x), Token::MacroLiteral(x) => write!(f, "{}", x), Token::Literal(x) => write!(f, "{}", x), Token::Number(x) => write!(f, "{}", x), Token::Comma => write!(f, ","), Token::Colon => write!(f, ":"), Token::LParen => write!(f, "("), Token::RParen => write!(f, ")"), Token::Not => write!(f, "~"), Token::And => write!(f, "&"), Token::Nand => write!(f, "~&"), Token::Or => write!(f, "|"), Token::Nor => write!(f, "~|"), Token::Xor => write!(f, "^"), Token::Xnor => write!(f, "~^"), Token::Lsh => write!(f, "<<"), Token::Rsh => write!(f, ">>"), Token::Add => write!(f, "+"), Token::Sub => write!(f, "-"), Token::Mul => write!(f, "*"), Token::Div => write!(f, "/"), Token::Pow => write!(f, "**"), Token::Cmp => write!(f, "<=>"), Token::Eq => write!(f, "=="), Token::Neq => write!(f, "!="), Token::Lt => write!(f, "<"), Token::Gt => write!(f, ">"), Token::Leq => write!(f, "<="), Token::Geq => write!(f, ">="), Token::Bol => write!(f, "!!"), Token::Inv => write!(f, "!"), Token::Rnd => write!(f, "?"), Token::Newline(x) | Token::Whitespace(x) => write!(f, "{}", x), } } } impl assembler::ToCode for Token {} pub fn lex(source: &str) -> Result> { let mut chars = source.chars().peekable(); let mut tokens: Vec = vec![]; while let Some(&ch) = chars.peek() { tokens.push(match ch { ';' => { chars.next(); chars.next_if(|c| *c == ';'); Token::Comment(chars.peeking_take_while(|c| *c != '\n').collect()) } '@' => { chars.next(); Token::Comment(chars.peeking_take_while(|c| *c != '\n').collect()) } '#' => { chars.next(); Token::Comment(chars.peeking_take_while(|c| *c != '\n').collect()) } '\'' => { chars.next(); Token::CharLiteral(chars.by_ref().take_while(|c| *c != '\'').collect()) } '"' => { chars.next(); Token::StringLiteral(chars.by_ref().take_while(|c| *c != '"').collect()) } '.' => { chars.next(); Token::MacroLiteral(format!( ".{}", chars .peeking_take_while(|c| c.is_alphabetic() || c.is_numeric() || *c == '_') .collect::() )) } _ if ch.is_alphabetic() => { let name: String = chars .peeking_take_while(|c| c.is_alphabetic() || c.is_numeric() || *c == '_') .collect(); Token::Literal(name) } _ if ch.is_numeric() => Token::Number( chars .peeking_take_while(|c| c.is_alphanumeric() || *c == '_') .collect(), ), ',' => { chars.next(); Token::Comma } ':' => { chars.next(); Token::Colon } '(' => { chars.next(); Token::LParen } ')' => { chars.next(); Token::RParen } '~' => { chars.next(); if let Some(c) = chars.peek() { match c { '&' => { chars.next(); Token::Nand } '|' => { chars.next(); Token::Nor } '^' => { chars.next(); Token::Xnor } _ => Token::Not, } } else { Token::Not } } '&' => { chars.next(); Token::And } '|' => { chars.next(); Token::Or } '^' => { chars.next(); Token::Xor } '<' => { chars.next(); match chars.peek() { Some('<') => { chars.next(); Token::Lsh } Some('=') => { chars.next(); match chars.peek() { Some('>') => { chars.next(); Token::Cmp } _ => Token::Leq, } } _ => Token::Lt, } } '>' => { chars.next(); match chars.peek() { Some('>') => { chars.next(); Token::Rsh } Some('=') => { chars.next(); Token::Geq } _ => Token::Gt, } } '+' => { chars.next(); Token::Add } '-' => { chars.next(); match chars.peek() { Some(ch) if ch.is_numeric() => Token::Number(format!( "-{}", chars .peeking_take_while(|c| c.is_alphanumeric() || *c == '_') .collect::(), )), _ => Token::Sub, } } '*' => { chars.next(); if let Some('*') = chars.peek() { chars.next(); Token::Pow } else { Token::Mul } } '/' => { chars.next(); Token::Div } '=' => { chars.next(); if let Some('=') = chars.peek() { chars.next(); } Token::Eq } '!' => { chars.next(); match chars.peek() { Some('!') => { chars.next(); Token::Bol } Some('=') => { chars.next(); Token::Neq } _ => Token::Inv, } } '?' => { chars.next(); Token::Rnd } '\n' => Token::Newline(chars.peeking_take_while(|c| *c == '\n').collect()), _ if ch.is_whitespace() => Token::Whitespace( chars .peeking_take_while(|c| c.is_whitespace() && *c != '\n') .collect(), ), _ => bail!("Unexpected token: {}", ch), }); } Ok(tokens) }