fengari-node-cli
Version:
The Lua command line application, but using fengari under node
521 lines (466 loc) • 14.2 kB
JavaScript
;
const {
FENGARI_COPYRIGHT,
to_jsstring,
to_luastring,
lua: {
LUA_OK,
LUA_SIGNATURE,
lua_checkstack,
lua_close,
lua_dump,
lua_load,
lua_pcall,
lua_pushcfunction,
lua_pushinteger,
lua_pushlightuserdata,
lua_tointeger,
lua_tojsstring,
lua_topointer,
lua_touserdata
},
lauxlib: {
luaL_loadfile,
luaL_newstate
}
} = require('fengari');
/* We need some fengari internals */
let fengariPath = require.resolve('fengari');
fengariPath = fengariPath.substr(0, fengariPath.lastIndexOf('/'));
const {
constant_types: {
LUA_TBOOLEAN,
LUA_TLNGSTR,
LUA_TNIL,
LUA_TNUMFLT,
LUA_TNUMINT,
LUA_TSHRSTR
}
} = require(`${fengariPath}/defs.js`);
const {
INDEXK,
ISK,
OpArgK,
OpArgN,
OpArgU,
OpCodes,
OpCodesI: {
OP_ADD,
OP_BAND,
OP_BOR,
OP_BXOR,
OP_CLOSURE,
OP_DIV,
OP_EQ,
OP_EXTRAARG,
OP_FORLOOP,
OP_FORPREP,
OP_GETTABLE,
OP_GETTABUP,
OP_GETUPVAL,
OP_IDIV,
OP_JMP,
OP_LE,
OP_LOADK,
OP_LT,
OP_MUL,
OP_POW,
OP_SELF,
OP_SETLIST,
OP_SETTABLE,
OP_SETTABUP,
OP_SETUPVAL,
OP_SHL,
OP_SHR,
OP_SUB,
OP_TFORLOOP
},
getBMode,
getCMode,
getOpMode,
iABC,
iABx,
iAsBx,
iAx
} = require(`${fengariPath}/lopcodes.js`);
const {
LUA_INTEGER_FMT,
LUA_NUMBER_FMT
} = require(`${fengariPath}/luaconf.js`);
const fs = require("fs");
const sprintf = require("sprintf-js").sprintf;
const PROGNAME = "fengaric"; /* default program name */
const OUTPUT = PROGNAME + ".out"; /* default output file */
let listing = false; /* list bytecodes? */
let dumping = true; /* dump bytecodes? */
let stripping = false; /* strip debug information? */
let output = OUTPUT; /* actual output file name */
let progname = PROGNAME; /* actual program name */
const print = function(message) {
process.stdout.write(Array.isArray(message) ? Buffer.from(message) : message);
};
const fatal = function(message) {
process.stderr.write(Buffer.from((`${progname}: ${message}\n`)));
process.exit(1);
};
const usage = function(message) {
if (message[0] === "-")
process.stderr.write(Buffer.from((`${progname}: unrecognized option '${message}'\n`)));
else
process.stderr.write(Buffer.from((`${progname}: ${message}\n`)));
process.stderr.write(Buffer.from((
`usage: ${progname} [options] [filename]\n`
+ `Available options are:\n`
+ ` -l list (use -l -l for full listing)\n`
+ ` -o name output to file 'name' (default is "${OUTPUT}")\n`
+ ` -p parse only\n`
+ ` -s strip debug information\n`
+ ` -v show version information\n`
+ ` -- stop handling options\n`
+ ` - stop handling options and process stdin\n`
)));
process.exit(1);
};
const doargs = function() {
let version = 0;
let i;
for (i = 2; i < process.argv.length; i++) {
let arg = process.argv[i];
if (arg[0] !== "-") /* end of options; keep it */
break;
else if (arg === "--") { /* end of options; skip it */
++i;
if (version) ++version;
break;
} else if (arg === "-") /* end of options; use stdin */
break;
else if (arg === "-l") /* list */
++listing;
else if (arg === "-o") { /* output file */
output = process.argv[++i];
if (!output || (output[0] == "-" && output.length > 1))
usage("'-o' needs argument");
if (arg === "-") output = null;
} else if (arg === "-p") /* parse only */
dumping = false;
else if (arg === "s") /* strip debug information */
stripping = true;
else if (arg === "-v") /* show version */
++version;
else /* unknown option */
usage(arg);
}
if (i === process.argv.length && (listing || !dumping)) {
dumping = false;
process.argv[--i] = OUTPUT;
}
if (version) {
print(FENGARI_COPYRIGHT + "\n");
if (version === process.argv.length - 2) process.exit(0);
}
return i;
};
const FUNCTION = to_luastring("(function()end)();");
const reader = function(L, ud) {
if (ud--)
return FUNCTION;
else
return null;
};
const toproto = function(L, i) {
return lua_topointer(L, i).p;
};
const combine = function(L, n) {
if (n === 1)
return toproto(L, -1);
else {
let i = n;
if (lua_load(L, reader, i, to_luastring(`=(${PROGNAME})`), null) !== LUA_OK)
fatal(lua_tojsstring(L, -1));
let f = toproto(L, -1);
for (i = 0; i < n; i++) {
f.p[i] = lua_topointer(L, i - n - 1).p;
if (f.p[i].upvalues.length > 0)
f.p[i].upvalues[0].instack = false;
}
f.lineinfo.length = 0;
return f;
}
};
const writer = function(L, p, size, u) {
return fs.writeSync(u, Buffer.from(p), 0, size) > 0 ? 0 : 1;
};
const pmain = function(L) {
let argc = lua_tointeger(L,1);
let argv = lua_touserdata(L,2);
let i;
if (!lua_checkstack(L,argc))
fatal("too many input files");
for (i = 0; i < argc; i++) {
let filename = argv[i] === "-" ? null : to_luastring(argv[i]);
if (luaL_loadfile(L, filename) !== LUA_OK)
fatal(lua_tojsstring(L,-1));
}
let f = combine(L, argc);
if (listing)
PrintFunction(f, listing > 1);
if (dumping) {
try {
let D = (output === null) ? process.stdout.fd : fs.openSync(output, 'w');
lua_dump(L, writer, D, stripping);
} catch (e) {
fatal(e.message);
}
}
return 0;
};
const main = function() {
let i = doargs();
if (process.argv.length - i <= 0)
usage("no input files given");
let L = luaL_newstate();
lua_pushcfunction(L, pmain);
lua_pushinteger(L, process.argv.length - i);
lua_pushlightuserdata(L, process.argv.slice(i));
if (lua_pcall(L,2,0,0) !== LUA_OK)
fatal(lua_tojsstring(L,-1));
lua_close(L);
process.exit(0);
};
const isprint = function(c) {
return /^[\x20-\x7E]$/.test(String.fromCharCode(c));
};
const PrintString = function(ts) {
print("\"");
let str = ts.value.getstr();
for (let i = 0; i < ts.value.tsslen(); i++) {
let c = str[i];
switch (String.fromCharCode(c)) {
case '"': print("\\\""); break;
case '\\': print("\\\\"); break;
case '\b': print("\\b"); break;
case '\f': print("\\f"); break;
case '\n': print("\\n"); break;
case '\r': print("\\r"); break;
case '\t': print("\\t"); break;
case '\v': print("\\v"); break;
default:
if (isprint(c))
print(String.fromCharCode(c));
else
print(`\\${sprintf("%03d", c)}`);
}
}
print("\"");
};
const PrintConstant = function(f, i) {
let o = f.k[i];
switch (o.ttype()) {
case LUA_TNIL:
print("nil");
break;
case LUA_TBOOLEAN:
print(o.value ? "true" : "false");
break;
case LUA_TNUMFLT: {
let fltstr = sprintf(LUA_NUMBER_FMT, o.value);
if (/^\d*$/.test(fltstr)) // Add .0 if no decimal part in string
fltstr = `${fltstr}.0`;
print(fltstr);
break;
}
case LUA_TNUMINT:
print(sprintf(LUA_INTEGER_FMT, o.value));
break;
case LUA_TSHRSTR: case LUA_TLNGSTR:
PrintString(o);
break;
default:
print(`? type=${o.ttype()}`);
break;
}
};
const UPVALNAME = function(f, x) {
return f.upvalues[x].name ? to_jsstring(f.upvalues[x].name.getstr()) : "-";
};
const MYK = function(x) {
return -1-x;
};
const PrintCode = function(f) {
let code = f.code;
for (let pc = 0; pc < code.length; pc++) {
let i = code[pc];
let o = i.opcode;
let a = i.A;
let b = i.B;
let c = i.C;
let ax = i.Ax;
let bx = i.Bx;
let sbx = i.sBx;
let line = f.lineinfo[pc] ? f.lineinfo[pc] : -1;
print(`\t${pc + 1}\t`);
if (line > 0) print(`[${line}]\t`);
else print("[-]\t");
let opcode = OpCodes[o].substr(0,9);
let tabfill = 9 - opcode.length;
print(`${opcode}${" ".repeat(tabfill)}\t`);
switch(getOpMode(o)) {
case iABC: {
print(`${a}`);
if (getBMode(o) != OpArgN)
print(` ${ISK(b) ? MYK(INDEXK(b)) : b}`);
if (getCMode(o) != OpArgN)
print(` ${ISK(c) ? MYK(INDEXK(c)) : c}`);
break;
}
case iABx: {
print(`${a}`);
if (getBMode(o) == OpArgK) print(` ${MYK(bx)}`);
if (getBMode(o) == OpArgU) print(` ${bx}`);
break;
}
case iAsBx: {
print(`${a} ${sbx}`);
break;
}
case iAx: {
print(`${MYK(ax)}`);
break;
}
}
switch(o) {
case OP_LOADK: {
print("\t; ");
PrintConstant(f, bx);
break;
}
case OP_GETUPVAL:
case OP_SETUPVAL: {
print(`\t; ${UPVALNAME(f, b)}`);
break;
}
case OP_GETTABUP: {
print(`\t; ${UPVALNAME(f, b)}`);
if (ISK(c)) {
print(" ");
PrintConstant(f, INDEXK(c));
}
break;
}
case OP_SETTABUP: {
print(`\t; ${UPVALNAME(f, a)}`);
if (ISK(b)) {
print(" ");
PrintConstant(f, INDEXK(b));
}
if (ISK(c)) {
print(" ");
PrintConstant(f, INDEXK(c));
}
break;
}
case OP_GETTABLE:
case OP_SELF: {
if (ISK(c)) {
print("\t; ");
PrintConstant(f, INDEXK(c));
}
break;
}
case OP_SETTABLE:
case OP_ADD:
case OP_SUB:
case OP_MUL:
case OP_POW:
case OP_DIV:
case OP_IDIV:
case OP_BAND:
case OP_BOR:
case OP_BXOR:
case OP_SHL:
case OP_SHR:
case OP_EQ:
case OP_LT:
case OP_LE: {
if (ISK(b) || ISK(c)) {
print("\t; ");
if (ISK(b)) PrintConstant(f, INDEXK(b));
else print("-");
print(" ");
if (ISK(c)) PrintConstant(f, INDEXK(c));
else print("-");
}
break;
}
case OP_JMP:
case OP_FORLOOP:
case OP_FORPREP:
case OP_TFORLOOP: {
print(`\t; to ${sbx + pc + 2}`);
break;
}
case OP_CLOSURE: {
print(`\t; 0x${f.p[bx].id.toString(16)}`); // TODO: %p
break;
}
case OP_SETLIST: {
if (c === 0) print(`\t; ${code[++pc]}`);
else print(`\t; ${c}`);
break;
}
case OP_EXTRAARG: {
print("\t; ");
PrintConstant(f, ax);
break;
}
default:
break;
}
print("\n");
}
};
const SS = function(n) {
return n === 1 ? "" : "s";
};
const PrintHeader = function(f) {
let s = f.source ? f.source.getstr() : to_luastring("=?");
if (s[0] === "@".charCodeAt(0) || s[0] === "=".charCodeAt(0))
s = s.slice(1);
else if (s[0] === LUA_SIGNATURE.charCodeAt(0))
s = to_luastring("(bstring)");
else
s = to_luastring("(string)");
print(`\n${f.linedefined === 0 ? "main" : "function"} <${to_jsstring(s)}:${f.linedefined},${f.lastlinedefined}> (${f.code.length} instruction${SS(f.code.length)} at 0x${f.id.toString(16)})\n`);
print(`${f.numparams}${f.is_vararg ? "+" : ""} param${SS(f.numparams)}, ${f.maxstacksize} slot${SS(f.maxstacksize)}, ${f.upvalues.length} upvalue${SS(f.upvalues.length)}, `);
print(`${f.locvars.length} local${SS(f.locvars.length)}, ${f.k.length} constant${SS(f.k.length)}, ${f.p.length} function${SS(f.p.length)}\n`);
};
const PrintDebug = function(f) {
let n = f.k.length;
print(`constants (${n}) for 0x${f.id.toString(16)}:\n`);
for (let i = 0; i < n; i++) {
print(`\t${i + 1}\t`);
PrintConstant(f, i);
print("\n");
}
n = f.locvars.length;
print(`locals (${n}) for 0x${f.id.toString(16)}:\n`);
for (let i = 0; i < n; i++) {
let locvar = f.locvars[i];
print(`\t${i}\t${to_jsstring(locvar.varname.getstr())}\t${locvar.startpc + 1}\t${locvar.endpc + 1}\n`);
}
n = f.upvalues.length;
print(`upvalues (${n}) for 0x${f.id.toString(16)}:\n`);
for (let i = 0; i < n; i++) {
print(`\t${i}\t${UPVALNAME(f, i)}\t${f.upvalues[i].instack ? 1 : 0}\t${f.upvalues[i].idx}\n`);
}
};
const PrintFunction = function(f, full) {
let n = f.p.length;
PrintHeader(f);
PrintCode(f);
if (full) PrintDebug(f);
for (let i = 0; i < n; i++)
PrintFunction(f.p[i], full);
};
main();