As of version 0.3, lua-repl supports a plugin mechanism. For an example, please download the source code for
lua-repl and look at repl/plugins/example.lua
.
lua-repl 0.3 ships with several plugins. If you use the example script rep.lua
, some of them are loaded automatically;
these are marked with an asterisk.
Automatically returns the the results of the evaluated expression. So instead of having to type 'return 3', you may simply type '3'.
Provides completion facilities via the 'completion' feature. Other plugins may hook into this to provide tab completion.
Simply an example plugin.
Provides history facilities (and the 'history' feature), storing each line entered as an individual history entry,
and persisting the history in $HOME/.rep.lua.history
. Other plugins may hook into this.
Retains the results of the previously evaluated expression in global variables. The first result is stored in _1
, the
second in _2
, etc. For brevity's sake, the first result is also stored in _
.
Hooks into the linenoise library. Allows the use of tab completion and history.
'Pretty prints' return values. Tables are printed in expanded form. Colors are provided if lua-term is installed.
Loads Lua code in $HOME/.rep.lua
if the file exists. The repl object is provided to the file in a variable named repl
, so
users may load plugins of their choosing.
Suppresses automatic printing of an expression's result if the expression ends in a semicolon.
If you would like to create your own plugin, it must be in a file under repl/plugins/
in your package.path
.
When a plugin is loaded, it is provided with five special objects that are used to affect the behavior of the REPL object loading the plugin.
The repl
object is a proxy object for the REPL object loading the plugin; you can invoke methods on it, create methods on
it, set properties on it, etc. However, if you try to create a method on the repl
object that already exists, an error
will occur. This is to keep plugin authors from stepping on each others' toes.
The before
object is another proxy object from the plugin environment. If you add methods to the before
object, the original
method remains intact; however, the method you added will be called before the original. For example, if you wanted to print
"I got some results!" before you display them on the command line, your plugin could do this:
function before:displayresults(results)
print 'I got some results!'
end
This is called advice, and is stolen from Moose, an object system for the Perl programming language.
When you apply multiple pieces of advice via before
, they are called in last-in-first-out order:
function before:method()
print 'Second!'
end
function before:method()
print 'First!'
end
before
also receives all of the parameters to the original method. If they are tables, userdata, etc, you may alter them,
which can alter the behavior of the original method, for better or for worse.
If the method you are applying advice to does not exist on the current REPL object, an error will occur. This way, developers can find out about API changes quickly, albeit noisily.
The after
object is another proxy object that attaches advice to the loading REPL object. As you can likely tell from its name,
advice applied via the after
object occurs after the original method. Advice applied via after is called in first-in-first-out order:
function after:method()
print 'First!'
end
function after:method()
print 'Second!'
end
Like before
, if you try applying advice to a method that doesn't exist, an error will occur. Also like before
, after advice receives
all of the parameters passed to the original method.
The around
object is another advice object, but it works a little differently than before
or after
. around
replaces the current
method will the advice, and like before
and after
, receives all of the parameters that would be passed to the original. However,
around
also receives an additional parameter immediately before the parameters: the original method. This way, you can invoke
the method's original functionality if needed. For example:
function around:displayresults(orig, results)
print "I'm displaying some results!"
orig(self, results) -- don't forget self!
print "Now I'm done!"
end
Like the other advice objects, you can't apply advice to a method that doesn't exist. Also, be warned: the around
advice does nothing
to make sure that the parameters are passed to the original function, and it doesn't make sure that the return values from the original
function are returned. You need to do that yourself.
The override
object isn't really an advice object; adding methods to it will replace the methods in the REPL object itself. However,
it will fail if that method does not already exist. The rule of thumb is if you want to add new methods to the REPL object, use
repl
; if you want to completely override an existing method, use override
. Keep in mind this will blow away all advice applied to
a method from other plugins; use with caution!
Sometimes, different plugins will want to provide a method, but implemented in a different way. For example, the completion plugin included with lua-repl implements a tab completion method; however, if you are embedding lua-repl into your own environment, you may have a more sophisicated way to provide completions. Other plugins (like the linenoise plugin) may want to hook into the completion feature itself, without being tied to a particular implementation. So plugins may advertise a list of features that they provide, so that they can develop loose relationships between one another. To advertise features for your plugin, simply set the features variable:
features = 'completion' -- make sure you're not setting a local!
If you wish you provide multiple features, simply use a table:
features = { 'completion', 'something_else' }
Obviously, plugins providing a feature need to agree on a standard interface of methods that they provide. No framework is in place for this as of yet.
REPL objects may provide features as well; for example, repl.console
provides the 'console' feature. You can use this to make sure your
plugins are only loaded in certain environments.
Now that you know how to affect the behavior of lua-repl with plugins, let's go over the methods you may advise/override, or call yourself from within your advised/overridden methods. Please keep in mind that since lua-repl is still a young project, this API is subject to change.
Returns the prompt string displayed for the given prompt level, which is either 1 or 2. 1 signifies that the REPL is not in a multi-line expression (like a for loop); 2 signifies otherwise.
Actually displays the prompt for the given level. You more likely want to deal with getprompt or showprompt.
Returns the name of the REPL, used when compiling the chunks for evaluation.
Returns a stack trace, prefixed by the given error message.
Detects whether or not the given error message means that more input is needed for a complete chunk. You probably shouldn't touch this.
Compiles the given chunk of code, returning a function, or a falsy value and an error message.
Returns the function environment that the REPL evaluates code in.
Handles a line of input, returning the prompt level (1 or 2). Note that if this method is called, an evaluation does not necessarily occur.
Displays the given prompt.
Displays the results from an evaluation. results
is a table with the individual values in
the integer indices of the table, with the n
key containing the number of values in the table.
Displays an error from an evaluation.
Returns true
if the given plugin has been loaded, false
otherwise.
Returns true
if the given feature has been loaded, false
otherwise.
If the given feature has been loaded, do nothing. Otherwise, raise an error.
If the given plugin has been loaded, call action
. Otherwise, if the plugin
is ever loaded in the future, call action
after that loading occurs.
If the given feature has been loaded, call action
. Otherwise, if the feature
is ever loaded in the future, call action
after that loading occurs.
Loads the given plugin. If the plugin returns a value, that value is returned.
Called when the REPL is exited. Don't call this yourself!
Returns an iterator that yields a line of input per invocation.
This is the first release of lua-repl with plugins. The future will bring various refinements to the plugin interface, along with the following planned features:
Earlier I mentioned that features have a sort of "gentlemen's aggreement" on what methods they will provide. It would be nice if the plugin system had a way of enforcing that.
Currently, if a plugin wants to store some information between method calls, it needs
to store it on the REPL object (self
) and hope no other plugins (or REPL clone) will
use the same name. Plugin-specific storage is a high priority.
Currently, plugins don't have any sort of configuration mechanism. I plan to change that.
Some plugins may want to leverage functionality of others without loading those others into the REPL itself. I call these library plugins.
If you try to add a method that has already been added, or provide a feature that has already been provided, you receive no information on which plugin provided the method or feature in question. It would be nice to know.