rpn_cli/
interface.rs

1use crate::action::Action;
2use crate::context::Context;
3use crate::engine::Engine;
4use crate::error::MyResult;
5use crate::helper::{CommandEditor, CommandHelper};
6use crate::undo::Undo;
7use crate::util;
8use crate::value::Value;
9use itertools::Itertools;
10use rustyline::config::Configurer;
11use rustyline::CompletionType;
12use std::collections::{BTreeMap, BTreeSet, HashMap};
13use std::io::Write;
14use std::rc::Rc;
15use std::time::SystemTime;
16
17pub enum Operation<W: Write> {
18    ValueNone(fn() -> MyResult<Value>),
19    ValueOne(fn(Value) -> MyResult<Value>),
20    ValueTwo(fn(Value, Value) -> MyResult<Value>),
21    ValueAll(fn(Vec<Value>) -> MyResult<Value>),
22    ValueTime(fn(&Option<SystemTime>) -> MyResult<Value>),
23    ContextNone(fn(&mut Context)),
24    ContextOne(fn(&mut Context, Value)),
25    EngineNone(fn(&mut Engine<W>, &mut W) -> MyResult<bool>, &'static str, &'static str),
26    EngineUndo(fn(&mut Engine<W>, &mut Undo, &mut Undo, Option<&str>) -> MyResult<bool>, &'static str, &'static str),
27}
28
29#[derive(Debug, PartialEq, Copy, Clone)]
30pub enum Completion {
31    Keyword,
32    Filename,
33}
34
35pub enum Directive<W: Write> {
36    EngineAll(fn(&mut Engine<W>, &mut W, Vec<String>) -> MyResult<bool>, Completion, &'static str),
37}
38
39pub enum Description {
40    Separator(String),
41    Operation((String, String, String, String)),
42    Directive((String, String, String)),
43}
44
45pub type Operations<W> = HashMap<String, Rc<Operation<W>>>;
46pub type Directives<W> = HashMap<String, Rc<Directive<W>>>;
47pub type Definitions<W> = BTreeMap<String, (Vec<Action<W>>, String)>;
48pub type Descriptions = Vec<Description>;
49
50pub struct Interface<W: Write> {
51    operations: Operations<W>,
52    directives: Directives<W>,
53    definitions: Definitions<W>,
54    descriptions: Descriptions,
55}
56
57impl<W: Write> Interface<W> {
58    pub fn new() -> Self {
59        let operations = Operations::new();
60        let directives = Directives::new();
61        let definitions = Definitions::new();
62        let descriptions = Descriptions::new();
63        Self { operations, directives, definitions, descriptions }
64    }
65
66    pub fn build() -> Self {
67        let interface = Self::new();
68        let interface = interface
69            .with_separator("Arithmetic operations")
70            .with_operation(vec!["add", "+"], Operation::ValueTwo(Value::calc_add), "Add two values")
71            .with_operation(vec!["sub", "-"], Operation::ValueTwo(Value::calc_sub), "Subtract two values")
72            .with_operation(vec!["mul", "*"], Operation::ValueTwo(Value::calc_mul), "Multiply two values")
73            .with_operation(vec!["div", "/"], Operation::ValueTwo(Value::calc_div), "Divide two values")
74            .with_operation(vec!["mod", "%"], Operation::ValueTwo(Value::calc_mod), "Modulo two values")
75            .with_operation(vec!["neg"], Operation::ValueOne(Value::calc_neg), "Find the negative")
76            .with_operation(vec!["inv"], Operation::ValueOne(Value::calc_inv), "Find the inverse")
77            .with_operation(vec!["pow"], Operation::ValueTwo(Value::calc_pow), "Raise to the power")
78            .with_operation(vec!["sqrt"], Operation::ValueOne(Value::calc_sqrt), "Find the square root")
79            .with_operation(vec!["sum"], Operation::ValueAll(Value::calc_sum), "Sum all values")
80            .with_operation(vec!["prod"], Operation::ValueAll(Value::calc_prod), "Multiply all values");
81        let interface = interface
82            .with_separator("Bitwise operations")
83            .with_operation(vec!["and"], Operation::ValueTwo(Value::calc_and), "Bitwise AND two values")
84            .with_operation(vec!["or"], Operation::ValueTwo(Value::calc_or), "Bitwise OR two values")
85            .with_operation(vec!["xor"], Operation::ValueTwo(Value::calc_xor), "Bitwise XOR two values")
86            .with_operation(vec!["shl"], Operation::ValueTwo(Value::calc_shl), "Shift left (multiply by power of 2)")
87            .with_operation(vec!["shr"], Operation::ValueTwo(Value::calc_shr), "Shift right (divide by power of 2)");
88        let interface = interface
89            .with_separator("Time operations")
90            .with_operation(vec!["now"], Operation::ValueTime(Value::calc_now), "Get the current time")
91            .with_operation(vec!["plain"], Operation::ValueOne(Value::cast_plain), "Convert to a plain value")
92            .with_operation(vec!["delta"], Operation::ValueOne(Value::cast_delta), "Convert to a delta value")
93            .with_operation(vec!["time"], Operation::ValueOne(Value::cast_time), "Convert to a time value");
94        let interface = interface
95            .with_separator("Formatting commands")
96            .with_operation(vec!["dec"], Operation::ContextNone(Context::set_dec), "Format values as decimal")
97            .with_operation(vec!["hex"], Operation::ContextNone(Context::set_hex), "Format values as hexadecimal")
98            .with_operation(vec!["sep"], Operation::ContextNone(Context::set_sep), "Include a separator")
99            .with_operation(vec!["nosep"], Operation::ContextNone(Context::no_sep), "Include no separator")
100            .with_operation(vec!["dp"], Operation::ContextOne(Context::set_dp), "Use fixed decimal places")
101            .with_operation(vec!["nodp"], Operation::ContextNone(Context::no_dp), "Use free decimal places");
102        let interface = interface
103            .with_separator("Stack commands")
104            .with_operation(vec!["clear", "c"], Operation::EngineUndo(Engine::clear_values, "*", ""), "Remove all values from the stack")
105            .with_operation(vec!["pop", "p"], Operation::EngineUndo(Engine::pop_value, "N", ""), "Remove a value from the stack")
106            .with_operation(vec!["dup", "d"], Operation::EngineUndo(Engine::dup_value, "N", "N N"), "Duplicate a value on the stack")
107            .with_operation(vec!["swap", "s"], Operation::EngineUndo(Engine::swap_values, "N N", "N N"), "Swap two values on the stack")
108            .with_operation(vec!["cut"], Operation::EngineUndo(Engine::cut_value, "N", ""), "Cut a value to the internal clipboard")
109            .with_operation(vec!["copy"], Operation::EngineUndo(Engine::copy_value, "N", "N"), "Copy a value to the internal clipboard")
110            .with_operation(vec!["paste"], Operation::EngineUndo(Engine::paste_value, "", "N"), "Paste a value from the internal clipboard");
111        let interface = interface
112            .with_separator("History commands")
113            .with_operation(vec!["undo", "u"], Operation::EngineUndo(Engine::undo_stack, "", ""), "Undo the last operation")
114            .with_operation(vec!["redo", "r"], Operation::EngineUndo(Engine::redo_stack, "", ""), "Redo the next operation")
115            .with_operation(vec!["hist", "h"], Operation::EngineNone(Engine::show_history, "", ""), "Show all undo/redo history");
116        let interface = interface
117            .with_separator("General directives")
118            .with_directive("import", Directive::EngineAll(Engine::import_file, Completion::Filename, "F"), "Import file (expect filename)")
119            .with_directive("export", Directive::EngineAll(Engine::export_file, Completion::Filename, "F"), "Export file (expect filename)")
120            .with_directive("define", Directive::EngineAll(Engine::define_function, Completion::Keyword, "K *"), "Define function (expect keyword then values and operations)");
121        let interface = interface
122            .with_separator("General commands")
123            .with_operation(vec!["show"], Operation::EngineNone(Engine::show_stack, "", ""), "Show all values on the stack")
124            .with_operation(vec!["help"], Operation::EngineNone(Engine::show_help, "", ""), "Show this help text");
125        interface
126    }
127
128    fn with_separator(mut self, description: &str) -> Self {
129        self.descriptions.push(Description::Separator(String::from(description)));
130        self
131    }
132
133    fn with_operation(
134        mut self,
135        keywords: Vec<&str>,
136        operation: Operation<W>,
137        description: &str,
138    ) -> Self {
139        let operation = Rc::new(operation);
140        for keyword in keywords.iter() {
141            self.operations.insert(keyword.to_string(), Rc::clone(&operation));
142        }
143        let keywords = Self::describe_keywords(keywords);
144        let (input, output) = Self::describe_arguments(&operation);
145        self.descriptions.push(Description::Operation((
146            input,
147            keywords,
148            output,
149            String::from(description),
150        )));
151        self
152    }
153
154    fn with_directive(
155        mut self,
156        keyword: &str,
157        directive: Directive<W>,
158        description: &str,
159    ) -> Self {
160        let Directive::EngineAll(_, _, output) = directive;
161        let directive = Rc::new(directive);
162        self.directives.insert(keyword.to_string(), directive);
163        self.descriptions.push(Description::Directive((
164            String::from(keyword),
165            String::from(output),
166            String::from(description),
167        )));
168        self
169    }
170
171    #[cfg(test)]
172    fn with_definition(
173        mut self,
174        keyword: &str,
175        actions: Vec<Action<W>>,
176        description: String,
177    ) -> Self {
178        self.add_definition(keyword, actions, description);
179        self
180    }
181
182    pub fn add_definition(
183        &mut self,
184        keyword: &str,
185        actions: Vec<Action<W>>,
186        description: String,
187    ) {
188        self.definitions.insert(keyword.to_string(), (actions, description));
189    }
190
191    fn describe_keywords(keywords: Vec<&str>) -> String {
192        if keywords.len() == 2 {
193            let primary = keywords[0];
194            let secondary = keywords[1];
195            if primary.starts_with(secondary) {
196                let chop = secondary.len();
197                return format!("{}({})", secondary, &primary[chop..]);
198            }
199        }
200        keywords.iter().map(|x| x.to_string()).join(",")
201    }
202
203    fn describe_arguments(operation: &Operation<W>) -> (String, String) {
204        match operation {
205            Operation::ValueNone(_) => (String::from(""), String::from("N")),
206            Operation::ValueOne(_) => (String::from("N"), String::from("N")),
207            Operation::ValueTwo(_) => (String::from("N N"), String::from("N")),
208            Operation::ValueAll(_) => (String::from("*"), String::from("N")),
209            Operation::ValueTime(_) => (String::from(""), String::from("N")),
210            Operation::ContextNone(_) => (String::from(""), String::from("")),
211            Operation::ContextOne(_) => (String::from("N"), String::from("")),
212            Operation::EngineNone(_, input, output) => (String::from(*input), String::from(*output)),
213            Operation::EngineUndo(_, input, output) => (String::from(*input), String::from(*output)),
214        }
215    }
216
217    pub fn create_editor(&self) -> MyResult<CommandEditor> {
218        let mut editor = CommandEditor::new()?;
219        editor.set_helper(Some(CommandHelper::new()));
220        editor.set_completion_type(CompletionType::List);
221        self.adjust_editor(&mut editor);
222        Ok(editor)
223    }
224
225    pub fn adjust_editor(&self, editor: &mut CommandEditor) {
226        if let Some(helper) = editor.helper_mut() {
227            helper.set_commands(self.get_commands());
228            helper.set_completions(self.get_completions());
229        }
230    }
231
232    fn get_commands(&self) -> Vec<String> {
233        let commands = self.operations.keys()
234            .chain(self.directives.keys())
235            .chain(self.definitions.keys())
236            .map(String::clone)
237            .collect::<BTreeSet<String>>();
238        commands.into_iter().collect::<Vec<String>>()
239    }
240
241    fn get_completions(&self) -> HashMap<String, Completion> {
242        let mut directives = HashMap::new();
243        for (keyword, directive) in self.directives.iter() {
244            let Directive::EngineAll(_, completion, _) = directive.as_ref();
245            directives.insert(String::from(keyword), *completion);
246        }
247        directives
248    }
249
250    pub fn get_operation(&self, token: &str) -> Option<Rc<Operation<W>>> {
251        self.operations.get(token).map(Rc::clone)
252    }
253
254    pub fn get_directive(&self, token: &str) -> Option<Rc<Directive<W>>> {
255        self.directives.get(token).map(Rc::clone)
256    }
257
258    pub fn get_definition(&self, token: &str) -> Option<Vec<Action<W>>> {
259        self.definitions.get(token).map(|(x, _)| x).map(Vec::clone)
260    }
261
262    pub fn show_help(&self, writer: &mut W, interact: bool) -> MyResult<()> {
263        let padding1 = if interact { "  " } else { "" };
264        for description in self.descriptions.iter() {
265            match description {
266                Description::Separator(description) => {
267                    writeln!(writer, "{}{}:", padding1, description)?;
268                }
269                Description::Operation((input, keyword, output, description)) => {
270                    let padding2 = util::create_padding(' ', 3, input.len(), 0);
271                    let padding3 = util::create_padding(' ', 8, keyword.len(), 0);
272                    let padding4 = util::create_padding(' ', 5, output.len(), 0);
273                    writeln!(
274                        writer,
275                        "{}{}{}  {}{}{}{}{}",
276                        padding1,
277                        padding2,
278                        input,
279                        keyword,
280                        padding3,
281                        output,
282                        padding4,
283                        description,
284                    )?;
285                }
286                Description::Directive((keyword, output, description)) => {
287                    let padding2 = util::create_padding(' ', 8, keyword.len(), 0);
288                    let padding3 = util::create_padding(' ', 5, output.len(), 0);
289                    writeln!(
290                        writer,
291                        "{}     {}{}{}{}{}",
292                        padding1,
293                        keyword,
294                        padding2,
295                        output,
296                        padding3,
297                        description,
298                    )?;
299                }
300            }
301        }
302        if !self.definitions.is_empty() {
303            writeln!(writer, "{}Defined functions:", padding1)?;
304            for (keyword, (_, description)) in self.definitions.iter() {
305                let padding2 = util::create_padding(' ', 13, keyword.len(), 1);
306                writeln!(
307                    writer,
308                    "{}     {}{}Function \"{}\"",
309                    padding1,
310                    keyword,
311                    padding2,
312                    description,
313                )?;
314            }
315        }
316        Ok(())
317    }
318}
319
320#[cfg(test)]
321pub mod tests {
322    use crate::engine::Engine;
323    use crate::error::MyResult;
324    use crate::interface::{Completion, Directive, Interface, Operation};
325    use crate::util::tests::BufferWriter;
326    use crate::value::Value;
327    use pretty_assertions::assert_eq;
328    use std::collections::HashMap;
329
330    #[test]
331    fn test_completes_commands() {
332        let expected_commands = vec![
333            "eight",
334            "five",
335            "four",
336            "nine",
337            "one",
338            "seven",
339            "six",
340            "ten",
341            "three",
342            "two",
343        ];
344        let expected_completions = HashMap::from([
345            (String::from("six"), Completion::Filename),
346            (String::from("seven"), Completion::Filename),
347            (String::from("eight"), Completion::Keyword),
348        ]);
349        let interface = Interface::<BufferWriter>::new()
350            .with_operation(vec!["one", "two"], Operation::ValueNone(dummy_operation), "")
351            .with_operation(vec!["three", "four"], Operation::ValueNone(dummy_operation), "")
352            .with_operation(vec!["five"], Operation::ValueNone(dummy_operation), "")
353            .with_directive("six", Directive::EngineAll(dummy_directive, Completion::Filename, ""), "")
354            .with_directive("seven", Directive::EngineAll(dummy_directive, Completion::Filename, ""), "")
355            .with_directive("eight", Directive::EngineAll(dummy_directive, Completion::Keyword, ""), "")
356            .with_definition("nine", vec![], String::from(""))
357            .with_definition("ten", vec![], String::from(""));
358        assert_eq!(interface.get_commands(), expected_commands);
359        assert_eq!(interface.get_completions(), expected_completions);
360    }
361
362    fn dummy_operation() -> MyResult<Value> {
363        Ok(Value::new(None))
364    }
365
366    fn dummy_directive(
367        _engine: &mut Engine<BufferWriter>,
368        _writer: &mut BufferWriter,
369        _tokens: Vec<String>,
370    ) -> MyResult<bool> {
371        Ok(false)
372    }
373
374    const EXPECTED_HELP: &'static str = "\
375Arithmetic operations:
376N N  add,+   N    Add two values
377N N  sub,-   N    Subtract two values
378N N  mul,*   N    Multiply two values
379N N  div,/   N    Divide two values
380N N  mod,%   N    Modulo two values
381  N  neg     N    Find the negative
382  N  inv     N    Find the inverse
383N N  pow     N    Raise to the power
384  N  sqrt    N    Find the square root
385  *  sum     N    Sum all values
386  *  prod    N    Multiply all values
387Bitwise operations:
388N N  and     N    Bitwise AND two values
389N N  or      N    Bitwise OR two values
390N N  xor     N    Bitwise XOR two values
391N N  shl     N    Shift left (multiply by power of 2)
392N N  shr     N    Shift right (divide by power of 2)
393Time operations:
394     now     N    Get the current time
395  N  plain   N    Convert to a plain value
396  N  delta   N    Convert to a delta value
397  N  time    N    Convert to a time value
398Formatting commands:
399     dec          Format values as decimal
400     hex          Format values as hexadecimal
401     sep          Include a separator
402     nosep        Include no separator
403  N  dp           Use fixed decimal places
404     nodp         Use free decimal places
405Stack commands:
406  *  c(lear)      Remove all values from the stack
407  N  p(op)        Remove a value from the stack
408  N  d(up)   N N  Duplicate a value on the stack
409N N  s(wap)  N N  Swap two values on the stack
410  N  cut          Cut a value to the internal clipboard
411  N  copy    N    Copy a value to the internal clipboard
412     paste   N    Paste a value from the internal clipboard
413History commands:
414     u(ndo)       Undo the last operation
415     r(edo)       Redo the next operation
416     h(ist)       Show all undo/redo history
417General directives:
418     import  F    Import file (expect filename)
419     export  F    Export file (expect filename)
420     define  K *  Define function (expect keyword then values and operations)
421General commands:
422     show         Show all values on the stack
423     help         Show this help text
424";
425    const EXPECTED_FUNCTIONS: &'static str = "\
426Defined functions:
427     answer       Function \"6 7 mul\"
428     cube         Function \"3 pow\"
429     very-long-a  Function \"\"
430     very-long-bb Function \"\"
431     very-long-ccc Function \"\"
432     very-long-dddd Function \"\"
433     very-long-eeeee Function \"\"
434";
435
436    #[test]
437    fn test_shows_help_no_functions() {
438        let expected = EXPECTED_HELP;
439        let interface = Interface::build();
440        let mut writer = BufferWriter::new();
441        assert!(interface.show_help(&mut writer, false).is_ok());
442        assert_eq!(writer.buffer, expected);
443    }
444
445    #[test]
446    fn test_shows_help_with_functions() {
447        let expected = EXPECTED_HELP.to_string() + EXPECTED_FUNCTIONS;
448        let mut interface = Interface::build();
449        let mut writer = BufferWriter::new();
450        interface.add_definition("answer", vec![], String::from("6 7 mul"));
451        interface.add_definition("cube", vec![], String::from("3 pow"));
452        interface.add_definition("very-long-a", vec![], String::from(""));
453        interface.add_definition("very-long-bb", vec![], String::from(""));
454        interface.add_definition("very-long-ccc", vec![], String::from(""));
455        interface.add_definition("very-long-dddd", vec![], String::from(""));
456        interface.add_definition("very-long-eeeee", vec![], String::from(""));
457        assert!(interface.show_help(&mut writer, false).is_ok());
458        assert_eq!(writer.buffer, expected);
459    }
460}