diff --git a/Cargo.lock b/Cargo.lock index 8801cf6..c5f0a87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + [[package]] name = "rpn" version = "0.1.0" +dependencies = [ + "indoc", +] diff --git a/Cargo.toml b/Cargo.toml index 1a822f6..eb00f78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +indoc = "2.0.4" diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b33dad8 --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +cargo build --target x86_64-pc-windows-gnu +cargo build --target x86_64-pc-windows-gnu -r +cargo build --target x86_64-unknown-linux-gnu +cargo build --target x86_64-unknown-linux-gnu -r +#cargo build --target aarch64-unknown-linux-gnu +#cargo build --target aarch64-unknown-linux-gnu -r +cargo build --target x86_64-unknown-linux-musl +cargo build --target x86_64-unknown-linux-musl -r +#cargo build --target x86_64-unknown-freebsd +#cargo build --target x86_64-unknown-freebsd -r diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f0fc2a9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,561 @@ +use indoc::printdoc; +use std::collections::HashMap; +use std::str::FromStr; +use std::sync::Arc; +pub type Value = f64; +pub type Result = std::result::Result<(), Error>; + +pub struct RPN { + custom: HashMap, + stack: Vec, +} + +#[derive(Debug)] +pub enum Error { + DivisionByZero, + StackUnderflow, + UnknownWord, + InvalidWord, + StackPushFailed, +} + +#[derive(Clone, Debug)] +struct Definition { + words: Arc>, +} + +// https://www.forth.com/starting-forth/2-stack-manipulation-operators-arithmetic/ +// for definition of swap, dup, over, drop operators +#[derive(Debug)] +enum Words { + Add, + Subtract, + Multiply, + Divide, + Modulo, + Floor, + Ceil, + Round, + Abs, + Pow, + Sqrt, + Cbrt, + LogN, + Log, + Sin, + Asin, + Sinh, + Acos, + Cos, + Cosh, + Tan, + Atan, + Tanh, + Epsilon, + Euler, + Pi, + Tau, + Push(Value), + Drop, + Duplicate, + Swap, + Over, + ClearStack, + Emit, + DefineStart, + DefineEnd, + Definition(Definition), + EndOfLine, + Help, + Quit, +} + +impl Default for RPN { + fn default() -> Self { + RPN::new() + } +} + +impl RPN { + pub fn new() -> RPN { + RPN { + custom: HashMap::new(), + stack: Vec::new(), + } + } + + pub fn stack(&self) -> &[Value] { + &self.stack + } + + fn add(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(y + x) + } + + fn subtract(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(y - x) + } + + fn multiply(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(y * x) + } + + fn divide(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + if x == 0f64 { + Err(Error::DivisionByZero) + } else { + self.push(y / x) + } + } + + fn push(&mut self, num: Value) -> Result { + self.stack.push(num); + //push can fail, but I have no idea how to actually catch that. + //This should only happen on an out of memory error anyway + Ok(()) + } + + fn pop(&mut self) -> std::result::Result { + self.stack.pop().ok_or(Error::StackUnderflow) + } + + fn duplicate(&mut self) -> Result { + let x = self.pop()?; + + self.push(x)?; + self.push(x) + } + + fn drop(&mut self) -> Result { + let _ = self.pop()?; + + Ok(()) + } + + fn swap(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(x)?; + self.push(y) + } + + fn over(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(y)?; + self.push(x)?; + self.push(y) + } + + fn modulo(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(y % x) + } + + fn abs(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.abs()) + } + + fn floor(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(RPN::floor_cmp(x, y)) + } + + fn floor_cmp(a: Value, b: Value) -> Value { + if a > b { + a + } else { + b + } + } + + fn ceil(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(RPN::ceil_cmp(x, y)) + } + + fn ceil_cmp(a: Value, b: Value) -> Value { + if a < b { + a + } else { + b + } + } + fn round(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.round()) + } + fn pow(&mut self) -> Result { + let x = self.pop()?; + let y = self.pop()?; + + self.push(y.powf(x)) + } + + fn sqrt(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.sqrt()) + } + + fn cbrt(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.cbrt()) + } + + fn log_natural(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.ln()) + } + + fn log_base10(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.log10()) + } + + fn sin(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.sin()) + } + + fn asin(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.asin()) + } + + fn sinh(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.sinh()) + } + + fn cos(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.cos()) + } + + fn acos(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.acos()) + } + + fn cosh(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.cosh()) + } + + fn tan(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.tan()) + } + + fn atan(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.atan()) + } + + fn tanh(&mut self) -> Result { + let x = self.pop()?; + + self.push(x.tanh()) + } + + fn emit(&mut self) -> Result { + let x = self.pop()?; + + println!("{x}"); + + Ok(()) + } + + fn define(&mut self, name: &str, definition: Definition) { + let name = name.to_lowercase(); + self.custom.insert(name, definition); + } + + fn do_definition(&mut self, definition: &Definition) -> Result { + let words_iter = Arc::clone(&definition.words); + for w in &*words_iter { + match self.do_word(w) { + Err(e) => return Err(e), + Ok(_) => continue, + } + } + Ok(()) + } + + fn do_word(&mut self, word: &Words) -> Result { + match word { + Words::Add => self.add(), + Words::Subtract => self.subtract(), + Words::Multiply => self.multiply(), + Words::Divide => self.divide(), + Words::Duplicate => self.duplicate(), + Words::Drop => self.drop(), + Words::Swap => self.swap(), + Words::Over => self.over(), + Words::Push(x) => self.push(*x), + Words::Modulo => self.modulo(), + Words::Abs => self.abs(), + Words::Floor => self.floor(), + Words::Ceil => self.ceil(), + Words::Round => self.round(), + Words::Pow => self.pow(), + Words::Sqrt => self.sqrt(), + Words::Cbrt => self.cbrt(), + Words::LogN => self.log_natural(), + Words::Log => self.log_base10(), + Words::Sin => self.sin(), + Words::Sinh => self.sinh(), + Words::Asin => self.asin(), + Words::Cos => self.cos(), + Words::Cosh => self.cosh(), + Words::Acos => self.acos(), + Words::Tan => self.tan(), + Words::Atan => self.atan(), + Words::Tanh => self.tanh(), + Words::Emit => self.emit(), + Words::Epsilon => self.push(f64::EPSILON), + Words::Euler => self.push(std::f64::consts::E), + Words::Pi => self.push(std::f64::consts::PI), + Words::Tau => self.push(std::f64::consts::TAU), + Words::ClearStack => { + self.stack.clear(); + Ok(()) + } + Words::Definition(x) => self.do_definition(x), + Words::Help => { + RPN::print_help(); + Ok(()) + } + Words::Quit => std::process::exit(0), + //definitions and line endings are special, and must be handled by eval() + Words::DefineStart => Err(Error::InvalidWord), + Words::DefineEnd => Err(Error::InvalidWord), + Words::EndOfLine => Err(Error::InvalidWord), + } + } + + fn parse(&self, cmd: &str) -> std::result::Result { + let q = cmd.to_ascii_lowercase(); + let def = self.custom.get(&q); + if let Some(x) = def { + return Ok(Words::Definition(x.clone())); + } + match q.as_str() { + "+" => Ok(Words::Add), + "-" => Ok(Words::Subtract), + "*" => Ok(Words::Multiply), + "/" => Ok(Words::Divide), + "dup" => Ok(Words::Duplicate), + "drop" => Ok(Words::Drop), + "swap" => Ok(Words::Swap), + "over" => Ok(Words::Over), + ":" => Ok(Words::DefineStart), + ";" => Ok(Words::DefineEnd), + "%" => Ok(Words::Modulo), + "abs" | "||" => Ok(Words::Abs), + "floor" | "fl" => Ok(Words::Floor), + "ceil" | "cl" => Ok(Words::Ceil), + "round" | "r" => Ok(Words::Round), + "pow" | "^" => Ok(Words::Pow), + "sqrt" | "v" => Ok(Words::Sqrt), + "cbrt" | "v3" => Ok(Words::Cbrt), + "ln" => Ok(Words::LogN), + "log" => Ok(Words::Log), + "sin" => Ok(Words::Sin), + "asin" => Ok(Words::Asin), + "sinh" => Ok(Words::Sinh), + "cos" => Ok(Words::Cos), + "acos" => Ok(Words::Acos), + "cosh" => Ok(Words::Cosh), + "tan" => Ok(Words::Tan), + "atan" => Ok(Words::Atan), + "tanh" => Ok(Words::Tanh), + "me" => Ok(Words::Epsilon), + "euler" | "e" => Ok(Words::Euler), + "pi" => Ok(Words::Pi), + "tau" => Ok(Words::Tau), + "help" | "?" => Ok(Words::Help), + "emit" | "." => Ok(Words::Emit), + "clear" | "c" => Ok(Words::ClearStack), + "quit" | "q" => Ok(Words::Quit), + unk => { + //determine if input is a number, if it is, push it to stack + //otherwise, return UnknownWord error + let num = Value::from_str(unk); + + if let Ok(x) = num { + return Ok(Words::Push(x)); + } + Err(Error::UnknownWord) + } + } + } + + fn get_next(&self, iter: &mut core::str::Split<'_, char>) -> std::result::Result { + let next = iter.next(); + match next { + None => Ok(Words::EndOfLine), + Some(cmd) => self.parse(cmd), + } + } + + pub fn do_define(&mut self, iter: &mut core::str::Split<'_, char>) -> Result { + let name: &str = match iter.next() { + None => return Err(Error::InvalidWord), + Some(x) => x, + }; + + //numbers can not be names of definitions + //if the parse succeeds (indicating that the input name is a number), error out + if Value::from_str(name).is_ok() { + return Err(Error::InvalidWord); + } + + let mut def_working: Vec = Vec::new(); + loop { + match self.get_next(iter) { + Err(e) => return Err(e), + Ok(w) => match w { + Words::DefineStart => { + //can not begin a new definition before this definition has completed + return Err(Error::InvalidWord); + } + Words::DefineEnd => { + let def: Definition = Definition { + words: Arc::new(def_working), + }; + self.define(name, def); + return Ok(()); + } + Words::EndOfLine => { + //line can not end before definition is completed + return Err(Error::InvalidWord); + } + word => { + def_working.push(word); + } + }, + } + } + } + + pub fn eval(&mut self, input: &str) -> Result { + let mut input_vec = input.trim_end().split(' '); + + loop { + let cur = self.get_next(&mut input_vec); + + match cur { + Ok(Words::EndOfLine) => break Ok(()), + Ok(Words::DefineStart) => self.do_define(&mut input_vec)?, + Ok(word) => self.do_word(&word)?, + Err(e) => break Err(e), + } + } + } + + fn print_help() { + printdoc! {" + RPN Calculator Help + + This is an RPN (Reverse Polish Notation) calculator program. This works by pushing numbers to a stack, and then completing operations on the pushed numbers in the order that they were added to the stack. + + Inputs can be chained together by inputting commands seperated by a space. + + Example: 4 [enter] 3 [enter] * [enter] + Will display 12 on the Current Stack (i.e. 4 * 3 = 12) + + Example: 4 [space] 3 [space] * [enter] + Will also display 12 on the Current Stack + + Many more math functions are available: + + [Command] [Function] + quit or q quit calculator (ctrl-c also works) + + add the last two numbers on the stack + - subtract the last two numbers on the stack + * or x multiply the last two numbers on the stack + / divide the last two numbers on the stack + % output the remainder of the division of the last two numbers on the stack (modulus) + clear or c clear the stack of all values + drop or d remove the last number pushed to the stack (this can be repeated multiple times) + floor take the last two numbers from the stack, and push the lower number back onto the stack + ceil take the last two numbers from the stack, and push the higher number back onto the stack + round round the last number on the stack (follows conventional rules, 0.5 rounds up) + abs change the last number on the stack to it's absolute value + pow raise the number on the stack prior to the last input to the power of the last number input + Example: 3 [enter] 2 [enter] pow [enter] Output: 9 + sqrt or v change the last number on the stack to it's square root + cbrt or v3 change the last number on the stack to it's cube root + ln change the last number on the stack to it's natural log + log change the last number on the stack to it's base10 log + sin change the last number on the stack to it's sine value (radians) + asin, sinh the same thing, but arc and hyperbolic instead + cos change the last number on the stack to it's cosine value (radians) + acos, cosh the same thing, but arc and hyperbolic instead + tan change the last number on the stack to it's tangent value (radians) + atan, tanh the same thing, but arc and hyperbolic instead + me pushes the machine epsilon value onto the stack + pi pushes pi onto the stack + tau pushes tau on to the stack + help or ? print this help text + + emit or . print the last number pushed to the stack, removing it from the stack + + + Additionally, this implements a subset of FORTH for a basic level of programability. + + The ':' character is used to begin a definition, and a definition is ended with ';' + + Example: : name 1 2 3 + + ; + + This creates a definition of 'name' that can then be called after it's definition. + + The definition pushes 1, 2, 3, onto the stack, and then adds 3 + 2, and then 5 + 1. + + Definitions can be used inside other definitions: + + Example: : name2 name name name ; + + Creates a definition 'name2' that executes the 'name' definition 3 times. + + + + "} + } +} diff --git a/src/main.rs b/src/main.rs index 469ea24..c54da33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,11 @@ +use rpn::RPN; use std::io; use std::io::Write; -use std::num::ParseFloatError; -use std::str::FromStr; fn main() { - println!("RPN Calculator"); - - let mut stack: Vec = Vec::new(); + println!("RPN Calculator (with FORTH)"); + let mut rpn = RPN::new(); loop { let mut input = String::new(); @@ -21,281 +19,14 @@ fn main() { .read_line(&mut input) .expect("failed to read line"); - let func = match input.trim() { - "q" => Function::Quit, - "+" => Function::Add, - "-" => Function::Subtract, - "*" | "x" => Function::Multiply, - "/" => Function::Divide, - "%" => Function::Modulo, - "clear" | "c" => Function::ClearStack, - "floor" | "fl" => Function::Floor, - "ceil" | "cl" => Function::Ceil, - "round" | "r" => Function::Round, - "abs" => Function::Abs, - "pow" | "^" => Function::Pow, - "sqrt" | "v" => Function::Sqrt, - "cbrt" | "v3" => Function::Cbrt, - "ln" => Function::LogN, - "log" => Function::Log, - "sin" => Function::Sin, - "asin" => Function::Asin, - "sinh" => Function::Sinh, - "acos" => Function::Acos, - "cos" => Function::Cos, - "cosh" => Function::Cosh, - "tan" => Function::Tan, - "atan" => Function::Atan, - "tanh" => Function::Tanh, - "me" => Function::Epsilon, - "euler" | "e" => Function::Euler, - "pi" => Function::Pi, - "tau" => Function::Tau, - "del" | "d" => Function::DeleteLast, - "help" | "?" => Function::Help, - _ => Function::Push(f64::from_str(input.trim())), - }; - - match func { - Function::Push(x) => stack_push(&mut stack, x), - Function::Quit => break, - Function::ClearStack => stack.clear(), - others => operate(&mut stack, others), - } - - println!("Current Stack: "); - - for i in &stack { - println!("{i}"); + match rpn.eval(&input) { + Err(e) => println!("Failed: {e:?}"), + Ok(_) => { + println!("Current Stack: "); + for n in rpn.stack() { + println!("{n}"); + } + } } } } - -#[derive(Debug)] -enum Function { - Add, - Subtract, - Multiply, - Divide, - Modulo, - Floor, - Ceil, - Round, - Abs, - Pow, - Sqrt, - Cbrt, - LogN, - Log, - Sin, - Asin, - Sinh, - Acos, - Cos, - Cosh, - Tan, - Atan, - Tanh, - Epsilon, - Euler, - Pi, - Tau, - Push(Result), - DeleteLast, - ClearStack, - Help, - Quit, -} - -fn stack_push(stack: &mut Vec, num: Result) { - match num { - Ok(num) => stack.push(num), - Err(e) => println!("Cannot push: {e}."), - } -} - -fn small_stack_err(count_expected: usize, func: &str) { - println!("Stack must contain at least {count_expected} numbers to {func}!"); -} - -fn operands_needed(func: &Function) -> usize { - match func { - Function::Add => 2, - Function::Subtract => 2, - Function::Multiply => 2, - Function::Divide => 2, - Function::Modulo => 2, - Function::Floor => 2, - Function::Ceil => 2, - Function::Round => 1, - Function::Abs => 1, - Function::Pow => 2, - Function::Sqrt => 1, - Function::Cbrt => 1, - Function::LogN => 1, - Function::Log => 1, - Function::Sin => 1, - Function::Asin => 1, - Function::Sinh => 1, - Function::Acos => 1, - Function::Cos => 1, - Function::Cosh => 1, - Function::Tan => 1, - Function::Atan => 1, - Function::Tanh => 1, - Function::Epsilon => 0, - Function::Euler => 0, - Function::Pi => 0, - Function::Tau => 0, - Function::Push(_) => 0, - Function::ClearStack => 0, - Function::DeleteLast => 1, - Function::Help => 0, - Function::Quit => 0, - } -} - -fn operate(stack: &mut Vec, func: Function) { - if stack.len() < operands_needed(&func) { - small_stack_err(operands_needed(&func), format!("{func:?}").as_str()); - } - let x; - let y; - //let z; - - match operands_needed(&func) { - 0 => { - x = 0f64; - y = 0f64; - //z = 0f64; - } - 1 => { - x = stack.pop().unwrap_or(0f64); - y = 0f64; - //z = 0f64; - } - 2 => { - x = stack.pop().unwrap_or(0f64); - y = stack.pop().unwrap_or(0f64); - //z = 0f64; - } - 3 => { - x = stack.pop().unwrap_or(0f64); - y = stack.pop().unwrap_or(0f64); - //z = stack.pop().unwrap_or(0f64); - } - _ => { - x = 0f64; - y = 0f64; - //z = 0f64; - println!("Function requires more operands than I know how to pull off of the stack :("); - } - } - - match func { - Function::Add => stack.push(x + y), - Function::Subtract => stack.push(x - y), - Function::Multiply => stack.push(x * y), - Function::Divide => stack.push(y / x), - //x is at top of stack, but divide is order - //dependent. We're dividing the number input prior - //to x (y), by x. i.e. x: 3, y: 6, push = 0.5 - Function::Modulo => stack.push(y % x), - //same reason as division above - Function::Floor => stack.push(floor(x, y)), - Function::Ceil => stack.push(ceil(x, y)), - Function::Round => stack.push(x.round()), - Function::Abs => stack.push(x.abs()), - Function::Pow => stack.push(y.powf(x)), - Function::Sqrt => stack.push(x.sqrt()), - Function::Cbrt => stack.push(x.cbrt()), - Function::LogN => stack.push(x.ln()), - Function::Log => stack.push(x.log10()), - Function::Sin => stack.push(x.sin()), - Function::Asin => stack.push(x.asin()), - Function::Sinh => stack.push(x.sinh()), - Function::Acos => stack.push(x.acos()), - Function::Cos => stack.push(x.cos()), - Function::Cosh => stack.push(x.cosh()), - Function::Tan => stack.push(x.tan()), - Function::Atan => stack.push(x.atan()), - Function::Tanh => stack.push(x.tanh()), - Function::Epsilon => stack.push(f64::EPSILON), - Function::Euler => stack.push(std::f64::consts::E), - Function::Pi => stack.push(std::f64::consts::PI), - Function::Tau => stack.push(std::f64::consts::TAU), - Function::Push(_) => println!("Push failed"), - //this shouldn't happen, as this should be pushed from main() - Function::Quit => println!("Quit failed"), - //this shouldn't happen, as this should be breaking in main() - Function::DeleteLast => {} - //the last value was popped, but we aren't doing anything with it - Function::ClearStack => stack.clear(), - Function::Help => print_help(), - } -} - -fn floor(a: f64, b: f64) -> f64 { - if a > b { - a - } else { - b - } -} - -fn ceil(a: f64, b: f64) -> f64 { - if a < b { - a - } else { - b - } -} - -fn print_help() { - println!("RPN Calculator Help"); - println!(); - println!("This is an RPN (Reverse Polish Notation) calculator program. This works by pushing numbers to a stack, and then completing operations on the pushed numbers in the order that they were added to the stack."); - println!(); - println!("Example: 4 [enter] 3 [enter] * [enter]"); - println!("Will display 12 on the Current Stack (i.e. 4 * 3 = 12)"); - println!(); - println!("Many more math functions are available:"); - println!(); - println!("[Command] [Function]"); - println!("q quit calculator (ctrl-c also works)"); - println!("+ add the last two numbers on the stack"); - println!("- subtract the last two numbers on the stack"); - println!("* or x multiply the last two numbers on the stack"); - println!("/ divide the last two numbers on the stack"); - println!("% output the remainder of the division of the last two numbers on the stack (modulus)"); - println!("clear or c clear the stack of all values"); - println!("del or d remove the last number pushed to the stack (this can be repeated multiple times)"); - println!("floor take the last two numbers from the stack, and push the lower number back onto the stack"); - println!("ceil take the last two numbers from the stack, and push the higher number back onto the stack"); - println!("round round the last number on the stack (follows conventional rules, 0.5 rounds up)"); - println!("abs change the last number on the stack to it's absolute value"); - println!("pow raise the number on the stack prior to the last input to the power of the last number input"); - println!(" Example: 3 [enter] 2 [enter] pow [enter] Output: 9"); - println!("sqrt or v change the last number on the stack to it's square root"); - println!("cbrt or v3 change the last number on the stack to it's cube root"); - println!("ln change the last number on the stack to it's natural log"); - println!("log change the last number on the stack to it's base10 log"); - println!( - "sin change the last number on the stack to it's sine value (radians)" - ); - println!("asin, sinh the same thing, but arc and hyperbolic instead"); - println!( - "cos change the last number on the stack to it's cosine value (radians)" - ); - println!("acos, cosh the same thing, but arc and hyperbolic instead"); - println!( - "tan change the last number on the stack to it's tangent value (radians)" - ); - println!("atan, tanh the same thing, but arc and hyperbolic instead"); - println!("me pushes the machine epsilon value onto the stack"); - println!("pi pushes pi onto the stack"); - println!("tau pushes tau on to the stack"); - println!("help or ? print this help text"); - println!(); - println!(); -}