Add fixed

This commit is contained in:
Austen Adler 2021-05-05 00:10:58 -04:00
parent 11fcae4db3
commit 61fa6ee530
5 changed files with 123 additions and 213 deletions

View File

@ -4,18 +4,19 @@ pub mod operations;
use confy::{load, store}; use confy::{load, store};
use constants::{ use constants::{
CalculatorAngleMode, CalculatorConstants, CalculatorConstantsIter, CalculatorDisplayMode, CalculatorAngleMode, CalculatorConstant, CalculatorConstants, CalculatorConstantsIter,
CalculatorMacros, CalculatorMacrosIter, CalculatorRegisters, CalculatorRegistersIter, CalculatorDisplayMode, CalculatorMacro, CalculatorMacros, CalculatorMacrosIter,
CalculatorSettings, CalculatorState, RegisterState, CalculatorRegisters, CalculatorRegistersIter, CalculatorState, RegisterState,
}; };
use errors::{CalculatorError, CalculatorResult}; use errors::{CalculatorError, CalculatorResult};
use operations::{CalculatorOperation, CalculatorStateChange, MacroState, OpArgs}; use operations::{CalculatorOperation, CalculatorStateChange, MacroState, OpArgs};
use serde::ser::{SerializeStruct, Serializer}; use serde::ser::Serializer;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::collections::{HashSet, VecDeque}; use std::collections::{HashSet, VecDeque};
const APP_NAME: &str = "rpn_rs"; const APP_NAME: &str = "rpn_rs";
const DEFAULT_PRECISION: usize = 3;
#[derive(PartialEq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Debug, Serialize, Deserialize)]
enum HistoryMode { enum HistoryMode {
@ -29,7 +30,8 @@ impl CalculatorStateChange {
} }
} }
#[derive(Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct Calculator { pub struct Calculator {
#[serde(skip)] #[serde(skip)]
l: String, l: String,
@ -38,7 +40,9 @@ pub struct Calculator {
macros: CalculatorMacros, macros: CalculatorMacros,
#[serde(skip)] #[serde(skip)]
active_macros: HashSet<char>, active_macros: HashSet<char>,
#[serde(serialize_with = "ordered_char_map")]
constants: CalculatorConstants, constants: CalculatorConstants,
#[serde(skip)]
registers: CalculatorRegisters, registers: CalculatorRegisters,
#[serde(skip)] #[serde(skip)]
undo_buf: Vec<CalculatorStateChange>, undo_buf: Vec<CalculatorStateChange>,
@ -46,12 +50,9 @@ pub struct Calculator {
redo_buf: Vec<CalculatorStateChange>, redo_buf: Vec<CalculatorStateChange>,
#[serde(skip)] #[serde(skip)]
state: CalculatorState, state: CalculatorState,
// TODO:
#[serde(skip)]
angle_mode: CalculatorAngleMode, angle_mode: CalculatorAngleMode,
// TODO:
#[serde(skip)]
display_mode: CalculatorDisplayMode, display_mode: CalculatorDisplayMode,
// calculator_alignment: CalculatorAlignment,
} }
fn ordered_char_map<S, T>(value: &HashMap<char, T>, serializer: S) -> Result<S::Ok, S::Error> fn ordered_char_map<S, T>(value: &HashMap<char, T>, serializer: S) -> Result<S::Ok, S::Error>
@ -59,10 +60,7 @@ where
T: Serialize, T: Serialize,
S: Serializer, S: Serializer,
{ {
// let ordered: HashMap<_, _> = value // Convert chars to string for TOML map; insert into BTreeMap to be sorted
// .iter()
// .map(|(key, t)| (String::from(*key), t.clone()))
// .collect();
let ordered: BTreeMap<_, _> = value let ordered: BTreeMap<_, _> = value
.iter() .iter()
.map(|(key, t)| (String::from(*key), t.clone())) .map(|(key, t)| (String::from(*key), t.clone()))
@ -72,94 +70,87 @@ where
impl Default for Calculator { impl Default for Calculator {
fn default() -> Self { fn default() -> Self {
Calculator::with_settings(CalculatorSettings::default()) Calculator {
.expect("Default calculator config is corrupt! Cannot start.") 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(),
macros: [
(
'e',
CalculatorMacro {
help: String::from("Empty"),
value: String::from(""),
},
),
(
'm',
CalculatorMacro {
help: String::from("<cr>64?>64%"),
value: String::from(" 64?>64%"),
},
),
(
'u',
CalculatorMacro {
help: String::from("Quadratic Formula"),
value: String::from(
"RcRbRarbnrb2 ^4 rarc**-v+2 ra*/rbnrb2^4 rarc**-v-2 ra*/",
),
},
),
]
.iter()
.cloned()
.collect(),
constants: [
(
't',
CalculatorConstant {
help: String::from("Tau (2pi)"),
value: std::f64::consts::TAU,
},
),
(
'e',
CalculatorConstant {
help: String::from("Euler's Number e"),
value: std::f64::consts::E,
},
),
(
'p',
CalculatorConstant {
help: String::from("Pi"),
value: std::f64::consts::PI,
},
),
]
.iter()
.cloned()
.collect(),
angle_mode: CalculatorAngleMode::default(),
display_mode: CalculatorDisplayMode::default(),
// calculator_alignment: CalculatorAlignment::default(),
} }
} }
impl Serialize for Calculator {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("Calculator", 2)?;
s.serialize_field("l", &self.l)?;
s.serialize_field("state", &self.state)?;
// s.serialize_field("k", &self.registers)?; // map key was not a string
//s.serialize_field("t", &self.undo_buf)?; // unsupported Rust type
s.end()
}
} }
impl Calculator { impl Calculator {
pub fn with_settings(settings: CalculatorSettings) -> CalculatorResult<Calculator> {
let angle_mode = settings.angle_mode;
let display_mode = settings.display_mode;
Ok(Calculator {
l: String::new(),
state: CalculatorState::Normal,
undo_buf: vec![],
redo_buf: vec![],
active_macros: HashSet::new(),
registers: CalculatorRegisters::new(),
stack: settings.stack.into(),
macros: settings
.macros
.into_iter()
.map(|(key, mac)| {
Ok((
key.chars()
.next()
.ok_or_else(|| CalculatorError::LoadError(None))?,
mac,
))
})
.collect::<CalculatorResult<CalculatorMacros>>()?,
constants: settings
.constants
.into_iter()
.map(|(key, constant)| {
Ok((
key.chars()
.next()
.ok_or_else(|| CalculatorError::LoadError(None))?,
constant,
))
})
.collect::<CalculatorResult<CalculatorConstants>>()?,
angle_mode,
display_mode,
})
}
pub fn load_config() -> CalculatorResult<Calculator> { pub fn load_config() -> CalculatorResult<Calculator> {
let settings = load(APP_NAME).map_err(|e| CalculatorError::LoadError(Some(e)))?; load(APP_NAME).map_err(|e| CalculatorError::LoadError(Some(e)))
Calculator::with_settings(settings)
} }
pub fn save_config(&self) -> CalculatorResult<()> { pub fn save_config(&self) -> CalculatorResult<()> {
let settings = CalculatorSettings { store(APP_NAME, self).map_err(|e| CalculatorError::SaveError(Some(e)))
angle_mode: self.angle_mode.clone(),
display_mode: self.display_mode.clone(),
stack: self.stack.iter().map(|f| *f).collect(),
macros: self
.macros
.iter()
.map(|(key, mac)| (String::from(*key), mac.clone()))
.collect(),
constants: self
.constants
.iter()
.map(|(key, constant)| (String::from(*key), constant.clone()))
.collect(),
};
store(APP_NAME, settings).map_err(|e| CalculatorError::SaveError(Some(e)))
} }
pub fn take_input(&mut self, c: char) -> CalculatorResult<()> { pub fn take_input(&mut self, c: char) -> CalculatorResult<()> {
match &self.state { match &self.state {
CalculatorState::Normal => match c { CalculatorState::Normal => match c {
c @ '0'..='9' | c @ '.' | c @ 'e' => self.entry(c), c @ '0'..='9' | c @ '.' | c @ 'e' => self.entry(c),
//'!' => self.op(CalculatorOperation::Factorial),
'+' => self.op(CalculatorOperation::Add), '+' => self.op(CalculatorOperation::Add),
'-' => self.op(CalculatorOperation::Subtract), '-' => self.op(CalculatorOperation::Subtract),
'*' => self.op(CalculatorOperation::Multiply), '*' => self.op(CalculatorOperation::Multiply),
@ -182,7 +173,6 @@ impl Calculator {
'^' => self.op(CalculatorOperation::Pow), '^' => self.op(CalculatorOperation::Pow),
'l' => self.op(CalculatorOperation::Log), 'l' => self.op(CalculatorOperation::Log),
'L' => self.op(CalculatorOperation::Ln), 'L' => self.op(CalculatorOperation::Ln),
//'e' => self.op(CalculatorOperation::E),
// Special // Special
'u' => self.op(CalculatorOperation::Undo), 'u' => self.op(CalculatorOperation::Undo),
'U' => self.op(CalculatorOperation::Redo), 'U' => self.op(CalculatorOperation::Redo),
@ -277,10 +267,10 @@ impl Calculator {
'd' => self.angle_mode = CalculatorAngleMode::Degrees, 'd' => self.angle_mode = CalculatorAngleMode::Degrees,
'r' => self.angle_mode = CalculatorAngleMode::Radians, 'r' => self.angle_mode = CalculatorAngleMode::Radians,
'g' => self.angle_mode = CalculatorAngleMode::Grads, 'g' => self.angle_mode = CalculatorAngleMode::Grads,
'_' => self.display_mode = CalculatorDisplayMode::Default(None), '_' => self.display_mode = CalculatorDisplayMode::Default,
',' => self.display_mode = CalculatorDisplayMode::Default(Some(',')), ',' => self.display_mode = CalculatorDisplayMode::Separated(','),
' ' => self.display_mode = CalculatorDisplayMode::Default(Some(' ')), ' ' => self.display_mode = CalculatorDisplayMode::Separated(' '),
's' => self.display_mode = CalculatorDisplayMode::Scientific(3), 's' => self.display_mode = CalculatorDisplayMode::Scientific(DEFAULT_PRECISION),
'S' => { 'S' => {
let precision = self.checked_get(0)? as usize; let precision = self.checked_get(0)? as usize;
if precision >= 20 { if precision >= 20 {
@ -288,7 +278,9 @@ impl Calculator {
} }
self.display_mode = CalculatorDisplayMode::Scientific(self.pop_usize()?) self.display_mode = CalculatorDisplayMode::Scientific(self.pop_usize()?)
} }
'e' => self.display_mode = CalculatorDisplayMode::Engineering(3), 'e' => {
self.display_mode = CalculatorDisplayMode::Engineering(DEFAULT_PRECISION)
}
'E' => { 'E' => {
let precision = self.checked_get(0)? as usize; let precision = self.checked_get(0)? as usize;
if precision >= 20 { if precision >= 20 {
@ -296,6 +288,8 @@ impl Calculator {
} }
self.display_mode = CalculatorDisplayMode::Engineering(self.pop_usize()?) self.display_mode = CalculatorDisplayMode::Engineering(self.pop_usize()?)
} }
'f' => self.display_mode = CalculatorDisplayMode::Fixed(DEFAULT_PRECISION),
'F' => self.display_mode = CalculatorDisplayMode::Fixed(self.pop_usize()?),
_ => return Err(CalculatorError::NoSuchSetting(c)), _ => return Err(CalculatorError::NoSuchSetting(c)),
}; };
self.state = CalculatorState::Normal; self.state = CalculatorState::Normal;
@ -486,7 +480,6 @@ impl Calculator {
} }
}), }),
CalculatorOperation::Sqrt => self.unary_op(|a| OpArgs::Unary(a.sqrt())), 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())), CalculatorOperation::Log => self.unary_op(|a| OpArgs::Unary(a.log10())),
CalculatorOperation::Ln => self.unary_op(|a| OpArgs::Unary(a.ln())), CalculatorOperation::Ln => self.unary_op(|a| OpArgs::Unary(a.ln())),
CalculatorOperation::Pow => self.binary_op(|[a, b]| OpArgs::Unary(b.powf(a))), CalculatorOperation::Pow => self.binary_op(|[a, b]| OpArgs::Unary(b.powf(a))),
@ -555,7 +548,6 @@ impl Calculator {
} else { } else {
MacroState::Start MacroState::Start
}); });
// println!("{:?} {:?}", s, history_mode);
self.apply_state_change(s, forward)?; self.apply_state_change(s, forward)?;
if history_mode == HistoryMode::One || macro_end { if history_mode == HistoryMode::One || macro_end {

View File

@ -55,6 +55,12 @@ pub enum CalculatorAngleMode {
Grads, Grads,
} }
impl Default for CalculatorAngleMode {
fn default() -> Self {
CalculatorAngleMode::Degrees
}
}
impl fmt::Display for CalculatorAngleMode { impl fmt::Display for CalculatorAngleMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
@ -65,136 +71,43 @@ impl fmt::Display for CalculatorAngleMode {
} }
} }
impl Default for CalculatorAngleMode {
fn default() -> Self {
CalculatorAngleMode::Degrees
}
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "display_mode")] #[serde(tag = "display_mode")]
pub enum CalculatorDisplayMode { pub enum CalculatorDisplayMode {
Default(Option<char>), Default,
Separated(char),
Scientific(usize), Scientific(usize),
Engineering(usize), Engineering(usize),
Fixed(usize),
} }
impl fmt::Display for CalculatorDisplayMode { impl fmt::Display for CalculatorDisplayMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
CalculatorDisplayMode::Default(None) => write!(f, "DEF"), CalculatorDisplayMode::Default => write!(f, "DEF"),
CalculatorDisplayMode::Default(Some(c)) => write!(f, "DEF({})", c), CalculatorDisplayMode::Separated(c) => write!(f, "DEF({})", c),
CalculatorDisplayMode::Scientific(p) => write!(f, "SCI({})", p), CalculatorDisplayMode::Scientific(p) => write!(f, "SCI({})", p),
CalculatorDisplayMode::Engineering(p) => write!(f, "ENG({})", p), CalculatorDisplayMode::Engineering(p) => write!(f, "ENG({})", p),
CalculatorDisplayMode::Fixed(p) => write!(f, "FIX({})", p),
} }
} }
} }
impl Default for CalculatorDisplayMode { impl Default for CalculatorDisplayMode {
fn default() -> Self { fn default() -> Self {
CalculatorDisplayMode::Default(None) CalculatorDisplayMode::Default
} }
} }
#[derive(Debug, Serialize, Deserialize)] // #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CalculatorSettings { // #[serde(tag = "calculator_alignment")]
// Order matters here as macros/constants cannot come first // pub enum CalculatorAlignment {
pub stack: Vec<f64>, // Right,
#[serde(skip)] // Left,
pub display_mode: CalculatorDisplayMode, // }
pub angle_mode: CalculatorAngleMode,
pub macros: HashMap<String, CalculatorMacro>,
pub constants: HashMap<String, CalculatorConstant>,
}
impl Default for CalculatorSettings { // impl Default for CalculatorAlignment {
fn default() -> CalculatorSettings { // fn default() -> Self {
CalculatorSettings { // CalculatorAlignment::Left
stack: vec![1.0, 2.0].into_iter().collect(), // }
macros: [ // }
(
String::from('e'),
CalculatorMacro {
help: String::from("Empty"),
value: String::from(""),
},
),
(
String::from('1'),
CalculatorMacro {
help: String::from("Push 1"),
value: String::from("1 "),
},
),
(
String::from('0'),
CalculatorMacro {
help: String::from("Push 0"),
value: String::from("0 "),
},
),
(
String::from('t'),
CalculatorMacro {
help: String::from("Test"),
value: String::from("m1m0\\m1m1\\\\"),
},
),
(
String::from('m'),
CalculatorMacro {
help: String::from("<cr>64?>64%"),
value: String::from(" 64?>64%"),
},
),
(
String::from('u'),
CalculatorMacro {
help: String::from("Quadratic Formula"),
value: String::from(
"RcRbRarbnrb2 ^4 rarc**-v+2 ra*/rbnrb2^4 rarc**-v-2 ra*/",
),
},
),
(
String::from('s'),
CalculatorMacro {
help: String::from("Sample data"),
value: String::from("\\\\2 5 3n"),
},
),
]
.iter()
.cloned()
.collect(),
constants: [
(
String::from('t'),
CalculatorConstant {
help: String::from("Tau (2pi)"),
value: std::f64::consts::TAU,
},
),
(
String::from('e'),
CalculatorConstant {
help: String::from("Euler's Number e"),
value: std::f64::consts::E,
},
),
(
String::from('p'),
CalculatorConstant {
help: String::from("Pi"),
value: std::f64::consts::PI,
},
),
]
.iter()
.cloned()
.collect(),
angle_mode: CalculatorAngleMode::Degrees,
display_mode: CalculatorDisplayMode::Default(None),
}
}
}

