diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/main.rs | 339 | ||||
-rw-r--r-- | src/tests.rs | 61 |
4 files changed, 371 insertions, 32 deletions
diff --git a/Cargo.lock b/Cargo.lock index 807b127..0fb098d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,6 +336,7 @@ dependencies = [ name = "rp_calc" version = "0.0.1" dependencies = [ + "num-traits", "rust_decimal", "rust_decimal_macros", ] diff --git a/Cargo.toml b/Cargo.toml index a337b4c..0628583 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" [dependencies] rust_decimal = { version = "1.34", features = ["maths", "std", "rand"] } rust_decimal_macros = "1.34" - +num-traits = "0.2" diff --git a/src/main.rs b/src/main.rs index bd5053e..7420e3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,86 @@ // use std::env; // TODO: Get args from here +use std::io::{self, BufRead}; use std::collections::HashMap; +use std::fmt; use rust_decimal::prelude::*; use rust_decimal_macros::dec; -#[derive(Debug)] +use num_traits::pow::Pow; + + +mod tests; + +#[derive(Debug,PartialEq,Eq,Clone)] enum Value { Str(String), Num(Decimal), } +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::Str(s) => write!(f, "[{}]", s), + Value::Num(d) => write!(f, "{}", d), + } + } +} + +fn print_value(v: &Value) { + match v { + Value::Str(s) => print!("{}", s), + Value::Num(d) => print!("{}", d), + } +} + enum Mode { Integer, Decimal, Str, - Command + CommandChar, + CommandNamed, + RegisterChar, + RegisterStr, } +#[derive(Debug)] +enum Exit { + WithMessage(String), + Quit, +} + +fn err_msg(msg: String) -> Exit { + Exit::WithMessage(msg) +} fn main() { let mut state = RPState { registers: HashMap::new(), stack: Vec::new(), - mode: Mode::Command, + mode: Mode::CommandChar, wip_str: String::from(""), + // TODO: I don't think we need a return stack + // But this the closest thing we have right now + reg_command: String::from(""), + eat_count: 0, num: dec!(0.0), decimal_offset: dec!(1), }; - - let result = eval(String::from("123 456 7.89+"), &mut state); - match result { - Ok(()) => println!("{:?}", state.stack), - Err(msg) => println!("Error: {}", msg), + let stdin = io::stdin(); + for line in stdin.lock().lines() { + match eval(&line.unwrap(), &mut state) { + Ok(()) => { + let mut first = true; + print!("{}", "stack: {"); + for elem in state.stack.iter() { + if !first { print!("{}", ", "); } + print!("{}", elem); + first = false; + } + println!("{}", "}"); + }, + Err(Exit::WithMessage(msg)) => println!("Error: {}", msg), + Err(Exit::Quit) => break, + } } } @@ -46,31 +96,183 @@ struct RPState { registers: HashMap<String, Value>, stack: Vec<Value>, mode: Mode, + reg_command: String, wip_str: String, + eat_count: u32, num: Decimal, decimal_offset: Decimal } -fn command(c: char, state: &mut RPState) -> Result<(), String> { +// TODO: Generalized shuffles +// [[a-aa\](shuf)]s[dup] +// [[ab-ba\](shuf)]s[swap] +// [[ab-b\](shuf)]s[nip] +// +// fn shuffle(_state: &mut RPState) -> Result<(), Exit> { +// Err(err_msg("(shuf) not implemented yet!".into())) +// } + +fn command_str(c: char, state: &mut RPState) -> Result<(), Exit> { + if c != ')' { + state.wip_str.push(c); + } else if c == ')' { + match state.wip_str.as_str() { + // TODO: + // (times) + // (while) + // (?) -- (cond ifTrue ifFalse) + // [(?)x]s[if-else] + // [[\](?)x]s[if] + // (r?) -- show contents of registers + // Scope out + word => { + if state.registers.contains_key(word) { + match &state.registers[word].clone() { + Value::Str(eval_body) => { + state.wip_str.clear(); + state.mode = Mode::CommandChar; + return eval(&eval_body, state); + } + _ => { + return Err(err_msg(format!("Unable to execute ({}) as a named word", word))) + } + } + + } else { + return Err(err_msg(format!("Unable to execute ({}) as a named word", word))) + } + } + + } + } + + Ok(()) +} + +fn command_char(c: char, state: &mut RPState) -> Result<(), Exit> { if c.is_digit(RADIX) { state.mode = Mode::Integer; state.num = dec!(0); - state.num += to_decimal(c.to_digit(10).ok_or_else(|| format!("{} isn't a digit", c))?); + state.num += to_decimal(c.to_digit(10).ok_or_else(|| err_msg(format!("{} isn't a digit", c)))?); Ok(()) } else if c == ' ' { // do nothing Ok(()) - } else if c == '+' { - - let a = state.stack.pop().ok_or_else(|| "Data underflow!")?; - let b = state.stack.pop().ok_or_else(|| "Data underflow!")?; - - match (a, b) { - (Value::Num(c), Value::Num(d)) => { - state.stack.push(Value::Num(c + d)); + } else if c == '(' { + state.mode = Mode::CommandNamed; + state.wip_str.clear(); + state.eat_count = 0; + Ok(()) + } else if c == '[' { + state.mode = Mode::Str; + state.wip_str.clear(); + state.eat_count = 0; + Ok(()) + } + else if let 'p' | 'n' | 'q' | 'l' | 's' | 'v' | 'x' | 'd' | ',' | 'c' = c { + match c { + 'q' => return Err(Exit::Quit), + 'c' => state.stack.clear(), + 'l' => { + state.reg_command = "l".into(); + state.mode = Mode::RegisterChar; + } + 'x' => { + if state.stack.is_empty() { + return Err(err_msg("Data underflow!".into())); + } + match state.stack.pop() { + Some(Value::Str(s)) => { + return eval(&s, state) + } + Some(v) => { + return Err(err_msg(format!("Cannot eval {}", v))); + } + None => { + return Err(err_msg("Data underflow!".into())); + } + } + + } + 's' => { + state.reg_command = "s".into(); + state.mode = Mode::RegisterChar; + } + 'd' => { + if state.stack.is_empty() { + return Err(err_msg("Data underflow!".into())); + } + let val = state.stack.pop().unwrap(); + state.stack.push(val.clone()); + state.stack.push(val.clone()); } + ',' => { + if state.stack.is_empty() { + return Err(err_msg("Data underflow!".into())); + } + state.stack.pop(); + } + 'v' => { + if state.stack.is_empty() { + return Err(err_msg("Data underflow!".into())); + } + match state.stack.pop() { + Some(Value::Num(d)) => { + match d.sqrt() { + Some(n) => state.stack.push(Value::Num(n)), + None => return Err(err_msg("Error attempting square root!".into())), + } + } + Some(v) => { + return Err(err_msg(format!("Invalid attempt to sqrt {}", v))); + } + None => { + return Err(err_msg("Impossible data underflow!".into())); + } + } + } + 'p' => { + if state.stack.is_empty() { + return Err(err_msg("Data underflow!".into())); + } + print_value(state.stack.last().unwrap()); + }, + 'n' => { + if state.stack.is_empty() { + return Err(err_msg("Data underflow!".into())); + } + print_value(&state.stack.pop().unwrap()); + }, + _ => return Err(err_msg(format!("{} is unimplemented, this shouldn't be reachable!", c))), + } + Ok(()) - _ => return Err("Invalid operation + on non-numbers!".to_string()) + } else if let '+' | '-' | '/' | '*' | '%' | '^' = c { + if state.stack.len() < 2 { + return Err(err_msg("Data underflow!".into())); + } + + let a = state.stack.pop().ok_or_else(|| err_msg("Data underflow!".into()))?; + let b = state.stack.pop().ok_or_else(|| err_msg("Data underflow!".into()))?; + + match (&a, &b) { + (Value::Num(ia), Value::Num(ib)) => { + let value = match c { + '+' => Some(*ib + *ia), + '-' => Some(*ib - *ia), + '/' => Some(*ib / *ia), + '*' => Some(*ib * *ia), + '%' => Some(*ib % *ia), + '^' => Some(Decimal::pow(*ib, *ia)), + _ => None + }.ok_or_else(|| err_msg("This should never happen".into()))?; + state.stack.push(Value::Num(value)); + } + _ => { + state.stack.push(b); + state.stack.push(a); + return Err(err_msg(format!("Invalid operation {} on non-numbers!", c))) + } } Ok(()) } else { @@ -78,7 +280,7 @@ fn command(c: char, state: &mut RPState) -> Result<(), String> { } } -fn integer(c: char, state: &mut RPState) -> Result<(), String> { +fn integer(c: char, state: &mut RPState) -> Result<(), Exit> { if c.is_digit(RADIX) { state.num *= INTEGER_RADIX; state.num += to_decimal(c.to_digit(10).unwrap()); @@ -88,33 +290,108 @@ fn integer(c: char, state: &mut RPState) -> Result<(), String> { } else { state.stack.push(Value::Num(state.num)); - state.mode = Mode::Command; - return command(c, state); + state.mode = Mode::CommandChar; + return command_char(c, state); } return Ok(()); } -fn decimal(c: char, state: &mut RPState) -> Result<(), String> { +fn decimal(c: char, state: &mut RPState) -> Result<(), Exit> { if c.is_digit(RADIX) { state.decimal_offset *= dec!(0.1); state.num += to_decimal(c.to_digit(10).unwrap()) * state.decimal_offset; } else { state.stack.push(Value::Num(state.num)); - state.mode = Mode::Command; - return command(c, state); + state.mode = Mode::CommandChar; + return command_char(c, state); } return Ok(()); } -fn eval(input: String, state: &mut RPState) -> Result<(), String> { +fn string(c: char, state: &mut RPState) -> Result<(), Exit> { + if state.eat_count > 0 { + state.eat_count-=1; + state.wip_str.push(c); + } else if c == '\\' { + state.eat_count = 1; + } else if c != ']' { + state.wip_str.push(c); + } else if c == ']' { + state.mode = Mode::CommandChar; + state.stack.push(Value::Str(state.wip_str.clone())); + state.wip_str.clear(); + } else { + return Err(err_msg("Should Not Get Here!".into())) + } + Ok(()) +} + +fn register_str(c: char, state: &mut RPState) -> Result<(), Exit> { + match (c, state.reg_command.as_str()) { + (']', "l") => { + if state.registers.contains_key(&state.wip_str) { + state.stack.push(state.registers[&state.wip_str].clone()); + } + state.wip_str.clear(); + state.mode = Mode::CommandChar; + } + (']', "s") => { + if state.stack.is_empty() { + return Err(err_msg(format!("Data underflow attempting to store to register {}", c))); + } + state.registers.insert(state.wip_str.clone(), state.stack.pop().unwrap()); + state.wip_str.clear(); + state.mode = Mode::CommandChar; + } + (_, "l"|"s") => { + state.wip_str.push(c) + } + _ => { + state.mode = Mode::CommandChar; + return Err(err_msg(format!("Unsupported register command {}", state.reg_command))); + } + } + Ok(()) +} + +fn register_char(c: char, state: &mut RPState) -> Result<(), Exit> { + match (state.reg_command.as_str(), c) { + (_, '[') => { + state.mode = Mode::RegisterStr; + Ok(()) + } + ("s", c) => { + if state.stack.is_empty() { + return Err(err_msg(format!("Data underflow attempting to store to register {}", c))); + } + state.registers.insert(String::from(c), state.stack.pop().unwrap()); + state.mode = Mode::CommandChar; + Ok(()) + } + ("l", c) => { + if state.registers.contains_key(&String::from(c)) { + state.stack.push(state.registers[&String::from(c)].clone()); + } + state.mode = Mode::CommandChar; + Ok(()) + } + _ => { + state.mode = Mode::CommandChar; + return Err(err_msg(format!("Unsupported register command {}", state.reg_command))); + } + } +} + +fn eval(input: &str, state: &mut RPState) -> Result<(), Exit> { for (_cpos, c) in input.char_indices() { let res = match state.mode { - Mode::Command => command(c, state), + Mode::CommandChar => command_char(c, state), + Mode::CommandNamed => command_str(c, state), Mode::Integer => integer(c, state), Mode::Decimal => decimal(c, state), - Mode::Str => { - panic!("Strings not implemented yet"); - } + Mode::Str => string(c, state), + Mode::RegisterChar => register_char(c, state), + Mode::RegisterStr => register_str(c, state), }; if res.is_err() { return res @@ -123,7 +400,7 @@ fn eval(input: String, state: &mut RPState) -> Result<(), String> { match state.mode { Mode::Integer | Mode::Decimal => { state.stack.push(Value::Num(state.num)); - state.mode = Mode::Command; + state.mode = Mode::CommandChar; }, _ => {} }; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..170d4f2 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,61 @@ + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use rust_decimal_macros::dec; + use crate::*; + + fn new_state() -> RPState { + return RPState { + registers: HashMap::new(), + stack: Vec::new(), + eat_count: 0, + mode: Mode::CommandChar, + wip_str: String::from(""), + reg_command: String::from(""), + num: dec!(0.0), + decimal_offset: dec!(1), + }; + } + + #[test] + fn test_binops() -> Result<(), Exit> { + let mut state = new_state(); + + eval("1 2+", &mut state)?; + assert_eq!(state.stack[0], Value::Num(dec!(3))); + + eval("3 4*", &mut state)?; + assert_eq!(state.stack[1], Value::Num(dec!(12))); + + eval("5 10/", &mut state)?; + assert_eq!(state.stack[2], Value::Num(dec!(0.5))); + + eval("20 15-", &mut state)?; + assert_eq!(state.stack[3], Value::Num(dec!(5))); + + eval("15 20-", &mut state)?; + assert_eq!(state.stack[4], Value::Num(dec!(-5))); + + eval("3 2%", &mut state)?; + assert_eq!(state.stack[5], Value::Num(dec!(1))); + + eval("2 2^", &mut state)?; + assert_eq!(state.stack[6], Value::Num(dec!(4))); + + Ok(()) + } + + #[test] + fn test_registers() -> Result<(), Exit> { + let mut state = new_state(); + eval("[1+]s[inc]", &mut state)?; + assert_eq!(state.registers["inc"], Value::Str("1+".into())); + eval("1(inc)(inc)", &mut state)?; + assert_eq!(state.stack[0], Value::Num(dec!(3))); + + + + Ok(()) + } +} |