use crate::error::*;
use crate::task::Task;
use chrono::prelude::*;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Clone)]
pub enum RPNSymbol {
Add,
Branch,
Divide,
Duplicate,
Equal,
GetEnvironment,
GetProp,
GetTag,
GreaterThan,
LessThan,
Log,
Multiply,
Number(f64),
Pow,
Rem,
Sqrt,
Subtract,
Swap,
Symbol(String),
}
impl RPNSymbol {
pub fn parse_programs(ss: &[String]) -> Vec<Self> {
let mut acc = vec![];
for s in ss {
for symbol in s.split_whitespace() {
acc.push(Self::parse(symbol));
}
}
acc
}
pub fn parse_program(s: &str) -> Vec<Self> {
s.split_whitespace().map(Self::parse).collect::<Vec<Self>>()
}
pub fn parse(s: &str) -> Self {
use RPNSymbol::*;
match s {
"+" => Add,
"?" => Branch,
"/" => Divide,
"&" => Duplicate,
"=" => Equal,
"$" => GetEnvironment,
":" => GetProp,
"#" => GetTag,
">" => GreaterThan,
"<" => LessThan,
"~" => Log,
"*" => Multiply,
"^" => Pow,
"%" => Rem,
"_" => Sqrt,
"@" => Swap,
"-" => Subtract,
x => match x.parse::<f64>() {
Ok(n) => Number(n),
Err(_) => Symbol(x.to_string()),
},
}
}
}
fn sanitize_date_time(dt: &Option<DateTime<Utc>>) -> f64 {
dt.map(|date_time| date_time.timestamp()).unwrap_or(0) as f64
}
macro_rules! stack_machine_binary_method {
($name:ident, $a: ident, $b: ident, $op:expr, $msg:expr) => {
fn $name(&mut self) -> HyperTaskResult<()> {
let $a = self.pop_number().map_err( |e| { HyperTaskError::new( HyperTaskErrorDomain::ScoreCalculator, HyperTaskErrorAction::Run).msg($msg).from(e) }) ?;
let $b = self.pop_number().map_err( |e| { HyperTaskError::new( HyperTaskErrorDomain::ScoreCalculator, HyperTaskErrorAction::Run).msg($msg).from(e) }) ?;
self.push_number($op)
}
};
}
macro_rules! stack_machine_unary_method {
($name:ident, $a: ident, $op:expr, $msg:expr) => {
fn $name(&mut self) -> HyperTaskResult<()> {
let $a = self.pop_number().map_err( |e| { HyperTaskError::new( HyperTaskErrorDomain::ScoreCalculator, HyperTaskErrorAction::Run).msg($msg).from(e) }) ?;
self.push_number($op)
}
};
}
pub struct StackMachine {
stack: Vec<RPNSymbol>,
instructions: Rc<Vec<RPNSymbol>>,
environment: HashMap<&'static str, f64>,
}
impl StackMachine {
pub fn new(instructions: Vec<RPNSymbol>, environment: HashMap<&'static str, f64>) -> Self {
Self {
stack: Vec::with_capacity((instructions.len() as f64).sqrt() as usize),
instructions: Rc::new(instructions),
environment,
}
}
fn pop(&mut self) -> HyperTaskResult<RPNSymbol> {
self.stack.pop().ok_or_else(|| {
HyperTaskError::new(
HyperTaskErrorDomain::ScoreCalculator,
HyperTaskErrorAction::Run,
)
.msg("tried to pop an empty stack")
})
}
fn pop_number(&mut self) -> HyperTaskResult<f64> {
let value = self.pop()?;
if let RPNSymbol::Number(n) = value {
Ok(n)
} else {
Err(HyperTaskError::new(
HyperTaskErrorDomain::ScoreCalculator,
HyperTaskErrorAction::Run,
)
.msg("popped value is not a number"))
}
}
fn pop_symbol(&mut self) -> HyperTaskResult<String> {
let value = self.pop()?;
if let RPNSymbol::Symbol(s) = value {
Ok(s)
} else {
Err(HyperTaskError::new(
HyperTaskErrorDomain::ScoreCalculator,
HyperTaskErrorAction::Run,
)
.msg("popped value is not a symbol"))
}
}
fn push_number(&mut self, number: f64) -> HyperTaskResult<()> {
self.stack.push(RPNSymbol::Number(number));
Ok(())
}
stack_machine_unary_method!(run_sqrt, lhs, lhs.sqrt(), "could not sqrt");
stack_machine_binary_method!(run_add, lhs, rhs, lhs + rhs, "could not add");
stack_machine_binary_method!(run_divide, lhs, rhs, lhs / rhs, "could not divide");
stack_machine_binary_method!(run_multiply, lhs, rhs, lhs * rhs, "could not multiply");
stack_machine_binary_method!(run_subtract, lhs, rhs, lhs - rhs, "could not subtract");
stack_machine_binary_method!(run_pow, lhs, rhs, lhs.powf(rhs), "could not pow");
stack_machine_binary_method!(run_log, lhs, rhs, lhs.log(rhs), "could not get log");
stack_machine_binary_method!(
run_rem,
lhs,
rhs,
lhs.rem_euclid(rhs),
"could not get remainder"
);
stack_machine_binary_method!(
run_equal,
lhs,
rhs,
if (lhs - rhs).abs() < std::f64::EPSILON {
1.0
} else {
0.0
},
"could not compare for equality"
);
stack_machine_binary_method!(
run_greater_than,
lhs,
rhs,
if lhs > rhs { 1.0 } else { 0.0 },
"could not compare for greater than"
);
stack_machine_binary_method!(
run_less_than,
lhs,
rhs,
if lhs < rhs { 1.0 } else { 0.0 },
"could not compare for less than"
);
fn run_duplicate(&mut self) -> HyperTaskResult<()> {
let x = self.pop()?;
self.stack.push(x.clone());
self.stack.push(x.clone());
Ok(())
}
fn run_get_prop(&mut self, task: &Task) -> HyperTaskResult<()> {
let prop_name = self.pop_symbol()?;
let replace = match prop_name.as_str() {
"created_at" => task.get_created_at().timestamp() as f64,
"done" => sanitize_date_time(task.get_done()),
"due" => sanitize_date_time(task.get_due()),
"snooze" => sanitize_date_time(task.get_snooze()),
"updated_at" => task.get_updated_at().timestamp() as f64,
"wait" => sanitize_date_time(task.get_wait()),
_ => {
return Err(HyperTaskError::new(
HyperTaskErrorDomain::ScoreCalculator,
HyperTaskErrorAction::Run,
)
.with_msg(|| format!("`{}` is not a valid prop name", &prop_name)));
}
};
self.push_number(replace)
}
fn run_get_tag(&mut self, task: &Task) -> HyperTaskResult<()> {
let tag_name = self.pop_symbol()?;
let replace = if task.get_tags().contains(&tag_name) {
1.0
} else {
0.0
};
self.push_number(replace)
}
fn run_get_environment(&mut self) -> HyperTaskResult<()> {
let environment_name = self.pop_symbol()?;
let replace = *self
.environment
.get(environment_name.as_str())
.ok_or_else(|| {
HyperTaskError::new(
HyperTaskErrorDomain::ScoreCalculator,
HyperTaskErrorAction::Run,
)
.with_msg(|| format!("`{}` is not a valid environment name", &environment_name))
})?;
self.push_number(replace)
}
fn run_swap(&mut self) -> HyperTaskResult<()> {
let one = self.pop()?;
let two = self.pop()?;
self.stack.push(one);
self.stack.push(two);
Ok(())
}
fn run_branch(&mut self) -> HyperTaskResult<()> {
let query = self.pop_number()?;
let if_true = self.pop()?;
let if_false = self.pop()?;
let to_push = if query == 0.0 || query.is_infinite() || query.is_nan() {
if_false
} else {
if_true
};
self.stack.push(to_push);
Ok(())
}
pub fn run_on(&mut self, task: &Task) -> HyperTaskResult<f64> {
self.stack.clear();
for instruction in &*(self.instructions.clone()) {
match instruction {
RPNSymbol::Add => self.run_add(),
RPNSymbol::Branch => self.run_branch(),
RPNSymbol::Divide => self.run_divide(),
RPNSymbol::Duplicate => self.run_duplicate(),
RPNSymbol::Equal => self.run_equal(),
RPNSymbol::GetEnvironment => self.run_get_environment(),
RPNSymbol::GetProp => self.run_get_prop(task),
RPNSymbol::GetTag => self.run_get_tag(task),
RPNSymbol::GreaterThan => self.run_greater_than(),
RPNSymbol::LessThan => self.run_less_than(),
RPNSymbol::Log => self.run_log(),
RPNSymbol::Multiply => self.run_multiply(),
RPNSymbol::Pow => self.run_pow(),
RPNSymbol::Rem => self.run_rem(),
RPNSymbol::Sqrt => self.run_sqrt(),
RPNSymbol::Swap => self.run_swap(),
RPNSymbol::Subtract => self.run_subtract(),
RPNSymbol::Number(n) => self.push_number(*n),
RPNSymbol::Symbol(s) => {
self.stack.push(RPNSymbol::Symbol(s.to_string()));
Ok(())
}
}?
}
Ok(self.pop_number()?)
}
}