The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Repl::Loop -- A command interpreter for applications.

SYNOPSIS

    # Create a Repl.
    #    
    my $repl = new Repl::Loop;
    
    # Register some functionality.
    #
    $repl->registerCommand("print", new Repl::Cmd::PrintCmd);
    $repl->registerCommand("exit", new Repl::Cmd::ExitCmd($repl));
    $repl->registerCommand("sleep", new Repl::Cmd::SleepCmd());
    
    # Start the loop, the user can interact.
    #
    $repl->start();

DESCRIPTION

It is a read-eval-print loop that supports a lisp-like syntax. It is meant as the top-level for command line oriented applications or for telnet access into an application (for monitoring, configuration or debuggin). Behind the screens it is a small lisp interpreter, but the syntax is tweaked so that it is more suited for top-level commands. Some examples from the file system command library, these commands immitate the standard directory browsing commands:

    ls recursive=true
    ls files=true dirs=false
    pwd quiet=true
    

Some examples from the math library:

    print (+ 1 2 3)
    print (+ (* 2 3) (* 4 5))
    print (fac 5) 

The repl supports LISP expressions, but the outermost parenthesis for the topmost expression can be omitted for the convenience of the user so that the commands look like real commands. But the full power of LISP expressions can be used if necessary especially for formulating subexpressions. As a result, ls recursive=true is equivalent to (ls recursive=true).

Another unconventional concept in the command syntax is the presence of "pairs". These are syntactical constructs, they look like named parameters and they can be used as such. Again, this is for the convenience of the user. The difference with other REPL's is that the left side and the right side of a pair can be full expressions.

The read-eval-print loop understands basic constructs like tests, loops and so on but all real functionality should be added in command libraries. It is up to the developer to decide which functionality will be included in the loop. A number of command libraries are included, but these are not activated by default.

The REPL provides a service to your application, it provides a full blown expression language that you get for free. The only thing you have to do to create an application is to create one or more commands that can be glued together using the REPL commands. The parser will parse lists, pairs and strings for you (as a developer), it will evaluate subexpressions and will call your commands.

To be honest, the read-eval-print loop is in reality a read-eval loop. You have to do the printing yourself. It is up to the commands to decide whether the result should be printed or not. The "ls" command in in the file system library for example returns a list of file names. The printing can be turned on or off using the 'quiet' option and the command looks like "ls quiet=true". The command could be used to provide a list of path names as an input for other commands (that you write). So you can tap into the power of other commands while writing your own commands. While writing your application, the trick is to find a set of commands that work well together, that use each others results so that they can build on each others functionality.

EXPRESSION SYNTAX

In this section we will describe the complete syntax of the repl expressions. The evaluator understands a simplified lisp syntax. Explicitly lacking: data structures, data structure manipulation. It should be done using Perl commands and an underlying Perl model. The language on top should help to manipulate the underlying Perl model.

quote

It prevents evaluation of an expression: (quote <expr>) or the shorthand '<expr>. It is necessary to provide this construct so that the user can use unevaluated expressions to describe data structures or other parameters that can be provided to the commands.

if

The if expression has the form (if <bool-expr> <then-expr> <else-expr>). It is a special form because the evaluation of the then-expr or else-expr depends on the outcome of the test. Since the evaluation order of all boolean constructs is deviant from normal evaluation they have to be built into the core.

TRUTHY

Strings of the form (case insensitive) "true", "ok", "on", "yes", "y", "t".

FALSY

Undefined Perl values, zero, empty strings, empty arrays and strings of the form (case insensitive) "false", "nok", "off", "no", "n", "f".

while

(while <bool-expr> <expr>).

set

Changes an existing binding. It evaluates the value before setting, the result is the value: (set name val) | (set name=val). Set generates an error if the binding does not exist. A binding can initially be created using one of the following constructs. Afther a binding is created it can be modified with set.

A defvar

Which creates/overwrites a global binding.

A defun

Which (re-)binds a global variable to a lambda.

A let or let* block.

Which adds a number of bindings for the duration of a block.

Other

Some commands add something to the context too. It is up to the various commands to specify this.

get

Retrieves a binding. It does not do any evaluation: (get name) or the shorthand notational convenience $name does exactly the same thing. It does not do Perl-like interpolation in strings, don't let the shorthand notation mislead you.

defvar

Creates a global binding. It evaluates the value before setting, the result is the value: (defvar name val) | (defvar name=val). The value can be changed with set.

let, let*

Defines variables locally: (let ((var val) | var=val | var ...) <expr>)

and, or, not

Shortcut boolean evaluation operators.

eq

Has Perl equals semantics. It is the only data inspection that is implemented in the Eval. It is included because it is a standard Perl function applicable to all data structures.

eval

Evaluate an expression.

lambda

A nameless function, it contains a reference to the lexical context where it was defined. So this creates closures. It can be handy for the user to pass nameless functions as command parameters. The "ls" command is such an example, the user can pass a lambda function which receives the file name and does some other processing.

defun

(defun name (<params>) <expr>) User defined functions, they are bound in the same context as the variables are. Functions are bound in the global context.

  • Name should be a string. The name is not evaluated.

  • (<params>), the parameter list a list of strings. The list of names is not evaluated.

