From 81856a923cf623084b32c4ed157edc956528ab72 Mon Sep 17 00:00:00 2001 From: Andrew Owen Date: Thu, 21 Mar 2024 05:35:21 -0600 Subject: Negative numbers, stack depth assert, smol stdlib - `_` now sets the "is a negative number" flag for the duration of the current number. In the future, it may be turned into a toggle - There is now a "stdlib" of sorts, mostly focused on basic unit conversions - `!` has been added for asserting a minumum stack depth. This is mostly in service of the "stdlib" functions --- src/main.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++++++----------- src/tests.rs | 33 ++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7420e3f..96e7abb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ fn print_value(v: &Value) { } } +#[derive(Debug)] enum Mode { Integer, Decimal, @@ -52,9 +53,22 @@ fn err_msg(msg: String) -> Exit { Exit::WithMessage(msg) } +fn baseline_regsiters() -> HashMap { + let kvs = [ + ("F->C", "1! 32- 5 9/*"), + ("C->F", "1! 9 5/* 32+"), + ("C->K", "1! 273+"), + ("K->C", "1! 273-"), + ("Km->mi", "1! 1.609344/"), + ("mi->Km", "1! 1.609344*"), + ].map(|kv: (&str, &str)| (String::from(kv.0), Value::Str(kv.1.into()))); + HashMap::from(kvs) +} + + fn main() { let mut state = RPState { - registers: HashMap::new(), + registers: baseline_regsiters(), stack: Vec::new(), mode: Mode::CommandChar, wip_str: String::from(""), @@ -63,6 +77,7 @@ fn main() { reg_command: String::from(""), eat_count: 0, num: dec!(0.0), + is_num_negative: false, decimal_offset: dec!(1), }; let stdin = io::stdin(); @@ -91,6 +106,10 @@ fn to_decimal(input: u32) -> Decimal { >::into(input) } +fn usize_to_decimal(input: usize) -> Decimal { + >::into(input) +} + struct RPState { registers: HashMap, @@ -99,6 +118,7 @@ struct RPState { reg_command: String, wip_str: String, eat_count: u32, + is_num_negative: bool, num: Decimal, decimal_offset: Decimal } @@ -134,7 +154,7 @@ fn command_str(c: char, state: &mut RPState) -> Result<(), Exit> { return eval(&eval_body, state); } _ => { - return Err(err_msg(format!("Unable to execute ({}) as a named word", word))) + return Err(err_msg(format!("Unable to execute ({}) as a named word", word))) } } @@ -155,6 +175,11 @@ fn command_char(c: char, state: &mut RPState) -> Result<(), Exit> { state.num = dec!(0); state.num += to_decimal(c.to_digit(10).ok_or_else(|| err_msg(format!("{} isn't a digit", c)))?); Ok(()) + } else if c == '_' { + state.mode = Mode::Integer; + state.num = dec!(0); + state.is_num_negative = true; + Ok(()) } else if c == ' ' { // do nothing Ok(()) @@ -169,8 +194,22 @@ fn command_char(c: char, state: &mut RPState) -> Result<(), Exit> { state.eat_count = 0; Ok(()) } - else if let 'p' | 'n' | 'q' | 'l' | 's' | 'v' | 'x' | 'd' | ',' | 'c' = c { + else if let 'p' | 'n' | 'q' | 'l' | 's' | 'v' | 'x' | 'd' | ',' | 'c' | '!' = c { match c { + '!' => { + if state.stack.is_empty() { + return Err(err_msg("Data underflow!".into())); + } + match state.stack.pop() { + Some(Value::Num(d)) => { + if usize_to_decimal(state.stack.len()) < d { + return Err(err_msg(format!("Stack depth should be at least: {}", d))); + } + } + Some(Value::Str(_)) => return Err(err_msg("Cannot assert a string as a stack depth".into())), + None => return Err(err_msg("Data underflow!".into())) + } + }, 'q' => return Err(Exit::Quit), 'c' => state.stack.clear(), 'l' => { @@ -280,6 +319,17 @@ fn command_char(c: char, state: &mut RPState) -> Result<(), Exit> { } } +fn finish_num(c: char, state: &mut RPState) -> Result<(), Exit> { + // print!("finishing number, negative? {}", state.is_num_negative); + if state.is_num_negative { + state.num *= dec!(-1); + } + state.stack.push(Value::Num(state.num)); + state.mode = Mode::CommandChar; + state.is_num_negative = false; + command_char(c, state) +} + fn integer(c: char, state: &mut RPState) -> Result<(), Exit> { if c.is_digit(RADIX) { state.num *= INTEGER_RADIX; @@ -287,11 +337,10 @@ fn integer(c: char, state: &mut RPState) -> Result<(), Exit> { } else if c == '.' { state.decimal_offset = dec!(1); state.mode = Mode::Decimal; - } - else { - state.stack.push(Value::Num(state.num)); - state.mode = Mode::CommandChar; - return command_char(c, state); + } else if c == '_' { + state.is_num_negative = true; + } else { + return finish_num(c, state); } return Ok(()); } @@ -301,9 +350,7 @@ fn decimal(c: char, state: &mut RPState) -> Result<(), Exit> { 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::CommandChar; - return command_char(c, state); + return finish_num(c, state) } return Ok(()); } @@ -399,8 +446,7 @@ fn eval(input: &str, state: &mut RPState) -> Result<(), Exit> { } match state.mode { Mode::Integer | Mode::Decimal => { - state.stack.push(Value::Num(state.num)); - state.mode = Mode::CommandChar; + return finish_num(' ', state) }, _ => {} }; diff --git a/src/tests.rs b/src/tests.rs index 170d4f2..013696b 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,16 +1,16 @@ #[cfg(test)] mod tests { - use std::collections::HashMap; use rust_decimal_macros::dec; use crate::*; fn new_state() -> RPState { return RPState { - registers: HashMap::new(), + registers: baseline_regsiters(), stack: Vec::new(), eat_count: 0, mode: Mode::CommandChar, + is_num_negative: false, wip_str: String::from(""), reg_command: String::from(""), num: dec!(0.0), @@ -43,6 +43,9 @@ mod tests { eval("2 2^", &mut state)?; assert_eq!(state.stack[6], Value::Num(dec!(4))); + eval("c_40 32-", &mut state)?; + assert_eq!(state.stack[0], Value::Num(dec!(-72))); + Ok(()) } @@ -54,7 +57,33 @@ mod tests { eval("1(inc)(inc)", &mut state)?; assert_eq!(state.stack[0], Value::Num(dec!(3))); + eval("c1(inc)", &mut state)?; + assert_eq!(state.stack[0], Value::Num(dec!(2))); + + Ok(()) + } + + #[test] + fn test_stdlib() -> Result<(), Exit> { + let mut state = new_state(); + // TODO: Add this back in one we've figured out booleans + // comparison + // + // Right now, `5 9 /` results in 0.5555555555555555555555555556 + // Which means that this conversion results in + // -40.000000000000000000000000003 + // Which, like, decimal bases are fun and all + // + // And I don't want to fuss with unwrapping state and all that + // mess. I'd much rather do something like + // eval("_40(F->C)", &mut state)?; + // assert_eq!(state.stack[0], Value::Num(dec!(-40))); + + eval("_40(C->F)", &mut state)?; + assert_eq!(state.stack[0], Value::Num(dec!(-40))); + eval("c0(C->F)", &mut state)?; + assert_eq!(state.stack[0], Value::Num(dec!(32))); Ok(()) } -- cgit 1.4.1