Compare commits

...

26 commits
v0.2.2 ... main

Author SHA1 Message Date
e6c3df373f updated README 2024-08-29 19:17:52 -04:00
400f535469 removed '$' last variable as it doesn't work 2024-08-29 14:20:54 -04:00
ded7ed55b0 fixed logarithm wrong-length message being incorrect 2024-08-29 14:07:36 -04:00
21b373af0e Merge branch 'refactor' 2024-08-29 14:00:49 -04:00
17165ff634 added rustyline 2024-08-29 13:54:53 -04:00
8c1610bcf9 refactored other helper methods 2024-08-29 13:52:19 -04:00
6a74756835 implemented trig functions and refactored math helpers 2024-08-29 13:39:46 -04:00
27abd81bf8 made non-terminal default to quiet 2024-08-28 19:19:58 -04:00
ca75ff1260 version bump 2024-08-28 13:46:32 -04:00
ed0e572f14 major main method cleanup and implemented quiet flag 2024-08-28 13:46:11 -04:00
2bae1475b6 gave context items module structure, moved context build to a new method, and implemented empty and set flags 2024-08-28 10:40:03 -04:00
6a526ba8a5 refactored help flag and initial implementations of 'empty' and 'version' flags 2024-08-22 13:19:33 -04:00
e5d358477f changed arg parse to use pico-args 2024-08-22 13:00:20 -04:00
c09d6c2854 fixed bad helper thing by un-fixing the thing i fixed before and i don't understand what changed 2024-07-12 19:22:37 -04:00
d144bd9245 added last result variable '$' 2024-07-11 09:54:24 -04:00
cf67bf73eb added auditing tools 2024-07-09 14:21:43 -04:00
ce65fefcba fixed incorrect ranges 2024-07-09 14:18:23 -04:00
f9e8696d72 fixed version display not using cargo version 2024-07-09 14:08:13 -04:00
57f973da4c added license and metadata 2024-03-04 20:12:46 -05:00
fbd2802f08 changed project name to reflect cargo package 2024-03-04 20:10:38 -05:00
573806e8e1 Merge remote-tracking branch 'refs/remotes/origin/main' 2024-03-04 20:03:57 -05:00
565c872406 changed project name to 'quickmath' 2024-03-04 20:03:32 -05:00
6b36e8340d added speed of light global 2024-02-28 20:59:52 -05:00
f7d6ee9d7e minor cleanup 2024-02-16 09:05:00 -05:00
351d845992 improved code readability 2023-08-20 13:27:14 -04:00
8abb3c778e added library descriptions to README 2023-06-20 12:34:49 -04:00
12 changed files with 580 additions and 238 deletions

View file

