Here is implementation of completion for the stand-alone Lua interpreter lua. It is based on the GNU Readline library. The standard Lua distribution already uses Readline optionally in the Makefile, by defining LUA_USE_READLINE. But Readline is only used for editing input command and for the history management, not the completion. To add this, lua.c has to install a callback function in Readline library. To simplify the implementation, this C function is just in charge to call a Lua function that will make the decisions depending on the context, and to return the results in the format expected by Readline. It is indeed very similar in features than Mike Pall's advanced readline patch on LuaPowerPatches. The difference is that it is written essentially in Lua, making it easier to customize for specific applications. ==C code in lua.c== #ifdef LUA_USE_READLINE /* This function is called repeatedly by rl_completion_matches inside do_completion, each time returning one element from the Lua table. */ static char* table_iterator (const char* text, int state) { size_t len; const char* str; char* result; lua_State* L = globalL; lua_rawgeti(L, -1, state+1); if(lua_isnil(L, -1)) return NULL; str = lua_tolstring(L, -1, &len); result = malloc(len + 1); strcpy(result, str); lua_pop(L, 1); return result; } /* This function is called by readline() when the user wants a completion. It has to return an array of strings that match the word in the context. do_completion is called with the word to complete and its position inside the editing buffer. It then calls back Lua function "completion" to do the hard work, which returns a table of matches. rl_completion_matches helps in the process to find the common matching part, and to sort the array. On any error, Lua stack is recovered and NULL is returned. */ static char** do_completion (const char* text, int istart, int iend) { char** matches = NULL; lua_State* L = globalL; int top; if(L == NULL) return NULL; do { top = lua_gettop(L); lua_getglobal(L, "completion"); if(!lua_isfunction(L, -1)) break; lua_pushstring(L, text); lua_pushstring(L, rl_line_buffer); lua_pushinteger(L, istart+1); lua_pushinteger(L, iend+1); if(lua_pcall(L, 4, 1, 0)) break; if(!lua_istable(L, -1)) break; matches = rl_completion_matches (text, table_iterator); } while(0); lua_settop(L, top); return (matches); } /* Initialise Readline for completion. It should be called from main().*/ static void init_completion(lua_State* L) { rl_attempted_completion_function = do_completion; /* This is a list of Lua operators, separating words. */ rl_basic_word_break_characters = " \t\n\"\\'><=;+-*/%^~#{}()[].,"; /* Inhibits the default added space on completed words. */ rl_completion_append_character = 0; /* The completion Lua function is loaded here. The user might prefer to "require" a module or incorporate it into an existing script. */ luaL_dofile(L, "complete.lua"); } #endif
==Lua Code complete.lua== function completion(word, line, startpos, endpos) local keywords = { 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while' } local matches = { } local function add(value) value = tostring(value) if value:match("^"..word) then matches[#matches+1] = value end end local function filename_list(str) local path, name = str:match("(.*)[\\/]+(.*)") path = (path or ".").."/" name = name or str for f in lfs.dir(path) do if (lfs.attributes(path..f) or {}).mode == 'directory' then add(f.."/") else add(f) end end end local function postfix(value) local t = type(value) if t == 'function' or (getmetatable(value) or {}).__call then return '(' elseif t == 'table' and #value > 0 then return '[' elseif t == 'table' then return '.' else return ' ' end end local function add_globals() for _,k in ipairs(keywords) do add(k..' ') end for k,v in pairs(_G) do add(k..postfix(v)) end end local function contextual_list(expr, sep, str) if str then return filename_list(str) end if expr == nil or expr == "" then return add_globals() end local v = loadstring("return "..expr) if not v then return end v = v() local t = type(v) if sep == '.' then if t ~= 'table' then return end for k,v2 in pairs(v) do if type(k) == 'string' then add(k..postfix(v2)) end end elseif sep == '[' then if t ~= 'table' then return end for k,v2 in pairs(v) do if type(k) == 'number' then add(k.."]"..postfix(v2)) end end if word ~= "" then add_globals() end elseif sep == '(' then add("function "..expr.." (...)"..string.rep(" ", 40)) add("°") end end function simplify_expression(expr) expr = expr:gsub("\\(['\"])", function(c) return string.format("\\%03d", string.byte(c)) end) local curstring while true do local idx1,_,equals = expr:find("%[(=*)%[") local idx2,_,sign = expr:find("(['\"])") if idx1 == nil and idx2 == nil then break end local idx,startpat,endpat if (idx1 or math.huge) < (idx2 or math.huge) then idx,startpat,endpat = idx1, "%["..equals.."%[", "%]"..equals.."%]" else idx,startpat,endpat = idx2, sign, sign end if expr:sub(idx):find("^"..startpat..".-"..endpat) then expr = expr:gsub(startpat.."(.-)"..endpat, " STRING ") else expr = expr:gsub(startpat.."(.*)", function(str) curstring = str; return "(CURSTRING " end) end end expr = expr:gsub("%b()"," PAREN ") expr = expr:gsub("%b{}"," TABLE ") expr = expr:gsub("(%w)%s+(%w)","%1|%2") expr = expr:gsub("%s","") return curstring,expr:match("([%.%w%[%]_]-)([%.%[%(])"..word.."$") end local str, expr, sep = simplify_expression(line:sub(1,endpos)) contextual_list(expr, sep, str) return matches end
-- PatrickRapin ==readline as standalone lua module== *If* your Lua interpreter is already compiled with readline, and you don't want to patch it, you can define a module that does all of the completion hooks setup itself. The following is just a very simple adaption of the previous code, then using the same 'complete.lua' for configuration. I cut down on some of the comments, since the code is partially exactly the same as above. /* build@ cc -shared readline.c -o readline.so ** Completely and absolutely minimal binding to the readline library ** Steve Donovan, 2007 ** Merged with http://lua-users.org/wiki/CompleteWithReadline ** by Ulrik, 2010. I disclaim any copyright on my contributions. * * Enable completion in your lua REPL with: * lua -lreadline -lcomplete * where complete.lua defines a global function: * completion (word, line, startpos, endpos) -> array of matches */ #include <stdlib.h> #include <stdio.h> #include <readline/readline.h> #include <readline/history.h> #define LUA_LIB #include "lua.h" #include "lauxlib.h" #include "lualib.h" /* The readline library uses a lot of global variables, * so we do too. Lua state for the completion function. * I'm sorry for this.. */ static lua_State *stateL; static int f_readline(lua_State* L) { const char* prompt = lua_tostring(L,1); char *line = readline(prompt); lua_pushstring(L, line); /* readline return value must be free'd */ free(line); return 1; } static int f_add_history(lua_State* L) { if (lua_strlen(L,1) > 0) { add_history(lua_tostring(L, 1)); } return 0; } static const struct luaL_reg lib[] = { {"readline", f_readline}, {"add_history",f_add_history}, {NULL, NULL}, }; /* This function is called repeatedly by rl_completion_matches inside do_completion, each time returning one element from the Lua table. */ static char* table_iterator (const char* text, int state) { size_t len; const char* str; char* result; lua_State* L = stateL; lua_rawgeti(L, -1, state+1); if(lua_isnil(L, -1)) return NULL; str = lua_tolstring(L, -1, &len); result = malloc(len + 1); strcpy(result, str); lua_pop(L, 1); return result; } /* This function is called by readline() when the user wants a completion. ... */ static char** do_completion (const char* text, int istart, int iend) { char** matches = NULL; lua_State* L = stateL; int top; if(L == NULL) return NULL; do { top = lua_gettop(L); lua_getglobal(L, "completion"); if(!lua_isfunction(L, -1)) break; lua_pushstring(L, text); lua_pushstring(L, rl_line_buffer); lua_pushinteger(L, istart+1); lua_pushinteger(L, iend+1); if(lua_pcall(L, 4, 1, 0)) break; if(!lua_istable(L, -1)) break; matches = rl_completion_matches (text, table_iterator); } while(0); lua_settop(L, top); rl_completion_suppress_append = 1; return (matches); } /* Initialise Readline for completion. It should be called from main().*/ static int init_completion(lua_State* L) { stateL = L; rl_attempted_completion_function = do_completion; /* This is a list of Lua operators, separating words. */ rl_basic_word_break_characters = " \t\n\"\\'><=;+-*/%^~#{}()[].,"; /* Inhibits the default added space on completed words. */ rl_completion_append_character = '\0'; /* The completion Lua function is loaded here. The user might prefer to "require" a module or incorporate it into an existing script. */ return 0; } int luaopen_readline (lua_State *L) { luaL_openlib (L, "readline", lib, 0); (void) init_completion(L); return 1; }
-- Ulrik 2010 apr
|