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}