GIT

In the last post where I experimented with readline I mentioned hacking on a REPL.

There are things like Devel::REPL that’s supposed to be ‘Modern Perl shells’, and while I’m sure they’re all great, it’s not what I wanted.

re.pl

There’s a couple of things that I wanted from my REPL:

  • persistent lexical variables
  • fatal warnings
  • tab-completion for keywords, functions and modules (internal and external)
  • integrated perldoc support (why leave the shell?)
  • a vi keymap

Persistent lexical variables

Persistent lexical variables was something I didn’t know I wanted before I understood what it was. The Eval::WithLexicals module by mst performs some really cool magic. To illustrate what this means, consider the following:

use strict;
use feature 'say';

say eval while <>;

Let’s see what happens:

$_ = 4
4
++$_
1
my $foo = 42;
42
$foo += 10
10

And with the use of Eval::WithLexicals:

> my $foo = 42
42

> ++$foo
43

Without the use of persistent lexicals, a REPL is no better than simply using perl with the -f flag from the command line. This allows us to use modules and write full programs in the repl, one line at a time.

Fatality

Another nice module (well, ‘pragma’) by mst is strictures. It enables strict and makes all warnings fatal - a warning will cause the program to die (there’s some exceptions, which is explained in the documentation).

Tabcompletion

The tab-completion was simple, using Term::ReadLine::Gnu as described in the previous post. That post also covers how I finally managed to get a functional vi keymap.

So how would I go about getting a list of all Perl keywords? All core functions? A search for ‘keywords’ on the CPAN revealed the B::Keywords module, which exports lists of reserved barewords and symbol names. Just what I wanted.

Documentation

For the perldoc integration, we simply call out to system(). For perldoc tab-completion, I wanted an updated list of modules available on the local machine. It’d be way to expensive recreating that database every single time the repl is run, so I use Storable, and added a –genmod flag for recreating it.

Storable allows us to make arbitary Perl data structures persistent by saving them them to disk.

# Get available modules. If the hash of modules have been generated before,
# we can simply retrieve it again, saving time and cpu cycles

my @modules = get_installed_modules();

sub get_installed_modules {
  if(-f $module_db) {
    %modules = %{ retrieve($module_db) },
  }
  else {
    local $| = 1;
    print "Generating list of available modules...";
    map {
      s%.+(?:core|site|vendor)_perl/(.+)\z%$1%;
      s|/|::|g;
      s/\.pm\z//;
      ($_ =~ m/^5.10/) ? undef : $modules{$_}++;
    } File::Find::Rule->file()->name('*.pm')->in(@INC);
    printf("%s\n", (scalar(keys(%modules)) > 0) ? "[OK]" : "");
  }

  store(\%modules, $module_db);
  return keys %modules;
}

If the hash with modules have been generated one time, and the –genmod flag is not specified, we skip the generation step and simply retrieves the structure that we saved earlier.

Conclusion

It was fun hacking up this little REPL. I’ve familiarized myself with several really interesting modules that I’m now using frequently.

Even though the Eval::WithLexicals module might not be that useful for other projects, it’s still a really interesting piece of code, and I’d recommend you to read the source.

The Storable module, on the other hand, is extremely useful for all kinds of tasks where a give data structure holds a not-so-dynamic dataset, and the time it takes to generate it over and over again, even though data data might still be the same, is frustrating. Storable is included in the Perl core since 5.7.3.

** Installation **

Install the required modules from the CPAN first:

cpan Eval::WithLexicals Term::ReadLine::Gnu B::Keywords strictures

Clone the git repository:

git clone git://github.com/trapd00r/re.pl.git

Build and install:

perl Makefile.PL
make && su -c 'make install'