@ -1,12 +1,21 @@
[package]
name = "quickmaths"
version = "0.2.2"
name = "quickmath"
version = "0.3.1"
edition = "2021"
authors = [ "Valerie Wolfe <sleeplessval@gmail.com>" ]
description = "A quick command-line math evaluator."
homepage = "https://git.vwolfe.io/valerie/qm"
repository = "https://git.vwolfe.io/valerie/qm"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "qm"
path = "src/main.rs"
[dependencies]
evalexpr = "11.0.0"
pico-args = "0.5.0"
rustyline = "14.0.0"
termion = "1.5.6"
[profile.release]
@ -15,5 +24,5 @@ codegen-units = 1
debug = false
lto = true
panic = "abort"
strip = "debuginfo"
strip = "symbols"

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2024 Valerie Wolfe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,5 +1,5 @@
# quickmaths
# quickmath (qm)
A small, pretty command line calculator.
@ -9,8 +9,34 @@ data science, and computer science.
More information is available on the [project wiki](https://git.vwolfe.io/valerie/qm/wiki).
## Installation
### Manual Install
<details>
<summary>Release Binary</summary>
Copy the compiled binary from the <a href="https://git.vwolfe.io/valerie/qm/releases">releases page</a>
to a directory in <code>$PATH</code>, such as <code>/usr/bin/</code>.
</details>
<details>
<summary>Compile from Source</summary>
Compile using cargo with the command <code>cargo build --release</code> and copy
the file from <code>target/release/</code> to a directory in <code>$PATH</code>,
such as <code>/usr/bin/</code>.
</details>
### Package Managers
<details>
<summary>Cargo: <code>quickmath</code></summary>
Install the package using Cargo with the command <code>cargo install quickmath</code>.
</details>
## Libraries
- [evalexpr](https://crates.io/crates/evalexpr)
- [termion](https://crates.io/crates/termion)
- [evalexpr](https://crates.io/crates/evalexpr) — expression evaluator
- [pico-args](https://crates.io/crates/pico_args) — argument parsing
- [rustyline](https://crates.io/crates/rustyline) — input handler
- [termion](https://crates.io/crates/termion) — ANSI formatting

4
deny.toml Normal file
View file

@ -0,0 +1,4 @@
[licenses]
allow = [ "MIT" ]

192
sbom.xml Normal file
View file

@ -0,0 +1,192 @@
{
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"created": "2024-07-09T18:21:25.518Z",
"creators": [
"Tool: cargo-sbom-v0.8.4"
]
},
"dataLicense": "CC0-1.0",
"documentNamespace": "https://spdx.org/spdxdocs/qm-0541a043-2174-483b-9558-35c5390ec42e",
"files": [
{
"SPDXID": "SPDXRef-File-qm",
"checksums": [],
"fileName": "qm",
"fileTypes": [
"BINARY"
]
}
],
"name": "qm",
"packages": [
{
"SPDXID": "SPDXRef-Package-redox_termios-0.1.3",
"description": "A Rust library to access Redox termios functions",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/redox_termios@0.1.3",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "redox_termios",
"versionInfo": "0.1.3"
},
{
"SPDXID": "SPDXRef-Package-bitflags-1.3.2",
"description": "A macro to generate structures which behave like bitflags.\n",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/bitflags@1.3.2",
"referenceType": "purl"
}
],
"homepage": "https://github.com/bitflags/bitflags",
"licenseConcluded": "MIT OR Apache-2.0",
"licenseDeclared": "MIT/Apache-2.0",
"name": "bitflags",
"versionInfo": "1.3.2"
},
{
"SPDXID": "SPDXRef-Package-libc-0.2.155",
"description": "Raw FFI bindings to platform libraries like libc.\n",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/libc@0.2.155",
"referenceType": "purl"
}
],
"homepage": "https://github.com/rust-lang/libc",
"licenseConcluded": "MIT OR Apache-2.0",
"licenseDeclared": "MIT OR Apache-2.0",
"name": "libc",
"versionInfo": "0.2.155"
},
{
"SPDXID": "SPDXRef-Package-numtoa-0.1.0",
"description": "Convert numbers into stack-allocated byte arrays",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/numtoa@0.1.0",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT OR Apache-2.0",
"licenseDeclared": "MIT OR Apache-2.0",
"name": "numtoa",
"versionInfo": "0.1.0"
},
{
"SPDXID": "SPDXRef-Package-evalexpr-11.3.0",
"description": "A powerful arithmetic and boolean expression evaluator",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/evalexpr@11.3.0",
"referenceType": "purl"
}
],
"homepage": "https://github.com/ISibboI/evalexpr",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "evalexpr",
"versionInfo": "11.3.0"
},
{
"SPDXID": "SPDXRef-Package-quickmath-0.2.3",
"description": "A quick command-line math evaluator.",
"downloadLocation": "NONE",
"homepage": "https://git.vwolfe.io/valerie/qm",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "quickmath",
"versionInfo": "0.2.3"
},
{
"SPDXID": "SPDXRef-Package-redox_syscall-0.2.16",
"description": "A Rust library to access raw Redox system calls",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/redox_syscall@0.2.16",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "redox_syscall",
"versionInfo": "0.2.16"
},
{
"SPDXID": "SPDXRef-Package-termion-1.5.6",
"description": "A bindless library for manipulating terminals.",
"downloadLocation": "registry+https://github.com/rust-lang/crates.io-index",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:cargo/termion@1.5.6",
"referenceType": "purl"
}
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "termion",
"versionInfo": "1.5.6"
}
],
"relationships": [
{
"relatedSpdxElement": "SPDXRef-Package-evalexpr-11.3.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-quickmath-0.2.3"
},
{
"relatedSpdxElement": "SPDXRef-Package-redox_syscall-0.2.16",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-1.5.6"
},
{
"relatedSpdxElement": "SPDXRef-Package-numtoa-0.1.0",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-1.5.6"
},
{
"relatedSpdxElement": "SPDXRef-Package-redox_termios-0.1.3",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-1.5.6"
},
{
"relatedSpdxElement": "SPDXRef-Package-bitflags-1.3.2",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-redox_syscall-0.2.16"
},
{
"relatedSpdxElement": "SPDXRef-Package-quickmath-0.2.3",
"relationshipType": "GENERATED_FROM",
"spdxElementId": "SPDXRef-File-qm"
},
{
"relatedSpdxElement": "SPDXRef-Package-termion-1.5.6",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-quickmath-0.2.3"
},
{
"relatedSpdxElement": "SPDXRef-Package-libc-0.2.155",
"relationshipType": "DEPENDS_ON",
"spdxElementId": "SPDXRef-Package-termion-1.5.6"
}
],
"spdxVersion": "SPDX-2.3"
}