funcall

(funcall name <args>). It is the official way to call a user defined function, but the shorthand is a call of the form (name arg-list). This form will lead to a function call if there was no registered command with the same name.

  • Name should be a string and is not evaluated.

  • <args>, the arguments in a separate list.

progn

Which accepts a list of expressions which are evaluated in order: (progn <expr1> <expr2> ...).

Remarks
  • Lists are Perl arrays and not conses. So list semantics is different (and maybe less efficient). There is no 'nil' concept; a list does not end with a nil, a nil is not the same as an empty array.

  • No separate name spaces for different constructs, there is only a single context stack.

  • Contexts have side effects, bindings can be changed.

  • Only strings are provided, there is no 'symbol' concept. If an application wants e.g. numbers for calculation, the commands should parse the strings.

  • Binding context lookup order. Scoping is lexical. The global context is always available for everybody, there is no need to introduce dynamic scoping.

    • Call context, arguments are bound to parameters. This context is especially created for this call. It contains all local bindings.

    • Lexical (static) context, where the function or lambda was defined. It is the closure of the lambda.

USING COMMAND LIBRARIES

Command libraries are the link between the repl and the business functionality. You can make use of the command libraries that come with the repl, these are described here. Later on we will describe how you can add your own command libraries (its not difficult).

PrintCmd

A single command to print stuff to the stdout.

ExitCmd

A command to exit the repl.

FileSysCmd

A command library to work with directories and files. It can be used in combination with your own command library. The file system command library provides the commands to look up files to go to directories and so on, your own command library could provide the commands to do some processing on these files.

LispCmd

Basic Lisp commands to manipulate lists.

LoadCmd

A command library to load scripts from files, to execute external files as if they were scripts. It can also be used to process user preferences files during startup of the application.

MathCmd

Basic mathematicl operations.

SleepCmd

A test command, it can be handy when you need a command that takes a while to execute in order to test the implementation of a function or anohter command.

DumpCmd

The command dumps the command line expression to the standard output. It can be handy for the command library developer to study the way the expressions are passed to the command implementations.

CREATING A COMMAND LIBRARY

Command libraries are the link between the repl and the business functionality. You can make use of the command libraries that come with the repl, but you probably want to include some new functionality as well. In this section we describe how you can add your own commands.

A command is a Perl object that implements an execute() method. Here is the implementation of the print command.

    sub execute
    {
        my $self = shift;
        my $ctx = shift;
        my $expr = shift;
        
        my @values = @$expr;
        print join(" ", @values[1..$#values]) if $#values >= 1;
        print "\n";
    }
  • The first parameter is the object itself.

  • The second parameter is an instance of Repl::Core::BasicContext, it is the data structure that contains all bindings. Your command has access to these bindings and can even change the context.

  • The third parameter is an array that represents the full command. The first element in the array is a string and represents the name with which your command was called. The other elements in the array are the parameters to your command.

Command implementation is *very* straightforward. The only repetitive work that might crop up is the validation of the parameters inside the expression. For simple parameters this can be done in the command implementation. The type system provides support to automate these parameter checking tasks.

TYPE SYSTEM

The type system consist of a number of modules to make it easier for the command library author to make argument checks and to report these in a consistent way. It is an optional component of the project, you can completely ignore it if you don't need it.

There is support for two types of argument lists:

  • Standard argument list. It can contain a number of required positional arguments, followed by a number of optional positional arguments (which cannot be Repl::Core::Pair instances) and finally a number of named, possibly optional arguments represented by Repl::Core::Pair objects. It is implemented by Repl::Spec::Args::StdArgList.

  • Variable argument list. It can contain a number of required positional arguments, followed by a variable number (minimum and maximum number can be specified) of arguments of the same type and finally a number of named, possibly optional arguments represented by Repl::Core::Pair objects. It is implemented by Repl::Spec::Args::VarArgList.

STDANDARD ARG LIST

An example:

    # Declare the types you want to use in your parameter lists.
    # 
    my $int_type = new Repl::Spec::Type::IntegerType();
    my $bool_type = new Repl::Spec::Type::BooleanType();
    
    # Create argument specifiers with these types.
    #
    my $fixed_number_arg = new Repl::Spec::Args::FixedArg($int_type);
    my $named_force_arg = new Repl::Spec::Args::NamedArg("force", $bool_type, "false", 1);

    # Finally we can define an argument list that takes a single integer as required
    # parameter and a second optional boolean parameter.
    #
    my $stdlst = new Repl::Spec::Args::StdArgList([$fixed_number_arg], [], [$named_force_arg]);
    
    # In your command implementation you can check the expression:
    #
    eval {$stdlist->eval($expr);}
    if($@)
    {
        # Error handling.
        ...
    }
    

The Perl module Repl::Spec::Types contains a number of frequently used types, you don't have to create these for each script, simply reuse the types as they are defined in that module.

VARIABLE ARG LIST

An example. It uses the same type definitions as above and the evaluation of the expression is identical.

    # An argument list of 2-3 integers, followed by a named boolean.
    # 
    my $varlist = new Repl::Spec::Args::VarArgList([$fixed_number_arg], $vararg, 2, 3, [$named_force_arg]);