View File

@ -40,7 +40,7 @@ struct App {
} }
impl Default for App { impl Default for App {
fn default() -> App { fn default() -> Self {
let (calculator, error_msg) = match Calculator::load_config() { let (calculator, error_msg) = match Calculator::load_config() {
Ok(c) => (c, None), Ok(c) => (c, None),
Err(e) => (Calculator::default(), Some(format!("{}", e))), Err(e) => (Calculator::default(), Some(format!("{}", e))),
@ -118,14 +118,17 @@ fn main() -> Result<(), Box<dyn Error>> {
.map(|(i, m)| { .map(|(i, m)| {
let content = vec![Spans::from(Span::raw( let content = vec![Spans::from(Span::raw(
match app.calculator.get_display_mode() { match app.calculator.get_display_mode() {
CalculatorDisplayMode::Default(None) => format!("{:>2}: {}", i, *m), CalculatorDisplayMode::Default => format!("{:>2}: {}", i, *m),
CalculatorDisplayMode::Default(Some(c)) => fmt_separated(i, *m, *c), CalculatorDisplayMode::Separated(c) => fmt_separated(i, *m, *c),
CalculatorDisplayMode::Scientific(precision) => { CalculatorDisplayMode::Scientific(precision) => {
fmt_scientific(i, *m, *precision) fmt_scientific(i, *m, *precision)
} }
CalculatorDisplayMode::Engineering(_precision) => { CalculatorDisplayMode::Engineering(_precision) => {
format!("{:>2}: {}", i, m) format!("{:>2}: {}", i, m)
} }
CalculatorDisplayMode::Fixed(precision) => {
format!("{:>2}: {:.precision$}", i, m, precision = precision)
}
}, },
))]; ))];
ListItem::new(content) ListItem::new(content)
@ -240,7 +243,9 @@ fn main() -> Result<(), Box<dyn Error>> {
s => Scientific\n\ s => Scientific\n\
S => Scientific (stack precision)\n\ S => Scientific (stack precision)\n\
e => Engineering\n\ e => Engineering\n\
E => Engineering (stack precision)\ E => Engineering (stack precision)\n\
f => Engineering\n\
F => Engineering (stack precision)\
", ",
}, },
f, f,

View File

@ -42,7 +42,7 @@ struct App {
} }
impl Default for App { impl Default for App {
fn default() -> App { fn default() -> Self {
App { App {
input: String::new(), input: String::new(),
input_mode: InputMode::Normal, input_mode: InputMode::Normal,

View File

@ -35,7 +35,7 @@ pub struct Config {
} }
impl Default for Config { impl Default for Config {
fn default() -> Config { fn default() -> Self {
Config { Config {
exit_key: Key::Char('q'), exit_key: Key::Char('q'),
tick_rate: Duration::from_millis(250), tick_rate: Duration::from_millis(250),