use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::io::Write;
use std::rc::Rc;
use rustyline::CompletionType;
use rustyline::config::Configurer;
use crate::action::Action;
use crate::context::Context;
use crate::engine::Engine;
use crate::error::MyResult;
use crate::helper::{CommandEditor, CommandHelper};
use crate::undo::Undo;
use crate::util;
use crate::value::Value;
pub 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>, &mut W) -> MyResult<bool>, &'static str, &'static str),
EngineUndo(fn(&mut Engine<W>, &mut Undo, &mut Undo, Option<&str>) -> MyResult<bool>, &'static str, &'static str),
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Completion {
Keyword,
Filename,
}
pub enum Directive<W: Write> {
EngineAll(fn(&mut Engine<W>, &mut W, Vec<String>) -> MyResult<bool>, Completion, &'static str),
}
pub enum Description {
Separator(String),
Operation((String, String, String, String)),
Directive((String, String, String)),
}
pub type Operations<W> = HashMap<String, Rc<Operation<W>>>;
pub type Directives<W> = HashMap<String, Rc<Directive<W>>>;
pub type Definitions<W> = BTreeMap<String, (Vec<Action<W>>, String)>;
pub type Descriptions = Vec<Description>;
pub struct Interface<W: Write> {
operations: Operations<W>,
directives: Directives<W>,
definitions: Definitions<W>,
descriptions: Descriptions,
}
impl<W: Write> Interface<W> {
pub fn new() -> Interface<W> {
let operations = Operations::new();
let directives = Directives::new();
let definitions = Definitions::new();
let descriptions = Descriptions::new();
return Interface { operations, directives, definitions, descriptions };
}
pub fn build() -> Interface<W> {
let interface = Self::new();
let interface = interface
.with_separator("Arithmetic operations")
.with_operation(vec!["add", "+"], Operation::ValueTwo(Value::calc_add), "Add two values")
.with_operation(vec!["sub", "-"], Operation::ValueTwo(Value::calc_sub), "Subtract two values")
.with_operation(vec!["mul", "*"], Operation::ValueTwo(Value::calc_mul), "Multiply two values")
.with_operation(vec!["div", "/"], Operation::ValueTwo(Value::calc_div), "Divide two values")
.with_operation(vec!["mod", "%"], Operation::ValueTwo(Value::calc_mod), "Modulo two values")
.with_operation(vec!["neg"], Operation::ValueOne(Value::calc_neg), "Find the negative")
.with_operation(vec!["inv"], Operation::ValueOne(Value::calc_inv), "Find the inverse")
.with_operation(vec!["pow"], Operation::ValueTwo(Value::calc_pow), "Raise to the power")
.with_operation(vec!["sqrt"], Operation::ValueOne(Value::calc_sqrt), "Find the square root")
.with_operation(vec!["sum"], Operation::ValueAll(Value::calc_sum), "Sum all values")
.with_operation(vec!["prod"], Operation::ValueAll(Value::calc_prod), "Multiply all values");
let interface = interface
.with_separator("Bitwise operations")
.with_operation(vec!["and"], Operation::ValueTwo(Value::calc_and), "Bitwise AND two values")
.with_operation(vec!["or"], Operation::ValueTwo(Value::calc_or), "Bitwise OR two values")
.with_operation(vec!["xor"], Operation::ValueTwo(Value::calc_xor), "Bitwise XOR two values")
.with_operation(vec!["shl"], Operation::ValueTwo(Value::calc_shl), "Shift left (multiply by power of 2)")
.with_operation(vec!["shr"], Operation::ValueTwo(Value::calc_shr), "Shift right (divide by power of 2)");
let interface = interface
.with_separator("Time operations")
.with_operation(vec!["now"], Operation::ValueNone(Value::calc_now), "Get the current time")
.with_operation(vec!["plain"], Operation::ValueOne(Value::cast_plain), "Convert to a plain value")
.with_operation(vec!["delta"], Operation::ValueOne(Value::cast_delta), "Convert to a delta value")
.with_operation(vec!["time"], Operation::ValueOne(Value::cast_time), "Convert to a time value");
let interface = interface
.with_separator("Formatting commands")
.with_operation(vec!["dec"], Operation::ContextNone(Context::set_decimal), "Parse and format values as decimal")
.with_operation(vec!["hex"], Operation::ContextNone(Context::set_hexadecimal), "Parse and format values as hexadecimal")
.with_operation(vec!["sep"], Operation::ContextNone(Context::set_separator), "Include a separator")
.with_operation(vec!["nosep"], Operation::ContextNone(Context::no_separator), "Include no separator")
.with_operation(vec!["dp"], Operation::ContextOne(Context::set_precision), "Use fixed decimal places")
.with_operation(vec!["nodp"], Operation::ContextNone(Context::no_precision), "Use free decimal places");
let interface = interface
.with_separator("Stack commands")
.with_operation(vec!["clear", "c"], Operation::EngineUndo(Engine::clear_values, "*", ""), "Remove all values from the stack")
.with_operation(vec!["pop", "p"], Operation::EngineUndo(Engine::pop_value, "N", ""), "Remove a value from the stack")
.with_operation(vec!["dup", "d"], Operation::EngineUndo(Engine::dup_value, "N", "N N"), "Duplicate a value on the stack")
.with_operation(vec!["swap", "s"], Operation::EngineUndo(Engine::swap_values, "N N", "N N"), "Swap two values on the stack")
.with_operation(vec!["cut"], Operation::EngineUndo(Engine::cut_value, "N", ""), "Cut a value to the internal clipboard")
.with_operation(vec!["copy"], Operation::EngineUndo(Engine::copy_value, "N", "N"), "Copy a value to the internal clipboard")
.with_operation(vec!["paste"], Operation::EngineUndo(Engine::paste_value, "", "N"), "Paste a value from the internal clipboard");
let interface = interface
.with_separator("History commands")
.with_operation(vec!["undo", "u"], Operation::EngineUndo(Engine::undo_stack, "", ""), "Undo the last operation")
.with_operation(vec!["redo", "r"], Operation::EngineUndo(Engine::redo_stack, "", ""), "Redo the next operation")
.with_operation(vec!["hist", "h"], Operation::EngineNone(Engine::show_history, "", ""), "Show all undo/redo history");
let interface = interface
.with_separator("General directives")
.with_directive("import", Directive::EngineAll(Engine::import_file, Completion::Filename, "F"), "Import file (expect filename)")
.with_directive("export", Directive::EngineAll(Engine::export_file, Completion::Filename, "F"), "Export file (expect filename)")
.with_directive("define", Directive::EngineAll(Engine::define_function, Completion::Keyword, "K *"), "Define function (expect keyword then values and operations)");
let interface = interface
.with_separator("General commands")
.with_operation(vec!["show"], Operation::EngineNone(Engine::show_stack, "", ""), "Show all values on the stack")
.with_operation(vec!["help"], Operation::EngineNone(Engine::show_help, "", ""), "Show this help text");
return interface;
}
fn with_separator(mut self, description: &str) -> Interface<W> {
self.descriptions.push(Description::Separator(String::from(description)));
return self;
}
fn with_operation(
mut self,
keywords: Vec<&str>,
operation: Operation<W>,
description: &str,
) -> Interface<W> {
let operation = Rc::new(operation);
for keyword in keywords.iter() {
self.operations.insert(keyword.to_string(), Rc::clone(&operation));
}
let keywords = Self::describe_keywords(keywords);
let (input, output) = Self::describe_arguments(&operation);
self.descriptions.push(Description::Operation((
input,
keywords,
output,
String::from(description),
)));
return self;
}
fn with_directive(
mut self,
keyword: &str,
directive: Directive<W>,
description: &str,
) -> Interface<W> {
let Directive::EngineAll(_, _, output) = directive;
let directive = Rc::new(directive);
self.directives.insert(keyword.to_string(), directive);
self.descriptions.push(Description::Directive((
String::from(keyword),
String::from(output),
String::from(description),
)));
return self;
}
#[cfg(test)]
fn with_definition(
mut self,
keyword: &str,
actions: Vec<Action<W>>,
description: String,
) -> Interface<W> {
self.add_definition(keyword, actions, description);
return self;
}
pub fn add_definition(
&mut self,
keyword: &str,
actions: Vec<Action<W>>,
description: String,
) {
self.definitions.insert(keyword.to_string(), (actions, description));
}
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 {
Operation::ValueNone(_) => (String::from(""), String::from("N")),
Operation::ValueOne(_) => (String::from("N"), String::from("N")),
Operation::ValueTwo(_) => (String::from("N N"), String::from("N")),
Operation::ValueAll(_) => (String::from("*"), String::from("N")),
Operation::ContextNone(_) => (String::from(""), String::from("")),
Operation::ContextOne(_) => (String::from("N"), String::from("")),
Operation::EngineNone(_, input, output) => (String::from(*input), String::from(*output)),
Operation::EngineUndo(_, input, output) => (String::from(*input), String::from(*output)),
}
}
pub fn create_editor(&self) -> MyResult<CommandEditor> {
let mut editor = CommandEditor::new()?;
editor.set_helper(Some(CommandHelper::new()));
editor.set_completion_type(CompletionType::List);
self.adjust_editor(&mut editor);
return Ok(editor);
}
pub fn adjust_editor(&self, editor: &mut CommandEditor) {
if let Some(helper) = editor.helper_mut() {
helper.set_commands(self.get_commands());
helper.set_completions(self.get_completions());
}
}
fn get_commands(&self) -> Vec<String> {
let commands = self.operations.keys()
.chain(self.directives.keys())
.chain(self.definitions.keys())
.map(String::clone)
.collect::<BTreeSet<String>>();
return commands.into_iter()
.collect::<Vec<String>>();
}
fn get_completions(&self) -> HashMap<String, Completion> {
let mut directives = HashMap::new();
for (keyword, directive) in self.directives.iter() {
let Directive::EngineAll(_, completion, _) = directive.as_ref();
directives.insert(String::from(keyword), *completion);
}
return directives;
}
pub fn get_operation(&self, token: &str) -> Option<Rc<Operation<W>>> {
self.operations.get(token).map(Rc::clone)
}
pub fn get_directive(&self, token: &str) -> Option<Rc<Directive<W>>> {
self.directives.get(token).map(Rc::clone)
}
pub fn get_definition(&self, token: &str) -> Option<Vec<Action<W>>> {
self.definitions.get(token).map(|(x, _)| x).map(Vec::clone)
}
pub fn show_help(&self, writer: &mut W, script: bool) -> MyResult<()> {
let padding1 = if script { "" } else { " " };
for description in self.descriptions.iter() {
match description {
Description::Separator(description) => {
writeln!(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!(
writer,
"{}{}{} {}{}{}{}{}",
padding1,
padding2,
input,
keyword,
padding3,
output,
padding4,
description,
)?;
}
Description::Directive((keyword, output, description)) => {
let padding2 = util::create_padding(' ', 8, keyword.len());
let padding3 = util::create_padding(' ', 5, output.len());
writeln!(
writer,
"{} {}{}{}{}{}",
padding1,
keyword,
padding2,
output,
padding3,
description,
)?;
}
}
}
if !self.definitions.is_empty() {
writeln!(writer, "{}Defined functions:", padding1)?;
for (keyword, (_, description)) in self.definitions.iter() {
let padding2 = util::create_padding(' ', 13, keyword.len());
writeln!(
writer,
"{} {}{}Function \"{}\"",
padding1,
keyword,
padding2,
description,
)?;
}
}
return Ok(());
}
}
#[cfg(test)]
pub mod tests {
use std::collections::HashMap;
use pretty_assertions::assert_eq;
use crate::engine::Engine;
use crate::error::MyResult;
use crate::interface::{Completion, Directive, Interface, Operation};
use crate::util::tests::BufferWriter;
use crate::value::Value;
#[test]
fn test_completes_commands() {
let expected_commands = vec![
"eight",
"five",
"four",
"nine",
"one",
"seven",
"six",
"ten",
"three",
"two",
];
let expected_completions = HashMap::from([
(String::from("six"), Completion::Filename),
(String::from("seven"), Completion::Filename),
(String::from("eight"), Completion::Keyword),
]);
let interface = Interface::<BufferWriter>::new()
.with_operation(vec!["one", "two"], Operation::ValueNone(dummy_operation), "")
.with_operation(vec!["three", "four"], Operation::ValueNone(dummy_operation), "")
.with_operation(vec!["five"], Operation::ValueNone(dummy_operation), "")
.with_directive("six", Directive::EngineAll(dummy_directive, Completion::Filename, ""), "")
.with_directive("seven", Directive::EngineAll(dummy_directive, Completion::Filename, ""), "")
.with_directive("eight", Directive::EngineAll(dummy_directive, Completion::Keyword, ""), "")
.with_definition("nine", vec![], String::from(""))
.with_definition("ten", vec![], String::from(""));
assert_eq!(interface.get_commands(), expected_commands);
assert_eq!(interface.get_completions(), expected_completions);
}
fn dummy_operation() -> MyResult<Value> {
Ok(Value::new(None))
}
fn dummy_directive(
_engine: &mut Engine<BufferWriter>,
_writer: &mut BufferWriter,
_tokens: Vec<String>,
) -> MyResult<bool> {
Ok(false)
}
const EXPECTED_HELP: &'static str = "\
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 Parse and format values as decimal
hex Parse 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 directives:
import F Import file (expect filename)
export F Export file (expect filename)
define K * Define function (expect keyword then values and operations)
General commands:
show Show all values on the stack
help Show this help text
";
const EXPECTED_FUNCTIONS: &'static str = "\
Defined functions:
answer Function \"6 7 mul\"
cube Function \"3 pow\"
noop Function \"\"
";
#[test]
fn test_shows_help_no_functions() {
let expected = EXPECTED_HELP;
let interface = Interface::build();
let mut writer = BufferWriter::new();
assert!(interface.show_help(&mut writer, true).is_ok());
assert_eq!(writer.buffer, expected);
}
#[test]
fn test_shows_help_with_functions() {
let expected = EXPECTED_HELP.to_string() + EXPECTED_FUNCTIONS;
let mut interface = Interface::build();
let mut writer = BufferWriter::new();
interface.add_definition("noop", vec![], String::from(""));
interface.add_definition("answer", vec![], String::from("6 7 mul"));
interface.add_definition("cube", vec![], String::from("3 pow"));
assert!(interface.show_help(&mut writer, true).is_ok());
assert_eq!(writer.buffer, expected);
}
}