Start serializing

This commit is contained in:
Austen Adler 2021-05-04 22:14:39 -04:00
parent 13d4464ea2
commit 11fcae4db3
4 changed files with 285 additions and 117 deletions

View File

@ -2,16 +2,21 @@ pub mod constants;
pub mod errors; pub mod errors;
pub mod operations; pub mod operations;
use confy::{load, store};
use constants::{ use constants::{
CalculatorAngleMode, CalculatorConstant, CalculatorConstants, CalculatorConstantsIter, CalculatorAngleMode, CalculatorConstants, CalculatorConstantsIter, CalculatorDisplayMode,
CalculatorDisplayMode, CalculatorMacro, CalculatorMacros, CalculatorMacrosIter, CalculatorMacros, CalculatorMacrosIter, CalculatorRegisters, CalculatorRegistersIter,
CalculatorRegisters, CalculatorRegistersIter, CalculatorState, RegisterState, CalculatorSettings, 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::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::collections::{HashSet, VecDeque}; use std::collections::{HashSet, VecDeque};
const APP_NAME: &str = "rpn_rs";
#[derive(PartialEq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Debug, Serialize, Deserialize)]
enum HistoryMode { enum HistoryMode {
One, One,
@ -24,122 +29,133 @@ impl CalculatorStateChange {
} }
} }
#[derive(Serialize, Deserialize)] #[derive(Deserialize)]
pub struct Calculator { pub struct Calculator {
#[serde(skip)]
l: String, l: String,
stack: VecDeque<f64>, stack: VecDeque<f64>,
#[serde(serialize_with = "ordered_char_map")]
macros: CalculatorMacros, macros: CalculatorMacros,
#[serde(skip)]
active_macros: HashSet<char>, active_macros: HashSet<char>,
constants: CalculatorConstants, constants: CalculatorConstants,
registers: CalculatorRegisters, registers: CalculatorRegisters,
#[serde(skip)]
undo_buf: Vec<CalculatorStateChange>, undo_buf: Vec<CalculatorStateChange>,
#[serde(skip)]
redo_buf: Vec<CalculatorStateChange>, redo_buf: Vec<CalculatorStateChange>,
#[serde(skip)]
state: CalculatorState, state: CalculatorState,
// TODO:
#[serde(skip)]
angle_mode: CalculatorAngleMode, angle_mode: CalculatorAngleMode,
// TODO:
#[serde(skip)]
display_mode: CalculatorDisplayMode, display_mode: CalculatorDisplayMode,
} }
fn ordered_char_map<S, T>(value: &HashMap<char, T>, serializer: S) -> Result<S::Ok, S::Error>
where
T: Serialize,
S: Serializer,
{
// let ordered: HashMap<_, _> = value
// .iter()
// .map(|(key, t)| (String::from(*key), t.clone()))
// .collect();
let ordered: BTreeMap<_, _> = value
.iter()
.map(|(key, t)| (String::from(*key), t.clone()))
.collect();
ordered.serialize(serializer)
}
impl Default for Calculator { impl Default for Calculator {
fn default() -> Calculator { fn default() -> Self {
Calculator { Calculator::with_settings(CalculatorSettings::default())
.expect("Default calculator config is corrupt! Cannot start.")
}
}
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 {
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(), l: String::new(),
stack: vec![1000.0, 1200.32].into_iter().collect(),
state: CalculatorState::Normal, state: CalculatorState::Normal,
undo_buf: vec![], undo_buf: vec![],
redo_buf: vec![], redo_buf: vec![],
active_macros: HashSet::new(), active_macros: HashSet::new(),
registers: CalculatorRegisters::new(), registers: CalculatorRegisters::new(),
macros: [ stack: settings.stack.into(),
( macros: settings
'e', .macros
CalculatorMacro { .into_iter()
help: String::from("Empty"), .map(|(key, mac)| {
value: String::from(""), Ok((
}, key.chars()
), .next()
( .ok_or_else(|| CalculatorError::LoadError(None))?,
'1', mac,
CalculatorMacro { ))
help: String::from("Push 1"), })
value: String::from("1 "), .collect::<CalculatorResult<CalculatorMacros>>()?,
}, constants: settings
), .constants
( .into_iter()
'0', .map(|(key, constant)| {
CalculatorMacro { Ok((
help: String::from("Push 0"), key.chars()
value: String::from("0 "), .next()
}, .ok_or_else(|| CalculatorError::LoadError(None))?,
), constant,
( ))
't', })
CalculatorMacro { .collect::<CalculatorResult<CalculatorConstants>>()?,
help: String::from("Test"), angle_mode,
value: String::from("m1m0\\m1m1\\\\"), display_mode,
}, })
), }
(
'm', pub fn load_config() -> CalculatorResult<Calculator> {
CalculatorMacro { let settings = load(APP_NAME).map_err(|e| CalculatorError::LoadError(Some(e)))?;
help: String::from("<cr>64?>64%"), Calculator::with_settings(settings)
value: String::from(" 64?>64%"), }
}, pub fn save_config(&self) -> CalculatorResult<()> {
), let settings = CalculatorSettings {
( angle_mode: self.angle_mode.clone(),
'u', display_mode: self.display_mode.clone(),
CalculatorMacro { stack: self.stack.iter().map(|f| *f).collect(),
help: String::from("Quadratic Formula"), macros: self
value: String::from( .macros
"RcRbRarbnrb2 ^4 rarc**-v+2 ra*/rbnrb2^4 rarc**-v-2 ra*/", .iter()
), .map(|(key, mac)| (String::from(*key), mac.clone()))
}, .collect(),
), constants: self
( .constants
's', .iter()
CalculatorMacro { .map(|(key, constant)| (String::from(*key), constant.clone()))
help: String::from("Sample data"), .collect(),
value: String::from("\\\\2 5 3n"), };
}, store(APP_NAME, settings).map_err(|e| CalculatorError::SaveError(Some(e)))
),
]
.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::Degrees,
display_mode: CalculatorDisplayMode::Default(None),
}
} }
}
impl Calculator {
pub fn take_input(&mut self, c: char) -> CalculatorResult<()> { pub fn take_input(&mut self, c: char) -> CalculatorResult<()> {
//for c in input.chars() {
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),

View File

@ -1,14 +1,15 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::hash_map::Iter; use std::collections::hash_map::Iter;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt;
#[derive(Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum RegisterState { pub enum RegisterState {
Save, Save,
Load, Load,
} }
#[derive(Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum CalculatorState { pub enum CalculatorState {
Normal, Normal,
WaitingForConstant, WaitingForConstant,
@ -17,13 +18,21 @@ pub enum CalculatorState {
WaitingForSetting, WaitingForSetting,
} }
impl Default for CalculatorState {
fn default() -> Self {
CalculatorState::Normal
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "constant")]
pub struct CalculatorConstant { pub struct CalculatorConstant {
pub help: String, pub help: String,
pub value: f64, pub value: f64,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "macro")]
pub struct CalculatorMacro { pub struct CalculatorMacro {
pub help: String, pub help: String,
pub value: String, pub value: String,
@ -38,16 +47,154 @@ pub type CalculatorMacrosIter<'a> = Iter<'a, char, CalculatorMacro>;
pub type CalculatorRegisters = HashMap<char, f64>; pub type CalculatorRegisters = HashMap<char, f64>;
pub type CalculatorRegistersIter<'a> = Iter<'a, char, f64>; pub type CalculatorRegistersIter<'a> = Iter<'a, char, f64>;
#[derive(Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "angle_mode")]
pub enum CalculatorAngleMode { pub enum CalculatorAngleMode {
Degrees, Degrees,
Radians, Radians,
Grads, Grads,
} }
#[derive(Serialize, Deserialize)] impl fmt::Display for CalculatorAngleMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CalculatorAngleMode::Degrees => write!(f, "DEG"),
CalculatorAngleMode::Radians => write!(f, "RAD"),
CalculatorAngleMode::Grads => write!(f, "GRD"),
}
}
}
impl Default for CalculatorAngleMode {
fn default() -> Self {
CalculatorAngleMode::Degrees
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "display_mode")]
pub enum CalculatorDisplayMode { pub enum CalculatorDisplayMode {
Default(Option<char>), Default(Option<char>),
Scientific(usize), Scientific(usize),
Engineering(usize), Engineering(usize),
} }
impl fmt::Display for CalculatorDisplayMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CalculatorDisplayMode::Default(None) => write!(f, "DEF"),
CalculatorDisplayMode::Default(Some(c)) => write!(f, "DEF({})", c),
CalculatorDisplayMode::Scientific(p) => write!(f, "SCI({})", p),
CalculatorDisplayMode::Engineering(p) => write!(f, "ENG({})", p),
}
}
}
impl Default for CalculatorDisplayMode {
fn default() -> Self {
CalculatorDisplayMode::Default(None)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CalculatorSettings {
// Order matters here as macros/constants cannot come first
pub stack: Vec<f64>,
#[serde(skip)]
pub display_mode: CalculatorDisplayMode,
pub angle_mode: CalculatorAngleMode,
pub macros: HashMap<String, CalculatorMacro>,
pub constants: HashMap<String, CalculatorConstant>,
}
impl Default for CalculatorSettings {
fn default() -> CalculatorSettings {
CalculatorSettings {
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

@ -1,7 +1,9 @@
use confy::ConfyError;
use std::fmt; use std::fmt;
pub type CalculatorResult<T> = Result<T, CalculatorError>; pub type CalculatorResult<T> = Result<T, CalculatorError>;
#[derive(Debug)]
pub enum CalculatorError { pub enum CalculatorError {
ArithmeticError, ArithmeticError,
NotEnoughStackEntries, NotEnoughStackEntries,
@ -15,6 +17,8 @@ pub enum CalculatorError {
RecursiveMacro(char), RecursiveMacro(char),
ParseError, ParseError,
PrecisionTooHigh, PrecisionTooHigh,
SaveError(Option<ConfyError>),
LoadError(Option<ConfyError>),
} }
impl fmt::Display for CalculatorError { impl fmt::Display for CalculatorError {
@ -34,6 +38,10 @@ impl fmt::Display for CalculatorError {
CalculatorError::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c), CalculatorError::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c),
CalculatorError::ParseError => write!(f, "Parse error"), CalculatorError::ParseError => write!(f, "Parse error"),
CalculatorError::PrecisionTooHigh => write!(f, "Precision too high"), CalculatorError::PrecisionTooHigh => write!(f, "Precision too high"),
CalculatorError::SaveError(None) => write!(f, "Could not save"),
CalculatorError::SaveError(Some(e)) => write!(f, "Could not save: {}", e),
CalculatorError::LoadError(None) => write!(f, "Could not load"),
CalculatorError::LoadError(Some(e)) => write!(f, "Could not load: {}", e),
} }
} }
} }

View File

@ -4,7 +4,7 @@
mod calc; mod calc;
mod util; mod util;
use calc::constants::{CalculatorAngleMode, CalculatorDisplayMode, CalculatorState, RegisterState}; use calc::constants::{CalculatorDisplayMode, CalculatorState, RegisterState};
use calc::errors::CalculatorResult; use calc::errors::CalculatorResult;
use calc::Calculator; use calc::Calculator;
use std::cmp; use std::cmp;
@ -41,9 +41,13 @@ struct App {
impl Default for App { impl Default for App {
fn default() -> App { fn default() -> App {
let (calculator, error_msg) = match Calculator::load_config() {
Ok(c) => (c, None),
Err(e) => (Calculator::default(), Some(format!("{}", e))),
};
App { App {
calculator: Calculator::default(), calculator,
error_msg: None, error_msg,
state: AppState::Calculator, state: AppState::Calculator,
current_macro: None, current_macro: None,
} }
@ -81,20 +85,6 @@ fn main() -> Result<(), Box<dyn Error>> {
Span::styled(e, Style::default().add_modifier(Modifier::RAPID_BLINK)), Span::styled(e, Style::default().add_modifier(Modifier::RAPID_BLINK)),
], ],
(None, AppState::Calculator) => { (None, AppState::Calculator) => {
// TODO: There has to be a better way than making strings each time
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![ vec![
Span::raw("Press "), Span::raw("Press "),
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
@ -102,7 +92,8 @@ fn main() -> Result<(), Box<dyn Error>> {
Span::styled("h", Style::default().add_modifier(Modifier::BOLD)), Span::styled("h", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(format!( Span::raw(format!(
" for help - [{}] [{}]", " for help - [{}] [{}]",
display_mode_str, angle_mode_str app.calculator.get_display_mode(),
app.calculator.get_angle_mode()
)), )),
] ]
} }
@ -293,6 +284,12 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
Key::Char('q') => { Key::Char('q') => {
return Ok(true); return Ok(true);
} }
Key::Ctrl('s') => {
app.calculator.save_config()?;
}
Key::Ctrl('l') => {
app.calculator = Calculator::load_config()?;
}
Key::Char('h') => { Key::Char('h') => {
app.state = AppState::Help; app.state = AppState::Help;
} }