2013-09-06
REPLs (Read-Eval–Print Loop) can be handy little things. In UAV::Pilot[1], the `uav` shell takes arbitrary Perl expressions and `eval()`'s them.
Before integrating with AnyEvent[2], handling the prompt was done by Term::ReadLine::Gnu[3]. When AnyEvent was integrated, I wanted the shell to use AnyEvent's non-blocking I/O, so it was migrated to AnyEvent::ReadLine::Gnu[4].
This also handles command history. Hit the 'Up' arrow to get your previous command. No code is necessary; AnyEvent::ReadLine::Gnu does it for you.
ReadLine also has options for tab-completion. I would like to add this to the `uav` shell eventually.
Using the AnyEvent version is quite simple. You pass a callback that takes input. In the `uav` callback, we only run the code when it ends with a semicolon (ignoring trailing whitespace). If it doesn't, we save it in a buffer and wait for more input.
Here's how this is implemented in UAV::Pilot:
my $readline; $readline = AnyEvent::ReadLine::Gnu->new( prompt => 'uav> ', on\_line => sub { my ($line) = @\_; add\_cmd( $line ); if( $line =~ /; \\s\* \\z/x ) { my $cmd = full\_cmd; $readline->hide; my $do\_continue = run\_cmd( $cmd, $repl ); $readline->show;$cv->send( $do\_continue ) unless $do\_continue; } }, );
The `add_cmd()` method adds the input to the buffer. If that did end with a semicolon, then we call `full_cmd()` to get back the text of the code. It also clears the buffer. `run_cmd()` then `eval()`'s the code. If we're meant to exit the program, it returns false, which we handle with the `$cv->send`.
The `$readline->hide` and `$readline->show` calls stop ReadLine from outputting the prompt when we might have other output going.