#![warn(clippy::allow_attributes_without_reason)]
#![warn(clippy::missing_safety_doc)]
#![warn(clippy::undocumented_unsafe_blocks)]
#![warn(clippy::unnecessary_safety_doc)]
#![warn(clippy::unwrap_in_result)]
#![warn(clippy::unwrap_used)]
#![warn(missing_docs)]
pub mod op;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum InputToken<V, F, O> {
Value(V),
LeftParen,
RightParen,
Function(F),
#[deprecated(
since = "0.1.1",
note = "Please use ArgSeparator instead. This variant will be removed in 0.2.0"
)]
ArgSeperator,
ArgSeparator,
Operator(O),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum StackToken<F, O> {
LeftParen(usize),
Function(F),
Operator(O),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum OutputToken<V, F, O> {
Value(V),
Function(F),
Operator(O),
}
pub trait Operator {
fn precedence(&self) -> usize;
fn is_left_associative(&self) -> bool;
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ParenMissmatchError {
pos: usize,
}
impl ParenMissmatchError {
pub fn pos(&self) -> usize {
self.pos
}
}
impl std::fmt::Display for ParenMissmatchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unexpected parenthese at position {}", self.pos)
}
}
impl std::error::Error for ParenMissmatchError {}
pub fn to_postfix<V, F, O>(
infix: impl IntoIterator<Item = InputToken<V, F, O>>,
) -> Result<Vec<OutputToken<V, F, O>>, ParenMissmatchError>
where
O: Operator,
{
let mut out_queue: Vec<OutputToken<V, F, O>> = Vec::new();
let mut stack: Vec<StackToken<F, O>> = Vec::new();
let mut paren_count: isize = 0;
for (pos, token) in infix.into_iter().enumerate() {
match token {
InputToken::Value(value) => out_queue.push(OutputToken::Value(value)),
InputToken::LeftParen => {
paren_count += 1;
stack.push(StackToken::LeftParen(pos))
}
InputToken::RightParen if paren_count == 0 => return Err(ParenMissmatchError { pos }),
InputToken::RightParen => {
paren_count -= 1;
while let Some(StackToken::Operator(op)) =
stack.pop_if(|token| matches!(token, StackToken::Operator(_)))
{
out_queue.push(OutputToken::Operator(op))
}
stack.pop();
if let Some(StackToken::Function(func)) =
stack.pop_if(|token| matches!(token, StackToken::Function(_)))
{
out_queue.push(OutputToken::Function(func));
}
}
InputToken::Function(func) => stack.push(StackToken::Function(func)),
#[expect(deprecated, reason = "")]
InputToken::ArgSeperator | InputToken::ArgSeparator => {
while let Some(StackToken::Operator(o)) =
stack.pop_if(|token| matches!(token, StackToken::Operator(_)))
{
out_queue.push(OutputToken::Operator(o))
}
}
InputToken::Operator(o1) => {
while let Some(StackToken::Operator(o2)) = stack.last() {
if o2.precedence() > o1.precedence()
|| (o1.precedence() == o2.precedence() && o1.is_left_associative())
{
let Some(StackToken::Operator(o2)) = stack.pop() else {
unsafe { std::hint::unreachable_unchecked() }
};
out_queue.push(OutputToken::Operator(o2))
} else {
break;
}
}
stack.push(StackToken::Operator(o1));
}
}
}
for token in stack.into_iter().rev() {
let out = match token {
StackToken::LeftParen(pos) => return Err(ParenMissmatchError { pos }),
StackToken::Function(func) => OutputToken::Function(func),
StackToken::Operator(o) => OutputToken::Operator(o),
};
out_queue.push(out);
}
Ok(out_queue)
}
#[cfg(test)]
mod tests {
use crate::{
op::{Logical, Math},
to_postfix, InputToken, OutputToken,
};
#[test]
fn value_only() {
let post_fix = to_postfix([InputToken::<_, (), Math>::Value(1)]);
assert_eq!(post_fix, Ok(vec![OutputToken::Value(1)]));
}
#[test]
fn simple_addition() {
let post_fix = to_postfix::<_, (), _>([
InputToken::Value(1),
InputToken::Operator(Math::Add),
InputToken::Value(2),
]);
assert_eq!(
post_fix,
Ok(vec![
OutputToken::Value(1),
OutputToken::Value(2),
OutputToken::Operator(Math::Add)
])
);
}
#[test]
fn precedence_0() {
let post_fix = to_postfix::<_, (), _>([
InputToken::Value(1),
InputToken::Operator(Math::Mul),
InputToken::Value(2),
InputToken::Operator(Math::Add),
InputToken::Value(3),
]);
assert_eq!(
post_fix,
Ok(vec![
OutputToken::Value(1),
OutputToken::Value(2),
OutputToken::Operator(Math::Mul),
OutputToken::Value(3),
OutputToken::Operator(Math::Add)
])
)
}
#[test]
fn precedence_1() {
let post_fix = to_postfix::<_, (), _>([
InputToken::Value(1),
InputToken::Operator(Math::Add),
InputToken::Value(2),
InputToken::Operator(Math::Mul),
InputToken::Value(3),
]);
assert_eq!(
post_fix,
Ok(vec![
OutputToken::Value(1),
OutputToken::Value(2),
OutputToken::Value(3),
OutputToken::Operator(Math::Mul),
OutputToken::Operator(Math::Add)
])
)
}
#[test]
fn wikipedia_example() {
let post_fix = to_postfix([
InputToken::Function("sin"),
InputToken::LeftParen,
InputToken::Function("max"),
InputToken::LeftParen,
InputToken::Value(2),
InputToken::ArgSeparator,
InputToken::Value(3),
InputToken::RightParen,
InputToken::Operator(Math::Div),
InputToken::Value(3),
InputToken::Operator(Math::Mul),
InputToken::Value(4),
InputToken::RightParen,
]);
assert_eq!(
post_fix,
Ok(vec![
OutputToken::Value(2),
OutputToken::Value(3),
OutputToken::Function("max"),
OutputToken::Value(3),
OutputToken::Operator(Math::Div),
OutputToken::Value(4),
OutputToken::Operator(Math::Mul),
OutputToken::Function("sin"),
])
)
}
#[test]
fn unary_operator_1() {
let postfix = to_postfix([
InputToken::<_, (), _>::Operator(Logical::Not),
InputToken::Value(true),
]);
assert_eq!(
postfix,
Ok(vec![
OutputToken::Value(true),
OutputToken::Operator(Logical::Not)
])
)
}
#[test]
fn unexpected_closing_paren() {
let postfix = to_postfix([
InputToken::Value(false),
InputToken::RightParen,
InputToken::Operator(Logical::And),
InputToken::<_, (), _>::Operator(Logical::Not),
InputToken::Value(true),
]);
assert_eq!(postfix, Err(crate::ParenMissmatchError { pos: 1 }))
}
#[test]
fn missing_closing_paren() {
let postfix = to_postfix([
InputToken::Value(false),
InputToken::LeftParen,
InputToken::Operator(Logical::And),
InputToken::<_, (), _>::Operator(Logical::Not),
InputToken::Value(true),
]);
assert_eq!(postfix, Err(crate::ParenMissmatchError { pos: 1 }))
}
#[test]
fn unary_operator_2() {
let postfix = to_postfix([
InputToken::Value(false),
InputToken::Operator(Logical::And),
InputToken::<_, (), _>::Operator(Logical::Not),
InputToken::Value(true),
]);
assert_eq!(
postfix,
Ok(vec![
OutputToken::Value(false),
OutputToken::Value(true),
OutputToken::Operator(Logical::Not),
OutputToken::Operator(Logical::And),
])
)
}
#[test]
fn function_call_without_paren() {
let postfix1 = to_postfix([
InputToken::<_, _, Math>::Function("fn"),
InputToken::Value(1),
]);
let postfix2 = to_postfix([
InputToken::Function("fn"),
InputToken::LeftParen,
InputToken::Value(1),
InputToken::RightParen,
]);
assert_eq!(postfix1, postfix2);
}
#[test]
fn function_call_without_paren_multi_arg() {
let postfix1 = to_postfix([
InputToken::<_, _, Math>::Function("fn"),
InputToken::Value(1),
InputToken::ArgSeparator,
InputToken::Value(2),
InputToken::Operator(Math::Add),
InputToken::Value(2),
]);
let postfix2 = to_postfix([
InputToken::Function("fn"),
InputToken::LeftParen,
InputToken::Value(1),
InputToken::ArgSeparator,
InputToken::Value(2),
InputToken::Operator(Math::Add),
InputToken::Value(2),
InputToken::RightParen,
]);
assert_eq!(postfix1, postfix2);
}
#[test]
fn function_call_without_paren_multi_arg_following_op() {
let postfix1 = to_postfix([
InputToken::<_, _, Math>::Function("fn"),
InputToken::Value(1),
InputToken::ArgSeparator,
InputToken::Value(2),
InputToken::Operator(Math::Add),
InputToken::Value(2),
]);
let postfix2 = to_postfix([
InputToken::Function("fn"),
InputToken::LeftParen,
InputToken::Value(1),
InputToken::ArgSeparator,
InputToken::Value(2),
InputToken::RightParen,
InputToken::Operator(Math::Add),
InputToken::Value(2),
]);
assert_ne!(postfix1, postfix2);
}
}