diff --git a/src/calc.rs b/src/calc.rs index c31d399..020ac9e 100644 --- a/src/calc.rs +++ b/src/calc.rs @@ -13,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 @@ -28,68 +28,68 @@ 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, - /// 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, - /// The stack - pub stack: VecDeque, - /// 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, + /// 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, + /// The stack + pub stack: VecDeque, + /// 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![], @@ -153,570 +153,612 @@ 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 - .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 => { - let angle_mode = self.angle_mode; - self.unary_op(|a| Ok(OpArgs::Unary(a.sin(angle_mode)?))) - } - CalculatorOperation::Cos => { - let angle_mode = self.angle_mode; - self.unary_op(|a| Ok(OpArgs::Unary(a.cos(angle_mode)?))) - } - CalculatorOperation::Tan => { - let angle_mode = self.angle_mode; - self.unary_op(|a| Ok(OpArgs::Unary(a.tan(angle_mode)?))) - } - CalculatorOperation::ASin => { - let angle_mode = self.angle_mode; - self.unary_op(|a| Ok(OpArgs::Unary(a.asin(angle_mode)?))) - } - CalculatorOperation::ACos => { - let angle_mode = self.angle_mode; - self.unary_op(|a| Ok(OpArgs::Unary(a.acos(angle_mode)?))) - } - CalculatorOperation::ATan => { - let angle_mode = self.angle_mode; - self.unary_op(|a| Ok(OpArgs::Unary(a.atan(angle_mode)?))) - } - 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 + pub fn close(&self) -> CalculatorResult<()> { + if self.save_on_close { + self.save_config() } 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); + Ok(()) } - } - OpArgs::Binary([a, b]) => { - if !a.is_valid() || !b.is_valid() { - return Err(CalculatorError::ArithmeticError); + } + + 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), } - } - OpArgs::Macro(_) | OpArgs::None => {} - }; + } + 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))?; - 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 => {} - }; + self.l = f.to_string(); + } - 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 !self.l.contains('e') { + self.l.push('e'); + } + Ok(()) + } - if forward { - self.undo_buf.push(c); - } else { - self.redo_buf.push(c); + '.' => { + 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(()) } - Ok(()) - } - - /// Checks if a value on the stack is equal to a given value - fn stack_eq(&mut self, idx: usize, value: Entry) -> CalculatorResult<()> { - if self.peek(idx)? == value { - Err(CalculatorError::CorruptStateChange(format!( - "Stack index {} should be {}, but is {}", - idx, - value, - self.peek(idx)?, - ))) - } else { - 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(()); + } - /// 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.peek(0)?; - 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.peek(0)?, self.peek(1)?]; - Ok(CalculatorStateChange { - pop: OpArgs::Binary(args), - push: op(args)?, - }) - } + 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 => { + let angle_mode = self.angle_mode; + self.unary_op(|a| Ok(OpArgs::Unary(a.sin(angle_mode)?))) + } + CalculatorOperation::Cos => { + let angle_mode = self.angle_mode; + self.unary_op(|a| Ok(OpArgs::Unary(a.cos(angle_mode)?))) + } + CalculatorOperation::Tan => { + let angle_mode = self.angle_mode; + self.unary_op(|a| Ok(OpArgs::Unary(a.tan(angle_mode)?))) + } + CalculatorOperation::ASin => { + let angle_mode = self.angle_mode; + self.unary_op(|a| Ok(OpArgs::Unary(a.asin(angle_mode)?))) + } + CalculatorOperation::ACos => { + let angle_mode = self.angle_mode; + self.unary_op(|a| Ok(OpArgs::Unary(a.acos(angle_mode)?))) + } + CalculatorOperation::ATan => { + let angle_mode = self.angle_mode; + self.unary_op(|a| Ok(OpArgs::Unary(a.atan(angle_mode)?))) + } + 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.clone(), 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.clone()); // TODO: Remove the clones + } + OpArgs::Binary([a, b]) => { + self.stack.push_front(b.clone()); // TODO: Remove the clones + self.stack.push_front(a.clone()); // TODO: Remove the clones + } + 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<()> { + if self.peek(idx)? == *value { + 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.peek(0)?; + Ok(CalculatorStateChange { + pop: OpArgs::Unary(arg.clone()), + 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.peek(0)?, self.peek(1)?]; + Ok(CalculatorStateChange { + pop: OpArgs::Binary(args.clone()), + push: op(args)?, + }) + } } // #[cfg(test)] diff --git a/src/calc/entries.rs b/src/calc/entries.rs index 667c429..4fde277 100644 --- a/src/calc/entries.rs +++ b/src/calc/entries.rs @@ -34,6 +34,7 @@ impl PartialEq for Number { // pub value: Vec, // } +// TODO: Remove the copy trait #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] #[serde(tag = "type")] pub enum Entry { @@ -194,61 +195,61 @@ impl CalculatorEntry for Number { } fn add(&self, arg: Entry) -> CalculatorResult { match arg { - Entry::Number(Number { value }) => Ok(Entry::Number(Number { + Entry::Number(Self { value }) => Ok(Entry::Number(Self { value: value + self.value, })), } } fn sub(&self, arg: Entry) -> CalculatorResult { match arg { - Entry::Number(Number { value }) => Ok(Entry::Number(Number { + Entry::Number(Self { value }) => Ok(Entry::Number(Self { value: value - self.value, })), } } fn mul(&self, arg: Entry) -> CalculatorResult { match arg { - Entry::Number(Number { value }) => Ok(Entry::Number(Number { + Entry::Number(Self { value }) => Ok(Entry::Number(Self { value: value * self.value, })), } } fn div(&self, arg: Entry) -> CalculatorResult { match arg { - Entry::Number(Number { value }) => Ok(Entry::Number(Number { + Entry::Number(Self { value }) => Ok(Entry::Number(Self { value: value / self.value, })), } } fn int_divide(&self, arg: Entry) -> CalculatorResult { match arg { - Entry::Number(Number { value }) => Ok(Entry::Number(Number { + Entry::Number(Self { value }) => Ok(Entry::Number(Self { value: value.div_euclid(self.value), })), } } fn negate(&self) -> CalculatorResult { - Ok(Entry::Number(Number { value: -self.value })) + Ok(Entry::Number(Self { value: -self.value })) } fn abs(&self) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: self.value.abs(), })) } fn inverse(&self) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: self.value.recip(), })) } fn modulo(&self, arg: Entry) -> CalculatorResult { match arg { - Entry::Number(Number { value }) => Ok(Entry::Number(Number { + Entry::Number(Self { value }) => Ok(Entry::Number(Self { value: value % self.value, })), } } fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: match angle_mode { CalculatorAngleMode::Degrees => self.value.to_radians().sin(), CalculatorAngleMode::Radians => self.value.sin(), @@ -259,7 +260,7 @@ impl CalculatorEntry for Number { })) } fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: match angle_mode { CalculatorAngleMode::Degrees => self.value.to_radians().cos(), CalculatorAngleMode::Radians => self.value.cos(), @@ -270,7 +271,7 @@ impl CalculatorEntry for Number { })) } fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: match angle_mode { CalculatorAngleMode::Degrees => self.value.to_radians().tan(), CalculatorAngleMode::Radians => self.value.tan(), @@ -281,7 +282,7 @@ impl CalculatorEntry for Number { })) } fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: match angle_mode { CalculatorAngleMode::Degrees => self.value.asin().to_degrees(), CalculatorAngleMode::Radians => self.value.asin(), @@ -292,7 +293,7 @@ impl CalculatorEntry for Number { })) } fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: match angle_mode { CalculatorAngleMode::Degrees => self.value.acos().to_degrees(), CalculatorAngleMode::Radians => self.value.acos(), @@ -303,7 +304,7 @@ impl CalculatorEntry for Number { })) } fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: match angle_mode { CalculatorAngleMode::Degrees => self.value.atan().to_degrees(), CalculatorAngleMode::Radians => self.value.atan(), @@ -314,23 +315,23 @@ impl CalculatorEntry for Number { })) } fn sqrt(&self) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: self.value.sqrt(), })) } fn log(&self) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: self.value.log10(), })) } fn ln(&self) -> CalculatorResult { - Ok(Entry::Number(Number { + Ok(Entry::Number(Self { value: self.value.ln(), })) } fn pow(&self, arg: Entry) -> CalculatorResult { match arg { - Entry::Number(Number { value }) => Ok(Entry::Number(Number { + Entry::Number(Self { value }) => Ok(Entry::Number(Self { value: value.powf(self.value), })), }