From 94924aa836f50f079d2cf0aa75d35b96b14e11cc Mon Sep 17 00:00:00 2001 From: Ronan Collobert Date: Sat, 15 Feb 2014 12:45:43 +0100 Subject: [PATCH] added support for readline --- luajit/CMakeLists.txt | 10 ++ luajit/cmake/FindReadline.cmake | 80 +++++++++ luajit/src/luajit.c | 293 ++++++++++++++++++++++++++++++-- 3 files changed, 372 insertions(+), 11 deletions(-) create mode 100644 luajit/cmake/FindReadline.cmake diff --git a/luajit/CMakeLists.txt b/luajit/CMakeLists.txt index 9e1fa74..4040183 100644 --- a/luajit/CMakeLists.txt +++ b/luajit/CMakeLists.txt @@ -9,6 +9,9 @@ project(LuaJIT C ASM) CMAKE_MINIMUM_REQUIRED(VERSION 2.6 FATAL_ERROR) CMAKE_POLICY(VERSION 2.6) +SET(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" + "${CMAKE_MODULE_PATH}") + OPTION(WITH_AMALG "Build eveything in one shot (needs memory)" ON) SET(INSTALL_INCLUDE_SUBDIR "include" CACHE STRING "installation include subdirectory name") @@ -50,6 +53,13 @@ IF(APPLE) ENDIF() ENDIF() +# Readline support +FIND_PACKAGE(Readline) +IF(READLINE_FOUND) + ADD_DEFINITIONS("-DLUA_USE_READLINE") + LIST(APPEND LIBS ${READLINE_LIBRARIES}) +ENDIF() + # Various includes INCLUDE(CheckLibraryExists) INCLUDE(CheckFunctionExists) diff --git a/luajit/cmake/FindReadline.cmake b/luajit/cmake/FindReadline.cmake new file mode 100644 index 0000000..95bb942 --- /dev/null +++ b/luajit/cmake/FindReadline.cmake @@ -0,0 +1,80 @@ +# - Find the readline library +# This module defines +# READLINE_INCLUDE_DIR, path to readline/readline.h, etc. +# READLINE_LIBRARIES, the libraries required to use READLINE. +# READLINE_FOUND, If false, do not try to use READLINE. +# also defined, but not for general use are +# READLINE_readline_LIBRARY, where to find the READLINE library. +# READLINE_ncurses_LIBRARY, where to find the ncurses library [might not be defined] + +# Apple readline does not support readline hooks +# So we look for another one by default +IF(APPLE) + FIND_PATH(READLINE_INCLUDE_DIR NAMES readline/readline.h PATHS + /sw/include + /opt/local/include + /opt/include + /usr/local/include + /usr/include/ + NO_DEFAULT_PATH + ) +ENDIF(APPLE) +FIND_PATH(READLINE_INCLUDE_DIR NAMES readline/readline.h) + + +# Apple readline does not support readline hooks +# So we look for another one by default +IF(APPLE) + FIND_LIBRARY(READLINE_readline_LIBRARY NAMES readline PATHS + /sw/lib + /opt/local/lib + /opt/lib + /usr/local/lib + /usr/lib + NO_DEFAULT_PATH + ) +ENDIF(APPLE) +FIND_LIBRARY(READLINE_readline_LIBRARY NAMES readline) + +# Sometimes readline really needs ncurses +IF(APPLE) + FIND_LIBRARY(READLINE_ncurses_LIBRARY NAMES ncurses PATHS + /sw/lib + /opt/local/lib + /opt/lib + /usr/local/lib + /usr/lib + NO_DEFAULT_PATH + ) +ENDIF(APPLE) +FIND_LIBRARY(READLINE_ncurses_LIBRARY NAMES ncurses) + +MARK_AS_ADVANCED( + READLINE_INCLUDE_DIR + READLINE_readline_LIBRARY + READLINE_ncurses_LIBRARY + ) + +SET( READLINE_FOUND "NO" ) +IF(READLINE_INCLUDE_DIR) + IF(READLINE_readline_LIBRARY) + SET( READLINE_FOUND "YES" ) + SET( READLINE_LIBRARIES + ${READLINE_readline_LIBRARY} + ) + + # some readline libraries depend on ncurses + IF(READLINE_ncurses_LIBRARY) + SET(READLINE_LIBRARIES ${READLINE_LIBRARIES} ${READLINE_ncurses_LIBRARY}) + ENDIF(READLINE_ncurses_LIBRARY) + + ENDIF(READLINE_readline_LIBRARY) +ENDIF(READLINE_INCLUDE_DIR) + +IF(READLINE_FOUND) + MESSAGE(STATUS "Found readline library") +ELSE(READLINE_FOUND) + IF(READLINE_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find readline -- please give some paths to CMake") + ENDIF(READLINE_FIND_REQUIRED) +ENDIF(READLINE_FOUND) diff --git a/luajit/src/luajit.c b/luajit/src/luajit.c index 4cccf04..6a2752a 100644 --- a/luajit/src/luajit.c +++ b/luajit/src/luajit.c @@ -40,6 +40,273 @@ static lua_State *globalL = NULL; static const char *progname = LUA_PROGNAME; +/* ------------------------------------------------------------------------ */ + +#ifdef LUA_USE_READLINE + +#include +#include + +#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) +#define lua_saveline(L,idx) \ + if (lua_strlen(L,idx) > 0) /* non-empty line? */ \ + add_history(lua_tostring(L, idx)); /* add it to history */ +#define lua_freeline(L,b) ((void)L, free(b)) + +/* +** Lua 5.1.4 advanced readline support for the GNU readline and history +** libraries or compatible replacements. +** +** Author: Mike Pall. +** Maintainer: Sean Bolton (sean at smbolton dot com). +** +** Copyright (C) 2004-2006, 2011 Mike Pall. Same license as Lua. See lua.h. +** +** Advanced features: +** - Completion of keywords and global variable names. +** - Recursive and metatable-aware completion of variable names. +** - Context sensitive delimiter completion. +** - Save/restore of the history to/from a file (LUA_HISTORY env variable). +** - Setting a limit for the size of the history (LUA_HISTSIZE env variable). +** - Setting the app name to allow for $if lua ... $endif in ~/.inputrc. +** +** Start lua and try these (replace ~ with the TAB key): +** +** ~~ +** fu~foo() ret~fa~end +** io~~~s~~~o~~~w~"foo\n") +** +** The ~~ are just for demonstration purposes (io~s~o~w~ suffices, of course). +** +** If you are used to zsh/tcsh-style completion support, try adding +** 'TAB: menu-complete' and 'C-d: possible-completions' to your ~/.inputrc. +** +** The patch has been successfully tested with: +** +** GNU readline 2.2.1 (1998-07-17) +** GNU readline 4.0 (1999-02-18) [harmless compiler warning] +** GNU readline 4.3 (2002-07-16) +** GNU readline 5.0 (2004-07-27) +** GNU readline 5.1 (2005-12-07) +** GNU readline 5.2 (2006-10-11) +** GNU readline 6.0 (2009-02-20) +** GNU readline 6.2 (2011-02-13) +** MacOSX libedit 2.11 (2008-07-12) +** NETBSD libedit 2.6.5 (2002-03-25) +** NETBSD libedit 2.6.9 (2004-05-01) +** +** Change Log: +** 2004-2006 Mike Pall - original patch +** 2009/08/24 Sean Bolton - updated for GNU readline version 6 +** 2011/12/14 Sean Bolton - fixed segfault when using Mac OS X libedit 2.11 +*/ + +#include + +static char *lua_rl_hist; +static int lua_rl_histsize; + +static lua_State *lua_rl_L; /* User data is not passed to rl callbacks. */ + +/* Reserved keywords. */ +static const char *const lua_rl_keywords[] = { + "and", "break", "do", "else", "elseif", "end", "false", + "for", "function", "if", "in", "local", "nil", "not", "or", + "repeat", "return", "then", "true", "until", "while", NULL +}; + +static int valididentifier(const char *s) +{ + if (!(isalpha(*s) || *s == '_')) return 0; + for (s++; *s; s++) if (!(isalpha(*s) || isdigit(*s) || *s == '_')) return 0; + return 1; +} + +/* Dynamically resizable match list. */ +typedef struct { + char **list; + size_t idx, allocated, matchlen; +} dmlist; + +/* Add prefix + string + suffix to list and compute common prefix. */ +static int lua_rl_dmadd(dmlist *ml, const char *p, size_t pn, const char *s, + int suf) +{ + char *t = NULL; + + if (ml->idx+1 >= ml->allocated && + !(ml->list = realloc(ml->list, sizeof(char *)*(ml->allocated += 32)))) + return -1; + + if (s) { + size_t n = strlen(s); + if (!(t = (char *)malloc(sizeof(char)*(pn+n+(suf?2:1))))) return 1; + memcpy(t, p, pn); + memcpy(t+pn, s, n); + n += pn; + t[n] = suf; + if (suf) t[++n] = '\0'; + + if (ml->idx == 0) { + ml->matchlen = n; + } else { + size_t i; + for (i = 0; i < ml->matchlen && i < n && ml->list[1][i] == t[i]; i++) ; + ml->matchlen = i; /* Set matchlen to common prefix. */ + } + } + + ml->list[++ml->idx] = t; + return 0; +} + +/* Get __index field of metatable of object on top of stack. */ +static int lua_rl_getmetaindex(lua_State *L) +{ + if (!lua_getmetatable(L, -1)) { lua_pop(L, 1); return 0; } + lua_pushstring(L, "__index"); + lua_rawget(L, -2); + lua_replace(L, -2); + if (lua_isnil(L, -1) || lua_rawequal(L, -1, -2)) { lua_pop(L, 2); return 0; } + lua_replace(L, -2); + return 1; +} /* 1: obj -- val, 0: obj -- */ + +/* Get field from object on top of stack. Avoid calling metamethods. */ +static int lua_rl_getfield(lua_State *L, const char *s, size_t n) +{ + int i = 20; /* Avoid infinite metatable loops. */ + do { + if (lua_istable(L, -1)) { + lua_pushlstring(L, s, n); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) { lua_replace(L, -2); return 1; } + lua_pop(L, 1); + } + } while (--i > 0 && lua_rl_getmetaindex(L)); + lua_pop(L, 1); + return 0; +} /* 1: obj -- val, 0: obj -- */ + +/* Completion callback. */ +static char **lua_rl_complete(const char *text, int start, int end) +{ + lua_State *L = lua_rl_L; + dmlist ml; + const char *s; + size_t i, n, dot, loop; + int savetop; + + if (!(text[0] == '\0' || isalpha(text[0]) || text[0] == '_')) return NULL; + + ml.list = NULL; + ml.idx = ml.allocated = ml.matchlen = 0; + + savetop = lua_gettop(L); + lua_pushvalue(L, LUA_GLOBALSINDEX); + for (n = (size_t)(end-start), i = dot = 0; i < n; i++) + if (text[i] == '.' || text[i] == ':') { + if (!lua_rl_getfield(L, text+dot, i-dot)) + goto error; /* Invalid prefix. */ + dot = i+1; /* Points to first char after dot/colon. */ + } + + /* Add all matches against keywords if there is no dot/colon. */ + if (dot == 0) + for (i = 0; (s = lua_rl_keywords[i]) != NULL; i++) + if (!strncmp(s, text, n) && lua_rl_dmadd(&ml, NULL, 0, s, ' ')) + goto error; + + /* Add all valid matches from all tables/metatables. */ + loop = 0; /* Avoid infinite metatable loops. */ + do { + if (lua_istable(L, -1) && + (loop == 0 || !lua_rawequal(L, -1, LUA_GLOBALSINDEX))) + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) + if (lua_type(L, -2) == LUA_TSTRING) { + s = lua_tostring(L, -2); + /* Only match names starting with '_' if explicitly requested. */ + if (!strncmp(s, text+dot, n-dot) && valididentifier(s) && + (*s != '_' || text[dot] == '_')) { + int suf = ' '; /* Default suffix is a space. */ + switch (lua_type(L, -1)) { + case LUA_TTABLE: suf = '.'; break; /* No way to guess ':'. */ + case LUA_TFUNCTION: suf = '('; break; + case LUA_TUSERDATA: + if (lua_getmetatable(L, -1)) { lua_pop(L, 1); suf = ':'; } + break; + } + if (lua_rl_dmadd(&ml, text, dot, s, suf)) goto error; + } + } + } while (++loop < 20 && lua_rl_getmetaindex(L)); + + if (ml.idx == 0) { +error: + lua_settop(L, savetop); + return NULL; + } else { + /* list[0] holds the common prefix of all matches (may be ""). */ + /* If there is only one match, list[0] and list[1] will be the same. */ + if (!(ml.list[0] = (char *)malloc(sizeof(char)*(ml.matchlen+1)))) + goto error; + memcpy(ml.list[0], ml.list[1], ml.matchlen); + ml.list[0][ml.matchlen] = '\0'; + /* Add the NULL list terminator. */ + if (lua_rl_dmadd(&ml, NULL, 0, NULL, 0)) goto error; + } + + lua_settop(L, savetop); +#if RL_READLINE_VERSION >= 0x0600 + rl_completion_suppress_append = 1; +#endif + return ml.list; +} + +/* Initialize readline library. */ +static void lua_rl_init(lua_State *L) +{ + char *s; + + lua_rl_L = L; + + /* This allows for $if lua ... $endif in ~/.inputrc. */ + rl_readline_name = "lua"; + /* Break words at every non-identifier character except '.' and ':'. */ + rl_completer_word_break_characters = + "\t\r\n !\"#$%&'()*+,-/;<=>?@[\\]^`{|}~"; + rl_completer_quote_characters = "\"'"; +#if RL_READLINE_VERSION < 0x0600 + rl_completion_append_character = '\0'; +#endif + rl_attempted_completion_function = lua_rl_complete; + rl_initialize(); + + /* Start using history, optionally set history size and load history file. */ + using_history(); + if ((s = getenv("LUA_HISTSIZE")) && + (lua_rl_histsize = atoi(s))) stifle_history(lua_rl_histsize); + if ((lua_rl_hist = getenv("LUA_HISTORY"))) read_history(lua_rl_hist); +} + +/* Finalize readline library. */ +static void lua_rl_exit(lua_State *L) +{ + /* Optionally save history file. */ + if (lua_rl_hist) write_history(lua_rl_hist); +} +#else +#define lua_readline(L,b,p) \ + ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ + fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ +#define lua_saveline(L,idx) { (void)L; (void)idx; } +#define lua_freeline(L,b) { (void)L; (void)b; } +#define lua_rl_init(L) ((void)L) +#define lua_rl_exit(L) ((void)L) +#endif + +/* ------------------------------------------------------------------------ */ + #if !LJ_TARGET_CONSOLE static void lstop(lua_State *L, lua_Debug *ar) { @@ -190,15 +457,14 @@ static int dolibrary(lua_State *L, const char *name) return report(L, docall(L, 1, 1)); } -static void write_prompt(lua_State *L, int firstline) +static const char* get_prompt(lua_State *L, int firstline) { const char *p; lua_getfield(L, LUA_GLOBALSINDEX, firstline ? "_PROMPT" : "_PROMPT2"); p = lua_tostring(L, -1); if (p == NULL) p = firstline ? LUA_PROMPT : LUA_PROMPT2; - fputs(p, stdout); - fflush(stdout); lua_pop(L, 1); /* remove global */ + return p; } static int incomplete(lua_State *L, int status) @@ -218,15 +484,17 @@ static int incomplete(lua_State *L, int status) static int pushline(lua_State *L, int firstline) { char buf[LUA_MAXINPUT]; - write_prompt(L, firstline); - if (fgets(buf, LUA_MAXINPUT, stdin)) { - size_t len = strlen(buf); - if (len > 0 && buf[len-1] == '\n') - buf[len-1] = '\0'; - if (firstline && buf[0] == '=') - lua_pushfstring(L, "return %s", buf+1); + char *b = buf; + const char *prmt = get_prompt(L, firstline); + if (lua_readline(L, b, prmt)) { + size_t len = strlen(b); + if (len > 0 && b[len-1] == '\n') + b[len-1] = '\0'; + if (firstline && b[0] == '=') + lua_pushfstring(L, "return %s", b+1); else - lua_pushstring(L, buf); + lua_pushstring(L, b); + lua_freeline(L, b); return 1; } return 0; @@ -247,6 +515,7 @@ static int loadline(lua_State *L) lua_insert(L, -2); /* ...between the two lines */ lua_concat(L, 3); /* join them */ } + lua_saveline(L, 1); lua_remove(L, 1); /* remove line */ return status; } @@ -256,6 +525,7 @@ static void dotty(lua_State *L) int status; const char *oldprogname = progname; progname = NULL; + lua_rl_init(L); while ((status = loadline(L)) != -1) { if (status == 0) status = docall(L, 0, 0); report(L, status); @@ -271,6 +541,7 @@ static void dotty(lua_State *L) lua_settop(L, 0); /* clear stack */ fputs("\n", stdout); fflush(stdout); + lua_rl_exit(L); progname = oldprogname; }