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