use crate::expression::{
ast::{BinaryOpr, ExprNum},
error::AstError,
};
#[cfg_attr(test, derive(Debug, PartialEq))]
pub enum ExprToken<'a> {
Literal(ExprNum),
Variable(&'a str),
BinaryOpr(BinaryOpr),
LeftParen,
RightParen,
}
impl<'a> ExprToken<'a> {
pub fn literal_from_str(input: &'a str) -> Result<Self, AstError<'a>> {
Ok(ExprToken::Literal(input.replace("_", "").parse().map_err(
|source| AstError::ParseLiteralError { input, source },
)?))
}
}
impl<'a> std::fmt::Display for ExprToken<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ExprToken::Literal(num) => write!(f, "{}", num),
ExprToken::Variable(var) => write!(f, "{}", var),
ExprToken::BinaryOpr(opr) => write!(f, "{}", opr.as_str()),
ExprToken::LeftParen => write!(f, "("),
ExprToken::RightParen => write!(f, ")"),
}
}
}
pub struct Expression<'a> {
pub tokens: Vec<ExprToken<'a>>,
}
impl<'a> Expression<'a> {
pub fn new() -> Self {
Expression { tokens: Vec::new() }
}
pub fn from_expr(infix_expr: &'a str) -> Result<Self, AstError<'a>> {
let mut tokenized = Expression::new();
let mut start_idx = None; let mut is_recording_var = false;
for (i, ch) in infix_expr.char_indices() {
match ch {
'0'..='9' | '.' | '_' => {
if start_idx.is_none() {
start_idx = Some(i);
is_recording_var = false; }
}
'A'..='Z' | 'a'..='z' => {
if let Some(s_idx) = start_idx {
if !is_recording_var {
tokenized
.tokens
.push(ExprToken::literal_from_str(&infix_expr[s_idx..i])?);
start_idx = Some(i);
is_recording_var = true;
}
} else {
start_idx = Some(i);
is_recording_var = true;
}
}
symbol => {
if let Some(s_idx) = start_idx {
if is_recording_var {
tokenized
.tokens
.push(ExprToken::Variable(&infix_expr[s_idx..i]));
} else {
tokenized
.tokens
.push(ExprToken::literal_from_str(&infix_expr[s_idx..i])?);
}
start_idx = None;
}
match symbol {
'+' | '-' | '*' | '/' => {
tokenized
.tokens
.push(ExprToken::BinaryOpr(BinaryOpr::from_opr(
&infix_expr[i..(i + 1)],
)?));
}
'(' => {
tokenized.tokens.push(ExprToken::LeftParen);
}
')' => {
tokenized.tokens.push(ExprToken::RightParen);
}
' ' | '\t' | '\n' => continue, _ => {
return Err(AstError::SyntaxError {
token: &infix_expr[i..(i + 1)],
position: i,
});
}
}
}
}
}
if let Some(s_idx) = start_idx {
if is_recording_var {
tokenized
.tokens
.push(ExprToken::Variable(&infix_expr[s_idx..]));
} else {
tokenized
.tokens
.push(ExprToken::literal_from_str(&infix_expr[s_idx..])?);
}
}
Ok(tokenized)
}
pub fn to_postfix(&mut self) -> Result<&Self, AstError<'a>> {
let mut write_idx = 0; let mut stack = Vec::new();
for cur_idx in 0..self.tokens.len() {
match self.tokens[cur_idx] {
ExprToken::Literal(_) | ExprToken::Variable(_) => {
self.tokens.swap(write_idx, cur_idx);
write_idx += 1;
}
ExprToken::BinaryOpr(cur_opr) => {
'check_pop: while let Some(top_tk) = stack.last() {
if let ExprToken::BinaryOpr(top_opr) = top_tk {
if top_opr.get_precedence() >= cur_opr.get_precedence() {
if let Some(pop_tk) = stack.pop() {
self.tokens[write_idx] = pop_tk;
write_idx += 1;
} else {
break 'check_pop; }
} else {
break 'check_pop; }
} else {
break 'check_pop;
}
}
stack.push(ExprToken::BinaryOpr(cur_opr));
}
ExprToken::LeftParen => stack.push(ExprToken::LeftParen),
ExprToken::RightParen => {
'until_left: while let Some(stack_last) = stack.pop() {
if matches!(stack_last, ExprToken::BinaryOpr(_)) {
self.tokens[write_idx] = stack_last;
write_idx += 1;
} else {
break 'until_left; }
}
}
}
}
while let Some(opr) = stack.pop() {
match opr {
ExprToken::BinaryOpr(_) => {
self.tokens[write_idx] = opr;
write_idx += 1;
}
ExprToken::LeftParen => return Err(AstError::UnmatchedParentheses),
_ => {
return Err(AstError::InvalidExpressionStructure(
"Unexpected token type in operator stack during infix-to-postfix conversion",
));
}
}
}
self.tokens.truncate(write_idx);
Ok(self)
}
}
impl<'a> std::fmt::Display for Expression<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
self.tokens
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(" ")
.replace("( ", "(") .replace(" )", ")")
)
}
}
impl<'a> Default for Expression<'a> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tokenize_expression() {
let expr =
Expression::from_expr(" ( abc123 + 12.33)\n-( (De12fg*12hiJ34)/1.234)K.1 ).2").unwrap();
assert_eq!(
expr.tokens,
vec![
ExprToken::LeftParen,
ExprToken::Variable("abc123"),
ExprToken::BinaryOpr(BinaryOpr::Add),
ExprToken::Literal(12.33),
ExprToken::RightParen,
ExprToken::BinaryOpr(BinaryOpr::Sub),
ExprToken::LeftParen,
ExprToken::LeftParen,
ExprToken::Variable("De12fg"),
ExprToken::BinaryOpr(BinaryOpr::Mul),
ExprToken::Literal(12.0),
ExprToken::Variable("hiJ34"),
ExprToken::RightParen,
ExprToken::BinaryOpr(BinaryOpr::Div),
ExprToken::Literal(1.234),
ExprToken::RightParen,
ExprToken::Variable("K.1"),
ExprToken::RightParen,
ExprToken::Literal(0.2),
]
);
assert_eq!(
expr.to_string(),
"(abc123 + 12.33) - ((De12fg * 12 hiJ34) / 1.234) K.1) 0.2"
);
}
#[test]
fn test_expression_to_postfix() {
let mut expr = Expression::from_expr("(1+(2-3))*(wow/zero)").unwrap();
assert_eq!(expr.to_string(), "(1 + (2 - 3)) * (wow / zero)");
assert_eq!(
expr.to_postfix().unwrap().tokens,
vec![
ExprToken::Literal(1.0),
ExprToken::Literal(2.0),
ExprToken::Literal(3.0),
ExprToken::BinaryOpr(BinaryOpr::Sub),
ExprToken::BinaryOpr(BinaryOpr::Add),
ExprToken::Variable("wow"),
ExprToken::Variable("zero"),
ExprToken::BinaryOpr(BinaryOpr::Div),
ExprToken::BinaryOpr(BinaryOpr::Mul),
]
);
assert_eq!(expr.to_string(), "1 2 3 - + wow zero / *");
}
#[test]
fn test_tokenize_can_not_parse() {
let expr = Expression::from_expr(".1a.1.1 .1").unwrap();
assert_eq!(
expr.tokens,
vec![
ExprToken::Literal(0.1),
ExprToken::Variable("a.1.1"),
ExprToken::Literal(0.1)
]
);
assert_eq!(expr.to_string(), "0.1 a.1.1 0.1");
let expr = Expression::from_expr(".1a.1.1 ..1");
assert!(expr.is_err());
}
}