use std::collections::HashMap;
use std::io::{BufRead, BufReader, Write};
use std::rc::Rc;
use fileinput::FileInput;
use rustyline::CompletionType;
use rustyline::config::Configurer;
use crate::config::Config;
use crate::context::{Context, Format};
use crate::engine::Operation::*;
use crate::error::{EngineError, MyError, MyResult};
use crate::helper::{CommandEditor, CommandHelper};
use crate::undo::Undo;
use crate::util;
use crate::value::Value;
enum Operation<W: Write> {
ValueNone(fn() -> MyResult<Value>),
ValueOne(fn(Value) -> MyResult<Value>),
ValueTwo(fn(Value, Value) -> MyResult<Value>),
ValueAll(fn(Vec<Value>) -> MyResult<Value>),
ContextNone(fn(&mut Context)),
ContextOne(fn(&mut Context, Value)),
EngineNone(fn(&mut Engine<W>) -> MyResult<bool>, &'static str, &'static str),
EngineUndo(fn(&mut Engine<W>, &mut Undo, &mut Undo, Option<&str>) -> MyResult<bool>, &'static str, &'static str),
}
enum Description {
Separator(String),
Operation((String, String, String, String)),
}
type Operations<W> = HashMap<String, Rc<Operation<W>>>;
type Descriptions = Vec<Description>;
impl<W: Write> Operation<W> {
pub fn create_operations() -> (Vec<String>, Operations<W>, Descriptions) {
let mut commands = Vec::new();
let mut operations = Operations::new();
let mut descriptions = Descriptions::new();
let mut append = |keywords: Vec<&str>, operation: Option<Operation<W>>, description: &str| {
if let Some(operation) = operation {
let operation = Rc::new(operation);
for keyword in keywords.iter() {
commands.push(keyword.to_string());
operations.insert(keyword.to_string(), Rc::clone(&operation));
}
let keywords = Self::describe_keywords(keywords);
let (input, output) = Self::describe_arguments(&operation);
descriptions.push(Description::Operation((input, keywords, output, String::from(description))));
} else {
descriptions.push(Description::Separator(String::from(description)));
}
};
append(vec![], None, "Arithmetic operations");
append(vec!["add", "+"], Some(ValueTwo(Value::calc_add)), "Add two values");
append(vec!["sub", "-"], Some(ValueTwo(Value::calc_sub)), "Subtract two values");
append(vec!["mul", "*"], Some(ValueTwo(Value::calc_mul)), "Multiply two values");
append(vec!["div", "/"], Some(ValueTwo(Value::calc_div)), "Divide two values");
append(vec!["mod", "%"], Some(ValueTwo(Value::calc_mod)), "Modulo two values");
append(vec!["neg"], Some(ValueOne(Value::calc_neg)), "Find the negative");
append(vec!["inv"], Some(ValueOne(Value::calc_inv)), "Find the inverse");
append(vec!["pow"], Some(ValueTwo(Value::calc_pow)), "Raise to the power");
append(vec!["sqrt"], Some(ValueOne(Value::calc_sqrt)), "Find the square root");
append(vec!["sum"], Some(ValueAll(Value::calc_sum)), "Sum all values");
append(vec!["prod"], Some(ValueAll(Value::calc_prod)), "Multiply all values");
append(vec![], None, "Bitwise operations");
append(vec!["and"], Some(ValueTwo(Value::calc_and)), "Bitwise AND two values");
append(vec!["or"], Some(ValueTwo(Value::calc_or)), "Bitwise OR two values");
append(vec!["xor"], Some(ValueTwo(Value::calc_xor)), "Bitwise XOR two values");
append(vec!["shl"], Some(ValueTwo(Value::calc_shl)), "Shift left (multiply by power of 2)");
append(vec!["shr"], Some(ValueTwo(Value::calc_shr)), "Shift right (divide by power of 2)");
append(vec![], None, "Time operations");
append(vec!["now"], Some(ValueNone(Value::calc_now)), "Get the current time");
append(vec!["plain"], Some(ValueOne(Value::cast_plain)), "Convert to a plain value");
append(vec!["delta"], Some(ValueOne(Value::cast_delta)), "Convert to a delta value");
append(vec!["time"], Some(ValueOne(Value::cast_time)), "Convert to a time value");
append(vec![], None, "Formatting commands");
append(vec!["dec"], Some(ContextNone(Context::set_decimal)), "Expect and format values as decimal");
append(vec!["hex"], Some(ContextNone(Context::set_hexadecimal)), "Expect and format values as hexadecimal");
append(vec!["sep"], Some(ContextNone(Context::set_separator)), "Include a separator");
append(vec!["nosep"], Some(ContextNone(Context::no_separator)), "Include no separator");
append(vec!["dp"], Some(ContextOne(Context::set_precision)), "Use fixed decimal places");
append(vec!["nodp"], Some(ContextNone(Context::no_precision)), "Use free decimal places");
append(vec![], None, "Stack commands");
append(vec!["clear", "c"], Some(EngineUndo(Engine::clear_values, "*", "")), "Remove all values from the stack");
append(vec!["pop", "p"], Some(EngineUndo(Engine::pop_value, "N", "")), "Remove a value from the stack");
append(vec!["dup", "d"], Some(EngineUndo(Engine::dup_value, "N", "N N")), "Duplicate a value on the stack");
append(vec!["swap", "s"], Some(EngineUndo(Engine::swap_values, "N N", "N N")), "Swap two values on the stack");
append(vec!["cut"], Some(EngineUndo(Engine::cut_value, "N", "")), "Cut a value to the internal clipboard");
append(vec!["copy"], Some(EngineUndo(Engine::copy_value, "N", "N")), "Copy a value to the internal clipboard");
append(vec!["paste"], Some(EngineUndo(Engine::paste_value, "", "N")), "Paste a value from the internal clipboard");
append(vec![], None, "History commands");
append(vec!["undo", "u"], Some(EngineUndo(Engine::undo_stack, "", "")), "Undo the last operation");
append(vec!["redo", "r"], Some(EngineUndo(Engine::redo_stack, "", "")), "Redo the next operation");
append(vec!["hist", "h"], Some(EngineNone(Engine::show_history, "", "")), "Show all undo/redo history");
append(vec![], None, "General commands");
append(vec!["show"], Some(EngineNone(Engine::show_stack, "", "")), "Show all values on the stack");
append(vec!["help"], Some(EngineNone(Engine::show_help, "", "")), "Show this help text");
return (commands, operations, descriptions);
}
fn describe_keywords(keywords: Vec<&str>) -> String {
if keywords.len() == 2 {
let primary = keywords[0];
let secondary = keywords[1];
if primary.starts_with(secondary) {
let chop = secondary.len();
return format!("{}({})", secondary, &primary[chop..]);
}
}
let keywords = keywords.iter().map(|x| x.to_string()).collect::<Vec<String>>();
return keywords.join(",");
}
fn describe_arguments(operation: &Operation<W>) -> (String, String) {
match operation {
ValueNone(_) => (String::from(""), String::from("N")),
ValueOne(_) => (String::from("N"), String::from("N")),
ValueTwo(_) => (String::from("N N"), String::from("N")),
ValueAll(_) => (String::from("*"), String::from("N")),
ContextNone(_) => (String::from(""), String::from("")),
ContextOne(_) => (String::from("N"), String::from("")),
EngineNone(_, input, output) => (String::from(*input), String::from(*output)),
EngineUndo(_, input, output) => (String::from(*input), String::from(*output)),
}
}
}
pub struct Engine<W: Write> {
config: Config,
writer: W,
editor: CommandEditor,
operations: Operations<W>,
descriptions: Descriptions,
context: Context,
stack: Vec<Value>,
undos: Vec<Undo>,
redos: Vec<Undo>,
clip: Option<Value>,
dirty: bool,
script: bool,
}
impl<W: Write> Engine<W> {
pub fn new(config: Config, writer: W) -> MyResult<Engine<W>> {
let (commands, operations, descriptions) = Operation::create_operations();
let editor = Self::create_editor(commands)?;
let context = Context::new();
let stack = Vec::new();
let undos = Vec::new();
let redos = Vec::new();
let engine = Engine {
config,
writer,
editor,
operations,
descriptions,
context,
stack,
undos,
redos,
clip: None,
dirty: false,
script: false,
};
return Ok(engine);
}
fn create_editor(commands: Vec<String>) -> MyResult<CommandEditor> {
let mut editor = CommandEditor::new()?;
editor.set_helper(Some(CommandHelper::new(commands)));
editor.set_completion_type(CompletionType::List);
return Ok(editor);
}
pub fn parse_input(&mut self) -> MyResult<()> {
if !self.config.values.is_empty() {
self.parse_values()
} else if !self.config.paths.is_empty() || atty::isnt(atty::Stream::Stdin) || atty::isnt(atty::Stream::Stdout) {
self.parse_files()
} else {
self.parse_loop()
}
}
fn parse_values(&mut self) -> MyResult<()> {
self.script = true;
let values = self.config.values.drain(..).collect::<Vec<String>>();
for value in values {
self.parse_line(&value)?;
}
self.show_simple()?;
return Ok(());
}
fn parse_files(&mut self) -> MyResult<()> {
self.script = true;
let reader = FileInput::new(&self.config.paths);
let reader = BufReader::new(reader);
for line in reader.lines() {
let line = line?;
self.parse_line(&line)?;
}
self.show_simple()?;
return Ok(());
}
fn parse_loop(&mut self) -> MyResult<()> {
self.script = false;
loop {
if self.dirty {
self.show_stack()?;
self.dirty = false;
}
let prompt = self.create_prompt();
let line = self.editor.readline(prompt)?;
self.editor.add_history_entry(&line)?;
if let Err(error) = self.parse_line(&line) {
if let MyError::Engine(error) = error {
writeln!(self.writer, " {}", error)?;
self.dirty = false;
} else {
return Err(error);
}
}
}
}
fn create_prompt(&self) -> &'static str {
match self.context.format {
Format::Base10 => "dec> ",
Format::Base16(_) => "hex> ",
}
}
fn parse_line(&mut self, line: &str) -> MyResult<()> {
let mut empty = true;
let mut undo = Undo::default();
let mut redo = Undo::default();
let (line, comment) = Self::split_comment(line, self.script);
for token in line.split_ascii_whitespace() {
empty = false;
if let Err(error) = self.parse_token(&mut undo, &mut redo, token) {
undo.apply_to(&mut self.stack);
return Err(error);
}
}
if let Some(comment) = comment {
empty = false;
if let Err(error) = self.apply_comment(&mut undo, comment) {
undo.apply_to(&mut self.stack);
return Err(error);
}
}
self.commit_undo(undo, redo);
self.dirty |= empty;
return Ok(());
}
fn split_comment(line: &str, script: bool) -> (&str, Option<&str>) {
if let Some(stop) = line.find('#') {
let start = stop + 1;
let values = &line[..stop].trim();
let comment = &line[start..].trim();
if comment.is_empty() || script {
return (values, None);
} else {
return (values, Some(comment));
}
} else {
let values = line.trim();
return (values, None);
}
}
fn parse_token(&mut self, undo: &mut Undo, redo: &mut Undo, token: &str) -> MyResult<()> {
if let Some(operation) = self.get_operation(token) {
match operation.as_ref() {
ValueNone(function) => {
let result = function()?;
self.push_single(undo, result, Some(token));
self.dirty = true;
}
ValueOne(function) => {
let value = self.pop_single(undo, None)?;
let result = function(value)?;
self.push_single(undo, result, Some(token));
self.dirty = true;
}
ValueTwo(function) => {
let (value1, value2) = self.pop_double(undo, None)?;
let result = function(value1, value2)?;
self.push_single(undo, result, Some(token));
self.dirty = true;
}
ValueAll(function) => {
let values = self.stack.drain(..).collect::<Vec<Value>>();
let result = function(values.clone())?;
self.stack.push(result);
self.dirty = true;
undo.merge(1, values, Some(token));
}
ContextNone(function) => {
function(&mut self.context);
self.dirty = true;
}
ContextOne(function) => {
let value = self.pop_single(undo, Some(token))?;
function(&mut self.context, value);
self.dirty = true;
}
EngineNone(function, _, _) => {
self.dirty |= function(self)?;
}
EngineUndo(function, _, _) => {
self.dirty |= function(self, undo, redo, Some(token))?;
}
}
} else {
match Value::from_string(&self.context, token) {
Ok(value) => {
self.push_single(undo, value, Some(token));
}
Err(_) => {
let error = EngineError::ParseError(String::from(token));
return Err(MyError::from(error));
}
}
}
return Ok(());
}
fn apply_comment(&mut self, undo: &mut Undo, comment: &str) -> MyResult<()> {
let value = self.pop_single(undo, Some("#"))?;
let value = value.with_comment(comment);
self.push_single(undo, value, Some(comment));
self.dirty = true;
return Ok(());
}
fn commit_undo(&mut self, undo: Undo, redo: Undo) {
if !undo.is_empty() {
self.undos.push(undo);
self.redos.clear();
} else if !redo.is_empty() {
self.redos.push(redo);
}
}
fn get_operation(&self, token: &str) -> Option<Rc<Operation<W>>> {
self.operations.get(token).map(Rc::clone)
}
fn pop_single(&mut self, undo: &mut Undo, token: Option<&str>) -> MyResult<Value> {
if let Some(value) = self.stack.pop() {
undo.merge(0, vec![value.clone()], token);
return Ok(value);
} else {
return Err(MyError::from(EngineError::MissingValue));
}
}
fn pop_double(&mut self, undo: &mut Undo, token: Option<&str>) -> MyResult<(Value, Value)> {
if self.stack.len() >= 2 {
let value2 = self.stack.pop().unwrap_or_default();
let value1 = self.stack.pop().unwrap_or_default();
undo.merge(0, vec![value1.clone(), value2.clone()], token);
return Ok((value1, value2));
} else {
return Err(MyError::from(EngineError::MissingValue));
}
}
fn push_single(&mut self, undo: &mut Undo, value: Value, token: Option<&str>) {
self.stack.push(value);
undo.merge(1, vec![], token);
}
fn clear_values(&mut self, undo: &mut Undo, redo: &mut Undo, token: Option<&str>) -> MyResult<bool> {
let values = self.stack.drain(..).collect();
undo.merge(0, values, token);
redo.clear();
return Ok(true);
}
fn pop_value(&mut self, undo: &mut Undo, redo: &mut Undo, token: Option<&str>) -> MyResult<bool> {
self.pop_single(undo, token)?;
redo.clear();
return Ok(true);
}
fn dup_value(&mut self, undo: &mut Undo, redo: &mut Undo, token: Option<&str>) -> MyResult<bool> {
if let Some(value) = self.stack.last() {
self.push_single(undo, value.clone(), token);
redo.clear();
return Ok(true);
} else {
return Err(MyError::from(EngineError::MissingValue));
}
}
fn swap_values(&mut self, undo: &mut Undo, redo: &mut Undo, token: Option<&str>) -> MyResult<bool> {
let (value1, value2) = self.pop_double(undo, None)?;
self.push_single(undo, value2, None);
self.push_single(undo, value1, token);
redo.clear();
return Ok(true);
}
fn cut_value(&mut self, undo: &mut Undo, redo: &mut Undo, token: Option<&str>) -> MyResult<bool> {
let value = self.pop_single(undo, token)?;
self.clip = Some(value);
redo.clear();
return Ok(true);
}
fn copy_value(&mut self, _undo: &mut Undo, _redo: &mut Undo, _token: Option<&str>) -> MyResult<bool> {
if let Some(value) = self.stack.last() {
self.clip = Some(value.clone());
return Ok(false);
} else {
return Err(MyError::from(EngineError::MissingValue));
}
}
fn paste_value(&mut self, undo: &mut Undo, redo: &mut Undo, token: Option<&str>) -> MyResult<bool> {
if let Some(clip) = &self.clip {
self.push_single(undo, clip.clone(), token);
redo.clear();
return Ok(true);
} else {
return Err(MyError::from(EngineError::MissingClip));
}
}
fn undo_stack(&mut self, undo: &mut Undo, redo: &mut Undo, _token: Option<&str>) -> MyResult<bool> {
if !undo.is_empty() {
self.redos.push(undo.apply_to(&mut self.stack));
redo.clear();
return Ok(true);
} else if let Some(mut undo) = self.undos.pop() {
self.redos.push(undo.apply_to(&mut self.stack));
redo.clear();
return Ok(true);
} else {
return Err(MyError::from(EngineError::MissingUndo));
}
}
fn redo_stack(&mut self, undo: &mut Undo, redo: &mut Undo, _token: Option<&str>) -> MyResult<bool> {
if undo.is_empty() {
if !redo.is_empty() {
self.undos.push(redo.apply_to(&mut self.stack));
return Ok(true);
} else if let Some(mut redo) = self.redos.pop() {
self.undos.push(redo.apply_to(&mut self.stack));
return Ok(true);
}
}
return Err(MyError::from(EngineError::MissingRedo));
}
fn show_history(&mut self) -> MyResult<bool> {
self.limit_history(10)?;
return Ok(false);
}
fn limit_history(&mut self, limit: usize) -> MyResult<()> {
for undo in self.undos.iter().rev().take(limit).rev() {
writeln!(self.writer, " {}", undo)?;
}
writeln!(self.writer, " <==")?;
for redo in self.redos.iter().rev().take(limit) {
writeln!(self.writer, " {}", redo)?;
}
return Ok(());
}
fn show_simple(&mut self) -> MyResult<()> {
self.measure_hexadecimal();
for value in &self.stack {
let (integer, fraction, _) = value.to_strings(&self.context);
writeln!(self.writer, "{}{}", integer, fraction)?;
}
return Ok(());
}
fn show_stack(&mut self) -> MyResult<bool> {
self.measure_hexadecimal();
let table = self.stack.iter()
.map(|value| value.to_strings(&self.context))
.collect::<Vec<(String, String, Option<String>)>>();
let integer_width = table.iter()
.map(|(x, _, _)| x.len())
.max()
.unwrap_or_default();
let fraction_width = table.iter()
.map(|(_, x, _)| x.len())
.max()
.unwrap_or_default();
for (integer, fraction, comment) in table {
let padding = util::create_padding(' ', integer_width + 2, integer.len());
write!(self.writer, "{}{}{}", padding, integer, fraction)?;
if let Some(comment) = comment {
let padding = util::create_padding(' ', fraction_width + 1, fraction.len());
write!(self.writer, "{}# {}", padding, comment)?;
}
writeln!(self.writer)?;
}
return Ok(false);
}
fn measure_hexadecimal(&mut self) {
if let Format::Base16(_) = self.context.format {
let chunks = self.stack.iter()
.map(|x| x.measure_hexadecimal())
.max()
.unwrap_or_default();
self.context.format = Format::Base16(chunks);
}
}
fn show_help(&mut self) -> MyResult<bool> {
let padding1 = if self.script { "" } else { " " };
for description in self.descriptions.iter() {
match description {
Description::Separator(description) => {
writeln!(self.writer, "{}{}:", padding1, description)?;
}
Description::Operation((input, keyword, output, description)) => {
let padding2 = util::create_padding(' ', 3, input.len());
let padding3 = util::create_padding(' ', 8, keyword.len());
let padding4 = util::create_padding(' ', 5, output.len());
writeln!(
self.writer,
"{}{}{} {}{}{}{}{}",
padding1,
padding2,
input,
keyword,
padding3,
output,
padding4,
description,
)?;
}
}
}
return Ok(false);
}
}
#[cfg(test)]
pub mod tests {
use std::io;
use std::io::Write;
use std::rc::Rc;
use pretty_assertions::assert_eq;
use crate::config::Config;
use crate::context::{Context, Format};
use crate::engine::{Engine, Operation};
use crate::error::MyResult;
use crate::undo::tests::create_undo;
use crate::value::Value;
#[test]
fn test_split_comment_finds_first_hash_if_interactive() {
assert_eq!(Engine::<BufferWriter>::split_comment("\tone two three\t", false), ("one two three", None));
assert_eq!(Engine::<BufferWriter>::split_comment("\tone two three #\t", false), ("one two three", None));
assert_eq!(Engine::<BufferWriter>::split_comment("\tone # two # three\t", false), ("one", Some("two # three")));
}
#[test]
fn test_split_comment_finds_first_hash_if_scripted() {
assert_eq!(Engine::<BufferWriter>::split_comment("\tone two three\t", true), ("one two three", None));
assert_eq!(Engine::<BufferWriter>::split_comment("\tone two three #\t", true), ("one two three", None));
assert_eq!(Engine::<BufferWriter>::split_comment("\tone # two # three\t", true), ("one", None));
}
#[test]
fn test_create_values_modifies_stack() {
let expected_stack = create_values(vec!["1", "2", "3", "4", "5", "6"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(3, vec![], vec!["4", "5", "6"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_comment_modifies_current_line() {
let expected_stack = vec![
create_value("1"),
create_value("2"),
create_value("3"),
create_value("42").with_comment("the answer"),
];
let expected_undos = vec![
create_undo(2, vec![], vec!["1", "2"]),
create_undo(2, vec![], vec!["3", "42", "#", "the answer"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2").is_ok());
assert!(parse_line(&mut engine, "3 42 # the answer").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_comment_modifies_previous_line() {
let expected_stack = vec![
create_value("1"),
create_value("2"),
create_value("3"),
create_value("42").with_comment("the answer"),
];
let expected_undos = vec![
create_undo(4, vec![], vec!["1", "2", "3", "42"]),
create_undo(1, vec!["42"], vec!["#", "the answer"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3 42").is_ok());
assert!(parse_line(&mut engine, "# the answer").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_comment_fails_cleanly() {
let expected_stack = vec![];
let expected_undos = vec![];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "# the answer").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_unknown_operation_fails_cleanly() {
let expected_stack = create_values(vec!["1", "2", "3"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "4 5 xyz 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Invalid number or keyword: xyz\n");
}
#[test]
fn test_nullary_operation_modifies_stack() {
let expected_stack = create_values(vec!["0"]);
let expected_undos = vec![
create_undo(1, vec![], vec!["zero"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
engine.operations.insert(String::from("zero"), create_nullary_operation());
assert!(parse_line(&mut engine, "zero").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
fn create_nullary_operation<W: Write>() -> Rc<Operation<W>> {
let operation = Operation::ValueNone(|| Ok(Value::default()));
return Rc::new(operation);
}
#[test]
fn test_unary_operation_modifies_stack() {
let expected_stack = create_values(vec!["1", "-2"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(1, vec!["2", "3"], vec!["pop", "neg"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "pop neg").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_unary_operation_fails_cleanly() {
let expected_stack = create_values(vec!["1", "2", "3"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "pop pop pop neg 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_binary_operation_modifies_stack() {
let expected_stack = create_values(vec!["1", "5"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(1, vec!["2", "3"], vec!["add"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_binary_operation_fails_cleanly() {
let expected_stack = create_values(vec!["1", "2", "3"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "pop pop add 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_series_operation_modifies_stack() {
let expected_stack = create_values(vec!["6"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(1, vec!["1", "2", "3"], vec!["sum"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "sum").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_clear_operation_modifies_stack() {
let expected_stack = vec![];
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(0, vec!["1", "2", "3"], vec!["clear"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "clear").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_pop_operation_modifies_stack() {
let expected_stack = create_values(vec!["1", "2"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(0, vec!["3"], vec!["pop"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "pop").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_pop_operation_fails_cleanly() {
let expected_stack = create_values(vec!["1", "2", "3"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "pop pop pop pop 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_dup_operation_modifies_stack() {
let expected_stack = create_values(vec!["1", "2", "3", "3"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(1, vec![], vec!["dup"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "dup").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_dup_operation_fails_cleanly() {
let expected_stack = create_values(vec!["1", "2", "3"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "pop pop pop dup 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_swap_operation_modifies_stack() {
let expected_stack = create_values(vec!["1", "3", "2"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(2, vec!["2", "3"], vec!["swap"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "swap").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_swap_operation_fails_cleanly() {
let expected_stack = create_values(vec!["1", "2", "3"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "pop pop swap 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_cut_operation_copies_value() {
let expected_stack = vec![];
let expected_undos = vec![
create_undo(1, vec![], vec!["42"]),
create_undo(0, vec!["42"], vec!["cut"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "42").is_ok());
assert!(parse_line(&mut engine, "cut").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.clip, Some(create_value("42")));
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_cut_operation_fails_cleanly() {
let expected_stack = vec![];
let expected_undos = vec![];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "cut").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.clip, None);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_copy_operation_copies_value() {
let expected_stack = create_values(vec!["42"]);
let expected_undos = vec![
create_undo(1, vec![], vec!["42"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "42").is_ok());
assert!(parse_line(&mut engine, "copy").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.clip, Some(create_value("42")));
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_copy_operation_fails_cleanly() {
let expected_stack = vec![];
let expected_undos = vec![];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "copy").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.clip, None);
assert_eq!(engine.writer.buffer, "Not enough values on stack\n");
}
#[test]
fn test_paste_operation_copies_value() {
let expected_stack = create_values(vec!["42", "42"]);
let expected_undos = vec![
create_undo(1, vec![], vec!["42"]),
create_undo(1, vec![], vec!["paste"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "42").is_ok());
assert!(parse_line(&mut engine, "copy").is_ok());
assert!(parse_line(&mut engine, "paste").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.clip, Some(create_value("42")));
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_paste_operation_fails_cleanly() {
let expected_stack = create_values(vec!["42"]);
let expected_undos = vec![
create_undo(1, vec![], vec!["42"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "42").is_ok());
assert!(parse_line(&mut engine, "paste").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.clip, None);
assert_eq!(engine.writer.buffer, "No value on clipboard\n");
}
#[test]
fn test_single_undo_resets_previous_line() {
let expected_stack = create_values(vec!["4", "5", "6"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
];
let expected_redos = vec![
create_undo(2, vec!["11"], vec!["add"]),
];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_double_undo_resets_previous_lines() {
let expected_stack = vec![];
let expected_undos = vec![];
let expected_redos = vec![
create_undo(2, vec!["11"], vec!["add"]),
create_undo(0, vec!["4", "5", "6"], vec!["4", "5", "6"]),
];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo undo").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_single_undo_with_suffix_resets_previous_line() {
let expected_stack = create_values(vec!["4", "30"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
create_undo(1, vec!["5", "6"], vec!["mul"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo mul").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_double_undo_with_suffix_resets_previous_lines() {
let expected_stack = create_values(vec!["1", "6"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["1", "2", "3"]),
create_undo(1, vec!["2", "3"], vec!["mul"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "1 2 3").is_ok());
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo undo mul").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_single_undo_with_prefix_resets_current_line() {
let expected_stack = create_values(vec!["4", "11"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
create_undo(1, vec!["5", "6"], vec!["add"]),
];
let expected_redos = vec![
create_undo(2, vec!["15"], vec!["add"]),
];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add undo").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_double_undo_with_prefix_resets_current_and_previous_lines() {
let expected_stack = create_values(vec!["4", "5", "6"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
];
let expected_redos = vec![
create_undo(2, vec!["15"], vec!["add"]),
create_undo(2, vec!["11"], vec!["add"]),
];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add undo undo").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_single_undo_with_prefix_and_suffix_resets_current_line() {
let expected_stack = create_values(vec!["44"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
create_undo(1, vec!["5", "6"], vec!["add"]),
create_undo(1, vec!["4", "11"], vec!["mul"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add undo mul").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_double_undo_with_prefix_and_suffix_resets_current_and_previous_lines() {
let expected_stack = create_values(vec!["4", "30"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
create_undo(1, vec!["5", "6"], vec!["mul"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add undo undo mul").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_triple_undo_operation_fails_cleanly() {
let expected_stack = vec![];
let expected_undos = vec![];
let expected_redos = vec![
create_undo(2, vec!["11"], vec!["add"]),
create_undo(0, vec!["4", "5", "6"], vec!["4", "5", "6"]),
];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo undo undo").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "Start of undo history\n");
}
#[test]
fn test_single_redo_operation_modifies_stack() {
let expected_stack = create_values(vec!["4", "11"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
create_undo(1, vec!["5", "6"], vec!["add"]),
];
let expected_redos = vec![
create_undo(2, vec!["15"], vec!["add"]),
];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo undo").is_ok());
assert!(parse_line(&mut engine, "redo").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_double_redo_operation_modifies_stack() {
let expected_stack = create_values(vec!["15"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
create_undo(1, vec!["5", "6"], vec!["add"]),
create_undo(1, vec!["4", "11"], vec!["add"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo undo").is_ok());
assert!(parse_line(&mut engine, "redo redo").is_ok());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_single_redo_operation_with_prefix_fails_cleanly() {
let expected_stack = create_values(vec!["4", "5", "6"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
];
let expected_redos = vec![
create_undo(2, vec!["15"], vec!["add"]),
create_undo(2, vec!["11"], vec!["add"]),
];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo undo").is_ok());
assert!(parse_line(&mut engine, "mul redo 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "End of undo history\n");
}
#[test]
fn test_triple_redo_operation_fails_cleanly() {
let expected_stack = create_values(vec!["15"]);
let expected_undos = vec![
create_undo(3, vec![], vec!["4", "5", "6"]),
create_undo(1, vec!["5", "6"], vec!["add"]),
create_undo(1, vec!["4", "11"], vec!["add"]),
];
let expected_redos = vec![];
let mut engine = create_engine();
assert!(parse_line(&mut engine, "4 5 6").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "add").is_ok());
assert!(parse_line(&mut engine, "undo undo").is_ok());
assert!(parse_line(&mut engine, "redo redo redo 999").is_err());
assert_eq!(engine.stack, expected_stack);
assert_eq!(engine.undos, expected_undos);
assert_eq!(engine.redos, expected_redos);
assert_eq!(engine.writer.buffer, "End of undo history\n");
}
#[test]
fn test_prints_error_on_invalid_decimal() {
let mut engine = create_engine();
assert!(parse_line(&mut engine, "xyz").is_err());
assert_eq!(engine.writer.buffer, "Invalid number or keyword: xyz\n");
}
#[test]
fn test_prints_error_on_invalid_hexadecimal() {
let mut engine = create_engine();
engine.context.set_hexadecimal();
assert!(parse_line(&mut engine, "xyz").is_err());
assert_eq!(engine.writer.buffer, "Invalid number or keyword: xyz\n");
}
#[test]
fn test_shows_history_with_lower_limit() {
let expected = "\
34
56 78 90
<==
98 76
54
";
let mut engine = create_engine();
engine.undos.push(create_undo(0, vec![], vec!["12"]));
engine.undos.push(create_undo(0, vec![], vec!["34"]));
engine.undos.push(create_undo(0, vec![], vec!["56", "78", "90"]));
engine.redos.push(create_undo(0, vec![], vec!["32", "10"]));
engine.redos.push(create_undo(0, vec![], vec!["54"]));
engine.redos.push(create_undo(0, vec![], vec!["98", "76"]));
assert!(engine.limit_history(2).is_ok());
assert_eq!(engine.writer.buffer, indent_multiline(expected));
}
#[test]
fn test_shows_history_with_higher_limit() {
let expected = "\
12
34
56 78 90
<==
98 76
54
32 10
";
let mut engine = create_engine();
engine.undos.push(create_undo(0, vec![], vec!["12"]));
engine.undos.push(create_undo(0, vec![], vec!["34"]));
engine.undos.push(create_undo(0, vec![], vec!["56", "78", "90"]));
engine.redos.push(create_undo(0, vec![], vec!["32", "10"]));
engine.redos.push(create_undo(0, vec![], vec!["54"]));
engine.redos.push(create_undo(0, vec![], vec!["98", "76"]));
assert!(engine.limit_history(4).is_ok());
assert_eq!(engine.writer.buffer, indent_multiline(expected));
}
#[test]
fn test_shows_stack_with_no_values() {
let mut engine = create_engine();
assert!(engine.show_stack().is_ok());
assert_eq!(engine.writer.buffer, "");
}
#[test]
fn test_shows_stack_as_decimal_with_ratios() {
let expected = "\
99999 # first
123.45 # second
3.142 # third
3.142
";
let mut engine = create_engine();
engine.stack.push(create_value("99999").with_comment("first"));
engine.stack.push(create_value("123.45").with_comment("second"));
engine.stack.push(create_value("3.142").with_comment("third"));
engine.stack.push(create_value("3.142"));
assert!(engine.show_stack().is_ok());
assert_eq!(engine.writer.buffer, indent_multiline(expected));
}
#[test]
fn test_shows_stack_as_hexadecimal_single() {
let expected = "\
7fffffff # first
00000001 # second
00000000 # third
00000000
";
let mut engine = create_engine();
engine.context.separator = true;
engine.context.format = Format::Base16(0);
engine.stack.push(create_value("2147483647").with_comment("first"));
engine.stack.push(create_value("1").with_comment("second"));
engine.stack.push(create_value("0").with_comment("third"));
engine.stack.push(create_value("0"));
assert!(engine.show_stack().is_ok());
assert_eq!(engine.writer.buffer, indent_multiline(expected));
}
#[test]
fn test_shows_stack_as_hexadecimal_double() {
let expected = "\
00000001 00000000 # first
00000000 00000001 # second
00000000 00000000 # third
00000000 00000000
";
let mut engine = create_engine();
engine.context.separator = true;
engine.context.format = Format::Base16(0);
engine.stack.push(create_value("4294967296").with_comment("first"));
engine.stack.push(create_value("1").with_comment("second"));
engine.stack.push(create_value("0").with_comment("third"));
engine.stack.push(create_value("0"));
assert!(engine.show_stack().is_ok());
assert_eq!(engine.writer.buffer, indent_multiline(expected));
}
#[test]
fn test_shows_help() {
let expected = "\
Arithmetic operations:
N N add,+ N Add two values
N N sub,- N Subtract two values
N N mul,* N Multiply two values
N N div,/ N Divide two values
N N mod,% N Modulo two values
N neg N Find the negative
N inv N Find the inverse
N N pow N Raise to the power
N sqrt N Find the square root
* sum N Sum all values
* prod N Multiply all values
Bitwise operations:
N N and N Bitwise AND two values
N N or N Bitwise OR two values
N N xor N Bitwise XOR two values
N N shl N Shift left (multiply by power of 2)
N N shr N Shift right (divide by power of 2)
Time operations:
now N Get the current time
N plain N Convert to a plain value
N delta N Convert to a delta value
N time N Convert to a time value
Formatting commands:
dec Expect and format values as decimal
hex Expect and format values as hexadecimal
sep Include a separator
nosep Include no separator
N dp Use fixed decimal places
nodp Use free decimal places
Stack commands:
* c(lear) Remove all values from the stack
N p(op) Remove a value from the stack
N d(up) N N Duplicate a value on the stack
N N s(wap) N N Swap two values on the stack
N cut Cut a value to the internal clipboard
N copy N Copy a value to the internal clipboard
paste N Paste a value from the internal clipboard
History commands:
u(ndo) Undo the last operation
r(edo) Redo the next operation
h(ist) Show all undo/redo history
General commands:
show Show all values on the stack
help Show this help text
";
let mut engine = create_engine();
assert!(engine.show_help().is_ok());
assert_eq!(engine.writer.buffer, indent_multiline(expected));
}
fn indent_multiline(lines: &str) -> String {
lines.lines()
.map(|x| format!(" {}\n", x))
.collect::<Vec<String>>()
.join("")
}
pub fn create_value(number: &str) -> Value {
let context = Context::new();
Value::from_string(&context, number).unwrap()
}
pub fn create_values(values: Vec<&str>) -> Vec<Value> {
values.iter().map(|x| create_value(x)).collect()
}
fn create_engine() -> Engine<BufferWriter> {
let config = Config::default();
let writer = BufferWriter::new();
return Engine::new(config, writer).unwrap();
}
fn parse_line(engine: &mut Engine<BufferWriter>, line: &str) -> MyResult<()> {
let result = engine.parse_line(line);
if let Err(error) = &result {
writeln!(engine.writer, "{}", error)?;
}
return result;
}
struct BufferWriter {
buffer: String,
}
impl BufferWriter {
fn new() -> BufferWriter {
let buffer = String::new();
return BufferWriter { buffer };
}
}
impl Write for BufferWriter {
fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
unsafe { self.buffer.as_mut_vec().write(buffer) }
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
}