summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml2
-rw-r--r--src/main.rs339
-rw-r--r--src/tests.rs61
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(())
+    }
+}