Skip to content

Instantly share code, notes, and snippets.

@NyxCode
Created March 22, 2024 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NyxCode/8e92bc3671a8790202b437a3b30c8a11 to your computer and use it in GitHub Desktop.
Save NyxCode/8e92bc3671a8790202b437a3b30c8a11 to your computer and use it in GitHub Desktop.
Rust - Calculator
// 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