# # Patch managed by http://www.holgerschurig.de/patcher.html # --- lua-5.0.2/etc/saconfig.c~advanced-readline +++ lua-5.0.2/etc/saconfig.c @@ -1,14 +1,14 @@ -/* sa-config.c -- configuration for stand-alone Lua interpreter +/* saconfig.c -- configuration for stand-alone Lua interpreter * * #define LUA_USERCONFIG to this file * * Here are the features that can be customized using #define: * -*** Line edit and history: +*** Line editing and history: * #define USE_READLINE to use the GNU readline library. * * To use another library for this, use the code below as a start. -* Make sure you #define lua_readline and lua_saveline accordingly. +* Make sure you #define lua_{read,save,init,exit}line accordingly. * If you do not #define lua_readline, you'll get a version based on fgets * that uses a static buffer of size MAXINPUT. * @@ -41,13 +41,20 @@ #ifdef USE_READLINE /* -* This section implements of lua_readline and lua_saveline for lua.c using -* the GNU readline and history libraries. It should also work with drop-in -* replacements such as editline and libedit (you may have to include -* different headers, though). +* This section implements lua_xxxxline for lua.c using the GNU readline +* and history libraries or compatible replacements. * +* It 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) +* NETBSD libedit 2.6.5 (2002-03-25) +* NETBSD libedit 2.6.9 (2004-05-01) */ +#define lua_initline myinitline +#define lua_exitline myexitline #define lua_readline myreadline #define lua_saveline mysaveline @@ -55,33 +62,226 @@ #include #include -static int myreadline (lua_State *L, const char *prompt) { - char *s=readline(prompt); - if (s==NULL) - return 0; - else { - lua_pushstring(L,s); - lua_pushliteral(L,"\n"); - lua_concat(L,2); - free(s); - return 1; - } +/* Environment variable names for the history file and the history size */ +#ifndef LUA_HISTORY_ENV +#define LUA_HISTORY_ENV "LUA_HISTORY" +#endif + +#ifndef LUA_HISTSIZE_ENV +#define LUA_HISTSIZE_ENV "LUA_HISTSIZE" +#endif + +static char *myhist; +static int myhistsize; + +static lua_State *myL; /* readline does not pass user data to callbacks */ + +/* Read a line from the terminal with line editing */ +static int myreadline(lua_State *L, const char *prompt) +{ + char *s; + if (!(s = readline(prompt))) return 0; + lua_pushstring(L, s); + lua_pushliteral(L, "\n"); + lua_concat(L, 2); + free(s); + return 1; } -static void mysaveline (lua_State *L, const char *s) { +/* Add a line to the history */ +static void mysaveline(lua_State *L, const char *s) +{ const char *p; - for (p=s; isspace(*p); p++) - ; - if (*p!=0) { - size_t n=strlen(s)-1; - if (s[n]!='\n') + for (p = s; isspace(*p); p++) ; + if (*p) { + size_t n = strlen(s)-1; + if (s[n] != '\n') { add_history(s); - else { - lua_pushlstring(L,s,n); - s=lua_tostring(L,-1); + } else { + lua_pushlstring(L, s, n); + s = lua_tostring(L, -1); add_history(s); - lua_remove(L,-1); + lua_pop(L, 1); + } + } +} + +/* Reserved lua keywords */ +static const char * const reskeywords[] = { + "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 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 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 safegetfield(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 && getmetaindex(L)); + lua_pop(L, 1); + return 0; +} /* 1: obj -- val, 0: obj -- */ + +/* Completion function */ +static char **mycomplete(const char *text, int start, int end) +{ + dmlist ml; + const char *s; + size_t i, n, dot; + 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(myL); + lua_pushvalue(myL, LUA_GLOBALSINDEX); + for (n = (size_t)(end-start), i = dot = 0; i < n; i++) + if (text[i] == '.' || text[i] == ':') { + if (!safegetfield(myL, 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 = reskeywords[i]) != NULL; i++) + if (!strncmp(s, text, n) && dmadd(&ml, NULL, 0, s, ' ')) goto error; + + /* Add all valid matches from all tables/metatables */ + i = 20; /* Avoid infinite metatable loops */ + do { + if (lua_istable(myL, -1)) + for (lua_pushnil(myL); lua_next(myL, -2); lua_pop(myL, 1)) + if (lua_type(myL, -2) == LUA_TSTRING) { + s = lua_tostring(myL, -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(myL, -1)) { + case LUA_TTABLE: suf = '.'; break; /* No way to guess ':' */ + case LUA_TFUNCTION: suf = '('; break; + case LUA_TUSERDATA: + if (lua_getmetatable(myL, -1)) { lua_pop(myL, 1); suf = ':'; } + break; + } + if (dmadd(&ml, text, dot, s, suf)) goto error; + } + } + } while (--i > 0 && getmetaindex(myL)); + + if (ml.idx > 1) { + /* list[0] holds the common prefix of all matches (may be "") */ + if (!(ml.list[0] = (char *)malloc(sizeof(char)*(ml.matchlen+1)))) { +error: + lua_settop(myL, savetop); + return NULL; } + memcpy(ml.list[0], ml.list[1], ml.matchlen); + ml.list[0][ml.matchlen] = '\0'; + /* Add the NULL list terminator */ + if (dmadd(&ml, NULL, 0, NULL, 0)) goto error; + } else if (ml.idx == 1) { + ml.list[0] = ml.list[1]; /* Only return common prefix */ + ml.list[1] = NULL; } + + lua_settop(myL, savetop); + return ml.list; +} + +/* Initialize library */ +static void myinitline(lua_State *L, char *pname) +{ + char *s; + + myL = L; + + /* This allows for $if lua ... $endif in ~/.inputrc */ + rl_readline_name = pname; + /* Break words at every non-identifier character except '.' and ':' */ + rl_completer_word_break_characters = + "\t\r\n !\"#$%&'()*+,-/;<=>?@[\\]^`{|}~"; + rl_completer_quote_characters = "\"'"; + rl_completion_append_character = '\0'; + rl_attempted_completion_function = mycomplete; + rl_initialize(); + + /* Start using history, optionally set history size and load history file */ + using_history(); + if ((s = getenv(LUA_HISTSIZE_ENV)) && + (myhistsize = atoi(s))) stifle_history(myhistsize); + if ((myhist = getenv(LUA_HISTORY_ENV))) read_history(myhist); +} + +/* Finalize library */ +static void myexitline(lua_State *L) +{ + /* Optionally save history file */ + if (myhist) write_history(myhist); } #endif --- lua-5.0.2/src/lua/lua.c~advanced-readline +++ lua-5.0.2/src/lua/lua.c @@ -265,6 +265,19 @@ /* +** these macros can be used to perform initialization and finalization +** for lua_saveline and lua_readline +*/ +#ifndef lua_initline +#define lua_initline(L,pname) /* empty */ +#endif + +#ifndef lua_exitline +#define lua_exitline(L) /* empty */ +#endif + + +/* ** this macro can be used by some `history' system to save lines ** read in manual input */ @@ -352,6 +365,7 @@ const char *oldprogname = progname; progname = NULL; do_path(); + lua_initline(L, PROGNAME); /* progname may contain a path, so use PROGNAME */ while ((status = load_string()) != -1) { if (status == 0) status = lcall(0, 0); report(status); @@ -365,6 +379,7 @@ } lua_settop(L, 0); /* clear stack */ fputs("\n", stdout); + lua_exitline(L); progname = oldprogname; }