Created
March 22, 2024 16:14
-
-
Save NyxCode/8e92bc3671a8790202b437a3b30c8a11 to your computer and use it in GitHub Desktop.
Rust - Calculator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=8eb18dcae59db15009c090867827a7d9 | |
use core::iter::Peekable; | |
#[derive(Debug)] | |
enum Expr { | |
BinOp(Box<Expr>, Op, Box<Expr>), | |
Num(f64), | |
Neg(Box<Expr>), | |
} | |
impl Expr { | |
fn eval(&self) -> f64 { | |
match self { | |
Self::BinOp(l, Op::Add, r) => l.eval() + r.eval(), | |
Self::BinOp(l, Op::Sub, r) => l.eval() - r.eval(), | |
Self::BinOp(l, Op::Mul, r) => l.eval() * r.eval(), | |
Self::BinOp(l, Op::Div, r) => l.eval() / r.eval(), | |
Self::BinOp(l, Op::Exp, r) => l.eval().powf(r.eval()), | |
Self::Neg(r) => -r.eval(), | |
Self::Num(num) => *num, | |
} | |
} | |
} | |
#[derive(Copy, Clone, Debug, Eq, PartialEq)] | |
enum Token { | |
Op(Op), | |
Num(u32), | |
ParOpen, | |
ParClose, | |
} | |
#[derive(Copy, Clone, Debug, Eq, PartialEq)] | |
enum Op { | |
Add, Sub, Mul, Div, Exp | |
} | |
impl Op { | |
fn presc(self) -> u8 { | |
match self { | |
Self::Add | Self::Sub => 0, | |
Self::Mul | Self::Div => 2, | |
Self::Exp => 4, | |
} | |
} | |
} | |
fn tokenize(src: &str) -> Peekable<impl Iterator<Item = Token> + '_> { | |
let mut chars = src.chars().peekable(); | |
std::iter::from_fn(move || { | |
while chars.peek()?.is_whitespace() { | |
chars.next(); | |
} | |
let token = match chars.next()? { | |
'+' => Token::Op(Op::Add), | |
'-' => Token::Op(Op::Sub), | |
'*' => Token::Op(Op::Mul), | |
'/' => Token::Op(Op::Div), | |
'^' => Token::Op(Op::Exp), | |
'(' => Token::ParOpen, | |
')' => Token::ParClose, | |
c @ '0'..='9' => { | |
let mut num = c as u32 - '0' as u32; | |
while let Some(c @ '0'..='9') = chars.peek() { | |
num = num * 10 + (*c as u32 - '0' as u32); | |
chars.next(); | |
} | |
Token::Num(num) | |
}, | |
c => panic!("illegal character {c}"), | |
}; | |
Some(token) | |
}).peekable() | |
} | |
fn parse_pratt( | |
lex: &mut Peekable<impl Iterator<Item = Token>>, | |
min_presc: u8 | |
) -> Expr { | |
let mut lhs = match lex.next().unwrap() { | |
Token::Num(num) => Expr::Num(num as f64), | |
Token::Op(Op::Sub) => { | |
let rhs = parse_pratt(lex, 3); | |
Expr::Neg(Box::new(rhs)) | |
}, | |
Token::ParOpen => { | |
let inner = parse_pratt(lex, 0); | |
assert_eq!(lex.next(), Some(Token::ParClose)); | |
inner | |
} | |
other => panic!("unexpected token {other:?}"), | |
}; | |
loop { | |
let op = match lex.peek().copied() { | |
Some(Token::Op(op)) if op.presc() < min_presc => { | |
return lhs; | |
} | |
Some(Token::Op(op)) => { | |
lex.next(); | |
op | |
}, | |
None | Some(Token::ParClose) => return lhs, | |
_ => panic!(), | |
}; | |
let rhs = parse_pratt(lex, op.presc() + 1); | |
lhs = Expr::BinOp(Box::new(lhs), op, Box::new(rhs)); | |
} | |
} | |
fn main() { | |
let mut lexer = tokenize("3/4 * 3^-1/4^-1"); | |
println!("{:#?}", parse_pratt(&mut lexer, 0).eval()); | |
assert_eq!(lexer.next(), None, "Trailing input"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment