summaryrefslogtreecommitdiff
path: root/lib/lua-repl
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-12-13 16:10:08 +1100
committerjacqueline <me@jacqueline.id.au>2023-12-13 16:10:08 +1100
commit64b106c13e18c33be0f2b0de532054e0ed3f731d (patch)
treeb54b1c90d941bc456b4d51e864970720bdf2d648 /lib/lua-repl
parent5a2f0b08e0e3f20cda977b510b680d5843ae7283 (diff)
downloadtangara-fw-64b106c13e18c33be0f2b0de532054e0ed3f731d.tar.gz
add a cool lua repl
Diffstat (limited to 'lib/lua-repl')
-rw-r--r--lib/lua-repl/.editorconfig15
-rw-r--r--lib/lua-repl/.gitignore3
-rw-r--r--lib/lua-repl/.proverc2
-rw-r--r--lib/lua-repl/.travis.yml31
-rw-r--r--lib/lua-repl/COPYING17
-rw-r--r--lib/lua-repl/Changes71
-rw-r--r--lib/lua-repl/IDEAS.md43
-rw-r--r--lib/lua-repl/Makefile14
-rw-r--r--lib/lua-repl/README.md102
-rw-r--r--lib/lua-repl/RELEASE-GUIDE.md34
-rw-r--r--lib/lua-repl/Roadmap.md44
-rw-r--r--lib/lua-repl/dev/RELEASE-MESSAGE21
-rw-r--r--lib/lua-repl/luarepl-0.10-1.rockspec38
-rw-r--r--lib/lua-repl/plugins.md285
-rwxr-xr-xlib/lua-repl/rep.lua41
-rw-r--r--lib/lua-repl/repl/compat.lua30
-rw-r--r--lib/lua-repl/repl/console.lua57
-rw-r--r--lib/lua-repl/repl/init.lua416
-rw-r--r--lib/lua-repl/repl/plugins/autoreturn.lua29
-rw-r--r--lib/lua-repl/repl/plugins/completion.lua192
-rw-r--r--lib/lua-repl/repl/plugins/example.lua41
-rw-r--r--lib/lua-repl/repl/plugins/filename_completion.lua63
-rw-r--r--lib/lua-repl/repl/plugins/history.lua60
-rw-r--r--lib/lua-repl/repl/plugins/keep_last_eval.lua43
-rw-r--r--lib/lua-repl/repl/plugins/linenoise.lua59
-rw-r--r--lib/lua-repl/repl/plugins/pretty_print.lua262
-rw-r--r--lib/lua-repl/repl/plugins/rcfile.lua54
-rw-r--r--lib/lua-repl/repl/plugins/rlwrap.lua41
-rw-r--r--lib/lua-repl/repl/plugins/semicolon_suppress_output.lua36
-rw-r--r--lib/lua-repl/repl/sync.lua46
-rw-r--r--lib/lua-repl/repl/utils.lua70
-rw-r--r--lib/lua-repl/t/abstract-repl-tests.lua33
-rw-r--r--lib/lua-repl/t/clone-repl-tests.lua119
-rw-r--r--lib/lua-repl/t/lib/test-utils.lua56
-rw-r--r--lib/lua-repl/t/plugin-after-tests.lua202
-rw-r--r--lib/lua-repl/t/plugin-around-tests.lua218
-rw-r--r--lib/lua-repl/t/plugin-basic-tests.lua206
-rw-r--r--lib/lua-repl/t/plugin-before-tests.lua201
-rw-r--r--lib/lua-repl/t/plugin-feature-tests.lua132
-rw-r--r--lib/lua-repl/t/plugin-override-tests.lua201
-rw-r--r--lib/lua-repl/t/plugin-repl-tests.lua75
-rw-r--r--lib/lua-repl/t/sync-repl-tests.lua52
42 files changed, 3755 insertions, 0 deletions
diff --git a/lib/lua-repl/.editorconfig b/lib/lua-repl/.editorconfig
new file mode 100644
index 00000000..ca4cf17b
--- /dev/null
+++ b/lib/lua-repl/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[{Makefile,*.makefile}]
+indent_style = tab
+
+[*.md]
+trim_trailing_whitespace = false
+
diff --git a/lib/lua-repl/.gitignore b/lib/lua-repl/.gitignore
new file mode 100644
index 00000000..08b58b64
--- /dev/null
+++ b/lib/lua-repl/.gitignore
@@ -0,0 +1,3 @@
+doc/
+*.tar.gz
+*.rock
diff --git a/lib/lua-repl/.proverc b/lib/lua-repl/.proverc
new file mode 100644
index 00000000..a5f42ae6
--- /dev/null
+++ b/lib/lua-repl/.proverc
@@ -0,0 +1,2 @@
+--ext .lua
+--exec lua
diff --git a/lib/lua-repl/.travis.yml b/lib/lua-repl/.travis.yml
new file mode 100644
index 00000000..d6390739
--- /dev/null
+++ b/lib/lua-repl/.travis.yml
@@ -0,0 +1,31 @@
+language: generic
+sudo: false
+
+env:
+ - LUA=5.1 LUAROCKS=2.3.0
+ - LUA=5.1.5 LUAROCKS=2.3.0
+ - LUA=5.2.0 LUAROCKS=2.3.0
+ - LUA=5.2.4 LUAROCKS=2.3.0
+ - LUA=5.3.0 LUAROCKS=2.3.0
+ - LUA=5.3.3 LUAROCKS=2.3.0
+
+cache:
+ apt: true
+ directories:
+ - $HOME/luas/
+
+addons:
+ apt:
+ packages:
+ - luarocks
+
+before_install:
+ - luarocks --local install vert
+
+install:
+ - ~/.luarocks/bin/vert init --lua-version=$LUA --luarocks-version=$LUAROCKS $HOME/luas/$LUA
+ - source $HOME/luas/$LUA/bin/activate
+ - luarocks install lua-testmore
+
+script:
+ - make test
diff --git a/lib/lua-repl/COPYING b/lib/lua-repl/COPYING
new file mode 100644
index 00000000..959dbc98
--- /dev/null
+++ b/lib/lua-repl/COPYING
@@ -0,0 +1,17 @@
+Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/lua-repl/Changes b/lib/lua-repl/Changes
new file mode 100644
index 00000000..1ccb438b
--- /dev/null
+++ b/lib/lua-repl/Changes
@@ -0,0 +1,71 @@
+NEXT
+
+0.10 Jan 14 2022
+ --- Bug Fixes ---
+ - Fixed issue where current buffer is stuck after certain syntax errors (GH #61, thanks Justin Blanchard!)
+
+0.9 Oct 30 2018
+ --- Bug Fixes ---
+ - Fixed rlwrap plugin under Lua 5.2 and greater (GH #57)
+
+ --- Features/Enhancements ---
+ - Improve support for Lua 5.3 (thanks, Iqbal Ansari!)
+
+0.8
+ Aug 23 2015
+ --- Bug Fixes ---
+ - Use package.searchers if package.loaders is not available (thanks, Simon Cozens!)
+
+ --- Backwards Incompatible Changes ---
+ - The default plugin bundle is no longer loaded if an rcfile is present. See
+ https://github.com/hoelzro/lua-repl/blob/0.8/README.md#removal-of-default-plugins-in-08
+ for details.
+
+0.7 May 22 2015
+ --- Features/Enhancements ---
+ - iffeature is implemented, thanks to Henry Kielmann!
+ - loadplugin now returns any values returned by the plugin function.
+
+ --- Deprecations ---
+ - 0.8 will break backwards compatability due a change in rcfile handling;
+ please consult the README for details.
+
+ --- Plugins ---
+ - we now have a filename completion plugin, thanks to Henry Kielmann!
+ - rlwrap functionality has been moved into a plugin.
+
+0.6 Oct 18 2014
+ --- Bug Fixes ---
+ - Fix rcfile plugin for Lua 5.2
+
+0.5 Oct 17 2014
+ --- Features/Enhancements ---
+ - Lua 5.2 support (Thanks to Simon Cozens!)
+
+0.4 Mar 29 2013
+ --- API Additions ---
+ - The repl object now has a VERSION property.
+
+ --- Bug Fixes ---
+ - Overriding/augmenting getcontext will actually have an effect.
+ - Fix pretty_print with 'unsupported' types
+
+ --- Features/Enhancements ---
+ - Change the example REPL's prompt to resemble the standalone interpreter's
+ - The example REPL now prints a welcome message
+ - If linenoise is not available and rlwrap is, the example REPL falls back to rlwrap
+
+ --- Plugins ---
+ - Added some new plugins
+ - semicolon_suppress_output
+
+0.3 Oct 17 2012
+ - Add plugins API.
+ - Add a bunch of plugins.
+
+0.2 Sep 28 2012
+ - Add linenoise REPL.
+ - Add completion support.
+
+0.1 Oct 20 2011
+ Initial release. Basic REPL functionality.
diff --git a/lib/lua-repl/IDEAS.md b/lib/lua-repl/IDEAS.md
new file mode 100644
index 00000000..8b7eba24
--- /dev/null
+++ b/lib/lua-repl/IDEAS.md
@@ -0,0 +1,43 @@
+ * plugins
+ * replace /^=/ with 'return'
+ * handle locals
+ * override debug.debug
+ * supple (http://cgit.gitano.org.uk/supple.git)
+ * handle locals (debug.sethook?)
+ * debug.sethook, catch return of our chunk and grab its locals
+ * rewrite source code/bytecode before evaluation
+ * custom interpreter patch to "pcall and get bindings"
+ * custom module that dips into internals to "pcall and get bindings"
+ * some sort of debugger?
+ * don't contaminate globals
+ * tab completion (\_\_complete metamethod)
+ * "safe" evaluation (don't allow calling of C functions, except for those in a whitelist?)
+ * displaystack instead of displayerror(err)? (should xpcall return false, stack\_table?)
+ * visual REPL (like Factor; being able to print multi-colored/multi-sized text, images, etc)
+ * syntax highlighting
+ * paren/brace matching?
+ * snippets?
+ * code navigation (go to definition?)
+ * repls that "attach" to different objects (ie. inspect a single object; self is that object. completions happen against that object?)
+ * browsable/searchable REPL history
+ * not entirely sure what I mean here...
+ * safe termination of evaluated code (if I Control-C during an evaluation)
+ * store stdout/stderr output in a variable somewhere?
+ * persistence (pluto-based image)
+
+hooks
+=====
+
+ * what to do when we encounter an incomplete Lua fragment
+ * processing a line
+ * something for debug.debug...
+
+Implementations
+===============
+
+ * Console
+ * GUI
+ * Web
+ * IRC
+ * safety hooks
+ * Awesome
diff --git a/lib/lua-repl/Makefile b/lib/lua-repl/Makefile
new file mode 100644
index 00000000..a092d939
--- /dev/null
+++ b/lib/lua-repl/Makefile
@@ -0,0 +1,14 @@
+LUA_FILES=$(shell find repl -type f -name '*.lua')
+.PHONY: doc install
+
+doc:
+ luadoc -d doc $(LUA_FILES)
+
+install:
+ # TODO
+
+test:
+ LUA_INIT='' LUA_PATH=';;$(LUA_PATH);?.lua;?/init.lua;t/lib/?.lua' prove
+
+clean:
+ rm -rf doc/
diff --git a/lib/lua-repl/README.md b/lib/lua-repl/README.md
new file mode 100644
index 00000000..58f128b0
--- /dev/null
+++ b/lib/lua-repl/README.md
@@ -0,0 +1,102 @@
+# REPL.lua - a reusable Lua REPL written in Lua, and an alternative to /usr/bin/lua
+
+This project has two uses:
+
+ - An alternative to the standalone interpreter included with Lua, one that supports
+ things like plugins, tab completion, and automatic insertion of `return` in front
+ of expressions.
+
+ - A REPL library you may embed in your application, to provide all of the niceties
+ of the standalone interpreter included with Lua and then some.
+
+Many software projects have made the choice to embed Lua in their projects to
+allow their users some extra flexibility. Some of these projects would also
+like to provide a Lua REPL in their programs for debugging or rapid development.
+Most Lua programmers are familiar with the standalone Lua interpreter as a Lua REPL;
+however, it is bound to the command line. Until now, Lua programmers would have to
+implement their own REPL from scratch if they wanted to include one in their programs.
+This project aims to provide a REPL implemented in pure Lua that almost any project can
+make use of.
+
+This library also includes an example application (rep.lua), which serves as an alternative
+to the standalone interpreter included with Lua. If the lua-linenoise library is installed,
+it uses linenoise for history and tab completion; otherwise, it tries to use rlwrap for
+basic line editing. If you would like the arrow keys to work as expected rather than printing
+things like `^[[A`, please install the lua-linenoise library or the rlwrap program.
+
+# Project Goals
+
+ * Provide REPL logic to Lua programs that include this module.
+
+ * Be extensible through polymorphism and plugins.
+
+ * Abstract away I/O, so you can run this REPL on the command line or in your own event loop and expect the same behavior.
+
+# Building
+
+ * You need Luadoc (http://keplerproject.github.com/luadoc/) installed to build the documentation.
+
+ * You need Test.More (http://fperrad.github.com/lua-TestMore/testmore.html) installed to run the tests.
+
+# Compatibility
+
+The current version of the software runs on Lua 5.1, LuaJIT ?.? etc.
+A port to Lua 5.2 is envisaged, but is not at this stage a priority.
+Since it is written purely in Lua, it should work on any platform that
+has one of those versions of Lua installed.
+
+XXX Check which version of LuaJIT this works with
+XXX Check that it works with other Lua interpreters
+
+# Installation
+
+You can install lua-repl via LuaRocks:
+
+ luarocks install luarepl
+
+You can also install it by hand by copying the `repl`
+directory to a location in your `package.path`, and
+copying rep.lua to somewhere in your `PATH`.
+
+# Recommended packages
+
+`rep.lua` works best if you also have `linenoise` installed,
+available from https://github.com/hoelzro/lua-linenoise.
+`rep.lua` will fallback to using rlwrap if you have that as well;
+without either of these, you will have command editing, history,
+or other features generally provided by `readline`.
+
+# Features
+
+`rep.lua` prints the results of simple expressions without requiring
+a `return ` or a `= ` in front of it. If `linenoise` is installed,
+it also offers persistent history and tab completion. It also offers
+a number of plugins; see plugins.md for a list of plugins that come
+with lua-repl.
+
+# Backwards Compatibility Changes
+
+## Removal of default plugins in 0.8
+
+Lua REPL 0.8 breaks backwards compatability by disabling the loading of the
+default plugins (currently `linenoise`, `rlwrap`, `history`, `completion`, and
+`autoreturn`) if an rcfile is found for a user. This is so that plugins may
+not be forced onto a user if they don't want them, or play tricks with their
+setup (see issue #47). If you would like to continue using these plugins, please
+put the following code into your `~/.rep.lua`:
+
+```lua
+if repl.VERSION >= 0.8 then
+ -- default plugins
+ repl:loadplugin 'linenoise'
+ repl:loadplugin 'history'
+ repl:loadplugin 'completion'
+ repl:loadplugin 'autoreturn'
+end
+
+-- suppress warning message
+repl.quiet_default_plugins = true
+```
+
+As mentioned in the code snippet, `repl.quiet_default_plugins` suppresses the warning.
+You can remove this after upgrading to Lua REPL 0.8.
diff --git a/lib/lua-repl/RELEASE-GUIDE.md b/lib/lua-repl/RELEASE-GUIDE.md
new file mode 100644
index 00000000..1efcb728
--- /dev/null
+++ b/lib/lua-repl/RELEASE-GUIDE.md
@@ -0,0 +1,34 @@
+# lua-repl release guide
+
+ - [ ] Bump VERSION (in repl/init.lua, look for references to current version)
+ - [ ] Update Changelog
+ - [ ] Rename and update rockspec
+ - [ ] Make sure tests pass
+ - [ ] Push & tag latest release
+ - [ ] Submit rockspec to luarocks
+ - [ ] E-mail lua-l
+ - [ ] Submit lua-l gmane link to reddit
+
+## E-mail template for lua-l
+
+Hi Lua users,
+
+I have just released version {{VERSION}} of lua-repl, an alternative to the
+standalone REPL included with Lua and a library for embedding a Lua
+REPL within a Lua application.
+
+lua-repl provides a library for adding a Lua REPL to your Lua-powered
+application. It also provides an example REPL in the form of rep.lua,
+which can take the place of the standalone interpreter provided with
+Lua. It has a plugin facility; plugins for things like history and tab
+completion of symbols are included in the lua-repl distribution.
+
+{{CHANGES}}
+
+You can install lua-repl via Luarocks (called luarepl there), or
+manually from the source [{{REFERENCE}}].
+
+-Rob
+
+[{{REFERENCE}}] https://github.com/hoelzro/lua-repl/archive/0.8.tar.gz
+
diff --git a/lib/lua-repl/Roadmap.md b/lib/lua-repl/Roadmap.md
new file mode 100644
index 00000000..baaa3870
--- /dev/null
+++ b/lib/lua-repl/Roadmap.md
@@ -0,0 +1,44 @@
+0.10
+===
+
+ * Process Lua command line options with rep.lua
+ * Verify that it works with LuaJIT, Lua 5.0, Lua 5.2, LuaJ or something
+ * __pretty support for pretty print plugin
+ * __complete support for completion plugin
+ * Documentation improvements
+ * More clearly reference PLUGINS.md from README.md
+ * More clearly reference rep.lua from README.md
+ * Make sure that autocompletion is talked up in plugins.md (and mention in readme that many default/optional behaviors are present there)
+ * Make sure documentation on ~/.rep.lua is clear
+ * Move docs into doc/
+
+Future
+======
+
+ * Plugins
+ * where do plugins store values (self, storage object, etc?)
+ * configuration
+ * global assignments in plugins
+ * we need a way to do method advice in REPL "subclasses"
+ * test: using advice from within ifplugin/iffeature
+ * luaish plugin
+ * moonscript plugin - compile Moonscript instead of Lua
+ * Steal ideas from ilua
+ * Variables in ilua must be declared before use
+ * -L is like -l, except it automatically brings it into the global NS
+ * require() wrapper that does this ↑
+ * table display logic control, float precision control
+ * print\_handler (custom print logic for types)
+ * \_\_pretty
+ * global\_handler (custom lookup logic to complement strict mode)
+ * easily done via a plugin
+ * line\_handler (custom handling of lines before being processed)
+ * Steal ideas from luaish
+ * Shell commands (lines beginning with ., filename completion)
+ * Steal ideas from http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print\_loop
+ * Steal ideas from pry, ipython, bpython, Devel::REPL, Factor REPL
+ * Steal ideas from https://github.com/tpope/vim-foreplay
+ * Async implementation
+ * GTK implementation
+ * IRC bot implementation
+ * Awesome library
diff --git a/lib/lua-repl/dev/RELEASE-MESSAGE b/lib/lua-repl/dev/RELEASE-MESSAGE
new file mode 100644
index 00000000..0fdbdf34
--- /dev/null
+++ b/lib/lua-repl/dev/RELEASE-MESSAGE
@@ -0,0 +1,21 @@
+To: <lua-l@lists.lua.org>
+Subject: [ANN] lua-repl $VERSION
+
+Hi everyone,
+
+I'm happy to announce a new version of lua-repl!
+
+For those of you who aren't aware of it, lua-repl is a library to make
+it easy to embed a REPL similar to the standalone interpreter in your
+Lua application. It also comes with a suite of plugins for enhancing
+itself, as well as a "demo" console REPL that may be used as an
+alternative to the standalone REPL (named rep.lua).
+
+$CHANGE_SUMMARY
+
+The source for lua-repl is located on Github:
+https://github.com/hoelzro/lua-repl. The release tarball is here:
+https://github.com/hoelzro/lua-repl/archive/$VERSION.tar.gz
+
+Thanks,
+Rob
diff --git a/lib/lua-repl/luarepl-0.10-1.rockspec b/lib/lua-repl/luarepl-0.10-1.rockspec
new file mode 100644
index 00000000..4b3401a5
--- /dev/null
+++ b/lib/lua-repl/luarepl-0.10-1.rockspec
@@ -0,0 +1,38 @@
+package = 'luarepl'
+version = '0.10-1'
+source = {
+ url = 'https://github.com/hoelzro/lua-repl/archive/0.10.tar.gz',
+ dir = 'lua-repl-0.10',
+}
+description = {
+ summary = 'A reusable REPL component for Lua, written in Lua',
+ homepage = 'https://github.com/hoelzro/lua-repl',
+ license = 'MIT/X11',
+}
+dependencies = {
+ 'lua >= 5.1'
+}
+build = {
+ type = 'builtin',
+ modules = {
+ ['repl'] = 'repl/init.lua',
+ ['repl.utils'] = 'repl/utils.lua',
+ ['repl.sync'] = 'repl/sync.lua',
+ ['repl.compat'] = 'repl/compat.lua',
+ ['repl.console'] = 'repl/console.lua',
+ ['repl.plugins.autoreturn'] = 'repl/plugins/autoreturn.lua',
+ ['repl.plugins.completion'] = 'repl/plugins/completion.lua',
+ ['repl.plugins.example'] = 'repl/plugins/example.lua',
+ ['repl.plugins.history'] = 'repl/plugins/history.lua',
+ ['repl.plugins.keep_last_eval'] = 'repl/plugins/keep_last_eval.lua',
+ ['repl.plugins.linenoise'] = 'repl/plugins/linenoise.lua',
+ ['repl.plugins.pretty_print'] = 'repl/plugins/pretty_print.lua',
+ ['repl.plugins.rcfile'] = 'repl/plugins/rcfile.lua',
+ ['repl.plugins.semicolon_suppress_output'] = 'repl/plugins/semicolon_suppress_output.lua',
+ ['repl.plugins.filename_completion'] = 'repl/plugins/filename_completion.lua',
+ ['repl.plugins.rlwrap'] = 'repl/plugins/rlwrap.lua',
+ },
+ install = {
+ bin = { 'rep.lua' },
+ }
+}
diff --git a/lib/lua-repl/plugins.md b/lib/lua-repl/plugins.md
new file mode 100644
index 00000000..2a3591b4
--- /dev/null
+++ b/lib/lua-repl/plugins.md
@@ -0,0 +1,285 @@
+# Plugins
+
+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`.
+
+# Available Plugins
+
+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.
+
+## autoreturn (\*)
+
+Automatically returns the the results of the evaluated expression. So instead of having to type 'return 3', you may
+simply type '3'.
+
+## completion (\*)
+
+Provides completion facilities via the 'completion' feature. Other plugins may hook into this to provide tab completion.
+
+## example
+
+Simply an example plugin.
+
+## history (\*)
+
+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.
+
+## keep\_last\_eval
+
+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 `_`.
+
+## linenoise (\*)
+
+Hooks into the linenoise library. Allows the use of tab completion and history.
+
+## pretty\_print
+
+'Pretty prints' return values. Tables are printed in expanded form. Colors are provided if lua-term is installed.
+
+## rcfile (\*)
+
+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.
+
+## semicolon_suppress_output
+
+Suppresses automatic printing of an expression's result if the expression ends in a semicolon.
+
+# Creating a Plugin
+
+If you would like to create your own plugin, it must be in a file under `repl/plugins/` in your `package.path`.
+
+# Plugin Objects
+
+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.
+
+## repl
+
+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.
+
+## before
+
+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:
+
+```lua
+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:
+
+```lua
+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.
+
+## after
+
+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:
+
+```lua
+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.
+
+## around
+
+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:
+
+```lua
+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.
+
+## override
+
+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!
+
+# Features
+
+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:
+
+```lua
+features = 'completion' -- make sure you're not setting a local!
+```
+
+If you wish you provide multiple features, simply use a table:
+
+```lua
+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.
+
+# REPL Methods
+
+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.
+
+## repl:getprompt(level)
+
+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.
+
+## repl:prompt(level)
+
+Actually displays the prompt for the given level. You more likely want to deal with
+getprompt or showprompt.
+
+## repl:name()
+
+Returns the name of the REPL, used when compiling the chunks for evaluation.
+
+## repl:traceback(err)
+
+Returns a stack trace, prefixed by the given error message.
+
+## repl:detectcontinue(err)
+
+Detects whether or not the given error message means that more input is needed for a complete
+chunk. You probably shouldn't touch this.
+
+## repl:compilechunk(code)
+
+Compiles the given chunk of code, returning a function, or a falsy value and an error message.
+
+## repl:getcontext()
+
+Returns the function environment that the REPL evaluates code in.
+
+## repl:handleline(line)
+
+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.
+
+## repl:showprompt(prompt)
+
+Displays the given prompt.
+
+## repl:displayresults(results)
+
+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.
+
+## repl:displayerror(err)
+
+Displays an error from an evaluation.
+
+## repl:hasplugin(plugin)
+
+Returns `true` if the given plugin has been loaded, `false` otherwise.
+
+## repl:hasfeature(feature)
+
+Returns `true` if the given feature has been loaded, `false` otherwise.
+
+## repl:requirefeature(feature)
+
+If the given feature has been loaded, do nothing. Otherwise, raise an error.
+
+## repl:ifplugin(plugin, action)
+
+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.
+
+## repl:iffeature(feature, action)
+
+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.
+
+## repl:loadplugin(plugin)
+
+Loads the given plugin. If the plugin returns a value, that value is returned.
+
+## repl:shutdown()
+
+Called when the REPL is exited. Don't call this yourself!
+
+## repl:lines() -- repl.sync only
+
+Returns an iterator that yields a line of input per invocation.
+
+# The Future
+
+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:
+
+## Feature Interfaces
+
+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.
+
+## Attribute Storage
+
+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.
+
+## Configuration
+
+Currently, plugins don't have any sort of configuration mechanism. I plan to change that.
+
+## Library Plugins
+
+Some plugins may want to leverage functionality of others without loading those others into
+the REPL itself. I call these *library plugins*.
+
+## Better Diagnostics
+
+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.
diff --git a/lib/lua-repl/rep.lua b/lib/lua-repl/rep.lua
new file mode 100755
index 00000000..029c3192
--- /dev/null
+++ b/lib/lua-repl/rep.lua
@@ -0,0 +1,41 @@
+#!/usr/bin/env lua
+
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- Not as cool a name as re.pl, but I tried.
+
+local repl = require 'repl.console'
+local rcfile_loaded = repl:loadplugin 'rcfile'
+
+if not rcfile_loaded then
+ local has_linenoise = pcall(require, 'linenoise')
+
+ if has_linenoise then
+ repl:loadplugin 'linenoise'
+ else
+ pcall(repl.loadplugin, repl, 'rlwrap')
+ end
+
+ repl:loadplugin 'history'
+ repl:loadplugin 'completion'
+ repl:loadplugin 'autoreturn'
+end
+
+print('Lua REPL ' .. tostring(repl.VERSION))
+repl:run()
diff --git a/lib/lua-repl/repl/compat.lua b/lib/lua-repl/repl/compat.lua
new file mode 100644
index 00000000..fd47748c
--- /dev/null
+++ b/lib/lua-repl/repl/compat.lua
@@ -0,0 +1,30 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+return {
+ -- unpack was moved to table.unpack on Lua version 5.2
+ -- See https://www.lua.org/manual/5.2/manual.html#8
+ unpack = unpack or table.unpack,
+ -- loadstring was deprecated in favor of load, which was updated
+ -- to handle string arguments
+ loadstring = loadstring or load,
+ -- package.loaders was renamed package.searchers in Lua 5.2
+ package = {
+ searchers = package.loaders or package.searchers
+ }
+}
diff --git a/lib/lua-repl/repl/console.lua b/lib/lua-repl/repl/console.lua
new file mode 100644
index 00000000..154d0372
--- /dev/null
+++ b/lib/lua-repl/repl/console.lua
@@ -0,0 +1,57 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- @class repl.console
+--- This module implements a command line-based REPL,
+--- similar to the standalone Lua interpreter.
+
+local sync_repl = require 'repl.sync'
+local compat = require 'repl.compat'
+local console_repl = sync_repl:clone()
+local stdout = io.stdout
+local stdin = io.stdin
+local print = print
+local unpack = unpack
+
+-- @see repl:showprompt(prompt)
+function console_repl:showprompt(prompt)
+ stdout:write(prompt .. ' ')
+end
+
+-- @see repl.sync:lines()
+function console_repl:lines()
+ return stdin:lines()
+end
+
+-- @see repl:displayresults(results)
+function console_repl:displayresults(results)
+ if results.n == 0 then
+ return
+ end
+
+ print(compat.unpack(results, 1, results.n))
+end
+
+-- @see repl:displayerror(err)
+function console_repl:displayerror(err)
+ print(err)
+end
+
+console_repl._features.console = true
+
+return console_repl
diff --git a/lib/lua-repl/repl/init.lua b/lib/lua-repl/repl/init.lua
new file mode 100644
index 00000000..b5499f59
--- /dev/null
+++ b/lib/lua-repl/repl/init.lua
@@ -0,0 +1,416 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- @class repl
+--- This module implements the core functionality of a REPL.
+
+local plugins_lookup_meta = { __mode = 'k' }
+
+local repl = { _buffer = '', _plugins = setmetatable({}, plugins_lookup_meta), _features = {}, _ifplugin = {}, _iffeature = {}, VERSION = 0.10 }
+local compat = require 'repl.compat'
+local select = select
+local dtraceback = debug.traceback
+local setmetatable = setmetatable
+local sformat = string.format
+local smatch = string.match
+local error = error
+local setfenv = require('repl.utils').setfenv
+
+local function gather_results(success, ...)
+ local n = select('#', ...)
+ return success, { n = n, ... }
+end
+
+local function tcopy(t, copy)
+ copy = copy or {}
+
+ for k, v in pairs(t) do
+ copy[k] = v
+ end
+
+ return copy
+end
+
+--- Returns the prompt for a given level.
+-- @param level The prompt level. Either 1 or 2.
+function repl:getprompt(level)
+ return level == 1 and '>' or '>>'
+end
+
+--- Displays a prompt for the given prompt level.
+-- @param level The prompt level. Either 1 or 2.
+function repl:prompt(level)
+ self:showprompt(self:getprompt(level))
+end
+
+--- Returns the name of the REPL. For usage in chunk compilation.
+-- @return The REPL's name.
+-- @see load
+function repl:name()
+ return 'REPL'
+end
+
+--- Gets a traceback for an error.
+-- @param ... All of the stuff that xpcall passes to error functions.
+-- @see xpcall
+-- @return A stack trace. The default implementation returns a simple string-based trace.
+function repl:traceback(...)
+ return dtraceback(...)
+end
+
+--- Uses the compilation error to determine whether or not further input
+--- is pending after the last line. You shouldn't have to override this
+--- unless you use an implementation of Lua that varies in its error
+--- messages.
+-- @param err The compilation error from Lua.
+-- @return Whether or not the input should continue after this line.
+function repl:detectcontinue(err)
+ return smatch(err, "'<eof>'$") or smatch(err, "<eof>$")
+end
+
+function repl:compilechunk(chunk)
+ return compat.loadstring(chunk, self:name())
+end
+
+--- Evaluates a line of input, and displays return value(s).
+-- @param line The line to evaluate
+-- @return The prompt level (1 or 2)
+function repl:handleline(line)
+ local chunk = self._buffer .. line
+ local f, err = self:compilechunk(chunk)
+
+ if f then
+ self._buffer = ''
+
+ setfenv(f, self:getcontext())
+ local success, results = gather_results(xpcall(f, function(...) return self:traceback(...) end))
+ if success then
+ self:displayresults(results)
+ else
+ self:displayerror(results[1])
+ end
+ else
+ if self:detectcontinue(err) then
+ self._buffer = chunk .. '\n'
+ return 2
+ else
+ self:displayerror(err)
+ self._buffer = ''
+ end
+ end
+
+ return 1
+end
+
+--- Creates a new REPL object, so you can override methods without fear.
+-- @return A REPL clone.
+function repl:clone()
+ local plugins_copy = tcopy(self._plugins, setmetatable({}, plugins_lookup_meta))
+ local features_copy = tcopy(self._features)
+ local ifplugin_copy = {}
+ local iffeature_copy = {}
+
+ for k, v in pairs(self._ifplugin) do
+ ifplugin_copy[k] = tcopy(v)
+ end
+
+ for k, v in pairs(self._iffeature) do
+ iffeature_copy[k] = tcopy(v)
+ end
+
+ return setmetatable({
+ _buffer = '',
+ _plugins = plugins_copy,
+ _features = features_copy,
+ _ifplugin = ifplugin_copy,
+ _iffeature = iffeature_copy,
+ }, { __index = self })
+end
+
+--- Displays the given prompt to the user. Must be overriden.
+-- @param prompt The prompt to display.
+function repl:showprompt(prompt)
+ error 'You must implement the showprompt method'
+end
+
+--- Displays the results from evaluate(). Must be overriden.
+-- @param results The results to display. The results are a table, with the integer keys containing the results, and the 'n' key containing the highest integer key.
+
+function repl:displayresults(results)
+ error 'You must implement the displayresults method'
+end
+
+--- Displays errors from evaluate(). Must be overriden.
+-- @param err The error value returned from repl:traceback().
+-- @see repl:traceback
+function repl:displayerror(err)
+ error 'You must implement the displayerror method'
+end
+
+--- Checks whether this REPL object has loaded the given plugin.
+-- @param plugin The plugin that the REPL may have loaded.
+function repl:hasplugin(plugin)
+ return self._plugins[plugin]
+end
+
+function repl:hasfeature(feature)
+ return self._features[feature]
+end
+
+function repl:requirefeature(feature)
+ if not self:hasfeature(feature) then
+ error(sformat('required feature %q not present', feature), 2)
+ end
+end
+
+function repl:ifplugin(plugin, action)
+ if self:hasplugin(plugin) then
+ action()
+ else
+ local pending_actions = self._ifplugin[plugin]
+
+ if not pending_actions then
+ pending_actions = {}
+ self._ifplugin[plugin] = pending_actions
+ end
+
+ pending_actions[#pending_actions + 1] = action
+ end
+end
+
+--- 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.
+function repl:iffeature(feature, action)
+ if self:hasfeature(feature) then
+ action()
+ else
+ local pending_features = self._iffeature[feature]
+
+ if not pending_features then
+ pending_features = {}
+ self._iffeature[feature] = pending_features
+ end
+
+ pending_features[#pending_features + 1] = action
+ end
+end
+
+local function setup_before(repl)
+ local mt = {}
+
+ function mt:__newindex(key, value)
+ if type(value) ~= 'function' then
+ error(tostring(value) .. " is not a function", 2)
+ end
+
+ local old_value = repl[key]
+
+ if old_value == nil then
+ error(sformat("The '%s' method does not exist", key), 2)
+ end
+
+ repl[key] = function(...)
+ value(...)
+ return old_value(...)
+ end
+ end
+
+ return setmetatable({}, mt)
+end
+
+local function setup_after(repl)
+ local mt = {}
+
+ function mt:__newindex(key, value)
+ if type(value) ~= 'function' then
+ error(tostring(value) .. " is not a function", 2)
+ end
+
+ local old_value = repl[key]
+
+ if old_value == nil then
+ error(sformat("The '%s' method does not exist", key), 2)
+ end
+
+ repl[key] = function(...)
+ local _, results = gather_results(true, old_value(...))
+ value(...)
+ return compat.unpack(results, 1, results.n)
+ end
+ end
+
+ return setmetatable({}, mt)
+end
+
+local function setup_around(repl)
+ local mt = {}
+
+ function mt:__newindex(key, value)
+ if type(value) ~= 'function' then
+ error(tostring(value) .. " is not a function", 2)
+ end
+
+ local old_value = repl[key]
+
+ if old_value == nil then
+ error(sformat("The '%s' method does not exist", key), 2)
+ end
+
+ repl[key] = function(self, ...)
+ return value(self, old_value, ...)
+ end
+ end
+
+ return setmetatable({}, mt)
+end
+
+local function setup_override(repl)
+ local mt = {}
+
+ function mt:__newindex(key, value)
+ if type(value) ~= 'function' then
+ error(tostring(value) .. " is not a function", 2)
+ end
+
+ local old_value = repl[key]
+
+ if old_value == nil then
+ error(sformat("The '%s' method does not exist", key), 2)
+ end
+
+ repl[key] = value
+ end
+
+ return setmetatable({}, mt)
+end
+
+local function setup_repl(repl)
+ local mt = {}
+
+ function mt:__newindex(key, value)
+ local old_value = repl[key]
+
+ if old_value ~= nil then
+ error(sformat("The '%s' method already exists", key), 2)
+ end
+
+ repl[key] = value
+ end
+
+ function mt:__index(key)
+ local value = repl[key]
+
+ if type(value) == 'function' then
+ -- XXX cache this?
+ return function(_, ...)
+ return value(repl, ...)
+ end
+ end
+
+ return value
+ end
+
+ return setmetatable({}, mt)
+end
+
+-- TODO use lua-procure for this (eventually)
+local function findchunk(name)
+ for _, loader in pairs(compat.package.searchers) do
+ local chunk = loader(name)
+
+ if type(chunk) == 'function' then
+ return chunk
+ end
+ end
+
+ error('unable to locate plugin', 3)
+end
+
+function repl:loadplugin(chunk)
+ if self:hasplugin(chunk) then
+ error(sformat('plugin %q has already been loaded', tostring(chunk)), 2)
+ end
+ self._plugins[chunk] = true
+
+ local plugin_actions = self._ifplugin[chunk]
+ self._ifplugin[chunk] = nil
+
+ if type(chunk) == 'string' then
+ chunk = findchunk('repl.plugins.' .. chunk)
+ end
+
+ local plugin_env = {
+ repl = setup_repl(self),
+ before = setup_before(self),
+ after = setup_after(self),
+ around = setup_around(self),
+ override = setup_override(self),
+ init = function() end,
+ }
+
+ local function ro_globals(_, key, _)
+ error(sformat('global environment is read-only (key = %q)', key), 2)
+ end
+
+ plugin_env._G = plugin_env
+ plugin_env.features = {}
+ setmetatable(plugin_env, { __index = _G, __newindex = ro_globals })
+
+ setfenv(chunk, plugin_env)
+ local _, results = gather_results(nil, chunk())
+
+ local features = plugin_env.features or {}
+
+ if type(features) == 'string' then
+ features = { features }
+ end
+
+ for _, feature in ipairs(features) do
+ if self._features[feature] then
+ error(sformat('feature %q already present', feature), 2)
+ end
+
+ self._features[feature] = true
+
+ local feature_actions = self._iffeature[feature]
+ self._iffeature[feature] = nil
+ if feature_actions then
+ for _, action in ipairs(feature_actions) do
+ action()
+ end
+ end
+ end
+
+ if plugin_actions then
+ for _, action in ipairs(plugin_actions) do
+ action()
+ end
+ end
+
+ return compat.unpack(results, 1, results.n)
+end
+
+-- XXX how to guarantee this gets called?
+function repl:shutdown()
+end
+
+function repl:getcontext()
+ return _G
+end
+
+return repl
diff --git a/lib/lua-repl/repl/plugins/autoreturn.lua b/lib/lua-repl/repl/plugins/autoreturn.lua
new file mode 100644
index 00000000..a0e15eab
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/autoreturn.lua
@@ -0,0 +1,29 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- A plugin that causes the REPL to automatically return evaluation results
+
+function around:compilechunk(orig, chunk)
+ local f, err = orig(self, 'return ' .. chunk)
+
+ if not f then
+ f, err = orig(self, chunk)
+ end
+
+ return f, err
+end
diff --git a/lib/lua-repl/repl/plugins/completion.lua b/lib/lua-repl/repl/plugins/completion.lua
new file mode 100644
index 00000000..938c7439
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/completion.lua
@@ -0,0 +1,192 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local utils = require 'repl.utils'
+local getmetatable = getmetatable
+local pairs = pairs
+local sfind = string.find
+local sgmatch = string.gmatch
+local smatch = string.match
+local ssub = string.sub
+local tconcat = table.concat
+local tsort = table.sort
+local type = type
+
+local function isindexable(value)
+ if type(value) == 'table' then
+ return true
+ end
+
+ local mt = getmetatable(value)
+
+ return mt and mt.__index
+end
+
+local function getcompletions(t)
+ local union = {}
+
+ while isindexable(t) do
+ if type(t) == 'table' then
+ -- XXX what about the pairs metamethod in 5.2?
+ -- either we don't care, we implement a __pairs-friendly
+ -- pairs for 5.1, or implement a 'rawpairs' for 5.2
+ for k, v in pairs(t) do
+ if union[k] == nil then
+ union[k] = v
+ end
+ end
+ end
+
+ local mt = getmetatable(t)
+ t = mt and mt.__index or nil
+ end
+
+ return pairs(union)
+end
+
+local function split_ns(expr)
+ if expr == '' then
+ return { '' }
+ end
+
+ local pieces = {}
+
+ -- XXX method calls too (option?)
+ for m in sgmatch(expr, '[^.]+') do
+ pieces[#pieces + 1] = m
+ end
+
+ -- logic for determining whether to pad the matches with the empty
+ -- string (ugly)
+ if ssub(expr, -1) == '.' then
+ pieces[#pieces + 1] = ''
+ end
+
+ return pieces
+end
+
+local function determine_ns(expr)
+ local ns = _G -- XXX what if the REPL lives in a special context? (option?)
+ local pieces = split_ns(expr)
+
+ for index = 1, #pieces - 1 do
+ local key = pieces[index]
+ -- XXX rawget? or regular access? (option?)
+ ns = ns[key]
+
+ if not isindexable(ns) then
+ return {}, '', ''
+ end
+ end
+
+ expr = pieces[#pieces]
+
+ local prefix = ''
+
+ if #pieces > 1 then
+ prefix = tconcat(pieces, '.', 1, #pieces - 1) .. '.'
+ end
+
+ local last_piece = pieces[#pieces]
+
+ local before, after = smatch(last_piece, '(.*):(.*)')
+
+ if before then
+ ns = ns[before] -- XXX rawget
+ prefix = prefix .. before .. ':'
+ expr = after
+ end
+
+ return ns, prefix, expr
+end
+
+local isidentifierchar
+
+do
+ local ident_chars_set = {}
+ -- XXX I think this can be done with isalpha in C...
+ local ident_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.:_0123456789'
+
+ for i = 1, #ident_chars do
+ local char = ssub(ident_chars, i, i)
+ ident_chars_set[char] = true
+ end
+
+ function isidentifierchar(char)
+ return ident_chars_set[char]
+ end
+end
+
+local function extract_innermost_expr(expr)
+ local index = #expr
+
+ while index > 0 do
+ local char = ssub(expr, index, index)
+ if isidentifierchar(char) then
+ index = index - 1
+ else
+ break
+ end
+ end
+
+ index = index + 1
+
+ return ssub(expr, 1, index - 1), ssub(expr, index)
+end
+
+-- XXX is this logic (namely, returning the entire line) too specific to
+-- linenoise?
+function repl:complete(expr, callback)
+ if utils.ends_in_unfinished_string(expr) then
+ return
+ end
+
+ local ns, prefix, path
+
+ prefix, expr = extract_innermost_expr(expr)
+
+ ns, path, expr = determine_ns(expr)
+
+ prefix = prefix .. path
+
+ local completions = {}
+
+ for k, v in getcompletions(ns) do
+ if sfind(k, expr, 1, true) == 1 then
+ local suffix = ''
+ local type = type(v)
+
+ -- XXX this should be optional
+ if type == 'function' then
+ suffix = '('
+ elseif type == 'table' then
+ suffix = '.'
+ end
+
+ completions[#completions + 1] = prefix .. k .. suffix
+ end
+ end
+
+ tsort(completions)
+
+ for _, completion in ipairs(completions) do
+ callback(completion)
+ end
+end
+
+features = 'completion'
diff --git a/lib/lua-repl/repl/plugins/example.lua b/lib/lua-repl/repl/plugins/example.lua
new file mode 100644
index 00000000..d55fd076
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/example.lua
@@ -0,0 +1,41 @@
+-- Example plugin that demonstrates the objects available to a
+-- plugin, as well as the methods that a plugin should make use
+-- of
+
+-- Adding methods and properties to the repl object adds them to
+-- the REPL object loading the plugin. If such a method or property
+-- already exists, the current plugin will fail to load.
+function repl:newmethod(...)
+end
+
+-- Adding methods to the before object causes them to be called
+-- before the actual method itself. If the method being added
+-- (in this case displayresults) does not exist on the REPL object
+-- loading this plugin, the current plugin will fail to load.
+function before:displayresults(results)
+end
+
+-- Adding methods to the after object causes them to be called
+-- after the actual method itself. If the method being added
+-- (in this case displayresults) does not exist on the REPL object
+-- loading this plugin, the current plugin will fail to load.
+function after:displayresults(results)
+end
+
+-- Adding methods to the around object causes them to be called
+-- instead of the original method of the same name. The new
+-- method receives all of the arguments that the original would,
+-- except it also receives the original method as the first argument.
+-- This way, the new method may invoke the original as it pleases.
+-- If the method being added (in this case displayresults) does not exist on
+-- the REPL object loading this plugin, the current plugin will fail to load.
+function around:evalute(orig, chunk)
+end
+
+-- Adding methods to the override object causes them to be called
+-- instead of the original method of the same name. If the method being added
+-- (in this case displayresults) does not exist on the REPL object loading this
+-- plugin, the current plugin will fail to load.
+function override:name()
+ return 'Plugin!'
+end
diff --git a/lib/lua-repl/repl/plugins/filename_completion.lua b/lib/lua-repl/repl/plugins/filename_completion.lua
new file mode 100644
index 00000000..c16729f3
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/filename_completion.lua
@@ -0,0 +1,63 @@
+local utils = require 'repl.utils'
+local lfs = require 'lfs'
+
+repl:requirefeature 'completion'
+
+local function guess_directory_separator(file_name)
+ return file_name:match('/') or
+ file_name:match('\\') or
+ '/'
+end
+
+local function split_parent_directory(file_name)
+ local parent_directory, directory_entry =
+ file_name:match('^(.+)[\\/](.+)$')
+ if not parent_directory then
+ parent_directory = '.'
+ directory_entry = file_name
+ end
+ return parent_directory, directory_entry
+end
+
+local function is_ignored_directory_entry(entry)
+ return entry == '.' or
+ entry == '..'
+end
+
+local function replace_end_of_string(str, suffix, replacement)
+ assert(str:sub(-#suffix) == suffix)
+ return str:sub(1, -(#suffix+1)) .. replacement
+end
+
+local function complete_file_name(file_name, expr, callback)
+ local directory, partial_entry = split_parent_directory(file_name)
+ for entry in lfs.dir(directory) do
+ if not is_ignored_directory_entry(entry) and
+ entry:find(partial_entry, 1, true) == 1 then
+ callback(replace_end_of_string(expr, partial_entry, entry))
+ end
+ end
+end
+
+local function complete_directory(directory, expr, callback)
+ for entry in lfs.dir(directory) do
+ if not is_ignored_directory_entry(entry) then
+ callback(expr..entry)
+ end
+ end
+end
+
+function after:complete(expr, callback)
+ if utils.ends_in_unfinished_string(expr) then
+ local file_name = expr:match('[%w@/\\.-_+#$%%{}[%]!~ ]+$')
+ if file_name then
+ if file_name:find('[/\\]$') then
+ complete_directory(file_name, expr, callback)
+ else
+ complete_file_name(file_name, expr, callback)
+ end
+ else
+ complete_directory('.', expr, callback)
+ end
+ end
+end
diff --git a/lib/lua-repl/repl/plugins/history.lua b/lib/lua-repl/repl/plugins/history.lua
new file mode 100644
index 00000000..6330dbd2
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/history.lua
@@ -0,0 +1,60 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local history_file
+
+local function invokecallback(self, name, ...)
+ if not self._history_callbacks then
+ return
+ end
+
+ local impl = self._history_callbacks[name]
+ return impl(...)
+end
+
+local function init()
+ if os.getenv 'HOME' then
+ history_file = os.getenv('HOME') .. '/.rep.lua.history'
+ end
+end
+
+-- XXX I don't know if this callback setup way
+-- is the best way to go about this (in fact
+-- I'm pretty sure it isn't), but I just need
+-- something that works right now.
+function repl:setuphistorycallbacks(callbacks)
+ self._history_callbacks = callbacks
+
+ if history_file then
+ invokecallback(self, 'load', history_file)
+ end
+end
+
+function after:handleline(line)
+ invokecallback(self, 'addline', line)
+end
+
+function before:shutdown()
+ if history_file then
+ invokecallback(self, 'save', history_file)
+ end
+end
+
+features = 'history'
+
+init()
diff --git a/lib/lua-repl/repl/plugins/keep_last_eval.lua b/lib/lua-repl/repl/plugins/keep_last_eval.lua
new file mode 100644
index 00000000..01a77946
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/keep_last_eval.lua
@@ -0,0 +1,43 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- A plugin that stores the results of the last evaluation in _G._
+
+local tostring = tostring
+
+function before:displayresults(results)
+ local context = self:getcontext()
+
+ if self._keep_eval_lastn then
+ context._ = nil
+
+ for i = 1, self._keep_eval_lastn do
+ context['_' .. tostring(i)] = nil
+ end
+ end
+
+ if results.n > 0 then
+ context._ = results[1]
+
+ for i = 1, results.n do
+ context['_' .. tostring(i)] = results[i]
+ end
+
+ self._keep_eval_lastn = results.n
+ end
+end
diff --git a/lib/lua-repl/repl/plugins/linenoise.lua b/lib/lua-repl/repl/plugins/linenoise.lua
new file mode 100644
index 00000000..4407c535
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/linenoise.lua
@@ -0,0 +1,59 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- A plugin that uses linenoise (https://github.com/hoelzro/lua-linenoise) for prompting
+
+local ln = require 'linenoise'
+
+repl:requirefeature 'console'
+
+function override:showprompt(prompt)
+ self._prompt = prompt -- XXX how do we make sure other plugins don't step on this?
+end
+
+function override:lines()
+ return function()
+ return ln.linenoise(self._prompt .. ' ')
+ end
+end
+
+repl:iffeature('completion', function()
+ ln.setcompletion(function(completions, line)
+ repl:complete(line, function(completion)
+ ln.addcompletion(completions, completion)
+ end)
+ end)
+end)
+
+repl:ifplugin('history', function()
+ repl:setuphistorycallbacks {
+ load = function(filename)
+ ln.historyload(filename)
+ end,
+
+ addline = function(line)
+ ln.historyadd(line)
+ end,
+
+ save = function(filename)
+ ln.historysave(filename)
+ end,
+ }
+end)
+
+features = 'input'
diff --git a/lib/lua-repl/repl/plugins/pretty_print.lua b/lib/lua-repl/repl/plugins/pretty_print.lua
new file mode 100644
index 00000000..2318f444
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/pretty_print.lua
@@ -0,0 +1,262 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- Pretty prints expression results (console only)
+
+local format = string.format
+local tconcat = table.concat
+local tsort = table.sort
+local tostring = tostring
+local type = type
+local floor = math.floor
+local pairs = pairs
+local ipairs = ipairs
+local error = error
+local stderr = io.stderr
+
+pcall(require, 'luarocks.require')
+local ok, term = pcall(require, 'term')
+if not ok then
+ term = nil
+end
+
+local keywords = {
+ ['and'] = true,
+ ['break'] = true,
+ ['do'] = true,
+ ['else'] = true,
+ ['elseif'] = true,
+ ['end'] = true,
+ ['false'] = true,
+ ['for'] = true,
+ ['function'] = true,
+ ['if'] = true,
+ ['in'] = true,
+ ['local'] = true,
+ ['nil'] = true,
+ ['not'] = true,
+ ['or'] = true,
+ ['repeat'] = true,
+ ['return'] = true,
+ ['then'] = true,
+ ['true'] = true,
+ ['until'] = true,
+ ['while'] = true,
+}
+
+local function compose(f, g)
+ return function(...)
+ return f(g(...))
+ end
+end
+
+local emptycolormap = setmetatable({}, { __index = function()
+ return function(s)
+ return s
+ end
+end})
+
+local colormap = emptycolormap
+
+if term then
+ colormap = {
+ ['nil'] = term.colors.blue,
+ string = term.colors.yellow,
+ punctuation = compose(term.colors.green, term.colors.bright),
+ ident = term.colors.red,
+ boolean = term.colors.green,
+ number = term.colors.cyan,
+ path = term.colors.white,
+ misc = term.colors.magenta,
+ }
+end
+
+local function isinteger(n)
+ return type(n) == 'number' and floor(n) == n
+end
+
+local function isident(s)
+ return type(s) == 'string' and not keywords[s] and s:match('^[a-zA-Z_][a-zA-Z0-9_]*$')
+end
+
+-- most of these are arbitrary, I *do* want numbers first, though
+local type_order = {
+ number = 0,
+ string = 1,
+ userdata = 2,
+ table = 3,
+ thread = 4,
+ boolean = 5,
+ ['function'] = 6,
+ cdata = 7,
+}
+
+local function cross_type_order(a, b)
+ local pos_a = type_order[ type(a) ]
+ local pos_b = type_order[ type(b) ]
+
+ if pos_a == pos_b then
+ return a < b
+ else
+ return pos_a < pos_b
+ end
+end
+
+local function sortedpairs(t)
+ local keys = {}
+
+ local seen_non_string
+
+ for k in pairs(t) do
+ keys[#keys + 1] = k
+
+ if not seen_non_string and type(k) ~= 'string' then
+ seen_non_string = true
+ end
+ end
+
+ local sort_func = seen_non_string and cross_type_order or nil
+ tsort(keys, sort_func)
+
+ local index = 1
+ return function()
+ if keys[index] == nil then
+ return nil
+ else
+ local key = keys[index]
+ local value = t[key]
+ index = index + 1
+
+ return key, value
+ end
+ end, keys
+end
+
+local function find_longstring_nest_level(s)
+ local level = 0
+
+ while s:find(']' .. string.rep('=', level) .. ']', 1, true) do
+ level = level + 1
+ end
+
+ return level
+end
+
+local function dump(params)
+ local pieces = params.pieces
+ local seen = params.seen
+ local path = params.path
+ local v = params.value
+ local indent = params.indent
+
+ local t = type(v)
+
+ if t == 'nil' or t == 'boolean' or t == 'number' then
+ pieces[#pieces + 1] = colormap[t](tostring(v))
+ elseif t == 'string' then
+ if v:match '\n' then
+ local level = find_longstring_nest_level(v)
+ pieces[#pieces + 1] = colormap.string('[' .. string.rep('=', level) .. '[' .. v .. ']' .. string.rep('=', level) .. ']')
+ else
+ pieces[#pieces + 1] = colormap.string(format('%q', v))
+ end
+ elseif t == 'table' then
+ if seen[v] then
+ pieces[#pieces + 1] = colormap.path(seen[v])
+ return
+ end
+
+ seen[v] = path
+
+ local lastintkey = 0
+
+ pieces[#pieces + 1] = colormap.punctuation '{\n'
+ for i, v in ipairs(v) do
+ for j = 1, indent do
+ pieces[#pieces + 1] = ' '
+ end
+ dump {
+ pieces = pieces,
+ seen = seen,
+ path = path .. '[' .. tostring(i) .. ']',
+ value = v,
+ indent = indent + 1,
+ }
+ pieces[#pieces + 1] = colormap.punctuation ',\n'
+ lastintkey = i
+ end
+
+ for k, v in sortedpairs(v) do
+ if not (isinteger(k) and k <= lastintkey and k > 0) then
+ for j = 1, indent do
+ pieces[#pieces + 1] = ' '
+ end
+
+ if isident(k) then
+ pieces[#pieces + 1] = colormap.ident(k)
+ else
+ pieces[#pieces + 1] = colormap.punctuation '['
+ dump {
+ pieces = pieces,
+ seen = seen,
+ path = path .. '.' .. tostring(k),
+ value = k,
+ indent = indent + 1,
+ }
+ pieces[#pieces + 1] = colormap.punctuation ']'
+ end
+ pieces[#pieces + 1] = colormap.punctuation ' = '
+ dump {
+ pieces = pieces,
+ seen = seen,
+ path = path .. '.' .. tostring(k),
+ value = v,
+ indent = indent + 1,
+ }
+ pieces[#pieces + 1] = colormap.punctuation ',\n'
+ end
+ end
+
+ for j = 1, indent - 1 do
+ pieces[#pieces + 1] = ' '
+ end
+
+ pieces[#pieces + 1] = colormap.punctuation '}'
+ else
+ pieces[#pieces + 1] = colormap.misc(tostring(v))
+ end
+end
+
+repl:requirefeature 'console'
+
+function override:displayresults(results)
+ local pieces = {}
+
+ for i = 1, results.n do
+ dump {
+ pieces = pieces,
+ seen = {},
+ path = '<topvalue>',
+ value = results[i],
+ indent = 1,
+ }
+ pieces[#pieces + 1] = '\n'
+ end
+
+ stderr:write(tconcat(pieces, ''))
+end
diff --git a/lib/lua-repl/repl/plugins/rcfile.lua b/lib/lua-repl/repl/plugins/rcfile.lua
new file mode 100644
index 00000000..a74e81ac
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/rcfile.lua
@@ -0,0 +1,54 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-- A plugin that runs code in $HOME/.rep.lua before the REPL starts
+
+local setfenv = require('repl.utils').setfenv
+
+local function readable(filename)
+ local f = io.open(filename, 'r')
+ if not f then
+ return false
+ end
+ f:close()
+ return true
+end
+
+local function init()
+ local home = os.getenv 'HOME'
+
+ if not home then
+ return
+ end
+
+ local rcfile = home .. '/.rep.lua'
+
+ if not readable(rcfile) then
+ return
+ end
+
+ local chunk = assert(loadfile(rcfile))
+ local env = setmetatable({ repl = repl }, { __index = _G, __newindex = _G })
+
+ setfenv(chunk, env)
+
+ chunk()
+ return true
+end
+
+return init()
diff --git a/lib/lua-repl/repl/plugins/rlwrap.lua b/lib/lua-repl/repl/plugins/rlwrap.lua
new file mode 100644
index 00000000..9f195a3f
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/rlwrap.lua
@@ -0,0 +1,41 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+if os.getenv 'LUA_REPL_RLWRAP' then
+ features = 'input'
+else
+ -- XXX check that we're not receiving input from a non-tty
+ local has_rlwrap = os.execute('which rlwrap >/dev/null 2>/dev/null')
+
+ if type(has_rlwrap) ~= 'boolean' then
+ has_rlwrap = has_rlwrap == 0
+ end
+
+ if not has_rlwrap then
+ error 'Please install rlwrap in order to use the rlwrap plugin'
+ end
+
+ local lowest_index = -1
+
+ while arg[lowest_index] ~= nil do
+ lowest_index = lowest_index - 1
+ end
+ lowest_index = lowest_index + 1
+ os.execute(string.format('LUA_REPL_RLWRAP=1 rlwrap %q %q', arg[lowest_index], arg[0]))
+ os.exit(0)
+end
diff --git a/lib/lua-repl/repl/plugins/semicolon_suppress_output.lua b/lib/lua-repl/repl/plugins/semicolon_suppress_output.lua
new file mode 100644
index 00000000..4adaecb6
--- /dev/null
+++ b/lib/lua-repl/repl/plugins/semicolon_suppress_output.lua
@@ -0,0 +1,36 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local smatch = string.match
+
+-- XXX will this affect any other plugins?
+function around:compilechunk(orig, chunk)
+ local f, err = orig(self, chunk)
+
+ if not f then
+ return f, err
+ end
+
+ if smatch(chunk, ';%s*$') then
+ return function()
+ f()
+ end
+ end
+
+ return f
+end
diff --git a/lib/lua-repl/repl/sync.lua b/lib/lua-repl/repl/sync.lua
new file mode 100644
index 00000000..a422ed4b
--- /dev/null
+++ b/lib/lua-repl/repl/sync.lua
@@ -0,0 +1,46 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local repl = require 'repl'
+local sync_repl = repl:clone()
+local error = error
+
+-- @class repl.sync
+--- This module implements a synchronous REPL. It provides
+--- a run() method for actually running the REPL, and requires
+--- that implementors implement the lines() method.
+
+--- Run a REPL loop in a synchronous fashion.
+-- @name repl.sync:run
+function sync_repl:run()
+ self:prompt(1)
+ for line in self:lines() do
+ local level = self:handleline(line)
+ self:prompt(level)
+ end
+ self:shutdown()
+end
+
+--- Returns an iterator that yields lines to be evaluated.
+-- @name repl.sync:lines
+-- @return An iterator.
+function sync_repl:lines()
+ error 'You must implement the lines method'
+end
+
+return sync_repl
diff --git a/lib/lua-repl/repl/utils.lua b/lib/lua-repl/repl/utils.lua
new file mode 100644
index 00000000..3e2ca18f
--- /dev/null
+++ b/lib/lua-repl/repl/utils.lua
@@ -0,0 +1,70 @@
+-- Copyright (c) 2011-2015 Rob Hoelz <rob@hoelz.ro>
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+-- the Software, and to permit persons to whom the Software is furnished to do so,
+-- subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+-- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+-- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local setfenv = setfenv or function(f, t)
+ local upvalue_index = 1
+
+ -- XXX we may need a utility library if debug isn't available
+ while true do
+ local name = debug.getupvalue(f, upvalue_index)
+ -- some functions don't have an _ENV upvalue, because
+ -- they never refer to globals
+ if not name then
+ return
+ end
+ if name == '_ENV' then
+ debug.setupvalue(f, upvalue_index, t)
+ return
+ end
+ upvalue_index = upvalue_index + 1
+ end
+end
+
+--- Tests wheter an expression ends in an unfinished string literal.
+-- @return First position in the unfinished string literal or `nil`.
+local function ends_in_unfinished_string(expr)
+ local position = 0
+ local quote
+ local current_delimiter
+ local last_unmatched_start
+ while true do
+ -- find all quotes:
+ position, quote = expr:match('()([\'"])', position+1)
+ if not position then break end
+ -- if we're currently in a string:
+ if current_delimiter then
+ -- would end current string?
+ if current_delimiter == quote then
+ -- not escaped?
+ if expr:sub(position-1, position-1) ~= '\\' then
+ current_delimiter = nil
+ last_unmatched_start = nil
+ end
+ end
+ else
+ current_delimiter = quote
+ last_unmatched_start = position+1
+ end
+ end
+ return last_unmatched_start
+end
+
+return {
+ setfenv = setfenv,
+ ends_in_unfinished_string = ends_in_unfinished_string
+}
diff --git a/lib/lua-repl/t/abstract-repl-tests.lua b/lib/lua-repl/t/abstract-repl-tests.lua
new file mode 100644
index 00000000..50d4b07c
--- /dev/null
+++ b/lib/lua-repl/t/abstract-repl-tests.lua
@@ -0,0 +1,33 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+
+plan(8)
+
+do -- getprompt tests {{{
+ is(repl:getprompt(1), '>')
+ is(repl:getprompt(2), '>>')
+end -- }}}
+
+do -- prompt abstract tests {{{
+ error_like(function()
+ repl:prompt(1)
+ end, 'You must implement the showprompt method')
+
+ error_like(function()
+ repl:prompt(2)
+ end, 'You must implement the showprompt method')
+end -- }}}
+
+do -- name tests {{{
+ is(repl:name(), 'REPL')
+end -- }}}
+
+do -- handleline abstract tests {{{
+ is(_G.testresult, nil)
+ error_like(function()
+ repl:handleline '_G.testresult = 17'
+ end, 'You must implement the displayresults method')
+ is(_G.testresult, 17)
+end -- }}}
diff --git a/lib/lua-repl/t/clone-repl-tests.lua b/lib/lua-repl/t/clone-repl-tests.lua
new file mode 100644
index 00000000..3ec82f92
--- /dev/null
+++ b/lib/lua-repl/t/clone-repl-tests.lua
@@ -0,0 +1,119 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+
+plan(45)
+
+local clone = repl:clone()
+local prompt
+local results
+local errmsg
+
+isnt(type(clone), 'nil')
+
+function clone:showprompt(p)
+ prompt = p
+end
+
+function clone:displayresults(r)
+ results = r
+end
+
+function clone:displayerror(err)
+ errmsg = err
+end
+
+do -- prompt tests {{{
+ lives_ok(function()
+ clone:prompt(1)
+ end)
+
+ is(prompt, '>')
+
+ lives_ok(function()
+ clone:prompt(2)
+ end)
+
+ is(prompt, '>>')
+end -- }}}
+
+do -- handleline tests {{{
+ is(_G.testresult, nil)
+ is(results, nil)
+
+ lives_ok(function()
+ clone:handleline '_G.testresult = 18'
+ end)
+
+ is(_G.testresult, 18)
+
+ is(type(results), 'table')
+ is(results.n, 0)
+ is(#results, 0)
+
+ lives_ok(function()
+ clone:handleline 'return 19'
+ end)
+
+ is(type(results), 'table')
+ is(results.n, 1)
+ is(#results, 1)
+ is(results[1], 19)
+
+ lives_ok(function()
+ clone:handleline 'return 20, 21, 22'
+ end)
+
+ is(type(results), 'table')
+ is(results.n, 3)
+ is(#results, 3)
+ is(results[1], 20)
+ is(results[2], 21)
+ is(results[3], 22)
+
+ lives_ok(function()
+ clone:handleline 'return 1, nil, nil, nil, nil, nil, nil, 2'
+ end)
+
+ is(type(results), 'table')
+ is(results.n, 8)
+ is(results[1], 1)
+ for i = 2, 7 do
+ is(results[i], nil)
+ end
+ is(results[8], 2)
+end -- }}}
+
+do -- error handling tests {{{
+ lives_ok(function()
+ clone:handleline '3 4'
+ end)
+
+ isnt(type(errmsg), 'nil')
+
+ errmsg = nil
+
+ lives_ok(function()
+ clone:handleline 'error "foo"'
+ end)
+
+ like(errmsg, 'foo')
+end -- }}}
+
+do -- multi-line input tests {{{
+ errmsg = nil
+ _G.t = {}
+
+ lives_ok(function()
+ clone:handleline 'for i = 1, 3 do'
+ clone:handleline ' table.insert(_G.t, i)'
+ clone:handleline 'end'
+ end)
+
+ is(errmsg, nil)
+ is(#_G.t, 3)
+ is(_G.t[1], 1)
+ is(_G.t[2], 2)
+ is(_G.t[3], 3)
+end -- }}}
diff --git a/lib/lua-repl/t/lib/test-utils.lua b/lib/lua-repl/t/lib/test-utils.lua
new file mode 100644
index 00000000..a0bb16ac
--- /dev/null
+++ b/lib/lua-repl/t/lib/test-utils.lua
@@ -0,0 +1,56 @@
+local _M = {}
+
+function _M.next_line_number()
+ local info = debug.getinfo(2, 'l')
+ return info.currentline + 1 -- doesn't work with whitespace
+end
+
+function _M.cmp_tables(lhs, rhs)
+ local ok = true
+ local got
+ local expected
+ local failing_k
+
+ for k, v in pairs(lhs) do
+ local rv = rhs[k]
+
+ if v ~= rv then
+ ok = false
+ failing_k = k
+ got = v
+ expected = rv
+ break
+ end
+ end
+
+ if ok then
+ for k, v in pairs(rhs) do
+ local lv = lhs[k]
+
+ if v ~= lv then
+ ok = false
+ failing_k = k
+ got = lv
+ expected = v
+ break
+ end
+ end
+ end
+
+ if ok then
+ pass()
+ else
+ fail 'value mismatch'
+ diag(string.format(' got[%q]: %s', tostring(failing_k), tostring(got)))
+ diag(string.format('expected[%q]: %s', tostring(failing_k), tostring(expected)))
+ end
+end
+
+function _M.gather_results(...)
+ return {
+ n = select('#', ...),
+ ...,
+ }
+end
+
+return _M
diff --git a/lib/lua-repl/t/plugin-after-tests.lua b/lib/lua-repl/t/plugin-after-tests.lua
new file mode 100644
index 00000000..15a24c11
--- /dev/null
+++ b/lib/lua-repl/t/plugin-after-tests.lua
@@ -0,0 +1,202 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+local utils = require 'test-utils'
+
+plan(26)
+
+local clone = repl:clone()
+
+do -- basic tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_normal
+ local has_called_after
+
+ function with_plugin:foo()
+ has_called_normal = true
+ end
+
+ with_plugin:loadplugin(function()
+ function after:foo()
+ has_called_after = true
+ ok(has_called_normal)
+ end
+ end)
+
+ with_plugin:foo()
+ ok(has_called_normal)
+ ok(has_called_after)
+
+ local line_no
+
+ local _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ function after:nonexistent()
+ end
+ end)
+ end)
+
+ like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
+
+ _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ after.foo = 17
+ end)
+ end)
+
+ like(err, string.format('%d: 17 is not a function', line_no))
+end -- }}}
+
+do -- arguments tests {{{
+ local with_plugin = clone:clone()
+ local orig_args
+ local got_args
+
+ function with_plugin:foo(...)
+ orig_args = {
+ n = select('#', ...),
+ ...,
+ }
+ end
+
+ with_plugin:loadplugin(function()
+ function after:foo(...)
+ got_args = {
+ n = select('#', ...),
+ ...,
+ }
+ end
+ end)
+
+ with_plugin:foo()
+ is(got_args.n, 0)
+ utils.cmp_tables(orig_args, got_args)
+
+ with_plugin:foo(1, 2, 3)
+ is(got_args.n, 3)
+ is(got_args[1], 1)
+ is(got_args[2], 2)
+ is(got_args[3], 3)
+ utils.cmp_tables(orig_args, got_args)
+
+ with_plugin:foo(1, nil, nil, nil, 5)
+ is(got_args.n, 5)
+ is(got_args[1], 1)
+ is(got_args[2], nil)
+ is(got_args[3], nil)
+ is(got_args[4], nil)
+ is(got_args[5], 5)
+ utils.cmp_tables(orig_args, got_args)
+end -- }}}
+
+do -- exception tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_original
+
+ function with_plugin:foo()
+ has_called_original = true
+ end
+
+ with_plugin:loadplugin(function()
+ function after:foo()
+ error 'uh-oh'
+ end
+ end)
+
+ local _, err = pcall(with_plugin.foo, with_plugin)
+
+ like(err, 'uh%-oh')
+ ok(has_called_original)
+end -- }}}
+
+do -- return value tests {{{
+ local with_plugin = clone:clone()
+
+ function with_plugin:foo()
+ return 17
+ end
+
+ function with_plugin:bar()
+ return 18, 19, 20
+ end
+
+ function with_plugin:baz()
+ return 1, nil, nil, nil, 5
+ end
+
+ with_plugin:loadplugin(function()
+ function after:foo()
+ return 18
+ end
+
+ function after:bar()
+ return 18
+ end
+
+ function after:baz()
+ return 18
+ end
+ end)
+
+ local result = with_plugin:foo()
+ is(result, 17)
+
+ local results = utils.gather_results(with_plugin:bar())
+ utils.cmp_tables(results, { n = 3, 18, 19, 20 })
+
+ results = utils.gather_results(with_plugin:baz())
+ utils.cmp_tables(results, { n = 5, 1, nil, nil, nil, 5 })
+end -- }}}
+
+do -- multiple advice, multiple plugins {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function after:foo()
+ calls[#calls + 1] = 'first'
+ end
+ end)
+
+ with_plugin:loadplugin(function()
+ function after:foo()
+ calls[#calls + 1] = 'second'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'original', 'first', 'second' })
+end -- }}}
+
+do -- multiple advice, single plugin {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function after:foo()
+ calls[#calls + 1] = 'first'
+ end
+
+ function after:foo()
+ calls[#calls + 1] = 'second'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'original', 'first', 'second' })
+end -- }}}
diff --git a/lib/lua-repl/t/plugin-around-tests.lua b/lib/lua-repl/t/plugin-around-tests.lua
new file mode 100644
index 00000000..df0ea073
--- /dev/null
+++ b/lib/lua-repl/t/plugin-around-tests.lua
@@ -0,0 +1,218 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+local compat = require 'repl.compat'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+local utils = require 'test-utils'
+
+plan(27)
+
+local clone = repl:clone()
+
+do -- basic tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_normal
+ local has_called_around
+
+ function with_plugin:foo()
+ has_called_normal = true
+ end
+
+ with_plugin:loadplugin(function()
+ function around:foo(orig, ...)
+ has_called_around = true
+ ok(not has_called_normal)
+ local return_values = utils.gather_results(orig(self, ...))
+ ok(has_called_normal)
+ return compat.unpack(return_values, 1, return_values.n)
+ end
+ end)
+
+ with_plugin:foo()
+ ok(has_called_normal)
+ ok(has_called_around)
+
+ local line_no
+
+ local _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ function around:nonexistent()
+ end
+ end)
+ end)
+
+ like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
+
+ _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ around.foo = 17
+ end)
+ end)
+
+ like(err, string.format('%d: 17 is not a function', line_no))
+end -- }}}
+
+do -- arguments tests {{{
+ local with_plugin = clone:clone()
+ local orig_args
+ local got_args
+
+ function with_plugin:foo(...)
+ orig_args = {
+ n = select('#', ...),
+ ...,
+ }
+ end
+
+ with_plugin:loadplugin(function()
+ function around:foo(orig, ...)
+ got_args = {
+ n = select('#', ...),
+ ...,
+ }
+
+ return orig(self, ...)
+ end
+ end)
+
+ with_plugin:foo()
+ is(got_args.n, 0)
+ utils.cmp_tables(orig_args, got_args)
+
+ with_plugin:foo(1, 2, 3)
+ is(got_args.n, 3)
+ is(got_args[1], 1)
+ is(got_args[2], 2)
+ is(got_args[3], 3)
+ utils.cmp_tables(orig_args, got_args)
+
+ with_plugin:foo(1, nil, nil, nil, 5)
+ is(got_args.n, 5)
+ is(got_args[1], 1)
+ is(got_args[2], nil)
+ is(got_args[3], nil)
+ is(got_args[4], nil)
+ is(got_args[5], 5)
+ utils.cmp_tables(orig_args, got_args)
+end -- }}}
+
+do -- exception tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_original
+
+ function with_plugin:foo()
+ has_called_original = true
+ end
+
+ with_plugin:loadplugin(function()
+ function around:foo()
+ error 'uh-oh'
+ end
+ end)
+
+ local _, err = pcall(with_plugin.foo, with_plugin)
+
+ like(err, 'uh%-oh')
+ ok(not has_called_original)
+end -- }}}
+
+do -- return value tests {{{
+ local with_plugin = clone:clone()
+
+ function with_plugin:foo()
+ return 17
+ end
+
+ function with_plugin:bar()
+ return 18, 19, 20
+ end
+
+ function with_plugin:baz()
+ return 1, nil, nil, nil, 5
+ end
+
+ with_plugin:loadplugin(function()
+ function around:foo()
+ return 18
+ end
+
+ function around:bar()
+ return 19, 20, 21
+ end
+
+ function around:baz()
+ return 1, nil, nil, 4
+ end
+ end)
+
+ local result = with_plugin:foo()
+ is(result, 18)
+
+ local results = utils.gather_results(with_plugin:bar())
+ utils.cmp_tables(results, { n = 3, 19, 20, 21 })
+
+ results = utils.gather_results(with_plugin:baz())
+ utils.cmp_tables(results, { n = 4, 1, nil, nil, 4 })
+end -- }}}
+
+do -- multiple advice, multiple plugins {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function around:foo(orig)
+ calls[#calls + 1] = 'before_one'
+ orig()
+ calls[#calls + 1] = 'after_one'
+ end
+ end)
+
+ with_plugin:loadplugin(function()
+ function around:foo(orig)
+ calls[#calls + 1] = 'before_two'
+ orig()
+ calls[#calls + 1] = 'after_two'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'before_two', 'before_one', 'original',
+ 'after_one', 'after_two' })
+end -- }}}
+
+do -- multiple advice, single plugin {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function around:foo(orig)
+ calls[#calls + 1] = 'before_one'
+ orig()
+ calls[#calls + 1] = 'after_one'
+ end
+
+ function around:foo(orig)
+ calls[#calls + 1] = 'before_two'
+ orig()
+ calls[#calls + 1] = 'after_two'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'before_two', 'before_one', 'original',
+ 'after_one', 'after_two' })
+end -- }}}
diff --git a/lib/lua-repl/t/plugin-basic-tests.lua b/lib/lua-repl/t/plugin-basic-tests.lua
new file mode 100644
index 00000000..21283ef8
--- /dev/null
+++ b/lib/lua-repl/t/plugin-basic-tests.lua
@@ -0,0 +1,206 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+local utils = require 'test-utils'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+
+plan(28)
+
+local clone = repl:clone()
+
+do -- basic tests {{{
+ local loaded
+
+ clone:loadplugin(function()
+ loaded = true
+ end)
+
+ ok(loaded)
+
+ error_like(function()
+ clone:loadplugin(function()
+ error 'uh-oh'
+ end)
+ end, 'uh%-oh')
+
+end -- }}}
+
+do -- loading the same plugin twice {{{
+ local function plugin()
+ end
+
+ local line_no
+
+ clone:loadplugin(plugin)
+ local _, err = pcall(function()
+ line_no = utils.next_line_number()
+ clone:loadplugin(plugin)
+ end)
+ like(err, tostring(line_no) .. ': plugin "function:%s+%S+" has already been loaded')
+
+ _, err = pcall(function()
+ line_no = utils.next_line_number()
+ clone:clone():loadplugin(plugin)
+ end)
+ like(err, tostring(line_no) .. ': plugin "function:%s+%S+" has already been loaded')
+
+ repl:clone():loadplugin(plugin)
+ repl:clone():loadplugin(plugin)
+end -- }}}
+
+do -- loading plugins by name {{{
+ local loaded
+
+ package.preload['repl.plugins.test'] = function()
+ loaded = true
+ end
+
+ clone:clone():loadplugin 'test'
+
+ ok(loaded)
+ loaded = false
+
+ clone:clone():loadplugin 'test'
+
+ ok(loaded, 'loading a plugin twice should initialize it twice')
+
+ package.preload['repl.plugins.test'] = function()
+ error 'uh-oh'
+ end
+
+ error_like(function()
+ clone:clone():loadplugin 'test'
+ end, 'uh%-oh')
+
+ package.preload['repl.plugins.test'] = nil
+
+ local line_no
+
+ local _, err = pcall(function()
+ line_no = utils.next_line_number()
+ clone:clone():loadplugin 'test'
+ end)
+ like(err, tostring(line_no) .. ': unable to locate plugin')
+end -- }}}
+
+do -- hasplugin tests {{{
+ local child = repl:clone()
+
+ local plugin = function()
+ end
+
+ child:loadplugin(plugin)
+
+ local grandchild = child:clone()
+
+ ok(not repl:hasplugin(plugin))
+ ok(child:hasplugin(plugin))
+ ok(grandchild:hasplugin(plugin))
+
+ plugin = function()
+ end
+
+ child:loadplugin(plugin)
+
+ ok(not repl:hasplugin(plugin))
+ ok(child:hasplugin(plugin))
+ ok(not grandchild:hasplugin(plugin))
+end -- }}}
+
+do -- global tests {{{
+ local clone = repl:clone()
+ local line_no
+
+ local _, err = pcall(function()
+ clone:loadplugin(function()
+ line_no = utils.next_line_number()
+ foo = 17
+ end)
+ end)
+
+ like(err, tostring(line_no) .. ': global environment is read%-only %(key = "foo"%)')
+
+ _, err = pcall(function()
+ clone:loadplugin(function()
+ line_no = utils.next_line_number()
+ _G.foo = 17
+ end)
+ end)
+
+ like(err, tostring(line_no) .. ': global environment is read%-only %(key = "foo"%)')
+end -- }}}
+
+do -- ifplugin tests {{{
+ local clone = repl:clone()
+ local has_run
+
+ package.preload['repl.plugins.test'] = function()
+ end
+
+ clone:ifplugin('test', function()
+ has_run = true
+ end)
+
+ ok(not has_run)
+
+ clone:loadplugin 'test'
+
+ ok(has_run)
+
+ has_run = false
+
+ clone:ifplugin('test', function()
+ has_run = true
+ end)
+
+ ok(has_run)
+end -- }}}
+
+do -- ifplugin multiple times {{{
+ local clone = repl:clone()
+ local has_run
+ local has_run2
+
+ package.preload['repl.plugins.test'] = function()
+ end
+
+ clone:ifplugin('test', function()
+ has_run = true
+ end)
+
+ clone:ifplugin('test', function()
+ has_run2 = true
+ end)
+
+ clone:loadplugin 'test'
+
+ ok(has_run)
+ ok(has_run2)
+end -- }}}
+
+do -- plugin return value {{{
+ local clone = repl:clone()
+
+ local result = clone:loadplugin(function()
+ return 17
+ end)
+
+ local result2 = clone:loadplugin(function()
+ end)
+
+ local result3, result4 = clone:loadplugin(function()
+ return 18, 19
+ end)
+
+ local result5, result6, result7 = clone:loadplugin(function()
+ return 20, nil, 21
+ end)
+
+ is(result, 17)
+ is(result2, nil)
+ is(result3, 18)
+ is(result4, 19)
+ is(result5, 20)
+ is(result6, nil)
+ is(result7, 21)
+end -- }}}
diff --git a/lib/lua-repl/t/plugin-before-tests.lua b/lib/lua-repl/t/plugin-before-tests.lua
new file mode 100644
index 00000000..cc23ba78
--- /dev/null
+++ b/lib/lua-repl/t/plugin-before-tests.lua
@@ -0,0 +1,201 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+local utils = require 'test-utils'
+
+plan(26)
+
+local clone = repl:clone()
+
+do -- basic tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_normal
+ local has_called_before
+
+ function with_plugin:foo()
+ has_called_normal = true
+ end
+
+ with_plugin:loadplugin(function()
+ function before:foo()
+ has_called_before = true
+ ok(not has_called_normal)
+ end
+ end)
+
+ with_plugin:foo()
+ ok(has_called_normal)
+ ok(has_called_before)
+
+ local line_no
+
+ local _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ function before:nonexistent()
+ end
+ end)
+ end)
+
+ like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
+
+ _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ before.foo = 17
+ end)
+ end)
+
+ like(err, string.format('%d: 17 is not a function', line_no))
+end -- }}}
+
+do -- arguments tests {{{
+ local with_plugin = clone:clone()
+ local orig_args
+ local got_args
+
+ function with_plugin:foo(...)
+ orig_args = {
+ n = select('#', ...),
+ ...,
+ }
+ end
+
+ with_plugin:loadplugin(function()
+ function before:foo(...)
+ got_args = {
+ n = select('#', ...),
+ ...,
+ }
+ end
+ end)
+
+ with_plugin:foo()
+ is(got_args.n, 0)
+ utils.cmp_tables(orig_args, got_args)
+
+ with_plugin:foo(1, 2, 3)
+ is(got_args.n, 3)
+ is(got_args[1], 1)
+ is(got_args[2], 2)
+ is(got_args[3], 3)
+ utils.cmp_tables(orig_args, got_args)
+
+ with_plugin:foo(1, nil, nil, nil, 5)
+ is(got_args.n, 5)
+ is(got_args[1], 1)
+ is(got_args[2], nil)
+ is(got_args[3], nil)
+ is(got_args[4], nil)
+ is(got_args[5], 5)
+ utils.cmp_tables(orig_args, got_args)
+end -- }}}
+
+do -- exception tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_original
+
+ function with_plugin:foo()
+ has_called_original = true
+ end
+
+ with_plugin:loadplugin(function()
+ function before:foo()
+ error 'uh-oh'
+ end
+ end)
+
+ local _, err = pcall(with_plugin.foo, with_plugin)
+
+ like(err, 'uh%-oh')
+ ok(not has_called_original)
+end -- }}}
+
+do -- return value tests {{{
+ local with_plugin = clone:clone()
+
+ function with_plugin:foo()
+ return 17
+ end
+
+ function with_plugin:bar()
+ return 18, 19, 20
+ end
+
+ function with_plugin:baz()
+ return 1, nil, nil, nil, 5
+ end
+
+ with_plugin:loadplugin(function()
+ function before:foo()
+ return 18
+ end
+
+ function before:bar()
+ return 18
+ end
+
+ function before:baz()
+ end
+ end)
+
+ local result = with_plugin:foo()
+ is(result, 17)
+
+ local results = utils.gather_results(with_plugin:bar())
+ utils.cmp_tables(results, { n = 3, 18, 19, 20 })
+
+ results = utils.gather_results(with_plugin:baz())
+ utils.cmp_tables(results, { n = 5, 1, nil, nil, nil, 5 })
+end -- }}}
+
+do -- multiple advice, multiple plugins {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function before:foo()
+ calls[#calls + 1] = 'first'
+ end
+ end)
+
+ with_plugin:loadplugin(function()
+ function before:foo()
+ calls[#calls + 1] = 'second'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'second', 'first', 'original' })
+end -- }}}
+
+do -- multiple advice, single plugin {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function before:foo()
+ calls[#calls + 1] = 'first'
+ end
+
+ function before:foo()
+ calls[#calls + 1] = 'second'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'second', 'first', 'original' })
+end -- }}}
diff --git a/lib/lua-repl/t/plugin-feature-tests.lua b/lib/lua-repl/t/plugin-feature-tests.lua
new file mode 100644
index 00000000..4c74a4c1
--- /dev/null
+++ b/lib/lua-repl/t/plugin-feature-tests.lua
@@ -0,0 +1,132 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+local utils = require 'test-utils'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+
+plan(19)
+
+do -- basic tests {{{
+ local clone = repl:clone()
+
+ clone:loadplugin(function()
+ features = 'foo'
+ end)
+
+ ok(clone:hasfeature 'foo')
+ ok(not clone:hasfeature 'bar')
+ ok(not clone:hasfeature 'baz')
+
+ clone:loadplugin(function()
+ features = { 'bar', 'baz' }
+ end)
+
+ ok(clone:hasfeature 'foo')
+ ok(clone:hasfeature 'bar')
+ ok(clone:hasfeature 'baz')
+end -- }}}
+
+do -- requirefeature {{{
+ local clone = repl:clone()
+
+ clone:loadplugin(function()
+ features = 'foo'
+ end)
+
+ clone:requirefeature 'foo'
+
+ local line_no
+ local _, err = pcall(function()
+ line_no = utils.next_line_number()
+ clone:requirefeature 'bar'
+ end)
+
+ like(err, tostring(line_no) .. ': required feature "bar" not present')
+end -- }}}
+
+do -- conflicts {{{
+ local clone = repl:clone()
+ local line_no
+
+ clone:loadplugin(function()
+ features = 'foo'
+ end)
+
+ local _, err = pcall(function()
+ line_no = utils.next_line_number()
+ clone:loadplugin(function()
+ features = 'foo'
+ end)
+ end)
+ like(err, tostring(line_no) .. ': feature "foo" already present')
+
+ -- XXX what about methods injected into the object?
+end -- }}}
+
+do -- clone:hasfeature {{{
+ local child = repl:clone()
+
+ child:loadplugin(function()
+ features = 'foo'
+ end)
+
+ local grandchild = child:clone()
+
+ ok(not repl:hasfeature 'foo')
+ ok(child:hasfeature 'foo')
+ ok(grandchild:hasfeature 'foo')
+
+ child:loadplugin(function()
+ features = 'bar'
+ end)
+
+ ok(not repl:hasfeature 'bar')
+ ok(child:hasfeature 'bar')
+ ok(not grandchild:hasfeature 'bar')
+end -- }}}
+
+do -- iffeature tests {{{
+ local clone = repl:clone()
+ local has_run
+
+ clone:iffeature('foo', function()
+ has_run = true
+ end)
+
+ ok(not has_run)
+
+ clone:loadplugin(function()
+ features = 'foo'
+ end)
+
+ ok(has_run)
+
+ has_run = false
+
+ clone:iffeature('foo', function()
+ has_run = true
+ end)
+
+ ok(has_run)
+end -- }}}
+
+do -- iffeature multiple times {{{
+ local clone = repl:clone()
+ local has_run
+ local has_run2
+
+ clone:iffeature('foo', function()
+ has_run = true
+ end)
+
+ clone:iffeature('foo', function()
+ has_run2 = true
+ end)
+
+ clone:loadplugin(function()
+ features = 'foo'
+ end)
+
+ ok(has_run)
+ ok(has_run2)
+end -- }}}
diff --git a/lib/lua-repl/t/plugin-override-tests.lua b/lib/lua-repl/t/plugin-override-tests.lua
new file mode 100644
index 00000000..6ea11d8b
--- /dev/null
+++ b/lib/lua-repl/t/plugin-override-tests.lua
@@ -0,0 +1,201 @@
+-- vim:foldmethod=marker
+local repl = require 'repl'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+local utils = require 'test-utils'
+
+plan(25)
+
+local clone = repl:clone()
+
+do -- basic tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_normal
+ local has_called_override
+
+ function with_plugin:foo()
+ has_called_normal = true
+ end
+
+ with_plugin:loadplugin(function()
+ function override:foo()
+ has_called_override = true
+ end
+ end)
+
+ with_plugin:foo()
+ ok(not has_called_normal)
+ ok(has_called_override)
+
+ local line_no
+
+ local _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ function override:nonexistent()
+ end
+ end)
+ end)
+
+ like(err, string.format("%d: The 'nonexistent' method does not exist", line_no))
+
+ _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ override.foo = 17
+ end)
+ end)
+
+ like(err, string.format('%d: 17 is not a function', line_no))
+end -- }}}
+
+do -- arguments tests {{{
+ local with_plugin = clone:clone()
+ local orig_args
+ local got_args
+
+ function with_plugin:foo(...)
+ orig_args = {
+ n = select('#', ...),
+ ...,
+ }
+ end
+
+ with_plugin:loadplugin(function()
+ function override:foo(...)
+ got_args = {
+ n = select('#', ...),
+ ...,
+ }
+ end
+ end)
+
+ with_plugin:foo()
+ is(got_args.n, 0)
+ ok(not orig_args)
+
+ with_plugin:foo(1, 2, 3)
+ is(got_args.n, 3)
+ is(got_args[1], 1)
+ is(got_args[2], 2)
+ is(got_args[3], 3)
+ ok(not orig_args)
+
+ with_plugin:foo(1, nil, nil, nil, 5)
+ is(got_args.n, 5)
+ is(got_args[1], 1)
+ is(got_args[2], nil)
+ is(got_args[3], nil)
+ is(got_args[4], nil)
+ is(got_args[5], 5)
+ ok(not orig_args)
+end -- }}}
+
+do -- exception tests {{{
+ local with_plugin = clone:clone()
+
+ local has_called_original
+
+ function with_plugin:foo()
+ has_called_original = true
+ end
+
+ with_plugin:loadplugin(function()
+ function override:foo()
+ error 'uh-oh'
+ end
+ end)
+
+ local _, err = pcall(with_plugin.foo, with_plugin)
+
+ like(err, 'uh%-oh')
+ ok(not has_called_original)
+end -- }}}
+
+do -- return value tests {{{
+ local with_plugin = clone:clone()
+
+ function with_plugin:foo()
+ return 17
+ end
+
+ function with_plugin:bar()
+ return 18, 19, 20
+ end
+
+ function with_plugin:baz()
+ return 1, nil, nil, nil, 5
+ end
+
+ with_plugin:loadplugin(function()
+ function override:foo()
+ return 18
+ end
+
+ function override:bar()
+ return 19, 20, 21
+ end
+
+ function override:baz()
+ return 1, nil, nil, 4
+ end
+ end)
+
+ local result = with_plugin:foo()
+ is(result, 18)
+
+ local results = utils.gather_results(with_plugin:bar())
+ utils.cmp_tables(results, { n = 3, 19, 20, 21 })
+
+ results = utils.gather_results(with_plugin:baz())
+ utils.cmp_tables(results, { n = 4, 1, nil, nil, 4 })
+end -- }}}
+
+do -- multiple advice, multiple plugins {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function override:foo()
+ calls[#calls + 1] = 'first'
+ end
+ end)
+
+ with_plugin:loadplugin(function()
+ function override:foo()
+ calls[#calls + 1] = 'second'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'second' })
+end
+
+do -- multiple advice, single plugin {{{
+ local with_plugin = clone:clone()
+ local calls = {}
+
+ function with_plugin:foo()
+ calls[#calls + 1] = 'original'
+ end
+
+ with_plugin:loadplugin(function()
+ function override:foo()
+ calls[#calls + 1] = 'first'
+ end
+
+ function override:foo()
+ calls[#calls + 1] = 'second'
+ end
+ end)
+
+ with_plugin:foo()
+
+ utils.cmp_tables(calls, { 'second' })
+end
diff --git a/lib/lua-repl/t/plugin-repl-tests.lua b/lib/lua-repl/t/plugin-repl-tests.lua
new file mode 100644
index 00000000..b3aa7665
--- /dev/null
+++ b/lib/lua-repl/t/plugin-repl-tests.lua
@@ -0,0 +1,75 @@
+-- vim:foldmethod=marker
+local r = require 'repl' -- we don't call it 'repl' so we don't shadow
+ -- repl in the plugin environment
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+local utils = require 'test-utils'
+
+plan(5)
+
+local clone = r:clone()
+
+do -- basic tests {{{
+ local with_plugin = clone:clone()
+
+ function with_plugin:foo()
+ end
+
+ local line_no
+
+ local _, err = pcall(function()
+ with_plugin:loadplugin(function()
+ line_no = utils.next_line_number()
+ function repl:foo()
+ end
+ end)
+ end)
+
+ like(err, string.format("%d: The 'foo' method already exists", line_no))
+
+ with_plugin:loadplugin(function()
+ function repl:bar()
+ return 17
+ end
+ end)
+
+ is(with_plugin:bar(), 17)
+
+ with_plugin:loadplugin(function()
+ repl.baz = 18
+ end)
+
+ is(with_plugin.baz, 18)
+end -- }}}
+
+do -- conflict tests {{{
+ local clone = r:clone()
+ local line_no
+
+ clone:loadplugin(function()
+ function repl:foo()
+ end
+ end)
+
+ local _, err = pcall(function()
+ clone:loadplugin(function()
+ line_no = utils.next_line_number()
+ function repl:foo()
+ end
+ end)
+ end)
+
+ like(err, tostring(line_no) .. ": The 'foo' method already exists")
+end -- }}}
+
+do -- proxy tests {{{
+ local clone = r:clone()
+
+ clone:loadplugin(function()
+ features = 'foo'
+ end)
+
+ clone:loadplugin(function()
+ ok(repl:hasfeature 'foo')
+ end)
+end -- }}}
diff --git a/lib/lua-repl/t/sync-repl-tests.lua b/lib/lua-repl/t/sync-repl-tests.lua
new file mode 100644
index 00000000..d5249ec2
--- /dev/null
+++ b/lib/lua-repl/t/sync-repl-tests.lua
@@ -0,0 +1,52 @@
+-- vim:foldmethod=marker
+local sync = require 'repl.sync'
+pcall(require, 'luarocks.loader')
+require 'Test.More'
+
+plan(13)
+
+local clone = sync:clone()
+local resultlist = {}
+
+function clone:lines()
+ local index = 0
+ local function iterator(s)
+ index = index + 1
+ return s[index]
+ end
+
+ return iterator, {
+ 'return foo',
+ 'return 1',
+ 'return "bar"',
+ 'return {}',
+ 'return 1, 2, 3',
+ }
+end
+
+function clone:showprompt()
+end
+
+function clone:displayresults(results)
+ resultlist[#resultlist + 1] = results
+end
+
+clone:run()
+
+is(#resultlist, 5)
+is(resultlist[1].n, 1)
+is(resultlist[1][1], nil)
+
+is(resultlist[2].n, 1)
+is(resultlist[2][1], 1)
+
+is(resultlist[3].n, 1)
+is(resultlist[3][1], 'bar')
+
+is(resultlist[4].n, 1)
+is(type(resultlist[4][1]), 'table')
+
+is(resultlist[5].n, 3)
+is(resultlist[5][1], 1)
+is(resultlist[5][2], 2)
+is(resultlist[5][3], 3)