Using the REPL

The Relision library provides a generic read, evaluate, print loop (a REPL) that is available through the `relision::repl::Repl` struct. This provides the following services.

  • Loading and saving of command history
  • Reading lines from the prompt with history
  • Detecting and executing commands, including providing help
  • Word wrapping of the output
  • Coloring of error and other message types.

The REPL support a concept of commands. These are indicated by a leading colon, such as :help.

The minimum that must be done is to make a new instance of the Repl struct and then invoke its run method. This will not load or save any history, and it will provide the default set of commands and will, by default, just echo other lines.

The one wrinkle is that you must provide some data to the constructor. Why this is so will become clear later. For now, let's just provide &() to the constructor.

  1. use relision::repl;
  2. fn main() {
  3. let mut repl = repl::Repl::new(());
  4. repl.run();
  5. }

We can try this.

Commands start with a colon (:).  Enter :help for help.
> :help
Commands can be abbreviated to the first few characters that uniquely identify them.

:help       Access the help system.
:history    Provide help on commands.
:info       Provide system information.
:quit       Quit the REPL.

> :fred
Error: Command fred not found.
> fred
> :quit

Let's add a custom evaluator for lines. Our “evaluation” will consist of just echoing the line, converted to all uppercase and if the line is “QUIT” then we will also exit the REPL.

To do this we need to create a function that takes a &String and a relision::repl::ReplContext. The &String is the line to evaluate, and the ReplContext packages information from the REPL that we might need. The return is an Option<String> and a Boolean. The return value is interpreted by the REPL as follows.

  • If the returned Option<String> is not None then it is output.
  • If the Boolean is true then the REPL continues, and if it is false the REPL terminates.

The function we want looks like this.

  1. fn echo(line: &String, _context: ReplContext) -> (Option<String>, bool) {
  2. let answer = line.to_uppercase();
  3. let cont = answer != "QUIT";
  4. (Some(answer), cont)
  5. }

We install it as the evaluation function after creating the instance and before invoking run.

  1. fn main() {
  2. let mut repl = repl::Repl::new(&());
  3. repl.set_eval(echo);
  4. repl.run();
  5. }

Let's try this.

Commands start with a colon (:).  Enter :help for help.
> :help
Commands can be abbreviated to the first few characters that uniquely identify them.

:help       Access the help system.
:history    Provide help on commands.
:info       Provide system information.
:quit       Quit the REPL.

> :fred
Error: Command fred not found.
> fred
FRED
> quit
QUIT

We can add a new command to the REPL using the add_command method. We need to provide the command name, the help text, and a callback for the command. The callback has the same form as for the evaluation method.

We will use our echo function for this, but we don't want to quit when the user enters quit, so we just modify the returned Boolean to always be true. Again, we need to install this command after creating the REPL instance and before invoking run. While we are at it, let's also install a “no-op” command that does nothing. We could do this with a closure, as before: |_,_| (None, true), but let's use a function pointer, instead.

We will also change the prompt, using set_prompt.

  1. fn noop(_line: &String, _context: ReplContext) -> (Option<String>, bool) {
  2. (None, true)
  3. }
  4.  
  5. fn main() {
  6. let mut repl = repl::Repl::new(&());
  7. repl.set_eval(echo);
  8.  
  9. // Install a custom command (using a closure).
  10. repl.add_command(
  11. "echo".to_string(),
  12. "Echo the rest of the line in uppercase.".to_string(),
  13. "Usage: :echo [line]\n\
  14. Echo the provided line, after converting it to all upper case."
  15. .to_string(),
  16. |line, context| (echo(line, context).0, true),
  17. );
  18.  
  19. // Install a custom colon command (using a function pointer).
  20. repl.add_command(
  21. "noop".to_string(),
  22. "Perform no action.".to_string(),
  23. "Usage: :noop\n\
  24. Ignore any arguments and perform no action. Could be a comment."
  25. .to_string(),
  26. noop,
  27. );
  28.  
  29. // Set the prompt and start the REPL.
  30. repl.set_prompt("repl> ".to_string());
  31. repl.run();
  32. }

Again, let's try this.

Commands start with a colon (:).  Enter :help for help.
repl> :help
Commands can be abbreviated to the first few characters that uniquely identify them.

:echo       Echo the rest of the line in uppercase.
:help       Access the help system.
:history    Provide help on commands.
:info       Provide system information.
:noop       Perform no action.
:quit       Quit the REPL.

repl> :echo I'm not shouting.
I'M NOT SHOUTING.
repl> :noop This is ignored.
repl> :echo quit
QUIT
repl> quit
QUIT

Our functions will probably want to do something a little more sophisticated, so let's take a look at ReplContext. We get some important things packaged in this struct.

  • The line editor (provided by the rustyline crate) being used by the REPL, which gives us access to line history as well as the ability to read lines ourselves and have those lines be part of the same history.
  • The terminal (provided by the console crate) that lets us provide a consistent output, though this is not essential.
  • The current value of the “quiet” flag, that lets us decide whether we should provide output. If we simply return some string, that string is always printed.
  • Some user data, which can hold anything we want.
  • lib/repl.txt
  • Last modified: 2019/01/03 07:30
  • by sprowell