Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
-- 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.8 } 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) 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