Refactor ui code

This commit is contained in:
Austen Adler 2021-04-28 00:48:40 -04:00
parent 5772147ef1
commit 4600f5d31e
5 changed files with 312 additions and 145 deletions

View File

@ -5,10 +5,11 @@ pub mod operations;
use constants::{ use constants::{
CalculatorConstant, CalculatorConstants, CalculatorConstantsIter, CalculatorMacro, CalculatorConstant, CalculatorConstants, CalculatorConstantsIter, CalculatorMacro,
CalculatorMacros, CalculatorMacrosIter, CalculatorRegisters, CalculatorRegistersIter, CalculatorMacros, CalculatorMacrosIter, CalculatorRegisters, CalculatorRegistersIter,
CalculatorState, RegisterState,
}; };
use errors::{CalculatorError, CalculatorResult}; use errors::{CalculatorError, CalculatorResult};
use operations::CalculatorOperation; use operations::CalculatorOperation;
use std::collections::VecDeque; use std::collections::{HashSet, VecDeque};
enum OpArgs { enum OpArgs {
Unary(f64), Unary(f64),
@ -22,20 +23,26 @@ struct CalculatorStateChange {
} }
pub struct Calculator<'a> { pub struct Calculator<'a> {
l: String,
stack: VecDeque<f64>, stack: VecDeque<f64>,
macros: CalculatorMacros<'a>, macros: CalculatorMacros<'a>,
active_macros: HashSet<char>,
constants: CalculatorConstants<'a>, constants: CalculatorConstants<'a>,
registers: CalculatorRegisters, registers: CalculatorRegisters,
undo_buf: Vec<CalculatorStateChange>, undo_buf: Vec<CalculatorStateChange>,
redo_buf: Vec<CalculatorStateChange>, redo_buf: Vec<CalculatorStateChange>,
state: CalculatorState,
} }
impl<'a> Default for Calculator<'a> { impl<'a> Default for Calculator<'a> {
fn default() -> Calculator<'a> { fn default() -> Calculator<'a> {
Calculator { Calculator {
l: String::new(),
stack: vec![1.2, 1.3].into_iter().collect(), stack: vec![1.2, 1.3].into_iter().collect(),
state: CalculatorState::Normal,
undo_buf: vec![], undo_buf: vec![],
redo_buf: vec![], redo_buf: vec![],
active_macros: HashSet::new(),
registers: CalculatorRegisters::new(), registers: CalculatorRegisters::new(),
macros: [ macros: [
( (
@ -94,6 +101,150 @@ impl<'a> Default for Calculator<'a> {
} }
impl<'a> Calculator<'a> { impl<'a> Calculator<'a> {
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),
//'!' => self.op(CalculatorOperation::Factorial),
'+' => self.op(CalculatorOperation::Add),
'-' => self.op(CalculatorOperation::Subtract),
'*' => self.op(CalculatorOperation::Multiply),
'/' => self.op(CalculatorOperation::Divide),
'n' => self.op(CalculatorOperation::Negate),
'|' => self.op(CalculatorOperation::AbsoluteValue),
'i' => self.op(CalculatorOperation::Inverse),
'%' => self.op(CalculatorOperation::Modulo),
'\\' => self.op(CalculatorOperation::Drop),
'?' => self.op(CalculatorOperation::IntegerDivide),
' ' => self.op(CalculatorOperation::Dup),
'>' => self.op(CalculatorOperation::Swap),
's' => self.op(CalculatorOperation::Sin),
'c' => self.op(CalculatorOperation::Cos),
't' => self.op(CalculatorOperation::Tan),
'S' => self.op(CalculatorOperation::ASin),
'C' => self.op(CalculatorOperation::ACos),
'T' => self.op(CalculatorOperation::ATan),
'v' => self.op(CalculatorOperation::Sqrt),
'^' => self.op(CalculatorOperation::Pow),
'l' => self.op(CalculatorOperation::Log),
'L' => self.op(CalculatorOperation::Ln),
//'e' => self.op(CalculatorOperation::E),
// Special
'u' => self.op(CalculatorOperation::Undo),
'U' => self.op(CalculatorOperation::Redo),
// State modifiers
'm' => {
self.state = CalculatorState::WaitingForMacro;
Ok(())
}
'r' => {
self.state = CalculatorState::WaitingForRegister(RegisterState::Load);
Ok(())
}
'R' => {
self.state = CalculatorState::WaitingForRegister(RegisterState::Load);
Ok(())
}
_ => {
return Err(CalculatorError::NoSuchOperator(c));
}
},
CalculatorState::WaitingForConstant => {
let f = self
.constants
.get(&c)
.ok_or(CalculatorError::NoSuchConstant(c))?
.value;
self.push(f)
}
CalculatorState::WaitingForMacro => {
let value = self
.macros
.get(&c)
.ok_or(CalculatorError::NoSuchMacro(c))?
.value;
if self.active_macros.contains(&c) {
return Err(CalculatorError::RecursiveMacro(c));
}
// The macro needs to run in normal mode
self.state = CalculatorState::Normal;
// Record that we are running macro c
self.active_macros.insert(c);
for c in value.chars() {
self.take_input(c).or_else(|e| {
self.cancel();
Err(e)
})?;
}
// Macro c should be over now
self.active_macros.remove(&c);
Ok(())
// self.push(value)
}
CalculatorState::WaitingForRegister(register_state) => {
match register_state {
RegisterState::Save => {
let f = self.pop()?;
self.registers.insert(c, f);
}
RegisterState::Load => {
let f = self
.registers
.get(&c)
.ok_or(CalculatorError::NoSuchRegister(c))?;
let f = *f;
self.push(f)?;
}
}
self.state = CalculatorState::Normal;
Ok(())
}
}
//}
//Ok(())
}
pub fn cancel(&mut self) {
self.state = CalculatorState::Normal;
self.active_macros.clear();
}
fn entry(&mut self, c: char) -> CalculatorResult<()> {
match c {
'0'..='9' => {
self.l.push(c);
Ok(())
}
'e' => {
if self.l.is_empty() {
let f = self.pop().or(Err(CalculatorError::NotEnoughStackEntries))?;
self.l = f.to_string();
}
if !self.l.contains('e') {
self.l.push('e');
}
Ok(())
}
'.' => {
if !self.l.contains('.') && !self.l.contains('e') {
self.l.push('.');
}
Ok(())
}
_ => Err(CalculatorError::ParseError),
}
}
pub fn get_constants_iter(&'a self) -> CalculatorConstantsIter<'a> { pub fn get_constants_iter(&'a self) -> CalculatorConstantsIter<'a> {
self.constants.iter() self.constants.iter()
} }
@ -110,31 +261,40 @@ impl<'a> Calculator<'a> {
let value = *value; let value = *value;
self.push(value) self.push(value)
} }
None => Err(CalculatorError::NoSuchConstant), None => Err(CalculatorError::NoSuchConstant(key)),
} }
} }
pub fn push_register(&mut self, key: char) -> CalculatorResult<()> { // pub fn push_register(&mut self, key: char) -> CalculatorResult<()> {
match self.registers.get(&key) { // match self.registers.get(&key) {
Some(f) => { // Some(f) => {
let f = *f; // let f = *f;
self.push(f) // self.push(f)
} // }
None => Err(CalculatorError::NoSuchRegister), // None => Err(CalculatorError::NoSuchRegister),
} // }
} // }
// TODO: Use hashmap // pub fn save_register(&mut self, key: char) -> CalculatorResult<()> {
pub fn save_register(&mut self, key: char) -> CalculatorResult<()> { // let f = self.pop()?;
let f = self.pop()?; // self.registers.insert(key, f);
self.registers.insert(key, f); // Ok(())
Ok(()) // }
}
pub fn get_macro(&mut self, key: char) -> Result<&CalculatorMacro<'a>, CalculatorError> { pub fn get_macro(&mut self, key: char) -> Result<&CalculatorMacro<'a>, CalculatorError> {
match self.macros.get(&key) { match self.macros.get(&key) {
Some(m) => Ok(m), Some(m) => Ok(m),
None => Err(CalculatorError::NoSuchMacro), None => Err(CalculatorError::NoSuchMacro(key)),
} }
} }
pub fn flush_l(&mut self) -> CalculatorResult<bool> {
if self.l.is_empty() {
Ok(false)
} else {
let f = self.l.parse::<f64>().or(Err(CalculatorError::ParseError))?;
self.push(f)?;
self.l.clear();
Ok(true)
}
}
pub fn push(&mut self, f: f64) -> CalculatorResult<()> { pub fn push(&mut self, f: f64) -> CalculatorResult<()> {
self.direct_state_change(CalculatorStateChange { self.direct_state_change(CalculatorStateChange {
pop: OpArgs::None, pop: OpArgs::None,
@ -154,6 +314,12 @@ impl<'a> Calculator<'a> {
} }
//TODO: VecDeque could have other types //TODO: VecDeque could have other types
pub fn op(&mut self, op: CalculatorOperation) -> CalculatorResult<()> { pub fn op(&mut self, op: CalculatorOperation) -> CalculatorResult<()> {
// Dup is special -- don't actually run it if l needs to be flushed
if self.flush_l()? {
if let CalculatorOperation::Dup = op {
return Ok(());
}
}
let state_change = match op { let state_change = match op {
CalculatorOperation::Add => self.binary_op(|[a, b]| OpArgs::Unary(b + a)), CalculatorOperation::Add => self.binary_op(|[a, b]| OpArgs::Unary(b + a)),
CalculatorOperation::Subtract => self.binary_op(|[a, b]| OpArgs::Unary(b - a)), CalculatorOperation::Subtract => self.binary_op(|[a, b]| OpArgs::Unary(b - a)),
@ -198,6 +364,10 @@ impl<'a> Calculator<'a> {
.ok_or_else(|| CalculatorError::EmptyHistory(String::from("redo")))?; .ok_or_else(|| CalculatorError::EmptyHistory(String::from("redo")))?;
return self.apply_state_change(s, true); return self.apply_state_change(s, true);
} }
// TODO: This should not happen -- need to pull all macro/register accesses into their own enum
CalculatorOperation::Macro(_) => {
return Ok(());
}
}; };
self.direct_state_change(state_change?) self.direct_state_change(state_change?)

View File

@ -1,6 +1,19 @@
use std::collections::hash_map::Iter; use std::collections::hash_map::Iter;
use std::collections::HashMap; use std::collections::HashMap;
pub enum RegisterState {
Save,
Load,
}
pub enum CalculatorState {
Normal,
//Macro,
WaitingForConstant,
WaitingForMacro,
WaitingForRegister(RegisterState),
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct CalculatorConstant<'a> { pub struct CalculatorConstant<'a> {
pub help: &'a str, pub help: &'a str,

View File

@ -7,11 +7,12 @@ pub enum CalculatorError {
NotEnoughStackEntries, NotEnoughStackEntries,
CorruptStateChange(String), CorruptStateChange(String),
EmptyHistory(String), EmptyHistory(String),
NoSuchConstant, NoSuchConstant(char),
NoSuchRegister, NoSuchRegister(char),
NoSuchMacro, NoSuchMacro(char),
NoSuchOperator(char),
RecursiveMacro(char),
ParseError, ParseError,
NoSuchOperator,
} }
impl fmt::Display for CalculatorError { impl fmt::Display for CalculatorError {
@ -23,10 +24,11 @@ impl fmt::Display for CalculatorError {
write!(f, "Corrupt state change: {}", msg) write!(f, "Corrupt state change: {}", msg)
} }
CalculatorError::EmptyHistory(msg) => write!(f, "No history to {}", msg), CalculatorError::EmptyHistory(msg) => write!(f, "No history to {}", msg),
CalculatorError::NoSuchOperator => write!(f, "No such operator"), CalculatorError::NoSuchOperator(c) => write!(f, "No such operator '{}'", c),
CalculatorError::NoSuchConstant => write!(f, "No such constant"), CalculatorError::NoSuchConstant(c) => write!(f, "No such constant '{}'", c),
CalculatorError::NoSuchRegister => write!(f, "No such register"), CalculatorError::NoSuchRegister(c) => write!(f, "No such register '{}'", c),
CalculatorError::NoSuchMacro => write!(f, "No such macro"), CalculatorError::NoSuchMacro(c) => write!(f, "No such macro '{}'", c),
CalculatorError::RecursiveMacro(c) => write!(f, "Recursive macro '{}'", c),
CalculatorError::ParseError => write!(f, "Parse error"), CalculatorError::ParseError => write!(f, "Parse error"),
} }
} }

View File

@ -1,6 +1,7 @@
use super::errors::{CalculatorError, CalculatorResult}; use super::constants::CalculatorMacro;
// use super::errors::{CalculatorError, CalculatorResult};
pub enum CalculatorOperation { pub enum CalculatorOperation<'a> {
Add, Add,
Subtract, Subtract,
Multiply, Multiply,
@ -28,77 +29,51 @@ pub enum CalculatorOperation {
Log, Log,
Ln, Ln,
E, E,
Macro(CalculatorMacro<'a>),
} }
impl CalculatorOperation { // impl CalculatorOperation<'_> {
pub fn from_char(key: char) -> CalculatorResult<CalculatorOperation> { // pub fn from_char<'a>(key: char) -> CalculatorResult<CalculatorOperation<'a>> {
match key { // match key {
'+' => Ok(CalculatorOperation::Add), // _ => Err(CalculatorError::NoSuchOperator),
'-' => Ok(CalculatorOperation::Subtract), // }
'*' => Ok(CalculatorOperation::Multiply), // }
'/' => Ok(CalculatorOperation::Divide),
'n' => Ok(CalculatorOperation::Negate),
'|' => Ok(CalculatorOperation::AbsoluteValue),
'i' => Ok(CalculatorOperation::Inverse),
'%' => Ok(CalculatorOperation::Modulo),
//'r' => Ok(CalculatorOperation::Remainder),
'\\' => Ok(CalculatorOperation::Drop),
'?' => Ok(CalculatorOperation::IntegerDivide),
'\n' => Ok(CalculatorOperation::Dup),
'>' => Ok(CalculatorOperation::Swap),
's' => Ok(CalculatorOperation::Sin),
'c' => Ok(CalculatorOperation::Cos),
't' => Ok(CalculatorOperation::Tan),
'S' => Ok(CalculatorOperation::ASin),
'C' => Ok(CalculatorOperation::ACos),
'T' => Ok(CalculatorOperation::ATan),
'v' => Ok(CalculatorOperation::Sqrt),
//TODO: Should not be calculator states probably
'u' => Ok(CalculatorOperation::Undo),
'U' => Ok(CalculatorOperation::Redo),
'^' => Ok(CalculatorOperation::Pow),
//'!' => Ok(CalculatorOperation::Factorial),
'l' => Ok(CalculatorOperation::Log),
'L' => Ok(CalculatorOperation::Ln),
'e' => Ok(CalculatorOperation::E),
_ => Err(CalculatorError::NoSuchOperator),
}
}
pub fn num_stack(&self) -> usize { // pub fn num_stack(&self) -> usize {
match self { // match self {
CalculatorOperation::Add // CalculatorOperation::Add
| CalculatorOperation::Subtract // | CalculatorOperation::Subtract
| CalculatorOperation::Multiply // | CalculatorOperation::Multiply
| CalculatorOperation::Divide // | CalculatorOperation::Divide
| CalculatorOperation::Modulo // | CalculatorOperation::Modulo
| CalculatorOperation::IntegerDivide // | CalculatorOperation::IntegerDivide
| CalculatorOperation::Swap // | CalculatorOperation::Swap
| CalculatorOperation::Pow // | CalculatorOperation::Pow
| CalculatorOperation::E // | CalculatorOperation::E
//| CalculatorOperation::Remainder // //| CalculatorOperation::Remainder
=> 2, // => 2,
CalculatorOperation::Negate // CalculatorOperation::Negate
| CalculatorOperation::AbsoluteValue // | CalculatorOperation::AbsoluteValue
| CalculatorOperation::Inverse // | CalculatorOperation::Inverse
| CalculatorOperation::Drop // | CalculatorOperation::Drop
| CalculatorOperation::Sin // | CalculatorOperation::Sin
| CalculatorOperation::Cos // | CalculatorOperation::Cos
| CalculatorOperation::Tan // | CalculatorOperation::Tan
| CalculatorOperation::ASin // | CalculatorOperation::ASin
| CalculatorOperation::ACos // | CalculatorOperation::ACos
| CalculatorOperation::ATan // | CalculatorOperation::ATan
| CalculatorOperation::Sqrt // | CalculatorOperation::Sqrt
| CalculatorOperation::Dup // | CalculatorOperation::Dup
//|CalculatorOperation::Factorial // //|CalculatorOperation::Factorial
| CalculatorOperation::Log // | CalculatorOperation::Log
| CalculatorOperation::Ln // | CalculatorOperation::Ln
=> 1, // => 1,
CalculatorOperation::Undo // CalculatorOperation::Macro(_)
| CalculatorOperation::Redo // |CalculatorOperation::Undo
=> 0, // | CalculatorOperation::Redo
} // => 0,
} // }
} // }
// }

View File

@ -5,7 +5,6 @@ mod calc;
mod util; mod util;
use calc::errors::{CalculatorError, CalculatorResult}; use calc::errors::{CalculatorError, CalculatorResult};
use calc::operations::CalculatorOperation;
use calc::Calculator; use calc::Calculator;
use std::cmp; use std::cmp;
use std::convert::TryFrom; use std::convert::TryFrom;
@ -165,7 +164,8 @@ fn main() -> Result<(), Box<dyn Error>> {
<ret> => Dup l => Log\n\ <ret> => Dup l => Log\n\
> => Swap L => Ln\n\ > => Swap L => Ln\n\
e => E ^c => Constants\n\ e => E ^c => Constants\n\
m => Macros rR => Registers\ m => Macros rR => Registers\n\
uU => Undo/Redo\
", ",
}, },
f, f,
@ -257,18 +257,18 @@ 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('c') => { // Key::Ctrl('c') => {
app.state = AppState::Constants; // app.state = AppState::Constants;
} // }
Key::Char('r') => { // Key::Char('r') => {
app.state = AppState::Registers(RegisterState::Load); // app.state = AppState::Registers(RegisterState::Load);
} // }
Key::Char('R') => { // Key::Char('R') => {
app.state = AppState::Registers(RegisterState::Save); // app.state = AppState::Registers(RegisterState::Save);
} // }
Key::Char('m') => { // Key::Char('m') => {
app.state = AppState::Macros; // app.state = AppState::Macros;
} // }
Key::Char('h') => { Key::Char('h') => {
app.state = AppState::Help; app.state = AppState::Help;
} }
@ -295,7 +295,7 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
} }
Key::Char('\n') | Key::Char(' ') => { Key::Char('\n') | Key::Char(' ') => {
if app.input.is_empty() { if app.input.is_empty() {
calc_operation(app, '\n')?; calc_operation(app, ' ')?;
} else { } else {
let mut tmp_input = app.input.clone(); let mut tmp_input = app.input.clone();
if tmp_input.ends_with('e') { if tmp_input.ends_with('e') {
@ -313,6 +313,7 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
calc_operation(app, '>')?; calc_operation(app, '>')?;
} }
Key::Down => { Key::Down => {
// TODO: Internal calculator function
if let Ok(x) = app.calculator.pop() { if let Ok(x) = app.calculator.pop() {
app.input = x.to_string(); app.input = x.to_string();
} }
@ -329,63 +330,68 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
_ => {} _ => {}
}, },
AppState::Help => match key { AppState::Help => match key {
Key::Esc => { Key::Esc | Key::Char('q') => {
app.state = AppState::Calculator; app.state = AppState::Calculator;
app.calculator.cancel();
} }
_ => {} _ => {}
}, },
AppState::Constants => match key { AppState::Constants => match key {
Key::Esc => { Key::Esc => {
app.state = AppState::Calculator; app.state = AppState::Calculator;
app.calculator.cancel();
} }
Key::Char(c) => { Key::Char(c) => {
app.calculator.push_constant(c)?; app.calculator.push_constant(c)?;
app.input.clear(); app.input.clear();
app.state = AppState::Calculator; app.state = AppState::Calculator;
app.calculator.cancel();
} }
_ => {} _ => {}
}, },
AppState::Registers(task) => match key { AppState::Registers(task) => match key {
Key::Esc => { Key::Esc => {
app.state = AppState::Calculator; app.state = AppState::Calculator;
app.calculator.cancel();
} }
Key::Char(c) => { // Key::Char(c) => {
match task { // match task {
RegisterState::Save => { // RegisterState::Save => {
app.calculator.save_register(c)?; // app.calculator.save_register(c)?;
} // }
RegisterState::Load => { // RegisterState::Load => {
app.calculator.push_register(c)?; // app.calculator.push_register(c)?;
} // }
} // }
app.input.clear(); // app.input.clear();
app.state = AppState::Calculator; // app.state = AppState::Calculator;
} // }
_ => {} _ => {}
}, },
AppState::Macros => match key { AppState::Macros => match key {
Key::Esc => { Key::Esc => {
app.state = AppState::Calculator; app.state = AppState::Calculator;
app.calculator.cancel();
} }
Key::Char(c) => { // Key::Char(c) => {
if !app.input.is_empty() { // if !app.input.is_empty() {
//TODO: A better way to do this // //TODO: A better way to do this
//Can use calc_operation in the future. Already performs this check // //Can use calc_operation in the future. Already performs this check
let f = app // let f = app
.input // .input
.parse::<f64>() // .parse::<f64>()
.or(Err(CalculatorError::ParseError))?; // .or(Err(CalculatorError::ParseError))?;
app.calculator.push(f).and_then(|()| { // app.calculator.push(f).and_then(|()| {
app.input.clear(); // app.input.clear();
Ok(()) // Ok(())
})?; // })?;
} // }
// TODO: Handle macros internally to the calculator // // TODO: Handle macros internally to the calculator
let mac = app.calculator.get_macro(c)?; // let mac = app.calculator.get_macro(c)?;
events.fill_event_buf(mac.value); // events.fill_event_buf(mac.value);
app.state = AppState::Calculator; // app.state = AppState::Calculator;
} // }
_ => {} _ => {}
}, },
} }
@ -393,7 +399,6 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<bool
} }
fn calc_operation(app: &mut App, c: char) -> CalculatorResult<()> { fn calc_operation(app: &mut App, c: char) -> CalculatorResult<()> {
let op = CalculatorOperation::from_char(c)?;
if !app.input.is_empty() { if !app.input.is_empty() {
let f = app let f = app
.input .input
@ -403,7 +408,9 @@ fn calc_operation(app: &mut App, c: char) -> CalculatorResult<()> {
app.input.clear(); app.input.clear();
} }
app.calculator.op(op) //let op = CalculatorOperation::from_char(c)?;
app.calculator.take_input(c)
//app.calculator.op(op)
} }
struct ClippyRectangle<'a> { struct ClippyRectangle<'a> {