diff --git a/Cargo.lock b/Cargo.lock index 2cbc20d..62b4b82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - [[package]] name = "bitflags" version = "1.3.2" @@ -28,24 +22,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "daisycalc" -version = "1.0.1" +version = "1.1.0" dependencies = [ "cfg-if", - "rug", + "num", "termion", "toml", ] -[[package]] -name = "gmp-mpfr-sys" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b560063e2ffa8ce9c2ef9bf487f2944a97deca5b8de0b5bcd0ae6437ef8b75f" -dependencies = [ - "libc", - "windows-sys", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -64,9 +48,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.140" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "memchr" @@ -74,6 +58,82 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + [[package]] name = "numtoa" version = "0.1.0" @@ -98,17 +158,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "rug" -version = "1.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555e8b44763d034526db899c88cd56ccc4486cd38b444c8aa0e79d4e70ae5a34" -dependencies = [ - "az", - "gmp-mpfr-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.164" @@ -170,63 +219,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -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.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "winnow" version = "0.4.6" diff --git a/Cargo.toml b/Cargo.toml index d611062..327e09e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daisycalc" -version = "1.0.1" +version = "1.1.0" edition = "2021" build = "buildscript/main.rs" license = "GPL-3.0-only" @@ -28,7 +28,8 @@ cfg-if = "1.0.0" [target.'cfg(target_family = "unix")'.dependencies] termion = "2.0.1" -rug = "1.19.2" +num = "0.4.1" +#astro-float = "0.7.1" [build-dependencies] toml = "0.7.4" \ No newline at end of file diff --git a/TODO.md b/TODO.md index 470cf3d..8a7ebba 100644 --- a/TODO.md +++ b/TODO.md @@ -1,46 +1,40 @@ ## Version Bump checklist + - TODO: build and publish script - update Cargo.toml - run cargo test - commit - - git tag -a v1.0.0 -m "Version 1.0.0" - - git push - - git push origin v1.0.0 + - push + - merge + - git tag -a v1.0.0 -m "Version 1.0.0" on merge commit - cargo publish - Update packages - ## Pre-release - - Fix linelocation (consistent, what does an operator's linelocation mean?) - Tuple operations - we don't need vectors as arguments to operators - - Assignment tests + - Fix linelocation when evaluating functions ## Parser - - Better error when `sin = 2` - Should functions be operators? - Binary, hex, octal numbers ## General + - Better tests (assignment, many expressions in one context) - Optional config file - Optional history file - - daisyrc file - Compile to WASM, publish a webapp - evaluate straight from command line - - Auto-push to crates.io - - Package for debian - + - Package for debian, nix ## Internals - Non-recursive treeify - Faster factorial function. Maybe use gamma instead? - Arbitrary precision float (rug doesn't offer arbitrary exponents) - - Remove rug dependency (too big, incompatible) ## Math Features - - Dice - Mean, Median, Min - Arbitrary base logarithm - Derivatives @@ -48,21 +42,16 @@ - Complex numbers - acot/acoth functions - Sums and products with functional arguments + - Add functions: gcd, inverse mod, dice ## Prompt + - Fix terminal color detection - Live syntax/output (like firefox js terminal) - - Syntax highlight input and output + - Syntax highlighting - fish-style tab completion - Numbered expressions, history recall - - Color configuration - Enable/disable unit sets (defaults?) - Consistent unit ordering - - Better linelocation - - we shouldn't need to re-print user input on evaluation errors, red arrows should adjust themselves to the prettyprinted string - - Backend-independent colorful printing - - Better colors in error texts - - Better substitution. Consistent: when ascii, when unicode? - - Command to list substitutions ## Units - long prefixes (megatonne, etc) diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..dff6d69 --- /dev/null +++ b/shell.nix @@ -0,0 +1,11 @@ +let + pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/f155f0cf4ea43c4e3c8918d2d327d44777b6cad4.tar.gz") {}; +in pkgs.mkShell { + buildInputs = with pkgs; [ + cargo + rustc + rustfmt + m4 + ]; +} + diff --git a/src/command/mod.rs b/src/command/mod.rs index 68f16bc..ebc774c 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -224,7 +224,7 @@ pub fn do_command( if args.len() != 2 { return FormattedText::new( format!( - "[c]{first}[n] [t]takes exactly two arguments.[n]\n\n", + "[c]{first}[n] [t]takes exactly one argument.[n]\n\n", ) ); } diff --git a/src/context.rs b/src/context.rs index b1fef92..3839f2f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -19,7 +19,7 @@ pub struct Config { // with prettier unicode alternatives? // // Automatically disabled if enable_unicode is off. - //pub enable_substituion: bool, + pub enable_substituion: bool, // Should we print simple powers // as unicode superscript chars? @@ -38,7 +38,7 @@ impl Config { pub fn new() -> Config { Config{ term_color_type: 2, - //enable_substituion: true, + enable_substituion: true, //enable_unicode: true, enable_super_powers: true, enable_one_over_power: true @@ -61,7 +61,7 @@ impl Config { #[derive(Debug)] -#[derive(Clone)] +//#[derive(Clone)] pub struct Context { pub config: Config, @@ -76,12 +76,12 @@ pub struct Context { // General functions impl Context { pub fn new() -> Context { - Context{ + Context { config: Config::new(), history: Vec::new(), variables: HashMap::new(), functions: HashMap::new(), - shadow: HashMap::new(), + shadow: HashMap::new() } } @@ -126,6 +126,7 @@ impl Context { } else { panic!() } } + // Can we define a new variable with this name? pub fn valid_varible(&self, s: &str) -> bool { if { Function::from_string(s).is_some() || @@ -145,10 +146,17 @@ impl Context { } } + // Can we get a value fro mthis variable name? pub fn is_varible(&self, s: &str) -> bool { return { - self.valid_varible(s) && - (self.variables.contains_key(s) || self.shadow.contains_key(s)) + ( + s == "ans" && + self.history.len() != 0 + ) || + ( + self.valid_varible(s) && + (self.variables.contains_key(s) || self.shadow.contains_key(s)) + ) }; } diff --git a/src/entrypoint/unix/promptbuffer.rs b/src/entrypoint/unix/promptbuffer.rs index 2b711ab..619f759 100644 --- a/src/entrypoint/unix/promptbuffer.rs +++ b/src/entrypoint/unix/promptbuffer.rs @@ -1,11 +1,12 @@ use std::collections::VecDeque; use std::io::Write; use termion::raw::RawTerminal; -use termion::color; -use termion::style; +use crate::formattedtext; use crate::parser::substitute_cursor; use crate::context::Context; +const PROMPT_STR: &str = "==> "; + #[derive(Debug)] pub struct PromptBuffer { // History @@ -38,30 +39,11 @@ impl PromptBuffer { // Same as write_primpt, but pretends there is no cursor pub fn write_prompt_nocursor(&mut self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { - // Draw prettyprinted expression - let (_, s) = substitute_cursor(context, &self.get_contents(), self.buffer.chars().count()); - - write!( - stdout, "\r{}{}==>{}{} {}", - style::Bold, - color::Fg(color::Blue), - color::Fg(color::Reset), - style::Reset, - s - )?; - - // If this string is shorter, clear the remaining old one. - if s.chars().count() < self.last_print_len { - write!( - stdout, "{}{}", - " ".repeat(self.last_print_len - s.chars().count()), - termion::cursor::Left((self.last_print_len - s.chars().count()) as u16) - )?; - } - - self.last_print_len = s.chars().count(); - stdout.flush()?; - return Ok(()); + let tmp = self.cursor; + self.cursor = 0; + let r = self.write_prompt(context, stdout); + self.cursor = tmp; + return r; } pub fn write_prompt(&mut self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { @@ -69,38 +51,30 @@ impl PromptBuffer { let i = if l == 0 {0} else {l - self.cursor}; // Draw prettyprinted expression - let (display_cursor, s) = substitute_cursor(context, &self.get_contents(), i); - + let (display_c, s) = substitute_cursor(context, &self.get_contents(), i); + write!( - stdout, "\r{}{}==>{}{} {}", - style::Bold, - color::Fg(color::Blue), - color::Fg(color::Reset), - style::Reset, + stdout, "\r{}{PROMPT_STR}{}{}", + formattedtext::format_map('p', context).unwrap(), + formattedtext::format_map('n', context).unwrap(), s )?; // If this string is shorter, clear the remaining old one. if s.chars().count() < self.last_print_len { - write!( - stdout, "{}{}", - " ".repeat(self.last_print_len - s.chars().count()), - termion::cursor::Left((self.last_print_len - s.chars().count()) as u16) - )?; - } - - - // Move cursor to correct position - if display_cursor != 0 { write!( stdout, "{}", - termion::cursor::Left(display_cursor as u16) + " ".repeat(self.last_print_len - s.chars().count()), )?; - stdout.flush()?; } - self.last_print_len = s.chars().count(); + + write!( + stdout, "\r{}", + termion::cursor::Right((display_c + PROMPT_STR.chars().count()) as u16) + )?; stdout.flush()?; + self.last_print_len = s.chars().count(); return Ok(()); } diff --git a/src/entrypoint/unix/unix.rs b/src/entrypoint/unix/unix.rs index b0a6347..104a60a 100644 --- a/src/entrypoint/unix/unix.rs +++ b/src/entrypoint/unix/unix.rs @@ -23,11 +23,11 @@ pub fn main() -> Result<(), std::io::Error> { // Set color compatibilty let term_colors = stdout.available_colors().unwrap_or(0); if term_colors >= 256 { - context.config.term_color_type = 2 + context.config.term_color_type = 2; } else if term_colors >= 8 { - context.config.term_color_type = 1 + context.config.term_color_type = 1; } else { - context.config.term_color_type = 0 + context.config.term_color_type = 0; } context.config.check(); diff --git a/src/evaluate/evaluate.rs b/src/evaluate/evaluate.rs index 4c46d95..d80109a 100644 --- a/src/evaluate/evaluate.rs +++ b/src/evaluate/evaluate.rs @@ -63,7 +63,7 @@ pub fn evaluate( context.get_variable(&s) }, - Expression::Operator(_, Operator::Function(_), _) => { Some(eval_function(g)?) }, + Expression::Operator(_, Operator::Function(_), _) => { eval_function(g)? }, Expression::Operator(_, _, _) => { eval_operator(context, g)? }, }; diff --git a/src/evaluate/function.rs b/src/evaluate/function.rs index 005523d..e24a391 100644 --- a/src/evaluate/function.rs +++ b/src/evaluate/function.rs @@ -26,7 +26,7 @@ fn to_radians(q: Quantity) -> Result { -pub fn eval_function(g: &Expression) -> Result { +pub fn eval_function(g: &Expression) -> Result, (LineLocation, DaisyError)> { let Expression::Operator(loc, Operator::Function(f), args) = g else {unreachable!()}; @@ -41,113 +41,112 @@ pub fn eval_function(g: &Expression) -> Result { return Ok(Expression::Quantity(*loc + *l, q.without_unit())); } - Function::ToBase => { return Ok(Expression::Quantity(*loc + *l, q.convert_to_base())); } + Function::NoUnit => { return Ok(Some(Expression::Quantity(*loc + *l, q.without_unit()))); } + Function::ToBase => { return Ok(Some(Expression::Quantity(*loc + *l, q.convert_to_base()))); } Function::Abs => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.abs())); + return Ok(Some(Expression::Quantity(*loc + *l, q.abs()))); }, Function::Floor => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.floor())); + return Ok(Some(Expression::Quantity(*loc + *l, q.floor()))); }, Function::Ceil => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.ceil())); + return Ok(Some(Expression::Quantity(*loc + *l, q.ceil()))); }, Function::Round => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.round())); + return Ok(Some(Expression::Quantity(*loc + *l, q.round()))); }, Function::NaturalLog => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.ln())); + return Ok(Some(Expression::Quantity(*loc + *l, q.ln()))); }, Function::TenLog => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.log10())); + return Ok(Some(Expression::Quantity(*loc + *l, q.log10()))); }, Function::Sin => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.sin())); + return Ok(Some(Expression::Quantity(*loc + *l, q.sin()))); }, Function::Cos => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.cos())); + return Ok(Some(Expression::Quantity(*loc + *l, q.cos()))); }, Function::Tan => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.tan())); + return Ok(Some(Expression::Quantity(*loc + *l, q.tan()))); }, Function::Csc => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.csc())); + return Ok(Some(Expression::Quantity(*loc + *l, q.csc()))); }, Function::Sec => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.sec())); + return Ok(Some(Expression::Quantity(*loc + *l, q.sec()))); }, Function::Cot => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.cot())); + return Ok(Some(Expression::Quantity(*loc + *l, q.cot()))); }, Function::Sinh => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.sinh())); + return Ok(Some(Expression::Quantity(*loc + *l, q.sinh()))); }, Function::Cosh => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.cosh())); + return Ok(Some(Expression::Quantity(*loc + *l, q.cosh()))); }, Function::Tanh => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.tanh())); + return Ok(Some(Expression::Quantity(*loc + *l, q.tanh()))); }, Function::Csch => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.csch())); + return Ok(Some(Expression::Quantity(*loc + *l, q.csch()))); }, Function::Sech => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.sech())); + return Ok(Some(Expression::Quantity(*loc + *l, q.sech()))); }, Function::Coth => { let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); }; - return Ok(Expression::Quantity(*loc + *l, q.coth())); + return Ok(Some(Expression::Quantity(*loc + *l, q.coth()))); }, Function::Asin => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.asin())); + return Ok(Some(Expression::Quantity(*loc + *l, q.asin()))); }, Function::Acos => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.acos())); + return Ok(Some(Expression::Quantity(*loc + *l, q.acos()))); }, Function::Atan => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.atan())); + return Ok(Some(Expression::Quantity(*loc + *l, q.atan()))); }, Function::Asinh => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.asinh())); + return Ok(Some(Expression::Quantity(*loc + *l, q.asinh()))); }, Function::Acosh => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.acosh())); + return Ok(Some(Expression::Quantity(*loc + *l, q.acosh()))); }, Function::Atanh => { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} - return Ok(Expression::Quantity(*loc + *l, q.atanh())); + return Ok(Some(Expression::Quantity(*loc + *l, q.atanh()))); }, @@ -160,7 +159,7 @@ pub fn eval_function(g: &Expression) -> Result { let mut k = Quantity::new_rational(1f64).unwrap(); @@ -172,7 +171,7 @@ pub fn eval_function(g: &Expression) -> Result { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} @@ -181,7 +180,7 @@ pub fn eval_function(g: &Expression) -> Result { if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));} @@ -191,7 +190,7 @@ pub fn eval_function(g: &Expression) -> Result Result Result Result Result Result Option { Some(match c { 'n'|'i'|'t'|'a'| - 'e'|'c'|'s'|'r' + 'e'|'c'|'s'|'r'| + 'p' => { "".to_string() }, _ => { return None } }) @@ -37,34 +38,39 @@ fn format_map_ansi(c: char) -> Option { 'i' => { // Normal italic text format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) }, - 't' => { // Title text + 't' => { // Title text (should be cyan) format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset)) }, - 'a' => { // Colored text + 'a' => { // Colored text (should be pink) format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) }, - 'e' => { // Error titles + 'e' => { // Error titles (should be red) format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset)) }, - 'c' => { // Console text + 'c' => { // Console text (inverted black on white) format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7))) }, - 's' => { // Repeat prompt (how => is styled) - format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset)) - }, - 'r' => { // Result prompt (how = is styled) + 'p' => { // Input prompt (how ==> is styled) (should be blue) format!("{}{}", color::Fg(color::AnsiValue(4)), color::Bg(color::Reset)) }, + 's' => { // Repeat prompt (how => is styled) (should be pink) + format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) + }, + 'r' => { // Result prompt (how = is styled) (should be green) + format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset)) + }, _ => { return None } }) } +// style::reset also resets color. +// Make sure color comes AFTER style reset. fn format_map_full(c: char) -> Option { Some(match c { 'n' => { // Normal text - format!("{}{}", color::Fg(color::Reset), style::Reset) + format!("{}{}", style::Reset, color::Fg(color::Reset)) }, 'i' => { // Normal italic text format!("{}{}", color::Fg(color::Reset), style::Italic) @@ -73,7 +79,7 @@ fn format_map_full(c: char) -> Option { format!("{}{}", color::Fg(color::Magenta), style::Bold) }, 'a' => { // Colored text - format!("{}{}", color::Fg(color::Magenta), style::Reset) + format!("{}{}", style::Reset, color::Fg(color::Magenta)) }, 'e' => { // Error titles format!("{}{}", color::Fg(color::Red), style::Bold) @@ -81,6 +87,9 @@ fn format_map_full(c: char) -> Option { 'c' => { // Console text format!("{}{}", color::Fg(color::LightBlack), style::Italic) }, + 'p' => { // Input prompt (how ==> is styled) + format!("{}{}", color::Fg(color::Blue), style::Bold) + }, 's' => { // Repeat prompt (how => is styled) format!("{}{}", color::Fg(color::Magenta), style::Bold) }, @@ -88,17 +97,24 @@ fn format_map_full(c: char) -> Option { format!("{}{}", color::Fg(color::Green), style::Bold) }, + _ => { return None } }) } +pub fn format_map(c: char, context: &Context) -> Option { + match context.config.term_color_type { + 0 => format_map_none(c), + 1 => format_map_ansi(c), + 2 => format_map_full(c), + _ => unreachable!("Invalid term_color_type") + } +} + impl FormattedText { pub fn newline(stdout: &mut RawTerminal) -> Result<(), std::io::Error> { - write!( - stdout, - "\r\n", - )?; + write!(stdout, "\n")?; return Ok(()); } } @@ -145,12 +161,7 @@ impl FormattedText { match (a, b) { (c, ']') => { // Normal text - let q = match context.config.term_color_type { - 0 => format_map_none(c), - 1 => format_map_ansi(c), - 2 => format_map_full(c), - _ => unreachable!("Invalid term_color_type") - }; + let q = format_map(c, context); if q.is_some() { s.push_str(&q.unwrap()); @@ -173,7 +184,7 @@ impl FormattedText { } } - write!(stdout, "{}", s)?; + write!(stdout, "\r{}", s)?; return Ok(()); } } diff --git a/src/parser/expression/expression.rs b/src/parser/expression/expression.rs index 564a162..f366e63 100644 --- a/src/parser/expression/expression.rs +++ b/src/parser/expression/expression.rs @@ -11,6 +11,23 @@ use super::super::LineLocation; #[derive(Debug)] #[derive(Clone)] pub enum Expression { + // Meaning of `LineLocation`: + // + // For Variables, Constants, Quantities, Tuples: + // If this expression was parsed, LineLocation is what part of the prompt was parsed to get this expression + // If this expression is the result of a calculation, LineLocaion is the sum of the LineLocations of + // all expressions used to make it. In other words, it points to the part of the prompt that was evaluated + // to get this new expression. + // + // For Operators: + // Linelocation points to the operator's position in the prompt. + // If this is a function, it points to the function name. + // If this is `+`, `!`, or etc, it points to that character. + // Operator arguments are NOT included in this linelocation. + // + // + // All the above rules are implemented when parsing and evaluating expressions. + Variable(LineLocation, String), Quantity(LineLocation, Quantity), Constant(LineLocation, Constant), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 32c608d..fe3a0d5 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -21,8 +21,10 @@ pub fn parse( context: &Context, s: &String ) -> Result { - let expressions = stage::tokenize(context, s); - let (_, expressions) = stage::find_subs(expressions); + let mut expressions = stage::tokenize(context, s); + if context.config.enable_substituion { + (_, expressions) = stage::find_subs(expressions); + } let g = stage::groupify(context, expressions)?; let g = stage::treeify(context, g)?; @@ -33,7 +35,14 @@ pub fn parse_no_context(s: &String) -> Result String { + if !context.config.enable_substituion { return s.clone(); } let (_, s) = substitute_cursor(context, s, s.chars().count()); return s; } @@ -43,34 +52,45 @@ pub fn substitute_cursor( s: &String, // The string to substitute c: usize // Location of the cursor right now ) -> ( - usize, // Location of cursor in substituted string + usize, // New cursor String // String with substitutions ) { + + if !context.config.enable_substituion { return (c, s.clone()); } if s == "" { return (c, s.clone()) } + + let mut new_s = s.clone(); - let l = s.chars().count(); let expressions = stage::tokenize(context, s); let (mut subs, _) = stage::find_subs(expressions); - let mut new_c = l - c; + let mut new_c = c.clone(); while subs.len() > 0 { - let r = subs.pop_back().unwrap(); // Apply substitutions in reverse order + // r is the current substitution: (linelocation, string) + let r = subs.pop_back().unwrap(); if { // Don't substitute if our cursor is inside the substitution c >= r.0.pos && c < r.0.pos+r.0.len } { continue; } - if c < r.0.pos { - let ct = r.1.chars().count(); - if ct >= r.0.len { - if new_c >= ct - r.0.len { - new_c += ct - r.0.len - } - } else { - new_c -= r.0.len - ct + // If this substitution is before our cursor, + // we need to adjust our cursor's position. + if c > r.0.pos { + let c_o = r.0.len; // Old length + let c_n = r.1.chars().count(); // New length + + if c_n > c_o { + // Move cursor right by difference + new_c += c_n - c_o; + + } else if c_n < c_o { + // Move cursor left by difference + if new_c >= c_o - c_n { + new_c -= c_o - c_n; + } else { new_c = 0; } } } diff --git a/src/parser/stage/find_subs.rs b/src/parser/stage/find_subs.rs index 058cf54..a7b09d4 100644 --- a/src/parser/stage/find_subs.rs +++ b/src/parser/stage/find_subs.rs @@ -6,11 +6,69 @@ use super::super::{ }; +fn sub_string(s: &str) -> Option<&'static str> { + let r = match s { + + /* Only found in operator tokens */ + + "*" => "×", + "/" => "÷", + "sqrt" => "√", + "rt" => "√", + + + + /* Only found in word tokens */ + + // Greek letters + "alpha" => "α", + "beta" => "β", + "gamma" => "γ", + "delta" => "δ", + "epsilon" => "ε", + "zeta" => "ζ", + "eta" => "η", + "theta" => "θ", + //"iota" => {Some("ι")}, // looks just like i + //"kappa" => {Some("κ")}, // looks just like k + "lambda" => "λ", + "mu" => "μ", + //"nu" => {Some("ν")}, // looks just like v + "xi" => "ξ", + //"omicron" => {Some("ο")}, // looks exactly like o + "pi" => "π", + "rho" => "ρ", + "sigma" => "σ", + "tau" => "τ", + //"upsilon" => {Some("υ")}, // looks just like u + "phi" => "φ", + "chi" => "χ", + //"psi" => {Some("ψ")}, Conflict with pound / square inch + "omega" => "ω", + + // Constants + "epsilon_zero" => "ε₀", + "eps_zero" => "ε₀", + "g_zero" => "g₀", + "mu_zero" => "μ₀", + "h_bar" => "ℏ", + + // Misc + "deg" => "°", + + _ => { return None; } + }; + return Some(r); +} + + +// Finds substitutions in an array of tokens. +// Returns new token array and substitution list. pub fn find_subs( mut g: VecDeque, ) -> ( - VecDeque<(LineLocation, String)>, - VecDeque + VecDeque<(LineLocation, String)>, // List of substrings to replace (in order) + VecDeque // New token array, with updated strings and linelocations ) { // Array of replacements @@ -24,62 +82,19 @@ pub fn find_subs( while g.len() > 0 { let mut t = g.pop_front().unwrap(); + let target: Option<&str> = match &mut t { Token::Operator(_, s) => { - let target = match &s[..] { - "*" => {Some("×")}, - "/" => {Some("÷")}, - "sqrt" => {Some("√")}, - "rt" => {Some("√")}, - _ => {None} - }; + let target = sub_string(s); // Update token contents too. - // This makes sure that errors also contain the updated text. + // This makes errors and printouts use the updated string. if target.is_some() { *s = String::from(target.unwrap()); } target }, Token::Word(_, s) => { - let target = match &s[..] { - // Greek letters - "alpha" => {Some("α")}, - "beta" => {Some("β")}, - "gamma" => {Some("γ")}, - "delta" => {Some("δ")}, - "epsilon" => {Some("ε")}, - "zeta" => {Some("ζ")}, - "eta" => {Some("η")}, - "theta" => {Some("θ")}, - //"iota" => {Some("ι")}, - //"kappa" => {Some("κ")}, - "lambda" => {Some("λ")}, - "mu" => {Some("μ")}, - //"nu" => {Some("ν")}, - "xi" => {Some("ξ")}, - //"omicron" => {Some("ο")}, - "pi" => {Some("π")}, - "rho" => {Some("ρ")}, - "sigma" => {Some("σ")}, - "tau" => {Some("τ")}, - //"upsilon" => {Some("υ")}, - "phi" => {Some("φ")}, - "chi" => {Some("χ")}, - //"psi" => {Some("ψ")}, Conflict with pound / square inch - "omega" => {Some("ω")}, - - // Constants - "epsilon_zero" => {Some("ε₀")}, - "eps_zero" => {Some("ε₀")}, - "g_zero" => {Some("g₀")}, - "mu_zero" => {Some("μ₀")}, - "h_bar" => {Some("ℏ")}, - - // Misc - "deg" => {Some("°")} - _ => {None} - }; - + let target = sub_string(s); if target.is_some() { *s = String::from(target.unwrap()); } target }, @@ -88,7 +103,7 @@ pub fn find_subs( }; if target.is_none() { - // Even if nothing changed, we need to update token location + // Even if nothing changed, we need to update the new token's linelocation let l = t.get_mut_linelocation(); *l = LineLocation{pos: l.pos - offset, len: l.len}; } else { diff --git a/src/quantity/scalar/f64base.rs b/src/quantity/scalar/f64base.rs index 3901920..f9f445e 100644 --- a/src/quantity/scalar/f64base.rs +++ b/src/quantity/scalar/f64base.rs @@ -8,6 +8,7 @@ use std::ops::{ use std::cmp::Ordering; use super::ScalarBase; +use super::dec_to_sci; macro_rules! foward { @@ -25,16 +26,39 @@ pub struct F64Base where { } impl ToString for F64Base { - fn to_string(&self) -> String { self.val.to_string() } + fn to_string(&self) -> String { + // Remove negative sign from string + let mut s = self.val.to_string(); + + let neg = s.starts_with("-"); + if neg { s = String::from(&s[1..]); } + + // Power of ten + let mut p: i64 = { + if let Some(x) = s.find(".") { + x as i64 + } else { + s.len() as i64 + } + }; + p -= 1; + + // We no longer need a decimal point in our string. + // also, trim off leading zeros and adjust power. + let mut s: &str = &s.replace(".", ""); + s = &s[0..]; + s = s.trim_end_matches('0'); + while s.starts_with('0') { + s = &s[1..]; + p -= 1; + } + + return dec_to_sci(neg, s.to_string(), p); + } } impl ScalarBase for F64Base { - - fn from_f64(f: f64) -> Option { - return Some(F64Base{ val: f }); - } - fn from_string(s: &str) -> Option { let v = s.parse::(); let v = match v { @@ -51,6 +75,7 @@ impl ScalarBase for F64Base { fn is_one(&self) -> bool {self.val == 1f64} fn is_negative(&self) -> bool { self.val.is_sign_negative() } fn is_positive(&self) -> bool { self.val.is_sign_positive() } + fn is_int(&self) -> bool { self.val.floor() == self.val } foward!(abs); foward!(floor); @@ -60,9 +85,11 @@ impl ScalarBase for F64Base { foward!(sin); foward!(cos); foward!(tan); - foward!(csc); - foward!(sec); - foward!(cot); + + fn csc(&self) -> Option { Some(F64Base{ val: 1f64/self.val.sin() }) } + fn sec(&self) -> Option { Some(F64Base{ val: 1f64/self.val.cos() }) } + fn cot(&self) -> Option { Some(F64Base{ val: 1f64/self.val.tan() }) } + foward!(asin); foward!(acos); foward!(atan); @@ -70,9 +97,11 @@ impl ScalarBase for F64Base { foward!(sinh); foward!(cosh); foward!(tanh); - foward!(csch); - foward!(sech); - foward!(coth); + + fn csch(&self) -> Option { Some(F64Base{ val: 1f64/self.val.sinh() }) } + fn sech(&self) -> Option { Some(F64Base{ val: 1f64/self.val.cosh() }) } + fn coth(&self) -> Option { Some(F64Base{ val: 1f64/self.val.tanh() }) } + foward!(asinh); foward!(acosh); foward!(atanh); @@ -161,11 +190,11 @@ impl Rem for F64Base { fn rem(self, modulus: F64Base) -> Self::Output { if { - (!self.fract().unwrap().is_zero()) || - (!modulus.fract().unwrap().is_zero()) + (!self.is_int()) || + (!modulus.is_int()) } { panic!() } - F64Base{val : self.val.fract() % modulus.val.fract()} + F64Base{val : self.val.round() % modulus.val.round()} } } diff --git a/src/quantity/scalar/floatbase.rs b/src/quantity/scalar/floatbase.rs index 37d0d5e..94ff120 100644 --- a/src/quantity/scalar/floatbase.rs +++ b/src/quantity/scalar/floatbase.rs @@ -1,7 +1,7 @@ -use rug::Float; -use rug::Assign; -use rug::ops::AssignRound; -use rug::ops::Pow; +use bigdecimal::BigDecimal; +use bigdecimal::Zero; +use bigdecimal::RoundingMode; +use std::str::FromStr; use std::ops::{ Add, Sub, Mul, Div, @@ -14,158 +14,151 @@ use std::ops::{ use std::cmp::Ordering; use super::ScalarBase; -use super::PRINT_LEN; -use super::FLOAT_PRECISION; +use super::dec_to_sci; + #[derive(Debug)] #[derive(Clone)] pub struct FloatBase where { - pub val: Float + pub val: BigDecimal } impl FloatBase { - pub fn from(a: T) -> Option where - Float: Assign + AssignRound - { - let v = Float::with_val(FLOAT_PRECISION, a); - return Some(FloatBase{ val: v }); + pub fn new(s: &str) -> FloatBase { + return FloatBase { + val: s.parse().unwrap() + }; } } + impl ToString for FloatBase { fn to_string(&self) -> String { - let (sign, mut string, exp) = self.val.to_sign_string_exp(10, Some(PRINT_LEN)); - // zero, nan, or inf. - let sign = if sign {"-"} else {""}; - if exp.is_none() { return format!("{sign}{string}"); } - let exp = exp.unwrap(); - - // Remove trailing zeros. - // At this point, string is guaranteed to be nonzero. - while string.chars().last().unwrap() == '0' { - string.remove(string.len() - 1); + if self.val.is_nan() { + return "NaN".to_string(); + } else if self.val.is_inf_neg() { + return "-Inf".to_string(); + } else if self.val.is_inf_pos() { + return "+Inf".to_string(); } - let exp_u: usize; - if exp < 0 { - exp_u = (-exp).try_into().unwrap() - } else { - exp_u = exp.try_into().unwrap() - } + // Already in scientific notation,we just need to trim significant digits. + let mut _a = self.val.round(32, astro_float::RoundingMode::Up).to_string(); + let mut _b = _a.split('e'); - if exp_u >= PRINT_LEN { - // Exponential notation - let pre = &string[0..1]; - let post = &string[1..]; + let mut s = String::from(_b.next().unwrap()); // Decimal + let p: i64 = _b.next().unwrap().parse().unwrap(); // Exponent - format!( - "{pre}{}{post}e{}", - if post.len() != 0 {"."} else {""}, - //if exp > 0 {"+"} else {""}, - exp - 1 - ) - } else { - if exp <= 0 { // Decimal, needs `0.` and leading zeros - format!( - "{sign}0.{}{string}", - "0".repeat(exp_u) - ) - } else if exp_u < string.len() { // Decimal, needs only `.` - format!( - "{sign}{}.{}", - &string[0..exp_u], - &string[exp_u..] - ) - } else { // Integer, needs trailing zeros - format!( - "{sign}{string}{}", - "0".repeat(exp_u - string.len()) - ) - } - } + // Remove negative sign from string + let neg = s.starts_with("-"); + if neg { s = String::from(&s[1..]); } + // We no longer need a decimal point in our string. + // also, trim off leading zeros and adjust power. + let mut s: &str = &s.replace(".", ""); + s = &s[0..]; + s = s.trim_end_matches('0'); + s = s.trim_start_matches('0'); + + return dec_to_sci(neg, s.to_string(), p); } } -macro_rules! foward { - ( $x:ident ) => { - fn $x(&self) -> Option { - Some(FloatBase{ val: self.val.clone().$x()}) - } - } -} impl ScalarBase for FloatBase { - fn from_f64(f: f64) -> Option { - let v = Float::with_val(FLOAT_PRECISION, f); - return Some(FloatBase{ val: v }); - } - fn from_string(s: &str) -> Option { - let v = Float::parse(s); + let v = BigDecimal::from_str(s); let v = match v { Ok(x) => x, Err(_) => return None }; - return Some( - FloatBase{ val: - Float::with_val(FLOAT_PRECISION, v) - } - ); + return Some(FloatBase{ val: v }); } - foward!(fract); + //foward!(fract); fn is_zero(&self) -> bool {self.val.is_zero()} - fn is_one(&self) -> bool {self.val == Float::with_val(FLOAT_PRECISION, 1)} - fn is_negative(&self) -> bool { self.val.is_sign_negative() } - fn is_positive(&self) -> bool { self.val.is_sign_positive() } + fn is_one(&self) -> bool {self.val == BigDecimal::from_str("1").unwrap()} + fn is_negative(&self) -> bool { self.val.sign() == num::bigint::Sign::Minus } + fn is_positive(&self) -> bool { self.val.sign() == num::bigint::Sign::Plus } - fn is_int(&self) -> bool { - self.fract() == FloatBase::from_f64(0f64) + fn is_int(&self) -> bool { self.val.is_integer() } + + fn abs(&self) -> Option { Some(FloatBase{ val: self.val.abs() }) } + fn round(&self) -> Option { Some(FloatBase{ val: self.val.round(0) }) } + + fn floor(&self) -> Option { + let (_, scale) = self.val.as_bigint_and_exponent(); + Some(FloatBase{ val: self.val.with_scale_round(scale, RoundingMode::Down) }) } - foward!(abs); - foward!(floor); - foward!(ceil); - foward!(round); - - foward!(sin); - foward!(cos); - foward!(tan); - foward!(csc); - foward!(sec); - foward!(cot); - foward!(asin); - foward!(acos); - foward!(atan); - - foward!(sinh); - foward!(cosh); - foward!(tanh); - foward!(csch); - foward!(sech); - foward!(coth); - foward!(asinh); - foward!(acosh); - foward!(atanh); - - foward!(exp); - foward!(ln); - foward!(log10); - foward!(log2); - - fn log(&self, base: FloatBase) -> Option { - Some(FloatBase{ val: self.val.clone().log10() } / base.log10().unwrap()) + fn ceil(&self) -> Option { + let (_, scale) = self.val.as_bigint_and_exponent(); + Some(FloatBase{ val: self.val.with_scale_round(scale, RoundingMode::Up) }) } - fn pow(&self, base: FloatBase) -> Option { - Some(FloatBase{ val: self.val.clone().pow(base.val)}) + fn fract(&self) -> Option { Some(self.clone() - self.floor().unwrap()) } + + + fn sin(&self) -> Option { + let c0: BigDecimal = "1.276278962".parse().unwrap(); + let c1: BigDecimal = "-.285261569".parse().unwrap(); + let c2: BigDecimal = "0.009118016".parse().unwrap(); + let c3: BigDecimal = "-.000136587".parse().unwrap(); + let c4: BigDecimal = "0.000001185".parse().unwrap(); + let c5: BigDecimal = "-.000000007".parse().unwrap(); + + + // z should be between -0.25 to 0.25 (percent of a full circle) + let z: BigDecimal = self.val.clone() / 360f64; + let w = BigDecimal::from(4) * z; + let x: BigDecimal = 2 * w.clone() * w.clone() - 1; + + let p = ( + c0 * 1 + + c1 * x.clone() + + c2 * (2 * x.clone()*x.clone() - 1) + + c3 * (4 * x.clone()*x.clone()*x.clone() - 3 * x.clone()) + + c4 * (8 * x.clone()*x.clone()*x.clone()*x.clone() - 8 * x.clone()*x.clone() + 1) + + c5 * (16 * x.clone()*x.clone()*x.clone()*x.clone()*x.clone() - 20 * x.clone()*x.clone()*x.clone() + 5 * x.clone()) + ) * w; + + return Some(FloatBase{ val: p }) + } + fn cos(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn tan(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn csc(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn sec(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn cot(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn asin(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn acos(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn atan(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn sinh(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn cosh(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn tanh(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn csch(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn sech(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn coth(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn asinh(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn acosh(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn atanh(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn exp(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn ln(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn log10(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + fn log2(&self) -> Option { Some(FloatBase{ val: "1".parse().unwrap() }) } + + + fn log(&self, _base: FloatBase) -> Option { + Some(FloatBase{ val: "1".parse().unwrap() }) + } + + fn pow(&self, _base: FloatBase) -> Option { + Some(FloatBase{ val: "1".parse().unwrap() }) } } @@ -223,7 +216,7 @@ impl Div for FloatBase { impl DivAssign for FloatBase where { fn div_assign(&mut self, other: Self) { - self.val /= other.val; + self.val = self.val.clone() / other.val; } } @@ -240,11 +233,11 @@ impl Rem for FloatBase { fn rem(self, modulus: FloatBase) -> Self::Output { if { - (!self.fract().unwrap().is_zero()) || - (!modulus.fract().unwrap().is_zero()) + (!self.is_int()) || + (!modulus.is_int()) } { panic!() } - FloatBase{val : self.val.fract() % modulus.val.fract()} + FloatBase{val : self.val.round(0) % modulus.val.round(0)} } } diff --git a/src/quantity/scalar/mod.rs b/src/quantity/scalar/mod.rs index 65a4722..976998d 100644 --- a/src/quantity/scalar/mod.rs +++ b/src/quantity/scalar/mod.rs @@ -1,10 +1,92 @@ -const FLOAT_PRECISION: u32 = 1024; -const PRINT_LEN: usize = 5; // How many significant digits we will show in output +//const FLOAT_PRECISION: u32 = 1024; +const SHOW_SIG: usize = 5; // How many significant digits we will show in output +const MAX_LEN: usize = 5; // If a scientific exponent is >= this value, do not use scientific notation. pub(in self) mod rationalbase; -pub(in self) mod floatbase; -//mod f64base; + + +// Pick a float implementation. +// floatbase is high-precision, f64base is for testing. + +//pub(in self) mod floatbase; +//pub use floatbase::FloatBase; + +pub(in self) mod f64base; +pub use f64base::F64Base as FloatBase; + + mod scalar; pub use self::scalar::Scalar; -pub use self::scalar::ScalarBase; \ No newline at end of file +pub use self::scalar::ScalarBase; + + + +// Convert a string to scientific notation, +// with parameters SHOW_SIG and MAX_LEN. +// +// input: +// neg: true if negative +// s: decimal portion. Must contain only digits and a single decimal point. +// zeros must be stripped from both ends. +// p: power of ten to multiply by. +// +// So, (-1)^(neg) + (s * 10^p) should give us our number. + +#[allow(dead_code)] +pub(in self) fn dec_to_sci(neg: bool, mut s: String, p: i64) -> String { + // Pick significant digits and round + if s.len() > SHOW_SIG { + let round; + if s.len() != SHOW_SIG + 1 { + round = s[SHOW_SIG..SHOW_SIG+1].parse().unwrap(); + } else { round = 0; } + + s = String::from(&s[0..SHOW_SIG]); + + if round >= 5 { + let new = s[s.len()-1..s.len()].parse::().unwrap() + 1u8; + if new != 10 { + s = format!("{}{new}", &s[0..s.len()-1]); + } + } + } + + s = format!("{s}{}", "0".repeat(SHOW_SIG - s.len())); + // at this point, s is guaranteed to have exactly SHOW_SIG digits. + + let neg = if neg {"-"} else {""}; + + if (p.abs() as usize) < MAX_LEN { + // Print whole decimal + + if p >= 0 { + let q = p as usize; + + let first = &s[0..q+1]; + let mut rest = &s[q+1..]; + rest = rest.trim_end_matches('0'); + if rest == "" { + return format!("{neg}{first}"); + } else { + return format!("{neg}{first}.{rest}"); + } + } else { + let q = p.abs() as usize; + let t = format!("0.{}{s}", "0".repeat(q-1)); + return format!("{neg}{}", t.trim_end_matches('0')); + } + + } else { + // Print full scientific notation + + let first = &s[0..1]; + let mut rest = &s[1..]; + rest = rest.trim_end_matches('0'); + if rest == "" { + return format!("{neg}{first}e{p}"); + } else { + return format!("{neg}{first}.{rest}e{p}"); + } + } +} \ No newline at end of file diff --git a/src/quantity/scalar/rationalbase.rs b/src/quantity/scalar/rationalbase.rs index 4270b1b..eca8c97 100644 --- a/src/quantity/scalar/rationalbase.rs +++ b/src/quantity/scalar/rationalbase.rs @@ -1,5 +1,7 @@ -use rug::Rational; -use rug::Integer; +use num::rational::BigRational; +use num::BigInt; +use num::Num; +use num::Signed; use std::ops::{ Add, Sub, Mul, Div, @@ -22,7 +24,7 @@ macro_rules! cant_do { #[derive(Debug)] #[derive(Clone)] pub struct RationalBase where { - pub val: Rational + pub val: BigRational } impl ToString for RationalBase{ @@ -33,18 +35,12 @@ impl ToString for RationalBase{ impl RationalBase { pub fn from_frac(t: i64, b: i64) -> Option { - let v = Rational::from((t, b)); + let v = BigRational::new_raw(BigInt::from(t), BigInt::from(b)); return Some(RationalBase{ val: v }); } } impl ScalarBase for RationalBase { - fn from_f64(f: f64) -> Option { - let v = Rational::from_f64(f); - if v.is_none() { return None } - return Some(RationalBase{ val: v.unwrap() }); - } - fn from_string(s: &str) -> Option { // Scientific notation let mut sci = s.split("e"); @@ -89,7 +85,7 @@ impl ScalarBase for RationalBase { // From fraction string - let r = Rational::from_str_radix(&s, 10); + let r = BigRational::from_str_radix(&s, 10); let r = match r { Ok(x) => x, Err(_) => return None @@ -100,18 +96,13 @@ impl ScalarBase for RationalBase { } - fn fract(&self) -> Option { - Some(RationalBase{val: self.val.clone().fract_floor(Integer::new()).0}) - } + fn fract(&self) -> Option { Some(RationalBase{val: self.val.fract()}) } + fn is_int(&self) -> bool { self.val.is_integer() } - fn is_int(&self) -> bool { - self.fract() == RationalBase::from_f64(0f64) - } - - fn is_zero(&self) -> bool {self.val == Rational::from((0,1))} - fn is_one(&self) -> bool {self.val == Rational::from((1,1))} - fn is_negative(&self) -> bool { self.val.clone().signum() == -1 } - fn is_positive(&self) -> bool { self.val.clone().signum() == 1 } + fn is_zero(&self) -> bool {self.val == BigRational::from_integer(BigInt::from(0))} + fn is_one(&self) -> bool {self.val == BigRational::from_integer(BigInt::from(1))} + fn is_negative(&self) -> bool { self.val.is_negative() } + fn is_positive(&self) -> bool { self.val.is_positive() } fn abs(&self) -> Option {Some(RationalBase{val: self.val.clone().abs()})} fn floor(&self) -> Option {Some(RationalBase{val: self.val.clone().floor()})} @@ -153,9 +144,7 @@ impl Add for RationalBase where { type Output = Self; fn add(self, other: Self) -> Self::Output { - Self { - val: self.val + other.val - } + Self { val: self.val + other.val } } } @@ -169,9 +158,7 @@ impl Sub for RationalBase { type Output = Self; fn sub(self, other: Self) -> Self::Output { - Self { - val: self.val - other.val - } + Self { val: self.val - other.val } } } @@ -185,9 +172,7 @@ impl Mul for RationalBase { type Output = Self; fn mul(self, other: Self) -> Self::Output { - Self { - val: self.val * other.val - } + Self { val: self.val * other.val } } } @@ -201,9 +186,7 @@ impl Div for RationalBase { type Output = Self; fn div(self, other: Self) -> Self::Output { - Self { - val: self.val / other.val - } + Self { val: self.val / other.val } } } @@ -217,9 +200,7 @@ impl Neg for RationalBase where { type Output = Self; fn neg(self) -> Self::Output { - Self { - val: -self.val - } + Self { val: -self.val } } } @@ -228,15 +209,15 @@ impl Rem for RationalBase { fn rem(self, modulus: RationalBase) -> Self::Output { if { - *self.val.denom() != 1 || - *modulus.val.denom() != 1 + *self.val.denom() != BigInt::from(1) || + *modulus.val.denom() != BigInt::from(1) } { panic!() } RationalBase{ - val : Rational::from(( + val : BigRational::new_raw( self.val.numer() % modulus.val.numer(), - 1 - )) + BigInt::from(1) + ) } } } diff --git a/src/quantity/scalar/scalar.rs b/src/quantity/scalar/scalar.rs index 514e575..0901c5f 100644 --- a/src/quantity/scalar/scalar.rs +++ b/src/quantity/scalar/scalar.rs @@ -7,7 +7,7 @@ use std::ops::{ }; use std::cmp::Ordering; -use super::floatbase::FloatBase as FloatBase; +use super::FloatBase as FloatBase; use super::rationalbase::RationalBase; @@ -21,7 +21,6 @@ pub trait ScalarBase: PartialEq + PartialOrd { // Creation - fn from_f64(f: f64) -> Option; fn from_string(s: &str) -> Option; // Utility @@ -87,8 +86,8 @@ fn to_float(r: Scalar) -> Scalar { match &r { Scalar::Float {..} => r, Scalar::Rational {v} => wrap_float!( - FloatBase::from(v.val.numer()).unwrap() / - FloatBase::from(v.val.denom()).unwrap() + FloatBase::from_string(&v.val.numer().to_string()).unwrap() / + FloatBase::from_string(&v.val.denom().to_string()).unwrap() ) } } @@ -105,13 +104,13 @@ impl ToString for Scalar { // Creation methods impl Scalar { pub fn new_float(f: f64) -> Option { - let v = FloatBase::from_f64(f); + let v = FloatBase::from_string(&f.to_string()); if v.is_none() { return None; } return Some(wrap_float!(v.unwrap())); } pub fn new_rational(f: f64) -> Option { - let r = RationalBase::from_f64(f); + let r = RationalBase::from_string(&f.to_string()); if r.is_none() { return None; } return Some(wrap_rational!(r.unwrap())); } @@ -185,8 +184,8 @@ impl Scalar { pub fn is_nan(&self) -> bool { match self { - Scalar::Float {v} => {v.val.is_nan()}, - Scalar::Rational {..} => {panic!()} + Scalar::Float{ v } => {v.val.is_nan()}, + Scalar::Rational {..} => {false} } } diff --git a/src/quantity/unit/unit.rs b/src/quantity/unit/unit.rs index c49e0b3..68f7beb 100644 --- a/src/quantity/unit/unit.rs +++ b/src/quantity/unit/unit.rs @@ -275,7 +275,6 @@ impl Unit { let mut q = Quantity::new_rational(1f64).unwrap(); q.set_unit(b); return Some(q); - } } diff --git a/src/tests.rs b/src/tests.rs index 0f41c8d..d54f10f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -131,7 +131,7 @@ fn operators() { good_expr("125", "5^(+3)"); good_expr("125", "+5^3"); - good_expr("0.2148", "3 ^ (-1.4)"); + good_expr("0.21479", "3 ^ (-1.4)"); // Should parse as ((2^3)^4)^5 good_expr("1.1529e18", "2^3^4^5"); @@ -162,6 +162,7 @@ fn operators() { good_expr("2", "6/3"); good_expr("2", "5%3"); + good_expr("4", "2^5 mod 7"); good_expr("8", "5+3"); good_expr("64", "4^3"); good_expr("64", "4 ^ 3"); @@ -184,6 +185,7 @@ fn operators() { bad_expr("1e5!"); bad_expr("0^(-1)"); bad_expr("pi!"); + bad_expr("2.5 mod 8"); } #[test]