Start work on formatting

This commit is contained in:
Austen Adler 2021-05-03 18:17:58 -04:00
parent 06c4ab6b55
commit 3b81fc7d1b
4 changed files with 225 additions and 79 deletions

View File

@ -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<CalculatorStateChange>,
redo_buf: Vec<CalculatorStateChange>,
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<f64> {
&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<bool> {
if self.l.is_empty() {
@ -343,7 +382,7 @@ impl<'a> Calculator<'a> {
}
pub fn pop_usize(&mut self) -> CalculatorResult<usize> {
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())),

View File

@ -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<char, f64>;
pub type CalculatorRegistersIter<'a> = Iter<'a, char, f64>;
pub enum CalculatorAngleMode {
Degrees,
Radians,
Grads,
}
pub enum CalculatorDisplayMode {
Default(Option<char>),
Scientific(usize),
Engineering(usize),
}

View File

@ -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"),
}

View File

@ -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<char>),
Scientific(usize),
Engineering,
}
struct AppSettings {
display_mode: DisplayMode,
}
enum AppState {
AppSettings,
Calculator,
Help,
}
@ -48,7 +37,6 @@ struct App<'a> {
error_msg: Option<String>,
state: AppState,
current_macro: Option<char>,
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<dyn Error>> {
],
(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<dyn Error>> {
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<dyn Error>> {
.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<dyn Error>> {
);
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<dyn Error>> {
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<bool
Key::Char('h') => {
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<bool
}
_ => {}
},
(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<bool
_ => {}
},
(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<T: std::io::Write>(c: ClippyRectangle, f: &mut Frame<Termion
// Based on https://stackoverflow.com/a/65266882
fn fmt_scientific(i: usize, 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 (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);
}
}
}