rpn_cli/read/
interface.rs

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