rockbox/utils/hwstub/tools/prompt.c
Marcin Bukat e70ea5d21f 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
2015-06-28 17:55:17 +02:00

1659 lines
42 KiB
C

/* 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 <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <unistd.h>
#include "prompt.h"
#ifdef HAVE_IOCTL
#include <sys/ioctl.h>
#endif
#include <glob.h>
#include <lualib.h>
#include <lauxlib.h>
#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 <readline/readline.h>
#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 <readline/history.h>
#endif /* HAVE_READLINE_HISTORY */
#if LUA_VERSION_NUM == 501
#define EOF_MARKER "'<eof>'"
#else
#define EOF_MARKER "<eof>"
#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");
}