"use strict"; const { LUA_OPADD, LUA_OPBAND, LUA_OPBNOT, LUA_OPBOR, LUA_OPBXOR, LUA_OPDIV, LUA_OPIDIV, LUA_OPMOD, LUA_OPMUL, LUA_OPPOW, LUA_OPSHL, LUA_OPSHR, LUA_OPSUB, LUA_OPUNM, constant_types: { LUA_NUMTAGS, LUA_TBOOLEAN, LUA_TCCL, LUA_TFUNCTION, LUA_TLCF, LUA_TLCL, LUA_TLIGHTUSERDATA, LUA_TLNGSTR, LUA_TNIL, LUA_TNUMBER, LUA_TNUMFLT, LUA_TNUMINT, LUA_TSHRSTR, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA }, from_userstring, luastring_indexOf, luastring_of, to_jsstring, to_luastring } = require('./defs.js'); const { lisdigit, lisprint, lisspace, lisxdigit } = require('./ljstype.js'); const ldebug = require('./ldebug.js'); const ldo = require('./ldo.js'); const lstate = require('./lstate.js'); const { luaS_bless, luaS_new } = require('./lstring.js'); const ltable = require('./ltable.js'); const { LUA_COMPAT_FLOATSTRING, ldexp, lua_getlocaledecpoint, lua_integer2str, lua_number2str } = require('./luaconf.js'); const lvm = require('./lvm.js'); const { MAX_INT, luai_nummod, lua_assert } = require("./llimits.js"); const ltm = require('./ltm.js'); const LUA_TPROTO = LUA_NUMTAGS; const LUA_TDEADKEY = LUA_NUMTAGS+1; class TValue { constructor(type, value) { this.type = type; this.value = value; } /* type tag of a TValue (bits 0-3 for tags + variant bits 4-5) */ ttype() { return this.type & 0x3F; } /* type tag of a TValue with no variants (bits 0-3) */ ttnov() { return this.type & 0x0F; } checktag(t) { return this.type === t; } checktype(t) { return this.ttnov() === t; } ttisnumber() { return this.checktype(LUA_TNUMBER); } ttisfloat() { return this.checktag(LUA_TNUMFLT); } ttisinteger() { return this.checktag(LUA_TNUMINT); } ttisnil() { return this.checktag(LUA_TNIL); } ttisboolean() { return this.checktag(LUA_TBOOLEAN); } ttislightuserdata() { return this.checktag(LUA_TLIGHTUSERDATA); } ttisstring() { return this.checktype(LUA_TSTRING); } ttisshrstring() { return this.checktag(LUA_TSHRSTR); } ttislngstring() { return this.checktag(LUA_TLNGSTR); } ttistable() { return this.checktag(LUA_TTABLE); } ttisfunction() { return this.checktype(LUA_TFUNCTION); } ttisclosure() { return (this.type & 0x1F) === LUA_TFUNCTION; } ttisCclosure() { return this.checktag(LUA_TCCL); } ttisLclosure() { return this.checktag(LUA_TLCL); } ttislcf() { return this.checktag(LUA_TLCF); } ttisfulluserdata() { return this.checktag(LUA_TUSERDATA); } ttisthread() { return this.checktag(LUA_TTHREAD); } ttisdeadkey() { return this.checktag(LUA_TDEADKEY); } l_isfalse() { return this.ttisnil() || (this.ttisboolean() && this.value === false); } setfltvalue(x) { this.type = LUA_TNUMFLT; this.value = x; } chgfltvalue(x) { lua_assert(this.type == LUA_TNUMFLT); this.value = x; } setivalue(x) { this.type = LUA_TNUMINT; this.value = x; } chgivalue(x) { lua_assert(this.type == LUA_TNUMINT); this.value = x; } setnilvalue() { this.type = LUA_TNIL; this.value = null; } setfvalue(x) { this.type = LUA_TLCF; this.value = x; } setpvalue(x) { this.type = LUA_TLIGHTUSERDATA; this.value = x; } setbvalue(x) { this.type = LUA_TBOOLEAN; this.value = x; } setsvalue(x) { this.type = LUA_TLNGSTR; /* LUA_TSHRSTR? */ this.value = x; } setuvalue(x) { this.type = LUA_TUSERDATA; this.value = x; } setthvalue(x) { this.type = LUA_TTHREAD; this.value = x; } setclLvalue(x) { this.type = LUA_TLCL; this.value = x; } setclCvalue(x) { this.type = LUA_TCCL; this.value = x; } sethvalue(x) { this.type = LUA_TTABLE; this.value = x; } setdeadvalue() { this.type = LUA_TDEADKEY; this.value = null; } setfrom(tv) { /* in lua C source setobj2t is often used for this */ this.type = tv.type; this.value = tv.value; } tsvalue() { lua_assert(this.ttisstring()); return this.value; } svalue() { return this.tsvalue().getstr(); } vslen() { return this.tsvalue().tsslen(); } jsstring(from, to) { return to_jsstring(this.svalue(), from, to, true); } } const pushobj2s = function(L, tv) { L.stack[L.top++] = new TValue(tv.type, tv.value); }; const pushsvalue2s = function(L, ts) { L.stack[L.top++] = new TValue(LUA_TLNGSTR, ts); }; /* from stack to (same) stack */ const setobjs2s = function(L, newidx, oldidx) { L.stack[newidx].setfrom(L.stack[oldidx]); }; /* to stack (not from same stack) */ const setobj2s = function(L, newidx, oldtv) { L.stack[newidx].setfrom(oldtv); }; const setsvalue2s = function(L, newidx, ts) { L.stack[newidx].setsvalue(ts); }; const luaO_nilobject = new TValue(LUA_TNIL, null); Object.freeze(luaO_nilobject); module.exports.luaO_nilobject = luaO_nilobject; class LClosure { constructor(L, n) { this.id = L.l_G.id_counter++; this.p = null; this.nupvalues = n; this.upvals = new Array(n); /* list of upvalues. initialised in luaF_initupvals */ } } class CClosure { constructor(L, f, n) { this.id = L.l_G.id_counter++; this.f = f; this.nupvalues = n; this.upvalue = new Array(n); /* list of upvalues as TValues */ while (n--) { this.upvalue[n] = new TValue(LUA_TNIL, null); } } } class Udata { constructor(L, size) { this.id = L.l_G.id_counter++; this.metatable = null; this.uservalue = new TValue(LUA_TNIL, null); this.len = size; this.data = Object.create(null); // ignores size argument } } /* ** Description of a local variable for function prototypes ** (used for debug information) */ class LocVar { constructor() { this.varname = null; this.startpc = NaN; /* first point where variable is active */ this.endpc = NaN; /* first point where variable is dead */ } } const RETS = to_luastring("..."); const PRE = to_luastring("[string \""); const POS = to_luastring("\"]"); const luaO_chunkid = function(source, bufflen) { let l = source.length; let out; if (source[0] === 61 /* ('=').charCodeAt(0) */) { /* 'literal' source */ if (l < bufflen) { /* small enough? */ out = new Uint8Array(l-1); out.set(source.subarray(1)); } else { /* truncate it */ out = new Uint8Array(bufflen); out.set(source.subarray(1, bufflen+1)); } } else if (source[0] === 64 /* ('@').charCodeAt(0) */) { /* file name */ if (l <= bufflen) { /* small enough? */ out = new Uint8Array(l-1); out.set(source.subarray(1)); } else { /* add '...' before rest of name */ out = new Uint8Array(bufflen); out.set(RETS); bufflen -= RETS.length; out.set(source.subarray(l - bufflen), RETS.length); } } else { /* string; format as [string "source"] */ out = new Uint8Array(bufflen); let nli = luastring_indexOf(source, 10 /* ('\n').charCodeAt(0) */); /* find first new line (if any) */ out.set(PRE); /* add prefix */ let out_i = PRE.length; bufflen -= PRE.length + RETS.length + POS.length; /* save space for prefix+suffix */ if (l < bufflen && nli === -1) { /* small one-line source? */ out.set(source, out_i); /* keep it */ out_i += source.length; } else { if (nli !== -1) l = nli; /* stop at first newline */ if (l > bufflen) l = bufflen; out.set(source.subarray(0, l), out_i); out_i += l; out.set(RETS, out_i); out_i += RETS.length; } out.set(POS, out_i); out_i += POS.length; out = out.subarray(0, out_i); } return out; }; const luaO_hexavalue = function(c) { if (lisdigit(c)) return c - 48; else return (c & 0xdf) - 55; }; const UTF8BUFFSZ = 8; const luaO_utf8esc = function(buff, x) { let n = 1; /* number of bytes put in buffer (backwards) */ lua_assert(x <= 0x10FFFF); if (x < 0x80) /* ascii? */ buff[UTF8BUFFSZ - 1] = x; else { /* need continuation bytes */ let mfb = 0x3f; /* maximum that fits in first byte */ do { buff[UTF8BUFFSZ - (n++)] = 0x80 | (x & 0x3f); x >>= 6; /* remove added bits */ mfb >>= 1; /* now there is one less bit available in first byte */ } while (x > mfb); /* still needs continuation byte? */ buff[UTF8BUFFSZ - n] = (~mfb << 1) | x; /* add first byte */ } return n; }; /* maximum number of significant digits to read (to avoid overflows even with single floats) */ const MAXSIGDIG = 30; /* ** convert an hexadecimal numeric string to a number, following ** C99 specification for 'strtod' */ const lua_strx2number = function(s) { let i = 0; let dot = lua_getlocaledecpoint(); let r = 0.0; /* result (accumulator) */ let sigdig = 0; /* number of significant digits */ let nosigdig = 0; /* number of non-significant digits */ let e = 0; /* exponent correction */ let neg; /* 1 if number is negative */ let hasdot = false; /* true after seen a dot */ while (lisspace(s[i])) i++; /* skip initial spaces */ if ((neg = (s[i] === 45 /* ('-').charCodeAt(0) */))) i++; /* check signal */ else if (s[i] === 43 /* ('+').charCodeAt(0) */) i++; if (!(s[i] === 48 /* ('0').charCodeAt(0) */ && (s[i+1] === 120 /* ('x').charCodeAt(0) */ || s[i+1] === 88 /* ('X').charCodeAt(0) */))) /* check '0x' */ return null; /* invalid format (no '0x') */ for (i += 2; ; i++) { /* skip '0x' and read numeral */ if (s[i] === dot) { if (hasdot) break; /* second dot? stop loop */ else hasdot = true; } else if (lisxdigit(s[i])) { if (sigdig === 0 && s[i] === 48 /* ('0').charCodeAt(0) */) /* non-significant digit (zero)? */ nosigdig++; else if (++sigdig <= MAXSIGDIG) /* can read it without overflow? */ r = (r * 16) + luaO_hexavalue(s[i]); else e++; /* too many digits; ignore, but still count for exponent */ if (hasdot) e--; /* decimal digit? correct exponent */ } else break; /* neither a dot nor a digit */ } if (nosigdig + sigdig === 0) /* no digits? */ return null; /* invalid format */ e *= 4; /* each digit multiplies/divides value by 2^4 */ if (s[i] === 112 /* ('p').charCodeAt(0) */ || s[i] === 80 /* ('P').charCodeAt(0) */) { /* exponent part? */ let exp1 = 0; /* exponent value */ let neg1; /* exponent signal */ i++; /* skip 'p' */ if ((neg1 = (s[i] === 45 /* ('-').charCodeAt(0) */))) i++; /* signal */ else if (s[i] === 43 /* ('+').charCodeAt(0) */) i++; if (!lisdigit(s[i])) return null; /* invalid; must have at least one digit */ while (lisdigit(s[i])) /* read exponent */ exp1 = exp1 * 10 + s[i++] - 48 /* ('0').charCodeAt(0) */; if (neg1) exp1 = -exp1; e += exp1; } if (neg) r = -r; return { n: ldexp(r, e), i: i }; }; const lua_str2number = function(s) { try { s = to_jsstring(s); } catch (e) { return null; } /* use a regex to validate number and also to get length parseFloat ignores trailing junk */ let r = /^[\t\v\f \n\r]*[+-]?(?:[0-9]+\.?[0-9]*|\.[0-9]*)(?:[eE][+-]?[0-9]+)?/.exec(s); if (!r) return null; let flt = parseFloat(r[0]); return !isNaN(flt) ? { n: flt, i: r[0].length } : null; }; const l_str2dloc = function(s, mode) { let result = mode === 'x' ? lua_strx2number(s) : lua_str2number(s); /* try to convert */ if (result === null) return null; while (lisspace(s[result.i])) result.i++; /* skip trailing spaces */ return (result.i === s.length || s[result.i] === 0) ? result : null; /* OK if no trailing characters */ }; const SIGILS = [ 46 /* (".").charCodeAt(0) */, 120 /* ("x").charCodeAt(0) */, 88 /* ("X").charCodeAt(0) */, 110 /* ("n").charCodeAt(0) */, 78 /* ("N").charCodeAt(0) */ ]; const modes = { [ 46]: ".", [120]: "x", [ 88]: "x", [110]: "n", [ 78]: "n" }; const l_str2d = function(s) { let l = s.length; let pmode = 0; for (let i=0; i= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg)) /* overflow? */ return null; /* do not accept it (as integer) */ a = (a * 10 + d)|0; empty = false; } } while (i < s.length && lisspace(s[i])) i++; /* skip trailing spaces */ if (empty || (i !== s.length && s[i] !== 0)) return null; /* something wrong in the numeral */ else { return { n: (neg ? -a : a)|0, i: i }; } }; const luaO_str2num = function(s, o) { let s2i = l_str2int(s); if (s2i !== null) { /* try as an integer */ o.setivalue(s2i.n); return s2i.i+1; } else { /* else try as a float */ s2i = l_str2d(s); if (s2i !== null) { o.setfltvalue(s2i.n); return s2i.i+1; } else return 0; /* conversion failed */ } }; const luaO_tostring = function(L, obj) { let buff; if (obj.ttisinteger()) buff = to_luastring(lua_integer2str(obj.value)); else { let str = lua_number2str(obj.value); if (!LUA_COMPAT_FLOATSTRING && /^[-0123456789]+$/.test(str)) { /* looks like an int? */ str += String.fromCharCode(lua_getlocaledecpoint()) + '0'; /* adds '.0' to result */ } buff = to_luastring(str); } obj.setsvalue(luaS_bless(L, buff)); }; const pushstr = function(L, str) { ldo.luaD_inctop(L); setsvalue2s(L, L.top-1, luaS_new(L, str)); }; const luaO_pushvfstring = function(L, fmt, argp) { let n = 0; let i = 0; let a = 0; let e; for (;;) { e = luastring_indexOf(fmt, 37 /* ('%').charCodeAt(0) */, i); if (e == -1) break; pushstr(L, fmt.subarray(i, e)); switch(fmt[e+1]) { case 115 /* ('s').charCodeAt(0) */: { let s = argp[a++]; if (s === null) s = to_luastring("(null)", true); else { s = from_userstring(s); /* respect null terminator */ let i = luastring_indexOf(s, 0); if (i !== -1) s = s.subarray(0, i); } pushstr(L, s); break; } case 99 /* ('c').charCodeAt(0) */: { let buff = argp[a++]; if (lisprint(buff)) pushstr(L, luastring_of(buff)); else luaO_pushfstring(L, to_luastring("<\\%d>", true), buff); break; } case 100 /* ('d').charCodeAt(0) */: case 73 /* ('I').charCodeAt(0) */: ldo.luaD_inctop(L); L.stack[L.top-1].setivalue(argp[a++]); luaO_tostring(L, L.stack[L.top-1]); break; case 102 /* ('f').charCodeAt(0) */: ldo.luaD_inctop(L); L.stack[L.top-1].setfltvalue(argp[a++]); luaO_tostring(L, L.stack[L.top-1]); break; case 112 /* ('p').charCodeAt(0) */: { let v = argp[a++]; if (v instanceof lstate.lua_State || v instanceof ltable.Table || v instanceof Udata || v instanceof LClosure || v instanceof CClosure) { pushstr(L, to_luastring("0x"+v.id.toString(16))); } else { switch(typeof v) { case "undefined": pushstr(L, to_luastring("undefined")); break; case "number": /* before check object as null is an object */ pushstr(L, to_luastring("Number("+v+")")); break; case "string": /* before check object as null is an object */ pushstr(L, to_luastring("String("+JSON.stringify(v)+")")); break; case "boolean": /* before check object as null is an object */ pushstr(L, to_luastring(v?"Boolean(true)":"Boolean(false)")); break; case "object": if (v === null) { /* null is special */ pushstr(L, to_luastring("null")); break; } /* fall through */ case "function": { let id = L.l_G.ids.get(v); if (!id) { id = L.l_G.id_counter++; L.l_G.ids.set(v, id); } pushstr(L, to_luastring("0x"+id.toString(16))); break; } default: /* user provided object. no id available */ pushstr(L, to_luastring("")); } } break; } case 85 /* ('U').charCodeAt(0) */: { let buff = new Uint8Array(UTF8BUFFSZ); let l = luaO_utf8esc(buff, argp[a++]); pushstr(L, buff.subarray(UTF8BUFFSZ - l)); break; } case 37 /* ('%').charCodeAt(0) */: pushstr(L, to_luastring("%", true)); break; default: ldebug.luaG_runerror(L, to_luastring("invalid option '%%%c' to 'lua_pushfstring'"), fmt[e + 1]); } n += 2; i = e + 2; } ldo.luaD_checkstack(L, 1); pushstr(L, fmt.subarray(i)); if (n > 0) lvm.luaV_concat(L, n+1); return L.stack[L.top-1].svalue(); }; const luaO_pushfstring = function(L, fmt, ...argp) { return luaO_pushvfstring(L, fmt, argp); }; /* ** converts an integer to a "floating point byte", represented as ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if ** eeeee !== 0 and (xxx) otherwise. */ const luaO_int2fb = function(x) { let e = 0; /* exponent */ if (x < 8) return x; while (x >= (8 << 4)) { /* coarse steps */ x = (x + 0xf) >> 4; /* x = ceil(x / 16) */ e += 4; } while (x >= (8 << 1)) { /* fine steps */ x = (x + 1) >> 1; /* x = ceil(x / 2) */ e++; } return ((e+1) << 3) | (x - 8); }; const intarith = function(L, op, v1, v2) { switch (op) { case LUA_OPADD: return (v1 + v2)|0; case LUA_OPSUB: return (v1 - v2)|0; case LUA_OPMUL: return lvm.luaV_imul(v1, v2); case LUA_OPMOD: return lvm.luaV_mod(L, v1, v2); case LUA_OPIDIV: return lvm.luaV_div(L, v1, v2); case LUA_OPBAND: return (v1 & v2); case LUA_OPBOR: return (v1 | v2); case LUA_OPBXOR: return (v1 ^ v2); case LUA_OPSHL: return lvm.luaV_shiftl(v1, v2); case LUA_OPSHR: return lvm.luaV_shiftl(v1, -v2); case LUA_OPUNM: return (0 - v1)|0; case LUA_OPBNOT: return (~0 ^ v1); default: lua_assert(0); } }; const numarith = function(L, op, v1, v2) { switch (op) { case LUA_OPADD: return v1 + v2; case LUA_OPSUB: return v1 - v2; case LUA_OPMUL: return v1 * v2; case LUA_OPDIV: return v1 / v2; case LUA_OPPOW: return Math.pow(v1, v2); case LUA_OPIDIV: return Math.floor(v1 / v2); case LUA_OPUNM: return -v1; case LUA_OPMOD: return luai_nummod(L, v1, v2); default: lua_assert(0); } }; const luaO_arith = function(L, op, p1, p2, p3) { let res = (typeof p3 === "number") ? L.stack[p3] : p3; /* FIXME */ switch (op) { case LUA_OPBAND: case LUA_OPBOR: case LUA_OPBXOR: case LUA_OPSHL: case LUA_OPSHR: case LUA_OPBNOT: { /* operate only on integers */ let i1, i2; if ((i1 = lvm.tointeger(p1)) !== false && (i2 = lvm.tointeger(p2)) !== false) { res.setivalue(intarith(L, op, i1, i2)); return; } else break; /* go to the end */ } case LUA_OPDIV: case LUA_OPPOW: { /* operate only on floats */ let n1, n2; if ((n1 = lvm.tonumber(p1)) !== false && (n2 = lvm.tonumber(p2)) !== false) { res.setfltvalue(numarith(L, op, n1, n2)); return; } else break; /* go to the end */ } default: { /* other operations */ let n1, n2; if (p1.ttisinteger() && p2.ttisinteger()) { res.setivalue(intarith(L, op, p1.value, p2.value)); return; } else if ((n1 = lvm.tonumber(p1)) !== false && (n2 = lvm.tonumber(p2)) !== false) { res.setfltvalue(numarith(L, op, n1, n2)); return; } else break; /* go to the end */ } } /* could not perform raw operation; try metamethod */ lua_assert(L !== null); /* should not fail when folding (compile time) */ ltm.luaT_trybinTM(L, p1, p2, p3, (op - LUA_OPADD) + ltm.TMS.TM_ADD); }; module.exports.CClosure = CClosure; module.exports.LClosure = LClosure; module.exports.LUA_TDEADKEY = LUA_TDEADKEY; module.exports.LUA_TPROTO = LUA_TPROTO; module.exports.LocVar = LocVar; module.exports.TValue = TValue; module.exports.Udata = Udata; module.exports.UTF8BUFFSZ = UTF8BUFFSZ; module.exports.luaO_arith = luaO_arith; module.exports.luaO_chunkid = luaO_chunkid; module.exports.luaO_hexavalue = luaO_hexavalue; module.exports.luaO_int2fb = luaO_int2fb; module.exports.luaO_pushfstring = luaO_pushfstring; module.exports.luaO_pushvfstring = luaO_pushvfstring; module.exports.luaO_str2num = luaO_str2num; module.exports.luaO_tostring = luaO_tostring; module.exports.luaO_utf8esc = luaO_utf8esc; module.exports.numarith = numarith; module.exports.pushobj2s = pushobj2s; module.exports.pushsvalue2s = pushsvalue2s; module.exports.setobjs2s = setobjs2s; module.exports.setobj2s = setobj2s; module.exports.setsvalue2s = setsvalue2s;