From e70ea5d21ff1aca5b8c534da8f3a1ccadd330a2e Mon Sep 17 00:00:00 2001 From: Marcin Bukat Date: Sun, 28 Jun 2015 17:51:43 +0200 Subject: hwstub: Add completion and some pretty printing to the shell This uses slightly hacked luaprompt to provide all the goodis. See https://github.com/dpapavas/luaprompt for original. Change-Id: Iedddb79abae5809299322bc215722dd928c35cca --- utils/hwstub/tools/Makefile | 2 +- utils/hwstub/tools/hwstub_shell.cpp | 20 +- utils/hwstub/tools/lua/hwlib.lua | 6 +- utils/hwstub/tools/prompt.c | 1659 +++++++++++++++++++++++++++++++++++ utils/hwstub/tools/prompt.h | 55 ++ 5 files changed, 1724 insertions(+), 18 deletions(-) create mode 100644 utils/hwstub/tools/prompt.c create mode 100644 utils/hwstub/tools/prompt.h (limited to 'utils/hwstub/tools') diff --git a/utils/hwstub/tools/Makefile b/utils/hwstub/tools/Makefile index 6db0c709b1..f718300623 100644 --- a/utils/hwstub/tools/Makefile +++ b/utils/hwstub/tools/Makefile @@ -26,7 +26,7 @@ $(REGTOOLS_LIB_DIR)/libsocdesc.a: %.o: %.cpp $(CXX) $(CXXFLAGS) -c -o $@ $< -hwstub_shell: hwstub_shell.o $(LIBS) +hwstub_shell: hwstub_shell.o prompt.o $(LIBS) $(LD) -o $@ $^ $(LDFLAGS) hwstub_load: hwstub_load.o $(LIBS) diff --git a/utils/hwstub/tools/hwstub_shell.cpp b/utils/hwstub/tools/hwstub_shell.cpp index 8b7a8b9e80..f59ca8b82a 100644 --- a/utils/hwstub/tools/hwstub_shell.cpp +++ b/utils/hwstub/tools/hwstub_shell.cpp @@ -29,6 +29,9 @@ #include #include #include "soc_desc.hpp" +extern "C" { +#include "prompt.h" +} #if LUA_VERSION_NUM < 502 #warning You need at least lua 5.2 @@ -941,21 +944,8 @@ int main(int argc, char **argv) printf("error: %s\n", lua_tostring(g_lua, -1)); } - // use readline to provide some history and completion - rl_bind_key('\t', rl_complete); - while(!g_exit) - { - char *input = readline("> "); - if(!input) - break; - add_history(input); - // evaluate string - if(luaL_dostring(g_lua, input)) - printf("error: %s\n", lua_tostring(g_lua, -1)); - // pop everything to start from a clean stack - lua_pop(g_lua, lua_gettop(g_lua)); - free(input); - } + // start interactive shell + luap_enter(g_lua, &g_exit); Lerr: // display log if handled diff --git a/utils/hwstub/tools/lua/hwlib.lua b/utils/hwstub/tools/lua/hwlib.lua index 5bbd1e2668..02ab9718d4 100644 --- a/utils/hwstub/tools/lua/hwlib.lua +++ b/utils/hwstub/tools/lua/hwlib.lua @@ -22,6 +22,8 @@ function HWLIB.load_blob(filename, address) io.close(f) end -function HWLIB.printf(s,...) - return io.write(s:format(...)) +function HWLIB.printf(...) + local function wrapper(...) io.write(string.format(...)) end + local status, result = pcall(wrapper, ...) + if not status then error(result, 2) end end diff --git a/utils/hwstub/tools/prompt.c b/utils/hwstub/tools/prompt.c new file mode 100644 index 0000000000..4674158df6 --- /dev/null +++ b/utils/hwstub/tools/prompt.c @@ -0,0 +1,1659 @@ +/* Copyright (C) 2012-2015 Papavasileiou Dimitris + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include "prompt.h" + +#ifdef HAVE_IOCTL +#include +#endif + +#include + +#include +#include + + +#if LUA_VERSION_NUM == 501 +#define lua_pushglobaltable(L) lua_pushvalue (L, LUA_GLOBALSINDEX) +#define LUA_OK 0 +#define lua_rawlen lua_objlen +#endif + +#ifdef HAVE_LIBREADLINE +#include +#else + +/* This is a simple readline-like function in case readline is not + * available. */ + +#define MAXINPUT 1024 + +static char *readline(char *prompt) +{ + char *line = NULL; + int k; + + line = malloc (MAXINPUT); + + fputs(prompt, stdout); + fflush(stdout); + + if (!fgets(line, MAXINPUT, stdin)) { + return NULL; + } + + k = strlen (line); + + if (line[k - 1] == '\n') { + line[k - 1] = '\0'; + } + + return line; +} + +#endif /* HAVE_LIBREADLINE */ + +#ifdef HAVE_READLINE_HISTORY +#include +#endif /* HAVE_READLINE_HISTORY */ + +#if LUA_VERSION_NUM == 501 +#define EOF_MARKER "''" +#else +#define EOF_MARKER "" +#endif + +#define print_output(...) fprintf (stdout, __VA_ARGS__), fflush(stdout) +#define print_error(...) fprintf (stderr, __VA_ARGS__), fflush(stderr) +#define absolute(L, i) (i < 0 ? lua_gettop (L) + i + 1 : i) + +#define COLOR(i) (colorize ? colors[i] : "") + +static lua_State *M; +static int initialized = 0; +static char *logfile, *chunkname, *prompts[2][2], *buffer = NULL; + +#ifdef SAVE_RESULTS +static int results = LUA_REFNIL, results_n = 0; +#endif + +static int colorize = 1; +static const char *colors[] = {"\033[0m", + "\033[0;31m", + "\033[1;31m", + "\033[0;32m", + "\033[1;32m", + "\033[0;33m", + "\033[1;33m", + "\033[1m", + "\033[22m"}; + +#ifdef HAVE_LIBREADLINE + +static void display_matches (char **matches, int num_matches, int max_length) +{ + print_output ("%s", COLOR(7)); + rl_display_match_list (matches, num_matches, max_length); + print_output ("%s", COLOR(0)); + rl_on_new_line (); +} + +#ifdef COMPLETE_KEYWORDS +static char *keyword_completions (const char *text, int state) +{ + static const char **c, *keywords[] = { +#if LUA_VERSION_NUM == 502 + "goto", +#endif + "and", "break", "do", "else", "elseif", "end", "false", "for", + "function", "if", "in", "local", "nil", "not", "or", + "repeat", "return", "then", "true", "until", "while", NULL + }; + + int s, t; + + if (state == 0) { + c = keywords - 1; + } + + /* Loop through the list of keywords and return the ones that + * match. */ + + for (c += 1 ; *c ; c += 1) { + s = strlen (*c); + t = strlen(text); + + if (s >= t && !strncmp (*c, text, t)) { + return strdup (*c); + } + } + + return NULL; +} +#endif + +#ifdef COMPLETE_TABLE_KEYS + +static int look_up_metatable; + +static char *table_key_completions (const char *text, int state) +{ + static const char *c, *token; + static char oper; + static int h; + + if (state == 0) { + h = lua_gettop(M); + + /* Scan to the beginning of the to-be-completed token. */ + + for (c = text + strlen (text) - 1; + c >= text && *c != '.' && *c != ':' && *c != '['; + c -= 1); + + if (c > text) { + oper = *c; + token = c + 1; + + /* Get the iterable value, the keys of which we wish to + * complete. */ + + lua_pushliteral (M, "return "); + lua_pushlstring (M, text, token - text - 1); + lua_concat (M, 2); + + if (luaL_loadstring (M, lua_tostring (M, -1)) || + lua_pcall (M, 0, 1, 0) || + (lua_type (M, -1) != LUA_TUSERDATA && + lua_type (M, -1) != LUA_TTABLE)) { + + lua_settop(M, h); + return NULL; + } + } else { + oper = 0; + token = text; + + lua_pushglobaltable(M); + } + + if (look_up_metatable) { + /* Replace the to-be-iterated value with it's metatable + * and set up a call to next. */ + + if (!luaL_getmetafield(M, -1, "__index") || + (lua_type (M, -1) != LUA_TUSERDATA && + lua_type (M, -1) != LUA_TTABLE)) { + lua_settop(M, h); + return NULL; + } + + lua_getglobal(M, "next"); + lua_replace(M, -3); + lua_pushnil(M); + } else { + /* Call the standard pairs function. */ + + lua_getglobal (M, "pairs"); + lua_insert (M, -2); + + if(lua_type (M, -2) != LUA_TFUNCTION || + lua_pcall (M, 1, 3, 0)) { + + lua_settop(M, h); + return NULL; + } + } + } + + /* Iterate the table/userdata and generate matches. */ + + while (lua_pushvalue(M, -3), lua_insert (M, -3), + lua_pushvalue(M, -2), lua_insert (M, -4), + lua_pcall (M, 2, 2, 0) == 0) { + char *candidate; + size_t l, m; + int suppress, type, keytype; + + if (lua_isnil(M, -2)) { + lua_settop(M, h); + return NULL; + } + + /* Make some notes about the value we're completing. We'll + * make use of them later on. */ + + type = lua_type (M, -1); + suppress = (type == LUA_TTABLE || type == LUA_TUSERDATA || + type == LUA_TFUNCTION); + + keytype = LUA_TNIL; + if (type == LUA_TTABLE) { + lua_pushnil(M); + if (lua_next(M, -2)) { + keytype = lua_type (M, -2); + lua_pop (M, 2); + } else { + /* There are no keys in the table so we won't want to + * index it. Add a space. */ + + suppress = 0; + } + } + + /* Pop the value, keep the key. */ + + lua_pop (M, 1); + + /* We're mainly interested in strings at this point but if + * we're completing for the table[key] syntax we consider + * numeric keys too. */ + + if (lua_type (M, -1) == LUA_TSTRING || + (oper == '[' && lua_type (M, -1) == LUA_TNUMBER)) { + if (oper == '[') { + if (lua_type (M, -1) == LUA_TNUMBER) { + lua_Number n; + int i; + + n = lua_tonumber (M, -1); + i = lua_tointeger (M, -1); + + /* If this isn't an integer key, we may as well + * forget about it. */ + + if ((lua_Number)i == n) { + l = asprintf (&candidate, "%d]", i); + } else { + continue; + } + } else { + char q; + + q = token[0]; + if (q != '"' && q != '\'') { + q = '"'; + } + + l = asprintf (&candidate, "%c%s%c]", + q, lua_tostring (M, -1), q); + } + } else { + candidate = strdup((char *)lua_tolstring (M, -1, &l)); + } + + m = strlen(token); + + if (l >= m && !strncmp (token, candidate, m) && + (oper != ':' || type == LUA_TFUNCTION) +#ifdef HIDDEN_KEY_PREFIX + && strncmp(candidate, HIDDEN_KEY_PREFIX, + sizeof(HIDDEN_KEY_PREFIX) - 1) +#endif + ) { + char *match; + + /* If the candidate has been fully typed (or + * previously completed) consider adding certain + * helpful suffixes. */ +#ifndef ALWAYS_APPEND_SUFFIXES + if (l == m) { +#endif + if (type == LUA_TFUNCTION) { + rl_completion_append_character = '('; suppress = 0; + } else if (type == LUA_TTABLE) { + if (keytype == LUA_TSTRING) { + rl_completion_append_character = '.'; suppress = 0; + } else if (keytype != LUA_TNIL) { + rl_completion_append_character = '['; suppress = 0; + } + } +#ifndef ALWAYS_APPEND_SUFFIXES + }; +#endif + + if (token > text) { + /* Were not completing a global variable. Put the + * completed string together out of the table and + * the key. */ + + match = (char *)malloc ((token - text) + l + 1); + strncpy (match, text, token - text); + strcpy (match + (token - text), candidate); + + free(candidate); + } else { + /* Return the whole candidate as is, to be freed + * by Readline. */ + + match = candidate; + } + + /* Suppress the newline when completing a table + * or other potentially complex value. */ + + if (suppress) { + rl_completion_suppress_append = 1; + } + + return match; + } else { + free(candidate); + } + } + } + + lua_settop(M, h); + return NULL; +} +#endif + +#ifdef COMPLETE_MODULES +static char *module_completions (const char *text, int state) +{ + char *match = NULL; + static int h; + + if (state == 0) { + glob_t vector; + const char *b, *d, *q, *s, *t, *strings[3]; + int i, n = 0, ondot, hasdot, quoted; + + hasdot = strchr(text, '.') != NULL; + ondot = text[0] != '\0' && text[strlen(text) - 1] == '.'; + quoted = text[0] == '\'' || text[0] == '"'; + +#ifdef NO_MODULE_LOAD + if(!quoted) { + return NULL; + } +#endif + + lua_newtable(M); + h = lua_gettop(M); + + /* Try to load the input as a module. */ + + lua_getglobal(M, "require"); + + if (!lua_isfunction (M, -1)) { + lua_settop(M, h - 1); + return NULL; + } + + lua_pushliteral(M, "package"); + + if(lua_pcall(M, 1, 1, 0) != LUA_OK) { + lua_settop(M, h - 1); + return NULL; + } + + if (!ondot && !quoted && text[0] != '\0') { + lua_getfield(M, -1, "loaded"); + lua_pushstring(M, text); + lua_gettable(M, -2); + + /* If it's not an already loaded module, check whether the + * input is an available module by searching for it and/or + * trying to load it. */ + + if (!lua_toboolean(M, -1)) { + int load = 1; + + lua_pop(M, 2); + +#ifdef CONFIRM_MODULE_LOAD + /* Look for the module as require would and ask the + * user whether it should be loaded or not. */ + +#if LUA_VERSION_NUM == 501 + lua_getfield(M, -1, "loaders"); +#else + lua_getfield(M, -1, "searchers"); +#endif + lua_pushnil(M); + + while((load = lua_next(M, -2))) { + lua_pushstring(M, text); + lua_call(M, 1, 1); + + if (lua_isfunction(M, -1)) { + char c; + + print_output ("\nLoad module '%s' (y or n)", text); + + while ((c = tolower(rl_read_key())) != 'y' && c != 'n'); + + if (c == 'y') { + lua_pop(M, 3); + break; + } else { + print_output ("\n"); + rl_on_new_line (); + + /* If it was found but not loaded, return + * the module name as a match to avoid + * asking the user againg if the tab key + * is pressed repeatedly. */ + + lua_settop(M, h); + return strdup(text); + } + } + + lua_pop(M, 1); + } +#endif + + /* Load the model if needed. */ + + if (load) { + lua_pushfstring (M, "%s=require(\"%s\")", text, text); + + if (luaL_loadstring (M, lua_tostring (M, -1)) == LUA_OK && + lua_pcall (M, 0, 0, 0) == LUA_OK) { +#ifdef CONFIRM_MODULE_LOAD + print_output (" ...loaded\n"); +#else + print_output ("\nLoaded module '%s'.\n", text); +#endif + + rl_on_new_line (); + + lua_settop(M, h - 1); + return NULL; + } + } + } else { + lua_settop(M, h - 1); + return NULL; + } + + /* Clean up but leave the package.table on the stack. */ + + lua_settop(M, h + 1); + } + + /* Look for matches in package.preload. */ + + lua_getfield(M, -1, "preload"); + + lua_pushnil(M); + while(lua_next(M, -2)) { + lua_pop(M, 1); + + if (lua_type(M, -1) == LUA_TSTRING && + !strncmp(text + quoted, lua_tostring(M, -1), + strlen(text + quoted))) { + + lua_pushstring(M, text); + lua_rawseti (M, h, (n += 1)); + } + } + + lua_pop(M, 1); + + /* Get the configuration (directory, path separators, module + * name wildcard). */ + + lua_getfield(M, -1, "config"); + for (s = (char *)lua_tostring(M, -1), i = 0; + i < 3; + s = t + 1, i += 1) { + + t = strchr(s, '\n'); + lua_pushlstring(M, s, t - s); + strings[i] = lua_tostring(M, -1); + } + + lua_remove(M, -4); + + /* Get the path and cpath */ + + lua_getfield(M, -4, "path"); + lua_pushstring(M, strings[1]); + lua_getfield(M, -6, "cpath"); + lua_pushstring(M, strings[1]); + lua_concat(M, 4); + + /* Synthesize the pattern. */ + + if (hasdot) { + luaL_gsub(M, text + quoted, ".", strings[0]); + } else { + lua_pushstring(M, text + quoted); + } + + lua_pushliteral(M, "*"); + lua_concat(M, 2); + + for (b = d = lua_tostring(M, -2) ; d ; b = d + 1) { + size_t i; + + d = strstr(b, strings[1]); + q = strstr(b, strings[2]); + + if (!q || q > d) { + continue; + } + + lua_pushlstring(M, b, d - b); + luaL_gsub(M, lua_tostring(M, -1), strings[2], + lua_tostring(M, -2)); + + glob(lua_tostring(M, -1), 0, NULL, &vector); + + lua_pop(M, 2); + + for (i = 0 ; i < vector.gl_pathc ; i += 1) { + char *p = vector.gl_pathv[i]; + + if (quoted) { + lua_pushlstring(M, text, 1); + } + + lua_pushlstring(M, p + (q - b), strlen(p) - (d - b) + 1); + + if (hasdot) { + luaL_gsub(M, lua_tostring(M, -1), strings[0], "."); + lua_replace(M, -2); + } + + { + const char *s; + size_t l; + + s = lua_tolstring(M, -1, &l); + + /* Suppress submodules named init. */ + + if (l < sizeof("init") - 1 || + strcmp(s + l - sizeof("init") + 1, "init")) { + + if (quoted) { + lua_pushlstring(M, text, 1); + + lua_concat(M, 3); + } + + lua_rawseti(M, h, (n += 1)); + } else { + lua_pop(M, 1 + quoted); + } + } + } + + globfree(&vector); + } + + lua_pop(M, 6); + } + + /* Return the next match from the table of matches. */ + + lua_pushnil(M); + if (lua_next(M, -2)) { + match = strdup(lua_tostring(M, -1)); + + rl_completion_suppress_append = !(match[0] == '"' || match[0] == '\''); + + /* Pop the match. */ + + lua_pushnil(M); + lua_rawseti(M, -4, lua_tointeger(M, -3)); + + /* Pop key/value. */ + + lua_pop(M, 2); + } else { + /* Pop the empty table. */ + + lua_pop(M, 1); + } + + return match; +} +#endif + +static char *generator (const char *text, int state) +{ + static int which; + char *match = NULL; + + if (state == 0) { + which = 0; + } + + /* Try to complete a keyword. */ + + if (which == 0) { +#ifdef COMPLETE_KEYWORDS + if ((match = keyword_completions (text, state))) { + return match; + } +#endif + which += 1; + state = 0; + } + + /* Try to complete a module name. */ + + if (which == 1) { +#ifdef COMPLETE_MODULES + if ((match = module_completions (text, state))) { + return match; + } +#endif + which += 1; + state = 0; + } + + /* Try to complete a table access. */ + + if (which == 2) { +#ifdef COMPLETE_TABLE_KEYS + look_up_metatable = 0; + if ((match = table_key_completions (text, state))) { + return match; + } +#endif + which += 1; + state = 0; + } + + /* Try to complete a metatable access. */ + + if (which == 3) { +#ifdef COMPLETE_METATABLE_KEYS + look_up_metatable = 1; + if ((match = table_key_completions (text, state))) { + return match; + } +#endif + which += 1; + state = 0; + } + +#ifdef COMPLETE_FILE_NAMES + /* Try to complete a file name. */ + + if (which == 4) { + if (text[0] == '\'' || text[0] == '"') { + match = rl_filename_completion_function (text + 1, state); + + if (match) { + struct stat s; + int n; + + n = strlen (match); + stat(match, &s); + + /* If a match was produced, add the quote + * characters. */ + + match = (char *)realloc (match, n + 3); + memmove (match + 1, match, n); + match[0] = text[0]; + + /* If the file's a directory, add a trailing backslash + * and suppress the space, otherwise add the closing + * quote. */ + + if (S_ISDIR(s.st_mode)) { + match[n + 1] = '/'; + + rl_completion_suppress_append = 1; + } else { + match[n + 1] = text[0]; + } + + match[n + 2] = '\0'; + } + } + } +#endif + + return match; +} +#endif + +static void finish () +{ +#ifdef HAVE_READLINE_HISTORY + /* Save the command history on exit. */ + + if (logfile) { + write_history (logfile); + } +#endif +} + +static int traceback(lua_State *L) +{ + lua_Debug ar; + int i; + + if (lua_isnoneornil (L, 1) || + (!lua_isstring (L, 1) && + !luaL_callmeta(L, 1, "__tostring"))) { + lua_pushliteral(L, "(no error message)"); + } + + if (lua_gettop (L) > 1) { + lua_replace (L, 1); + lua_settop (L, 1); + } + + /* Print the Lua stack. */ + + lua_pushstring(L, "\n\nStack trace:\n"); + + for (i = 0 ; lua_getstack (L, i, &ar) ; i += 1) { +#if LUA_VERSION_NUM == 501 + lua_getinfo(M, "Snl", &ar); +#else + lua_getinfo(M, "Snlt", &ar); + + if (ar.istailcall) { + lua_pushfstring(L, "\t... tail calls\n"); + } +#endif + + if (!strcmp (ar.what, "C")) { + lua_pushfstring(L, "\t#%d %s[C]:%s in function ", + i, COLOR(7), COLOR(8)); + + if (ar.name) { + lua_pushfstring(L, "'%s%s%s'\n", + COLOR(7), ar.name, COLOR(8)); + } else { + lua_pushfstring(L, "%s?%s\n", COLOR(7), COLOR(8)); + } + } else if (!strcmp (ar.what, "main")) { + lua_pushfstring(L, "\t#%d %s%s:%d:%s in the main chunk\n", + i, COLOR(7), ar.short_src, ar.currentline, + COLOR(8)); + } else if (!strcmp (ar.what, "Lua")) { + lua_pushfstring(L, "\t#%d %s%s:%d:%s in function ", + i, COLOR(7), ar.short_src, ar.currentline, + COLOR(8)); + + if (ar.name) { + lua_pushfstring(L, "'%s%s%s'\n", + COLOR(7), ar.name, COLOR(8)); + } else { + lua_pushfstring(L, "%s?%s\n", COLOR(7), COLOR(8)); + } + } + } + + if (i == 0) { + lua_pushstring (L, "No activation records.\n"); + } + + lua_concat (L, lua_gettop(L)); + + return 1; +} + +static int execute () +{ + int i, h_0, h, status; + +#ifdef SAVE_RESULTS + /* Get the results table, and stash it behind the to-be-executed + * chunk. */ + + lua_rawgeti(M, LUA_REGISTRYINDEX, results); + lua_insert(M, -2); +#endif + + h_0 = lua_gettop(M); + status = luap_call (M, 0); + h = lua_gettop (M) - h_0 + 1; + + for (i = h ; i > 0 ; i -= 1) { + const char *result; + + result = luap_describe (M, -i); + +#ifdef SAVE_RESULTS + lua_pushvalue (M, -i); + lua_rawseti(M, h_0 - 1, (results_n += 1)); + + print_output ("%s%s[%d]%s = %s%s\n", + COLOR(4), RESULTS_TABLE_NAME, results_n, + COLOR(3), result, COLOR(0)); +#else + if (h == 1) { + print_output ("%s%s%s\n", COLOR(3), result, COLOR(0)); + } else { + print_output ("%s%d%s: %s%s\n", COLOR(4), h - i + 1, + COLOR(3), result, COLOR(0)); + } +#endif + } + + /* Clean up. We need to remove the results table as well if we + * track results. */ + +#ifdef SAVE_RESULTS + lua_settop (M, h_0 - 2); +#else + lua_settop (M, h_0 - 1); +#endif + + return status; +} + +/* This is the pretty-printing related stuff. */ + +static char *dump; +static int length, offset, indent, column, linewidth, ancestors; + +#define dump_literal(s) (check_fit(sizeof(s) - 1), \ + strcpy (dump + offset, s), \ + offset += sizeof(s) - 1, \ + column += width(s)) + +#define dump_character(c) (check_fit(1), \ + dump[offset] = c, \ + offset += 1, \ + column += 1) + +static int width (const char *s) +{ + const char *c; + int n, discard = 0; + + /* Calculate the printed width of the chunk s ignoring escape + * sequences. */ + + for (c = s, n = 0 ; *c ; c += 1) { + if (!discard && *c == '\033') { + discard = 1; + } + + if (!discard) { + n+= 1; + } + + if (discard && *c == 'm') { + discard = 0; + } + } + + return n; +} + +static void check_fit (int size) +{ + /* Check if a chunk fits in the buffer and expand as necessary. */ + + if (offset + size + 1 > length) { + length = offset + size + 1; + dump = (char *)realloc (dump, length * sizeof (char)); + } +} + +static int is_identifier (const char *s, int n) +{ + int i; + + /* Check whether a string can be used as a key without quotes and + * braces. */ + + for (i = 0 ; i < n ; i += 1) { + if (!isalpha(s[i]) && + (i == 0 || !isalnum(s[i])) && + s[i] != '_') { + return 0; + } + } + + return 1; +} + +static void break_line () +{ + int i; + + check_fit (indent + 1); + + /* Add a line break. */ + + dump[offset] = '\n'; + + /* And indent to the current level. */ + + for (i = 1 ; i <= indent ; i += 1) { + dump[offset + i] = ' '; + } + + offset += indent + 1; + column = indent; +} + +static void dump_string (const char *s, int n) +{ + int l; + + /* Break the line if the current chunk doesn't fit but it would + * fit if we started on a fresh line at the current indent. */ + + l = width(s); + + if (column + l > linewidth && indent + l <= linewidth) { + break_line(); + } + + check_fit (n); + + /* Copy the string to the buffer. */ + + memcpy (dump + offset, s, n); + dump[offset + n] = '\0'; + + offset += n; + column += l; +} + +static void describe (lua_State *L, int index) +{ + char *s; + size_t n; + int type; + + index = absolute (L, index); + type = lua_type (L, index); + + if (luaL_getmetafield (L, index, "__tostring")) { + lua_pushvalue (L, index); + lua_pcall (L, 1, 1, 0); + s = (char *)lua_tolstring (L, -1, &n); + lua_pop (L, 1); + + dump_string (s, n); + } else if (type == LUA_TNUMBER) { + /* Copy the value to avoid mutating it. */ + + lua_pushvalue (L, index); + s = (char *)lua_tolstring (L, -1, &n); + lua_pop (L, 1); + + dump_string (s, n); + } else if (type == LUA_TSTRING) { + int i, started, score, level, uselevel = 0; + + s = (char *)lua_tolstring (L, index, &n); + + /* Scan the string to decide how to print it. */ + + for (i = 0, score = n, started = 0 ; i < (int)n ; i += 1) { + if (s[i] == '\n' || s[i] == '\t' || + s[i] == '\v' || s[i] == '\r') { + /* These characters show up better in a long sting so + * bias towards that. */ + + score += linewidth / 2; + } else if (s[i] == '\a' || s[i] == '\b' || + s[i] == '\f' || !isprint(s[i])) { + /* These however go better with an escaped short + * string (unless you like the bell or weird + * characters). */ + + score -= linewidth / 4; + } + + /* Check what long string delimeter level to use so that + * the string won't be closed prematurely. */ + + if (!started) { + if (s[i] == ']') { + started = 1; + level = 0; + } + } else { + if (s[i] == '=') { + level += 1; + } else if (s[i] == ']') { + if (level >= uselevel) { + uselevel = level + 1; + } + } else { + started = 0; + } + } + } + + if (score > linewidth) { + /* Dump the string as a long string. */ + + dump_character ('['); + for (i = 0 ; i < uselevel ; i += 1) { + dump_character ('='); + } + dump_literal ("[\n"); + + dump_string (s, n); + + dump_character (']'); + for (i = 0 ; i < uselevel ; i += 1) { + dump_character ('='); + } + dump_literal ("]"); + } else { + /* Escape the string as needed and print it as a normal + * string. */ + + dump_literal ("\""); + + for (i = 0 ; i < (int)n ; i += 1) { + if (s[i] == '"' || s[i] == '\\') { + dump_literal ("\\"); + dump_character (s[i]); + } else if (s[i] == '\a') { + dump_literal ("\\a"); + } else if (s[i] == '\b') { + dump_literal ("\\b"); + } else if (s[i] == '\f') { + dump_literal ("\\f"); + } else if (s[i] == '\n') { + dump_literal ("\\n"); + } else if (s[i] == '\r') { + dump_literal ("\\r"); + } else if (s[i] == '\t') { + dump_literal ("\\t"); + } else if (s[i] == '\v') { + dump_literal ("\\v"); + } else if (isprint(s[i])) { + dump_character (s[i]); + } else { + char t[5]; + size_t n; + + n = sprintf (t, "\\%03u", ((unsigned char *)s)[i]); + dump_string (t, n); + } + } + + dump_literal ("\""); + } + } else if (type == LUA_TNIL) { + n = asprintf (&s, "%snil%s", COLOR(7), COLOR(8)); + dump_string (s, n); + free(s); + } else if (type == LUA_TBOOLEAN) { + n = asprintf (&s, "%s%s%s", + COLOR(7), + lua_toboolean (L, index) ? "true" : "false", + COLOR(8)); + dump_string (s, n); + free(s); + } else if (type == LUA_TFUNCTION) { + n = asprintf (&s, "<%sfunction:%s %p>", + COLOR(7), COLOR(8), lua_topointer (L, index)); + dump_string (s, n); + free(s); + } else if (type == LUA_TUSERDATA) { + n = asprintf (&s, "<%suserdata:%s %p>", + COLOR(7), COLOR(8), lua_topointer (L, index)); + + dump_string (s, n); + free(s); + } else if (type == LUA_TTHREAD) { + n = asprintf (&s, "<%sthread:%s %p>", + COLOR(7), COLOR(8), lua_topointer (L, index)); + dump_string (s, n); + free(s); + } else if (type == LUA_TTABLE) { + int i, l, n, oldindent, multiline, nobreak; + + /* Check if table is too deeply nested. */ + + if (indent > 8 * linewidth / 10) { + char *s; + size_t n; + + n = asprintf (&s, "{ %s...%s }", COLOR(7), COLOR(8)); + dump_string (s, n); + free(s); + + return; + } + + /* Check if the table introduces a cycle by checking whether + * it is a back-edge (that is equal to an ancestor table. */ + + lua_rawgeti (L, LUA_REGISTRYINDEX, ancestors); + n = lua_rawlen(L, -1); + + for (i = 0 ; i < n ; i += 1) { + lua_rawgeti (L, -1, n - i); +#if LUA_VERSION_NUM == 501 + if(lua_equal (L, -1, -3)) { +#else + if(lua_compare (L, -1, -3, LUA_OPEQ)) { +#endif + char *s; + size_t n; + + n = asprintf (&s, "{ %s[%d]...%s }", + COLOR(7), -(i + 1), COLOR(8)); + dump_string (s, n); + free(s); + lua_pop (L, 2); + + return; + } + + lua_pop (L, 1); + } + + /* Add the table to the ancestor list and pop the ancestor + * list table. */ + + lua_pushvalue (L, index); + lua_rawseti (L, -2, n + 1); + lua_pop (L, 1); + + /* Open the table and update the indentation level to the + * current column. */ + + dump_literal ("{ "); + oldindent = indent; + indent = column; + multiline = 0; + nobreak = 0; + + l = lua_rawlen (L, index); + + /* Traverse the array part first. */ + + for (i = 0 ; i < l ; i += 1) { + lua_pushinteger (L, i + 1); + lua_gettable (L, index); + + /* Start a fresh line when dumping tables to make sure + * there's plenty of room. */ + + if (lua_istable (L, -1)) { + if (!nobreak) { + break_line(); + } + + multiline = 1; + } + + nobreak = 0; + + /* Dump the value and separating comma. */ + + describe (L, -1); + dump_literal (", "); + + if (lua_istable (L, -1) && i != l - 1) { + break_line(); + nobreak = 1; + } + + lua_pop (L, 1); + } + + /* Now for the hash part. */ + + lua_pushnil (L); + while (lua_next (L, index) != 0) { + if (lua_type (L, -2) != LUA_TNUMBER || + lua_tonumber (L, -2) != lua_tointeger (L, -2) || + lua_tointeger (L, -2) < 1 || + lua_tointeger (L, -2) > l) { + + /* Keep each key-value pair on a separate line. */ + + break_line (); + multiline = 1; + + /* Dump the key and value. */ + + if (lua_type (L, -2) == LUA_TSTRING) { + char *s; + size_t n; + + s = (char *)lua_tolstring (L, -2, &n); + + if(is_identifier (s, n)) { + dump_string (COLOR(7), strlen(COLOR(7))); + dump_string (s, n); + dump_string (COLOR(8), strlen(COLOR(8))); + } else { + dump_literal ("["); + describe (L, -2); + dump_literal ("]"); + } + } else { + dump_literal ("["); + describe (L, -2); + dump_literal ("]"); + } + + dump_literal (" = "); + describe (L, -1); + dump_literal (","); + } + + lua_pop (L, 1); + } + + /* Remove the table from the ancestor list. */ + + lua_rawgeti (L, LUA_REGISTRYINDEX, ancestors); + lua_pushnil (L); + lua_rawseti (L, -2, n + 1); + lua_pop (L, 1); + + /* Pop the indentation level. */ + + indent = oldindent; + + if (multiline) { + break_line(); + dump_literal ("}"); + } else { + dump_literal (" }"); + } + } +} + +char *luap_describe (lua_State *L, int index) +{ + int oldcolorize; + +#ifdef HAVE_IOCTL + struct winsize w; + + /* Initialize the state. */ + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) < 0) { + linewidth = 80; + } else { + linewidth = w.ws_col; + } +#else + linewidth = 80; +#endif + + index = absolute (L, index); + offset = 0; + indent = 0; + column = 0; + + /* Suppress colorization, to avoid escape sequences in the + * returned strings. */ + + oldcolorize = colorize; + colorize = 0; + + /* Create a table to hold the ancestors for checking for cycles + * when printing table hierarchies. */ + + lua_newtable (L); + ancestors = luaL_ref (L, LUA_REGISTRYINDEX); + + describe (L, index); + + luaL_unref (L, LUA_REGISTRYINDEX, ancestors); + colorize = oldcolorize; + + return dump; +} + +/* These are custom commands. */ + +#ifdef HAVE_LIBREADLINE +static int describe_stack (int count, int key) +{ + int i, h; + + print_output ("%s", COLOR(7)); + + h = lua_gettop (M); + + if (count < 0) { + i = h + count + 1; + + if (i > 0 && i <= h) { + print_output ("\nValue at stack index %d(%d):\n%s%s", + i, -h + i - 1, COLOR(3), luap_describe (M, i)); + } else { + print_error ("Invalid stack index.\n"); + } + } else { + if (h > 0) { + print_output ("\nThe stack contains %d values.\n", h); + for (i = 1 ; i <= h ; i += 1) { + print_output ("\n%d(%d):\t%s", i, -h + i - 1, lua_typename(M, lua_type(M, i))); + } + } else { + print_output ("\nThe stack is empty."); + } + } + + print_output ("%s\n", COLOR(0)); + + rl_on_new_line (); + + return 0; +} +#endif + +int luap_call (lua_State *L, int n) { + int h, status; + + /* We can wind up here before reaching luap_enter, so this is + * needed. */ + + M = L; + + /* Push the error handler onto the stack. */ + + h = lua_gettop(L) - n; + lua_pushcfunction (L, traceback); + lua_insert (L, h); + + /* Try to execute the supplied chunk and keep note of any return + * values. */ + + status = lua_pcall(L, n, LUA_MULTRET, h); + + /* Print any errors. */ + + if (status != LUA_OK) { + print_error ("%s%s%s\n", COLOR(1), lua_tostring (L, -1), COLOR(0)); + lua_pop (L, 1); + } + + /* Remove the error handler. */ + + lua_remove (L, h); + + return status; +} + +void luap_setprompts(lua_State *L, const char *single, const char *multi) +{ + /* Plain, uncolored prompts. */ + + prompts[0][0] = (char *)realloc (prompts[0][0], strlen (single) + 1); + prompts[0][1] = (char *)realloc (prompts[0][1], strlen (multi) + 1); + strcpy(prompts[0][0], single); + strcpy(prompts[0][1], multi); + + /* Colored prompts. */ + + prompts[1][0] = (char *)realloc (prompts[1][0], strlen (single) + 16); + prompts[1][1] = (char *)realloc (prompts[1][1], strlen (multi) + 16); +#ifdef HAVE_LIBREADLINE + sprintf (prompts[1][0], "\001%s\002%s\001%s\002", + COLOR(6), single, COLOR(0)); + sprintf (prompts[1][1], "\001%s\002%s\001%s\002", + COLOR(6), multi, COLOR(0)); +#else + sprintf (prompts[1][0], "%s%s%s", COLOR(6), single, COLOR(0)); + sprintf (prompts[1][1], "%s%s%s", COLOR(6), multi, COLOR(0)); +#endif +} + +void luap_sethistory(lua_State *L, const char *file) +{ + if (file) { + logfile = realloc (logfile, strlen(file) + 1); + strcpy (logfile, file); + } else if (logfile) { + free(logfile); + logfile = NULL; + } +} + +void luap_setcolor(lua_State *L, int enable) +{ + /* Don't allow color if we're not writing to a terminal. */ + + if (!isatty (STDOUT_FILENO) || !isatty (STDERR_FILENO)) { + colorize = 0; + } else { + colorize = enable; + } +} + +void luap_setname(lua_State *L, const char *name) +{ + chunkname = (char *)realloc (chunkname, strlen(name) + 2); + chunkname[0] = '='; + strcpy (chunkname + 1, name); +} + +void luap_getprompts(lua_State *L, const char **single, const char **multi) +{ + *single = prompts[0][0]; + *multi = prompts[0][1]; +} + +void luap_gethistory(lua_State *L, const char **file) +{ + *file = logfile; +} + +void luap_getcolor(lua_State *L, int *enabled) +{ + *enabled = colorize; +} + +void luap_getname(lua_State *L, const char **name) +{ + *name = chunkname + 1; +} + +void luap_enter(lua_State *L, bool *terminate) +{ + int incomplete = 0, s = 0, t = 0, l; + char *line, *prepended; +#ifdef SAVE_RESULTS + int cleanup = 0; +#endif + + /* Save the state since it needs to be passed to some readline + * callbacks. */ + + M = L; + + if (!initialized) { +#ifdef HAVE_LIBREADLINE + rl_basic_word_break_characters = " \t\n`@$><=;|&{("; + rl_completion_entry_function = generator; + rl_completion_display_matches_hook = display_matches; + + rl_add_defun ("lua-describe-stack", describe_stack, META('s')); +#endif + +#ifdef HAVE_READLINE_HISTORY + /* Load the command history if there is one. */ + + if (logfile) { + read_history (logfile); + } +#endif + if (!chunkname) { + luap_setname (L, "lua"); + } + + if (!prompts[0][0]) { + luap_setprompts (L, "> ", ">> "); + } + + atexit (finish); + + initialized = 1; + } + +#ifdef SAVE_RESULTS + if (results == LUA_REFNIL) { + lua_newtable(L); + +#ifdef WEAK_RESULTS + lua_newtable(L); + lua_pushliteral(L, "v"); + lua_setfield(L, -2, "__mode"); + lua_setmetatable(L, -2); +#endif + + results = luaL_ref(L, LUA_REGISTRYINDEX); + } + + lua_getglobal(L, RESULTS_TABLE_NAME); + if (lua_isnil(L, -1)) { + lua_rawgeti(L, LUA_REGISTRYINDEX, results); + lua_setglobal(L, RESULTS_TABLE_NAME); + + cleanup = 1; + } + lua_pop(L, 1); +#endif + + while (!(*terminate) && + (line = readline (incomplete ? + prompts[colorize][1] : prompts[colorize][0]))) { + int status; + + if (*line == '\0') { + free(line); + continue; + } + + /* Add/copy the line to the buffer. */ + + if (incomplete) { + s += strlen (line) + 1; + + if (s > t) { + buffer = (char *)realloc (buffer, s + 1); + t = s; + } + + strcat (buffer, "\n"); + strcat (buffer, line); + } else { + s = strlen (line); + + if (s > t) { + buffer = (char *)realloc (buffer, s + 1); + t = s; + } + + strcpy (buffer, line); + } + + /* Try to execute the line with a return prepended first. If + * this works we can show returned values. */ + + l = asprintf (&prepended, "return %s", buffer); + + if (luaL_loadbuffer(L, prepended, l, chunkname) == LUA_OK) { + execute(); + + incomplete = 0; + } else { + lua_pop (L, 1); + + /* Try to execute the line as-is. */ + + status = luaL_loadbuffer(L, buffer, s, chunkname); + + incomplete = 0; + + if (status == LUA_ERRSYNTAX) { + const char *message; + const int k = sizeof(EOF_MARKER) / sizeof(char) - 1; + size_t n; + + message = lua_tolstring (L, -1, &n); + + /* If the error message mentions an unexpected eof + * then consider this a multi-line statement and wait + * for more input. If not then just print the error + * message.*/ + + if ((int)n > k && + !strncmp (message + n - k, EOF_MARKER, k)) { + incomplete = 1; + } else { + print_error ("%s%s%s\n", COLOR(1), lua_tostring (L, -1), + COLOR(0)); + } + + lua_pop (L, 1); + } else if (status == LUA_ERRMEM) { + print_error ("%s%s%s\n", COLOR(1), lua_tostring (L, -1), + COLOR(0)); + lua_pop (L, 1); + } else { + /* Try to execute the loaded chunk. */ + + execute (); + incomplete = 0; + } + } + +#ifdef HAVE_READLINE_HISTORY + /* Add the line to the history if non-empty. */ + + if (!incomplete) { + add_history (buffer); + } +#endif + + free (prepended); + free (line); + } + +#ifdef SAVE_RESULTS + if (cleanup) { + lua_pushnil(L); + lua_setglobal(L, RESULTS_TABLE_NAME); + } +#endif + + print_output ("\n"); +} diff --git a/utils/hwstub/tools/prompt.h b/utils/hwstub/tools/prompt.h new file mode 100644 index 0000000000..0ad044b934 --- /dev/null +++ b/utils/hwstub/tools/prompt.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2012-2015 Papavasileiou Dimitris + * + * 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. + */ + +#ifndef _PROMPT_H_ +#define _PROMPT_H_ + +#include +#include + +#define HAVE_LIBREADLINE +#define HAVE_READLINE_HISTORY +#define HAVE_IOCTL +#define COMPLETE_KEYWORDS +#define COMPLETE_MODULES +#define COMPLETE_TABLE_KEYS +#define COMPLETE_METATABLE_KEYS +#define COMPLETE_FILE_NAMES + +#define LUAP_VERSION "0.6" + +void luap_setprompts(lua_State *L, const char *single, const char *multi); +void luap_sethistory(lua_State *L, const char *file); +void luap_setname(lua_State *L, const char *name); +void luap_setcolor(lua_State *L, int enable); + +void luap_getprompts(lua_State *L, const char **single, const char **multi); +void luap_gethistory(lua_State *L, const char **file); +void luap_getcolor(lua_State *L, int *enabled); +void luap_getname(lua_State *L, const char **name); + +void luap_enter(lua_State *L, bool *terminate); +char *luap_describe (lua_State *L, int index); +int luap_call (lua_State *L, int n); + +#endif -- cgit v1.2.3