├── .gitignore ├── README.md ├── completer.lua ├── config.ld ├── iluajit.lua ├── readline.lua └── shell.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /docs 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ILuaJIT - Readline powered shell for LuaJIT 2 | =========================================== 3 | 4 | ## Introduction 5 | 6 | **This project is just at early stages of it's development.** 7 | It's nowhere near finished or stable or anything ! 8 | 9 | This script provides a shell with readline integration to LuaJIT. It uses a 10 | adapted version [lua-rlcompleter](https://github.com/rrthomas/lua-rlcompleter) 11 | for completion engine and a pure Lua binding for readline (thanks to LuaJIT FFI). 12 | 13 | It is also intended to ease interaction with Lua a bit like IPython for Python 14 | with features such as: 15 | 16 | * History (not persistant yet) 17 | * Improved error handling: shows the code which caused error 18 | * Pretty printed output: terminal colors, print tables, ... 19 | * Anything else you could imagine ! (any contribution is welcome) 20 | 21 | Complete HTML documentation is available online [here](http://jdesgats.github.com/ILuaJIT). 22 | 23 | 24 | ## Installation 25 | 26 | ILuaJIT has not been tested on a lot of systems but should work on any Readline 27 | capable system. 28 | 29 | You need at least the following dependencies installed : 30 | 31 | * [LuaJIT 2.0 beta 9](http://luajit.org/download.html) 32 | * [Penlight](https://github.com/stevedonovan/Penlight) in your `LUA_PATH` 33 | * libreadline 34 | 35 | Optionnally, if [lfs](http://keplerproject.github.com/luafilesystem) is installed, 36 | it will be used to complete file names inside strings. 37 | 38 | Once eveything is set up, just clone the repo or unpack an 39 | [archive](https://github.com/jdesgats/ILuaJIT/zipball/master) somewhere. 40 | 41 | [HTML documentation](http://jdesgats.github.com/ILuaJIT) is done using 42 | [LDoc](https://github.com/stevedonovan/LDoc). As a config file is provided, all 43 | you have to do to build it is to invoke LDoc in ILuaJIT directory. 44 | 45 | ## Usage 46 | 47 | To run the shell, start just `iluajit.lua` : 48 | 49 | luajit iluajit.lua 50 | 51 | An extra global variable `shell` is available to customize nearly any aspect of 52 | the shell. To see available options, see `shell` module documentation. 53 | 54 | ## Known issues 55 | 56 | * `undefined symbol: PC` error on startup: it's a dependency problem when 57 | loading libreadline, try to preload libtermcap (with something like 58 | `LD_PRELOAD=/usr/lib/libtermcap.so luajit iluajit.lua`). 59 | See [this post](http://lua-users.org/lists/lua-l/2011-12/msg00705.html). 60 | 61 | ## License (MIT) 62 | 63 | Copyright (C) 2011-2012 Julien Desgats 64 | 65 | Permission is hereby granted, free of charge, to any person obtaining a copy of 66 | this software and associated documentation files (the "Software"), to deal in 67 | the Software without restriction, including without limitation the rights to 68 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 69 | of the Software, and to permit persons to whom the Software is furnished to do 70 | so, subject to the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be included in all 73 | copies or substantial portions of the Software. 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 76 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 77 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 78 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 79 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 80 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 81 | SOFTWARE. 82 | -------------------------------------------------------------------------------- /completer.lua: -------------------------------------------------------------------------------- 1 | --- Completion engine 2 | -- Compute possible matches for input text. 3 | -- Based on [lua-rlcompleter](https://github.com/rrthomas/lua-rlcompleter) by 4 | -- Patrick Rapin and Reuben Thomas. 5 | -- @alias M 6 | 7 | local lfs = pcall(require, "lfs") and require"lfs" 8 | local cowrap, coyield = coroutine.wrap, coroutine.yield 9 | 10 | local M = { } 11 | 12 | --- The list of Lua keywords 13 | M.keywords = { 14 | 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 15 | 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 16 | 'return', 'then', 'true', 'until', 'while' 17 | } 18 | 19 | --- Callback function to set final character. 20 | -- Called to set the final chracter if a single match occur. Typically, it is used 21 | -- to close a string if completion occurs inside an open string. It is intended 22 | -- to be redefined as default function does nothing. 23 | -- @param[type=string] char Either an empty string or a string of length 1. 24 | M.final_char_setter = function(char) end 25 | 26 | --- References all completion generators (completers). 27 | -- Completion is context sensitive and there is 2 *contexts* : values 28 | -- (@{completers.value}) and strings (@{completers.string}). Some arguments are 29 | -- given to the completer the first time it's called. 30 | -- 31 | -- Each context has its corresponding table (sequence) with *completers* inside. 32 | -- These tables works a bit like `package.loaders` : each loader is called as a 33 | -- coroutine and can generate possible matches (by yielding strings). But, unlike 34 | -- `package.loaders`, all completers are always called; even if matches has been 35 | -- generated by previous ones. 36 | -- 37 | -- It it not the completer job to filter matches with current text (this is done 38 | -- by the complettion engine), but just generate all possible matches. 39 | M.completers = { } 40 | 41 | --- Completers for Lua values. 42 | -- Completers are called with two arguments : 43 | -- 44 | -- * `value` to complete (not necessarily a table) 45 | -- * `separator` used to index value (`.`, `:` or `[`) 46 | -- 47 | -- Default completers for values are : 48 | -- 49 | -- 1. Table field completer: searches for fields inside `value` if it's a table. 50 | -- 2. Metatable completer: if `value` has a metatable, calls first completer 51 | -- with that table. 52 | -- 53 | -- @table completers.value 54 | M.completers.value = { } 55 | 56 | --- Completers for strings. 57 | -- Completers are called with the string to complete. 58 | -- If `lfs` is can be loaded, a completer for files and folders is provided, it 59 | -- search for items starting with given string in current directory. 60 | -- @table completers.string 61 | M.completers.string = { } 62 | 63 | -- Table fields completer 64 | table.insert(M.completers.value, function(t, sep) 65 | if type(t) ~= "table" then return end 66 | for k, v in pairs(t) do 67 | if type(k) == "number" and sep == "[" then 68 | coyield(k.."]") 69 | elseif type(k) == "string" and (sep ~= ":" or type(v) == "function") then 70 | coyield(k) 71 | end 72 | end 73 | end) 74 | 75 | -- Metamethod completer 76 | table.insert(M.completers.value, function(t, sep) 77 | local mt = getmetatable(t) 78 | if mt and type(mt.__index) == "table" then 79 | return M.completers.value[1](mt.__index, sep) -- use regular table completer on metatable 80 | end 81 | end) 82 | 83 | -- This function does the same job as the default completion of readline, 84 | -- completing paths and filenames. Rewritten because 85 | -- rl_basic_word_break_characters is different. 86 | -- Uses LuaFileSystem (lfs) module for this task (if present). 87 | if lfs then 88 | table.insert(M.completers.string, function(str) 89 | local path, name = str:match("(.*)[\\/]+(.*)") 90 | path = (path or ".") .. "/" 91 | name = name or str 92 | -- avoid to trigger an error if folder does not exists 93 | if not lfs.attributes(path) then return end 94 | for f in lfs.dir(path) do 95 | if (lfs.attributes(path .. f) or {}).mode == 'directory' then 96 | coyield(f .. "/") 97 | else 98 | coyield(f) 99 | end 100 | end 101 | end) 102 | end 103 | 104 | -- This function is called back by C function do_completion, itself called 105 | -- back by readline library, in order to complete the current input line. 106 | function M.complete(word, line, startpos, endpos) 107 | -- Helper function registering possible completion words, verifying matches. 108 | local matches = {} 109 | local function add(value) 110 | value = tostring(value) 111 | if value:match("^" .. word) then 112 | matches[#matches + 1] = value 113 | end 114 | end 115 | 116 | local function call_completors(completers, ...) 117 | for _, completer in ipairs(completers) do 118 | local coro = cowrap(completer) 119 | local match = coro(...) -- first call => give parameters 120 | if match then 121 | add(match) 122 | -- continue calling to get next matches 123 | for match in coro do add(match) end 124 | end 125 | end 126 | end 127 | 128 | -- This function is called in a context where a keyword or a global 129 | -- variable can be inserted. Local variables cannot be listed! 130 | local function add_globals() 131 | for _, k in ipairs(M.keywords) do 132 | add(k) 133 | end 134 | call_completors(M.completers.value, _G) 135 | end 136 | 137 | -- Main completion function. It evaluates the current sub-expression 138 | -- to determine its type. Currently supports tables fields, global 139 | -- variables and function prototype completion. 140 | local function contextual_list(expr, sep, str) 141 | if str then 142 | M.final_char_setter('"') 143 | return call_completors(M.completers.string, str) 144 | end 145 | M.final_char_setter("") 146 | if expr and expr ~= "" then 147 | local v = loadstring("return " .. expr) 148 | if v then 149 | call_completors(M.completers.value, v(), sep) 150 | end 151 | end 152 | if #matches == 0 then 153 | add_globals() 154 | end 155 | end 156 | 157 | -- This complex function tries to simplify the input line, by removing 158 | -- literal strings, full table constructors and balanced groups of 159 | -- parentheses. Returns the sub-expression preceding the word, the 160 | -- separator item ( '.', ':', '[', '(' ) and the current string in case 161 | -- of an unfinished string literal. 162 | local function simplify_expression(expr) 163 | -- Replace annoying sequences \' and \" inside literal strings 164 | expr = expr:gsub("\\(['\"])", function (c) 165 | return string.format("\\%03d", string.byte(c)) 166 | end) 167 | local curstring 168 | -- Remove (finished and unfinished) literal strings 169 | while true do 170 | local idx1, _, equals = expr:find("%[(=*)%[") 171 | local idx2, _, sign = expr:find("(['\"])") 172 | if idx1 == nil and idx2 == nil then 173 | break 174 | end 175 | local idx, startpat, endpat 176 | if (idx1 or math.huge) < (idx2 or math.huge) then 177 | idx, startpat, endpat = idx1, "%[" .. equals .. "%[", "%]" .. equals .. "%]" 178 | else 179 | idx, startpat, endpat = idx2, sign, sign 180 | end 181 | if expr:sub(idx):find("^" .. startpat .. ".-" .. endpat) then 182 | expr = expr:gsub(startpat .. "(.-)" .. endpat, " STRING ") 183 | else 184 | expr = expr:gsub(startpat .. "(.*)", function (str) 185 | curstring = str 186 | return "(CURSTRING " 187 | end) 188 | end 189 | end 190 | expr = expr:gsub("%b()"," PAREN ") -- Remove groups of parentheses 191 | expr = expr:gsub("%b{}"," TABLE ") -- Remove table constructors 192 | -- Avoid two consecutive words without operator 193 | expr = expr:gsub("(%w)%s+(%w)","%1|%2") 194 | expr = expr:gsub("%s+", "") -- Remove now useless spaces 195 | -- This main regular expression looks for table indexes and function calls. 196 | return curstring, expr:match("([%.%w%[%]_]-)([:%.%[%(])" .. word .. "$") 197 | end 198 | 199 | -- Now call the processing functions and return the list of results. 200 | local str, expr, sep = simplify_expression(line:sub(1, endpos)) 201 | contextual_list(expr, sep, str) 202 | return matches 203 | end 204 | 205 | return M 206 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | -- LDoc configuration file 2 | title = "ILuaJIT" 3 | description = "Readline powered shell for LuaJIT" 4 | format = "markdown" 5 | 6 | -- Automatic module field detection does not support nested tables. 7 | new_type("setting", "Settings") 8 | 9 | readme = "README.md" 10 | file = { 11 | "shell.lua", 12 | "completer.lua" 13 | } 14 | -------------------------------------------------------------------------------- /iluajit.lua: -------------------------------------------------------------------------------- 1 | --- ILuaNG: improved interactive shell for Lua JIT 2 | -- This little program mainly brings Readline autocompletion (thanks to FFI callbacks) 3 | -- and some others improvements to regular LuaJIT shell. 4 | -- 5 | -- TODO list (just a bunch of ideas, some could be useful, others are completely crazy): 6 | -- * Interface with other Lua implementations (e.g. Lua 5.2 through FFI) 7 | -- * Smarter completion (e.g. module listing on require) 8 | -- * Real time syntax highlight ? 9 | -- * Allow to customize preprocessors (like transforming "=" to "return") 10 | 11 | local readline = require "readline" 12 | local coyield = coroutine.yield 13 | 14 | shell = require "shell" 15 | 16 | shell.completer.final_char_setter = readline.completion_append_character 17 | 18 | io.stdout:write(shell.greetings) 19 | readline.shell{ 20 | getcommand = function() 21 | local func, err 22 | -- get the first line and resolve the "=" shortcut 23 | local cmd = coyield(shell.prompt(true)) .. "\n" 24 | if cmd:sub(1,1) == "=" then 25 | cmd = "return "..cmd:sub(2) 26 | end 27 | 28 | -- continue to get lines until get a complete chunk 29 | while true do 30 | func, err = loadstring(cmd) 31 | if func or err:sub(-7) ~= "''" then break end 32 | cmd = cmd .. coyield(shell.prompt(false)) .. "\n" 33 | end 34 | 35 | if not cmd:match("^%s*$") then 36 | local output 37 | if func then 38 | output = shell.try(cmd) 39 | else output = err end 40 | 41 | -- display the result 42 | io.stdout:write(output, #output > 0 and output:sub(-1, -1) ~= "\n" and "\n" or "") 43 | return cmd:sub(1, -2) -- remove last \n for history 44 | end 45 | end, 46 | 47 | complete = shell.completer.complete, 48 | word_break_characters = " \t\n\"\\'><=;:+-*/%^~#{}()[].,", 49 | } 50 | 51 | io.stderr:write"\n" -- avoid to concatenate iLua and sh prompts at exit 52 | -------------------------------------------------------------------------------- /readline.lua: -------------------------------------------------------------------------------- 1 | -- Very simple binding for Readline in Lua (using FFI callbacks) 2 | -- This is meant to be simple, not complete. 3 | 4 | local ffi = require"ffi" 5 | local assert = assert 6 | local cocreate, coresume, costatus = coroutine.create, coroutine.resume, coroutine.status 7 | local M = { } 8 | 9 | ffi.cdef[[ 10 | /* libc definitions */ 11 | void* malloc(size_t bytes); 12 | void free(void *); 13 | 14 | /* basic history handling */ 15 | char *readline (const char *prompt); 16 | void add_history(const char *line); 17 | 18 | /* completion */ 19 | typedef char **rl_completion_func_t (const char *, int, int); 20 | typedef char *rl_compentry_func_t (const char *, int); 21 | 22 | char **rl_completion_matches (const char *, rl_compentry_func_t *); 23 | 24 | const char *rl_basic_word_break_characters; 25 | rl_completion_func_t *rl_attempted_completion_function; 26 | char *rl_line_buffer; 27 | int rl_completion_append_character; 28 | int rl_attempted_completion_over; 29 | ]] 30 | 31 | local libreadline = ffi.load("readline") 32 | 33 | function M.completion_append_character(char) 34 | libreadline.rl_completion_append_character = #char > 0 and char:byte(1,1) or 0 35 | end 36 | 37 | function M.shell(config) 38 | -- configure completion, if any 39 | if config.complete then 40 | if config.word_break_characters then 41 | libreadline.rl_basic_word_break_characters = config.word_break_characters 42 | end 43 | 44 | function libreadline.rl_attempted_completion_function(word, startpos, endpos) 45 | local strword = ffi.string(word) 46 | local buffer = ffi.string(libreadline.rl_line_buffer) 47 | local matches = config.complete(strword, buffer, startpos, endpos) 48 | if not matches then return nil end 49 | -- if matches is an empty array, tell readline to not call default completion (file) 50 | libreadline.rl_attempted_completion_over = 1 51 | -- translate matches table to C strings 52 | -- (there is probably more efficient ways to do it) 53 | return libreadline.rl_completion_matches(word, function(text, i) 54 | local match = matches[i+1] 55 | if match then 56 | -- readline will free the C string by itself, so create copies of them 57 | local buf = ffi.C.malloc(#match + 1) 58 | ffi.copy(buf, match, #match+1) 59 | return buf 60 | else 61 | return ffi.new("void*", nil) 62 | end 63 | end) 64 | end 65 | end 66 | 67 | -- main loop 68 | local running = true 69 | while running do 70 | local userfunc = cocreate(config.getcommand) 71 | local _, prompt = assert(coresume(userfunc)) 72 | while costatus(userfunc) ~= "dead" do 73 | -- get next line 74 | local s = libreadline.readline(prompt) 75 | if s == nil then -- end of file 76 | running = false 77 | break 78 | end 79 | 80 | local line = ffi.string(s) 81 | ffi.C.free(s) 82 | _, prompt = assert(coresume(userfunc, line)) 83 | end 84 | 85 | if prompt then -- final return value is the value to add to history 86 | libreadline.add_history(prompt) 87 | end 88 | end 89 | end 90 | 91 | return M 92 | -------------------------------------------------------------------------------- /shell.lua: -------------------------------------------------------------------------------- 1 | --- Defines shell behavior and configuration settings. 2 | -- This module is exposed as `shell` global variable in ILuaJIT. Unless stated 3 | -- otherwise, all settings and functions can be changed at any time and changes 4 | -- takes effect immediately. 5 | -- However settings are used internally by callbacks so if you change these 6 | -- functions without take care to read relevant settings at each invocation, 7 | -- some of them could become ineffective. 8 | -- 9 | -- Callbacks should not print someting directly to screen but return text to be 10 | -- displayed. 11 | -- @alias shell 12 | 13 | local completer = require "completer" 14 | local stringio = require "pl.stringio" 15 | local pretty = require "pl.pretty" 16 | 17 | --- Adds VT100 control codes to colorize text anr resets settings. 18 | -- Result is `[...m[0m` 19 | local function colorize(text, ...) 20 | local codes = { } 21 | for i, code in ipairs{...} do codes[i] = tostring(code) end 22 | return "\027[" .. table.concat(codes, ";") .. "m" .. text .. "\027[0m" 23 | end 24 | 25 | --- Highlights a line in a file. 26 | -- Prints given line and some sorrounding lines in a file. The target line is highlighted. 27 | local function highlight_line(file, target, area) 28 | local tmpl = " %04d: %s" 29 | local buffer = { } 30 | local lineno = 1 31 | for line in file:lines() do 32 | if lineno > target+area then break -- area to display is passed 33 | elseif lineno == target then 34 | buffer[#buffer+1] = tmpl:format(lineno, colorize(line, 31)) 35 | elseif lineno >= target - area then 36 | buffer[#buffer+1] = tmpl:format(lineno, line) 37 | end 38 | lineno = lineno + 1 39 | end 40 | return table.concat(buffer, "\n") 41 | end 42 | 43 | local shell = { } 44 | 45 | -------------------------------------------------------------------------------- 46 | -- Misc settings 47 | -- @section misc 48 | 49 | --- ILuaJIT version number. 50 | -- @setting _VERSION 51 | shell._VERSION = "0.1" 52 | 53 | --- ILuaJIT copyright information. 54 | -- @setting _COPYRIGHT 55 | shell._COPYRIGHT = "(c) 2011-2012 Julien Desgats, with contributions from Patrick Rapin and Reuben Thomas" 56 | 57 | --- ILuaJIT license information. 58 | -- @setting _LICENSE 59 | shell._LICENSE = "MIT License" 60 | 61 | --- Message displayed when ILuaJIT is started. 62 | -- @setting greetings 63 | if jit then 64 | shell.greetings = ("ILuaJIT %s, running %s\nJIT:%s %s\n"):format(shell._VERSION, jit.version, 65 | jit.status() and "ON" or "OFF", table.concat({ select(2, jit.status()) }, " ")) 66 | else 67 | shell.greetings = ("ILuaJIT %s, running %s\n"):format(shell._VERSION, _VERSION) 68 | end 69 | 70 | --- Completion engine. 71 | -- 72 | -- Default value is the module @{completer} 73 | -- @setting completer 74 | shell.completer = completer 75 | 76 | --- Command count 77 | -- Number incremented for each new command. It is used internally to generate 78 | -- chunk names. *This value should be **read only**, do not attempt to modify it !* 79 | -- @setting input_sequence 80 | shell.input_sequence = 1 81 | 82 | --- Generates prompt text. 83 | -- Called at each line to generate prompt text. Default implementation returns 84 | -- `> ` for the first line and `>> ` for next ones. 85 | -- @param[type=boolean] primary `true` for first line, `false` for next ones. 86 | -- @return[type=string] Prompt to display. 87 | shell.prompt = function(primary) return primary and "> " or ">> " end 88 | 89 | -------------------------------------------------------------------------------- 90 | -- Values display 91 | -- @section value 92 | 93 | shell.value = { } 94 | 95 | --- Separator for each result. 96 | -- This string is used to join results (in case of multiple results). 97 | -- 98 | -- Default value is `\n`. 99 | -- @setting value.separator 100 | shell.value.separator = "\n" 101 | 102 | --- Whether tables are pretty-printed. 103 | -- If this setting is set to true, then tables are fully printed (insted of 104 | -- traditional `table: 0x...` notation. This setting is currently not fully 105 | -- implemented, it can result in huge outputs as tables are *fully* printed. 106 | -- 107 | -- Default value is `true`. 108 | -- @setting value.prettyprint_tables 109 | shell.value.prettyprint_tables = true 110 | 111 | --- Whether __tostring metamethod is honored. 112 | -- Tells if table pretty printing is done or not if table has a `__tostring` 113 | -- metamethod. If set to `false`, tables will always be pretty printed even if 114 | -- they have the metamethod. This setting takes effect only if 115 | -- @{value.prettyprint_tables} is set to `true`. 116 | -- 117 | -- Default value is `true`. 118 | -- @setting value.table_use_tostring 119 | shell.value.table_use_tostring = true -- when false, pretty print tables even if a __tostring method exists. 120 | 121 | --- Callback to transform result into a printable string. 122 | -- @param[type=number] pos Result position (starting at one). 123 | -- @param value Result to print (can be any type). 124 | -- @return[type=string] A printable representation of `value`. 125 | -- @function value.handler 126 | function shell.value.handler(pos, value) 127 | local tvalue = type(value) 128 | if tvalue == "table" and shell.value.prettyprint_tables then 129 | -- if table has a __tostring metamethod, then use it 130 | local mt = getmetatable(value) 131 | if mt and mt.__tostring and shell.value.table_use_tostring then 132 | value = tostring(value) 133 | else 134 | -- otherwise pretty-print it 135 | -- TODO: make a custom pretty print function: short tables on a single line, 136 | -- clearer indentation, ... 137 | value = pretty.write(value) 138 | end 139 | else -- fall back to default tostring 140 | value = tostring(value) 141 | end 142 | return colorize("["..pos.."]", 1, 30) .. " "..value 143 | end 144 | 145 | 146 | -------------------------------------------------------------------------------- 147 | -- Error handling 148 | -- @section value 149 | shell.onerror = { } 150 | 151 | --- Prints code where error has happend. 152 | -- If set to `true`, error handler will try to load source files to get the code 153 | -- which caused error and print it to screen to help you to catch what happend. 154 | -- 155 | -- Default value is `true`. 156 | -- @setting onerror.print_code 157 | shell.onerror.print_code = true 158 | 159 | --- Number of lines before and after the error to print. 160 | -- 161 | -- Default value is 3. 162 | -- @setting onerror.area 163 | shell.onerror.area = 3 164 | 165 | -- mapping between typed commands (as chunk names) and corresponding source 166 | local src_history = { } 167 | 168 | --- Error handler. 169 | -- This function is directly called by @{xpcall} if command exection has failed 170 | -- (so this will not be called for a syntax error). 171 | -- @param err Error object (not always a string). 172 | -- @return[type=string] Error message to be printed. 173 | -- @function onerror.handler 174 | function shell.onerror.handler(err) 175 | local buffer = { colorize(tostring(err), 31), "Stack traceback:" } 176 | local tmpl = " At %s:%d (in %s %s)" 177 | -- index in buffer of last xpcall call in stack, it will be used to strip ILuaJIT 178 | -- internal functions of the error traceback. 179 | local lastxpcall = 0 180 | for i=2, math.huge do 181 | local info = debug.getinfo(i) 182 | if not info then break end 183 | if info.func == xpcall then lastxpcall = #buffer end 184 | buffer[#buffer+1] = tmpl:format(info.source, info.currentline or -1, info.namewhat, info.name or "?") 185 | if shell.onerror.print_code and (info.what == "Lua" or info.what == "main") and info.currentline then 186 | local file 187 | if src_history[info.source] then file = stringio.open(src_history[info.source]) 188 | elseif info.source:match("^@") then file = io.open(info.source:sub(2), "r") end 189 | if file then 190 | buffer[#buffer+1] = highlight_line(file, info.currentline, shell.onerror.area) 191 | end 192 | end 193 | end 194 | return table.concat(buffer, "\n", 1, lastxpcall) 195 | end 196 | 197 | 198 | -------------------------------------------------------------------------------- 199 | -- Internal functions. 200 | -- These functions should be changed with care. Take a look at source code to see 201 | -- exectly what original functions do. 202 | -- @section internal 203 | 204 | --- Tries to execute a command. 205 | -- Called with command string (once syntax check has passed), this functions 206 | -- compiles the code and execute it. Results are then handled by 207 | -- @{result_handler}. 208 | -- @param[type=string] cmd Valid Lua string to execute. 209 | -- @return[type=string] Execution output. 210 | -- @function try 211 | function shell.try(cmd) 212 | local chunkname = "stdin#"..shell.input_sequence 213 | local func = assert(loadstring(cmd, chunkname)) 214 | shell.input_sequence = shell.input_sequence + 1 215 | src_history[chunkname] = cmd 216 | return shell.result_handler(xpcall(func, shell.onerror.handler)) 217 | end 218 | 219 | --- Handles command results 220 | -- Called by @{try} to format execution results. Default implementation calls 221 | -- @{value.handler} for each result and returns the concatenation of calls. 222 | -- @param[type=boolean] success Whether the call has been successfull. 223 | -- @param ... Command results in case of success or error message in case of failure. 224 | -- @return[type=string] Results as printable string. 225 | function shell.result_handler(success, ...) 226 | if success then 227 | local buf = { } 228 | for i=1, select("#", ...) do 229 | buf[i] = shell.value.handler(i, select(i, ...)) 230 | end 231 | return table.concat(buf, shell.value.separator) 232 | end 233 | -- error 234 | return (...) 235 | end 236 | 237 | return shell 238 | --------------------------------------------------------------------------------