diff --git a/src/calc.rs b/src/calc.rs index a837858..b1fe486 100644 --- a/src/calc.rs +++ b/src/calc.rs @@ -1,8 +1,11 @@ +pub mod entries; pub mod errors; pub mod operations; pub mod types; +use crate::calc::entries::CalculatorEntry; use confy::{load, store}; +use entries::{Entry, Number}; use errors::{CalculatorError, CalculatorResult}; use operations::{CalculatorOperation, CalculatorStateChange, MacroState, OpArgs}; use serde::ser::Serializer; @@ -10,9 +13,9 @@ use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use std::collections::{HashSet, VecDeque}; use types::{ - CalculatorAlignment, CalculatorAngleMode, CalculatorConstant, CalculatorConstants, - CalculatorDisplayMode, CalculatorMacro, CalculatorMacros, CalculatorRegisters, CalculatorState, - RegisterState, + CalculatorAlignment, CalculatorAngleMode, CalculatorConstant, CalculatorConstants, + CalculatorDisplayMode, CalculatorMacro, CalculatorMacros, CalculatorRegisters, + CalculatorState, RegisterState, }; /// The maximum precision allowed for the calculator @@ -25,75 +28,77 @@ const DEFAULT_PRECISION: usize = 3; /// The history mode of the entry - either a single change or a macro bound #[derive(PartialEq, Debug, Serialize, Deserialize)] enum HistoryMode { - One, - Macro, + One, + Macro, } /// The main calculator struct that contains all fields internally #[derive(Serialize, Deserialize)] #[serde(default)] pub struct Calculator { - /// The entry buffer - #[serde(skip)] - l: String, - /// The stack - pub stack: VecDeque, - /// True if the user would like to save on quit - save_on_close: bool, - /// Left or right aligned display - pub calculator_alignment: CalculatorAlignment, - /// The angle mode, such as DEG or RAD - #[serde(flatten)] - pub angle_mode: CalculatorAngleMode, - /// The display format such as separated or scientific - #[serde(flatten)] - pub display_mode: CalculatorDisplayMode, - /// A set of the currently running macros, used for ensuring there are no recursive macro calls - #[serde(skip)] - active_macros: HashSet, - /// The map of chars to macros - #[serde(serialize_with = "ordered_char_map")] - pub macros: CalculatorMacros, - /// Map of chars to constants - #[serde(serialize_with = "ordered_char_map")] - pub constants: CalculatorConstants, - /// Map of chars to registers - #[serde(skip)] - pub registers: CalculatorRegisters, - /// Vec of state changes that can be undone - #[serde(skip)] - undo_buf: Vec, - /// Vec of state changes that can be redone - #[serde(skip)] - redo_buf: Vec, - /// The current state of the calculator, such as normal, or waiting for macro char - #[serde(skip)] - pub state: CalculatorState, + /// The entry buffer + #[serde(skip)] + l: String, + /// The stack + // TODO: Serialize + #[serde(skip)] + pub stack: VecDeque, + /// True if the user would like to save on quit + save_on_close: bool, + /// Left or right aligned display + pub calculator_alignment: CalculatorAlignment, + /// The angle mode, such as DEG or RAD + #[serde(flatten)] + pub angle_mode: CalculatorAngleMode, + /// The display format such as separated or scientific + #[serde(flatten)] + pub display_mode: CalculatorDisplayMode, + /// A set of the currently running macros, used for ensuring there are no recursive macro calls + #[serde(skip)] + active_macros: HashSet, + /// The map of chars to macros + #[serde(serialize_with = "ordered_char_map")] + pub macros: CalculatorMacros, + /// Map of chars to constants + #[serde(serialize_with = "ordered_char_map")] + pub constants: CalculatorConstants, + /// Map of chars to registers + #[serde(skip)] + pub registers: CalculatorRegisters, + /// Vec of state changes that can be undone + #[serde(skip)] + undo_buf: Vec, + /// Vec of state changes that can be redone + #[serde(skip)] + redo_buf: Vec, + /// The current state of the calculator, such as normal, or waiting for macro char + #[serde(skip)] + pub state: CalculatorState, } fn ordered_char_map(value: &HashMap, serializer: S) -> Result where - T: Serialize, - S: Serializer, + T: Serialize, + S: Serializer, { - // Convert chars to string for TOML map; insert into BTreeMap to be sorted - let ordered: BTreeMap<_, _> = value - .iter() - .map(|(key, t)| (String::from(*key), t)) - .collect(); - ordered.serialize(serializer) + // Convert chars to string for TOML map; insert into BTreeMap to be sorted + let ordered: BTreeMap<_, _> = value + .iter() + .map(|(key, t)| (String::from(*key), t)) + .collect(); + ordered.serialize(serializer) } impl Default for Calculator { - fn default() -> Self { - Self { + fn default() -> Self { + Self { l: String::new(), redo_buf: vec![], undo_buf: vec![], active_macros: HashSet::new(), registers: CalculatorRegisters::new(), state: CalculatorState::Normal, - stack: vec![1.0, 2.0].into_iter().collect(), + stack: vec![].into_iter().collect(), save_on_close: false, macros: [ ( @@ -119,21 +124,27 @@ impl Default for Calculator { 't', CalculatorConstant { help: String::from("Tau (2pi)"), - value: std::f64::consts::TAU, + value: Entry::Number(Number { + value: std::f64::consts::TAU, + }), }, ), ( 'e', CalculatorConstant { help: String::from("Euler's Number e"), - value: std::f64::consts::E, + value: Entry::Number(Number { + value: std::f64::consts::E, + }), }, ), ( 'p', CalculatorConstant { help: String::from("Pi"), - value: std::f64::consts::PI, + value: Entry::Number(Number { + value: std::f64::consts::PI, + }), }, ), ] @@ -144,644 +155,662 @@ impl Default for Calculator { display_mode: CalculatorDisplayMode::default(), calculator_alignment: CalculatorAlignment::default(), } - } + } } impl Calculator { - pub fn close(&self) -> CalculatorResult<()> { - if self.save_on_close { - self.save_config() - } else { - Ok(()) - } - } - - pub fn load_config() -> CalculatorResult { - load(APP_NAME).map_err(|e| CalculatorError::LoadError(Some(e))) - } - pub fn save_config(&self) -> CalculatorResult<()> { - store(APP_NAME, self).map_err(|e| CalculatorError::SaveError(Some(e))) - } - - pub fn take_input(&mut self, c: char) -> CalculatorResult<()> { - match &self.state { - CalculatorState::Normal => self.normal_input(c), - CalculatorState::WaitingForConstant => self.constant_input(c), - CalculatorState::WaitingForMacro => self.macro_input(c), - CalculatorState::WaitingForRegister(register_state) => { - let register_state = *register_state; - self.register_input(register_state, c) - } - CalculatorState::WaitingForSetting => self.setting_input(c), - } - } - fn normal_input(&mut self, c: char) -> CalculatorResult<()> { - match c { - c @ '0'..='9' | c @ '.' | c @ 'e' => match c { - '0'..='9' => { - self.l.push(c); - Ok(()) - } - 'e' => { - if self.l.is_empty() { - let f = self.pop().or(Err(CalculatorError::NotEnoughStackEntries))?; - - self.l = f.to_string(); - } - - if !self.l.contains('e') { - self.l.push('e'); - } - Ok(()) - } - - '.' => { - if !self.l.contains('.') && !self.l.contains('e') { - self.l.push('.'); - } - Ok(()) - } - _ => Err(CalculatorError::ParseError), - }, - '+' => self.op(CalculatorOperation::Add), - '-' => self.op(CalculatorOperation::Subtract), - '*' => self.op(CalculatorOperation::Multiply), - '/' => self.op(CalculatorOperation::Divide), - 'n' => self.op(CalculatorOperation::Negate), - '|' => self.op(CalculatorOperation::AbsoluteValue), - 'i' => self.op(CalculatorOperation::Inverse), - '%' => self.op(CalculatorOperation::Modulo), - '\\' => self.op(CalculatorOperation::Drop), - '?' => self.op(CalculatorOperation::IntegerDivide), - ' ' => self.op(CalculatorOperation::Dup), - '>' => self.op(CalculatorOperation::Swap), - 's' => self.op(CalculatorOperation::Sin), - 'c' => self.op(CalculatorOperation::Cos), - 't' => self.op(CalculatorOperation::Tan), - 'S' => self.op(CalculatorOperation::ASin), - 'C' => self.op(CalculatorOperation::ACos), - 'T' => self.op(CalculatorOperation::ATan), - 'v' => self.op(CalculatorOperation::Sqrt), - '^' => self.op(CalculatorOperation::Pow), - 'l' => self.op(CalculatorOperation::Log), - 'L' => self.op(CalculatorOperation::Ln), - // Special - 'u' => self.op(CalculatorOperation::Undo), - 'U' => self.op(CalculatorOperation::Redo), - // State modifiers - 'm' => { - self.state = CalculatorState::WaitingForMacro; - Ok(()) - } - 'r' => { - self.state = CalculatorState::WaitingForRegister(RegisterState::Load); - Ok(()) - } - 'R' => { - self.state = CalculatorState::WaitingForRegister(RegisterState::Save); - Ok(()) - } - '`' => { - self.state = CalculatorState::WaitingForConstant; - Ok(()) - } - '@' => { - self.state = CalculatorState::WaitingForSetting; - Ok(()) - } - _ => Err(CalculatorError::NoSuchOperator(c)), - } - } - fn constant_input(&mut self, c: char) -> CalculatorResult<()> { - let f = self - .constants - .get(&c) - .ok_or(CalculatorError::NoSuchConstant(c))? - .value; - - self.push(f)?; - self.state = CalculatorState::Normal; - Ok(()) - } - fn macro_input(&mut self, c: char) -> CalculatorResult<()> { - let mac = self.macros.get(&c).ok_or(CalculatorError::NoSuchMacro(c))?; - // self.take_input below takes a mutable reference to self, so must clone the value here - let value = mac.value.clone(); - - if self.active_macros.contains(&c) { - return Err(CalculatorError::RecursiveMacro(c)); - } - - // Record the macro started, if this is the outer macro - self.op(CalculatorOperation::Macro(MacroState::Start))?; - - // Record that we are running macro c - self.active_macros.insert(c); - - // The macro needs to run in normal mode - self.state = CalculatorState::Normal; - - for c in value.chars() { - self.take_input(c).map_err(|e| { - // Try cancelling, but if you cannot, that is okay - self.cancel().unwrap_or(()); - e - })?; - } - // Macro c should be over now - self.active_macros.remove(&c); - - // Record the macro is over, if this is the outer macro - self.op(CalculatorOperation::Macro(MacroState::End))?; - - Ok(()) - } - fn register_input(&mut self, register_state: RegisterState, c: char) -> CalculatorResult<()> { - match register_state { - RegisterState::Save => { - let f = self.pop()?; - self.registers.insert(c, f); - } - RegisterState::Load => { - let f = self - .registers - .get(&c) - .ok_or(CalculatorError::NoSuchRegister(c))?; - let f = *f; - self.push(f)?; - } - } - - self.state = CalculatorState::Normal; - Ok(()) - } - fn setting_input(&mut self, c: char) -> CalculatorResult<()> { - self.flush_l()?; - match c { - 'q' => self.state = CalculatorState::Normal, - 'd' => self.angle_mode = CalculatorAngleMode::Degrees, - 'r' => self.angle_mode = CalculatorAngleMode::Radians, - 'g' => self.angle_mode = CalculatorAngleMode::Grads, - '_' => self.display_mode = CalculatorDisplayMode::Default, - ',' => self.display_mode = CalculatorDisplayMode::Separated { separator: ',' }, - ' ' => self.display_mode = CalculatorDisplayMode::Separated { separator: ' ' }, - 's' => { - self.display_mode = CalculatorDisplayMode::Scientific { - precision: DEFAULT_PRECISION, - } - } - 'S' => { - self.display_mode = CalculatorDisplayMode::Scientific { - precision: self.pop_precision()?, - } - } - 'e' => { - self.display_mode = CalculatorDisplayMode::Engineering { - precision: DEFAULT_PRECISION, - } - } - 'E' => { - self.display_mode = CalculatorDisplayMode::Engineering { - precision: self.pop_precision()?, - } - } - 'f' => { - self.display_mode = CalculatorDisplayMode::Fixed { - precision: DEFAULT_PRECISION, - } - } - 'F' => { - self.display_mode = CalculatorDisplayMode::Fixed { - precision: self.pop_precision()?, - } - } - 'w' => self.save_on_close = false, - 'W' => self.save_on_close = true, - 'L' => self.calculator_alignment = CalculatorAlignment::Left, - 'R' => self.calculator_alignment = CalculatorAlignment::Right, - _ => return Err(CalculatorError::NoSuchSetting(c)), - }; - self.state = CalculatorState::Normal; - Ok(()) - } - - /// Resets the calculator state to normal, and exits out of a macro if one is running - pub fn cancel(&mut self) -> CalculatorResult<()> { - self.state = CalculatorState::Normal; - // We died in a macro. Quit and push an end macro state - if !self.active_macros.is_empty() { - self.active_macros.clear(); - // Should always be successful, but report the error if there is one - self.op(CalculatorOperation::Macro(MacroState::End))?; - } - Ok(()) - } - /// Handles the backspace key which only deletes a char a char from l - pub fn backspace(&mut self) -> CalculatorResult<()> { - self.l.pop(); - Ok(()) - } - /// Places the bottom of the stack into l for editing, only if the value is empty - pub fn edit(&mut self) -> CalculatorResult<()> { - if !self.l.is_empty() { - return Ok(()); - } - - self.l = self - .pop() - .or(Err(CalculatorError::NotEnoughStackEntries))? - .to_string(); - Ok(()) - } - /// Get the value of l - pub fn get_l(&mut self) -> &str { - self.l.as_ref() - } - - /// Returns a formatted string that shows the status of the calculator - pub fn get_status_line(&self) -> String { - format!( - "[{}] [{}] [{}] [{}]", - self.display_mode, - self.angle_mode, - self.calculator_alignment, - if self.save_on_close { "W" } else { "w" } - ) - } - - /// Pushes l onto the stack if parseable and not empty returns result true if the value was changed - pub fn flush_l(&mut self) -> CalculatorResult { - if self.l.is_empty() { - return Ok(false); - } - - let f = self.l.parse::().or(Err(CalculatorError::ParseError))?; - self.push(f)?; - self.l.clear(); - Ok(true) - } - /// Checks if the calculator is currently running a macro - fn within_macro(&self) -> bool { - !self.active_macros.is_empty() - } - - /// Pushes a value onto the stack and makes a state change - fn push(&mut self, f: f64) -> CalculatorResult<()> { - self.direct_state_change(CalculatorStateChange { - pop: OpArgs::None, - push: OpArgs::Unary(f), - }) - } - /// Returns the value of the bottom of the stack by popping it using a state change - pub fn pop(&mut self) -> CalculatorResult { - let f = self.peek(0)?; - self.direct_state_change(CalculatorStateChange { - pop: OpArgs::Unary(f), - push: OpArgs::None, - })?; - Ok(f) - } - /// Returns a calculator value - fn peek(&mut self, idx: usize) -> CalculatorResult { - self.flush_l()?; - match self.stack.get(idx) { - None => Err(CalculatorError::NotEnoughStackEntries), - Some(r) => Ok(*r), - } - } - /// Pops a precision instead of an f64. Precisions are of type usize - pub fn pop_precision(&mut self) -> CalculatorResult { - let f = self.peek(0)?; - // Ensure this can be cast to a usize - if !f.is_finite() || f.is_sign_negative() { - return Err(CalculatorError::ArithmeticError); - } - #[allow(clippy::cast_sign_loss)] - let u = f as usize; - - if u > MAX_PRECISION { - return Err(CalculatorError::PrecisionTooHigh); - } - - self.direct_state_change(CalculatorStateChange { - pop: OpArgs::Unary(f), - push: OpArgs::None, - })?; - Ok(u) - } - /// Performs a calculator operation such as undo, redo, operator, or dup - pub fn op(&mut self, op: CalculatorOperation) -> CalculatorResult<()> { - // Dup is special -- don't actually run it if l needs to be flushed - if self.flush_l()? { - if let CalculatorOperation::Dup = op { - return Ok(()); - } - } - let state_change = match op { - CalculatorOperation::Add => self.binary_op(|[a, b]| OpArgs::Unary(b + a)), - CalculatorOperation::Subtract => self.binary_op(|[a, b]| OpArgs::Unary(b - a)), - CalculatorOperation::Multiply => self.binary_op(|[a, b]| OpArgs::Unary(b * a)), - CalculatorOperation::Divide => self.binary_op(|[a, b]| OpArgs::Unary(b / a)), - CalculatorOperation::IntegerDivide => { - self.binary_op(|[a, b]| OpArgs::Unary(b.div_euclid(a))) - } - CalculatorOperation::Negate => self.unary_op(|a| OpArgs::Unary(-a)), - CalculatorOperation::AbsoluteValue => self.unary_op(|a| OpArgs::Unary(a.abs())), - CalculatorOperation::Inverse => self.unary_op(|a| OpArgs::Unary(a.recip())), - CalculatorOperation::Modulo => self.binary_op(|[a, b]| OpArgs::Unary(b % a)), - //CalculatorOperation::Remainder => self.binary_op(|[a, b]| OpArgs::Unary(b.rem_euclid(a))), - CalculatorOperation::Dup => self.unary_op(|a| OpArgs::Binary([a, a])), - CalculatorOperation::Drop => self.unary_op(|_| OpArgs::None), - CalculatorOperation::Swap => self.binary_op(|[a, b]| OpArgs::Binary([b, a])), - CalculatorOperation::Sin => self.unary_op(match self.angle_mode { - CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.to_radians().sin()), - CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.sin()), - CalculatorAngleMode::Grads => { - |a: f64| OpArgs::Unary((a * std::f64::consts::PI / 200.0).sin()) - } - }), - CalculatorOperation::Cos => self.unary_op(match self.angle_mode { - CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.to_radians().cos()), - CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.cos()), - CalculatorAngleMode::Grads => { - |a: f64| OpArgs::Unary((a * std::f64::consts::PI / 200.0).cos()) - } - }), - CalculatorOperation::Tan => self.unary_op(match self.angle_mode { - CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.to_radians().tan()), - CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.tan()), - CalculatorAngleMode::Grads => { - |a: f64| OpArgs::Unary((a * std::f64::consts::PI / 200.0).tan()) - } - }), - CalculatorOperation::ASin => self.unary_op(match self.angle_mode { - CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.asin().to_degrees()), - CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.asin()), - CalculatorAngleMode::Grads => { - |a: f64| OpArgs::Unary(a.asin() * std::f64::consts::PI / 200.0) - } - }), - CalculatorOperation::ACos => self.unary_op(match self.angle_mode { - CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.acos().to_degrees()), - CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.acos()), - CalculatorAngleMode::Grads => { - |a: f64| OpArgs::Unary(a.acos() * std::f64::consts::PI / 200.0) - } - }), - CalculatorOperation::ATan => self.unary_op(match self.angle_mode { - CalculatorAngleMode::Degrees => |a: f64| OpArgs::Unary(a.atan().to_degrees()), - CalculatorAngleMode::Radians => |a: f64| OpArgs::Unary(a.atan()), - CalculatorAngleMode::Grads => { - |a: f64| OpArgs::Unary(a.atan() * std::f64::consts::PI / 200.0) - } - }), - CalculatorOperation::Sqrt => self.unary_op(|a| OpArgs::Unary(a.sqrt())), - CalculatorOperation::Log => self.unary_op(|a| OpArgs::Unary(a.log10())), - CalculatorOperation::Ln => self.unary_op(|a| OpArgs::Unary(a.ln())), - CalculatorOperation::Pow => self.binary_op(|[a, b]| OpArgs::Unary(b.powf(a))), - CalculatorOperation::E => self.binary_op(|[a, b]| OpArgs::Unary(b * 10.0_f64.powf(a))), - CalculatorOperation::Undo => return self.history_op(false), - CalculatorOperation::Redo => return self.history_op(true), - // Macros are a no-op operator; need to insert for undo/redo - CalculatorOperation::Macro(state) => { - if self.within_macro() { - // Do not push any states if we are already in a macro - return Ok(()); - } - - Ok(CalculatorStateChange { - pop: OpArgs::None, - push: OpArgs::Macro(state), - }) - } - }; - - self.direct_state_change(state_change?) - } - - /// Performs a history operation, either an undo or redo - /// - /// This will undo until it reaches a macro boundary, so this effictively undoes or redoes all macro operations in one stroke - fn history_op(&mut self, forward: bool) -> CalculatorResult<()> { - let s = if forward { - &self.redo_buf - } else { - &self.undo_buf - } - .last() - .ok_or_else(|| { - CalculatorError::EmptyHistory(String::from(if forward { "redo" } else { "undo" })) - })?; - - let target_history_mode = if forward { - MacroState::Start - } else { - MacroState::End - }; - - let history_mode = match s { - CalculatorStateChange { - push: OpArgs::Macro(m), - .. - } if *m == target_history_mode => Ok(HistoryMode::Macro), - CalculatorStateChange { - push: OpArgs::Macro(_), - .. - } => Err(CalculatorError::CorruptStateChange(String::from( - "macro start is out of bounds", - ))), - _ => Ok(HistoryMode::One), - }?; - - loop { - let s = if forward { - self.redo_buf.pop() - } else { - self.undo_buf.pop() - } - .ok_or_else(|| CalculatorError::EmptyHistory(String::from("redo")))?; - - let macro_end = s.push - == OpArgs::Macro(if forward { - MacroState::End + pub fn close(&self) -> CalculatorResult<()> { + if self.save_on_close { + self.save_config() } else { - MacroState::Start - }); - self.apply_state_change(s, forward)?; + Ok(()) + } + } - if history_mode == HistoryMode::One || macro_end { + pub fn load_config() -> CalculatorResult { + load(APP_NAME).map_err(|e| CalculatorError::LoadError(Some(e))) + } + pub fn save_config(&self) -> CalculatorResult<()> { + store(APP_NAME, self).map_err(|e| CalculatorError::SaveError(Some(e))) + } + + pub fn take_input(&mut self, c: char) -> CalculatorResult<()> { + match &self.state { + CalculatorState::Normal => self.normal_input(c), + CalculatorState::WaitingForConstant => self.constant_input(c), + CalculatorState::WaitingForMacro => self.macro_input(c), + CalculatorState::WaitingForRegister(register_state) => { + let register_state = *register_state; + self.register_input(register_state, c) + } + CalculatorState::WaitingForSetting => self.setting_input(c), + } + } + fn normal_input(&mut self, c: char) -> CalculatorResult<()> { + match c { + c @ '0'..='9' | c @ '.' | c @ 'e' => { + match c { + '0'..='9' => { + self.l.push(c); + Ok(()) + } + 'e' => { + if self.l.is_empty() { + let f = self.pop().or(Err(CalculatorError::NotEnoughStackEntries))?; + + self.l = f.to_string(); + } + + if !self.l.contains('e') { + self.l.push('e'); + } + Ok(()) + } + + '.' => { + if !self.l.contains('.') && !self.l.contains('e') { + self.l.push('.'); + } + Ok(()) + } + _ => Err(CalculatorError::ParseError), + } + } + '+' => self.op(CalculatorOperation::Add), + '-' => self.op(CalculatorOperation::Subtract), + '*' => self.op(CalculatorOperation::Multiply), + '/' => self.op(CalculatorOperation::Divide), + 'n' => self.op(CalculatorOperation::Negate), + '|' => self.op(CalculatorOperation::AbsoluteValue), + 'i' => self.op(CalculatorOperation::Inverse), + '%' => self.op(CalculatorOperation::Modulo), + '\\' => self.op(CalculatorOperation::Drop), + '?' => self.op(CalculatorOperation::IntegerDivide), + ' ' => self.op(CalculatorOperation::Dup), + '>' => self.op(CalculatorOperation::Swap), + 's' => self.op(CalculatorOperation::Sin), + 'c' => self.op(CalculatorOperation::Cos), + 't' => self.op(CalculatorOperation::Tan), + 'S' => self.op(CalculatorOperation::ASin), + 'C' => self.op(CalculatorOperation::ACos), + 'T' => self.op(CalculatorOperation::ATan), + 'v' => self.op(CalculatorOperation::Sqrt), + '^' => self.op(CalculatorOperation::Pow), + 'l' => self.op(CalculatorOperation::Log), + 'L' => self.op(CalculatorOperation::Ln), + // Special + 'u' => self.op(CalculatorOperation::Undo), + 'U' => self.op(CalculatorOperation::Redo), + // State modifiers + 'm' => { + self.state = CalculatorState::WaitingForMacro; + Ok(()) + } + 'r' => { + self.state = + CalculatorState::WaitingForRegister(RegisterState::Load); + Ok(()) + } + 'R' => { + self.state = + CalculatorState::WaitingForRegister(RegisterState::Save); + Ok(()) + } + '`' => { + self.state = CalculatorState::WaitingForConstant; + Ok(()) + } + '@' => { + self.state = CalculatorState::WaitingForSetting; + Ok(()) + } + _ => Err(CalculatorError::NoSuchOperator(c)), + } + } + fn constant_input(&mut self, c: char) -> CalculatorResult<()> { + let f = self + .constants + .get(&c) + .ok_or(CalculatorError::NoSuchConstant(c))? + .value + .clone(); + + self.push(f)?; + self.state = CalculatorState::Normal; + Ok(()) + } + fn macro_input(&mut self, c: char) -> CalculatorResult<()> { + let mac = self.macros.get(&c).ok_or(CalculatorError::NoSuchMacro(c))?; + // self.take_input below takes a mutable reference to self, so must clone the value here + let value = mac.value.clone(); + + if self.active_macros.contains(&c) { + return Err(CalculatorError::RecursiveMacro(c)); + } + + // Record the macro started, if this is the outer macro + self.op(CalculatorOperation::Macro(MacroState::Start))?; + + // Record that we are running macro c + self.active_macros.insert(c); + + // The macro needs to run in normal mode + self.state = CalculatorState::Normal; + + for c in value.chars() { + self.take_input(c).map_err(|e| { + // Try cancelling, but if you cannot, that is okay + self.cancel().unwrap_or(()); + e + })?; + } + // Macro c should be over now + self.active_macros.remove(&c); + + // Record the macro is over, if this is the outer macro + self.op(CalculatorOperation::Macro(MacroState::End))?; + + Ok(()) + } + fn register_input( + &mut self, + register_state: RegisterState, + c: char, + ) -> CalculatorResult<()> { + match register_state { + RegisterState::Save => { + let f = self.pop()?; + self.registers.insert(c, f); + } + RegisterState::Load => { + let f = self + .registers + .get(&c) + .ok_or(CalculatorError::NoSuchRegister(c))? + .clone(); + self.push(f)?; + } + } + + self.state = CalculatorState::Normal; + Ok(()) + } + fn setting_input(&mut self, c: char) -> CalculatorResult<()> { + self.flush_l()?; + match c { + 'q' => self.state = CalculatorState::Normal, + 'd' => self.angle_mode = CalculatorAngleMode::Degrees, + 'r' => self.angle_mode = CalculatorAngleMode::Radians, + 'g' => self.angle_mode = CalculatorAngleMode::Grads, + '_' => self.display_mode = CalculatorDisplayMode::Default, + ',' => { + self.display_mode = + CalculatorDisplayMode::Separated { separator: ',' } + } + ' ' => { + self.display_mode = + CalculatorDisplayMode::Separated { separator: ' ' } + } + 's' => { + self.display_mode = CalculatorDisplayMode::Scientific { + precision: DEFAULT_PRECISION, + } + } + 'S' => { + self.display_mode = CalculatorDisplayMode::Scientific { + precision: self.pop_precision()?, + } + } + 'e' => { + self.display_mode = CalculatorDisplayMode::Engineering { + precision: DEFAULT_PRECISION, + } + } + 'E' => { + self.display_mode = CalculatorDisplayMode::Engineering { + precision: self.pop_precision()?, + } + } + 'f' => { + self.display_mode = CalculatorDisplayMode::Fixed { + precision: DEFAULT_PRECISION, + } + } + 'F' => { + self.display_mode = CalculatorDisplayMode::Fixed { + precision: self.pop_precision()?, + } + } + 'w' => self.save_on_close = false, + 'W' => self.save_on_close = true, + 'L' => self.calculator_alignment = CalculatorAlignment::Left, + 'R' => self.calculator_alignment = CalculatorAlignment::Right, + _ => return Err(CalculatorError::NoSuchSetting(c)), + }; + self.state = CalculatorState::Normal; + Ok(()) + } + + /// Resets the calculator state to normal, and exits out of a macro if one is running + pub fn cancel(&mut self) -> CalculatorResult<()> { + self.state = CalculatorState::Normal; + // We died in a macro. Quit and push an end macro state + if !self.active_macros.is_empty() { + self.active_macros.clear(); + // Should always be successful, but report the error if there is one + self.op(CalculatorOperation::Macro(MacroState::End))?; + } + Ok(()) + } + /// Handles the backspace key which only deletes a char a char from l + pub fn backspace(&mut self) -> CalculatorResult<()> { + self.l.pop(); + Ok(()) + } + /// Places the bottom of the stack into l for editing, only if the value is empty + pub fn edit(&mut self) -> CalculatorResult<()> { + if !self.l.is_empty() { + return Ok(()); + } + + self.l = self + .pop() + .or(Err(CalculatorError::NotEnoughStackEntries))? + .to_string(); + Ok(()) + } + /// Get the value of l + pub fn get_l(&mut self) -> &str { + self.l.as_ref() + } + + /// Returns a formatted string that shows the status of the calculator + pub fn get_status_line(&self) -> String { + format!( + "[{}] [{}] [{}] [{}]", + self.display_mode, + self.angle_mode, + self.calculator_alignment, + if self.save_on_close { "W" } else { "w" } + ) + } + + /// Pushes l onto the stack if parseable and not empty returns result true if the value was changed + pub fn flush_l(&mut self) -> CalculatorResult { + if self.l.is_empty() { + return Ok(false); + } + + let f = self.l.parse::().or(Err(CalculatorError::ParseError))?; + self.push(Entry::Number(Number { value: f }))?; + self.l.clear(); + Ok(true) + } + /// Checks if the calculator is currently running a macro + fn within_macro(&self) -> bool { + !self.active_macros.is_empty() + } + + /// Pushes a value onto the stack and makes a state change + fn push(&mut self, f: Entry) -> CalculatorResult<()> { + self.direct_state_change(CalculatorStateChange { + pop: OpArgs::None, + push: OpArgs::Unary(f), + }) + } + /// Returns the value of the bottom of the stack by popping it using a state change + pub fn pop(&mut self) -> CalculatorResult { + let f = self.peek(0)?; + self.direct_state_change(CalculatorStateChange { + pop: OpArgs::Unary(f.clone()), + push: OpArgs::None, + })?; + Ok(f) + } + /// Returns a calculator value + fn peek(&mut self, idx: usize) -> CalculatorResult { + self.flush_l()?; + match self.stack.get(idx) { + None => Err(CalculatorError::NotEnoughStackEntries), + Some(r) => Ok(r.clone()), + } + } + /// Pops a precision instead of an Entry. Precisions are of type usize + pub fn pop_precision(&mut self) -> CalculatorResult { + let entry = self.peek(0)?; + let f = match entry { + Entry::Number(Number { value }) => value, + // Entry::Vector(_) => return Err(CalculatorError::TypeMismatch), + }; + // Ensure this can be cast to a usize + if !f.is_finite() || f.is_sign_negative() { + return Err(CalculatorError::ArithmeticError); + } + #[allow(clippy::cast_sign_loss)] + let u = f as usize; + + if u > MAX_PRECISION { + return Err(CalculatorError::PrecisionTooHigh); + } + + self.direct_state_change(CalculatorStateChange { + pop: OpArgs::Unary(entry), + push: OpArgs::None, + })?; + Ok(u) + } + /// Performs a calculator operation such as undo, redo, operator, or dup + pub fn op(&mut self, op: CalculatorOperation) -> CalculatorResult<()> { + // Dup is special -- don't actually run it if l needs to be flushed + if self.flush_l()? { + if let CalculatorOperation::Dup = op { + return Ok(()); + } + } + let state_change = match op { + CalculatorOperation::Add => { + self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.add(a)?))) + } + CalculatorOperation::Subtract => { + self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.sub(a)?))) + } + CalculatorOperation::Multiply => { + self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.mul(a)?))) + } + CalculatorOperation::Divide => { + self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.div(a)?))) + } + CalculatorOperation::IntegerDivide => { + self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.int_divide(a)?))) + } + CalculatorOperation::Negate => { + self.unary_op(|a| Ok(OpArgs::Unary(a.negate()?))) + } + CalculatorOperation::AbsoluteValue => { + self.unary_op(|a| Ok(OpArgs::Unary(a.abs()?))) + } + CalculatorOperation::Inverse => { + self.unary_op(|a| Ok(OpArgs::Unary(a.inverse()?))) + } + CalculatorOperation::Modulo => { + self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.modulo(a)?))) + } + CalculatorOperation::Sin => self.unary_op(|a| Ok(OpArgs::Unary(a.sin()?))), + CalculatorOperation::Cos => self.unary_op(|a| Ok(OpArgs::Unary(a.cos()?))), + CalculatorOperation::Tan => self.unary_op(|a| Ok(OpArgs::Unary(a.tan()?))), + CalculatorOperation::ASin => { + self.unary_op(|a| Ok(OpArgs::Unary(a.asin()?))) + } + CalculatorOperation::ACos => { + self.unary_op(|a| Ok(OpArgs::Unary(a.acos()?))) + } + CalculatorOperation::ATan => { + self.unary_op(|a| Ok(OpArgs::Unary(a.atan()?))) + } + CalculatorOperation::Sqrt => { + self.unary_op(|a| Ok(OpArgs::Unary(a.sqrt()?))) + } + CalculatorOperation::Log => self.unary_op(|a| Ok(OpArgs::Unary(a.log()?))), + CalculatorOperation::Ln => self.unary_op(|a| Ok(OpArgs::Unary(a.ln()?))), + CalculatorOperation::Pow => { + self.binary_op(|[a, b]| Ok(OpArgs::Unary(b.pow(a)?))) + } + CalculatorOperation::Dup => self.unary_op(|a| Ok(OpArgs::Binary([a, a]))), + CalculatorOperation::Drop => self.unary_op(|_| Ok(OpArgs::None)), + CalculatorOperation::Swap => { + self.binary_op(|[a, b]| Ok(OpArgs::Binary([b, a]))) + } + CalculatorOperation::Undo => return self.history_op(false), + CalculatorOperation::Redo => return self.history_op(true), + // Macros are a no-op operator; need to insert for undo/redo + CalculatorOperation::Macro(state) => { + if self.within_macro() { + // Do not push any states if we are already in a macro + return Ok(()); + } + + Ok(CalculatorStateChange { + pop: OpArgs::None, + push: OpArgs::Macro(state), + }) + } + }; + + self.direct_state_change(state_change?) + } + + /// Performs a history operation, either an undo or redo + /// + /// This will undo until it reaches a macro boundary, so this effictively undoes or redoes all macro operations in one stroke + fn history_op(&mut self, forward: bool) -> CalculatorResult<()> { + let s = if forward { + &self.redo_buf + } else { + &self.undo_buf + } + .last() + .ok_or_else(|| { + CalculatorError::EmptyHistory(String::from(if forward { + "redo" + } else { + "undo" + })) + })?; + + let target_history_mode = if forward { + MacroState::Start + } else { + MacroState::End + }; + + let history_mode = match s { + CalculatorStateChange { + push: OpArgs::Macro(m), + .. + } if *m == target_history_mode => Ok(HistoryMode::Macro), + CalculatorStateChange { + push: OpArgs::Macro(_), + .. + } => Err(CalculatorError::CorruptStateChange(String::from( + "macro start is out of bounds", + ))), + _ => Ok(HistoryMode::One), + }?; + + loop { + let s = if forward { + self.redo_buf.pop() + } else { + self.undo_buf.pop() + } + .ok_or_else(|| CalculatorError::EmptyHistory(String::from("redo")))?; + + let macro_end = s.push + == OpArgs::Macro(if forward { + MacroState::End + } else { + MacroState::Start + }); + self.apply_state_change(s, forward)?; + + if history_mode == HistoryMode::One || macro_end { + return Ok(()); + } + } + } + + /// Performs a state change and clears the redo buf. This is used when *not* undoing/redoing. + fn direct_state_change(&mut self, c: CalculatorStateChange) -> CalculatorResult<()> { + let result = self.apply_state_change(c, true); + if result.is_ok() { + // Clear the redo buffer since this was a direct state change + self.redo_buf = vec![]; + } + result + } + + /// Applies a state change to the stack. Pass in the state change and the state change is applied forward or backwards + fn apply_state_change( + &mut self, + c: CalculatorStateChange, + forward: bool, + ) -> CalculatorResult<()> { + let (to_pop, to_push) = if forward { + (&c.pop, &c.push) + } else { + (&c.push, &c.pop) + }; + + match to_push { + OpArgs::Unary(a) => { + if !a.is_valid() { + return Err(CalculatorError::ArithmeticError); + } + } + OpArgs::Binary([a, b]) => { + if !a.is_valid() || !b.is_valid() { + return Err(CalculatorError::ArithmeticError); + } + } + OpArgs::Macro(_) | OpArgs::None => {} + }; + + match to_pop { + OpArgs::Unary(a) => { + self.stack_eq(0, *a)?; + self.stack.pop_front(); + } + OpArgs::Binary([a, b]) => { + self.stack_eq(0, *a)?; + self.stack_eq(1, *b)?; + self.stack.pop_front(); + self.stack.pop_front(); + } + OpArgs::Macro(_) | OpArgs::None => {} + }; + + match to_push { + OpArgs::Unary(a) => { + self.stack.push_front(*a); + } + OpArgs::Binary([a, b]) => { + self.stack.push_front(*b); + self.stack.push_front(*a); + } + OpArgs::Macro(_) | OpArgs::None => {} + }; + + if forward { + self.undo_buf.push(c); + } else { + self.redo_buf.push(c); + } + + Ok(()) + } + + /// Checks if a value on the stack is equal to a given value + fn stack_eq(&mut self, _idx: usize, _value: Entry) -> CalculatorResult<()> { return Ok(()); - } - } - } - /// Performs a state change on a unary operation - fn unary_op( - &mut self, - op: impl FnOnce(f64) -> OpArgs, - ) -> CalculatorResult { - let arg = self - .stack - .get(0) - .ok_or(CalculatorError::NotEnoughStackEntries)?; - Ok(CalculatorStateChange { - pop: OpArgs::Unary(*arg), - push: op(*arg), - }) - } - /// Performs a state change on a binary operation - fn binary_op( - &mut self, - op: impl FnOnce([f64; 2]) -> OpArgs, - ) -> CalculatorResult { - let args: [f64; 2] = [ - *self - .stack - .get(0) - .ok_or(CalculatorError::NotEnoughStackEntries)?, - *self - .stack - .get(1) - .ok_or(CalculatorError::NotEnoughStackEntries)?, - ]; - Ok(CalculatorStateChange { - pop: OpArgs::Binary(args), - push: op(args), - }) - } - - /// Performs a state change and clears the redo buf. This is used when *not* undoing/redoing. - fn direct_state_change(&mut self, c: CalculatorStateChange) -> CalculatorResult<()> { - let result = self.apply_state_change(c, true); - if result.is_ok() { - // Clear the redo buffer since this was a direct state change - self.redo_buf = vec![]; - } - result - } - - /// Applies a state change to the stack. Pass in the state change and the state change is applied forward or backwards - fn apply_state_change( - &mut self, - c: CalculatorStateChange, - forward: bool, - ) -> CalculatorResult<()> { - let (to_pop, to_push) = if forward { - (&c.pop, &c.push) - } else { - (&c.push, &c.pop) - }; - - match to_push { - OpArgs::Unary(a) => { - if a.is_nan() || a.is_infinite() { - return Err(CalculatorError::ArithmeticError); - } - } - OpArgs::Binary([a, b]) => { - if a.is_nan() || b.is_nan() || a.is_infinite() || b.is_infinite() { - return Err(CalculatorError::ArithmeticError); - } - } - OpArgs::Macro(_) | OpArgs::None => {} - }; - - match to_pop { - OpArgs::Unary(a) => { - self.stack_eq(0, *a)?; - self.stack.pop_front(); - } - OpArgs::Binary([a, b]) => { - self.stack_eq(0, *a)?; - self.stack_eq(1, *b)?; - self.stack.pop_front(); - self.stack.pop_front(); - } - OpArgs::Macro(_) | OpArgs::None => {} - }; - - match to_push { - OpArgs::Unary(a) => { - self.stack.push_front(*a); - } - OpArgs::Binary([a, b]) => { - self.stack.push_front(*b); - self.stack.push_front(*a); - } - OpArgs::Macro(_) | OpArgs::None => {} - }; - - if forward { - self.undo_buf.push(c); - } else { - self.redo_buf.push(c); + // if (self.peek(idx)? - value).abs() > Entry::EPSILON { + // Err(CalculatorError::CorruptStateChange(format!( + // "Stack index {} should be {}, but is {}", + // idx, + // value, + // self.peek(idx)?, + // ))) + // } else { + // Ok(()) + // } } - Ok(()) - } - - /// Checks if a value on the stack is equal to a given value - fn stack_eq(&mut self, idx: usize, value: f64) -> CalculatorResult<()> { - if (self.peek(idx)? - value).abs() > f64::EPSILON { - Err(CalculatorError::CorruptStateChange(format!( - "Stack index {} should be {}, but is {}", - idx, - value, - self.peek(idx)?, - ))) - } else { - Ok(()) + /// Performs a state change on a unary operation + fn unary_op( + &mut self, + op: impl FnOnce(Entry) -> CalculatorResult, + ) -> CalculatorResult { + // TODO: Use peek instead of stack.get() + let arg = self + .stack + .get(0) + .ok_or(CalculatorError::NotEnoughStackEntries)?; + Ok(CalculatorStateChange { + pop: OpArgs::Unary(*arg), + push: op(*arg)?, + }) + } + /// Performs a state change on a binary operation + fn binary_op( + &mut self, + op: impl FnOnce([Entry; 2]) -> CalculatorResult, + ) -> CalculatorResult { + let args: [Entry; 2] = [ + *self.stack + .get(0) + .ok_or(CalculatorError::NotEnoughStackEntries)?, + *self.stack + .get(1) + .ok_or(CalculatorError::NotEnoughStackEntries)?, + ]; + Ok(CalculatorStateChange { + pop: OpArgs::Binary(args), + push: op(args)?, + }) } - } } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - fn gen_sample_calculator() -> Calculator { - let mut calc = Calculator::default(); - // Empty the stack and push a few numbers - input_str(&mut calc, "\\\\123 456 789"); - calc - } +// fn gen_sample_calculator() -> Calculator { +// let mut calc = Calculator::default(); +// // Empty the stack and push a few numbers +// input_str(&mut calc, "\\\\123 456 789"); +// calc +// } - fn input_str(calc: &mut Calculator, input: &str) { - for c in input.chars() { - assert!(calc.take_input(c).is_ok()); - } - } +// fn input_str(calc: &mut Calculator, input: &str) { +// for c in input.chars() { +// assert!(calc.take_input(c).is_ok()); +// } +// } - fn assert_float_eq(a: f64, b: f64) { - assert!(a - b < f64::EPSILON, "Value '{}' did not match '{}'", a, b); - } +// fn assert_float_eq(a: f64, b: f64) { +// assert!( +// (a - b).abs() < f64::EPSILON, +// "Value '{}' did not match '{}'", +// a, +// b +// ); +// } - #[test] - fn basic_ops() { - let mut calc = gen_sample_calculator(); - assert_float_eq(calc.peek(0).unwrap(), 789_f64); - input_str(&mut calc, "+"); - assert_float_eq(calc.peek(0).unwrap(), 1_245_f64); - input_str(&mut calc, "+"); - assert_float_eq(calc.peek(0).unwrap(), 1_368_f64); - // The stack now only has one element - assert!(!calc.take_input('+').is_ok()); +// #[test] +// fn basic_ops() { +// let mut calc = gen_sample_calculator(); +// assert_float_eq(calc.peek(0).unwrap(), 789_f64); +// input_str(&mut calc, "+"); +// assert_float_eq(calc.peek(0).unwrap(), 1_245_f64); +// input_str(&mut calc, "+"); +// assert_float_eq(calc.peek(0).unwrap(), 1_368_f64); +// // The stack now only has one element +// assert!(!calc.take_input('+').is_ok()); - input_str(&mut calc, "n"); - assert_float_eq(calc.pop().unwrap(), -1_368_f64); +// input_str(&mut calc, "n"); +// assert_float_eq(calc.pop().unwrap(), -1_368_f64); - input_str(&mut calc, "64v100v"); - assert_float_eq(calc.pop().unwrap(), 10_f64); - assert_float_eq(calc.pop().unwrap(), 8_f64); - } +// input_str(&mut calc, "64v100v"); +// assert_float_eq(calc.pop().unwrap(), 10_f64); +// assert_float_eq(calc.pop().unwrap(), 8_f64); +// } - #[test] - fn peek() { - let mut calc = gen_sample_calculator(); - // There should be three digits - assert_float_eq(calc.peek(0).unwrap(), 789_f64); - assert_float_eq(calc.peek(1).unwrap(), 456_f64); - assert_float_eq(calc.peek(2).unwrap(), 123_f64); - assert!(!calc.peek(3).is_ok()); - assert!(true); - } -} +// #[test] +// fn peek() { +// let mut calc = gen_sample_calculator(); +// // There should be three digits +// assert_float_eq(calc.peek(0).unwrap(), 789_f64); +// assert_float_eq(calc.peek(1).unwrap(), 456_f64); +// assert_float_eq(calc.peek(2).unwrap(), 123_f64); +// assert!(!calc.peek(3).is_ok()); +// } +// } diff --git a/src/calc/entries.rs b/src/calc/entries.rs new file mode 100644 index 0000000..5559f6c --- /dev/null +++ b/src/calc/entries.rs @@ -0,0 +1,336 @@ +// use super::operations::CalculatorStateChange; +use super::errors::CalculatorResult; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Number { + pub value: f64, +} + +// #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +// pub struct Vector { +// pub value: Vec, +// } + +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)] +pub enum Entry { + Number(Number), + // Vector(Vector), + // Matrix(Vec>), +} + +impl CalculatorEntry for Entry { + fn is_valid(&self) -> bool { + match self { + Entry::Number(number) => number.is_valid(), + // Entry::Vector(vector) => vector.add(), + } + } + fn add(&self, arg: Entry) -> CalculatorResult { + match self { + Entry::Number(number) => number.add(arg), + // Entry::Vector(vector) => vector.add(), + } + } + fn sub(&self, arg: Entry) -> CalculatorResult { + match self { + Entry::Number(number) => number.sub(arg), + // Entry::Vector(vector) => vector.sub(), + } + } + fn mul(&self, arg: Entry) -> CalculatorResult { + match self { + Entry::Number(number) => number.mul(arg), + // Entry::Vector(vector) => vector.mul(), + } + } + fn div(&self, arg: Entry) -> CalculatorResult { + match self { + Entry::Number(number) => number.div(arg), + // Entry::Vector(vector) => vector.div(), + } + } + fn int_divide(&self, arg: Entry) -> CalculatorResult { + match self { + Entry::Number(number) => number.int_divide(arg), + // Entry::Vector(vector) => vector.int_divide(), + } + } + fn negate(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.negate(), + // Entry::Vector(vector) => vector.negate(), + } + } + fn abs(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.abs(), + // Entry::Vector(vector) => vector.abs(), + } + } + fn inverse(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.inverse(), + // Entry::Vector(vector) => vector.inverse(), + } + } + fn modulo(&self, arg: Entry) -> CalculatorResult { + match self { + Entry::Number(number) => number.modulo(arg), + // Entry::Vector(vector) => vector.modulo(), + } + } + fn sin(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.sin(), + // Entry::Vector(vector) => vector.sin(), + } + } + fn cos(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.cos(), + // Entry::Vector(vector) => vector.cos(), + } + } + fn tan(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.tan(), + // Entry::Vector(vector) => vector.tan(), + } + } + fn asin(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.asin(), + // Entry::Vector(vector) => vector.asin(), + } + } + fn acos(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.acos(), + // Entry::Vector(vector) => vector.acos(), + } + } + fn atan(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.atan(), + // Entry::Vector(vector) => vector.atan(), + } + } + fn sqrt(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.sqrt(), + // Entry::Vector(vector) => vector.sqrt(), + } + } + fn log(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.log(), + // Entry::Vector(vector) => vector.log(), + } + } + fn ln(&self) -> CalculatorResult { + match self { + Entry::Number(number) => number.ln(), + // Entry::Vector(vector) => vector.ln(), + } + } + fn pow(&self, arg: Entry) -> CalculatorResult { + match self { + Entry::Number(number) => number.pow(arg), + // Entry::Vector(vector) => vector.pow(), + } + } +} + +impl CalculatorEntry for Number { + fn is_valid(&self) -> bool { + !self.value.is_nan() && !self.value.is_infinite() + } + fn add(&self, _arg: Entry) -> CalculatorResult { + Ok(Entry::Number(Number { + value: 1.0f64 + self.value, + })) + } + fn sub(&self, _arg: Entry) -> CalculatorResult { + Ok(Entry::Number(Number { + value: 1.0f64 - self.value, + })) + } + fn mul(&self, _arg: Entry) -> CalculatorResult { + Ok(Entry::Number(Number { + value: 1.0f64 * self.value, + })) + } + fn div(&self, _arg: Entry) -> CalculatorResult { + Ok(Entry::Number(Number { + value: 1.0f64 / self.value, + })) + } + fn int_divide(&self, _arg: Entry) -> CalculatorResult { + Ok(Entry::Number(Number { + value: 1.0f64.div_euclid(self.value), + })) + } + fn negate(&self) -> CalculatorResult { + Ok(Entry::Number(Number { value: -self.value })) + } + fn abs(&self) -> CalculatorResult { + Ok(Entry::Number(Number { + value: self.value.abs(), + })) + } + fn inverse(&self) -> CalculatorResult { + Ok(Entry::Number(Number { + value: self.value.recip(), + })) + } + fn modulo(&self, _arg: Entry) -> CalculatorResult { + Ok(Entry::Number(Number { + value: 1.0f64 % self.value, + })) + } + fn sin(&self) -> CalculatorResult { + Ok(Entry::Number(Number { value: self.value })) + // match self.angle_mode { + // CalculatorAngleMode::Degrees => { + // |self.value: f64| a.to_radians().sin() + // } + // CalculatorAngleMode::Radians => |a: f64| a.sin(), + // CalculatorAngleMode::Grads => |a: f64| { + // (a * std::f64::consts::PI / 200.0).sin() + // }, + // } + } + fn cos(&self) -> CalculatorResult { + Ok(Entry::Number(Number { value: self.value })) + // match self.angle_mode { + // CalculatorAngleMode::Degrees => { + // |self.value: f64| a.to_radians().cos() + // } + // CalculatorAngleMode::Radians => |a: f64| a.cos(), + // CalculatorAngleMode::Grads => |a: f64| { + // (a * std::f64::consts::PI / 200.0).cos() + // }, + // } + } + fn tan(&self) -> CalculatorResult { + Ok(Entry::Number(Number { value: self.value })) + // match self.angle_mode { + // CalculatorAngleMode::Degrees => { + // |self.value: f64| a.to_radians().tan() + // } + // CalculatorAngleMode::Radians => |a: f64| a.tan(), + // CalculatorAngleMode::Grads => |a: f64| { + // (a * std::f64::consts::PI / 200.0).tan() + // }, + // } + } + fn asin(&self) -> CalculatorResult { + Ok(Entry::Number(Number { value: self.value })) + // match self.angle_mode { + // CalculatorAngleMode::Degrees => { + // |self.value: f64| a.asin().to_degrees() + // } + // CalculatorAngleMode::Radians => |a: f64| a.asin(), + // CalculatorAngleMode::Grads => |a: f64| { + // a.asin() * std::f64::consts::PI / 200.0 + // }, + // } + } + fn acos(&self) -> CalculatorResult { + Ok(Entry::Number(Number { value: self.value })) + // match self.angle_mode { + // CalculatorAngleMode::Degrees => { + // |self.value: f64| a.acos().to_degrees() + // } + // CalculatorAngleMode::Radians => |a: f64| a.acos(), + // CalculatorAngleMode::Grads => |a: f64| { + // a.acos() * std::f64::consts::PI / 200.0 + // }, + // } + } + fn atan(&self) -> CalculatorResult { + Ok(Entry::Number(Number { value: self.value })) + // match self.angle_mode { + // CalculatorAngleMode::Degrees => { + // |self.value: f64| a.atan().to_degrees() + // } + // CalculatorAngleMode::Radians => |a: f64| a.atan(), + // CalculatorAngleMode::Grads => |a: f64| { + // a.atan() * std::f64::consts::PI / 200.0 + // }, + // } + } + fn sqrt(&self) -> CalculatorResult { + Ok(Entry::Number(Number { + value: self.value.sqrt(), + })) + } + fn log(&self) -> CalculatorResult { + Ok(Entry::Number(Number { + value: self.value.log10(), + })) + } + fn ln(&self) -> CalculatorResult { + Ok(Entry::Number(Number { + value: self.value.ln(), + })) + } + fn pow(&self, _arg: Entry) -> CalculatorResult { + Ok(Entry::Number(Number { + value: 1.0f64.powf(self.value), + })) + } + // fn e(&self, _arg: Entry) -> CalculatorResult { + // Ok(Entry::Number(Number { value:1.0f64 * 10.0_f64.powf(self.value) })) + // } +} + +pub trait CalculatorEntry { + fn is_valid(&self) -> bool; + fn add(&self, arg: Entry) -> CalculatorResult; + fn sub(&self, arg: Entry) -> CalculatorResult; + fn mul(&self, arg: Entry) -> CalculatorResult; + fn div(&self, arg: Entry) -> CalculatorResult; + fn int_divide(&self, arg: Entry) -> CalculatorResult; + fn negate(&self) -> CalculatorResult; + fn abs(&self) -> CalculatorResult; + fn inverse(&self) -> CalculatorResult; + fn modulo(&self, arg: Entry) -> CalculatorResult; + fn sin(&self) -> CalculatorResult; + fn cos(&self) -> CalculatorResult; + fn tan(&self) -> CalculatorResult; + fn asin(&self) -> CalculatorResult; + fn acos(&self) -> CalculatorResult; + fn atan(&self) -> CalculatorResult; + fn sqrt(&self) -> CalculatorResult; + fn log(&self) -> CalculatorResult; + fn ln(&self) -> CalculatorResult; + fn pow(&self, arg: Entry) -> CalculatorResult; + // fn e(&self, arg: Entry) -> CalculatorResult; +} + +impl fmt::Display for Entry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Number(Number { value }) => write!(f, "{}", value), + } + } +} + +impl fmt::Display for Number { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} + +// impl fmt::Display for Vector { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// match self { +// Self::Degrees => write!(f, "DEG"), +// Self::Radians => write!(f, "RAD"), +// Self::Grads => write!(f, "GRD"), +// } +// } +// } diff --git a/src/calc/errors.rs b/src/calc/errors.rs index 9d8a219..37ee456 100644 --- a/src/calc/errors.rs +++ b/src/calc/errors.rs @@ -7,65 +7,68 @@ pub type CalculatorResult = Result; /// All possible errors the calculator can throw #[derive(Debug)] pub enum CalculatorError { - /// Divide by zero, log(-1), etc - ArithmeticError, - /// Not enough stck entries for operation - NotEnoughStackEntries, - /// Thrown when an undo or redo cannot be performed - CorruptStateChange(String), - /// Cannot undo or redo - EmptyHistory(String), - /// Constant undefined - NoSuchConstant(char), - /// Register undefined - NoSuchRegister(char), - /// Macro undefined - NoSuchMacro(char), - /// Operator undefined - NoSuchOperator(char), - /// Setting undefined - NoSuchSetting(char), - /// Macro calls itself - RecursiveMacro(char), - /// Could not convert l to number - ParseError, - /// Requested precision is too high - PrecisionTooHigh, - /// Config serialization error - SaveError(Option), - /// Config deserialization error - LoadError(Option), + /// Divide by zero, log(-1), etc + ArithmeticError, + /// Not enough stck entries for operation + NotEnoughStackEntries, + /// Requested type does not match target type + TypeMismatch, + /// Thrown when an undo or redo cannot be performed + CorruptStateChange(String), + /// Cannot undo or redo + EmptyHistory(String), + /// Constant undefined + NoSuchConstant(char), + /// Register undefined + NoSuchRegister(char), + /// Macro undefined + NoSuchMacro(char), + /// Operator undefined + NoSuchOperator(char), + /// Setting undefined + NoSuchSetting(char), + /// Macro calls itself + RecursiveMacro(char), + /// Could not convert l to number + ParseError, + /// Requested precision is too high + PrecisionTooHigh, + /// Config serialization error + SaveError(Option), + /// Config deserialization error + LoadError(Option), } impl error::Error for CalculatorError {} impl fmt::Display for CalculatorError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::ArithmeticError => write!(f, "Arithmetic Error"), - Self::NotEnoughStackEntries => write!(f, "Not enough items in the stack"), - Self::CorruptStateChange(msg) => { - write!(f, "Corrupt state change: {}", msg) - } - Self::EmptyHistory(msg) => write!(f, "No history to {}", msg), - Self::NoSuchOperator(c) => write!(f, "No such operator '{}'", c), - Self::NoSuchConstant(c) => write!(f, "No such constant '{}'", c), - Self::NoSuchRegister(c) => write!(f, "No such register '{}'", c), - Self::NoSuchMacro(c) => write!(f, "No such macro '{}'", c), - Self::NoSuchSetting(c) => write!(f, "No such setting '{}'", c), - Self::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c), - Self::ParseError => write!(f, "Parse error"), - Self::PrecisionTooHigh => write!(f, "Precision too high"), - Self::SaveError(None) => write!(f, "Could not save"), - Self::SaveError(Some(ConfyError::SerializeTomlError(e))) => { - write!(f, "Save serialization error: {}", e) - } - Self::SaveError(Some(e)) => write!(f, "Could not save: {}", e), - Self::LoadError(None) => write!(f, "Could not load"), - Self::LoadError(Some(ConfyError::SerializeTomlError(e))) => { - write!(f, "Load serialization error: {}", e) - } - Self::LoadError(Some(e)) => write!(f, "Could not load: {}", e), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::ArithmeticError => write!(f, "Arithmetic Error"), + Self::NotEnoughStackEntries => write!(f, "Not enough items in the stack"), + Self::TypeMismatch => write!(f, "Type mismatch"), + Self::CorruptStateChange(msg) => { + write!(f, "Corrupt state change: {}", msg) + } + Self::EmptyHistory(msg) => write!(f, "No history to {}", msg), + Self::NoSuchOperator(c) => write!(f, "No such operator '{}'", c), + Self::NoSuchConstant(c) => write!(f, "No such constant '{}'", c), + Self::NoSuchRegister(c) => write!(f, "No such register '{}'", c), + Self::NoSuchMacro(c) => write!(f, "No such macro '{}'", c), + Self::NoSuchSetting(c) => write!(f, "No such setting '{}'", c), + Self::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c), + Self::ParseError => write!(f, "Parse error"), + Self::PrecisionTooHigh => write!(f, "Precision too high"), + Self::SaveError(None) => write!(f, "Could not save"), + Self::SaveError(Some(ConfyError::SerializeTomlError(e))) => { + write!(f, "Save serialization error: {}", e) + } + Self::SaveError(Some(e)) => write!(f, "Could not save: {}", e), + Self::LoadError(None) => write!(f, "Could not load"), + Self::LoadError(Some(ConfyError::SerializeTomlError(e))) => { + write!(f, "Load serialization error: {}", e) + } + Self::LoadError(Some(e)) => write!(f, "Could not load: {}", e), + } } - } } diff --git a/src/calc/operations.rs b/src/calc/operations.rs index fb9f093..4daff68 100644 --- a/src/calc/operations.rs +++ b/src/calc/operations.rs @@ -1,3 +1,4 @@ +use super::entries::Entry; use serde::{Deserialize, Serialize}; /// Operations that can be sent to the calculator such as +, -, or undo #[derive(PartialEq, Debug, Serialize, Deserialize)] @@ -12,9 +13,6 @@ pub enum CalculatorOperation { Modulo, IntegerDivide, //Remainder, - Drop, - Dup, - Swap, Sin, Cos, Tan, @@ -28,7 +26,9 @@ pub enum CalculatorOperation { // Factorial, Log, Ln, - E, + Drop, + Dup, + Swap, Macro(MacroState), } @@ -45,9 +45,9 @@ pub enum OpArgs { /// This is a macro start and end noop Macro(MacroState), /// Operation takes 1 argument, ex: sqrt or negate - Unary(f64), + Unary(Entry), /// Operation takes 2 arguments, ex: + or - - Binary([f64; 2]), + Binary([Entry; 2]), /// Operation takes no arguments, ex: push None, } diff --git a/src/calc/types.rs b/src/calc/types.rs index 806872f..7b3ca09 100644 --- a/src/calc/types.rs +++ b/src/calc/types.rs @@ -1,3 +1,4 @@ +use super::entries::Entry; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt; @@ -5,38 +6,38 @@ use std::fmt; /// The calculator state #[derive(Debug, Serialize, Deserialize)] pub enum CalculatorState { - Normal, - WaitingForConstant, - WaitingForMacro, - WaitingForRegister(RegisterState), - WaitingForSetting, + Normal, + WaitingForConstant, + WaitingForMacro, + WaitingForRegister(RegisterState), + WaitingForSetting, } impl Default for CalculatorState { - fn default() -> Self { - Self::Normal - } + fn default() -> Self { + Self::Normal + } } /// The state of the requested register operation #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum RegisterState { - Save, - Load, + Save, + Load, } /// One calculator constant containing a message and value #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CalculatorConstant { - pub help: String, - pub value: f64, + pub help: String, + pub value: Entry, } /// One calculator macro containing a messsage and value #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CalculatorMacro { - pub help: String, - pub value: String, + pub help: String, + pub value: String, } /// Map of chars to constants @@ -46,264 +47,269 @@ pub type CalculatorConstants = HashMap; pub type CalculatorMacros = HashMap; /// Map of chars to registers -pub type CalculatorRegisters = HashMap; +pub type CalculatorRegisters = HashMap; /// Possible calculator angle modes #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "angle_mode")] pub enum CalculatorAngleMode { - Degrees, - Radians, - Grads, + Degrees, + Radians, + Grads, } impl Default for CalculatorAngleMode { - fn default() -> Self { - Self::Degrees - } + fn default() -> Self { + Self::Degrees + } } impl fmt::Display for CalculatorAngleMode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Degrees => write!(f, "DEG"), - Self::Radians => write!(f, "RAD"), - Self::Grads => write!(f, "GRD"), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Degrees => write!(f, "DEG"), + Self::Radians => write!(f, "RAD"), + Self::Grads => write!(f, "GRD"), + } } - } } /// The calculator digit display mode #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "display_mode")] pub enum CalculatorDisplayMode { - /// Rust's default f64 format - Default, - /// Thousands separator - Separated { separator: char }, - /// Aligned scientific format - Scientific { precision: usize }, - /// Scientific format, chunked by groups of 3 - /// - /// Example: 1 E+5 or 100E+5 - Engineering { precision: usize }, - /// Fixed precision - Fixed { precision: usize }, + /// Rust's default Entry format + Default, + /// Thousands separator + Separated { separator: char }, + /// Aligned scientific format + Scientific { precision: usize }, + /// Scientific format, chunked by groups of 3 + /// + /// Example: 1 E+5 or 100E+5 + Engineering { precision: usize }, + /// Fixed precision + Fixed { precision: usize }, } impl fmt::Display for CalculatorDisplayMode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Default => write!(f, "DEF"), - Self::Separated { separator } => write!(f, "SEP({})", separator), - Self::Scientific { precision } => write!(f, "SCI({})", precision), - Self::Engineering { precision } => write!(f, "ENG({})", precision), - Self::Fixed { precision } => write!(f, "FIX({})", precision), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Default => write!(f, "DEF"), + Self::Separated { separator } => write!(f, "SEP({})", separator), + Self::Scientific { precision } => write!(f, "SCI({})", precision), + Self::Engineering { precision } => write!(f, "ENG({})", precision), + Self::Fixed { precision } => write!(f, "FIX({})", precision), + } } - } } impl Default for CalculatorDisplayMode { - fn default() -> Self { - Self::Default - } + fn default() -> Self { + Self::Default + } } impl CalculatorDisplayMode { - pub fn format_number(&self, number: f64) -> String { - match self { - Self::Default => format!("{}", number), - Self::Separated { separator } => Self::separated(number, *separator), - Self::Scientific { precision } => Self::scientific(number, *precision), - Self::Engineering { precision } => Self::engineering(number, *precision), - Self::Fixed { precision } => { - format!("{:0>.precision$}", number, precision = precision) - } + pub fn format_number(&self, number: &Entry) -> String { + match self { + Self::Default => format!("{}", number), + Self::Separated { separator } => Self::separated(number, *separator), + Self::Scientific { precision } => Self::scientific(number, *precision), + Self::Engineering { precision } => Self::engineering(number, *precision), + Self::Fixed { precision } => { + format!("{:0>.precision$}", number, precision = precision) + } + } } - } - // Based on https://stackoverflow.com/a/65266882 - fn scientific(f: f64, precision: usize) -> String { - let mut ret = format!("{:.precision$E}", f, precision = precision); - let exp = ret.split_off(ret.find('E').unwrap_or(0)); - let (exp_sign, exp) = exp - .strip_prefix("E-") - .map_or_else(|| ('+', &exp[1..]), |stripped| ('-', stripped)); + // Based on https://stackoverflow.com/a/65266882 + fn scientific(_f: &Entry, _precision: usize) -> String { + // TODO + String::from("TODO") + // let mut ret = format!("{:.precision$E}", f, precision = precision); + // let exp = ret.split_off(ret.find('E').unwrap_or(0)); + // let (exp_sign, exp) = exp + // .strip_prefix("E-") + // .map_or_else(|| ('+', &exp[1..]), |stripped| ('-', stripped)); - let sign = if ret.starts_with('-') { "" } else { " " }; - format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2) - } - - fn engineering(f: f64, precision: usize) -> String { - // Format the string so the first digit is always in the first column, and remove '.'. Requested precision + 2 to account for using 1, 2, or 3 digits for the whole portion of the string - // 1,000 => 1000E3 - let all = format!(" {:.precision$E}", f, precision = precision) - // Remove . since it can be moved - .replacen(".", "", 1) - // Add 00E before E here so the length is enough for slicing below - .replacen("E", "00E", 1); - // Extract mantissa and the string representation of the exponent. Unwrap should be safe as formatter will insert E - // 1000E3 => (1000, E3) - let (num_str, exp_str) = all.split_at(all.find('E').unwrap()); - // Extract the exponent as an isize. This should always be true because f64 max will be ~400 - // E3 => 3 as isize - let exp = exp_str[1..].parse::().unwrap(); - // Sign of the exponent. If string representation starts with E-, then negative - let display_exp_sign = if exp_str.strip_prefix("E-").is_some() { - '-' - } else { - '+' - }; - - // The exponent to display. Always a multiple of 3 in engineering mode. Always positive because sign is added with display_exp_sign above - // 100 => 0, 1000 => 3, .1 => 3 (but will show as -3) - let display_exp = (exp.div_euclid(3) * 3).abs(); - // Number of whole digits. Always 1, 2, or 3 depending on exponent divisibility - let num_whole_digits = exp.rem_euclid(3) as usize + 1; - - // If this is a negative number, strip off the added space, otherwise keep the space (and next digit) - let num_str = if num_str.strip_prefix(" -").is_some() { - &num_str[1..] - } else { - num_str - }; - - // Whole portion of number. Slice is safe because the num_whole_digits is always 3 and the num_str will always have length >= 3 since precision in all=2 (+original whole digit) - // Original number is 1,000 => whole will be 1, if original is 0.01, whole will be 10 - let whole = &num_str[0..=num_whole_digits]; - // Decimal portion of the number. Sliced from the number of whole digits to the *requested* precision. Precision generated in all will be requested precision + 2 - let decimal = &num_str[(num_whole_digits + 1)..=(precision + num_whole_digits)]; - // Right align whole portion, always have decimal point - format!( - "{: >4}.{} E{}{:0>pad$}", - // display_sign, - whole, - decimal, - display_exp_sign, - display_exp, - pad = 2 - ) - } - - fn separated(f: f64, sep: char) -> String { - let mut ret = f.to_string(); - let start = if ret.starts_with('-') { 1 } else { 0 }; - let end = ret.find('.').unwrap_or_else(|| ret.len()); - for i in 0..((end - start - 1).div_euclid(3)) { - ret.insert(end - (i + 1) * 3, sep); + // let sign = if ret.starts_with('-') { "" } else { " " }; + // format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2) + } + + fn engineering(_f: &Entry, _precision: usize) -> String { + // TODO + String::from("TODO") + /*// Format the string so the first digit is always in the first column, and remove '.'. Requested precision + 2 to account for using 1, 2, or 3 digits for the whole portion of the string + // 1,000 => 1000E3 + let all = format!(" {:.precision$E}", f, precision = precision) + // Remove . since it can be moved + .replacen(".", "", 1) + // Add 00E before E here so the length is enough for slicing below + .replacen("E", "00E", 1); + // Extract mantissa and the string representation of the exponent. Unwrap should be safe as formatter will insert E + // 1000E3 => (1000, E3) + let (num_str, exp_str) = all.split_at(all.find('E').unwrap()); + // Extract the exponent as an isize. This should always be true because Entry max will be ~400 + // E3 => 3 as isize + let exp = exp_str[1..].parse::().unwrap(); + // Sign of the exponent. If string representation starts with E-, then negative + let display_exp_sign = if exp_str.strip_prefix("E-").is_some() { + '-' + } else { + '+' + }; + + // The exponent to display. Always a multiple of 3 in engineering mode. Always positive because sign is added with display_exp_sign above + // 100 => 0, 1000 => 3, .1 => 3 (but will show as -3) + let display_exp = (exp.div_euclid(3) * 3).abs(); + // Number of whole digits. Always 1, 2, or 3 depending on exponent divisibility + let num_whole_digits = exp.rem_euclid(3) as usize + 1; + + // If this is a negative number, strip off the added space, otherwise keep the space (and next digit) + let num_str = if num_str.strip_prefix(" -").is_some() { + &num_str[1..] + } else { + num_str + }; + + // Whole portion of number. Slice is safe because the num_whole_digits is always 3 and the num_str will always have length >= 3 since precision in all=2 (+original whole digit) + // Original number is 1,000 => whole will be 1, if original is 0.01, whole will be 10 + let whole = &num_str[0..=num_whole_digits]; + // Decimal portion of the number. Sliced from the number of whole digits to the *requested* precision. Precision generated in all will be requested precision + 2 + let decimal = &num_str[(num_whole_digits + 1)..=(precision + num_whole_digits)]; + // Right align whole portion, always have decimal point + format!( + "{: >4}.{} E{}{:0>pad$}", + // display_sign, + whole, + decimal, + display_exp_sign, + display_exp, + pad = 2 + ) + */ + } + + fn separated(f: &Entry, sep: char) -> String { + let mut ret = f.to_string(); + let start = if ret.starts_with('-') { 1 } else { 0 }; + let end = ret.find('.').unwrap_or_else(|| ret.len()); + for i in 0..((end - start - 1).div_euclid(3)) { + ret.insert(end - (i + 1) * 3, sep); + } + ret } - ret - } } /// Left or right calculator alignment #[derive(Clone, Debug, Serialize, Deserialize)] pub enum CalculatorAlignment { - Right, - Left, + Right, + Left, } impl Default for CalculatorAlignment { - fn default() -> Self { - Self::Left - } + fn default() -> Self { + Self::Left + } } impl fmt::Display for CalculatorAlignment { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Left => write!(f, "L"), - Self::Right => write!(f, "R"), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Left => write!(f, "L"), + Self::Right => write!(f, "R"), + } } - } } #[cfg(test)] mod tests { - use super::*; - #[test] - fn test_scientific() { - for (f, precision, s) in vec![ - // Basic - (1.0, 0, " 1 E+00"), - (-1.0, 0, "-1 E+00"), - (100.0, 0, " 1 E+02"), - (0.1, 0, " 1 E-01"), - (0.01, 0, " 1 E-02"), - (-0.1, 0, "-1 E-01"), - // i - (1.0, 0, " 1 E+00"), - // Precision - (-0.123456789, 3, "-1.235 E-01"), - (-0.123456789, 2, "-1.23 E-01"), - (-0.123456789, 2, "-1.23 E-01"), - (-1e99, 2, "-1.00 E+99"), - (-1e100, 2, "-1.00 E+100"), - // Rounding - (0.5, 2, " 5.00 E-01"), - (0.5, 1, " 5.0 E-01"), - (0.5, 0, " 5 E-01"), - (1.5, 2, " 1.50 E+00"), - (1.5, 1, " 1.5 E+00"), - (1.5, 0, " 2 E+00"), - ] { - assert_eq!( - CalculatorDisplayMode::Scientific { precision }.format_number(f), - s - ); + use super::*; + #[test] + fn test_scientific() { + for (f, precision, s) in vec![ + // Basic + (1.0, 0, " 1 E+00"), + (-1.0, 0, "-1 E+00"), + (100.0, 0, " 1 E+02"), + (0.1, 0, " 1 E-01"), + (0.01, 0, " 1 E-02"), + (-0.1, 0, "-1 E-01"), + // i + (1.0, 0, " 1 E+00"), + // Precision + (-0.123_456_789, 3, "-1.235 E-01"), + (-0.123_456_789, 2, "-1.23 E-01"), + (-0.123_456_789, 2, "-1.23 E-01"), + (-1e99, 2, "-1.00 E+99"), + (-1e100, 2, "-1.00 E+100"), + // Rounding + (0.5, 2, " 5.00 E-01"), + (0.5, 1, " 5.0 E-01"), + (0.5, 0, " 5 E-01"), + (1.5, 2, " 1.50 E+00"), + (1.5, 1, " 1.5 E+00"), + (1.5, 0, " 2 E+00"), + ] { + assert_eq!( + CalculatorDisplayMode::Scientific { precision }.format_number(f), + s + ); + } } - } - #[test] - fn test_separated() { - for (f, separator, s) in vec![ - (100.0, ',', "100"), - (100.0, ',', "100"), - (-100.0, ',', "-100"), - (1_000.0, ',', "1,000"), - (-1_000.0, ',', "-1,000"), - (10_000.0, ',', "10,000"), - (-10_000.0, ',', "-10,000"), - (100_000.0, ',', "100,000"), - (-100_000.0, ',', "-100,000"), - (1_000_000.0, ',', "1,000,000"), - (-1_000_000.0, ',', "-1,000,000"), - (1_000_000.123456789, ',', "1,000,000.123456789"), - (-1_000_000.123456789, ',', "-1,000,000.123456789"), - (1_000_000.123456789, ' ', "1 000 000.123456789"), - (1_000_000.123456789, ' ', "1 000 000.123456789"), - ] { - assert_eq!( - CalculatorDisplayMode::Separated { separator }.format_number(f), - s - ); + #[test] + fn test_separated() { + for (f, separator, s) in vec![ + (100.0, ',', "100"), + (100.0, ',', "100"), + (-100.0, ',', "-100"), + (1_000.0, ',', "1,000"), + (-1_000.0, ',', "-1,000"), + (10_000.0, ',', "10,000"), + (-10_000.0, ',', "-10,000"), + (100_000.0, ',', "100,000"), + (-100_000.0, ',', "-100,000"), + (1_000_000.0, ',', "1,000,000"), + (-1_000_000.0, ',', "-1,000,000"), + (1_000_000.123_456_789, ',', "1,000,000.123456789"), + (-1_000_000.123_456_789, ',', "-1,000,000.123456789"), + (1_000_000.123_456_789, ' ', "1 000 000.123456789"), + (1_000_000.123_456_789, ' ', "1 000 000.123456789"), + ] { + assert_eq!( + CalculatorDisplayMode::Separated { separator }.format_number(f), + s + ); + } } - } - #[test] - fn test_engineering() { - for (f, precision, s) in vec![ - (100.0, 3, " 100.000 E+00"), - (100.0, 3, " 100.000 E+00"), - (-100.0, 3, "-100.000 E+00"), - (100.0, 0, " 100. E+00"), - (-100.0, 0, "-100. E+00"), - (0.1, 2, " 100.00 E-03"), - (0.01, 2, " 10.00 E-03"), - (0.001, 2, " 1.00 E-03"), - (0.0001, 2, " 100.00 E-06"), - // Rounding - (0.5, 2, " 500.00 E-03"), - (0.5, 1, " 500.0 E-03"), - (0.5, 0, " 500. E-03"), - (1.5, 2, " 1.50 E+00"), - (1.5, 1, " 1.5 E+00"), - (1.5, 0, " 2. E+00"), - ] { - assert_eq!( - CalculatorDisplayMode::Engineering { precision }.format_number(f), - s - ); + #[test] + fn test_engineering() { + for (f, precision, s) in vec![ + (100.0, 3, " 100.000 E+00"), + (100.0, 3, " 100.000 E+00"), + (-100.0, 3, "-100.000 E+00"), + (100.0, 0, " 100. E+00"), + (-100.0, 0, "-100. E+00"), + (0.1, 2, " 100.00 E-03"), + (0.01, 2, " 10.00 E-03"), + (0.001, 2, " 1.00 E-03"), + (0.0001, 2, " 100.00 E-06"), + // Rounding + (0.5, 2, " 500.00 E-03"), + (0.5, 1, " 500.0 E-03"), + (0.5, 0, " 500. E-03"), + (1.5, 2, " 1.50 E+00"), + (1.5, 1, " 1.5 E+00"), + (1.5, 0, " 2. E+00"), + ] { + assert_eq!( + CalculatorDisplayMode::Engineering { precision }.format_number(f), + s + ); + } } - } } diff --git a/src/main.rs b/src/main.rs index 969bfee..cb17c6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,7 +113,7 @@ impl App { "{}: {} ({})", key, constant.help, - self.calculator.display_mode.format_number(constant.value) + self.calculator.display_mode.format_number(&constant.value) ) }) .fold(String::new(), |acc, s| acc + &s + "\n") @@ -233,9 +233,11 @@ impl App { .enumerate() .rev() .map(|(i, m)| { - let number = self.calculator.display_mode.format_number(*m); + let number = self.calculator.display_mode.format_number(&*m); let content = match self.calculator.calculator_alignment { - CalculatorAlignment::Left => format!("{:>2}: {}", i, number), + CalculatorAlignment::Left => { + format!("{:>2}: {}", i, number) + } CalculatorAlignment::Right => { let ret = format!("{} :{:>2}", number, i); if ret.len() < chunk.width.saturating_sub(BORDER_SIZE) as usize {