From 3b81fc7d1b089571070f83e2220f7c431bddf44d Mon Sep 17 00:00:00 2001 From: Austen Adler Date: Mon, 3 May 2021 18:17:58 -0400 Subject: [PATCH] Start work on formatting --- src/calc.rs | 99 +++++++++++++++++++--- src/calc/constants.rs | 13 +++ src/calc/errors.rs | 2 + src/main.rs | 190 +++++++++++++++++++++++++++--------------- 4 files changed, 225 insertions(+), 79 deletions(-) diff --git a/src/calc.rs b/src/calc.rs index e951b8f..07487c1 100644 --- a/src/calc.rs +++ b/src/calc.rs @@ -3,9 +3,9 @@ pub mod errors; pub mod operations; use constants::{ - CalculatorConstant, CalculatorConstants, CalculatorConstantsIter, CalculatorMacro, - CalculatorMacros, CalculatorMacrosIter, CalculatorRegisters, CalculatorRegistersIter, - CalculatorState, RegisterState, + CalculatorAngleMode, CalculatorConstant, CalculatorConstants, CalculatorConstantsIter, + CalculatorDisplayMode, CalculatorMacro, CalculatorMacros, CalculatorMacrosIter, + CalculatorRegisters, CalculatorRegistersIter, CalculatorState, RegisterState, }; use errors::{CalculatorError, CalculatorResult}; use operations::{CalculatorOperation, CalculatorStateChange, MacroState, OpArgs}; @@ -33,13 +33,15 @@ pub struct Calculator<'a> { undo_buf: Vec, redo_buf: Vec, state: CalculatorState, + angle_mode: CalculatorAngleMode, + display_mode: CalculatorDisplayMode, } impl<'a> Default for Calculator<'a> { fn default() -> Calculator<'a> { Calculator { l: String::new(), - stack: vec![1.2, 1.3].into_iter().collect(), + stack: vec![1000.0, 1200.32].into_iter().collect(), state: CalculatorState::Normal, undo_buf: vec![], redo_buf: vec![], @@ -125,6 +127,8 @@ impl<'a> Default for Calculator<'a> { .iter() .cloned() .collect(), + angle_mode: CalculatorAngleMode::Degrees, + display_mode: CalculatorDisplayMode::Default(None), } } } @@ -175,6 +179,14 @@ impl<'a> Calculator<'a> { self.state = CalculatorState::WaitingForRegister(RegisterState::Save); Ok(()) } + '\t' => { + self.state = CalculatorState::WaitingForConstant; + Ok(()) + } + '@' => { + self.state = CalculatorState::WaitingForSetting; + Ok(()) + } _ => Err(CalculatorError::NoSuchOperator(c)), }, CalculatorState::WaitingForConstant => { @@ -184,7 +196,9 @@ impl<'a> Calculator<'a> { .ok_or(CalculatorError::NoSuchConstant(c))? .value; - self.push(f) + self.push(f)?; + self.state = CalculatorState::Normal; + Ok(()) } CalculatorState::WaitingForMacro => { let mac = *self.macros.get(&c).ok_or(CalculatorError::NoSuchMacro(c))?; @@ -237,6 +251,25 @@ impl<'a> Calculator<'a> { self.state = CalculatorState::Normal; Ok(()) } + CalculatorState::WaitingForSetting => { + match c { + 'd' => self.angle_mode = CalculatorAngleMode::Degrees, + 'r' => self.angle_mode = CalculatorAngleMode::Radians, + 'g' => self.angle_mode = CalculatorAngleMode::Grads, + '_' => self.display_mode = CalculatorDisplayMode::Default(None), + ',' => self.display_mode = CalculatorDisplayMode::Default(Some(',')), + ' ' => self.display_mode = CalculatorDisplayMode::Default(Some(' ')), + 's' => self.display_mode = CalculatorDisplayMode::Scientific(3), + 'S' => self.display_mode = CalculatorDisplayMode::Scientific(self.pop_usize()?), + 'e' => self.display_mode = CalculatorDisplayMode::Engineering(3), + 'E' => { + self.display_mode = CalculatorDisplayMode::Engineering(self.pop_usize()?) + } + _ => return Err(CalculatorError::NoSuchSetting(c)), + }; + self.state = CalculatorState::Normal; + Ok(()) + } } } @@ -312,6 +345,12 @@ impl<'a> Calculator<'a> { pub fn get_stack(&self) -> &VecDeque { &self.stack } + pub fn get_angle_mode(&self) -> &CalculatorAngleMode { + &self.angle_mode + } + pub fn get_display_mode(&self) -> &CalculatorDisplayMode { + &self.display_mode + } pub fn flush_l(&mut self) -> CalculatorResult { if self.l.is_empty() { @@ -343,7 +382,7 @@ impl<'a> Calculator<'a> { } pub fn pop_usize(&mut self) -> CalculatorResult { let f = self.checked_get(0)?; - let ret = usize::from(f as usize); + let ret = f as usize; self.direct_state_change(CalculatorStateChange { pop: OpArgs::Unary(f), push: OpArgs::None, @@ -373,12 +412,48 @@ impl<'a> Calculator<'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(|a| OpArgs::Unary(a.sin())), - CalculatorOperation::Cos => self.unary_op(|a| OpArgs::Unary(a.cos())), - CalculatorOperation::Tan => self.unary_op(|a| OpArgs::Unary(a.tan())), - CalculatorOperation::ASin => self.unary_op(|a| OpArgs::Unary(a.asin())), - CalculatorOperation::ACos => self.unary_op(|a| OpArgs::Unary(a.acos())), - CalculatorOperation::ATan => self.unary_op(|a| OpArgs::Unary(a.atan())), + 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::Factorial => self.unary_op(|a| OpArgs::Unary(a.())), CalculatorOperation::Log => self.unary_op(|a| OpArgs::Unary(a.log10())), diff --git a/src/calc/constants.rs b/src/calc/constants.rs index 932f584..18defe2 100644 --- a/src/calc/constants.rs +++ b/src/calc/constants.rs @@ -11,6 +11,7 @@ pub enum CalculatorState { WaitingForConstant, WaitingForMacro, WaitingForRegister(RegisterState), + WaitingForSetting, } #[derive(Debug, Clone, Copy)] @@ -33,3 +34,15 @@ pub type CalculatorMacrosIter<'a> = Iter<'a, char, CalculatorMacro<'a>>; pub type CalculatorRegisters = HashMap; pub type CalculatorRegistersIter<'a> = Iter<'a, char, f64>; + +pub enum CalculatorAngleMode { + Degrees, + Radians, + Grads, +} + +pub enum CalculatorDisplayMode { + Default(Option), + Scientific(usize), + Engineering(usize), +} diff --git a/src/calc/errors.rs b/src/calc/errors.rs index 3da452e..192204c 100644 --- a/src/calc/errors.rs +++ b/src/calc/errors.rs @@ -11,6 +11,7 @@ pub enum CalculatorError { NoSuchRegister(char), NoSuchMacro(char), NoSuchOperator(char), + NoSuchSetting(char), RecursiveMacro(char), ParseError, } @@ -28,6 +29,7 @@ impl fmt::Display for CalculatorError { CalculatorError::NoSuchConstant(c) => write!(f, "No such constant '{}'", c), CalculatorError::NoSuchRegister(c) => write!(f, "No such register '{}'", c), CalculatorError::NoSuchMacro(c) => write!(f, "No such macro '{}'", c), + CalculatorError::NoSuchSetting(c) => write!(f, "No such setting '{}'", c), CalculatorError::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c), CalculatorError::ParseError => write!(f, "Parse error"), } diff --git a/src/main.rs b/src/main.rs index 89907cc..cb8143c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ mod calc; mod util; -use calc::constants::{CalculatorState, RegisterState}; +use calc::constants::{CalculatorAngleMode, CalculatorDisplayMode, CalculatorState, RegisterState}; use calc::errors::CalculatorResult; use calc::Calculator; use std::cmp; @@ -27,18 +27,7 @@ struct Dimensions { height: u16, } -enum DisplayMode { - Default(Option), - Scientific(usize), - Engineering, -} - -struct AppSettings { - display_mode: DisplayMode, -} - enum AppState { - AppSettings, Calculator, Help, } @@ -48,7 +37,6 @@ struct App<'a> { error_msg: Option, state: AppState, current_macro: Option, - app_settings: AppSettings, } impl<'a> Default for App<'a> { @@ -58,9 +46,6 @@ impl<'a> Default for App<'a> { error_msg: None, state: AppState::Calculator, current_macro: None, - app_settings: AppSettings { - display_mode: DisplayMode::Default(None), - }, } } } @@ -97,11 +82,17 @@ fn main() -> Result<(), Box> { ], (None, AppState::Calculator) => { // TODO: There has to be a better way than making strings each time - let display_mode_str = match &app.app_settings.display_mode { - DisplayMode::Default(None) => String::from("[d]"), - DisplayMode::Default(Some(c)) => format!("[d({})]", c), - DisplayMode::Scientific(p) => format!("[s({})]", p), - DisplayMode::Engineering => String::from("[e]"), + let display_mode_str = match app.calculator.get_display_mode() { + CalculatorDisplayMode::Default(None) => String::from("DEF"), + CalculatorDisplayMode::Default(Some(c)) => format!("DEF({})", c), + CalculatorDisplayMode::Scientific(p) => format!("SCI({})", p), + CalculatorDisplayMode::Engineering(p) => format!("ENG({})", p), + }; + + let angle_mode_str = match app.calculator.get_angle_mode() { + CalculatorAngleMode::Degrees => String::from("DEG"), + CalculatorAngleMode::Radians => String::from("RAD"), + CalculatorAngleMode::Grads => String::from("GRD"), }; vec![ @@ -109,8 +100,10 @@ fn main() -> Result<(), Box> { Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to exit, "), Span::styled("h", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" for help - "), - Span::raw(display_mode_str), + Span::raw(format!( + " for help - [{}] [{}]", + display_mode_str, angle_mode_str + )), ] } (None, _) => vec![ @@ -133,11 +126,13 @@ fn main() -> Result<(), Box> { .rev() .map(|(i, m)| { let content = vec![Spans::from(Span::raw( - match &app.app_settings.display_mode { - DisplayMode::Default(None) => format!("{:>2}: {}", i, *m), - DisplayMode::Default(Some(c)) => fmt_separated(i, *m, *c), - DisplayMode::Scientific(precision) => fmt_scientific(i, *m, *precision), - DisplayMode::Engineering => { + match app.calculator.get_display_mode() { + CalculatorDisplayMode::Default(None) => format!("{:>2}: {}", i, *m), + CalculatorDisplayMode::Default(Some(c)) => fmt_separated(i, *m, *c), + CalculatorDisplayMode::Scientific(precision) => { + fmt_scientific(i, *m, *precision) + } + CalculatorDisplayMode::Engineering(_precision) => { format!("{:>2}: {}", i, m) } }, @@ -169,22 +164,6 @@ fn main() -> Result<(), Box> { ); match (&app.state, app.calculator.get_state()) { - (AppState::AppSettings, _) => { - draw_clippy_rect( - ClippyRectangle { - title: "App Settings", - msg: "\ - d => Default\n\ - , => Default (comma separated)\n\ - s => Scientific\n\ - S => Scientific (stack precision)\n\ - e => Engineering\n\ - E => Engineering (stack precision)\n\ - ", - }, - f, - ); - } (AppState::Help, _) => { draw_clippy_rect( ClippyRectangle { @@ -258,6 +237,24 @@ fn main() -> Result<(), Box> { f, ); } + (AppState::Calculator, CalculatorState::WaitingForSetting) => { + draw_clippy_rect( + ClippyRectangle { + title: "Help", + msg: "\ + d => Degrees\n\ + r => Radians\n\ + d => Default\n\ + , => Default (comma separated)\n\ + s => Scientific\n\ + S => Scientific (stack precision)\n\ + e => Engineering\n\ + E => Engineering (stack precision)\ + ", + }, + f, + ); + } _ => {} } })?; @@ -299,9 +296,6 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult { app.state = AppState::Help; } - Key::Ctrl('s') => { - app.state = AppState::AppSettings; - } Key::Char('\n') | Key::Char(' ') => { app.calculator.take_input(' ')?; } @@ -319,20 +313,6 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult {} }, - (AppState::AppSettings, _) => match key { - Key::Esc | Key::Char('q') => { - app.state = AppState::Calculator; - app.calculator.cancel()?; - } - Key::Char('d') => app.app_settings.display_mode = DisplayMode::Default(None), - Key::Char(',') => app.app_settings.display_mode = DisplayMode::Default(Some(',')), - Key::Char('s') => app.app_settings.display_mode = DisplayMode::Scientific(3), - Key::Char('S') => { - app.app_settings.display_mode = DisplayMode::Scientific(app.calculator.pop_usize()?) - } - Key::Char('e') => app.app_settings.display_mode = DisplayMode::Engineering, - _ => {} - }, (AppState::Help, _) => match key { Key::Esc | Key::Char('q') => { app.state = AppState::Calculator; @@ -341,6 +321,7 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult {} }, (AppState::Calculator, CalculatorState::WaitingForConstant) + | (AppState::Calculator, CalculatorState::WaitingForSetting) | (AppState::Calculator, CalculatorState::WaitingForRegister(_)) | (AppState::Calculator, CalculatorState::WaitingForMacro) => match key { Key::Esc => { @@ -407,16 +388,16 @@ fn draw_clippy_rect(c: ClippyRectangle, f: &mut Frame String { - let mut ret = format!("{:.precision$e}", f, precision = precision); - let exp = ret.split_off(ret.find('e').unwrap_or(0)); - let (pow_sign, exp) = if exp.starts_with("e-") { + let mut ret = format!("{:.precision$E}", f, precision = precision); + let exp = ret.split_off(ret.find('E').unwrap_or(0)); + let (pow_sign, exp) = if exp.starts_with("E-") { ('-', &exp[2..]) } else { ('+', &exp[1..]) }; let sign = if !ret.starts_with('-') { " " } else { "" }; format!( - "{:>2}: {}{}e{}{:0>pad$}", + "{:>2}: {}{}E{}{:0>pad$}", i, sign, ret, @@ -426,12 +407,87 @@ fn fmt_scientific(i: usize, f: f64, precision: usize) -> String { ) } +// // 100 E+3 +// fn fmt_engineering(i: usize, f: f64, precision: usize) -> String { +// let mut ret = format!(" {:.precision$e}", f, precision = precision + 2); +// // The length of ret will always be at least 5 -- 2 leading spaces, integer portion, and precision + 2 +// let exp = ret.split_off(ret.find('E').unwrap_or(0)); +// let left = ret[()..()] +// let first_three = match exp { +// } +// let (pow_sign, exp) = if exp.starts_with("E-") { +// ('-', &exp[2..]) +// } else { +// ('+', &exp[1..]) +// }; +// let sign = if !ret.starts_with('-') { " " } else { "" }; +// format!( +// "{:>2}: {}{}e{}{:0>pad$}", +// i, +// sign, +// ret, +// pow_sign, +// exp, +// pad = 2 +// ) +// } + fn fmt_separated(i: usize, 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(ret.len()); - for i in 0..((end - start).div_euclid(3)) { - ret.insert(end - start - (i + 1) * 3, sep); + for i in 0..((end - start - 1).div_euclid(3)) { + ret.insert(end - (i + 1) * 3, sep); } format!("{:>2}: {}", i, ret) } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_fmt_scientific() { + for (i, f, p, s) in vec![ + // Basic + (0, 1.0, 0, " 0: 1E+00"), + (0, -1.0, 0, " 0: -1E+00"), + (0, 100.0, 0, " 0: 1E+02"), + (0, 0.1, 0, " 0: 1E-01"), + (0, 0.01, 0, " 0: 1E-02"), + (0, -0.1, 0, " 0: -1E-01"), + // i + (22, 1.0, 0, "22: 1E+00"), + // Precision + (0, -0.123456789, 3, " 0: -1.235E-01"), + (0, -0.123456789, 2, " 0: -1.23E-01"), + (0, -0.123456789, 2, " 0: -1.23E-01"), + (0, -1e99, 2, " 0: -1.00E+99"), + (0, -1e100, 2, " 0: -1.00E+100"), + ] { + assert_eq!(fmt_scientific(i, f, p), s); + } + } + + #[test] + fn test_fmt_separated() { + for (i, f, c, s) in vec![ + (10, 100.0, ',', "10: 100"), + (0, 100.0, ',', " 0: 100"), + (0, -100.0, ',', " 0: -100"), + (0, 1_000.0, ',', " 0: 1,000"), + (0, -1_000.0, ',', " 0: -1,000"), + (0, 10_000.0, ',', " 0: 10,000"), + (0, -10_000.0, ',', " 0: -10,000"), + (0, 100_000.0, ',', " 0: 100,000"), + (0, -100_000.0, ',', " 0: -100,000"), + (0, 1_000_000.0, ',', " 0: 1,000,000"), + (0, -1_000_000.0, ',', " 0: -1,000,000"), + (0, 1_000_000.123456789, ',', " 0: 1,000,000.123456789"), + (0, -1_000_000.123456789, ',', " 0: -1,000,000.123456789"), + (0, 1_000_000.123456789, ' ', " 0: 1 000 000.123456789"), + (0, 1_000_000.123456789, ' ', " 0: 1 000 000.123456789"), + ] { + assert_eq!(fmt_separated(i, f, c), s); + } + } +}