add a cool lua repl
This commit is contained in:
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user