View file

@ -1,6 +1,7 @@
pub const EULER: f64 = 2.718281828459045;
pub const GOLDEN_RATIO: f64 = 1.618033988749895;
pub const LIGHT_SPEED: f64 = 299792458f64;
pub const PI: f64 = 3.141592653589793;
pub const ROOT_TWO: f64 = 1.414213562373095;

140
src/context/helper.rs Normal file
View file

@ -0,0 +1,140 @@
use evalexpr::{
Value,
EvalexprError
};
use crate::util;
pub type EvalResult = Result<Value, EvalexprError>;
// Mathematics
pub fn cosine(arg: &Value) -> EvalResult {
Ok(
if let Value::Float(float) = arg { float.clone() }
else if let Value::Int(int) = arg { int.clone() as f64 }
else { return Err(EvalexprError::expected_number(arg.clone())) }
.cos().into()
)
}
pub fn fix(arg: &Value) -> EvalResult {
if let Value::Tuple(args) = arg {
let len = args.len();
if len == 2 {
let value =
if let Value::Float(float) = args[0] { float.clone() }
else { return Err(EvalexprError::expected_float(args[0].clone())); };
let operand = 10u64.pow(
if let Value::Int(int) = args[1] { int.clone() }
else { return Err(EvalexprError::expected_int(args[1].clone())); }
as u32
) as f64;
Ok( ((value * operand).round() / operand).into() )
} else { Err(EvalexprError::wrong_function_argument_amount(len, 2)) }
} else { Err(EvalexprError::wrong_function_argument_amount(1, 2)) }
}
pub fn logarithm(arg: &Value) -> EvalResult {
let value: f64;
let base: Option<f64>;
match arg {
Value::Tuple(tuple)
=> {
let len = tuple.len();
if len != 2 { return Err(EvalexprError::wrong_function_argument_amount_range(len, 1..=2)); }
let i_value = tuple.get(0).unwrap();
if let Value::Float(float) = i_value { value = float.clone(); }
else if let Value::Int(int) = i_value { value = int.clone() as f64; }
else { return Err(EvalexprError::expected_number(i_value.clone())); }
let i_base = tuple.get(1).unwrap();
if let Value::Float(float) = i_value { base = Some(float.clone()); }
else if let Value::Int(int) = i_value { base = Some(int.clone() as f64); }
else { return Err(EvalexprError::expected_number(i_base.clone())); }
},
Value::Float(float)
=> {
value = float.clone();
base = None;
},
Value::Int(int)
=> {
value = int.clone() as f64;
base = None;
}
_ => return Err(EvalexprError::CustomMessage("Expected numbers.".to_owned()))
}
if let Some(base) = base { Ok(value.log(base).into()) }
else { Ok(value.ln().into()) }
}
pub fn sine(arg: &Value) -> EvalResult {
Ok(
if let Value::Float(float) = arg { float.clone() }
else if let Value::Int(int) = arg { int.clone() as f64 }
else { return Err(EvalexprError::expected_number(arg.clone())) }
.sin().into()
)
}
pub fn square_root(arg: &Value) -> EvalResult {
Ok(
if let Value::Float(float) = arg { float.clone() }
else if let Value::Int(int) = arg { int.clone() as f64 }
else { return Err(EvalexprError::expected_number(arg.clone())) }
.sqrt().into()
)
}
pub fn tangent(arg: &Value) -> EvalResult {
Ok(
if let Value::Float(float) = arg { float.clone() }
else if let Value::Int(int) = arg { int.clone() as f64 }
else { return Err(EvalexprError::expected_number(arg.clone())) }
.tan().into()
)
}
// Data Science
pub fn average(arg: &Value) -> EvalResult {
if let Value::Tuple(args) = arg {
let len = args.len() as f64;
let mut total = 0f64;
for arg in args {
total +=
if let Value::Float(float) = arg { float.clone() }
else if let Value::Int(int) = arg { int.clone() as f64 }
else { todo!() };
}
Ok( (total / len).into() )
} else { todo!() }
}
// Radix conversion
pub fn binary(arg: &Value) -> EvalResult {
if let Value::Int(int) = arg { Ok( format!("0b{:b}", int).into() ) }
else if let Value::String(string) = arg { util::parse_radix("0b", 2, string) }
else { Err(EvalexprError::expected_number_or_string(arg.clone())) }
}
pub fn hexadecimal(arg: &Value) -> EvalResult {
if let Value::Int(int) = arg { Ok( format!("0x{:X}", int).into() ) }
else if let Value::String(string) = arg { util::parse_radix("0x", 16, string) }
else { Err(EvalexprError::expected_number_or_string(arg.clone())) }
}
pub fn octal(arg: &Value) -> EvalResult {
if let Value::Int(int) = arg { Ok( format!("0o{:#o}", int).into() ) }
else if let Value::String(string) = arg { util::parse_radix("0o", 8, string) }
else { Err(EvalexprError::expected_number_or_string(arg.clone())) }
}

