Compare commits
10 commits
c575e0bf4d
...
4a7e284047
Author | SHA1 | Date | |
---|---|---|---|
4a7e284047 | |||
b73ef17d8d | |||
b086186949 | |||
6ffd55a637 | |||
e8bec6e32d | |||
4f82f8ace7 | |||
e241b824cc | |||
fbd05b9775 | |||
06a24c6a7d | |||
04dacdb562 |
8 changed files with 233 additions and 82 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
|
65
Cargo.lock
generated
65
Cargo.lock
generated
|
@ -1,65 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# 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 = "evalexpr"
|
|
||||||
version = "7.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1d4fd7bd9e32c1205549decf6f36772d7b606a579b26afaffa335ae148151a5d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.126"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "numtoa"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quickmaths"
|
|
||||||
version = "0.1.1"
|
|
||||||
dependencies = [
|
|
||||||
"evalexpr",
|
|
||||||
"termion",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.2.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_termios"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
|
||||||
dependencies = [
|
|
||||||
"redox_syscall",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "termion"
|
|
||||||
version = "1.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"numtoa",
|
|
||||||
"redox_syscall",
|
|
||||||
"redox_termios",
|
|
||||||
]
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "quickmaths"
|
name = "quickmaths"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
# quickmaths
|
||||||
|
|
||||||
|
A small Rust program that can do common math from the command line.
|
||||||
|
|
||||||
|
I got VERY tired of opening a calculator or a full Python interpreter to do simple math, so I initially created `quickmaths` in Python to do math from the command line.
|
||||||
|
|
||||||
|
As the features I wanted changed, such as variable assignment, interactive mode, and output status and formatting, I decided to rebuild the utility in Rust.
|
||||||
|
|
||||||
|
|
6
src/global.rs
Normal file
6
src/global.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
pub const EULER: f64 = 2.718281828459045;
|
||||||
|
pub const GOLDEN_RATIO: f64 = 1.618033988749895;
|
||||||
|
pub const PI: f64 = 3.141592653589793;
|
||||||
|
pub const ROOT_TWO: f64 = 1.414213562373095;
|
||||||
|
|
127
src/helper.rs
Normal file
127
src/helper.rs
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
use evalexpr::{
|
||||||
|
Value,
|
||||||
|
|
||||||
|
EvalexprError
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
|
// Mathematics
|
||||||
|
pub fn logarithm(arg: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
let arguments: Vec<Value>;
|
||||||
|
let count: usize;
|
||||||
|
if arg.is_tuple() {
|
||||||
|
arguments = arg.as_tuple()?;
|
||||||
|
count = arguments.len();
|
||||||
|
} else if arg.is_float() {
|
||||||
|
arguments = vec!(arg.as_float()?.into());
|
||||||
|
count = 1;
|
||||||
|
} else if arg.is_int() {
|
||||||
|
arguments = vec!((arg.as_int()? as f64).into());
|
||||||
|
count = 1;
|
||||||
|
} else {
|
||||||
|
return Err(EvalexprError::CustomMessage("Expected numbers".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let output: Value;
|
||||||
|
match count {
|
||||||
|
1 => {
|
||||||
|
let argument = &arguments[0];
|
||||||
|
if !argument.is_number() {
|
||||||
|
return Err(EvalexprError::CustomMessage("Expected number".to_string()));
|
||||||
|
}
|
||||||
|
let number = if argument.is_float() { argument.as_float()? } else { argument.as_int()? as f64 };
|
||||||
|
output = number.ln().into();
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
let arg_value = &arguments[0];
|
||||||
|
let arg_base = &arguments[1];
|
||||||
|
if !(arg_value.is_number() && arg_base.is_number()) {
|
||||||
|
return Err(EvalexprError::CustomMessage("Expected two numbers".to_string()));
|
||||||
|
}
|
||||||
|
let value: f64 = if arg_value.is_float() { arg_value.as_float()? } else { arg_value.as_int()? as f64 };
|
||||||
|
let base: f64 = if arg_base.is_float() { arg_base.as_float()? } else { arg_base.as_int()? as f64 };
|
||||||
|
output = value.log(base).into();
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(EvalexprError::WrongFunctionArgumentAmount { expected: 2, actual: count });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn square_root(arg: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
if !arg.is_number() {
|
||||||
|
return Err(EvalexprError::CustomMessage("Expected a number.".to_string()));
|
||||||
|
}
|
||||||
|
let value: f64 = if arg.is_float() { arg.as_float()? } else { arg.as_int()? as f64 };
|
||||||
|
return Ok(value.sqrt().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data Science
|
||||||
|
pub fn average(arg: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
let arguments = arg.as_tuple()?;
|
||||||
|
let count = arguments.len() as i64;
|
||||||
|
let mut is_float = false;
|
||||||
|
let mut total_i = 0i64;
|
||||||
|
let mut total_f = 0f64;
|
||||||
|
|
||||||
|
for argument in arguments {
|
||||||
|
if let Value::Float(float) = argument {
|
||||||
|
if !is_float {
|
||||||
|
total_f = total_i as f64;
|
||||||
|
is_float = true;
|
||||||
|
}
|
||||||
|
total_f += float;
|
||||||
|
} else if let Value::Int(int) = argument {
|
||||||
|
if is_float {
|
||||||
|
total_f += int as f64;
|
||||||
|
} else {
|
||||||
|
total_i += int;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result_i: i64;
|
||||||
|
let result_f: f64;
|
||||||
|
if !is_float {
|
||||||
|
is_float = total_i % count == 0;
|
||||||
|
total_f = total_i as f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_float {
|
||||||
|
result_f = total_f / (count as f64);
|
||||||
|
return Ok(result_f.into());
|
||||||
|
} else {
|
||||||
|
result_i = total_i / count;
|
||||||
|
return Ok(result_i.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radix conversion
|
||||||
|
pub fn binary(arg: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
if !arg.is_string() {
|
||||||
|
let num = arg.as_int()?;
|
||||||
|
let fmt = format!("0b{:b}", num);
|
||||||
|
return Ok(fmt.into());
|
||||||
|
}
|
||||||
|
util::parse_radix("0b", 2, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hexadecimal(arg: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
if !arg.is_string() {
|
||||||
|
let num = arg.as_int()?;
|
||||||
|
let fmt = format!("0x{:X}", num);
|
||||||
|
return Ok(fmt.into());
|
||||||
|
}
|
||||||
|
util::parse_radix("0x", 16, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn octal(arg: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
if !arg.is_string() {
|
||||||
|
let num = arg.as_int()?;
|
||||||
|
let fmt = format!("{:#o}", num);
|
||||||
|
return Ok(fmt.into());
|
||||||
|
}
|
||||||
|
util::parse_radix("0o", 8, arg)
|
||||||
|
}
|
84
src/main.rs
84
src/main.rs
|
@ -1,55 +1,97 @@
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
io::stdin
|
io::{
|
||||||
|
stdin,
|
||||||
|
stdout,
|
||||||
|
|
||||||
|
Write
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
use evalexpr::{
|
use evalexpr::{
|
||||||
|
context_map,
|
||||||
|
|
||||||
eval_with_context_mut,
|
eval_with_context_mut,
|
||||||
|
|
||||||
HashMapContext,
|
HashMapContext,
|
||||||
Value,
|
Value
|
||||||
ValueType
|
|
||||||
};
|
};
|
||||||
use termion::{
|
use termion::{
|
||||||
color,
|
color,
|
||||||
style
|
style
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod global;
|
||||||
|
mod helper;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub const VERSION: &str = "0.2.0";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut context = HashMapContext::new();
|
let mut context = context_map! {
|
||||||
|
// globals
|
||||||
|
"e" => global::EULER,
|
||||||
|
"phi" => global::GOLDEN_RATIO,
|
||||||
|
"pi" => global::PI,
|
||||||
|
"√2" => global::ROOT_TWO,
|
||||||
|
|
||||||
|
// math functions
|
||||||
|
"log" => Function::new(|arg| helper::logarithm(arg)),
|
||||||
|
"sqrt" => Function::new(|arg| helper::square_root(arg)),
|
||||||
|
|
||||||
|
// data science functions
|
||||||
|
"avg" => Function::new(|arg| helper::average(arg)),
|
||||||
|
|
||||||
|
// radix functions
|
||||||
|
"bin" => Function::new(|arg| helper::binary(arg)),
|
||||||
|
"hex" => Function::new(|arg| helper::hexadecimal(arg)),
|
||||||
|
"oct" => Function::new(|arg| helper::octal(arg)),
|
||||||
|
|
||||||
|
// character aliases
|
||||||
|
"ϕ" => global::GOLDEN_RATIO,
|
||||||
|
"π" => global::PI,
|
||||||
|
"√" => Function::new(|arg| helper::square_root(arg))
|
||||||
|
}.unwrap();
|
||||||
let expressions: Vec<String> = env::args().skip(1).collect();
|
let expressions: Vec<String> = env::args().skip(1).collect();
|
||||||
if expressions.len() == 0 {
|
if expressions.len() == 0 {
|
||||||
println!("{}quickmaths v0.1.1{}\n{}Interactive Mode{}", style::Bold, style::Reset, style::Faint, style::Reset);
|
println!("{}quickmaths v{}{}\n{}Interactive Mode{}", style::Bold, VERSION, style::Reset, style::Faint, style::Reset);
|
||||||
loop {
|
loop {
|
||||||
|
print!("> ");
|
||||||
|
stdout().flush().unwrap();
|
||||||
let mut i_line = String::new();
|
let mut i_line = String::new();
|
||||||
let line_result = stdin().read_line(&mut i_line);
|
let line_result = stdin().read_line(&mut i_line);
|
||||||
if line_result.is_err() {
|
if line_result.is_err() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let line = i_line.trim().to_string();
|
let line = i_line.trim().to_string();
|
||||||
if line.is_empty() {
|
match line.as_str() {
|
||||||
break;
|
"" |
|
||||||
|
"exit" => break,
|
||||||
|
_ => do_eval(line, &mut context)
|
||||||
}
|
}
|
||||||
let result = do_eval(line, &mut context);
|
reset();
|
||||||
println!("{}{}{}", result.0, color::Fg(color::Reset), style::Reset);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for expression in expressions {
|
for expression in expressions {
|
||||||
let result = do_eval(expression, &mut context);
|
match expression.as_str() {
|
||||||
println!("{}{}{}", result.0, color::Fg(color::Reset), style::Reset);
|
"help" => help_text(),
|
||||||
|
_ => do_eval(expression, &mut context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_eval(i_expression: String, context: &mut HashMapContext) -> (String, Option<Value>) {
|
fn do_eval(i_expression: String, context: &mut HashMapContext) {
|
||||||
let expression = i_expression.as_str();
|
let expression = i_expression.as_str();
|
||||||
let i_result = eval_with_context_mut(expression, context);
|
let i_result = eval_with_context_mut(expression, context);
|
||||||
if i_result.is_err() {
|
if i_result.is_err() {
|
||||||
return (format!("{}🞪 {}{}", color::Fg(color::Red), style::Bold, expression), None);
|
println!("{}✕ {}{}", color::Fg(color::Red), style::Bold, expression);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let result = i_result.ok().unwrap();
|
let result = i_result.ok().unwrap();
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
return (format!("{}✓ {}{}", color::Fg(color::Green), style::Bold, expression), None);
|
println!("{}✓ {}{}", color::Fg(color::Green), style::Bold, expression);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let delimiter;
|
let delimiter;
|
||||||
match result {
|
match result {
|
||||||
|
@ -57,6 +99,18 @@ fn do_eval(i_expression: String, context: &mut HashMapContext) -> (String, Optio
|
||||||
Value::String(ref _str) => delimiter = "=>",
|
Value::String(ref _str) => delimiter = "=>",
|
||||||
_ => delimiter = "="
|
_ => delimiter = "="
|
||||||
}
|
}
|
||||||
return (format!("{}{}{}{} {} {}{}", style::Faint, style::Italic, expression, style::Reset, delimiter, style::Bold, result), Some(result));
|
println!("{}{}{}{} {} {}{}", style::Faint, style::Italic, expression, style::Reset, delimiter, style::Bold, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reset() {
|
||||||
|
print!("{}{}", style::Reset, color::Fg(color::Reset));
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help_text() {
|
||||||
|
println!("{}quickmaths v{}{}", style::Bold, crate::VERSION, style::Reset);
|
||||||
|
println!("Valerie Wolfe <sleeplessval@gmail.com>");
|
||||||
|
println!("A mathematical expression evaluator written in Rust.\n");
|
||||||
|
println!("USAGE:");
|
||||||
|
println!("\tqm [EXPRESSION]...\n");
|
||||||
|
}
|
||||||
|
|
18
src/util.rs
Normal file
18
src/util.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use evalexpr::{
|
||||||
|
Value,
|
||||||
|
|
||||||
|
EvalexprError
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) fn parse_radix(prefix: &str, base: u32, arg: &Value) -> Result<Value, EvalexprError> {
|
||||||
|
let i_parse = arg.as_string()?;
|
||||||
|
let parse = i_parse.strip_prefix(prefix).unwrap_or(i_parse.as_str());
|
||||||
|
|
||||||
|
let i_result = i64::from_str_radix(parse, base);
|
||||||
|
if i_result.is_err() {
|
||||||
|
return Err(EvalexprError::CustomMessage("failed to parse integer from string".to_string()));
|
||||||
|
}
|
||||||
|
let result = i_result.ok();
|
||||||
|
|
||||||
|
return Ok(result.unwrap().into());
|
||||||
|
}
|
Loading…
Reference in a new issue