3 Commits
v1 ... devel

Author SHA1 Message Date
c7dfc926ed now with much nicer user input interaction! 2023-10-08 22:18:31 -04:00
a5e8e7db45 added autosum 2023-09-21 17:59:21 -04:00
da34f7dd40 added additional features and refactoring 2023-09-21 17:53:22 -04:00
5 changed files with 917 additions and 293 deletions

308
Cargo.lock generated
View File

@@ -2,6 +2,314 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clipboard-win"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "errno"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "fd-lock"
version = "3.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
dependencies = [
"cfg-if",
"rustix",
"windows-sys",
]
[[package]]
name = "home"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
dependencies = [
"windows-sys",
]
[[package]]
name = "indoc"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
[[package]]
name = "libc"
version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "linux-raw-sys"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45786cec4d5e54a224b15cb9f06751883103a27c19c93eda09b0b4f5f08fefac"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
]
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "rpn"
version = "0.1.0"
dependencies = [
"indoc",
"simple-repl",
]
[[package]]
name = "rustix"
version = "0.38.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustyline"
version = "12.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
"clipboard-win",
"fd-lock",
"home",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"scopeguard",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "simple-repl"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b458204316913f8fc5da3d0666c7773c2d532f99856eae62861fb801df423a"
dependencies = [
"rustyline",
]
[[package]]
name = "smallvec"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
[[package]]
name = "str-buf"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

View File

@@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
indoc = "2.0.4"
simple-repl = "0.1.4"

10
build.sh Executable file
View File

@@ -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

576
src/lib.rs Normal file
View File

@@ -0,0 +1,576 @@
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<String, Definition>,
stack: Vec<Value>,
}
#[derive(Debug)]
pub enum Error {
DivisionByZero,
StackUnderflow,
UnknownWord,
InvalidWord,
StackPushFailed,
}
#[derive(Clone, Debug)]
struct Definition {
words: Arc<Vec<Words>>,
}
// 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,
Sum,
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<Value, Error> {
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 sum(&mut self) -> Result {
let mut accumulator = self.pop()?;
while let Ok(x) = self.pop() {
accumulator += x;
}
self.push(accumulator)
}
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::Sum => self.sum(),
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<Words, Error> {
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),
"sum" => Ok(Words::Sum),
"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<Words, Error> {
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<Words> = 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
sum sum the entire stack, pushing the result back onto 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.
"}
}
}

View File

@@ -1,301 +1,29 @@
use std::io;
use std::io::Write;
use std::num::ParseFloatError;
use std::str::FromStr;
use rpn::RPN;
use simple_repl::*;
fn main() {
println!("RPN Calculator");
println!("RPN Calculator (with FORTH)");
let mut stack: Vec<f64> = Vec::new();
loop {
let mut input = String::new();
print!("> ");
io::stdout()
.flush()
.expect("flush to stdout should not fail");
io::stdin()
.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),
let mut rpn = RPN::new();
//we do not want to exit if rpn.eval returns an error, we just want to print it for the user
//no need to use anything other than () for passthrough and error types
let mut eval = |input: &str|-> Result<EvalResult<()>, ()> {
match rpn.eval(input) {
Err(e) => println!("Failed: {e:?}"),
Ok(_) => {
println!("Current Stack: ");
for n in rpn.stack() {
println!("{n}");
}
}
}
//no need to have simple-repl do anything but loop forever with this application
Ok(EvalResult::Continue)
};
println!("Current Stack: ");
for i in &stack {
println!("{i}");
}
}
//simple-repl handles the loop for us
let _ = repl(&mut eval);
}
#[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<f64, ParseFloatError>),
DeleteLast,
ClearStack,
Help,
Quit,
}
fn stack_push(stack: &mut Vec<f64>, num: Result<f64, ParseFloatError>) {
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<f64>, 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!();
}