Created KalkNum and moved output formatting from kalk_cli to kalk

This commit is contained in:
PaddiM8 2020-12-13 01:27:55 +01:00
parent 402c3e327c
commit 0284475b2c
8 changed files with 126 additions and 63 deletions

5
Cargo.lock generated
View File

@ -153,7 +153,7 @@ dependencies = [
[[package]]
name = "kalk"
version = "0.2.3"
version = "1.2.3"
dependencies = [
"lazy_static",
"regex",
@ -163,13 +163,12 @@ dependencies = [
[[package]]
name = "kalk_cli"
version = "0.3.4"
version = "0.3.5"
dependencies = [
"ansi_term",
"kalk",
"lazy_static",
"regex",
"rug",
"rustyline",
"winres",
]

View File

@ -1,6 +1,6 @@
[package]
name = "kalk"
version = "0.2.3"
version = "1.2.3"
authors = ["PaddiM8"]
edition = "2018"
readme = "README.md"

View File

@ -1,4 +1,5 @@
use crate::ast::{Expr, Stmt};
use crate::kalk_num::KalkNum;
use crate::lexer::TokenKind;
use crate::parser::CalcError;
use crate::parser::DECL_UNIT;
@ -22,10 +23,7 @@ impl<'a> Context<'a> {
}
}
pub fn interpret(
&mut self,
statements: Vec<Stmt>,
) -> Result<Option<(Float, String)>, CalcError> {
pub fn interpret(&mut self, statements: Vec<Stmt>) -> Result<Option<KalkNum>, CalcError> {
for (i, stmt) in statements.iter().enumerate() {
let (value, unit) = eval_stmt(self, stmt)?;
@ -47,7 +45,7 @@ impl<'a> Context<'a> {
if i == statements.len() - 1 {
if let Stmt::Expr(_) = stmt {
return Ok(Some((value, unit)));
return Ok(Some(KalkNum::new(value, &unit)));
}
}
}
@ -371,7 +369,7 @@ mod tests {
);
}
fn interpret_with_unit(stmt: Stmt) -> Result<Option<(Float, String)>, CalcError> {
fn interpret_with_unit(stmt: Stmt) -> Result<Option<KalkNum>, CalcError> {
let mut symbol_table = SymbolTable::new();
symbol_table
.insert(DEG_RAD_UNIT.clone())
@ -381,15 +379,15 @@ mod tests {
context.interpret(vec![stmt])
}
fn interpret(stmt: Stmt) -> Result<Option<Float>, CalcError> {
if let Some((result, _)) = interpret_with_unit(stmt)? {
fn interpret(stmt: Stmt) -> Result<Option<KalkNum>, CalcError> {
if let Some(result) = interpret_with_unit(stmt)? {
Ok(Some(result))
} else {
Ok(None)
}
}
fn cmp(x: Float, y: f64) -> bool {
fn cmp(x: KalkNum, y: f64) -> bool {
println!("{} = {}", x.to_f64(), y);
(x.to_f64() - y).abs() < 0.0001
}
@ -463,12 +461,11 @@ mod tests {
rad_context
.interpret(vec![implicit.clone()])
.unwrap()
.unwrap()
.0,
.unwrap(),
0.84147098
));
assert!(cmp(
deg_context.interpret(vec![implicit]).unwrap().unwrap().0,
deg_context.interpret(vec![implicit]).unwrap().unwrap(),
0.01745240
));
}
@ -482,7 +479,7 @@ mod tests {
symbol_table.insert(var_decl("x", literal("1")));
let mut context = Context::new(&mut symbol_table, "rad", PRECISION);
assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap().0, 1);
assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 1);
}
#[test]
@ -519,7 +516,7 @@ mod tests {
));
let mut context = Context::new(&mut symbol_table, "rad", PRECISION);
assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap().0, 3);
assert_eq!(context.interpret(vec![stmt]).unwrap().unwrap(), 3);
}
#[test]

100
kalk/src/kalk_num.rs Normal file
View File

@ -0,0 +1,100 @@
use rug::Float;
pub struct KalkNum {
value: Float,
unit: String,
}
pub struct ScientificNotation {
pub negative: bool,
pub digits: String,
pub exponent: i32,
}
impl ScientificNotation {
pub fn to_string(&self) -> String {
let sign = if self.negative { "-" } else { "" };
let mut digits_and_mul = if self.digits == "1" {
String::new()
} else {
format!("{}*", &self.digits)
};
if self.digits.len() > 1 {
digits_and_mul.insert(1usize, '.');
}
format!("{}{}10^{}", sign, digits_and_mul, self.exponent.to_string())
}
}
impl KalkNum {
pub fn new(value: Float, unit: &str) -> Self {
Self {
value,
unit: unit.to_string(),
}
}
pub fn to_f64(&self) -> f64 {
self.value.to_f64_round(rug::float::Round::Nearest)
}
pub fn to_string(&self) -> String {
let as_str = self.to_f64().to_string();
if as_str.contains(".") {
as_str
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
} else {
as_str
}
}
pub fn is_too_big(&self) -> bool {
self.value.is_infinite()
}
pub fn to_string_with_unit(&self) -> String {
format!("{} {}", self.to_string(), self.unit)
}
pub fn get_unit(self) -> String {
self.unit
}
pub fn to_scientific_notation(&self) -> ScientificNotation {
let (neg, digits, exp_option) =
self.value
.to_sign_string_exp_round(10, None, rug::float::Round::Up);
ScientificNotation {
negative: neg,
digits: digits
.trim_end_matches('0')
.trim_end_matches('.')
.to_string(),
exponent: if let Some(exp) = exp_option { exp } else { 0 },
}
}
}
impl Into<String> for ScientificNotation {
fn into(self) -> String {
self.to_string()
}
}
impl Into<f64> for KalkNum {
fn into(self) -> f64 {
self.to_f64()
}
}
impl Into<String> for KalkNum {
fn into(self) -> String {
self.to_string()
}
}

View File

@ -1,6 +1,7 @@
pub mod ast;
mod interpreter;
mod inverter;
pub mod kalk_num;
mod lexer;
pub mod parser;
mod prelude;

View File

@ -1,10 +1,10 @@
use crate::kalk_num::KalkNum;
use crate::{
ast::{Expr, Stmt},
interpreter,
lexer::{Lexer, Token, TokenKind},
symbol_table::SymbolTable,
};
use rug::Float;
pub const DECL_UNIT: &'static str = ".u";
pub const DEFAULT_ANGLE_UNIT: &'static str = "rad";
@ -83,7 +83,7 @@ pub fn eval(
context: &mut Context,
input: &str,
precision: u32,
) -> Result<Option<(Float, String)>, CalcError> {
) -> Result<Option<KalkNum>, CalcError> {
let statements = parse(context, input)?;
let mut interpreter =

View File

@ -1,6 +1,6 @@
[package]
name = "kalk_cli"
version = "0.3.4"
version = "0.3.5"
authors = ["PaddiM8"]
edition = "2018"
readme = "../README.md"
@ -16,11 +16,10 @@ path = "src/main.rs"
name = "kalk"
[dependencies]
kalk = { path = "../kalk", version = "^0.2.3" }
kalk = { path = "../kalk", version = "^1.2.3" }
rustyline = "7.0.0"
ansi_term = "0.12"
regex = "1"
rug = { version = "1.11.0", features = ["float"] }
lazy_static = "1.4.0"
[target.'cfg(windows)'.build-dependencies]

View File

@ -3,41 +3,15 @@ use kalk::parser::{self, CalcError, CalcError::*};
pub fn eval(parser: &mut parser::Context, input: &str) {
match parser::eval(parser, input, 53) {
Ok(Some((result, unit))) => {
let (_, digits, exp_option) = result.to_sign_string_exp(10, None);
let exp = if let Some(exp) = exp_option { exp } else { 0 };
if result.is_infinite() {
print_err("Too big to process.");
Ok(Some(result)) => {
let sci_notation = result.to_scientific_notation();
let result_str = if sci_notation.exponent > 8 || sci_notation.exponent < -6 {
sci_notation.to_string()
} else {
let use_sci_notation = exp > 8 || exp < -6;
result.to_string()
};
if !use_sci_notation {
println!("{}", round_value(result));
return;
}
let comma_pos = if use_sci_notation { 1 } else { exp as usize };
let sign = if result >= 0 { "" } else { "-" };
let num = if exp <= 0 {
// 0 < x < 1
format!("0.{}{}", "0".repeat(exp.abs() as usize), digits)
.trim_end_matches('0')
.to_string()
} else {
// Insert the comma if there are supposed to be decimals.
let mut chars: Vec<char> = digits
.trim_end_matches('0')
.trim_end_matches('.')
.chars()
.collect();
chars.insert(comma_pos, '.');
chars.into_iter().collect::<String>()
};
println!("{}{}*10^{} {}", sign, num, exp - 1, unit);
}
println!("{} {}", result_str, result.get_unit());
}
Ok(None) => print!(""),
Err(err) => print_calc_err(err),
@ -70,10 +44,3 @@ fn print_calc_err(err: CalcError) {
Unknown => format!("Unknown error."),
});
}
fn round_value(value: rug::Float) -> String {
format!("{:.10}", value.to_f64_round(rug::float::Round::Down))
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
}