fengari-node-cli
Version:
The Lua command line application, but using fengari under node
352 lines (323 loc) • 10.4 kB
JavaScript
;
const {
FENGARI_COPYRIGHT,
to_jsstring,
to_luastring,
lua: {
LUA_ERRSYNTAX,
LUA_MULTRET,
LUA_OK,
LUA_REGISTRYINDEX,
LUA_TSTRING,
LUA_TTABLE,
lua_createtable,
lua_getglobal,
lua_gettop,
lua_insert,
lua_pcall,
lua_pop,
lua_pushboolean,
lua_pushcfunction,
lua_pushliteral,
lua_pushstring,
lua_rawgeti,
lua_remove,
lua_setfield,
lua_setglobal,
lua_seti,
lua_settop,
lua_tojsstring,
lua_tostring,
lua_type
},
lauxlib: {
luaL_callmeta,
luaL_checkstack,
luaL_error,
luaL_len,
luaL_loadbuffer,
luaL_loadfile,
luaL_newstate,
luaL_traceback,
luaL_typename,
lua_writestringerror
},
lualib: {
LUA_VERSUFFIX,
luaL_openlibs
}
} = require('fengari');
const readlineSync = require('readline-sync');
const stdin = to_luastring("=stdin");
const _PROMPT = to_luastring("_PROMPT");
const _PROMPT2 = to_luastring("_PROMPT2");
const LUA_INIT_VAR = "LUA_INIT";
const LUA_INITVARVERSION = LUA_INIT_VAR + LUA_VERSUFFIX;
const report = function(L, status) {
if (status !== LUA_OK) {
lua_writestringerror(`${lua_tojsstring(L, -1)}\n`);
lua_pop(L, 1);
}
return status;
};
const msghandler = function(L) {
let msg = lua_tostring(L, 1);
if (msg === null) { /* is error object not a string? */
if (luaL_callmeta(L, 1, to_luastring("__tostring")) && /* does it have a metamethod */
lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */
return 1; /* that is the message */
else
msg = lua_pushstring(L, to_luastring(`(error object is a ${to_jsstring(luaL_typename(L, 1))} value)`));
}
luaL_traceback(L, L, msg, 1); /* append a standard traceback */
return 1; /* return the traceback */
};
const docall = function(L, narg, nres) {
let base = lua_gettop(L) - narg;
lua_pushcfunction(L, msghandler);
lua_insert(L, base);
let status = lua_pcall(L, narg, nres, base);
lua_remove(L, base);
return status;
};
const dochunk = function(L, status) {
if (status === LUA_OK) {
status = docall(L, 0, 0);
}
return report(L, status);
};
const dofile = function(L, name) {
return dochunk(L, luaL_loadfile(L, name?to_luastring(name):null));
};
const dostring = function(L, s, name) {
let buffer = to_luastring(s);
return dochunk(L, luaL_loadbuffer(L, buffer, buffer.length, to_luastring(name)));
};
const dolibrary = function(L, name) {
lua_getglobal(L, to_luastring("require"));
lua_pushliteral(L, name);
let status = docall(L, 1, 1); /* call 'require(name)' */
if (status === LUA_OK)
lua_setglobal(L, to_luastring(name)); /* global[name] = require return */
return report(L, status);
};
let progname = process.argv[1];
const print_usage = function(badoption) {
lua_writestringerror(`${progname}: `);
if (badoption[1] === "e" || badoption[1] === "l")
lua_writestringerror(`'${badoption}' needs argument\n`);
else
lua_writestringerror(`'unrecognized option '${badoption}'\n`);
lua_writestringerror(
`usage: ${progname} [options] [script [args]]\n` +
"Available options are:\n" +
" -e stat execute string 'stat'\n" +
" -i enter interactive mode after executing 'script'\n" +
" -l name require library 'name'\n" +
" -v show version information\n" +
" -E ignore environment variables\n" +
" -- stop handling options\n" +
" - stop handling options and execute stdin\n"
);
};
const L = luaL_newstate();
let script = 2; // Where to start args from
let has_E = false;
let has_i = false;
let has_v = false;
let has_e = false;
(function() {
let i;
for (i = 2; i<process.argv.length; i++) {
script = i;
if (process.argv[i][0] != "-") {
return;
}
switch(process.argv[i][1]) {
case '-':
if (process.argv[i][2]) {
print_usage(process.argv[script]);
return process.exit(1);
}
script = i + 1;
return;
case void 0: /* script name is '-' */
return;
case 'E':
has_E = true;
break;
case 'i':
has_i = true;
/* (-i implies -v) */
/* falls through */
case 'v':
if (process.argv[i].length > 2) {
/* invalid option */
print_usage(process.argv[script]);
return process.exit(1);
}
has_v = true;
break;
case 'e':
has_e = true;
/* falls through */
case 'l': /* both options need an argument */
if (process.argv[i].length < 3) { /* no concatenated argument? */
i++; /* try next 'process.argv' */
if (process.argv.length <= i || process.argv[i][0] === '-') {
/* no next argument or it is another option */
print_usage(process.argv[script]);
return process.exit(1);
}
}
break;
default: /* invalid option */
print_usage(process.argv[script]);
return process.exit(1);
}
}
script = i;
})();
if (has_v)
console.log(FENGARI_COPYRIGHT);
if (has_E) {
/* signal for libraries to ignore env. vars. */
lua_pushboolean(L, 1);
lua_setfield(L, LUA_REGISTRYINDEX, to_luastring("LUA_NOENV"));
}
/* open standard libraries */
luaL_openlibs(L);
/* create table 'arg' */
lua_createtable(L, process.argv.length - (script + 1), script + 1);
for (let i = 0; i < process.argv.length; i++) {
lua_pushliteral(L, process.argv[i]);
lua_seti(L, -2, i - script); /* TODO: rawseti */
}
lua_setglobal(L, to_luastring("arg"));
if (!has_E) {
/* run LUA_INIT */
let name = LUA_INITVARVERSION;
let init = process.env[name];
if (!init) {
name = LUA_INIT_VAR;
init = process.env[name];
}
if (init) {
let status;
if (init[0] === '@') {
status = dofile(L, init.substring(1));
} else {
status = dostring(L, init, name);
}
if (status !== LUA_OK) {
return process.exit(1);
}
}
}
/* execute arguments -e and -l */
for (let i = 1; i < script; i++) {
let option = process.argv[i][1];
if (option == 'e' || option == 'l') {
let extra = process.argv[i].substring(2); /* both options need an argument */
if (extra.length === 0)
extra = process.argv[++i];
let status;
if (option == 'e') {
status = dostring(L, extra, "=(command line)");
} else {
status = dolibrary(L, extra);
}
if (status !== LUA_OK) {
return process.exit(1);
}
}
}
const pushargs = function(L) {
if (lua_getglobal(L, to_luastring("arg")) !== LUA_TTABLE)
luaL_error(L, to_luastring("'arg' is not a table"));
let n = luaL_len(L, -1);
luaL_checkstack(L, n+3, to_luastring("too many arguments to script"));
let i;
for (i=1; i<=n; i++)
lua_rawgeti(L, -i, i);
lua_remove(L, -i);
return n;
};
const handle_script = function(L, argv) {
let fname = argv[0];
let status;
if (fname === "-" && argv[-1] !== "--")
fname = null; /* stdin */
else
fname = to_luastring(fname);
status = luaL_loadfile(L, fname);
if (status === LUA_OK) {
let n = pushargs(L); /* push arguments to script */
status = docall(L, n, LUA_MULTRET);
}
return report(L, status);
};
const doREPL = function(L) {
for (;;) {
lua_getglobal(L, _PROMPT);
let input = readlineSync.prompt({
prompt: lua_tojsstring(L, -1) || '> '
});
lua_pop(L, 1);
if (input.length === 0)
continue;
let status;
{
let buffer = to_luastring("return " + input);
status = luaL_loadbuffer(L, buffer, buffer.length, stdin);
}
if (status !== LUA_OK) {
lua_pop(L, 1);
let buffer = to_luastring(input);
if (luaL_loadbuffer(L, buffer, buffer.length, stdin) === LUA_OK) {
status = LUA_OK;
}
}
while (status === LUA_ERRSYNTAX && lua_tojsstring(L, -1).endsWith("<eof>")) {
/* continuation */
lua_pop(L, 1);
lua_getglobal(L, _PROMPT2);
input += "\n" + readlineSync.prompt({
prompt: lua_tojsstring(L, -1) || '>> '
});
lua_pop(L, 1);
let buffer = to_luastring(input);
status = luaL_loadbuffer(L, buffer, buffer.length, stdin);
}
if (status === LUA_OK) {
status = docall(L, 0, LUA_MULTRET);
}
if (status === LUA_OK) {
let n = lua_gettop(L);
if (n > 0) { /* any result to be printed? */
lua_getglobal(L, to_luastring("print"));
lua_insert(L, 1);
if (lua_pcall(L, n, 0, 0) != LUA_OK) {
lua_writestringerror(`error calling 'print' (${lua_tojsstring(L, -1)})\n`);
}
}
} else {
report(L, status);
}
lua_settop(L, 0); /* remove eventual returns */
}
};
if (script < process.argv.length && /* execute main script (if there is one) */
handle_script(L, process.argv.slice(script)) !== LUA_OK) {
/* success */
} else if (has_i) {
doREPL(L);
} else if (script == process.argv.length && !has_e && !has_v) { /* no arguments? */
if (process.stdin.isTTY) { /* running in interactive mode? */
console.log(FENGARI_COPYRIGHT);
doREPL(L); /* do read-eval-print loop */
} else {
dofile(L, null);
}
}