use std::fmt::Debug;
pub fn eval(source: &str, width: i32, height: i32) -> Result<i32> {
let mut yard = Yard::new(source)?;
while !yard.source.is_empty() {
yard.shunt()?;
}
let mut ints = vec![];
for token in yard.expel()? {
match token {
Token::Int(int) => ints.push(int),
Token::Un(un) =>
match ints.pop() {
Some(int) => ints.push(UnFn::from(un)(int)),
_ => panic!(),
},
Token::Bin(bin) =>
match (ints.pop(), ints.pop()) {
(Some(0), Some(_)) if bin == Binary::Div => return Err(Error::DivideByZero),
(Some(right), Some(left)) => ints.push(BinFn::from(bin)(left, right)),
_ => panic!(),
},
Token::Width => ints.push(width),
Token::Height => ints.push(height),
Token::OpenParen => return Err(Error::DanglingOpenParen),
Token::CloseParen => return Err(Error::DanglingCloseParen),
}
}
Ok(*ints.first().unwrap())
}
struct Yard<'a> {
source: &'a str,
detour: Vec<Token>,
target: Vec<Token>,
edicts: [Edict; 2],
mode: Mode,
}
impl<'a> Yard<'a> {
fn new(source: &'a str) -> Result<Self> {
let source = source.trim_start();
if source.is_empty() {
Err(Error::EmptyExpression)
} else {
Ok(Self { source, detour: vec![], target: vec![], edicts: [default_placing, default_binding], mode: Mode::Place })
}
}
fn shunt(&mut self) -> Result<()> {
let (token, source) = Token::claim(self.source, self)?;
self.edicts[self.mode as usize](self, token)?;
Ok(self.source = source.trim_start())
}
fn expel(mut self) -> Result<Vec<Token>> {
match (self.detour.pop(), self.mode) {
(Some(Token::Bin(bin)), Mode::Place) => return Err(Error::DanglingOperator(char::from(bin))),
(Some(Token::Un(un)), Mode::Place) => return Err(Error::DanglingOperator(char::from(un))),
(Some(operator), _) => self.target.push(operator),
_ => (),
}
while !self.detour.is_empty() {
self.target.push(self.detour.pop().unwrap());
}
Ok(self.target)
}
fn insert(&mut self, new_token: Token) -> Result<()> {
while let Some(old_token) = self.detour.pop() {
match (old_token.clone(), new_token.clone()) {
(_, Token::OpenParen) | (Token::Un(_), Token::Un(_)) => {
self.detour.push(old_token);
break;
},
(Token::OpenParen, Token::CloseParen) => return Ok(()),
(Token::OpenParen, new_operator) => {
self.detour.push(Token::OpenParen);
self.detour.push(new_operator);
return Ok(());
},
(old_operator, Token::CloseParen) => self.target.push(old_operator),
(old_operator, new_operator) => {
if old_operator.precedence() >= new_operator.precedence() {
self.target.push(old_operator);
} else {
self.detour.push(old_operator);
break;
}
}
}
}
Ok(self.detour.push(new_token))
}
}
type UnFn = fn(i32) -> i32;
type BinFn = fn(i32, i32) -> i32;
use std::ops::*;
#[derive(Clone, Debug, PartialEq)]
enum Token {
Int(i32),
Un(Unary),
Bin(Binary),
OpenParen,
CloseParen,
Width,
Height,
}
impl Token {
const affirm: Token = Token::Un(Unary::Affirm);
const negate: Token = Token::Un(Unary::Negate);
const add: Token = Token::Bin(Binary::Add);
const sub: Token = Token::Bin(Binary::Sub);
const mul: Token = Token::Bin(Binary::Mul);
const div: Token = Token::Bin(Binary::Div);
fn claim<'a>(source: &'a str, yard: &Yard) -> Result<(Token, &'a str)> {
let mut chars = source.chars();
let token = match yard.mode {
Mode::Place => match chars.next().unwrap() {
'0' ..= '9' => {
let digit_count = chars.take_while(|c| c.is_digit(10)).count() + 1;
return Ok((Token::Int(source[0 .. digit_count].parse().unwrap()), &source[digit_count ..]));
},
'w' => Token::Width,
'h' => Token::Height,
'+' => Token::affirm,
'-' => Token::negate,
'*' => return Err(Error::MisplacedOperator('*')),
'/' => return Err(Error::MisplacedOperator('/')),
'(' => Token::OpenParen,
')' => return Err(Error::MisplacedCloseParen),
c => return Err(Error::UnknownCharacter(c))
},
Mode::Bind => match chars.next().unwrap() {
'0' ..= '9' => {
let digit_count = chars.take_while(|c| c.is_digit(10)).count() + 1;
return Err(Error::MisplacedInteger(source[0 .. digit_count].to_string()));
},
'w' => return Err(Error::MisplacedInteger("w".into())),
'h' => return Err(Error::MisplacedInteger("h".into())),
'+' => Token::add,
'-' => Token::sub,
'*' => Token::mul,
'/' => Token::div,
'(' => return Err(Error::MisplacedOpenParen),
')' => Token::CloseParen,
c => return Err(Error::UnknownCharacter(c))
}
};
Ok((token, &source[1 ..]))
}
fn precedence(&self) -> i32 {
match self {
&Token::affirm | &Token::negate => 3,
&Token::add | &Token::sub => 1,
&Token::mul | &Token::div => 2,
_ => panic!(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
enum Unary { Affirm, Negate }
impl From<Unary> for UnFn {
fn from(un: Unary) -> Self {
match un {
Unary::Affirm => i32::into,
Unary::Negate => i32::neg,
}
}
}
impl From<Unary> for char {
fn from(un: Unary) -> Self {
match un {
Unary::Affirm => '+',
Unary::Negate => '-'
}
}
}
#[derive(Clone, Debug, PartialEq)]
enum Binary { Add, Sub, Mul, Div }
impl From<Binary> for BinFn {
fn from(bin: Binary) -> Self {
match bin {
Binary::Add => i32::add,
Binary::Sub => i32::sub,
Binary::Mul => i32::mul,
Binary::Div => i32::div,
}
}
}
impl From<Binary> for char {
fn from(bin: Binary) -> Self {
match bin {
Binary::Add => '+',
Binary::Sub => '-',
Binary::Mul => '*',
Binary::Div => '/',
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Mode {
Place, Bind
}
type Edict = fn (&mut Yard, token: Token) -> Result<()>;
fn default_placing(yard: &mut Yard, token: Token) -> Result<()> {
match token {
Token::Int(_) | Token::Width | Token::Height => {
yard.target.push(token);
yard.mode = Mode::Bind;
},
Token::Un(_) => {
yard.insert(token)?;
},
Token::OpenParen => {
yard.edicts[Mode::Bind as usize] = paren_binding;
yard.insert(token)?
},
_ => panic!(),
}
Ok(())
}
fn default_binding(yard: &mut Yard, token: Token) -> Result<()> {
match token {
Token::Bin(_) => {
yard.insert(token)?;
yard.mode = Mode::Place;
},
Token::CloseParen => return Err(Error::DanglingCloseParen),
_ => panic!()
}
Ok(())
}
fn paren_binding(yard: &mut Yard, token: Token) -> Result<()> {
if token == Token::CloseParen {
yard.insert(token)
} else {
default_binding(yard, token)
}
}
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, PartialEq, thiserror::Error)]
pub enum Error {
#[error("empty expression")]
EmptyExpression,
#[error("unknown character: '{0}'")]
UnknownCharacter(char),
#[error("misplaced integer '{0}'")]
MisplacedInteger(String),
#[error("misplaced operator '{0}'")]
MisplacedOperator(char),
#[error("dangling operator '{0}'")]
DanglingOperator(char),
#[error("misplaced '('")]
MisplacedOpenParen,
#[error("misplaced ')'")]
MisplacedCloseParen,
#[error("dangling '('")]
DanglingOpenParen,
#[error("dangling ')'")]
DanglingCloseParen,
#[error("divide by zero")]
DivideByZero,
}