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 operations;
use confy::{load, store};
use constants::{
CalculatorAngleMode, CalculatorConstant, CalculatorConstants, CalculatorConstantsIter,
CalculatorDisplayMode, CalculatorMacro, CalculatorMacros, CalculatorMacrosIter,
CalculatorRegisters, CalculatorRegistersIter, CalculatorState, RegisterState,
CalculatorAngleMode, CalculatorConstants, CalculatorConstantsIter, CalculatorDisplayMode,
CalculatorMacros, CalculatorMacrosIter, CalculatorRegisters, CalculatorRegistersIter,
CalculatorSettings, CalculatorState, RegisterState,
};
use errors::{CalculatorError, CalculatorResult};
use operations::{CalculatorOperation, CalculatorStateChange, MacroState, OpArgs};
use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::collections::{HashSet, VecDeque};
const APP_NAME: &str = "rpn_rs";
#[derive(PartialEq, Debug, Serialize, Deserialize)]
enum HistoryMode {
One,
@ -24,122 +29,133 @@ impl CalculatorStateChange {
}
}
#[derive(Serialize, Deserialize)]
#[derive(Deserialize)]
pub struct Calculator {
#[serde(skip)]
l: String,
stack: VecDeque<f64>,
#[serde(serialize_with = "ordered_char_map")]
macros: CalculatorMacros,
#[serde(skip)]
active_macros: HashSet<char>,
constants: CalculatorConstants,
registers: CalculatorRegisters,
#[serde(skip)]
undo_buf: Vec<CalculatorStateChange>,
#[serde(skip)]
redo_buf: Vec<CalculatorStateChange>,
#[serde(skip)]
state: CalculatorState,
// TODO:
#[serde(skip)]
angle_mode: CalculatorAngleMode,
// TODO:
#[serde(skip)]
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 {
fn default() -> Calculator {
Calculator {
fn default() -> Self {
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(),
stack: vec![1000.0, 1200.32].into_iter().collect(),
state: CalculatorState::Normal,
undo_buf: vec![],
redo_buf: vec![],
active_macros: HashSet::new(),
registers: CalculatorRegisters::new(),
macros: [
(
'e',
CalculatorMacro {
help: String::from("Empty"),
value: String::from(""),
},
),
(
'1',
CalculatorMacro {
help: String::from("Push 1"),
value: String::from("1 "),
},
),
(
'0',
CalculatorMacro {
help: String::from("Push 0"),
value: String::from("0 "),
},
),
(
't',
CalculatorMacro {
help: String::from("Test"),
value: String::from("m1m0\\m1m1\\\\"),
},
),
(
'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*/",
),
},
),
(
's',
CalculatorMacro {
help: String::from("Sample data"),
value: String::from("\\\\2 5 3n"),
},
),
]
.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),
}
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> {
let settings = load(APP_NAME).map_err(|e| CalculatorError::LoadError(Some(e)))?;
Calculator::with_settings(settings)
}
pub fn save_config(&self) -> CalculatorResult<()> {
let settings = CalculatorSettings {
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)))
}
}
impl Calculator {
pub fn take_input(&mut self, c: char) -> CalculatorResult<()> {
//for c in input.chars() {
match &self.state {
CalculatorState::Normal => match c {
c @ '0'..='9' | c @ '.' | c @ 'e' => self.entry(c),

View File

@ -1,14 +1,15 @@
use serde::{Deserialize, Serialize};
use std::collections::hash_map::Iter;
use std::collections::HashMap;
use std::fmt;
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub enum RegisterState {
Save,
Load,
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub enum CalculatorState {
Normal,
WaitingForConstant,
@ -17,13 +18,21 @@ pub enum CalculatorState {
WaitingForSetting,
}
impl Default for CalculatorState {
fn default() -> Self {
CalculatorState::Normal
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "constant")]
pub struct CalculatorConstant {
pub help: String,
pub value: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "macro")]
pub struct CalculatorMacro {
pub help: String,
pub value: String,
@ -38,16 +47,154 @@ pub type CalculatorMacrosIter<'a> = Iter<'a, char, CalculatorMacro>;
pub type CalculatorRegisters = HashMap<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 {
Degrees,
Radians,
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 {
Default(Option<char>),
Scientific(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;
pub type CalculatorResult<T> = Result<T, CalculatorError>;
#[derive(Debug)]
pub enum CalculatorError {
ArithmeticError,
NotEnoughStackEntries,
@ -15,6 +17,8 @@ pub enum CalculatorError {
RecursiveMacro(char),
ParseError,
PrecisionTooHigh,
SaveError(Option<ConfyError>),
LoadError(Option<ConfyError>),
}
impl fmt::Display for CalculatorError {
@ -34,6 +38,10 @@ impl fmt::Display for CalculatorError {
CalculatorError::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c),
CalculatorError::ParseError => write!(f, "Parse error"),
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 util;
use calc::constants::{CalculatorAngleMode, CalculatorDisplayMode, CalculatorState, RegisterState};
use calc::constants::{CalculatorDisplayMode, CalculatorState, RegisterState};
use calc::errors::CalculatorResult;
use calc::Calculator;
use std::cmp;
@ -41,9 +41,13 @@ struct App {
impl Default for App {
fn default() -> App {
let (calculator, error_msg) = match Calculator::load_config() {
Ok(c) => (c, None),
Err(e) => (Calculator::default(), Some(format!("{}", e))),
};
App {
calculator: Calculator::default(),
error_msg: None,
calculator,
error_msg,
state: AppState::Calculator,
current_macro: None,
}
@ -81,20 +85,6 @@ fn main() -> Result<(), Box<dyn Error>> {
Span::styled(e, Style::default().add_modifier(Modifier::RAPID_BLINK)),
],
(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![
Span::raw("Press "),
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::raw(format!(
" 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') => {
return Ok(true);
}
Key::Ctrl('s') => {
app.calculator.save_config()?;
}
Key::Ctrl('l') => {
app.calculator = Calculator::load_config()?;
}
Key::Char('h') => {
app.state = AppState::Help;
}