Compare commits

..

No commits in common. "main" and "v0.2.2" have entirely different histories.
main ... v0.2.2

12 changed files with 238 additions and 580 deletions

View file

@ -1,21 +1,12 @@
[package] [package]
name = "quickmath" name = "quickmaths"
version = "0.3.1" version = "0.2.2"
edition = "2021" 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"
[[bin]] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
name = "qm"
path = "src/main.rs"
[dependencies] [dependencies]
evalexpr = "11.0.0" evalexpr = "11.0.0"
pico-args = "0.5.0"
rustyline = "14.0.0"
termion = "1.5.6" termion = "1.5.6"
[profile.release] [profile.release]
@ -24,5 +15,5 @@ codegen-units = 1
debug = false debug = false
lto = true lto = true
panic = "abort" panic = "abort"
strip = "symbols" strip = "debuginfo"

19
LICENSE
View file

@ -1,19 +0,0 @@
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 @@
# quickmath (qm) # quickmaths
A small, pretty command line calculator. A small, pretty command line calculator.
@ -9,34 +9,8 @@ data science, and computer science.
More information is available on the [project wiki](https://git.vwolfe.io/valerie/qm/wiki). 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 ## Libraries
- [evalexpr](https://crates.io/crates/evalexpr) — expression evaluator - [evalexpr](https://crates.io/crates/evalexpr)
- [pico-args](https://crates.io/crates/pico_args) — argument parsing - [termion](https://crates.io/crates/termion)
- [rustyline](https://crates.io/crates/rustyline) — input handler
- [termion](https://crates.io/crates/termion) — ANSI formatting

View file

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

192
sbom.xml
View file

@ -1,192 +0,0 @@
{
"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,140 +0,0 @@
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())) }
}

View file

@ -1,68 +0,0 @@
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
}

View file

@ -1,7 +0,0 @@
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,7 +1,6 @@
pub const EULER: f64 = 2.718281828459045; pub const EULER: f64 = 2.718281828459045;
pub const GOLDEN_RATIO: f64 = 1.618033988749895; pub const GOLDEN_RATIO: f64 = 1.618033988749895;
pub const LIGHT_SPEED: f64 = 299792458f64;
pub const PI: f64 = 3.141592653589793; pub const PI: f64 = 3.141592653589793;
pub const ROOT_TWO: f64 = 1.414213562373095; pub const ROOT_TWO: f64 = 1.414213562373095;

143
src/helper.rs Normal file
View file

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

View file

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