68
src/context/mod.rs Normal file
View file

@ -0,0 +1,68 @@
pub mod global;
pub mod helper;
use pico_args::Arguments;
use evalexpr::{
context_map,
ContextWithMutableVariables,
HashMapContext,
Value
};
use crate::flag;
pub fn build(args: &mut Arguments) -> HashMapContext {
let mut output =
if !args.contains(flag::EMPTY_CONTEXT) {
context_map! {
// globals
"c" => global::LIGHT_SPEED,
"e" => global::EULER,
"phi" => global::GOLDEN_RATIO,
"pi" => global::PI,
"√2" => global::ROOT_TWO,
// math functions
"cos" => Function::new(|arg| helper::cosine(arg)),
"fix" => Function::new(|arg| helper::fix(arg)),
"log" => Function::new(|arg| helper::logarithm(arg)),
"sin" => Function::new(|arg| helper::sine(arg)),
"sqrt" => Function::new(|arg| helper::square_root(arg)),
"tan" => Function::new(|arg| helper::tangent(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()
} else { HashMapContext::new() };
while let Ok(value) = args.value_from_str::<&str, String>(flag::SET) {
let split: Vec<&str> = value.split('=').collect();
if split.len() == 2 {
let key = split[0].to_owned();
let value_str = split[1];
let value =
if let Ok(integer) = value_str.parse::<i64>() { Value::Int(integer) }
else if let Ok(float) = value_str.parse::<f64>() { Value::Float(float) }
else { Value::from(value_str) };
output.set_value(key, value).ok(); }
else { std::process::exit(1); }
}
output
}

7
src/flag.rs Normal file
View file

@ -0,0 +1,7 @@
pub const EMPTY_CONTEXT: [&str;2] = [ "-E", "--empty"];
pub const HELP: [&str;2] = [ "-h", "--help" ];
pub const QUIET: [&str;2] = [ "-q", "--quiet" ];
pub const SET: &str = "--set";
pub const VERSION: [&str;2] = [ "-V", "--version" ];

View file

@ -1,143 +0,0 @@
use evalexpr::{
Value,
EvalexprError
};
use crate::util;
// Mathematics
pub fn fix(arg: &Value) -> Result<Value, EvalexprError> {
let args = arg.as_tuple()?;
let count = args.len();
if count != 2 {
return Err(EvalexprError::WrongFunctionArgumentAmount { expected: 2, actual: count });
}
let float = args[0].as_float()?;
let figures = args[1].as_int()?;
let operand: f64 = i64::pow(10, figures as u32) as f64;
let output = f64::round(float * operand) / operand;
return Ok(output.into());
}
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)
}

View file

@ -4,103 +4,123 @@ use std::{
stdin,
stdout,
IsTerminal,
Read,
Write
}
},
process::exit
};
use evalexpr::{
context_map,
eval_with_context_mut,
ContextWithMutableVariables,
EvalexprError,
HashMapContext,
Value
};
use pico_args::Arguments;
use rustyline::DefaultEditor;
use termion::{
color,
style
};
mod global;
mod helper;
mod context;
mod flag;
mod util;
pub const VERSION: &str = "0.2.0";
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
fn main() {
let mut context = context_map! {
// globals
"e" => global::EULER,
"phi" => global::GOLDEN_RATIO,
"pi" => global::PI,
"√2" => global::ROOT_TWO,
let mut args = Arguments::from_env();
// math functions
"fix" => Function::new(|arg| helper::fix(arg)),
"log" => Function::new(|arg| helper::logarithm(arg)),
"sqrt" => Function::new(|arg| helper::square_root(arg)),
// handle breaking flags
if args.contains(flag::HELP) {
help_text();
return;
}
if args.contains(flag::VERSION) {
version_text();
return;
}
// data science functions
"avg" => Function::new(|arg| helper::average(arg)),
let mut context = context::build(&mut args);
let is_terminal = stdin().is_terminal() && stdout().is_terminal();
let quiet = args.contains(flag::QUIET) || !is_terminal;
// 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();
if expressions.len() == 0 {
println!("{}quickmaths v{}{}\n{}Interactive Mode{}", style::Bold, VERSION, style::Reset, style::Faint, style::Reset);
loop {
print!("> ");
stdout().flush().unwrap();
let mut i_line = String::new();
let line_result = stdin().read_line(&mut i_line);
if line_result.is_err() {
break;
}
let line = i_line.trim().to_string();
match line.as_str() {
"" |
"exit" => break,
_ => do_eval(line, &mut context)
}
reset();
// collect args and evaluate if present
let expressions = args.finish();
if expressions.len() > 0 {
for expression in expressions {
let expression: String = expression.to_string_lossy().into();
eval(&expression, &mut context, quiet);
}
} else {
for expression in expressions {
match expression.as_str() {
"help" => help_text(),
_ => do_eval(expression, &mut context)
if !is_terminal {
let mut buffer = String::with_capacity(0);
if let Ok(_) = stdin().read_to_string(&mut buffer) {
for expression in buffer.lines() {
eval(&expression.to_string(), &mut context, quiet);
}
}
} else if let Ok(mut rl) = DefaultEditor::new() {
// enter interactive mode if no args are given
version_text();
println!("{}Interactive Mode{}", style::Faint, style::Reset);
loop {
if let Ok(line) = rl.readline("> ") {
match line.as_str() {
"" |
"exit" => break,
_ => eval(&line, &mut context, quiet)
}
}
reset();
}
}
}
}
fn do_eval(i_expression: String, context: &mut HashMapContext) {
let expression = i_expression.as_str();
let i_result = eval_with_context_mut(expression, context);
if i_result.is_err() {
println!("{}{}{}", color::Fg(color::Red), style::Bold, expression);
return;
fn eval(expression: &str, context: &mut HashMapContext, quiet: bool) {
let result = eval_with_context_mut(expression, context);
if quiet {
if let Ok(result) = result { println!("{result}") }
else { exit(1) }
} else { format(expression, result) }
}
fn format(expression: &str, result: Result<Value, EvalexprError>) {
if let Ok(result) = result {
if !result.is_empty() {
let delimiter =
match result {
Value::Boolean(_) => "is",
Value::String(_) => "=>",
_ => "="
};
println!(
"{faint}{italic}{expression}{reset} {delimiter} {bold}{result}",
bold = style::Bold,
faint = style::Faint,
italic = style::Italic,
reset = style::Reset
);
} else {
println!(
"{green}✓ {bold}{expression}",
bold = style::Bold,
green = color::Fg(color::Green)
);
}
} else {
println!(
"{red}✕ {bold}{expression}",
bold = style::Bold,
red = color::Fg(color::Red)
);
}
let result = i_result.ok().unwrap();
if result.is_empty() {
println!("{}{}{}", color::Fg(color::Green), style::Bold, expression);
return;
}
let delimiter;
match result {
Value::Boolean(_bool) => delimiter = "is",
Value::String(ref _str) => delimiter = "=>",
_ => delimiter = "="
}
println!("{}{}{}{} {} {}{}", style::Faint, style::Italic, expression, style::Reset, delimiter, style::Bold, result);
}
fn reset() {
@ -108,10 +128,17 @@ fn 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");
fn version_text() {
println!("qm v{VERSION}");
}
fn help_text() {
version_text();
println!(
"Valerie Wolfe <sleeplessval@gmail.com>
A mathematical expression evaluator written in Rust.
usage:
qm [EXPRESSION]..."
);
}

View file

@ -1,18 +1,10 @@
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());
use evalexpr::{ EvalexprError, Value };
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();
pub fn parse_radix(prefix: &str, base: u32, arg: &str) -> Result<Value, EvalexprError> {
let parse = arg.strip_prefix(prefix).unwrap_or(arg);
return Ok(result.unwrap().into());
if let Ok(int) = i64::from_str_radix(parse, base) { Ok(int.into()) }
else { Err(EvalexprError::CustomMessage("failed to parse integer from string".to_string())) }
}