Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
lib:repl [2019/01/03 07:30]
sprowell
lib:repl [2020/11/15 23:14] (current)
sprowell Stopped at printing; more needs to be done.
Line 9: Line 9:
   * Coloring of error and other message types.   * 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 REPL supports a concept of //commands// These are indicated by a leading colon, such as '':help''.
  
 ===== Simplest Use ===== ===== Simplest Use =====
Line 15: Line 15:
 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 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.+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.
  
 <code Rust [enable_line_numbers="true"]> <code Rust [enable_line_numbers="true"]>
 use relision::repl; use relision::repl;
 +use relision::configure_logging;
 fn main() { fn main() {
 +    configure_logging();
     let mut repl = repl::Repl::new(());     let mut repl = repl::Repl::new(());
     repl.run();     repl.run();
Line 25: Line 27:
 </code> </code>
  
-We can try this.+Note the ''configure_logging'' call.  The Relision library uses the ''log'' crate to send log messages.  If you are using your own logging framework, you should set it up however you want, but you can use this library call to configure it using the ''RELISION_LOG'' environment variable, if you wish. 
 + 
 +We can try this.  This example is ''repl_example1.rs'' in the ''examples'' folder.  You should be able to build and run it with the command ''<nowiki>cargo run --example repl_example1</nowiki>''.
  
 <code> <code>
Line 47: Line 51:
 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. 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.+To do this we need to create a function that takes a ''&String'' and a ''&mut 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 returned ''Option<String>'' is //not// ''None'' then it is output //regardless of the quiet setting//.
   * If the Boolean is ''true'' then the REPL continues, and if it is ''false'' the REPL terminates.   * If the Boolean is ''true'' then the REPL continues, and if it is ''false'' the REPL terminates.
  
Line 55: Line 59:
  
 <code Rust [enable_line_numbers="true"]> <code Rust [enable_line_numbers="true"]>
-fn echo(line: &String, _context: ReplContext) -> (Option<String>, bool) {+use relision::repl::ReplContext; 
 + 
 +fn echo<U>(line: &String, _context: &mut ReplContext<U>) -> (Option<String>, bool) {
     let answer = line.to_uppercase();     let answer = line.to_uppercase();
     let cont = answer != "QUIT";     let cont = answer != "QUIT";
Line 62: Line 68:
 </code> </code>
  
-We install it as the evaluation function after creating the instance and before invoking ''run''.+The ''<U>'' will be used later when we introduce user data, but for now you can ignore it.  Also note that the ''mut'' on context will be required later when we read lines from the editor, which is in the context and must be mutable. 
 + 
 +We install ''echo'' as the evaluation function after creating the instance and before invoking ''run''.  The full example looks as follows.
  
 <code Rust [enable_line_numbers="true"]> <code Rust [enable_line_numbers="true"]>
 +use relision::repl;
 +use relision::repl::ReplContext;
 +
 +fn echo<U>(line: &String, _context: &mut ReplContext<U>) -> (Option<String>, bool) {
 +    let answer = line.to_uppercase();
 +    let cont = answer != "QUIT";
 +    (Some(answer), cont)
 +}
 +
 fn main() { fn main() {
-    let mut repl = repl::Repl::new(&());+    let mut repl = repl::Repl::new(());
     repl.set_eval(echo);     repl.set_eval(echo);
     repl.run();     repl.run();
Line 72: Line 89:
 </code> </code>
  
-Let's try this.+Let's try this.  You can run this from the source distribution with ''<nowiki>cargo run --example repl_example2</nowiki>''.
  
 <code> <code>
Line 101: Line 118:
  
 <code Rust [enable_line_numbers="true"]> <code Rust [enable_line_numbers="true"]>
-fn noop(_line: &String, _context: ReplContext) -> (Option<String>, bool) {+use relision::repl; 
 +use relision::repl::ReplContext; 
 + 
 +fn noop<U>(_line: &String, _context: &mut ReplContext<U>) -> (Option<String>, bool) {
     (None, true)     (None, true)
 +}
 +
 +fn echo<U>(line: &String, _context: &mut ReplContext<U>) -> (Option<String>, bool) {
 +    let answer = line.to_uppercase();
 +    let cont = answer != "QUIT";
 +    (Some(answer), cont)
 } }
  
Line 108: Line 134:
     let mut repl = repl::Repl::new(&());     let mut repl = repl::Repl::new(&());
     repl.set_eval(echo);     repl.set_eval(echo);
-    + 
     // Install a custom command (using a closure).     // Install a custom command (using a closure).
     repl.add_command(     repl.add_command(
Line 118: Line 144:
         |line, context| (echo(line, context).0, true),         |line, context| (echo(line, context).0, true),
     );     );
 + 
     // Install a custom colon command (using a function pointer).     // Install a custom colon command (using a function pointer).
     repl.add_command(     repl.add_command(
Line 128: Line 154:
         noop,         noop,
     );     );
-    + 
     // Set the prompt and start the REPL.     // Set the prompt and start the REPL.
     repl.set_prompt("repl> ".to_string());     repl.set_prompt("repl> ".to_string());
Line 135: Line 161:
 </code> </code>
  
-Again, let's try this.+Again, let's try this.  Note that this is ''repl_example3'' in the distribution.
  
 <code> <code>
Line 162: Line 188:
 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. 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 [[https://crates.io/crates/rustyline|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 line editor (provided by the [[https://crates.io/crates/rustyline|rustyline]] crate) being used by the REPL, which gives us access to line history as well as the ability to read lines ourselves and (potentially) have those lines be part of the same history.
   * The terminal (provided by the [[https://crates.io/crates/console|console]] crate) that lets us provide a consistent output, though this is not essential.   * The terminal (provided by the [[https://crates.io/crates/console|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.+  * The current value of the "quiet" flag, that lets us decide whether we //should// provide output.  Note that if our callbacks return some string, that string is always printed.
   * Some user data, which can hold anything we want.   * Some user data, which can hold anything we want.
 +
 +==== Reading a Line ====
 +
 +You can read a line from the console using ''context.editor.readline(prompt)'' Here ''prompt'' is a string prompt to print.  The result will be one of the following.
 +
 +  * ''Ok(text)'' if text is successfully read
 +  * ''Err(ReadlineError::Interrupted)'' if CTRL+C is pressed
 +  * ''Err(ReadliineError::Eof)'' if CTRL+D is pressed or the input stream ends
 +  * ''Err(_)'' if some other error occurs
 +
 +Lines read in this way are not automatically added to the history; you can use ''context.editor.add_history_entry(text.clone())'' to add ''text'' to the history.
 +
 +==== Printing ====
 +
 +The context provides ''context.term''
 +
 +Let's add a guessing game to the program.  We will use the user data to hold the number of times the user has won and lost.
 +
 +<code Rust [enable_line_numbers="true"]>
 +pub struct Counts {
 +    pub win: u32,
 +    pub lose: u32,
 +}
 +</code>
 +
 +Now we will specialize ''ReplContext'' on this struct in the callbacks.  Let's write the guessing game code.
 +
 +<code Rust [enable_line_numbers="true"]>
 +fn guess(_line: &String, context: &mut ReplContext<Counts>) -> (Option<String>, bool) {
 +    let limit = 10;
 +    let high = 100;
 +    // Tell the user the rules.
 +    sayln!(context, "I'm choosing a number in the interval [1,{}].", high);
 +    sayln!(context, "You have played {} games and won {}.",
 +        context.get().win + context.get().lose, context.get().win);
 +    sayln!(context, "Try to guess it!  You have {} guesses.", limit);
 +    // Pick a random number.
 +    let secret = rand::thread_rng().gen_range(1, high+1);
 +    // Collect guesses.
 +    let mut attempt = 1;
 +    while attempt < limit {
 +        // Get the next guess.
 +        let line = context.editor.readline(
 +            format!("Enter your guess (attempt {}): ", attempt).as_str());
 +        match line {
 +            Ok(text) => {
 +                sayln!(context, "[ {} ]", text);
 +                match text.trim().parse::<u32>() {
 +                    Ok(number) => {
 +                        if number < secret {
 +                            sayln!(context, "Too low.");
 +                        } else if number > secret {
 +                            sayln!(context, "Too high.");
 +                        } else {
 +                            sayln!(context, "That's it!");
 +                            context.get_mut().win += 1;
 +                            return (None, true);
 +                        }
 +                    },
 +                    Err(_) => {
 +                        sayln!(context, "Please enter a number.");
 +                    }
 +                }
 +            },
 +            Err(ReadlineError::Interrupted) => {
 +                // This is a CTRL+C.  Stop the game.
 +                sayln!(context, "Game stopped by CTRL+C.");
 +                return (None, true);
 +            },
 +            Err(ReadlineError::Eof) => {
 +                // This is end of file (CTRL+D).  Stop the game.
 +                sayln!(context, "Game stopped by CTRL+D.");
 +                return (None, true);
 +            },
 +            Err(err) => {
 +                // This is just an error.  Continue.
 +                error!("{:?}", err);
 +            }
 +        }
 +        attempt += 1;
 +    } // Collect guesses.
 +    context.get_mut().lose += 1;
 +    sayln!(context, "You ran out of attempts!");
 +    (None, true)
 +}
 +</code>
 +
 +There is a lot going on in this function.  Note the ''context.editor.readline'' This function take a prompt and requests a line from the user through the configured editor.  You can see how the different kinds of errors are handled.  Note that lines read in this manner do not automatically go into the history.
 +
 +If you wanted to add the line to the history, you would do the following.
 +
 +<code Rust [enable_line_numbers=true]>
 +context.editor.add_history_entry(text.clone());
 +</code>
 +
 +