Skip to content

Instantly share code, notes, and snippets.

@worldOneo
Created November 15, 2022 15:20
Show Gist options
  • Save worldOneo/e637b2b2191d89c7afe5e4e5b7486dcf to your computer and use it in GitHub Desktop.
Save worldOneo/e637b2b2191d89c7afe5e4e5b7486dcf to your computer and use it in GitHub Desktop.
A simple calculator. First input is formula like `10*@log(x)+a` second input are variables like `x=2 a=4`
use std::{
collections::{HashMap, VecDeque},
io,
ops::{Add, Mul},
};
#[derive(Debug)]
enum AST {
Const(f64),
Func(String, Box<AST>),
Var(String),
Pow(Box<AST>, Box<AST>),
Add(Box<AST>, Box<AST>),
Mul(Box<AST>, Box<AST>),
Div(Box<AST>, Box<AST>),
Sub(Box<AST>, Box<AST>),
Neg(Box<AST>),
None(),
}
impl AST {
fn eval(&self, vars: &HashMap<String, AST>) -> f64 {
match self {
AST::None() => panic!("None in eval"),
AST::Const(v) => *v,
AST::Var(name) => vars[name].eval(vars),
AST::Pow(a, b) => a.eval(vars).powf(b.eval(vars)),
AST::Add(a, b) => a.eval(vars).add(b.eval(vars)),
AST::Mul(a, b) => a.eval(vars).mul(b.eval(vars)),
AST::Div(a, b) => a.eval(vars).add(b.eval(vars)),
AST::Sub(a, b) => a.eval(vars) / b.eval(vars),
AST::Neg(a) => -a.eval(vars),
AST::Func(name, ast) => match name.as_str() {
"sqrt" => ast.eval(vars).sqrt(),
"cos" => ast.eval(vars).cos(),
"sin" => ast.eval(vars).sin(),
"tan" => ast.eval(vars).tan(),
"acos" => ast.eval(vars).acos(),
"atan" => ast.eval(vars).atan(),
"asin" => ast.eval(vars).asin(),
"log" => ast.eval(vars).log(10.0),
"log2" => ast.eval(vars).log(2.0),
"sinh" => ast.eval(vars).sinh(),
"cosh" => ast.eval(vars).cosh(),
"tanh" => ast.eval(vars).tanh(),
_ => todo!(),
},
}
}
}
fn parse_num(input: &mut VecDeque<u8>) -> AST {
let mut base = 0.0;
let mut c: u8 = 0;
while !input.is_empty() {
c = input[0];
if c == '.' as u8 {
break;
}
if !c.is_ascii_digit() {
break;
}
input.pop_front();
base *= 10.0;
base += (c - '0' as u8) as f64;
}
if c == '.' as u8 {
let mut point = 0.0;
while !input.is_empty() {
c = input[0];
if !c.is_ascii_digit() {
break;
}
input.pop_front();
point /= 10.0;
point += (c - '0' as u8) as f64;
}
point /= 10.0;
base += point;
}
return AST::Const(base);
}
fn parse_text(input: &mut VecDeque<u8>) -> String {
let mut name: String = "".into();
while !input.is_empty() && !is_operator(input[0]) {
name.push(input.pop_front().unwrap() as char)
}
return name;
}
fn parse_var(input: &mut VecDeque<u8>) -> AST {
return AST::Var(parse_text(input));
}
fn is_operator(c: u8) -> bool {
match c as char {
'+' | '-' | '*' | '/' | '@' | '^' | ':' | '(' | ')' | ' ' | '=' => true,
_ => false,
}
}
fn parse_single(input: &mut VecDeque<u8>) -> AST {
let c = input[0];
let ast = match c {
c if c.is_ascii_digit() => parse_num(input),
c if c == '-' as u8 => {
input.pop_front();
AST::Neg(Box::new(parse(input)))
}
c if c == '@' as u8 => {
input.pop_front();
let name = parse_text(input);
AST::Func(name, Box::new(parse_single(input)))
}
c if c == '(' as u8 => {
input.pop_front();
let brace = parse_single(input);
if input.pop_front().unwrap() != ')' as u8 {
panic!("Expected ')'")
}
return brace;
}
_ => parse_var(input),
};
check_extension(input, ast)
}
fn check_extension(input: &mut VecDeque<u8>, ast: AST) -> AST {
if input.is_empty() {
return ast;
}
let c = input[0];
let ast = match c as char {
'+' => {
input.pop_front();
AST::Add(Box::new(ast), Box::new(parse_single(input)))
}
'-' => {
input.pop_front();
AST::Sub(Box::new(ast), Box::new(parse_single(input)))
}
'*' => {
input.pop_front();
AST::Mul(Box::new(ast), Box::new(parse_single(input)))
}
'/' => {
input.pop_front();
AST::Div(Box::new(ast), Box::new(parse_single(input)))
}
'^' => {
input.pop_front();
AST::Pow(Box::new(ast), Box::new(parse_single(input)))
}
' ' => {
input.pop_front();
ast
}
_ => return ast,
};
return check_extension(input, ast);
}
fn parse(input: &mut VecDeque<u8>) -> AST {
if input.is_empty() {
return AST::None();
}
let next = parse_single(input);
return check_extension(input, next);
}
fn parse_vars(input: &mut VecDeque<u8>) -> HashMap<String, AST> {
let mut map: HashMap<String, AST> = HashMap::new();
while !input.is_empty() {
let name = parse_text(input);
if input.pop_front().unwrap() != '=' as u8 {
panic!("Expected '=' ")
}
let val = parse(input);
map.insert(name, val);
}
map
}
fn main() -> io::Result<()> {
let mut buffer: String = String::new();
io::stdin().read_line(&mut buffer)?;
buffer = buffer.trim().into();
let mut input = buffer.chars().map(|c| c as u8).collect::<VecDeque<u8>>();
let ast = parse(&mut input);
loop {
let mut buffer: String = String::new();
io::stdin().read_line(&mut buffer)?;
buffer = buffer.trim().into();
let mut input = buffer.chars().map(|c| c as u8).collect::<VecDeque<u8>>();
let vars = parse_vars(&mut input);
println!("= {}", ast.eval(&vars));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment