add a cool lua repl

This commit is contained in:
jacqueline
2023-12-13 16:10:08 +11:00
parent 5a2f0b08e0
commit 64b106c13e
87 changed files with 8150 additions and 5 deletions
+15
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
doc/
*.tar.gz
*.rock
+2
View File
@@ -0,0 +1,2 @@
--ext .lua
--exec lua
+31
View File
@@ -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
+17
View File
@@ -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.
+71
View File
@@ -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.
+43
View File
@@ -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
+14
View File
@@ -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/
+102
View File
@@ -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.
+34
View File
@@ -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
+44
View File
@@ -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
+21
View File
@@ -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
+38
View File
@@ -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' },
}
}
+285
View File
@@ -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.
+41
View File
@@ -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()
+30
View File
@@ -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
}
}
+57
View File
@@ -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
+416
View File
@@ -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
+29
View File
@@ -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
+192
View File
@@ -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'
+41
View File
@@ -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
@@ -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
+60
View File
@@ -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()
@@ -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
+59
View File
@@ -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'
+262
View File
@@ -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
+54
View File
@@ -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()
+41
View File
@@ -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
@@ -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
+46
View File
@@ -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
+70
View File
@@ -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
}
+33
View File
@@ -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 -- }}}
+119
View File
@@ -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 -- }}}
+56
View File
@@ -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
+202
View File
@@ -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 -- }}}
+218
View File
@@ -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 -- }}}
+206
View File
@@ -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 -- }}}
+201
View File
@@ -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 -- }}}
+132
View File
@@ -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 -- }}}
+201
View File
@@ -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
+75
View File
@@ -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 -- }}}
+52
View File
@@ -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)