summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs343
1 files changed, 189 insertions, 154 deletions
diff --git a/src/main.rs b/src/main.rs
index 1ee9e88..5ee8d68 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -30,7 +30,7 @@ const HELP_TEXT: &str =
      d       duplicate the top item on the stack\n\
      c       clear the stack\n\
      ,       drop the top item from the stack\n\
-     !       something semi-implemented\n\
+     !       assert the stack height, with the top item as desired height\n\
      \n\
      REGISTER OPERATIONS\n\
      l       load from register (register name as next character)\n\
@@ -117,6 +117,7 @@ fn main() {
       (Mode::Integer, make_integer_readtable()),
       (Mode::Decimal, make_decimal_readtable()),
       (Mode::Str, make_string_readtable()),
+      (Mode::CommandChar, make_command_character_readtable()),
       (Mode::CommandNamed, make_command_string_readtable()),
       (Mode::RegisterChar, make_register_character_readtable()),
       (Mode::RegisterStr, make_register_string_readtable()),
@@ -180,7 +181,7 @@ impl ReadTable {
   }
 
   fn set_action(&mut self, c: char,
-         action: fn(char, &mut RPState) -> Result<(), Exit>)
+         action: impl Fn(char, &mut RPState) -> Result<(), Exit> + 'static)
   {
     let _ = self.action_map.insert(c, Rc::new(action));
   }
@@ -266,168 +267,210 @@ fn make_command_string_readtable() -> ReadTable {
 }
 
 
-fn command_char(c: char, state: &mut RPState) -> Result<(), Exit> {
-  if c.is_digit(RADIX) {
+fn make_command_character_readtable() -> ReadTable {
+  let mut result = ReadTable::new(move |c, _| {
+    panic!("{} isn't implemented yet!", c)
+  });
+
+  ////////////////////////////////////////////////////////////
+  // Whitespace actions
+  //
+  result.set_action(' ', move |_, _| Ok(()));
+
+  ////////////////////////////////////////////////////////////
+  // Literal actions
+  //
+  result.set_digit_action(move |c, state| {
     state.mode = Mode::Integer;
     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 == '_' {
+  });
+
+  result.set_action('_', move |_, state| {
     state.mode = Mode::Integer;
     state.num = dec!(0);
     state.is_num_negative = true;
     Ok(())
-  } else if c == ' ' {
-    // do nothing
-    Ok(())
-  } else if c == '(' {
-    state.mode = Mode::CommandNamed;
+  });
+
+  result.set_action('[', move |_, state| {
+    state.mode = Mode::Str;
     state.wip_str.clear();
     state.eat_count = 0;
     Ok(())
-  } else if c == '[' {
-    state.mode = Mode::Str;
+  });
+
+  result.set_action('(', move |_, state| {
+    state.mode = Mode::CommandNamed;
     state.wip_str.clear();
     state.eat_count = 0;
     Ok(())
-  } else if let 'p' | 'n' | 'q' | 'l' | 's' | 'v' | 'x' | 'd' | ',' | 'c'
-              | '!' | '?' = c
+  });
+
+  ////////////////////////////////////////////////////////////
+  // Arithmetic operation actions
+  //
+  let make_arithmetic_action =
+          move |arithmetic: fn(Decimal, Decimal) -> Decimal|
   {
-    match c {
-      '?' => {
-        print!("{}", HELP_TEXT);
-      },
-      '!' => {
-        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' => {
-        state.reg_command = "l".into();
-        state.mode = Mode::RegisterChar;
+    move |_, state: &mut RPState| {
+      if state.stack.len() < 2 {
+        return Err(err_msg("Data underflow!".into()));
       }
-      '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 right = state.stack.pop().ok_or_else(|| err_msg(
+          "Data underflow!".into()))?;
+      let left = state.stack.pop().ok_or_else(|| err_msg(
+          "Data underflow!".into()))?;
+
+      match (&left, &right) {
+        (Value::Num(left), Value::Num(right)) => {
+          let result = arithmetic(*left, *right);
+
+          state.stack.push(Value::Num(result));
+
+          Ok(())
         }
-        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.push(left);
+          state.stack.push(right);
+
+          Err(err_msg("Can't do arithmetic on non-numbers!".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()));
+    }
+  };
+
+  result.set_action('+', make_arithmetic_action(|left, right| left + right));
+  result.set_action('-', make_arithmetic_action(|left, right| left - right));
+  result.set_action('/', make_arithmetic_action(|left, right| left / right));
+  result.set_action('*', make_arithmetic_action(|left, right| left * right));
+  result.set_action('%', make_arithmetic_action(|left, right| left % right));
+  result.set_action('^', make_arithmetic_action(|left, right|
+      Decimal::pow(left, right)));
+
+  result.set_action('v', move |_, state| {
+    match state.stack.pop() {
+      Some(Value::Num(d)) => {
+        match d.sqrt() {
+          Some(n) => {
+            state.stack.push(Value::Num(n));
+            Ok(())
           }
+          None => Err(err_msg("Error attempting square root!".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))),
+      Some(v) => Err(err_msg(format!("Invalid attempt to sqrt {}", v))),
+      None => Err(err_msg("Data underflow!".into()))
     }
+  });
+
+  ////////////////////////////////////////////////////////////
+  // Stack operation actions
+  //
+  result.set_action('d', move |_, state| {
+    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());
 
     Ok(())
-  } else if let '+' | '-' | '/' | '*' | '%' | '^' = c {
-    if state.stack.len() < 2 {
+  });
+
+  result.set_action('c', move |_, state| {
+    state.stack.clear();
+
+    Ok(())
+  });
+
+  result.set_action(',', move |_, state| {
+    if state.stack.is_empty() {
       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.pop();
+
+    Ok(())
+  });
+
+  result.set_action('!', move |_, state| {
+    match state.stack.pop() {
+      Some(Value::Num(d)) => {
+        if usize_to_decimal(state.stack.len()) < d {
+          Err(err_msg(format!("Stack depth should be at least: {}", d)))
+        } else {
+          Ok(())
+        }
+      }
+      Some(Value::Str(_)) => {
+        Err(err_msg("Cannot assert a string as a stack depth".into()))
       }
-      _ =>  {
-        state.stack.push(b);
-        state.stack.push(a);
-        return Err(err_msg(format!(
-            "Invalid operation {} on non-numbers!", c)))
+      None => {
+        Err(err_msg("Data underflow!".into()))
       }
     }
+  });
+
+  ////////////////////////////////////////////////////////////
+  // Register operation actions
+  //
+  let register_command_action = move |c: char, state: &mut RPState| {
+    state.reg_command = c.into();
+    state.mode = Mode::RegisterChar;
+    Ok::<(), Exit>(())
+  };
+
+  result.set_action('l', register_command_action.clone());
+  result.set_action('s', register_command_action.clone());
+
+  ////////////////////////////////////////////////////////////
+  // Control flow actions
+  //
+  result.set_action('x', move |_, state| {
+    match state.stack.pop() {
+      Some(Value::Str(s)) => eval(&s, state),
+      Some(v) => Err(err_msg(format!("Cannot eval {}", v))),
+      None => Err(err_msg("Data underflow!".into())),
+    }
+  });
+
+  ////////////////////////////////////////////////////////////
+  // Meta actions
+  //
+  result.set_action('p', move |_, state| {
+    if state.stack.is_empty() {
+      return Err(err_msg("Data underflow!".into()));
+    }
+
+    print_value(state.stack.last().unwrap());
+
     Ok(())
-  } else {
-    panic!("{} isn't implemented yet!", c)
-  }
-}
+  });
 
+  result.set_action('n', move |_, state| {
+    if state.stack.is_empty() {
+      return Err(err_msg("Data underflow!".into()));
+    }
+
+    print_value(&state.stack.pop().unwrap());
+
+    Ok(())
+  });
+
+  result.set_action('q', move |_, _| Err(Exit::Quit));
+
+  result.set_action('?', move |_, _| {
+    print!("{}", HELP_TEXT);
+    Ok(())
+  });
+
+  result
+}
 
 fn finish_num(c: char, state: &mut RPState) -> Result<(), Exit> {
   // print!("finishing number, negative? {}", state.is_num_negative);
@@ -437,7 +480,7 @@ fn finish_num(c: char, state: &mut RPState) -> Result<(), Exit> {
   state.stack.push(Value::Num(state.num));
   state.mode = Mode::CommandChar;
   state.is_num_negative = false;
-  command_char(c, state)
+  eval_one(c, state)
 }
 
 
@@ -588,30 +631,9 @@ fn load_from_register(name: &str, state: &mut RPState) -> Result<(), Exit> {
 
 fn eval(input: &str, state: &mut RPState) -> Result<(), Exit> {
   for (_cpos, c) in input.char_indices() {
-    // TODO eventually this slightly weird logic will go away because
-    // everything will be in a readtable
-    let action = {
-      if let Some(readtable) = state.readtables.get_mut(&state.mode) {
-        if let Some(action) = readtable.action_map.get_mut(&c) {
-          Some(Rc::clone(&action))
-        } else {
-          Some(Rc::clone(&readtable.default_action))
-        }
-      } else {
-        None
-      }
-    };
-
-    if let Some(action) = action {
-      action(c, state)?;
-    } else {
-      match state.mode {
-        Mode::CommandChar => command_char(c, state),
-        _ => return Err(err_msg(
-            "Mode has neither readtable nor hardcoded case".to_string())),
-      }?;
-    }
+    eval_one(c, state)?
   }
+
   match state.mode {
     Mode::Integer | Mode::Decimal => {
       return finish_num(' ', state)
@@ -622,3 +644,16 @@ fn eval(input: &str, state: &mut RPState) -> Result<(), Exit> {
   Ok(())
 }
 
+
+fn eval_one(c: char, state: &mut RPState) -> Result<(), Exit> {
+  if let Some(readtable) = state.readtables.get_mut(&state.mode) {
+    if let Some(action) = readtable.action_map.get_mut(&c) {
+      (Rc::clone(&action))(c, state)
+    } else {
+      (Rc::clone(&readtable.default_action))(c, state)
+    }
+  } else {
+    Err(err_msg("No readtable found for this mode".into()))
+  }
+}
+