diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lapi.js | 40 | ||||
| -rw-r--r-- | src/lbaselib.js | 3 | ||||
| -rw-r--r-- | src/lcode.js | 1266 | ||||
| -rw-r--r-- | src/lcorolib.js | 1 | ||||
| -rw-r--r-- | src/ldebug.js | 6 | ||||
| -rw-r--r-- | src/ldo.js | 78 | ||||
| -rw-r--r-- | src/lfunc.js | 24 | ||||
| -rw-r--r-- | src/linit.js | 1 | ||||
| -rw-r--r-- | src/ljstype.js | 29 | ||||
| -rw-r--r-- | src/llex.js | 609 | ||||
| -rw-r--r-- | src/lmathlib.js | 1 | ||||
| -rw-r--r-- | src/lobject.js | 197 | ||||
| -rw-r--r-- | src/lopcodes.js | 232 | ||||
| -rw-r--r-- | src/lparser.js | 1560 | ||||
| -rw-r--r-- | src/lstate.js | 2 | ||||
| -rw-r--r-- | src/ltablib.js | 1 | ||||
| -rw-r--r-- | src/lua.js | 73 | ||||
| -rw-r--r-- | src/lualib.js | 25 | ||||
| -rw-r--r-- | src/lundump.js | 3 | ||||
| -rw-r--r-- | src/lvm.js | 65 | 
20 files changed, 4069 insertions, 147 deletions
diff --git a/src/lapi.js b/src/lapi.js index d0c099f..36da7c6 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -1,18 +1,18 @@ -/* jshint esversion: 6 */  "use strict";  const assert    = require('assert'); +const ldebug    = require('./ldebug.js');  const ldo       = require('./ldo.js'); +const lfunc     = require('./lfunc.js'); +const llex      = require('./llex.js');  const lobject   = require('./lobject.js'); +const lstate    = require('./lstate.js');  const ltm       = require('./ltm.js'); -const lfunc     = require('./lfunc.js');  const lua       = require('./lua.js');  const luaconf   = require('./luaconf.js'); -const lstate    = require('./lstate.js'); -const lvm       = require('./lvm.js');  const lundump   = require('./lundump.js'); -const ldebug    = require('./ldebug.js'); +const lvm       = require('./lvm.js');  const MAXUPVAL  = lfunc.MAXUPVAL;  const CT        = lua.constant_types;  const TS        = lua.thread_status; @@ -138,7 +138,7 @@ const lua_settop = function(L, idx) {  const lua_pop = function(L, n) {      lua_settop(L, -n - 1); -} +};  const reverse = function(L, from, to) {      for (; from < to; from++, to--) { @@ -150,7 +150,7 @@ const reverse = function(L, from, to) {  /*  ** Let x = AB, where A is a prefix of length 'n'. Then, -** rotate x n == BA. But BA == (A^r . B^r)^r. +** rotate x n === BA. But BA === (A^r . B^r)^r.  */  const lua_rotate = function(L, idx, n) {      let t = L.stack[L.top - 1]; @@ -357,7 +357,7 @@ const lua_settable = function(L, idx) {  };  const lua_setfield = function(L, idx, k) { -    auxsetstr(L, index2addr(L, idx), k) +    auxsetstr(L, index2addr(L, idx), k);  };  const lua_seti = function(L, idx, n) { @@ -515,11 +515,11 @@ const lua_rawlen = function(L, idx) {  };  const lua_tointeger = function(L, idx) { -    return lvm.tointeger(index2addr(L, idx)) +    return lvm.tointeger(index2addr(L, idx));  };  const lua_tonumber = function(L, idx) { -    return lvm.tonumber(index2addr(L, idx)) +    return lvm.tonumber(index2addr(L, idx));  };  const lua_tothread = function(L, idx) { @@ -587,7 +587,7 @@ const f_call = function(L, ud) {  const lua_type = function(L, idx) {      let o = index2addr(L, idx); -    return o.ttnov(); // TODO: isvalid ? luaO_nilobject != nil tvalue ? +    return o.ttnov(); // TODO: isvalid ? luaO_nilobject !== nil tvalue ?  };  const lua_typename = function(L, t) { @@ -597,7 +597,7 @@ const lua_typename = function(L, t) {  const lua_isnoneornil = function(L, n) {      return lua_type(L, n) <= 0; -} +};  const lua_istable = function(L, idx) {      return index2addr(L, idx).ttistable(); @@ -626,13 +626,14 @@ const lua_rawequal = function(L, index1, index2) {  ** 'load' and 'call' functions (run Lua code)  */ -const lua_load = function(L, data, chunckname) { +// TODO: reader is ignored because we don't implement ZIO +const lua_load = function(L, reader, data, chunckname, mode) { +    let z = new llex.MBuffer(data);      if (!chunckname) chunckname = "?"; -     -    let status = ldo.luaD_protectedparser(L, data, chunckname); -    if (status === TS.LUA_OK) { +    let status = ldo.luaD_protectedparser(L, z, chunckname, mode); +    if (status === TS.LUA_OK) {  /* no errors? */          let f = L.stack[L.top - 1]; /* get newly created function */ -        if (f.nupvalues >= 1) { /* does it have an upvalue? */ +        if (f.nupvalues >= 1) {  /* does it have an upvalue? */              /* get global table from registry */              let reg = L.l_G.l_registry;              let gt = reg.value.get(lua.LUA_RIDX_GLOBALS - 1); @@ -640,7 +641,6 @@ const lua_load = function(L, data, chunckname) {              f.upvals[0].u.value = gt;          }      } -      return status;  }; @@ -663,7 +663,7 @@ const lua_callk = function(L, nargs, nresults, ctx, k) {          ldo.luaD_callnoyield(L, func, nresults);      } -    if (nresults == lua.LUA_MULTRET && L.ci.top < L.top) +    if (nresults === lua.LUA_MULTRET && L.ci.top < L.top)          L.ci.top = L.top;  }; @@ -714,7 +714,7 @@ const lua_pcallk = function(L, nargs, nresults, errfunc, ctx, k) {          status = TS.LUA_OK;      } -    if (nresults == lua.LUA_MULTRET && L.ci.top < L.top) +    if (nresults === lua.LUA_MULTRET && L.ci.top < L.top)          L.ci.top = L.top;      return status; diff --git a/src/lbaselib.js b/src/lbaselib.js index 012f5c3..77638d0 100644 --- a/src/lbaselib.js +++ b/src/lbaselib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */  "use strict";  const assert  = require('assert'); @@ -91,7 +90,7 @@ const luaB_rawset = function(L) {  const luaB_type = function(L) {      let t = lapi.lua_type(L, 1); -    lauxlib.luaL_argcheck(L, t != CT.LUA_TNONE, 1, "value expected"); +    lauxlib.luaL_argcheck(L, t !== CT.LUA_TNONE, 1, "value expected");      lapi.lua_pushstring(L, lapi.lua_typename(L, t));      return 1;  }; diff --git a/src/lcode.js b/src/lcode.js new file mode 100644 index 0000000..d8b0252 --- /dev/null +++ b/src/lcode.js @@ -0,0 +1,1266 @@ +"use strict"; + +const assert   = require('assert'); + +const llex     = require('./llex.js'); +const llimit   = require('./llimit.js'); +const lobject  = require('./lobject.js'); +const lopcode  = require('./lopcodes.js'); +const lparser  = require('./lparser.js'); +const ltm      = require('./ltm.js'); +const lua      = require('./lua.js'); +const lvm      = require('./lvm.js'); +const CT       = lua.CT; +const OpCodesI = lopcode.OpCodesI; +const TValue   = lobject.TValue; + +const luaO_arith = function(L, op, p1, p2, res) { +    switch (op) { +        case lvm.LUA_OPBAND: case lvm.LUA_OPBOR: case lvm.LUA_OPBXOR: +        case lvm.LUA_OPSHL: case lvm.LUA_OPSHR: +        case lvm.LUA_OPBNOT: {  /* operate only on integers */ +            let i1 = lvm.tointeger(p1); +            let i2 = lvm.tointeger(p2); +            if (i1 !== false && i2 !== false) { +                res.type  = CT.LUA_TNUMINT; +                res.value = lobject.intarith(L, op, i1, i2); +                return; +            } +            else break;  /* go to the end */ +        } +        case lvm.LUA_OPDIV: case lvm.LUA_OPPOW: {  /* operate only on floats */ +            let n1 = lvm.tonumber(p1); +            let n2 = lvm.tonumber(p2); +            if (n1 !== false && n2 !== false) { +                res.type  = CT.LUA_TNUMFLT; +                res.value = lobject.numarith(L, op, n1, n2); +                return; +            } +            else break;  /* go to the end */ +        } +        default: {  /* other operations */ +            let n1 = lvm.tonumber(p1); +            let n2 = lvm.tonumber(p2); +            if (p1.ttisinteger() && p2.ttisinteger()) { +                res.type  = CT.LUA_TNUMINT; +                res.value = lobject.intarith(L, op, p1.value, p2.value); +                return; +            } +            else if (n1 !== false && n2 !== false) { +                res.type  = CT.LUA_TNUMFLT; +                res.value = lobject.numarith(L, op, n1, n2); +                return; +            } +            else break;  /* go to the end */ +        } +    } +    /* could not perform raw operation; try metamethod */ +    assert(L !== null);  /* should not fail when folding (compile time) */ +    ltm.luaT_trybinTM(L, p1, p2, res, (op - lua.LUA_OPADD) + ltm.TMS.TM_ADD); +}; + +/* Maximum number of registers in a Lua function (must fit in 8 bits) */ +const MAXREGS = 255; + +/* +** Marks the end of a patch list. It is an invalid value both as an absolute +** address, and as a list link (would link an element to itself). +*/ +const NO_JUMP = -1; + +const BinOpr = { +    OPR_ADD:      0, +    OPR_SUB:      1, +    OPR_MUL:      2, +    OPR_MOD:      3, +    OPR_POW:      4, +    OPR_DIV:      5, +    OPR_IDIV:     6, +    OPR_BAND:     7, +    OPR_BOR:      8, +    OPR_BXOR:     9, +    OPR_SHL:      10, +    OPR_SHR:      11, +    OPR_CONCAT:   12, +    OPR_EQ:       13, +    OPR_LT:       14, +    OPR_LE:       15, +    OPR_NE:       16, +    OPR_GT:       17, +    OPR_GE:       18, +    OPR_AND:      19, +    OPR_OR:       21, +    OPR_NOBINOPR: 22 +}; + +const UnOpr = { +    OPR_MINUS:    0, +    OPR_BNOT:     1, +    OPR_NOT:      2, +    OPR_LEN:      3, +    OPR_NOUNOPR:  4 +}; + +const hasjumps = function(e) { +    return e.t !== e.f; +}; + +/* +** If expression is a numeric constant, fills 'v' with its value +** and returns true. Otherwise, returns false. +*/ +const tonumeral = function(e, v) { +    let ek = lparser.expkind; +    if (hasjumps(e)) +        return false;  /* not a numeral */ +    switch (e.k) { +        case ek.VKINT: +            if (v) { +                v.type = CT.LUA_TNUMINT; +                v.value = e.u.ival; +            } +            return true; +        case ek.VKFLT: +            if (v) { +                v.type = CT.LUA_TNUMFLT; +                v.value = e.u.nval; +            } +            return true; +        default: return false; +    } +}; + +/* +** Create a OP_LOADNIL instruction, but try to optimize: if the previous +** instruction is also OP_LOADNIL and ranges are compatible, adjust +** range of previous instruction instead of emitting a new one. (For +** instance, 'local a; local b' will generate a single opcode.) +*/ +const luaK_nil = function(fs, from, n) { +    let previous; +    let l = from + n - 1;  /* last register to set nil */ +    if (fs.pc > fs.lasttarget) {  /* no jumps to current position? */ +        previous = fs.f.code[fs.pc-1]; +        if (previous.opcode === OpCodesI.OP_LOADNIL) {  /* previous is LOADNIL? */ +            let pfrom = previous.A;  /* get previous range */ +            let pl = pfrom + previous.B; +            if ((pfrom <= from && from <= pl + 1) || +                    (from <= pfrom && pfrom <= l + 1)) {  /* can connect both? */ +                if (pfrom < from) from = pfrom;  /* from = min(from, pfrom) */ +                if (pl > l) l = pl;  /* l = max(l, pl) */ +                lopcode.SETARG_A(previous, from); +                lopcode.SETARG_B(previous, l - from); +                return; +            } +        }  /* else go through */ +    } +    luaK_codeABC(fs, OpCodesI.OP_LOADNIL, from, n - 1, 0);  /* else no optimization */ +}; + +const getinstruction = function(fs, e) { +    return fs.f.code[e.u.info]; +}; + +/* +** Gets the destination address of a jump instruction. Used to traverse +** a list of jumps. +*/ +const getjump = function(fs, pc) { +    let offset = fs.f.code[pc].sBx; +    if (offset === NO_JUMP)  /* point to itself represents end of list */ +        return NO_JUMP;  /* end of list */ +    else +        return pc + 1 + offset;  /* turn offset into absolute position */ +}; + +/* +** Fix jump instruction at position 'pc' to jump to 'dest'. +** (Jump addresses are relative in Lua) +*/ +const fixjump = function(fs, pc, dest) { +    let jmp = fs.f.code[pc]; +    let offset = dest - (pc + 1); +    assert(dest !== NO_JUMP); +    if (Math.abs(offset) > lopcode.MAXARG_sBx) +        llex.luaX_syntaxerror(fs.ls, "control structure too long"); +    lopcode.SETARG_sBx(jmp, offset); +}; + +/* +** Concatenate jump-list 'l2' into jump-list 'l1' +*/ +const luaK_concat = function(fs, l1, l2) { +    if (l2 === NO_JUMP) return;  /* nothing to concatenate? */ +    else if (l1 === NO_JUMP)  /* no original list? */ +        l1 = l2; +    else { +        let list = l1; +        let next = getjump(fs, list); +        while (next !== NO_JUMP) {  /* find last element */ +            list = next; +            next = getjump(fs, list); +        } +        fixjump(fs, list, l2); +    } + +    return l1; +}; + +/* +** Create a jump instruction and return its position, so its destination +** can be fixed later (with 'fixjump'). If there are jumps to +** this position (kept in 'jpc'), link them all together so that +** 'patchlistaux' will fix all them directly to the final destination. +*/ +const luaK_jump = function (fs) { +    let jpc = fs.jpc;  /* save list of jumps to here */ +    fs.jpc = NO_JUMP;  /* no more jumps to here */ +    let j = luaK_codeAsBx(fs, OpCodesI.OP_JMP, 0, NO_JUMP); +    j = luaK_concat(fs, j, jpc);  /* keep them on hold */ +    return j; +}; + +const luaK_jumpto = function(fs, t) { +    return luaK_patchlist(fs, luaK_jump(fs), t); +}; + +/* +** Code a 'return' instruction +*/ +const luaK_ret = function(fs, first, nret) { +    luaK_codeABC(fs, OpCodesI.OP_RETURN, first, nret + 1, 0); +}; + +/* +** Code a "conditional jump", that is, a test or comparison opcode +** followed by a jump. Return jump position. +*/ +const condjump = function(fs, op, A, B, C) { +   luaK_codeABC(fs, op, A, B, C); +   return luaK_jump(fs); +}; + +/* +** returns current 'pc' and marks it as a jump target (to avoid wrong +** optimizations with consecutive instructions not in the same basic block). +*/ +const luaK_getlabel = function(fs) { +    fs.lasttarget = fs.pc; +    return fs.pc; +}; + +/* +** Returns the position of the instruction "controlling" a given +** jump (that is, its condition), or the jump itself if it is +** unconditional. +*/ +const getjumpcontrol = function(fs, pc) { +    if (pc >= 1 && lopcode.testTMode(fs.f.code[pc - 1].opcode)) +        return fs.f.code[pc - 1]; +    else +        return fs.f.code[pc]; +}; + +/* +** Patch destination register for a TESTSET instruction. +** If instruction in position 'node' is not a TESTSET, return 0 ("fails"). +** Otherwise, if 'reg' is not 'NO_REG', set it as the destination +** register. Otherwise, change instruction to a simple 'TEST' (produces +** no register value) +*/ +const patchtestreg = function(fs, node, reg) { +    let i = getjumpcontrol(fs, node); +    if (i.opcode !== OpCodesI.OP_TESTSET) +        return false;  /* cannot patch other instructions */ +    if (reg !== lopcode.NO_REG && reg !== i.B) +        lopcode.SETARG_A(i, reg); +    else { +        /* no register to put value or register already has the value; +           change instruction to simple test */ +        i = lopcode.CREATE_ABC(OpCodesI.OP_TEST, i.B, 0, i.C); +    } +    return true; +}; + +/* +** Traverse a list of tests ensuring no one produces a value +*/ +const removevalues = function(fs, list) { +    for (; list !== NO_JUMP; list = getjump(fs, list)) +        patchtestreg(fs, list, lopcode.NO_REG); +}; + +/* +** Traverse a list of tests, patching their destination address and +** registers: tests producing values jump to 'vtarget' (and put their +** values in 'reg'), other tests jump to 'dtarget'. +*/ +const patchlistaux = function(fs, list, vtarget, reg, dtarget) { +    while (list !== NO_JUMP) { +        let next = getjump(fs, list); +        if (patchtestreg(fs, list, reg)) +            fixjump(fs, list, vtarget); +        else +            fixjump(fs, list, dtarget);  /* jump to default target */ +        list = next; +    } +}; + +/* +** Ensure all pending jumps to current position are fixed (jumping +** to current position with no values) and reset list of pending +** jumps +*/ +const dischargejpc = function(fs) { +    patchlistaux(fs, fs.jpc, fs.pc, lopcode.NO_REG, fs.pc); +    fs.jpc = NO_JUMP; +}; + +/* +** Add elements in 'list' to list of pending jumps to "here" +** (current position) +*/ +const luaK_patchtohere = function(fs, list) { +    luaK_getlabel(fs);  /* mark "here" as a jump target */ +    fs.jpc = luaK_concat(fs, fs.jpc, list); +}; + +/* +** Path all jumps in 'list' to jump to 'target'. +** (The assert means that we cannot fix a jump to a forward address +** because we only know addresses once code is generated.) +*/ +const luaK_patchlist = function(fs, list, target) { +    if (target === fs.pc)  /* 'target' is current position? */ +        luaK_patchtohere(fs, list);  /* add list to pending jumps */ +    else { +        assert(target < fs.pc); +        patchlistaux(fs, list, target, lopcode.NO_REG, target); +    } +}; + +/* +** Path all jumps in 'list' to close upvalues up to given 'level' +** (The assertion checks that jumps either were closing nothing +** or were closing higher levels, from inner blocks.) +*/ +const luaK_patchclose = function(fs, list, level) { +    level++;  /* argument is +1 to reserve 0 as non-op */ +    for (; list !== NO_JUMP; list = getjump(fs, list)) { +        let ins = fs.f.code[list]; +        assert(ins.opcode === OpCodesI.OP_JMP && (ins.A === 0 || ins.A >= level)); +        lopcode.SETARG_A(ins, level); +    } +}; + +/* +** Emit instruction 'i', checking for array sizes and saving also its +** line information. Return 'i' position. +*/ +const luaK_code = function(fs, i) { +    let f = fs.f; +    dischargejpc(fs);  /* 'pc' will change */ +    /* put new instruction in code array */ +    f.code[fs.pc] = i; +    f.lineinfo[fs.pc] = fs.ls.lastline; +    return fs.pc++; +}; + +/* +** Format and emit an 'iABC' instruction. (Assertions check consistency +** of parameters versus opcode.) +*/ +const luaK_codeABC = function(fs, o, a, b, c) { +    assert(lopcode.getOpMode(o) === lopcode.iABC); +    assert(lopcode.getBMode(o) !== lopcode.OpArgN || b === 0); +    assert(lopcode.getCMode(o) !== lopcode.OpArgN || c === 0); +    assert(a <= lopcode.MAXARG_A && b <= lopcode.MAXARG_B && c <= lopcode.MAXARG_C); +    return luaK_code(fs, lopcode.CREATE_ABC(o, a, b, c)); +}; + +/* +** Format and emit an 'iABx' instruction. +*/ +const luaK_codeABx = function(fs, o, a, bc) { +    assert(lopcode.getOpMode(o) === lopcode.iABx || lopcode.getOpMode(o) === lopcode.iAsBx); +    assert(lopcode.getCMode(o) === lopcode.OpArgN); +    assert(a <= lopcode.MAXARG_A && bc <= lopcode.MAXARG_Bx); +    return luaK_code(fs, lopcode.CREATE_ABx(o, a, bc)); +}; + +const luaK_codeAsBx = function(fs,o,A,sBx) { +    return luaK_codeABx(fs, o, A, (sBx) + lopcode.MAXARG_sBx); +}; + +/* +** Emit an "extra argument" instruction (format 'iAx') +*/ +const codeextraarg = function(fs, a) { +    assert(a <= lopcode.MAXARG_Ax); +    return luaK_code(fs, lopcode.CREATE_Ax(OpCodesI.OP_EXTRAARG, a)); +}; + +/* +** Emit a "load constant" instruction, using either 'OP_LOADK' +** (if constant index 'k' fits in 18 bits) or an 'OP_LOADKX' +** instruction with "extra argument". +*/ +const luaK_codek = function(fs, reg, k) { +    if (k <= lopcode.MAXARG_Bx) +        return luaK_codeABx(fs, OpCodesI.OP_LOADK, reg, k); +    else { +        let p = luaK_codeABx(fs, OpCodesI.OP_LOADKX, reg, 0); +        codeextraarg(fs, k); +        return p; +    } +}; + +/* +** Check register-stack level, keeping track of its maximum size +** in field 'maxstacksize' +*/ +const luaK_checkstack = function(fs, n) { +    let newstack = fs.freereg + n; +    if (newstack > fs.f.maxstacksize) { +        if (newstack >= MAXREGS) +            llex.luaX_syntaxerror(fs.ls, "function or expression needs to many registers"); +        fs.f.maxstacksize = newstack; +    } +}; + +/* +** Reserve 'n' registers in register stack +*/ +const luaK_reserveregs = function(fs, n) { +   luaK_checkstack(fs, n); +   fs.freereg += n; +}; + +/* +** Free register 'reg', if it is neither a constant index nor +** a local variable. +*/ +const freereg = function(fs, reg) { +    if (!lopcode.ISK(reg) && reg >= fs.nactvar) { +        fs.freereg--; +        assert(reg === fs.freereg); +    } +}; + +/* +** Free register used by expression 'e' (if any) +*/ +const freeexp = function(fs, e) { +    if (e.k === lparser.expkind.VNONRELOC) +        freereg(fs, e.u.info); +}; + +/* +** Free registers used by expressions 'e1' and 'e2' (if any) in proper +** order. +*/ +const freeexps = function(fs, e1, e2) { +    let r1 = (e1.k === lparser.expkind.VNONRELOC) ? e1.u.info : -1; +    let r2 = (e2.k === lparser.expkind.VNONRELOC) ? e2.u.info : -1; +    if (r1 > r2) { +        freereg(fs, r1); +        freereg(fs, r2); +    } +    else { +        freereg(fs, r2); +        freereg(fs, r1); +    } +}; + + +/* +** Add constant 'v' to prototype's list of constants (field 'k'). +** Use scanner's table to cache position of constants in constant list +** and try to reuse constants. Because some values should not be used +** as keys (nil cannot be a key, integer keys can collapse with float +** keys), the caller must provide a useful 'key' for indexing the cache. +*/ +const addk = function(fs, key, v) { +    let f = fs.f; +    let idx = fs.ls.h.__index(fs.ls.h, key);  /* index scanner table */ +    if (idx && !idx.ttisnil()) {  /* is there an index there? */ +        /* correct value? (warning: must distinguish floats from integers!) */ +        if (f.k[idx].ttype() === v.ttype() && f.k[idx].value === v.value) +            return idx;  /* reuse index */ +    } +    /* constant not found; create a new entry */ +    let k = fs.nk; +    fs.ls.h.__newindex(fs.ls.h, key, k); +    f.k[k] = v; +    fs.nk++; +    return k; +}; + +/* +** Add a string to list of constants and return its index. +*/ +const luaK_stringK = function(fs, s) { +    return addk(fs, s, s);  /* use string itself as key */ +}; + + +/* +** Add an integer to list of constants and return its index. +** Integers use userdata as keys to avoid collision with floats with +** same value; conversion to 'void*' is used only for hashing, so there +** are no "precision" problems. +*/ +const luaK_intK = function(fs, n) { +    let k = new TValue(CT.LUA_TLNGSTR, `${n.value}`); +    let o = new TValue(CT.LUA_TNUMINT, n.value); +    return addk(fs, k, o); +}; + +/* +** Add a float to list of constants and return its index. +*/ +const luaK_numberK = function(fs, r) { +    return addk(fs, r, r);  /* use number itself as key */ +}; + + +/* +** Add a boolean to list of constants and return its index. +*/ +const boolK = function(fs, b) { +    return addk(fs, b, b);  /* use boolean itself as key */ +}; + + +/* +** Add nil to list of constants and return its index. +*/ +const nilK = function(fs) { +    return addk(fs, new TValue(CT.LUA_TLNGSTR, `null`), new TValue(CT.LUA_TNIL, null)); +}; + +/* +** Fix an expression to return the number of results 'nresults'. +** Either 'e' is a multi-ret expression (function call or vararg) +** or 'nresults' is LUA_MULTRET (as any expression can satisfy that). +*/ +const luaK_setreturns = function(fs, e, nresults) { +    let ek = lparser.expkind; +    if (e.k === ek.VCALL) {  /* expression is an open function call? */ +        lopcode.SETARG_C(getinstruction(fs, e), nresults + 1); +    } +    else if (e.k === ek.VVARARG) { +        let pc = getinstruction(fs, e); +        lopcode.SETARG_B(pc, nresults + 1); +        lopcode.SETARG_A(pc, fs.freereg); +        luaK_reserveregs(fs, 1); +    } +    else assert(nresults === lua.LUA_MULTRET); +}; + +const luaK_setmultret = function(fs, e) { +    luaK_setreturns(fs, e, lua.LUA_MULTRET); +}; + +/* +** Fix an expression to return one result. +** If expression is not a multi-ret expression (function call or +** vararg), it already returns one result, so nothing needs to be done. +** Function calls become VNONRELOC expressions (as its result comes +** fixed in the base register of the call), while vararg expressions +** become VRELOCABLE (as OP_VARARG puts its results where it wants). +** (Calls are created returning one result, so that does not need +** to be fixed.) +*/ +const luaK_setoneret = function(fs, e) { +    let ek = lparser.expkind; +    if (e.k === ek.VCALL) {  /* expression is an open function call? */ +        /* already returns 1 value */ +        assert(getinstruction(fs, e).C === 2); +        e.k = ek.VNONRELOC;  /* result has fixed position */ +        e.u.info = getinstruction(fs, e).A; +    } else if (e.k === ek.VVARARG) { +        lopcode.SETARG_B(getinstruction(fs, e), 2); +        e.k = ek.VRELOCABLE;  /* can relocate its simple result */ +    } +}; + +/* +** Ensure that expression 'e' is not a variable. +*/ +const luaK_dischargevars = function(fs, e) { +    let ek = lparser.expkind; + +    switch (e.k) { +        case ek.VLOCAL: {  /* already in a register */ +            e.k =  ek.VNONRELOC;  /* becomes a non-relocatable value */ +            break; +        } +        case ek.VUPVAL: {  /* move value to some (pending) register */ +            e.u.info = luaK_codeABC(fs, OpCodesI.OP_GETUPVAL, 0, e.u.info, 0); +            e.k = ek.VRELOCABLE; +            break; +        } +        case ek.VINDEXED: { +            let op; +            freereg(fs, e.u.ind.idx); +            if (e.u.ind.vt === ek.VLOCAL) {  /* is 't' in a register? */ +                freereg(fs, e.u.ind.t); +                op = OpCodesI.OP_GETTABLE; +            } else { +                assert(e.u.ind.vt === ek.VUPVAL); +                op = OpCodesI.OP_GETTABUP;  /* 't' is in an upvalue */ +            } +            e.u.info = luaK_codeABC(fs, op, 0, e.u.ind.t, e.u.ind.idx); +            e.k = OpCodesI.VRELOCABLE; +            break; +        } +        case ek.VVARARG: case ek.VCALL: { +            luaK_setoneret(fs, e); +            break; +        } +        default: break;  /* there is one value available (somewhere) */ +    } +}; + +const code_loadbool = function(fs, A, b, jump) { +    luaK_getlabel(fs);  /* those instructions may be jump targets */ +    return luaK_codeABC(fs, OpCodesI.OP_LOADBOOL, A, b, jump); +}; + +/* +** Ensures expression value is in register 'reg' (and therefore +** 'e' will become a non-relocatable expression). +*/ +const discharge2reg = function(fs, e, reg) { +    let ek = lparser.expkind; +    luaK_dischargevars(fs, e); +    switch (e.k) { +        case ek.VNIL: { +            luaK_nil(fs, reg, 1); +            break; +        } +        case ek.VFALSE: case ek.VTRUE: { +            luaK_codeABC(fs, OpCodesI.OP_LOADBOOL, reg, e.k === ek.VTRUE, 0); +            break; +        } +        case ek.VK: { +            luaK_codek(fs, reg, e.u.info); +            break; +        } +        case ek.VKFLT: { +            luaK_codek(fs, reg, luaK_numberK(fs, e.u.nval)); +            break; +        } +        case ek.VKINT: { +            luaK_codek(fs, reg, luaK_intK(fs, e.u.ival)); +            break; +        } +        case ek.VRELOCABLE: { +            let pc = getinstruction(fs, e); +            lopcode.SETARG_A(pc, reg);  /* instruction will put result in 'reg' */ +            break; +        } +        case ek.VNONRELOC: { +            if (reg !== e.u.info) +                luaK_codeABC(fs, OpCodesI.OP_MOVE, reg, e.u.info, 0); +            break; +        } +        default: { +            assert(e.k === ek.VJMP); +            return;  /* nothing to do... */ +        } +    } +    e.u.info = reg; +    e.k = ek.VNONRELOC; +}; + +/* +** Ensures expression value is in any register. +*/ +const discharge2anyreg = function(fs, e) { +    if (e.k !== lparser.expkind.VNONRELOC) {  /* no fixed register yet? */ +        luaK_reserveregs(fs, 1);  /* get a register */ +        discharge2reg(fs, e, fs.freereg-1);  /* put value there */ +    } +}; + +/* +** check whether list has any jump that do not produce a value +** or produce an inverted value +*/ +const need_value = function(fs, list) { +    for (; list !== NO_JUMP; list = getjump(fs, list)) { +        let i = getjumpcontrol(fs, list); +        if (i.opcode !== OpCodesI.OP_TESTSET) return true; +    } +    return false;  /* not found */ +}; + +/* +** Ensures final expression result (including results from its jump +** lists) is in register 'reg'. +** If expression has jumps, need to patch these jumps either to +** its final position or to "load" instructions (for those tests +** that do not produce values). +*/ +const exp2reg = function(fs, e, reg) { +    let ek = lparser.expkind; +    discharge2reg(fs, e, reg); +    if (e.k === ek.VJMP)  /* expression itself is a test? */ +        e.t = luaK_concat(fs, e.t, e.u.info);  /* put this jump in 't' list */ +    if (hasjumps(e)) { +        let final;  /* position after whole expression */ +        let p_f = NO_JUMP;  /* position of an eventual LOAD false */ +        let p_t = NO_JUMP;  /* position of an eventual LOAD true */ +        if (need_value(fs, e.t) || need_value(fs, e.f)) { +            let fj = (e.k === ek.VJMP) ? NO_JUMP : luaK_jump(fs); +            p_f = code_loadbool(fs, reg, 0, 1); +            p_t = code_loadbool(fs, reg, 1, 0); +            luaK_patchtohere(fs, fj); +        } +        final = luaK_getlabel(fs); +        patchlistaux(fs, e.f, final, reg, p_f); +        patchlistaux(fs, e.t, final, reg, p_t); +    } +    e.f = e.t = NO_JUMP; +    e.u.info = reg; +    e.k = ek.VNONRELOC; +}; + +/* +** Ensures final expression result (including results from its jump +** lists) is in next available register. +*/ +const luaK_exp2nextreg = function(fs, e) { +    luaK_dischargevars(fs, e); +    freeexp(fs, e); +    luaK_reserveregs(fs, 1); +    exp2reg(fs, e, fs.freereg - 1); +}; + + +/* +** Ensures final expression result (including results from its jump +** lists) is in some (any) register and return that register. +*/ +const luaK_exp2anyreg = function(fs, e) { +    luaK_dischargevars(fs, e); +    if (e.k === lparser.expkind.VNONRELOC) {  /* expression already has a register? */ +        if (!hasjumps(e))  /* no jumps? */ +            return e.u.info;  /* result is already in a register */ +        if (e.u.info >= fs.nactvar) {  /* reg. is not a local? */ +            exp2reg(fs, e, e.u.info);  /* put final result in it */ +            return e.u.info; +        } +    } +    luaK_exp2nextreg(fs, e);  /* otherwise, use next available register */ +    return e.u.info; +}; + +/* +** Ensures final expression result is either in a register or in an +** upvalue. +*/ +const luaK_exp2anyregup = function(fs, e) { +    if (e.k !== lparser.expkind.VUPVAL || hasjumps(e)) +        luaK_exp2anyreg(fs, e); +}; + +/* +** Ensures final expression result is either in a register or it is +** a constant. +*/ +const luaK_exp2val = function(fs, e) { +    if (hasjumps(e)) +        luaK_exp2anyreg(fs, e); +    else +        luaK_dischargevars(fs, e); +}; + +/* +** Ensures final expression result is in a valid R/K index +** (that is, it is either in a register or in 'k' with an index +** in the range of R/K indices). +** Returns R/K index. +*/ +const luaK_exp2RK = function(fs, e) { +    let ek = lparser.expkind; +    let vk = false; +    luaK_exp2val(fs, e); +    switch (e.k) {  /* move constants to 'k' */ +        case ek.VTRUE: e.u.info = boolK(fs, 1); vk = true; break; +        case ek.VFALSE: e.u.info = boolK(fs, 0); vk = true; break; +        case ek.VNIL: e.u.info = nilK(fs); vk = true; break; +        case ek.VKINT: e.u.info = luaK_intK(fs, e.u.ival); vk = true; break; +        case ek.VKFLT: e.u.info = luaK_numberK(fs, e.u.nval); vk = true; break; +        case ek.VK: vk = true; break; +        default: break; +    } + +    if (vk) { +        e.k = ek.VK; +        if (e.u.info <= lopcode.MAXINDEXRK)  /* constant fits in 'argC'? */ +            return lopcode.RKASK(e.u.info); +    } + +    /* not a constant in the right range: put it in a register */ +    return luaK_exp2anyreg(fs, e); +}; + +/* +** Generate code to store result of expression 'ex' into variable 'var'. +*/ +const luaK_storevar = function(fs, vr, ex) { +    let ek = lparser.expkind; +    switch (vr.k) { +        case ek.VLOCAL: { +            freeexp(fs, ex); +            exp2reg(fs, ex, vr.u.info);  /* compute 'ex' into proper place */ +            return; +        } +        case ek.VUPVAL: { +            let e = luaK_exp2anyreg(fs, ex); +            luaK_codeABC(fs, OpCodesI.OP_SETUPVAL, e, vr.u.info, 0); +          break; +        } +        case ek.VINDEXED: { +            let op = (vr.u.ind.vt === ek.VLOCAL) ? OpCodesI.OP_SETTABLE : OpCodesI.OP_SETTABUP; +            let e = luaK_exp2RK(fs, ex); +            luaK_codeABC(fs, op, vr.u.ind.t, vr.u.ind.idx, e); +            break; +        } +    } +    freeexp(fs, ex); +}; + + +/* +** Emit SELF instruction (convert expression 'e' into 'e:key(e,'). +*/ +const luaK_self = function(fs, e, key) { +    luaK_exp2anyreg(fs, e); +    let ereg = e.u.info;  /* register where 'e' was placed */ +    freeexp(fs, e); +    e.u.info = fs.freereg;  /* base register for op_self */ +    e.k = lparser.expkind.VNONRELOC;  /* self expression has a fixed register */ +    luaK_reserveregs(fs, 2);  /* function and 'self' produced by op_self */ +    luaK_codeABC(fs, OpCodesI.OP_SELF, e.u.info, ereg, luaK_exp2RK(fs, key)); +    freeexp(fs, key); +}; + +/* +** Negate condition 'e' (where 'e' is a comparison). +*/ +const negatecondition = function(fs, e) { +    let pc = getjumpcontrol(fs, e.u.info); +    assert(lopcode.testTMode(pc.opcode) && pc.opcode !== OpCodesI.OP_TESTSET && pc.opcode !== OpCodesI.OP_TEST); +    lopcode.SETARG_A(pc, !(pc.A)); +}; + +/* +** Emit instruction to jump if 'e' is 'cond' (that is, if 'cond' +** is true, code will jump if 'e' is true.) Return jump position. +** Optimize when 'e' is 'not' something, inverting the condition +** and removing the 'not'. +*/ +const jumponcond = function(fs, e, cond) { +    if (e.k === lparser.expkind.VRELOCABLE) { +        let ie = getinstruction(fs, e); +        if (ie.opcode === OpCodesI.OP_NOT) { +            fs.pc--;  /* remove previous OP_NOT */ +            return condjump(fs, OpCodesI.OP_TEST, ie.B, 0, !cond); +        } +        /* else go through */ +    } +    discharge2anyreg(fs, e); +    freeexp(fs, e); +    return condjump(fs, OpCodesI.OP_TESTSET, lopcode.NO_REG, e.u.info, cond); +}; + +/* +** Emit code to go through if 'e' is true, jump otherwise. +*/ +const luaK_goiftrue = function(fs, e) { +    let ek = lparser.expkind; +    let pc;  /* pc of new jump */ +    luaK_dischargevars(fs, e); +    switch (e.k) { +        case ek.VJMP: {  /* condition? */ +           negatecondition(fs, e);  /* jump when it is false */ +           pc = e.u.info;  /* save jump position */ +           break; +        } +        case ek.VK: case ek.VKFLT: case ek.VKINT: case ek.VTRUE: { +           pc = NO_JUMP;  /* always true; do nothing */ +           break; +        } +        default: { +           pc = jumponcond(fs, e, 0);  /* jump when false */ +           break; +        } +    } +    e.f = luaK_concat(fs, e.f, pc);  /* insert new jump in false list */ +    luaK_patchtohere(fs, e.t);  /* true list jumps to here (to go through) */ +    e.t = NO_JUMP; +}; + +/* +** Emit code to go through if 'e' is false, jump otherwise. +*/ +const luaK_goiffalse = function(fs, e) { +    let ek = lparser.expkind; +    let pc;  /* pc of new jump */ +    luaK_dischargevars(fs, e); +    switch (e.k) { +        case ek.VJMP: { +            pc = e.u.info;  /* already jump if true */ +            break; +        } +        case ek.VNIL: case ek.VFALSE: { +            pc = NO_JUMP;  /* always false; do nothing */ +            break; +        } +        default: { +            pc = jumponcond(fs, e, 1);  /* jump if true */ +            break; +        } +    } +    e.t = luaK_concat(fs, e.t, pc);  /* insert new jump in 't' list */ +    luaK_patchtohere(fs, e.f);  /* false list jumps to here (to go through) */ +    e.f = NO_JUMP; +}; + +/* +** Code 'not e', doing constant folding. +*/ +const codenot = function(fs, e) { +    let ek = lparser.expkind; +    luaK_dischargevars(fs, e); +    switch (e.k) { +        case ek.VNIL: case ek.VFALSE: { +            e.k = ek.VTRUE;  /* true === not nil === not false */ +            break; +        } +        case ek.VK: case ek.VKFLT: case ek.VKINT: case ek.VTRUE: { +            e.k = ek.VFALSE;  /* false === not "x" === not 0.5 === not 1 === not true */ +            break; +        } +        case ek.VJMP: { +            negatecondition(fs, e); +            break; +        } +        case ek.VRELOCABLE: +        case ek.VNONRELOC: { +            discharge2anyreg(fs, e); +            freeexp(fs, e); +            e.u.info = luaK_codeABC(fs, OpCodesI.OP_NOT, 0, e.u.info, 0); +            e.k = ek.VRELOCABLE; +            break; +        } +    } +    /* interchange true and false lists */ +    { let temp = e.f; e.f = e.t; e.t = temp; } +    removevalues(fs, e.f);  /* values are useless when negated */ +    removevalues(fs, e.t); +}; + +/* +** Create expression 't[k]'. 't' must have its final result already in a +** register or upvalue. +*/ +const luaK_indexed = function(fs, t, k) { +    let ek = lparser.expkind; +    assert(!hasjumps(t) && (lparser.vkisinreg(t.k) || t.k === ek.VUPVAL)); +    t.u.ind.t = t.u.info;  /* register or upvalue index */ +    t.u.ind.idx = luaK_exp2RK(fs, k);  /* R/K index for key */ +    t.u.ind.vt = (t.k === ek.VUPVAL) ? ek.VUPVAL : ek.VLOCAL; +    t.k = ek.VINDEXED; +}; + +/* +** Return false if folding can raise an error. +** Bitwise operations need operands convertible to integers; division +** operations cannot have 0 as divisor. +*/ +const validop = function(op, v1, v2) { +    switch (op) { +        case lua.LUA_OPBAND: case lua.LUA_OPBOR: case lua.LUA_OPBXOR: +        case lua.LUA_OPSHL: case lua.LUA_OPSHR: case lua.LUA_OPBNOT: {  /* conversion errors */ +            return (lvm.tointeger(v1) && lvm.tointeger(v2)); +        } +        case lua.LUA_OPDIV: case lua.LUA_OPIDIV: case lua.LUA_OPMOD:  /* division by 0 */ +            return (v2.value !== 0); +        default: return 1;  /* everything else is valid */ +    } +}; + +/* +** Try to "constant-fold" an operation; return 1 iff successful. +** (In this case, 'e1' has the final result.) +*/ +const constfolding = function(fs, op, e1, e2) { +    let ek = lparser.expkind; +    let v1 = new TValue(); +    let v2 = new TValue(); +    let res = new TValue(); +    if (!tonumeral(e1, v1) || !tonumeral(e2, v2) || !validop(op, v1, v2)) +        return 0;  /* non-numeric operands or not safe to fold */ +    luaO_arith(fs.ls.L, op, v1, v2, res);  /* does operation */ +    if (res.ttisinteger()) { +        e1.k = ek.VKINT; +        e1.u.ival = res.value; +    } +    else {  /* folds neither NaN nor 0.0 (to avoid problems with -0.0) */ +        let n = res.value; +        if (isNaN(n) || n === 0) +            return false; +        e1.k = ek.VKFLT; +        e1.u.nval = n; +    } +    return true; +}; + +/* +** Emit code for unary expressions that "produce values" +** (everything but 'not'). +** Expression to produce final result will be encoded in 'e'. +*/ +const codeunexpval = function(fs, op, e, line) { +   let r = luaK_exp2anyreg(fs, e);  /* opcodes operate only on registers */ +   freeexp(fs, e); +   e.u.info = luaK_codeABC(fs, op, 0, r, 0);  /* generate opcode */ +   e.k = lparser.expkind.VRELOCABLE;  /* all those operations are relocatable */ +   luaK_fixline(fs, line); +}; + +/* +** Emit code for binary expressions that "produce values" +** (everything but logical operators 'and'/'or' and comparison +** operators). +** Expression to produce final result will be encoded in 'e1'. +** Because 'luaK_exp2RK' can free registers, its calls must be +** in "stack order" (that is, first on 'e2', which may have more +** recent registers to be released). +*/ +const codebinexpval = function(fs, op, e1, e2, line) { +    let rk2 = luaK_exp2RK(fs, e2);  /* both operands are "RK" */ +    let rk1 = luaK_exp2RK(fs, e1); +    freeexps(fs, e1, e2); +    e1.u.info = luaK_codeABC(fs, op, 0, rk1, rk2);  /* generate opcode */ +    e1.k = lparser.expkind.VRELOCABLE;  /* all those operations are relocatable */ +    luaK_fixline(fs, line); +}; + + +/* +** Emit code for comparisons. +** 'e1' was already put in R/K form by 'luaK_infix'. +*/ +const codecomp = function(fs, opr, e1, e2) { +    let ek = lparser.expkind; +    let rk1 = (e1.k === ek.VK) ? lopcode.RKASK(e1.u.info) : llimit.check_exp(e1.k === ek.VNONRELOC, e1.u.info); +    let rk2 = luaK_exp2RK(fs, e2); +    freeexps(fs, e1, e2); +    switch (opr) { +        case BinOpr.OPR_NE: {  /* '(a ~= b)' ==> 'not (a === b)' */ +            e1.u.info = condjump(fs, OpCodesI.OP_EQ, 0, rk1, rk2); +            break; +        } +        case BinOpr.OPR_GT: case BinOpr.OPR_GE: { +            /* '(a > b)' ==> '(b < a)';  '(a >= b)' ==> '(b <= a)' */ +            let op = (opr - BinOpr.OPR_NE) + OpCodesI.OP_EQ; +            e1.u.info = condjump(fs, op, 1, rk2, rk1);  /* invert operands */ +            break; +        } +        default: {  /* '==', '<', '<=' use their own opcodes */ +            let op = (opr - BinOpr.OPR_EQ) + OpCodesI.OP_EQ; +            e1.u.info = condjump(fs, op, 1, rk1, rk2); +            break; +        } +    } +    e1.k = ek.VJMP; +}; + +/* +** Apply prefix operation 'op' to expression 'e'. +*/ +const luaK_prefix = function(fs, op, e, line) { +    let ef = new lparser.expdesc(); +    ef.k = lparser.expkind.VKINT; +    ef.u.ival = e.u.nval = e.u.info = 0; +    e.t = NO_JUMP; +    e.f = NO_JUMP; +    switch (op) { +        case UnOpr.OPR_MINUS: case UnOpr.OPR_BNOT:  /* use 'ef' as fake 2nd operand */ +            if (constfolding(fs, op + lua.LUA_OPUNM, e, ef)) +                break; +            /* FALLTHROUGH */ +        case UnOpr.OPR_LEN: +            codeunexpval(fs, op + UnOpr.OP_UNM, e, line); +            break; +        case UnOpr.OPR_NOT: codenot(fs, e); break; +    } +}; + +/* +** Process 1st operand 'v' of binary operation 'op' before reading +** 2nd operand. +*/ +const luaK_infix = function(fs, op, v) { +    switch (op) { +        case BinOpr.OPR_AND: { +            luaK_goiftrue(fs, v);  /* go ahead only if 'v' is true */ +            break; +        } +        case BinOpr.OPR_OR: { +            luaK_goiffalse(fs, v);  /* go ahead only if 'v' is false */ +            break; +        } +        case BinOpr.OPR_CONCAT: { +            luaK_exp2nextreg(fs, v);  /* operand must be on the 'stack' */ +            break; +        } +        case BinOpr.OPR_ADD: case BinOpr.OPR_SUB: +        case BinOpr.OPR_MUL: case BinOpr.OPR_DIV: case BinOpr.OPR_IDIV: +        case BinOpr.OPR_MOD: case BinOpr.OPR_POW: +        case BinOpr.OPR_BAND: case BinOpr.OPR_BOR: case BinOpr.OPR_BXOR: +        case BinOpr.OPR_SHL: case BinOpr.OPR_SHR: { +            if (!tonumeral(v, null)) +                luaK_exp2RK(fs, v); +            /* else keep numeral, which may be folded with 2nd operand */ +            break; +        } +        default: { +            luaK_exp2RK(fs, v); +            break; +        } +    } +}; + +/* +** Finalize code for binary operation, after reading 2nd operand. +** For '(a .. b .. c)' (which is '(a .. (b .. c))', because +** concatenation is right associative), merge second CONCAT into first +** one. +*/ +const luaK_posfix = function(fs, op, e1, e2, line) { +    let ek = lparser.expkind; +    switch (op) { +        case BinOpr.OPR_AND: { +            assert(e1.t === NO_JUMP);  /* list closed by 'luK_infix' */ +            luaK_dischargevars(fs, e2); +            e2.f = luaK_concat(fs, e2.f, e1.f); +            // WARN: *e1 = *e2; +            break; +        } +        case BinOpr.OPR_OR: { +            assert(e1.f === NO_JUMP);  /* list closed by 'luK_infix' */ +            luaK_dischargevars(fs, e2); +            e2.t = luaK_concat(fs, e2.t, e1.t); +            // WARN: *e1 = *e2; +            break; +        } +        case BinOpr.OPR_CONCAT: { +            let ins = getinstruction(fs, e2); +            luaK_exp2val(fs, e2); +            if (e2.k === ek.VRELOCABLE && ins.opcode === OpCodesI.OP_CONCAT) { +                assert(e1.u.info === ins.B - 1); +                freeexp(fs, e1); +                lopcode.SETARG_B(ins, e1.u.info); +                e1.k = ek.VRELOCABLE; e1.u.info = e2.u.info; +            } +            else { +                luaK_exp2nextreg(fs, e2);  /* operand must be on the 'stack' */ +                codebinexpval(fs, OpCodesI.OP_CONCAT, e1, e2, line); +            } +            break; +        } +        case BinOpr.OPR_ADD: case BinOpr.OPR_SUB: case BinOpr.OPR_MUL: case BinOpr.OPR_DIV: +        case BinOpr.OPR_IDIV: case BinOpr.OPR_MOD: case BinOpr.OPR_POW: +        case BinOpr.OPR_BAND: case BinOpr.OPR_BOR: case BinOpr.OPR_BXOR: +        case BinOpr.OPR_SHL: case BinOpr.OPR_SHR: { +            if (!constfolding(fs, op + lua.LUA_OPADD, e1, e2)) +                codebinexpval(fs, op + OpCodesI.OP_ADD, e1, e2, line); +            break; +        } +        case BinOpr.OPR_EQ: case BinOpr.OPR_LT: case BinOpr.OPR_LE: +        case BinOpr.OPR_NE: case BinOpr.OPR_GT: case BinOpr.OPR_GE: { +            codecomp(fs, op, e1, e2); +            break; +        } +    } +}; + +/* +** Change line information associated with current position. +*/ +const luaK_fixline = function(fs, line) { +    fs.f.lineinfo[fs.pc - 1] = line; +}; + +/* +** Emit a SETLIST instruction. +** 'base' is register that keeps table; +** 'nelems' is #table plus those to be stored now; +** 'tostore' is number of values (in registers 'base + 1',...) to add to +** table (or LUA_MULTRET to add up to stack top). +*/ +const luaK_setlist = function(fs, base, nelems, tostore) { +    let c =  (nelems - 1)/lopcode.LFIELDS_PER_FLUSH + 1; +    let b = (tostore === lua.LUA_MULTRET) ? 0 : tostore; +    assert(tostore !== 0 && tostore <= lopcode.LFIELDS_PER_FLUSH); +    if (c <= lopcode.MAXARG_C) +        luaK_codeABC(fs, OpCodesI.OP_SETLIST, base, b, c); +    else if (c <= lopcode.MAXARG_Ax) { +        luaK_codeABC(fs, OpCodesI.OP_SETLIST, base, b, 0); +        codeextraarg(fs, c); +    } +    else +        llex.luaX_syntaxerror(fs.ls, "constructor too long"); +    fs.freereg = base + 1;  /* free registers with list values */ +}; + + +module.exports.BinOpr             = BinOpr; +module.exports.NO_JUMP            = NO_JUMP; +module.exports.UnOpr              = UnOpr; +module.exports.getinstruction     = getinstruction; +module.exports.luaK_checkstack    = luaK_checkstack; +module.exports.luaK_code          = luaK_code; +module.exports.luaK_codeABC       = luaK_codeABC; +module.exports.luaK_codeABx       = luaK_codeABx; +module.exports.luaK_codeAsBx      = luaK_codeAsBx; +module.exports.luaK_codek         = luaK_codek; +module.exports.luaK_concat        = luaK_concat; +module.exports.luaK_dischargevars = luaK_dischargevars; +module.exports.luaK_exp2RK        = luaK_exp2RK; +module.exports.luaK_exp2anyreg    = luaK_exp2anyreg; +module.exports.luaK_exp2anyregup  = luaK_exp2anyregup; +module.exports.luaK_exp2nextreg   = luaK_exp2nextreg; +module.exports.luaK_exp2val       = luaK_exp2val; +module.exports.luaK_fixline       = luaK_fixline; +module.exports.luaK_getlabel      = luaK_getlabel; +module.exports.luaK_goiffalse     = luaK_goiffalse; +module.exports.luaK_goiftrue      = luaK_goiftrue; +module.exports.luaK_indexed       = luaK_indexed; +module.exports.luaK_infix         = luaK_infix; +module.exports.luaK_intK          = luaK_intK; +module.exports.luaK_jump          = luaK_jump; +module.exports.luaK_jumpto        = luaK_jumpto; +module.exports.luaK_nil           = luaK_nil; +module.exports.luaK_numberK       = luaK_numberK; +module.exports.luaK_patchclose    = luaK_patchclose; +module.exports.luaK_patchlist     = luaK_patchlist; +module.exports.luaK_patchtohere   = luaK_patchtohere; +module.exports.luaK_posfix        = luaK_posfix; +module.exports.luaK_prefix        = luaK_prefix; +module.exports.luaK_reserveregs   = luaK_reserveregs; +module.exports.luaK_ret           = luaK_ret; +module.exports.luaK_self          = luaK_self; +module.exports.luaK_setlist       = luaK_setlist; +module.exports.luaK_setmultret    = luaK_setmultret; +module.exports.luaK_setoneret     = luaK_setoneret; +module.exports.luaK_setreturns    = luaK_setreturns; +module.exports.luaK_storevar      = luaK_storevar; +module.exports.luaK_stringK       = luaK_stringK;
\ No newline at end of file diff --git a/src/lcorolib.js b/src/lcorolib.js index 05f3b00..b2d0de8 100644 --- a/src/lcorolib.js +++ b/src/lcorolib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */  "use strict";  const assert  = require('assert'); diff --git a/src/ldebug.js b/src/ldebug.js index f6d0633..091b896 100644 --- a/src/ldebug.js +++ b/src/ldebug.js @@ -60,7 +60,7 @@ const upvalname = function(p, uv) {  };  const funcinfo = function(ar, cl) { -    if (cl === null || cl.type == CT.LUA_TCCL) { +    if (cl === null || cl.type === CT.LUA_TCCL) {          ar.source = "=[JS]";          ar.linedefined = -1;          ar.lastlinedefined = -1; @@ -77,7 +77,7 @@ const funcinfo = function(ar, cl) {  };  const collectvalidlines = function(L, f) { -    if (f === null || f.c.type == CT.LUA_TCCL) { +    if (f === null || f.c.type === CT.LUA_TCCL) {          L.stack[L.top++] = ldo.nil;          assert(L.top <= L.ci.top, "stack overflow");      } else { @@ -123,7 +123,7 @@ const auxgetinfo = function(L, what, ar, f, ci) {              }              case 'u': {                  ar.nups = f === null ? 0 : f.c.nupvalues; -                if (f === null || f.c.type == CT.LUA_TCCL) { +                if (f === null || f.c.type === CT.LUA_TCCL) {                      ar.isvararg = true;                      ar.nparams = 0;                  } else { @@ -3,15 +3,18 @@  const assert         = require('assert'); -const lua            = require('./lua.js'); +const BytecodeParser = require('./lundump.js'); +const lapi           = require('./lapi.js'); +const ldebug         = require('./ldebug.js'); +const lfunc          = require('./lfunc.js'); +const llex           = require('./llex.js'); +const llimit         = require('./llimit.js');  const lobject        = require('./lobject.js'); +const lparser        = require('./lparser.js');  const lstate         = require('./lstate.js'); -const llimit         = require('./llimit.js');  const ltm            = require('./ltm.js'); +const lua            = require('./lua.js');  const lvm            = require('./lvm.js'); -const lfunc          = require('./lfunc.js'); -const BytecodeParser = require('./lundump.js'); -const ldebug         = require('./ldebug.js');  const CT             = lua.constant_types;  const TS             = lua.thread_status;  const LUA_MULTRET    = lua.LUA_MULTRET; @@ -475,7 +478,7 @@ const lua_yield = function(L, n) {  const luaD_pcall = function(L, func, u, old_top, ef) {      let old_ci = L.ci; -    // TODO: lu_byte old_allowhooks = L->allowhook; +    let old_allowhooks = L.allowhook;      let old_nny = L.nny;      let old_errfunc = L.errfunc;      L.errfunc = ef; @@ -486,7 +489,7 @@ const luaD_pcall = function(L, func, u, old_top, ef) {          lfunc.luaF_close(L, old_top);          seterrorobj(L, status, old_top);          L.ci = old_ci; -        // TODO: L->allowhook = old_allowhooks; +        L.allowhook = old_allowhooks;          L.nny = old_nny;      } @@ -504,31 +507,64 @@ const luaD_callnoyield = function(L, off, nResults) {    L.nny--;  }; -// TODO: since we only handle binary, no need for a reader or mode -const f_parser = function(L, data) { -    let p = new BytecodeParser(data); -    let cl = p.luaU_undump(L); +/* +** Execute a protected parser. +*/ +class SParser { +    constructor() {  /* data to 'f_parser' */ +        this.z = new llex.MBuffer(); +        this.buff = new llex.MBuffer();  /* dynamic structure used by the scanner */ +        this.dyd = new lparser.Dyndata();  /* dynamic structures used by the parser */ +        this.mode = null; +        this.name = null; +    } +} + +const checkmode = function(L, mode, x) { +    if (mode && mode !== x) { +        lapi.lua_pushstring(L, `attempt to load a ${x} chunk (mode is '${mode}')`); +        luaD_throw(L, TS.LUA_ERRSYNTAX); +    } +}; -    assert(cl.nupvalues == cl.p.upvalues.length); +const f_parser = function(L, p) { +    let cl; +    let c = p.z.getc();  /* read first character */ +    if (String.fromCharCode(c) === lua.LUA_SIGNATURE.charAt(0)) { +        checkmode(L, p.mode, "binary"); +        cl = new BytecodeParser(p.z.buffer).luaU_undump(L); +    } else { +        checkmode(L, p.mode, "text"); +        cl = lparser.luaY_parser(L, p.z, p.buff, p.dyd, p.name, c); +    } +    assert(cl.nupvalues === cl.p.upvalues.length);      lfunc.luaF_initupvals(L, cl);  }; -const luaD_protectedparser = function(L, data, name) { -    L.nny++; +const luaD_protectedparser = function(L, z, name, mode) { +    let p = new SParser(); +    L.nny++;  /* cannot yield during parsing */ -    let status = luaD_pcall(L, f_parser, data, L.top, L.errfunc); +    p.z = z; +    p.name = name; +    p.mode = mode; +    p.dyd.actvar.arr = []; +    p.dyd.actvar.size = 0; +    p.dyd.gt.arr = []; +    p.dyd.gt.size = 0; +    p.dyd.label.arr = []; +    p.dyd.label.size = 0; + +    let status = luaD_pcall(L, f_parser, p, L.top, L.errfunc);      L.nny--;      return status;  }; +module.exports.SParser              = SParser;  module.exports.adjust_varargs       = adjust_varargs; -module.exports.lua_isyieldable      = lua_isyieldable; -module.exports.lua_resume           = lua_resume; -module.exports.lua_yield            = lua_yield; -module.exports.lua_yieldk           = lua_yieldk;  module.exports.luaD_call            = luaD_call;  module.exports.luaD_callnoyield     = luaD_callnoyield;  module.exports.luaD_pcall           = luaD_pcall; @@ -537,6 +573,10 @@ module.exports.luaD_precall         = luaD_precall;  module.exports.luaD_protectedparser = luaD_protectedparser;  module.exports.luaD_rawrunprotected = luaD_rawrunprotected;  module.exports.luaD_throw           = luaD_throw; +module.exports.lua_isyieldable      = lua_isyieldable; +module.exports.lua_resume           = lua_resume; +module.exports.lua_yield            = lua_yield; +module.exports.lua_yieldk           = lua_yieldk;  module.exports.moveresults          = moveresults;  module.exports.nil                  = nil;  module.exports.stackerror           = stackerror; diff --git a/src/lfunc.js b/src/lfunc.js index 3b3c8ce..227606d 100644 --- a/src/lfunc.js +++ b/src/lfunc.js @@ -1,6 +1,8 @@  /*jshint esversion: 6 */  "use strict"; -const assert = require('assert'); +const assert  = require('assert'); + +const lobject = require('./lobject.js');  class Proto { @@ -53,6 +55,15 @@ class UpVal {  } +const luaF_newLclosure = function(L, n) { +    let c = new lobject.LClosure(); +    c.p = null; +    c.nupvalues = n; +    while (n--) c.upvals[n] = null; +    return c; +}; + +  const findupval = function(L, level) {      let pp = L.openupval; @@ -91,12 +102,16 @@ const luaF_close = function(L, level) {      }  }; +/* +** fill a closure with new closed upvalues +*/  const luaF_initupvals = function(L, cl) {      for (let i = 0; i < cl.nupvalues; i++) {          let uv = new UpVal(L);          uv.refcount = 1;          uv.u.value = null;          uv.v = uv.u.value; +        cl.upvals[i] = uv;      }  }; @@ -108,7 +123,7 @@ const luaF_getlocalname = function(f, local_number, pc) {      for (let i = 0; i < f.locvars.length && f.locvars[i].startpc <= pc; i++) {          if (pc < f.locvars[i].endpc) {  /* is variable active? */              local_number--; -            if (local_number == 0) +            if (local_number === 0)                  return f.locvars[i].varname;          }      } @@ -116,10 +131,11 @@ const luaF_getlocalname = function(f, local_number, pc) {  } +module.exports.MAXUPVAL          = 255;  module.exports.Proto             = Proto;  module.exports.UpVal             = UpVal;  module.exports.findupval         = findupval;  module.exports.luaF_close        = luaF_close; -module.exports.MAXUPVAL          = 255; +module.exports.luaF_getlocalname = luaF_getlocalname  module.exports.luaF_initupvals   = luaF_initupvals; -module.exports.luaF_getlocalname = luaF_getlocalname
\ No newline at end of file +module.exports.luaF_newLclosure  = luaF_newLclosure;
\ No newline at end of file diff --git a/src/linit.js b/src/linit.js index 36c0121..b926683 100644 --- a/src/linit.js +++ b/src/linit.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */  "use strict";  const assert = require('assert'); diff --git a/src/ljstype.js b/src/ljstype.js new file mode 100644 index 0000000..a92c8d0 --- /dev/null +++ b/src/ljstype.js @@ -0,0 +1,29 @@ +"use strict"; + +const assert = require('assert'); + +const lisdigit = function(c) { +    return /^\d$/.test(c.charAt(0)); +}; + +const lisxdigit = function(c) { +    return /^[0-9a-fA-F]$/.test(c.charAt(0)); +}; + +const lisspace = function(c) { +    return /^\s$/.test(c.charAt(0)); +}; + +const lislalpha = function(c) { +    return /^[_a-zA-z]$/.test(c.charAt(0)); +}; + +const lislalnum = function(c) { +    return /^[_a-zA-z0-9]$/.test(c.charAt(0)); +}; + +module.exports.lisdigit   = lisdigit; +module.exports.lislalnum  = lislalnum; +module.exports.lislalpha  = lislalpha; +module.exports.lisspace   = lisspace; +module.exports.lisxdigit  = lisxdigit;
\ No newline at end of file diff --git a/src/llex.js b/src/llex.js new file mode 100644 index 0000000..63a1fbb --- /dev/null +++ b/src/llex.js @@ -0,0 +1,609 @@ +"use strict"; + +const DataView = require('buffer-dataview'); +const assert   = require('assert'); + +const lapi     = require('./lapi.js'); +const lauxlib  = require('./lauxlib.js'); +const ldebug   = require('./ldebug.js'); +const ldo      = require('./ldo.js'); +const ljstype  = require('./ljstype'); +const lobject  = require('./lobject'); +const lua      = require('./lua.js'); +const TValue   = lobject.TValue; +const CT       = lua.constant_types; +const TS       = lua.thread_status; + +const FIRST_RESERVED = 257; + +const RESERVED = { +    /* terminal symbols denoted by reserved words */ +    TK_AND:      FIRST_RESERVED, +    TK_BREAK:    FIRST_RESERVED + 1, +    TK_DO:       FIRST_RESERVED + 2, +    TK_ELSE:     FIRST_RESERVED + 3, +    TK_ELSEIF:   FIRST_RESERVED + 4, +    TK_END:      FIRST_RESERVED + 5, +    TK_FALSE:    FIRST_RESERVED + 6, +    TK_FOR:      FIRST_RESERVED + 7, +    TK_FUNCTION: FIRST_RESERVED + 8, +    TK_GOTO:     FIRST_RESERVED + 9, +    TK_IF:       FIRST_RESERVED + 10, +    TK_IN:       FIRST_RESERVED + 11, +    TK_LOCAL:    FIRST_RESERVED + 12, +    TK_NIL:      FIRST_RESERVED + 13, +    TK_NOT:      FIRST_RESERVED + 14, +    TK_OR:       FIRST_RESERVED + 15, +    TK_REPEAT:   FIRST_RESERVED + 16, +    TK_RETURN:   FIRST_RESERVED + 17, +    TK_THEN:     FIRST_RESERVED + 18, +    TK_TRUE:     FIRST_RESERVED + 19, +    TK_UNTIL:    FIRST_RESERVED + 20, +    TK_WHILE:    FIRST_RESERVED + 21, +    /* other terminal symbols */ +    TK_IDIV:     FIRST_RESERVED + 22, +    TK_CONCAT:   FIRST_RESERVED + 23, +    TK_DOTS:     FIRST_RESERVED + 24, +    TK_EQ:       FIRST_RESERVED + 25, +    TK_GE:       FIRST_RESERVED + 26, +    TK_LE:       FIRST_RESERVED + 27, +    TK_NE:       FIRST_RESERVED + 28, +    TK_SHL:      FIRST_RESERVED + 29, +    TK_SHR:      FIRST_RESERVED + 30, +    TK_DBCOLON:  FIRST_RESERVED + 31, +    TK_EOS:      FIRST_RESERVED + 32, +    TK_FLT:      FIRST_RESERVED + 33, +    TK_INT:      FIRST_RESERVED + 34, +    TK_NAME:     FIRST_RESERVED + 35, +    TK_STRING:   FIRST_RESERVED + 36 +}; + +const R = RESERVED; + +const luaX_tokens = [ +    "and", "break", "do", "else", "elseif", +    "end", "false", "for", "function", "goto", "if", +    "in", "local", "nil", "not", "or", "repeat", +    "return", "then", "true", "until", "while", +    "//", "..", "...", "==", ">=", "<=", "~=", +    "<<", ">>", "::", "<eof>", +    "<number>", "<integer>", "<name>", "<string>" +]; + +const NUM_RESERVED = Object.keys(RESERVED).length; + +class MBuffer { +    constructor(data) { +        this.buffer = typeof data === "string" ? data.split('') : (data ? data : []); +        this.n = this.buffer instanceof DataView ? this.buffer.byteLength : this.buffer.length; +        this.off = 0; +    } + +    getc() { +        if (this.buffer instanceof DataView) +            return this.n-- > 0 ? this.buffer.getUint8(this.off++, true) : -1; + +        return this.n-- > 0 ? this.buffer[this.off++] : -1; +    } +} + +class SemInfo { +    constructor() { +        this.r = NaN; +        this.i = NaN; +        this.ts = null; +    } +} + +class Token { +    constructor() { +        this.token = NaN; +        this.seminfo = new SemInfo(); +    } +} + +/* state of the lexer plus state of the parser when shared by all +   functions */ +class LexState { +    constructor() { +        this.current = NaN;  /* current character (charint) */ +        this.linenumber = NaN;  /* input line counter */ +        this.lastline = NaN;  /* line of last token 'consumed' */ +        this.t = new Token();  /* current token */ +        this.lookahead = new Token();  /* look ahead token */ +        this.fs = null;  /* current function (parser) */ +        this.L = null; +        this.z = new MBuffer(); +        this.buff = new MBuffer();  /* buffer for tokens */ +        this.h = null;  /* to avoid collection/reuse strings */ +        this.dyd = null;  /* dynamic structures used by the parser */ +        this.source = null;  /* current source name */ +        this.envn = null;  /* environment variable name */ +    } +} + +const save = function(ls, c) { +    let b = ls.buff; +    if (b.n + 1 > b.buffer.length) { +        if (b.buffer.length >= Number.MAX_SAFE_INTEGER/2) +            lexerror(ls, "lexical element too long", 0); +    } +    b.buffer[b.n++] = c; +}; + +const luaX_token2str = function(ls, token) { +    if (token < FIRST_RESERVED) {  /* single-byte symbols? */ +        return `'${String.fromCharCode(token)}'`; +    } else { +        let s = luaX_tokens[token - FIRST_RESERVED]; +        if (token < R.TK_EOS)  /* fixed format (symbols and reserved words)? */ +            return `'${s}'`; +        else  /* names, strings, and numerals */ +            return s; +    } +}; + +const currIsNewline = function(ls) { +    return ls.current === '\n' || ls.current === '\r'; +}; + +const next = function(ls) { +    ls.current = ls.z.getc(); +}; + +const save_and_next = function(ls) { +    save(ls, ls.current); +    next(ls); +}; + +/* +** increment line number and skips newline sequence (any of +** \n, \r, \n\r, or \r\n) +*/ +const inclinenumber = function(ls) { +    let old = ls.current; +    assert(currIsNewline(ls)); +    next(ls);  /* skip '\n' or '\r' */ +    if (currIsNewline(ls) && ls.current !== old) +        next(ls);  /* skip '\n\r' or '\r\n' */ +    if (++ls.linenumber >= Number.MAX_SAFE_INTEGER) +        lexerror(ls, "chunk has too many lines", 0); +}; + +const luaX_setinput = function(L, ls, z, source, firstchar) { +    ls.t = { +        token: 0, +        seminfo: { +            i: NaN, +            r: NaN, +            ts: null +        } +    }; +    ls.L = L; +    ls.current = firstchar; +    ls.lookahead = { +        token: R.TK_EOS, +        seminfo: { +            i: NaN, +            r: NaN, +            ts: null +        } +    }; +    ls.z = z; +    ls.fs = null; +    ls.linenumber = 1; +    ls.lastline = 1; +    ls.source = source; +    ls.envn = new TValue(CT.LUA_TLNGSTR, "_ENV"); +}; + +const check_next1 = function(ls, c) { +    if (ls.current === c) { +        next(ls); +        return true; +    } +     +    return false; +}; + +/* +** Check whether current char is in set 'set' (with two chars) and +** saves it +*/ +const check_next2 = function(ls, set) { +    if (ls.current === set.charAt(0) || ls.current === set.charAt(1)) { +        save_and_next(ls); +        return true; +    } + +    return false; +}; + +const read_numeral = function(ls, seminfo) { +    let expo = "Ee"; +    let first = ls.current; +    assert(ljstype.lisdigit(ls.current)); +    save_and_next(ls); +    if (first === '0' && check_next2(ls, "xX"))  /* hexadecimal? */ +        expo = "Pp"; + +    for (;;) { +        if (check_next2(ls, expo))  /* exponent part? */ +            check_next2(ls, "-+");  /* optional exponent sign */ +        if (ljstype.lisxdigit(ls.current)) +            save_and_next(ls); +        else if (ls.current === '.') +            save_and_next(ls); +        else break; +    } + +    save(ls, '\0'); + +    let obj = lobject.luaO_str2num(ls.buff.buffer); +    if (obj === false)  /* format error? */ +        lexerror(ls, "malformed number", R.TK_FLT); +    if (obj.ttisinteger()) { +        seminfo.i = obj.value; +        return R.TK_INT; +    } else { +        assert(obj.ttisfloat()); +        seminfo.r = obj.value; +        return R.TK_FLT; +    } +}; + +const txtToken = function(ls, token) { +    switch (token) { +        case R.TK_NAME: case R.TK_STRING: +        case R.TK_FLT: case R.TK_INT: +            save(ls, '\0'); +            return `'${ls.buff.buffer.join('')}'`; +        default: +            return luaX_token2str(ls, token); +    } +}; + +const lexerror = function(ls, msg, token) { +    msg = ldebug.luaG_addinfo(ls.L, msg, ls.source, ls.linenumber); +    if (token) +        lapi.lua_pushstring(ls.L, `${msg} near ${txtToken(ls, token)}`); +    ldo.luaD_throw(ls.L, TS.LUA_ERRSYNTAX); +}; + +const luaX_syntaxerror = function(ls, msg) { +    lexerror(ls, msg, ls.t.token); +}; + +/* +** skip a sequence '[=*[' or ']=*]'; if sequence is well formed, return +** its number of '='s; otherwise, return a negative number (-1 iff there +** are no '='s after initial bracket) +*/ +const skip_sep = function(ls) { +    let count = 0; +    let s = ls.current; +    assert(s === '[' || s === ']'); +    save_and_next(ls); +    while (ls.current === '=') { +        save_and_next(ls); +        count++; +    } +    return ls.current === s ? count : (-count) - 1; +}; + +const read_long_string = function(ls, seminfo, sep) { +    let line = ls.linenumber;  /* initial line (for error message) */ +    save_and_next(ls);  /* skip 2nd '[' */ + +    if (currIsNewline(ls))  /* string starts with a newline? */ +        inclinenumber(ls);  /* skip it */ + +    let skip = false; +    for (; !skip ;) { +        switch (ls.current) { +            case -1: {  /* error */ +                let what = seminfo ? "string" : "comment"; +                let msg = `unfinished long ${what} (starting at line ${line})`; +                lexerror(ls, msg, R.TK_EOS); +                break; +            } +            case ']': { +                if (skip_sep(ls) === sep) { +                    save_and_next(ls);  /* skip 2nd ']' */ +                    skip = true; +                } +                break; +            } +            case '\n': case '\r': { +                save(ls, '\n'); +                inclinenumber(ls); +                if (!seminfo) { +                    ls.buff.n = 0; +                    ls.buff.buffer = []; +                } +                break; +            } +            default: { +                if (seminfo) save_and_next(ls); +                else next(ls); +            } +        } +    } + +    if (seminfo) +        seminfo.ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.slice(2 + sep).join('')); +}; + +const esccheck = function(ls, c, msg) { +    if (!c) { +        if (ls.current !== -1) +            save_and_next(ls);  /* add current to buffer for error message */ +        lexerror(ls, msg, R.TK_STRING); +    } +}; + +const gethexa = function(ls) { +    save_and_next(ls); +    esccheck(ls, ljstype.lisxdigit(ls.current), "hexadecimal digit expected"); +    return lobject.luaO_hexavalue(ls.current); +}; + +const readhexaesc = function(ls) { +    let r = gethexa(ls); +    r = (r << 4) + gethexa(ls); +    ls.buff.n -= 2;  /* remove saved chars from buffer */ +    return r; +}; + +const readutf8desc = function(ls) { +    let i = 4;  /* chars to be removed: '\', 'u', '{', and first digit */ +    save_and_next(ls);  /* skip 'u' */ +    esccheck(ls, ls.current === '{', "missing '{'"); +    let r = gethexa(ls);  /* must have at least one digit */ + +    save_and_next(ls); +    while (ljstype.lisxdigit(ls.current)) { +        i++; +        r = (r << 4) + lobject.luaO_hexavalue(ls.current); +        esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large"); +        save_and_next(ls); +    } +    esccheck(ls, ls.current === '}', "missing '}'"); +    next(ls);  /* skip '}' */ +    ls.buff.n -= i;  /* remove saved chars from buffer */ +    return r; +}; + +const utf8esc = function(ls) { +    let buff = new Array(lobject.UTF8BUFFSZ); +    let n = lobject.luaO_utf8esc(buff, readutf8desc(ls)); +    for (; n > 0; n--)  /* add 'buff' to string */ +        save(ls, buff[lobject.UTF8BUFFSZ - n]); +}; + +const readdecesc = function(ls) { +    let r = 0;  /* result accumulator */ +    let i; +    for (i = 0; i < 3 && ljstype.lisdigit(ls.current); i++) {  /* read up to 3 digits */ +        r = 10 * r + parseInt(ls.current); +        save_and_next(ls); +    } +    esccheck(ls, r <= 255, "decimal escape too large"); +    ls.buff.n -= i;  /* remove read digits from buffer */ +    return r; +}; + +const read_string = function(ls, del, seminfo) { +    save_and_next(ls);  /* keep delimiter (for error messages) */ + +    while (ls.current !== del) { +        switch (ls.current) { +            case -1: +                lexerror(ls, "unfinished string", R.TK_EOS); +                break; +            case '\n': +            case '\r': +                lexerror(ls, "unfinished string", R.TK_STRING); +                break; +            case '\\': {  /* escape sequences */ +                save_and_next(ls);  /* keep '\\' for error messages */ +                let will; +                let c; +                switch(ls.current) { +                    case 'a': c = '\a'; will = 'read_save'; break; +                    case 'b': c = '\b'; will = 'read_save'; break; +                    case 'f': c = '\f'; will = 'read_save'; break; +                    case 'n': c = '\n'; will = 'read_save'; break; +                    case 'r': c = '\r'; will = 'read_save'; break; +                    case 't': c = '\t'; will = 'read_save'; break; +                    case 'v': c = '\v'; will = 'read_save'; break; +                    case 'x': c = readhexaesc(ls); will = 'read_save'; break; +                    case 'u': utf8esc(ls); will = 'read_save'; break; +                    case '\n': case '\r': +                        inclinenumber(ls); c = '\n'; will = 'read_save'; break; +                    case '\\': case '\"': case '\'': +                        c = ls.current; will = 'read_save'; break; +                    case -1: will = 'read_save'; break;  /* will raise an error next loop */ +                    case 'z': {  /* zap following span of spaces */ +                        ls.buff.n -= 1;  /* remove '\\' */ +                        next(ls);  /* skip the 'z' */ +                        while (ljstype.lisspace(ls.current)) { +                            if (currIsNewline(ls)) inclinenumber(ls); +                            else next(ls); +                        } +                        will = 'no_save'; break; +                    } +                    default: { +                        esccheck(ls, ljstype.lisdigit(ls.current), "invalid escape sequence"); +                        c = readdecesc(ls);  /* digital escape '\ddd' */ +                        will = 'only_save'; break; +                    } +                } + +                if (will === 'read_save') +                    next(ls); +                else if (will === 'only_save') { +                    ls.buff.n -= 1;  /* remove '\\' */ +                    save(ls, c); +                } else if (will === 'no_save') +                    break; +            } +            default: +                save_and_next(ls); +        } +    } +    save_and_next(ls);  /* skip delimiter */ +    seminfo.ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.slice(1, ls.buff.buffer.length-1).join('')); +}; + +const isreserved = function(w) { +    return luaX_tokens.slice(0, 22).indexOf(w) >= 0; +}; + +const llex = function(ls, seminfo) { +    ls.buff.n = 0; +    ls.buff.buffer = []; + +    for (;;) { +        switch (ls.current) { +            case '\n': case '\r': {  /* line breaks */ +                inclinenumber(ls); +                break; +            } +            case ' ': case '\f': case '\t': case '\v': {  /* spaces */ +                next(ls); +                break; +            } +            case '-': {  /* '-' or '--' (comment) */ +                next(ls); +                if (ls.current !== '-') return '-'; +                /* else is a comment */ +                next(ls); +                if (ls.current === '[') {  /* long comment? */ +                    let sep = skip_sep(ls); +                    ls.buff.n = 0;  /* 'skip_sep' may dirty the buffer */ +                    ls.buff.buffer = []; +                    if (sep >= 0) { +                        read_long_string(ls, null, sep);  /* skip long comment */ +                        ls.buff.n = 0;  /* previous call may dirty the buff. */ +                        ls.buff.buffer = []; +                        break; +                    } +                } + +                /* else short comment */ +                while (!currIsNewline(ls) && ls.current !== -1) +                    next(ls);  /* skip until end of line (or end of file) */ +                break; +            } +            case '[': {  /* long string or simply '[' */ +                let sep = skip_sep(ls); +                if (sep.charCodeAt(0) >= 0) { +                    read_long_string(ls, seminfo, sep); +                    return R.TK_STRING; +                } else if (sep !== -1)  /* '[=...' missing second bracket */ +                    lexerror(ls, "invalid long string delimiter", R.TK_STRING); +                return '['; +            } +            case '=': { +                next(ls); +                if (check_next1(ls, '=')) return R.TK_EQ; +                else return '='; +            } +            case '<': { +                next(ls); +                if (check_next1(ls, '=')) return R.TK_LE; +                else if (check_next1(ls, '<')) return R.TK_SHL; +                else return '<'; +            } +            case '>': { +                next(ls); +                if (check_next1(ls, '=')) return R.TK_GE; +                else if (check_next1(ls, '>')) return R.TK_SHR; +                else return '>'; +            } +            case '/': { +                next(ls); +                if (check_next1(ls, '/')) return R.TK_IDIV; +                else return '/'; +            } +            case '~': { +                next(ls); +                if (check_next1(ls, '=')) return R.TK_NE; +                else return '~'; +            } +            case ':': { +                next(ls); +                if (check_next1(ls, ':')) return R.TK_DBCOLON; +                else return ':'; +            } +            case '"': case '\'': {  /* short literal strings */ +                read_string(ls, ls.current, seminfo); +                return R.TK_STRING; +            } +            case '.': {  /* '.', '..', '...', or number */ +                save_and_next(ls); +                if (check_next1(ls, '.')) { +                    if (check_next1(ls, '.')) +                        return R.TK_DOTS;   /* '...' */ +                    else return R.TK_CONCAT;   /* '..' */ +                } +                else if (!ljstype.lisdigit(ls.current)) return '.'; +                else return read_numeral(ls, seminfo); +            } +            case '0': case '1': case '2': case '3': case '4': +            case '5': case '6': case '7': case '8': case '9': { +                return read_numeral(ls, seminfo); +            } +            case -1: { +                return R.TK_EOS; +            } +            default: { +                if (ljstype.lislalpha(ls.current)) {  /* identifier or reserved word? */ +                    do { +                        save_and_next(ls); +                    } while (ljstype.lislalnum(ls.current)); + +                    let ts = new TValue(CT.LUA_TLNGSTR, ls.buff.buffer.join('')); +                    seminfo.ts = ts; +                    let kidx = luaX_tokens.slice(0, 22).indexOf(ts.value); +                    if (kidx >= 0)  /* reserved word? */ +                        return kidx + FIRST_RESERVED; +                    else +                        return R.TK_NAME; +                } else {  /* single-char tokens (+ - / ...) */ +                    let c = ls.current; +                    next(ls); +                    return c; +                } +            } +        } +    } +}; + +const luaX_next = function(ls) { +    ls.lastline = ls.linenumber; +    if (ls.lookahead.token !== R.TK_EOS) {  /* is there a look-ahead token? */ +        ls.t = ls.lookahead;  /* use this one */ +        ls.lookahead.token = R.TK_EOS;  /* and discharge it */ +    } else +        ls.t.token = llex(ls, ls.t.seminfo);  /* read next token */ +}; + +const luaX_lookahead = function(ls) { +    assert(ls.lookahead.token === R.TK_EOS); +    ls.lookahead.token = llex(ls. ls.lookahead.seminfo); +    return ls.lookahead.token; +}; + +module.exports.FIRST_RESERVED   = FIRST_RESERVED; +module.exports.LexState         = LexState; +module.exports.MBuffer          = MBuffer; +module.exports.RESERVED         = RESERVED; +module.exports.isreserved       = isreserved; +module.exports.luaX_lookahead   = luaX_lookahead; +module.exports.luaX_next        = luaX_next; +module.exports.luaX_setinput    = luaX_setinput; +module.exports.luaX_syntaxerror = luaX_syntaxerror; +module.exports.luaX_token2str   = luaX_token2str; +module.exports.luaX_tokens      = luaX_tokens;
\ No newline at end of file diff --git a/src/lmathlib.js b/src/lmathlib.js index 54bdc46..55f8416 100644 --- a/src/lmathlib.js +++ b/src/lmathlib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */  "use strict";  const assert     = require('assert'); diff --git a/src/lobject.js b/src/lobject.js index 086af00..bd234a1 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -1,8 +1,12 @@ -/*jshint esversion: 6 */ + /*jshint esversion: 6 */  "use strict"; -const CT    = require('./lua.js').constant_types; -const UpVal = require('./lfunc.js').UpVal; +const assert = require('assert'); + +const ljstype = require('./ljstype.js'); +const lua     = require('./lua.js'); +const CT      = lua.constant_types; +const UpVal   = require('./lfunc.js').UpVal;  class TValue { @@ -122,6 +126,7 @@ class Table extends TValue {          // Those lua values are used by value, others by reference          if (key instanceof TValue              && [CT.LUA_TNIL, +                CT.LUA_TBOOLEAN,                  CT.LUA_TSTRING,                  CT.LUA_TSHRSTR,                  CT.LUA_TLNGSTR, @@ -199,6 +204,18 @@ class CClosure extends TValue {  } +/* +** 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 = "...";  const PRE  = "[string \"";  const POS  = "\"]"; @@ -223,7 +240,7 @@ const luaO_chunkid = function(source, bufflen) {          let nli = source.indexOf('\n');  /* find first new line (if any) */          let nl = nli ? source.slice(nli) : null;          out = `${PRE}`;  /* add prefix */ -        bufflen -= PRE.length - RETS.length; - POS.length + 1;  /* save space for prefix+suffix+'\0' */ +        bufflen -= PRE.length + RETS.length + POS.length + 1;  /* save space for prefix+suffix+'\0' */          if (l < bufflen && nl === null) {  /* small one-line source? */              out += `${source}`;  /* keep it */          } else { @@ -237,8 +254,170 @@ const luaO_chunkid = function(source, bufflen) {      return out;  }; -module.exports.LClosure     = LClosure; -module.exports.CClosure     = CClosure; -module.exports.TValue       = TValue; -module.exports.Table        = Table; -module.exports.luaO_chunkid = luaO_chunkid;
\ No newline at end of file +const luaO_hexavalue = function(c) { +    if (ljstype.lisdigit(c)) return c.charCodeAt(0) - '0'.charCodeAt(0); +    else return (c.toLowerCase().charCodeAt(0) - 'a'.charCodeAt(0)) + 10; +}; + +const UTF8BUFFSZ = 8; + +const luaO_utf8desc = function(buff, x) { +    let n = 1;  /* number of bytes put in buffer (backwards) */ +    assert(x <= 0x10FFFF); +    if (x < 0x80)  /* ascii? */ +        buff[UTF8BUFFSZ - 1] = String.fromCharCode(x); +    else {  /* need continuation bytes */ +        let mfb = 0x3f;  /* maximum that fits in first byte */ +        do { +            buff[UTF8BUFFSZ - (n++)] = String.fromCharCode(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] = String.fromCharCode((~mfb << 1) | x);  /* add first byte */ +    } +    return n; +}; + +const l_str2dloc = function(s, result, mode) { +    result[0] = mode === 'x' ? parseInt(s, '16') : parseInt(s); +    if (isNaN(result[0])) return null;  /* nothing recognized? */ +    while (ljstype.lisspace(result[0])) result[0] = result[0].slice(1);  /* skip trailing spaces */ +    return result[0] === '\0' ? result : null;  /* OK if no trailing characters */ +}; + +const l_str2d = function(s) { +    let result = [null, null]; +    let pidx = /[.xXnN]/g.exec(s).index; +    let pmode = pidx ? s.slice(pidx) : null; +    let mode = pmode ? pmode.toLowerCase() : 0; +    if (mode === 'n')  /* reject 'inf' and 'nan' */ +        return null; +    let end = l_str2dloc(s, result, mode)[0];  /* try to convert */ +    if (end === null) {   /* failed? may be a different locale */ +        throw new Error("Locale not available to handle number"); // TODO +    } +    return [end, result[1]]; +}; + +const MAXBY10  = Number.MAX_SAFE_INTEGER / 10; +const MAXLASTD = Number.MAX_SAFE_INTEGER % 10; + +const l_str2int = function(s) { +    let result = [null, null]; +    let a = 0; +    let empty = true; +    let neg; + +    while (ljstype.lisspace(s[0])) s = s.slice(1);  /* skip initial spaces */ +    neg = s[0] === '-'; + +    if (neg || s[0] === '+') +        s = s.slice(1); + +    if (s[0] === '0' && (s[1] === 'x' || s[1] === 'X')) {  /* hex? */ +        s = s.slice(2);  /* skip '0x' */ + +        for (; ljstype.lisxdigit(s[0]); s = s.slice(1)) { +            a = a * 16 + luaO_hexavalue(s); +            empty = false; +        } +    } else {  /* decimal */ +        for (; ljstype.lisdigit(s[0]); s = s.slice(1)) { +            let d = parseInt(s[0]); +            if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg))  /* overflow? */ +                return null;  /* do not accept it (as integer) */ +            a = a * 10 + d; +            empty = false; +        } +    } + +    while (ljstype.lisspace(s[0])) s = s.slice(1);  /* skip trailing spaces */ + +    if (empty)/* TODO: || s[0] !== "") */ return null;  /* something wrong in the numeral */ +    else { +        result[1] = neg ? -a : a; +        result[0] = s; +        return result; +    } +}; + +const luaO_str2num = function(s) { +    let s2i = l_str2int(s); +    let e = s2i[0]; +    let i = s2i[1]; + +    if (e !== null) {   /* try as an integer */ +        return new TValue(CT.LUA_TNUMINT, i); +    } else {   /* else try as a float */ +        s2i = l_str2d(s); +        e = s2i[0]; +        i = s2i[1]; + +        if (e !== null) { +            return new TValue(CT.LUA_TNUMFLT, i); +        } else +            return false;  /* conversion failed */ +    } +}; + +/* +** 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.LUA_OPADD:  return (v1 + v2)|0; +        case lua.LUA_OPSUB:  return (v1 - v2)|0; +        case lua.LUA_OPMUL:  return (v1 * v2)|0; +        case lua.LUA_OPMOD:  return (v1 % v2)|0; +        case lua.LUA_OPIDIV: return (v1 / v2)|0; +        case lua.LUA_OPBAND: return (v1 & v2)|0; +        case lua.LUA_OPBOR:  return (v1 | v2)|0; +        case lua.LUA_OPBXOR: return (v1 ^ v2)|0; +        case lua.LUA_OPSHL:  return (v1 << v2)|0; +        case lua.LUA_OPSHR:  return (v1 >> v2)|0; +        case lua.LUA_OPUNM:  return (-v1)|0; +        case lua.LUA_OPBNOT: return (~v1)|0; +    } +}; + + +const numarith = function(L, op, v1, v2) { +    switch (op) { +        case lua.LUA_OPADD:  return v1 + v2; +        case lua.LUA_OPSUB:  return v1 - v2; +        case lua.LUA_OPMUL:  return v1 * v2; +        case lua.LUA_OPDIV:  return v1 / v2; +        case lua.LUA_OPPOW:  return Math.pow(v1, v2); +        case lua.LUA_OPIDIV: return (v1 / v2)|0; +        case lua.LUA_OPUNM:  return -v1; +        case lua.LUA_OPMOD:  return v1 % v2; +    } +}; + +module.exports.CClosure       = CClosure; +module.exports.LClosure       = LClosure; +module.exports.LocVar         = LocVar; +module.exports.TValue         = TValue; +module.exports.Table          = Table; +module.exports.UTF8BUFFSZ     = UTF8BUFFSZ; +module.exports.luaO_chunkid   = luaO_chunkid; +module.exports.luaO_hexavalue = luaO_hexavalue; +module.exports.luaO_int2fb    = luaO_int2fb; +module.exports.luaO_str2num   = luaO_str2num; +module.exports.luaO_utf8desc  = luaO_utf8desc;
\ No newline at end of file diff --git a/src/lopcodes.js b/src/lopcodes.js index 5723abb..bfcd993 100644 --- a/src/lopcodes.js +++ b/src/lopcodes.js @@ -51,6 +51,56 @@ const OpCodes = [      "OP_EXTRAARG"  ]; +const OpCodesI = { +    OP_MOVE:     0, +    OP_LOADK:    1, +    OP_LOADKX:   2, +    OP_LOADBOOL: 3, +    OP_LOADNIL:  4, +    OP_GETUPVAL: 5, +    OP_GETTABUP: 6, +    OP_GETTABLE: 7, +    OP_SETTABUP: 8, +    OP_SETUPVAL: 9, +    OP_SETTABLE: 10, +    OP_NEWTABLE: 11, +    OP_SELF:     12, +    OP_ADD:      13, +    OP_SUB:      14, +    OP_MUL:      15, +    OP_MOD:      16, +    OP_POW:      17, +    OP_DIV:      18, +    OP_IDIV:     19, +    OP_BAND:     20, +    OP_BOR:      21, +    OP_BXOR:     22, +    OP_SHL:      23, +    OP_SHR:      24, +    OP_UNM:      25, +    OP_BNOT:     26, +    OP_NOT:      27, +    OP_LEN:      28, +    OP_CONCAT:   29, +    OP_JMP:      30, +    OP_EQ:       31, +    OP_LT:       32, +    OP_LE:       33, +    OP_TEST:     34, +    OP_TESTSET:  35, +    OP_CALL:     36, +    OP_TAILCALL: 37, +    OP_RETURN:   38, +    OP_FORLOOP:  39, +    OP_FORPREP:  40, +    OP_TFORCALL: 41, +    OP_TFORLOOP: 42, +    OP_SETLIST:  43, +    OP_CLOSURE:  44, +    OP_VARARG:   45, +    OP_EXTRAARG: 46 +}; +  /*  ** masks for instruction properties. The format is:  ** bits 0-1: op mode @@ -120,10 +170,26 @@ const luaP_opmodes = [      0 << 7 | 0 << 6 | OpArgU << 4 | OpArgU << 2 | iAx     /* OP_EXTRAARG */  ]; +const getOpMode = function(m) { +    return luaP_opmodes[m] & 3; +}; + +const getBMode = function(m) { +    return (luaP_opmodes[m] >> 4) & 3; +}; + +const getCMode = function(m) { +    return (luaP_opmodes[m] >> 2) & 3; +}; +  const testAMode = function(m) {      return luaP_opmodes[m] & (1 << 6);  }; +const testTMode = function(m) { +    return luaP_opmodes[m] & (1 << 7); +}; +  const SIZE_C     = 9;  const SIZE_B     = 9;  const SIZE_Bx    = (SIZE_C + SIZE_B); @@ -143,40 +209,158 @@ const MAXARG_A   = ((1 << SIZE_A) - 1);  const MAXARG_B   = ((1 << SIZE_B) - 1);  const MAXARG_C   = ((1 << SIZE_C) - 1); +/* this bit 1 means constant (0 means register) */  const BITRK      = (1 << (SIZE_B - 1)); +/* +** invalid register that fits in 8 bits +*/ +const NO_REG     = MAXARG_A; + +/* test whether value is a constant */  const ISK = function (x) {      return x & BITRK;  }; +/* gets the index of the constant */  const INDEXK = function (r) {      return r & ~BITRK;  }; +/* code a constant index as a RK value */ +const RKASK = function(x) { +    return x | BITRK; +}; + + +/* creates a mask with 'n' 1 bits at position 'p' */ +const MASK1 = function(n, p) { +    return ((~((~0)<<(n)))<<(p)); +}; + +/* creates a mask with 'n' 0 bits at position 'p' */ +const MASK0 = function(n, p) { +    return (~MASK1(n, p)); +}; + +const setarg = function(i, v, pos, size) { +    i.code = (i.code & MASK0(size, pos)) | ((v << pos) & MASK1(size, pos)); +    fullins(i); +}; + +const SETARG_A = function(i,v) { +    setarg(i, v, POS_A, SIZE_A); +}; + +const SETARG_B = function(i,v) { +    setarg(i, v, POS_B, SIZE_B); +}; + +const SETARG_C = function(i,v) { +    setarg(i, v, POS_C, SIZE_C); +}; + +const SETARG_Bx = function(i,v) { +    setarg(i, v, POS_Bx, SIZE_Bx); +}; + +const SETARG_Ax = function(i,v) { +    setarg(i, v, POS_Ax, SIZE_Ax); +}; + +const SETARG_sBx = function(i, b) { +    SETARG_Bx(i, b + MAXARG_sBx); +}; + +/* +** Pre-calculate all possible part of the instruction +*/ +const fullins = function(ins) { +    if (typeof ins === "number") { +        return { +            code:   ins, +            opcode: (ins >> POS_OP) & MASK1(SIZE_OP, 0), +            A:      (ins >> POS_A)  & MASK1(SIZE_A,  0), +            B:      (ins >> POS_B)  & MASK1(SIZE_B,  0), +            C:      (ins >> POS_C)  & MASK1(SIZE_C,  0), +            Bx:     (ins >> POS_Bx) & MASK1(SIZE_Bx, 0), +            Ax:     (ins >> POS_Ax) & MASK1(SIZE_Ax, 0), +            sBx:    ((ins >> POS_Bx) & MASK1(SIZE_Bx, 0)) - MAXARG_sBx +        }; +    } else { +        let i = ins.code; +        ins.opcode = (i >> POS_OP) & MASK1(SIZE_OP, 0); +        ins.A      = (i >> POS_A)  & MASK1(SIZE_A,  0); +        ins.B      = (i >> POS_B)  & MASK1(SIZE_B,  0); +        ins.C      = (i >> POS_C)  & MASK1(SIZE_C,  0); +        ins.Bx     = (i >> POS_Bx) & MASK1(SIZE_Bx, 0); +        ins.Ax     = (i >> POS_Ax) & MASK1(SIZE_Ax, 0); +        ins.sBx    = ((i >> POS_Bx) & MASK1(SIZE_Bx, 0)) - MAXARG_sBx; +        return ins; +    } +}; + +const CREATE_ABC = function(o, a, b, c) { +    return fullins(o << POS_OP | a << POS_A | b << POS_B | c << POS_C); +}; + +const CREATE_ABx = function(o, a, bc) { +    return fullins(o << POS_OP | a << POS_A | bc << POS_Bx); +}; + +const CREATE_Ax = function(o, a) { +    return fullins(o << POS_OP | a << POS_Ax); +}; +  /* number of list items to accumulate before a SETLIST instruction */  const LFIELDS_PER_FLUSH = 50; -module.exports.OpCodes           = OpCodes; -module.exports.SIZE_C            = SIZE_C; -module.exports.SIZE_B            = SIZE_B; -module.exports.SIZE_Bx           = SIZE_Bx; -module.exports.SIZE_A            = SIZE_A; -module.exports.SIZE_Ax           = SIZE_Ax; -module.exports.SIZE_OP           = SIZE_OP; -module.exports.POS_OP            = POS_OP; -module.exports.POS_A             = POS_A; -module.exports.POS_C             = POS_C; -module.exports.POS_B             = POS_B; -module.exports.POS_Bx            = POS_Bx; -module.exports.POS_Ax            = POS_Ax; -module.exports.MAXARG_Bx         = MAXARG_Bx; -module.exports.MAXARG_sBx        = MAXARG_sBx; -module.exports.MAXARG_Ax         = MAXARG_Ax; -module.exports.MAXARG_A          = MAXARG_A; -module.exports.MAXARG_B          = MAXARG_B; -module.exports.MAXARG_C          = MAXARG_C; -module.exports.BITRK             = BITRK; -module.exports.ISK               = ISK; -module.exports.INDEXK            = INDEXK; -module.exports.LFIELDS_PER_FLUSH = LFIELDS_PER_FLUSH; -module.exports.testAMode         = testAMode;
\ No newline at end of file +module.exports.BITRK               = BITRK; +module.exports.CREATE_ABC          = CREATE_ABC; +module.exports.CREATE_ABx          = CREATE_ABx; +module.exports.CREATE_Ax           = CREATE_Ax; +module.exports.INDEXK              = INDEXK; +module.exports.ISK                 = ISK; +module.exports.LFIELDS_PER_FLUSH   = LFIELDS_PER_FLUSH; +module.exports.MAXARG_A            = MAXARG_A; +module.exports.MAXARG_Ax           = MAXARG_Ax; +module.exports.MAXARG_B            = MAXARG_B; +module.exports.MAXARG_Bx           = MAXARG_Bx; +module.exports.MAXARG_C            = MAXARG_C; +module.exports.MAXARG_sBx          = MAXARG_sBx; +module.exports.NO_REG              = NO_REG; +module.exports.OpArgK              = OpArgK; +module.exports.OpArgN              = OpArgN; +module.exports.OpArgR              = OpArgR; +module.exports.OpArgU              = OpArgU; +module.exports.OpCodes             = OpCodes; +module.exports.OpCodesI            = OpCodesI; +module.exports.POS_A               = POS_A; +module.exports.POS_Ax              = POS_Ax; +module.exports.POS_B               = POS_B; +module.exports.POS_Bx              = POS_Bx; +module.exports.POS_C               = POS_C; +module.exports.POS_OP              = POS_OP; +module.exports.RKASK               = RKASK; +module.exports.SETARG_A            = SETARG_A; +module.exports.SETARG_Ax           = SETARG_Ax; +module.exports.SETARG_B            = SETARG_B; +module.exports.SETARG_Bx           = SETARG_Bx; +module.exports.SETARG_C            = SETARG_C; +module.exports.SETARG_sBx          = SETARG_sBx; +module.exports.SIZE_A              = SIZE_A; +module.exports.SIZE_Ax             = SIZE_Ax; +module.exports.SIZE_B              = SIZE_B; +module.exports.SIZE_Bx             = SIZE_Bx; +module.exports.SIZE_C              = SIZE_C; +module.exports.SIZE_OP             = SIZE_OP; +module.exports.fullins             = fullins; +module.exports.getBMode            = getBMode; +module.exports.getCMode            = getCMode; +module.exports.getOpMode           = getOpMode; +module.exports.iABC                = iABC; +module.exports.iABx                = iABx; +module.exports.iAsBx               = iAsBx; +module.exports.iAx                 = iAx; +module.exports.testAMode           = testAMode; +module.exports.testTMode           = testTMode;
\ No newline at end of file diff --git a/src/lparser.js b/src/lparser.js new file mode 100644 index 0000000..1e454e0 --- /dev/null +++ b/src/lparser.js @@ -0,0 +1,1560 @@ +"use strict"; + +const assert = require('assert'); + +const lcode    = require('./lcode.js'); +const lfunc    = require('./lfunc.js'); +const llex     = require('./llex.js'); +const llimit   = require('./llimit.js'); +const lobject  = require('./lobject.js'); +const lopcode  = require('./lopcodes.js'); +const lua      = require('./lua.js'); +const BinOpr   = lcode.BinOpr; +const OpCodesI = lopcode.OpCodesI; +const Proto    = lfunc.Proto; +const R        = llex.RESERVED; +const TValue   = lobject.TValue; +const Table    = lobject.Table; +const UnOpr    = lcode.UnOpr; +const UpVal    = lfunc.UpVal; + +const MAXVARS = 200; + +const hasmultret = function(k) { +    return k === expkind.VCALL || k === expkind.VVARARG; +}; + +class BlockCnt { +    constructor() { +        this.previous = null;  /* chain */ +        this.firstlabel = NaN; /* index of first label in this block */ +        this.firstgoto = NaN;  /* index of first pending goto in this block */ +        this.nactvar = NaN;    /* # active locals outside the block */ +        this.upval = NaN;      /* true if some variable in the block is an upvalue */ +        this.isloop = NaN;     /* true if 'block' is a loop */ +    } +} + +const expkind = { +    VVOID: 0,        /* when 'expdesc' describes the last expression a list, +                        this kind means an empty list (so, no expression) */ +    VNIL: 1,         /* constant nil */ +    VTRUE: 2,        /* constant true */ +    VFALSE: 3,       /* constant false */ +    VK: 4,           /* constant in 'k'; info = index of constant in 'k' */ +    VKFLT: 5,        /* floating constant; nval = numerical float value */ +    VKINT: 6,        /* integer constant; nval = numerical integer value */ +    VNONRELOC: 7,    /* expression has its value in a fixed register; +                        info = result register */ +    VLOCAL: 8,       /* local variable; info = local register */ +    VUPVAL: 9,       /* upvalue variable; info = index of upvalue in 'upvalues' */ +    VINDEXED: 10,    /* indexed variable; +                        ind.vt = whether 't' is register or upvalue; +                        ind.t = table register or upvalue; +                        ind.idx = key's R/K index */ +    VJMP: 11,        /* expression is a test/comparison; +                        info = pc of corresponding jump instruction */ +    VRELOCABLE: 12,  /* expression can put result in any register; +                        info = instruction pc */ +    VCALL: 13,       /* expression is a function call; info = instruction pc */ +    VVARARG: 14      /* vararg expression; info = instruction pc */ +}; + +const vkisvar = function(k) { +    return expkind.VLOCAL <= k && k <= expkind.VINDEXED; +}; + +const vkisinreg = function(k) { +    return k === expkind.VNONRELOC || k === expkind.VLOCAL; +}; + +class expdesc { +    constructor() { +        this.k = NaN; +        this.u = { +            ival: NaN,    /* for VKINT */ +            nval: NaN,    /* for VKFLT */ +            info: NaN,    /* for generic use */ +            ind: {        /* for indexed variables (VINDEXED) */ +                idx: NaN, /* index (R/K) */ +                t: NaN,   /* table (register or upvalue) */ +                vt: NaN   /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */ +            } +        }; +        this.t = NaN;     /* patch list of 'exit when true' */ +        this.f = NaN;     /* patch list of 'exit when false' */ +    } +} + +class FuncState { +    constructor() { +        this.f = null;         /* current function header */ +        this.prev = null;      /* enclosing function */ +        this.ls = null;        /* lexical state */ +        this.bl = null;        /* chain of current blocks */ +        this.pc = NaN;         /* next position to code (equivalent to 'ncode') */ +        this.lasttarget = NaN; /* 'label' of last 'jump label' */ +        this.jpc = NaN;        /* list of pending jumps to 'pc' */ +        this.nk = NaN;         /* number of elements in 'k' */ +        this.np = NaN;         /* number of elements in 'p' */ +        this.firstlocal = NaN; /* index of first local var (in Dyndata array) */ +        this.nlocvars = NaN;   /* number of elements in 'f->locvars' */ +        this.nactvar = NaN;    /* number of active local variables */ +        this.nups = NaN;       /* number of upvalues */ +        this.freereg = NaN;    /* first free register */ +    } +} + +    /* description of active local variable */ +class Vardesc { +    constructor() { +        this.idx = NaN;  /* variable index in stack */ +    } +} + + +/* description of pending goto statements and label statements */ +class Labeldesc { +    constructor() { +        this.name = null;  /* label identifier */ +        this.pc = NaN;  /* position in code */ +        this.line = NaN;  /* line where it appeared */ +        this.nactvar = NaN;  /* local level where it appears in current block */ +    } +} + + +/* list of labels or gotos */ +class Labellist { +    constructor() { +        this.arr = []; /* array */ +        this.n = NaN;  /* number of entries in use */ +        this.size = NaN;  /* array size */ +    } +} + +/* dynamic structures used by the parser */ +class Dyndata { +    constructor() { +        this.actvar = {  /* list of active local variables */ +            arr: [], +            n: NaN, +            size: NaN +        }; +        this.gt = new Labellist(); +        this.label = new Labellist(); +    } +} + +const semerror = function(ls, msg) { +    ls.t.token = 0;  /* remove "near <token>" from final message */ +    llex.luaX_syntaxerror(ls, msg); +}; + +const error_expected = function(ls, token) { +    llex.luaX_syntaxerror(ls, `${llex.luaX_token2str(ls, token)} expected`); +}; + +const errorlimit = function(fs, limit, what) { +    let L = fs.ls.L; +    let line = fs.f.linedefined; +    let where = (line === 0) ? "main function" : `function at line ${line}`; +    let msg = `too many ${what} (limit is ${limit}) in ${where}`; +    llex.luaX_syntaxerror(fs.ls, msg); +}; + +const checklimit = function(fs, v, l, what) { +    if (v > l) errorlimit(fs, l, what); +}; + +const testnext = function(ls, c) { +    if (ls.t.token === c) { +        llex.luaX_next(ls); +        return true; +    } + +    return false; +}; + +const check = function(ls, c) { +    if (ls.t.token !== c) +        error_expected(ls, c); +}; + +const checknext = function(ls, c) { +    check(ls, c); +    llex.luaX_next(ls); +}; + +const check_condition = function(ls, c, msg) { +    if (!c) +        llex.luaX_syntaxerror(ls, msg); +}; + +const check_match = function(ls, what, who, where) { +    if (!testnext(ls, what)) { +        if (where === ls.linenumber) +            error_expected(ls, what); +        else +            llex.luaX_syntaxerror(ls, +                `${llex.luaX_token2str(ls, what)} expected (to close ${llex.luaX_token2str(ls, who)} at line ${where}`); +    } +}; + +const str_checkname = function(ls) { +    check(ls, R.TK_NAME); +    let ts = ls.t.seminfo.ts; +    llex.luaX_next(ls); +    return ts; +}; + +const init_exp = function(e, k, i) { +    e.f = e.t = lcode.NO_JUMP; +    e.k = k; +    e.u.info = i; +}; + +const codestring = function(ls, e, s) { +    init_exp(e, expkind.VK, lcode.luaK_stringK(ls.fs, s)); +}; + +const checkname = function(ls, e) { +    codestring(ls, e, str_checkname(ls)); +}; + +const registerlocalvar = function(ls, varname) { +    let fs = ls.fs; +    let f = fs.f; +    f.locvars[fs.nlocvars] = new lobject.LocVar(); +    f.locvars[fs.nlocvars].varname = varname; +    return fs.nlocvars++; +}; + +const new_localvar = function(ls, name) { +    let fs = ls.fs; +    let dyd = ls.dyd; +    let reg = registerlocalvar(ls, name); +    checklimit(fs, dyd.actvar.n + 1 - fs.firstlocal, MAXVARS, "local variables"); +    dyd.actvar.arr[dyd.actvar.n] = new Vardesc(); +    dyd.actvar.arr[dyd.actvar.n].idx = reg; +    dyd.actvar.n++; +}; + +const new_localvarliteral = function(ls, name) { +    new_localvar(ls, new TValue(lua.CT.LUA_TLNGSTR, name)); +}; + +const getlocvar = function(fs, i) { +    let idx = fs.ls.dyd.actvar.arr[fs.firstlocal + i].idx; +    assert(idx < fs.nlocvars); +    return fs.f.locvars[idx]; +}; + +const adjustlocalvars = function(ls, nvars) { +    let fs = ls.fs; +    fs.nactvar = fs.nactvar + nvars; +    for (; nvars; nvars--) +        getlocvar(fs, fs.nactvar - nvars).startpc = fs.pc; +}; + +const removevars = function(fs, tolevel) { +    fs.ls.dyd.actvar.n -= fs.nactvar - tolevel; +    while (fs.nactvar > tolevel) +        getlocvar(fs, --fs.nactvar).endpc = fs.pc; +}; + +const searchupvalue = function(fs, name) { +    let up = fs.f.upvalues; +    for (let i = 0; i < fs.nups; i++) { +        if (up[i].name.value === name.value) +            return i; +    } +    return -1;  /* not found */ +}; + +const newupvalue = function(fs, name, v) { +    let f = fs.f; +    checklimit(fs, fs.nups + 1, lfunc.MAXUPVAL, "upvalues"); +    f.upvalues[fs.nups] = new UpVal(fs.ls.L); +    f.upvalues[fs.nups].instack = v.k === expkind.VLOCAL; +    f.upvalues[fs.nups].idx = v.u.info; +    f.upvalues[fs.nups].name = name; +    return fs.nups++; +}; + +const searchvar = function(fs, n) { +    for (let i = fs.nactvar - 1; i >= 0; i--) { +        if (n.value === getlocvar(fs, i).varname.value) +            return i; +    } + +    return -1; +}; + +/* +** Mark block where variable at given level was defined +** (to emit close instructions later). +*/ +const markupval = function(fs, level) { +    let bl = fs.bl; +    while (bl.nactvar > level) +        bl = bl.previous; +    bl.upval = 1; +}; + +/* +** Find variable with given name 'n'. If it is an upvalue, add this +** upvalue into all intermediate functions. +*/ +const singlevaraux = function(fs, n, vr, base) { +    if (fs === null)  /* no more levels? */ +        init_exp(vr, expkind.VVOID, 0);  /* default is global */ +    else { +        let v = searchvar(fs, n);  /* look up locals at current level */ +        if (v >= 0) {  /* found? */ +            init_exp(vr, expkind.VLOCAL, v);  /* variable is local */ +            if (!base) +                markupval(fs, v);  /* local will be used as an upval */ +        } else {  /* not found as local at current level; try upvalues */ +            let idx = searchupvalue(fs, n);  /* try existing upvalues */ +            if (idx < 0) {  /* not found? */ +                singlevaraux(fs.prev, n, vr, 0);  /* try upper levels */ +                if (vr.k === expkind.VVOID)  /* not found? */ +                    return;  /* it is a global */ +                /* else was LOCAL or UPVAL */ +                idx = newupvalue(fs, n, vr);  /* will be a new upvalue */ +            } +            init_exp(vr, expkind.VUPVAL, idx);  /* new or old upvalue */ +        } +    } +}; + +const singlevar = function(ls, vr) { +    let varname = str_checkname(ls); +    let fs = ls.fs; +    singlevaraux(fs, varname, vr, 1); +    if (vr.k === expkind.VVOID) {  /* global name? */ +        let key = new expdesc(); +        singlevaraux(fs, ls.envn, vr, 1);  /* get environment variable */ +        assert(vr.k !== expkind.VVOID);  /* this one must exist */ +        codestring(ls, key, varname);  /* key is variable name */ +        lcode.luaK_indexed(fs, vr, key);  /* env[varname] */ +    } +}; + +const adjust_assign = function(ls, nvars, nexps, e) { +    let fs = ls.fs; +    let extra = nvars - nexps; +    if (hasmultret(e.k)) { +        extra++;  /* includes call itself */ +        if (extra < 0) extra = 0; +        lcode.luaK_setreturns(fs, e, extra);  /* last exp. provides the difference */ +        if (extra > 1) lcode.luaK_reserveregs(fs, extra - 1); +    } else { +        if (e.k !== expkind.VVOID) lcode.luaK_exp2nextreg(fs, e);  /* close last expression */ +        if (extra > 0) { +            let reg = fs.freereg; +            lcode.luaK_reserveregs(fs, extra); +            lcode.luaK_nil(fs, reg, extra); +        } +    } +    if (nexps > nvars) +        ls.fs.freereg -= nexps - nvars;  /* remove extra values */ +}; + +const enterlevel = function(ls) { +    let L = ls.L; +    ++L.nCcalls; +    checklimit(ls.fs, L.nCcalls, llimit.LUAI_MAXCCALLS, "JS levels"); +}; + +const leavelevel = function(ls) { +    return ls.L.nCcalls--; +}; + +const closegoto = function(ls, g, label) { +    let fs = ls.fs; +    let gl = ls.dyd.gt; +    let gt = gl.arr[g]; +    assert(gt.name.value === label.name.value); +    if (gt.nactvar < label.nactvar) { +        let vname = getlocvar(fs, gt.nactvar).varname; +        semerror(ls, `<goto ${gt.name.value}> at line ${gt.line} jumps into the scope of local '${vname.value}'`); +    } +    lcode.luaK_patchlist(fs, gt.pc, label.pc); +    /* remove goto from pending list */ +    for (let i = g; i < gl.n - 1; i++) +        gl.arr[i] = gl.arr[i + 1]; +    gl.n--; +}; + +/* +** try to close a goto with existing labels; this solves backward jumps +*/ +const findlabel = function(ls, g) { +    let bl = ls.fs.bl; +    let dyd = ls.dyd; +    let gt = dyd.gt.arr[g]; +    /* check labels in current block for a match */ +    for (let i = bl.firstlabel; i < dyd.label.n; i++) { +        let lb = dyd.label.arr[i]; +        if (lb.name.value === gt.name.value) {  /* correct label? */ +            if (gt.nactvar > lb.nactvar && (bl.upval || dyd.label.n > bl.firstlabel)) +                lcode.luaK_patchclose(ls.fs, gt.pc, lb.nactvar); +            closegoto(ls, g, lb);  /* close it */ +            return true; +        } +    } +    return false;  /* label not found; cannot close goto */ +}; + +const newlabelentry = function(ls, l, name, line, pc) { +    let n = l.n; +    l.arr[n] = new Labeldesc(); +    l.arr[n].name = name; +    l.arr[n].line = line; +    l.arr[n].nactvar = ls.fs.nactvar; +    l.arr[n].pc = pc; +    l.n = n + 1; +    return n; +}; + +/* +** check whether new label 'lb' matches any pending gotos in current +** block; solves forward jumps +*/ +const findgotos = function(ls, lb) { +    let gl = ls.dyd.gt; +    let i = ls.fs.bl.firstgoto; +    while (i < gl.n) { +        if (gl.arr[i].name.value === lb.name.value) +            closegoto(ls, i, lb); +        else +            i++; +    } +}; + +/* +** export pending gotos to outer level, to check them against +** outer labels; if the block being exited has upvalues, and +** the goto exits the scope of any variable (which can be the +** upvalue), close those variables being exited. +*/ +const movegotosout = function(fs, bl) { +    let i = bl.firstgoto; +    let gl = fs.ls.dydy.gt; +    /* correct pending gotos to current block and try to close it +       with visible labels */ +    while (i < gl.n) { +        let gt = gl.arr[i]; +        if (gt.nactvar > bl.nactvar) { +            if (bl.upval) +                lcode.luaK_patchclose(fs, gt.pc, bl.nactvar); +            gt.nactvar = bl.nactvar; +        } +        if (!findlabel(fs.ls, i)) +            i++;  /* move to next one */ +    } +}; + +const enterblock = function(fs, bl, isloop) { +    bl.isloop = isloop; +    bl.nactvar = fs.nactvar; +    bl.firstlabel = fs.ls.dyd.label.n; +    bl.firstgoto = fs.ls.dyd.gt.n; +    bl.upval = 0; +    bl.previous = fs.bl; +    fs.bl = bl; +    assert(fs.freereg === fs.nactvar); +}; + +/* +** create a label named 'break' to resolve break statements +*/ +const breaklabel = function(ls) { +    let n = new TValue(lua.CT.LUA_TLNGSTR, "break"); +    let l = newlabelentry(ls, ls.dyd.label, n, 0, ls.fs.pc); +    findgotos(ls, ls.dyd.label.arr[l]); +}; + +/* +** generates an error for an undefined 'goto'; choose appropriate +** message when label name is a reserved word (which can only be 'break') +*/ +const undefgoto = function(ls, gt) { +    const msg = llex.isreserved(gt.name.value) +                ? `<${gt.name.value}> at line ${gt.line} not inside a loop` +                : `no visible label '${gt.name.value}' for <goto> at line ${gt.line}`; +    semerror(ls, msg); +}; + +/* +** adds a new prototype into list of prototypes +*/ +const addprototype = function(ls) { +    let clp = new Proto(); +    let L = ls.L; +    let fs = ls.fs; +    let f = fs.f;  /* prototype of current function */ +    f.p[fs.np++] = clp; +    return clp; +}; + +/* +** codes instruction to create new closure in parent function. +*/ +const codeclosure = function(ls, v) { +    let fs = ls.fs.prev; +    init_exp(v, expkind.VRELOCABLE, lcode.luaK_codeABx(fs, OpCodesI.OP_CLOSURE, 0, fs.np -1)); +    lcode.luaK_exp2nextreg(fs, v);  /* fix it at the last register */ +}; + +const open_func = function(ls, fs, bl) { +    fs.prev = ls.fs;  /* linked list of funcstates */ +    fs.ls = ls; +    ls.fs = fs; +    fs.pc = 0; +    fs.lasttarget = 0; +    fs.jpc = lcode.NO_JUMP; +    fs.freereg = 0; +    fs.nk = 0; +    fs.np = 0; +    fs.nups = 0; +    fs.nlocvars = 0; +    fs.nactvar = 0; +    fs.firstlocal = ls.dyd.actvar.n; +    fs.bl = null; +    let f = new Proto(); +    f = fs.f; +    f.source = ls.source; +    f.maxstacksize = 2;  /* registers 0/1 are always valid */ +    enterblock(fs, bl, false); +}; + +const leaveblock = function(fs) { +    let bl = fs.bl; +    let ls = fs.ls; +    if (bl.previous && bl.upval) { +        /* create a 'jump to here' to close upvalues */ +        let j = lcode.luaK_jump(fs); +        lcode.luaK_patchclose(fs, j , bl.nactvar); +        lcode.luaK_patchtohere(fs, j); +    } + +    if (bl.isloop) +        breaklabel(ls);  /* close pending breaks */ + +    fs.bl = bl.previous; +    removevars(fs, bl.nactvar); +    assert(bl.nactvar === fs.nactvar); +    fs.freereg = fs.nactvar;  /* free registers */ +    ls.dyd.label.n = bl.firstlabel;  /* remove local labels */ +    if (bl.previous)  /* inner block? */ +        movegotosout(fs, bl);  /* update pending gotos to outer block */ +    else if (bl.firstgoto < ls.dyd.gt.n)  /* pending gotos in outer block? */ +        undefgoto(ls, ls.dyd.gt.arr[bl.firstgoto]);  /* error */ +}; + +const close_func = function(ls) { +    let L = ls.L; +    let fs = ls.fs; +    let f = fs.f; +    lcode.luaK_ret(fs, 0, 0);  /* final return */ +    leaveblock(fs); +    ls.fs = fs.prev; +}; + +/*============================================================*/ +/* GRAMMAR RULES */ +/*============================================================*/ + +const block_follow = function(ls, withuntil) { +    switch (ls.t.token) { +        case R.TK_ELSE: case R.TK_ELSEIF: +        case R.TK_END: case R.TK_EOS: +            return true; +        case R.TK_UNTIL: return withuntil; +        default: return false; +    } +}; + +const statlist = function(ls) { +    /* statlist -> { stat [';'] } */ +    while (!block_follow(ls, 1)) { +        if (ls.t.token === R.TK_RETURN) { +            statement(ls); +            return;  /* 'return' must be last statement */ +        } +        statement(ls); +    } +}; + +const fieldsel = function(ls, v) { +    /* fieldsel -> ['.' | ':'] NAME */ +    let fs = ls.fs; +    let key = new expdesc(); +    lcode.luaK_exp2anyregup(fs, v); +    llex.luaX_next(ls);  /* skip the dot or colon */ +    checkname(ls, key); +    lcode.luaK_indexed(fs, v, key); +}; + +const yindex = function(ls, v) { +    /* index -> '[' expr ']' */ +    llex.luaX_next(ls);  /* skip the '[' */ +    expr(ls, v); +    lcode.luaK_exp2val(ls.fs, v); +    checknext(ls, ']'); +}; + +/* +** {====================================================================== +** Rules for Constructors +** ======================================================================= +*/ + +class ConsControl { +    constructor() { +        this.v = new expdesc(); /* last list item read */ +        this.t = new expdesc(); /* table descriptor */ +        this.nh = NaN;          /* total number of 'record' elements */ +        this.na = NaN;          /* total number of array elements */ +        this.tostore = NaN;     /* number of array elements pending to be stored */ +    } +} + +const recfield = function(ls, cc) { +    /* recfield -> (NAME | '['exp1']') = exp1 */ +    let fs = ls.fs; +    let reg = ls.fs.freereg; +    let key = new expdesc(); +    let val = new expdesc(); + +    if (ls.t.token === R.TK_NAME) { +        checklimit(fs, cc.nh, Number.MAX_SAFE_INTEGER, "items in a constructor"); +        checkname(ls, key); +    } else  /* ls->t.token === '[' */ +        yindex(ls, key); +    cc.nh++; +    checknext(ls, '='); +    let rkkey = lcode.luaK_exp2RK(fs, key); +    expr(ls, val); +    lcode.luaK_codeABC(fs, OpCodesI.OP_SETTABLE, cc.t.u.info, rkkey, lcode.luaK_exp2RK(fs, val)); +    fs.freereg = reg;  /* free registers */ +}; + +const closelistfield = function(fs, cc) { +    if (cc.v.k === expkind.VVOID) return;  /* there is no list item */ +    lcode.luaK_exp2nextreg(fs, cc.v); +    cc.v.k = expkind.VVOID; +    if (cc.tostore === lopcode.LFIELDS_PER_FLUSH) { +        lcode.luaK_setlist(fs, cc.t.u.info, cc.na, cc.tostore);  /* flush */ +        cc.tostore = 0;  /* no more items pending */ +    } +}; + +const lastlistfield = function(fs, cc) { +    if (cc.tostore === 0) return; +    if (hasmultret(cc.v.k)) { +        lcode.luaK_setmultret(fs, cc.v); +        lcode.luaK_setlist(fs, cc.t.u.info, cc.na, lua.LUA_MULTRET); +        cc.na--;  /* do not count last expression (unknown number of elements) */ +    } else { +        if (cc.v.k !== expkind.VVOID) +            lcode.luaK_exp2nextreg(fs, cc.v); +        lcode.luaK_setlist(fs, cc.t.u.info, cc.na, cc.tostore); +    } +}; + +const listfield = function(ls, cc) { +    /* listfield -> exp */ +    expr(ls, cc.v); +    checklimit(ls.fs, cc.na, Number.MAX_SAFE_INTEGER, "items in a constructor"); +    cc.na++; +    cc.tostore++; +}; + +const field = function(ls, cc) { +    /* field -> listfield | recfield */ +    switch (ls.t.token) { +        case R.TK_NAME: {  /* may be 'listfield' or 'recfield' */ +            if (llex.luaX_lookahead(ls) !== '=')  /* expression? */ +                listfield(ls, cc); +            else +                recfield(ls, cc); +            break; +        } +        case '[': { +            recfield(ls, cc); +            break; +        } +        default: { +            listfield(ls, cc); +            break; +        } +    } +}; + +const constructor = function(ls, t) { +    /* constructor -> '{' [ field { sep field } [sep] ] '}' +       sep -> ',' | ';' */ +    let fs = ls.fs; +    let line = ls.linenumber; +    let pc = lcode.luaK_codeABC(fs, OpCodesI.OP_NEWTABLE, 0, 0, 0); +    let cc = new ConsControl(); +    cc.na = cc.nh = cc.tostore = 0; +    cc.t = t; +    init_exp(t, expkind.VRELOCABLE, pc); +    init_exp(cc.v, expkind.VVOID, 0);  /* no value (yet) */ +    lcode.luaK_exp2nextreg(ls.fs, t);  /* fix it at stack top */ +    checknext(ls, '{'); +    do { +        assert(cc.v.k === expkind.VVOID || cc.tostore > 0); +        if (ls.t.token === '}') break; +        closelistfield(fs, cc); +        field(ls, cc); +    } while (testnext(ls, ',') || testnext(ls, ';')); +    check_match(ls, '}', '{', line); +    lastlistfield(fs, cc); +    lopcode.SETARG_B(fs.f.code[pc], lobject.luaO_int2fb(cc.na));  /* set initial array size */ +    lopcode.SETARG_C(fs.f.code[pc], lobject.luaO_int2fb(cc.nh));  /* set initial table size */ +}; + +/* }====================================================================== */ + +const parlist = function(ls) { +    /* parlist -> [ param { ',' param } ] */ +    let fs = ls.fs; +    let f = fs.f; +    let nparams = 0; +    f.is_vararg = 0; +    if (ls.t.token !== ')') {  /* is 'parlist' not empty? */ +        do { +            switch (ls.t.token) { +                case R.TK_NAME: {  /* param -> NAME */ +                    new_localvar(ls, str_checkname(ls)); +                    nparams++; +                    break; +                } +                case R.TK_DOTS: {  /* param -> '...' */ +                    llex.luaX_next(ls); +                    f.is_vararg = 1;  /* declared vararg */ +                    break; +                } +                default: llex.luaX_syntaxerror(ls, "<name> or '...' expected"); +            } +        } while(!f.is_vararg && testnext(ls, ',')); +    } +    adjustlocalvars(ls, nparams); +    f.numparams = fs.nactvar; +    lcode.luaK_reserveregs(fs, fs.nactvar);  /* reserve register for parameters */ +}; + +const body = function(ls, e, ismethod, line) { +    /* body ->  '(' parlist ')' block END */ +    let new_fs = new FuncState(); +    let bl = new BlockCnt(); +    new_fs.f = addprototype(ls); +    new_fs.f.linedefined = line; +    open_func(ls, new_fs, bl); +    checknext(ls, '('); +    if (ismethod) { +        new_localvarliteral(ls, "self");  /* create 'self' parameter */ +        adjustlocalvars(ls, 1); +    } +    parlist(ls); +    checknext(ls, ')'); +    statlist(ls); +    new_fs.f.lastlinedefined = ls.linenumber; +    check_match(ls, R.TK_END, R.TK_FUNCTION, line); +    codeclosure(ls, e); +    close_func(ls); +}; + +const explist = function(ls, v) { +    /* explist -> expr { ',' expr } */ +    let n = 1;  /* at least one expression */ +    expr(ls, v); +    while (testnext(ls, ',')) { +        lcode.luaK_exp2nextreg(ls.fs, v); +        expr(ls, v); +        n++; +    } +    return n; +}; + +const funcargs = function(ls, f, line) { +    let fs = ls.fs; +    let args = new expdesc(); +    switch (ls.t.token) { +        case '(': {  /* funcargs -> '(' [ explist ] ')' */ +            llex.luaX_next(ls); +            if (ls.t.token === ')')  /* arg list is empty? */ +                args.k = expkind.VVOID; +            else { +                explist(ls, args); +                lcode.luaK_setmultret(fs, args); +            } +            check_match(ls, ')', '(', line); +            break; +        } +        case '{': {  /* funcargs -> constructor */ +            constructor(ls, args); +            break; +        } +        case R.TK_STRING: {  /* funcargs -> STRING */ +            codestring(ls, args, ls.t.seminfo.ts); +            llex.luaX_next(ls);  /* must use 'seminfo' before 'next' */ +            break; +        } +        default: { +            llex.luaX_syntaxerror(ls, "function arguments expected"); +        } +    } +    assert(f.k === expkind.VNONRELOC); +    let nparams; +    let base = f.u.info;  /* base register for call */ +    if (hasmultret(args.k)) +        nparams = lua.LUA_MULTRET;  /* open call */ +    else { +        if (args.k !== expkind.VVOID) +            lcode.luaK_exp2nextreg(fs, args);  /* close last argument */ +        nparams = fs.freereg - (base+1); +    } +    init_exp(f, expkind.VCALL, lcode.luaK_codeABC(fs, OpCodesI.OP_CALL, base, nparams+1, 2)); +    lcode.luaK_fixline(fs, line); +    fs.freereg = base + 1; /* call remove function and arguments and leaves (unless changed) one result */ +}; + +/* +** {====================================================================== +** Expression parsing +** ======================================================================= +*/ + +const primaryexp = function(ls, v) { +    /* primaryexp -> NAME | '(' expr ')' */ +    switch (ls.t.token) { +        case '(': { +            let line = ls.linenumber; +            llex.luaX_next(ls); +            expr(ls, v); +            check_match(ls, ')', '(', line); +            lcode.luaK_dischargevars(ls.fs, v); +            return; +        } +        case R.TK_NAME: { +            singlevar(ls, v); +            return; +        } +        default: { +            llex.luaX_syntaxerror(ls, "unexpected symbol"); +        } +    } +}; + +const suffixedexp = function(ls, v) { +    /* suffixedexp -> +       primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */ +    let fs = ls.fs; +    let line = ls.linenumber; +    primaryexp(ls, v); +    for (;;) { +        switch (ls.t.token) { +            case '.': {  /* fieldsel */ +                fieldsel(ls, v); +                break; +            } +            case '[': {  /* '[' exp1 ']' */ +                let key = new expdesc(); +                lcode.luaK_exp2anyregup(fs, v); +                yindex(ls, key); +                lcode.luaK_indexed(fs, v, key); +                break; +            } +            case ':': {  /* ':' NAME funcargs */ +                let key = new expdesc(); +                llex.luaX_next(ls); +                checkname(ls, key); +                lcode.luaK_self(fs, v, key); +                funcargs(ls, v, line); +                break; +            } +            case '(': case R.TK_STRING: case '{': {  /* funcargs */ +                lcode.luaK_exp2nextreg(fs, v); +                funcargs(ls, v, line); +                break; +            } +            default: return; +        } +    } +}; + +const simpleexp = function(ls, v) { +    /* simpleexp -> FLT | INT | STRING | NIL | TRUE | FALSE | ... | +       constructor | FUNCTION body | suffixedexp */ +    switch (ls.t.token) { +        case R.TK_FLT: { +            init_exp(v, expkind.VFLT, 0); +            v.u.nval = ls.t.seminfo.r; +            break; +        } +        case R.TK_INT: { +            init_exp(v, expkind.VKINT, 0); +            v.u.ival = ls.t.seminfo.i; +            break; +        } +        case R.TK_STRING: { +            codestring(ls, v, ls.t.seminfo.ts); +            break; +        } +        case R.TK_NIL: { +            init_exp(v, expkind.VNIL, 0); +            break; +        } +        case R.TK_TRUE: { +            init_exp(v, expkind.VTRUE, 0); +            break; +        } +        case R.TK_FALSE: { +            init_exp(v, expkind.VFALSE, 0); +            break; +        } +        case R.TK_DOTS: {  /* vararg */ +            let fs = ls.fs; +            check_condition(ls, fs.f.is_vararg, "cannot use '...' outside a vararg function"); +            init_exp(v, expkind.VVARARG, lcode.luaK_codeABC(fs, OpCodesI.OP_VARARG, 0, 1, 0)); +            break; +        } +        case '{': {  /* constructor */ +            constructor(ls, v); +            return; +        } +        case R.TK_FUNCTION: { +            llex.luaX_next(ls); +            body(ls, v, 0, ls.linenumber); +            return; +        } +        default: { +            suffixedexp(ls, v); +            return; +        } +    } +    llex.luaX_next(ls); +}; + +const getunopr = function(op) { +    switch (op) { +        case R.TK_NOT: return UnOpr.OPR_NOT; +        case '-':      return UnOpr.OPR_MINUS; +        case '~':      return UnOpr.OPR_BNOT; +        case '#':      return UnOpr.OPR_LEN; +        default:       return UnOpr.OPR_NOUNOPR; +    } +}; + +const getbinopr = function(op) { +    switch (op) { +        case '+':         return BinOpr.OPR_ADD; +        case '-':         return BinOpr.OPR_SUB; +        case '*':         return BinOpr.OPR_MUL; +        case '%':         return BinOpr.OPR_MOD; +        case '^':         return BinOpr.OPR_POW; +        case '/':         return BinOpr.OPR_DIV; +        case R.TK_IDIV:   return BinOpr.OPR_IDIV; +        case '&':         return BinOpr.OPR_BAND; +        case '|':         return BinOpr.OPR_BOR; +        case '~':         return BinOpr.OPR_BXOR; +        case R.TK_SHL:    return BinOpr.OPR_SHL; +        case R.TK_SHR:    return BinOpr.OPR_SHR; +        case R.TK_CONCAT: return BinOpr.OPR_CONCAT; +        case R.TK_NE:     return BinOpr.OPR_NE; +        case R.TK_EQ:     return BinOpr.OPR_EQ; +        case '<':         return BinOpr.OPR_LT; +        case R.TK_LE:     return BinOpr.OPR_LE; +        case '>':         return BinOpr.OPR_GT; +        case R.TK_GE:     return BinOpr.OPR_GE; +        case R.TK_AND:    return BinOpr.OPR_AND; +        case R.TK_OR:     return BinOpr.OPR_OR; +        default:          return BinOpr.OPR_NOBINOPR; +    } +}; + +const priority = [  /* ORDER OPR */ +    {left: 10, right: 10}, {left: 10, right: 10},     /* '+' '-' */ +    {left: 11, right: 11}, {left: 11, right: 11},     /* '*' '%' */ +    {left: 14, right: 13},               /* '^' (right associative) */ +    {left: 11, right: 11}, {left: 11, right: 11},     /* '/' '//' */ +    {left: 6, right: 6}, {left: 4, right: 4}, {left: 5, right: 5}, /* '&' '|' '~' */ +    {left: 7, right: 7}, {left: 7, right: 7},         /* '<<' '>>' */ +    {left: 9, right: 8},                 /* '..' (right associative) */ +    {left: 3, right: 3}, {left: 3, right: 3}, {left: 3, right: 3}, /* ==, <, <= */ +    {left: 3, right: 3}, {left: 3, right: 3}, {left: 3, right: 3}, /* ~=, >, >= */ +    {left: 2, right: 2}, {left: 1, right: 1}          /* and, or */ +]; + +const UNARY_PRIORITY = 12; + +/* +** subexpr -> (simpleexp | unop subexpr) { binop subexpr } +** where 'binop' is any binary operator with a priority higher than 'limit' +*/ +const subexpr = function(ls, v, limit) { +    enterlevel(ls); +    let uop = getunopr(ls.t.token); +    if (uop !== UnOpr.OPR_NOUNOPR) { +        let line = ls.linenumber; +        llex.luaX_next(ls); +        subexpr(ls, v, UNARY_PRIORITY); +        lcode.luaK_prefix(ls.fs, uop, v, line); +    } else +        simpleexp(ls, v); +    /* expand while operators have priorities higher than 'limit' */ +    let op = getbinopr(ls.t.token); +    while (op !== BinOpr.OPR_NOBINOPR && priority[op].left > limit) { +        let v2 = new expdesc(); +        let line = ls.linenumber; +        llex.luaX_next(ls); +        lcode.luaK_infix(ls.fs, op, v); +        /* read sub-expression with higher priority */ +        let nextop = subexpr(ls, v2, priority[op].right); +        lcode.luaK_posfix(ls.fs, op, v, v2, line); +        op = nextop; +    } +    leavelevel(ls); +    return op;  /* return first untreated operator */ +}; + +const expr = function(ls, v) { +    subexpr(ls, v, 0); +}; + +/* }==================================================================== */ + + + +/* +** {====================================================================== +** Rules for Statements +** ======================================================================= +*/ + +const block = function(ls) { +    /* block -> statlist */ +    let fs = ls.fs; +    let bl = new BlockCnt(); +    enterblock(fs, bl, 0); +    statlist(ls); +    leaveblock(fs); +}; + +/* +** structure to chain all variables in the left-hand side of an +** assignment +*/ +class LHS_assign { +    constructor() { +        this.prev = null; +        this.v = new expdesc();  /* variable (global, local, upvalue, or indexed) */ +    } +} + +/* +** check whether, in an assignment to an upvalue/local variable, the +** upvalue/local variable is begin used in a previous assignment to a +** table. If so, save original upvalue/local value in a safe place and +** use this safe copy in the previous assignment. +*/ +const check_conflict = function(ls, lh, v) { +    let fs = ls.fs; +    let extra = fs.freereg;  /* eventual position to save local variable */ +    let conflict = false; +    for (; lh; lh = lh.prev) {  /* check all previous assignments */ +        if (lh.v.k === expkind.VINDEXED) {  /* assigning to a table? */ +            /* table is the upvalue/local being assigned now? */ +            if (lh.v.u.ind.vt === v.k && lh.v.u.ind.t === v.u.info) { +                conflict = true; +                lh.v.u.ind.vt = expkind.VLOCAL; +                lh.v.u.ind.t = extra;  /* previous assignment will use safe copy */ +            } +            /* index is the local being assigned? (index cannot be upvalue) */ +            if (v.k === expkind.VLOCAL && lh.v.u.ind.idx === v.u.info) { +                conflict = true; +                lh.v.u.ind.idx = extra;  /* previous assignment will use safe copy */ +            } +        } +    } +    if (conflict) { +        /* copy upvalue/local value to a temporary (in position 'extra') */ +        let op = v.k === expkind.VLOCAL ? OpCodesI.OP_MOVE : OpCodesI.OP_GETUPVAL; +        lcode.luaK_codeABC(fs, op, extra, v.u.info, 0); +        lcode.luaK_reserveregs(fs, 1); +    } +}; + +const assignment = function(ls, lh, nvars) { +    let e = new expdesc(); +    check_condition(ls, vkisvar(lh.v.k), "syntax error"); +    if (testnext(ls, ',')) {  /* assignment -> ',' suffixedexp assignment */ +        let nv = new LHS_assign(); +        nv.prev = lh; +        suffixedexp(ls, nv.v); +        if (nv.v.k !== expkind.VINDEXED) +            check_conflict(ls, lh, nv.v); +        checklimit(ls.fs, nvars + ls.L.nCcalls, llimit.LUAI_MAXCCALLS, "JS levels"); +        assignment(ls, nv, nvars + 1); +    } else {  /* assignment -> '=' explist */ +        checknext(ls, '='); +        let nexps = explist(ls, e); +        if (nexps !== nvars) +            adjust_assign(ls, nvars, nexps, e); +        else { +            lcode.luaK_setoneret(ls.fs, e);  /* close last expression */ +            lcode.luaK_storevar(ls.fs, lh.v, e); +            return;  /* avoid default */ +        } +    } +    init_exp(e, expkind.VNONRELOC, ls.fs.freereg-1);  /* default assignment */ +    lcode.luaK_storevar(ls.fs, lh.v, e); +}; + +const cond = function(ls) { +    /* cond -> exp */ +    let v = expdesc(); +    expr(ls, v);  /* read condition */ +    if (v.k === expkind.VNIL) v.k = expkind.VFALSE;  /* 'falses' are all equal here */ +    lcode.luaK_goiftrue(ls.fs, v); +    return v; +}; + +const gotostat = function(ls, pc) { +    let line = ls.linenumber; +    let label; +    if (testnext(ls, R.TK_GOTO)) +        label = str_checkname(ls); +    else { +        llex.luaX_next(ls);  /* skip break */ +        label = new TValue(lua.CT.LUA_TLNGSTR, "break"); +    } +    let g = newlabelentry(ls, ls.dyd.gt, label, line, pc); +    findlabel(ls, g);  /* close it if label already defined */ +}; + +/* check for repeated labels on the same block */ +const checkrepeated = function(fs, ll, label) { +    for (let i = fs.bl.firstlabel; i < ll.n; i++) { +        if (label.value === ll.arr[i].name.value) { +            semerror(fs.ls, `label '${label}' already defined on line ${ll.arr[i].line}`); +        } +    } +}; + +/* skip no-op statements */ +const skipnoopstat = function(ls) { +    while (ls.t.token === ';' || ls.t.token === R.TK_DBCOLON) +        statement(ls); +}; + +const labelstat = function(ls, label, line) { +    /* label -> '::' NAME '::' */ +    let fs = ls.fs; +    let ll = ls.dyd.label; +    let l;  /* index of new label being created */ +    checkrepeated(fs, ll, label);  /* check for repeated labels */ +    checknext(ls, R.TK_DBCOLON);  /* skip double colon */ +    /* create new entry for this label */ +    l = newlabelentry(ls, ll, label, line, lcode.luaK_getlabel(fs)); +    skipnoopstat(ls);  /* skip other no-op statements */ +    if (block_follow(ls, 0)) {  /* label is last no-op statement in the block? */ +        /* assume that locals are already out of scope */ +        ll.arr[l].nactvar = fs.bl.nactvar; +    } +    findgotos(ls, ll.arr[l]); +}; + +const whilestat = function(ls, line) { +    /* whilestat -> WHILE cond DO block END */ +    let fs = ls.fs; +    let bl = new BlockCnt(); +    llex.luaX_next(ls);  /* skip WHILE */ +    let whileinit = lcode.luaK_getlabel(fs); +    let condexit = cond(ls); +    enterblock(fs, bl, 1); +    checknext(ls, R.TK_DO); +    block(ls); +    lcode.luaK_jumpto(fs, whileinit); +    check_match(ls, R.TK_END, R.TK_WHILE, line); +    leaveblock(fs); +    lcode.luaK_patchtohere(fs, condexit);  /* false conditions finish the loop */ +}; + +const repeatstat = function(ls, line) { +    /* repeatstat -> REPEAT block UNTIL cond */ +    let fs = ls.fs; +    let repeat_init = lcode.luaK_getlabel(fs); +    let bl1 = new BlockCnt(); +    let bl2 = new BlockCnt(); +    enterblock(fs, bl1, 1);  /* loop block */ +    enterblock(fs, bl2, 0);  /* scope block */ +    llex.luaX_next(ls);  /* skip REPEAT */ +    statlist(ls); +    check_match(ls, R.TK_UNTIL, R.TK_REPEAT, line); +    let condexit = cond(ls);  /* read condition (inside scope block) */ +    if (bl2.upval)  /* upvalues? */ +        lcode.luaK_patchclose(fs, condexit, bl2.nactvar); +    leaveblock(fs);  /* finish scope */ +    lcode.luaK_patchlist(fs, condexit, repeat_init);  /* close the loop */ +    leaveblock(fs);  /* finish loop */ +}; + +const exp1 = function(ls) { +    let e = new expdesc(); +    expr(ls, e); +    lcode.luaK_exp2nextreg(ls.fs, e); +    assert(e.k === expkind.VNONRELOC); +    let reg = e.u.info; +    return reg; +}; + +const forbody = function(ls, base, line, nvars, isnum) { +    /* forbody -> DO block */ +    let bl = new BlockCnt(); +    let fs = ls.fs; +    let endfor; +    adjustlocalvars(ls, 3);  /* control variables */ +    checknext(ls, R.TK_DO); +    let prep = isnum ? lcode.luaK_codeAsBx(fs, OpCodesI.OP_FORPREP, base, lcode.NO_JUMP) : lcode.luaK_jump(fs); +    enterblock(fs, bl, 0);  /* scope for declared variables */ +    adjustlocalvars(ls, nvars); +    lcode.luaK_reserveregs(fs, nvars); +    block(ls); +    leaveblock(fs);  /* end of scope for declared variables */ +    lcode.luaK_patchtohere(fs, prep); +    if (isnum)  /* end of scope for declared variables */ +        endfor = lcode.luaK_codeAsBx(fs, OpCodesI.OP_FORLOOP, base, lcode.NO_JUMP); +    else {  /* generic for */ +        lcode.luaK_codeABC(fs, OpCodesI.OP_TFORLOOP, base + 2, lcode.NO_JUMP); +        lcode.luaK_fixline(fs, line); +        endfor = lcode.luaK_codeAsBx(fs, OpCodesI.OP_TFORLOOP, base + 2, lcode.NO_JUMP); +    } +    lcode.luaK_patchlist(fs, endfor, prep + 1); +    lcode.luaK_fixline(fs, line); +}; + +const fornum = function(ls, varname, line) { +    /* fornum -> NAME = exp1,exp1[,exp1] forbody */ +    let fs = ls.fs; +    let base = fs.freereg; +    new_localvarliteral(ls, "(for index)"); +    new_localvarliteral(ls, "(for limit)"); +    new_localvarliteral(ls, "(for step)"); +    new_localvar(ls, varname); +    checknext(ls, '='); +    exp1(ls);  /* initial value */ +    checknext(ls, ','); +    exp1(ls);  /* limit */ +    if (testnext(ls, ',')) +        exp1(ls);  /* optional step */ +    else {  /* default step = 1 */ +        lcode.luaK_codek(fs, fs.freereg, lcode.luaK_intK(fs, 1)); +        lcode.luaK_reserveregs(fs, 1); +    } +    forbody(ls, base, line, 1, 1); +}; + +const forlist = function(ls, indexname) { +    /* forlist -> NAME {,NAME} IN explist forbody */ +    let fs = ls.fs; +    let e = new expdesc(); +    let nvars = 4;  /* gen, state, control, plus at least one declared var */ +    let base = fs.freereg(); +    /* create control variables */ +    new_localvarliteral(ls, "(for generator)"); +    new_localvarliteral(ls, "(for state)"); +    new_localvarliteral(ls, "(for control)"); +    /* create declared variables */ +    new_localvar(ls, indexname); +    while (testnext(ls, ',')) { +        new_localvar(ls, str_checkname(ls)); +        nvars++; +    } +    checknext(ls, R.TK_IN); +    let line = ls.linenumber; +    adjust_assign(ls, 3, explist(ls, e), e); +    lcode.luaK_checkstack(fs, 3);  /* extra space to call generator */ +    forbody(ls, base, line, nvars - 3, 0); +}; + +const forstat = function(ls, line) { +    /* forstat -> FOR (fornum | forlist) END */ +    let fs = ls.fs; +    let bl = new BlockCnt(); +    enterblock(fs, bl, 1);  /* scope for loop and control variables */ +    llex.luaX_next(ls);  /* skip 'for' */ +    let varname = str_checkname(ls);  /* first variable name */ +    switch (ls.t.token) { +        case '=': fornum(ls, varname, line); break; +        case ',': case R.TK_IN: forlist(ls, varname); break; +        default: llex.luaX_syntaxerror(ls, "'=' or 'in' expected"); +    } +    check_match(ls, R.TK_END, R.TK_FOR, line); +    leaveblock(fs);  /* loop scope ('break' jumps to this point) */ +}; + +const test_then_block = function(ls, escapelist) { +    /* test_then_block -> [IF | ELSEIF] cond THEN block */ +    let bl = new BlockCnt(); +    let fs = ls.fs; +    let v = new expdesc(); +    let jf;  /* instruction to skip 'then' code (if condition is false) */ + +    llex.luaX_next(ls);  /* skip IF or ELSEIF */ +    expr(ls, v);  /* read condition */ +    checknext(ls, R.TK_THEN); + +    if (ls.t.token === R.TK_GOTO || ls.t.token === R.TK_BREAK) { +        lcode.luaK_goiffalse(ls.fs, v);  /* will jump to label if condition is true */ +        enterblock(fs, bl, false);  /* must enter block before 'goto' */ +        gotostat(ls, v.t);   /* handle goto/break */ +        skipnoopstat(ls);  /* skip other no-op statements */ +        if (block_follow(ls, 0)) {  /* 'goto' is the entire block? */ +            leaveblock(fs); +            return;  /* and that is it */ +        } else  /* must skip over 'then' part if condition is false */ +            jf = lcode.luaK_jump(fs); +    } else {  /* regular case (not goto/break) */ +        lcode.luaK_goiftrue(ls.fs, v);  /* skip over block if condition is false */ +        enterblock(fs, bl, false); +        jf = v.f; +    } + +    statlist(ls);  /* 'then' part */ +    leaveblock(fs); +    if (ls.t.token === R.TK_ELSE || ls.t.token === R.TK_ELSEIF)  /* followed by 'else'/'elseif'? */ +        escapelist = lcode.luaK_concat(fs, escapelist, lcode.luaK_jump(fs));  /* must jump over it */ +    lcode.luaK_patchtohere(fs, jf); +}; + +const ifstat = function(ls, line) { +    /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block] END */ +    let fs = ls.fs; +    let escapelist = lcode.NO_JUMP;  /* exit list for finished parts */ +    test_then_block(ls, escapelist);  /* IF cond THEN block */ +    while (ls.t.token === R.TK_ELSEIF) +        test_then_block(ls, escapelist);  /* ELSEIF cond THEN block */ +    if (testnext(ls, R.TK_ELSE)) +        block(ls);  /* 'else' part */ +    check_match(ls, R.TK_END, R.TK_IF, line); +    lcode.luaK_patchtohere(fs, escapelist);  /* patch escape list to 'if' end */ +}; + +const localfunc = function(ls) { +    let b = new expdesc(); +    let fs = ls.fs; +    new_localvar(ls, str_checkname(ls));  /* new local variable */ +    adjustlocalvars(ls, 1);  /* enter its scope */ +    body(ls, b, 0, ls.linenumber);  /* function created in next register */ +    /* debug information will only see the variable after this point! */ +    getlocvar(fs, b.u.info).startpc = fs.pc; +}; + +const localstat = function(ls) { +    /* stat -> LOCAL NAME {',' NAME} ['=' explist] */ +    let nvars = 0; +    let nexps; +    let e = new expdesc(); +    do { +        new_localvar(ls, str_checkname(ls)); +        nvars++; +    } while (testnext(ls, ',')); +    if (testnext(ls, '=')) +        nexps = explist(ls, e); +    else { +        e.k = expkind.VVOID; +        nexps = 0; +    } +    adjust_assign(ls, nvars, nexps, e); +    adjustlocalvars(ls, nvars); +}; + +const funcname = function(ls, v) { +    /* funcname -> NAME {fieldsel} [':' NAME] */ +    let ismethod = 0; +    singlevar(ls, v); +    while (ls.t.token === '.') +        fieldsel(ls, v); +    if (ls.t.token === ':') { +        ismethod = 1; +        fieldsel(ls, v); +    } +    return ismethod; +}; + +const funcstat = function(ls, line) { +    /* funcstat -> FUNCTION funcname body */ +    let v = new expdesc(); +    let b = new expdesc(); +    llex.luaX_next(ls);  /* skip FUNCTION */ +    let ismethod = funcname(ls, v); +    body(ls, b, ismethod, line); +    lcode.luaK_storevar(ls.fs, v, b); +    lcode.luaK_fixline(ls.fs, line);  /* definition "happens" in the first line */ +}; + +const exprstat= function(ls) { +    /* stat -> func | assignment */ +    let fs = ls.fs; +    let v = new LHS_assign(); +    suffixedexp(ls, v.v); +    if (ls.t.token === '=' || ls.t.token === ',') { /* stat . assignment ? */ +        v.prev = null; +        assignment(ls, v, 1); +    } +    else {  /* stat -> func */ +        check_condition(ls, v.v.k === expkind.VCALL, "syntax error"); +        lopcode.SETARG_C(lcode.getinstruction(fs, v.v), 1);  /* call statement uses no results */ +    } +}; + +const retstat = function(ls) { +    /* stat -> RETURN [explist] [';'] */ +    let fs = ls.fs; +    let e = new expdesc(); +    let first, nret;  /* registers with returned values */ +    if (block_follow(ls, 1) || ls.t.token === ';') +        first = nret = 0;  /* return no values */ +    else { +        nret = explist(ls, e);  /* optional return values */ +        if (hasmultret(e.k)) { +            lcode.luaK_setmultret(fs, e); +            if (e.k === expkind.VCALL && nret === 1) {  /* tail call? */ +                lopcode.SET_OPCODE(lcode.getinstruction(fs, e), OpCodesI.OP_TAILCALL); +                assert(lcode.getinstruction(fs, e).A === fs.nactvar); +            } +            first = fs.nactvar; +            nret = lua.LUA_MULTRET;  /* return all values */ +        } else { +            if (nret === 1)  /* only one single value? */ +                first = lcode.luaK_exp2anyreg(fs, e); +            else { +                lcode.luaK_exp2nextreg(fs, e);  /* values must go to the stack */ +                first = fs.nactvar;  /* return all active values */ +                assert(nret === fs.freereg - first); +            } +        } +    } +    lcode.luaK_ret(fs, first, nret); +    testnext(ls, ';');  /* skip optional semicolon */ +}; + +const statement = function(ls) { +    let line = ls.linenumber;  /* may be needed for error messages */ +    enterlevel(ls); +    switch(ls.t.token) { +        case ';': {  /* stat -> ';' (empty statement) */ +            llex.luaX_next(ls);  /* skip ';' */ +            break; +        } +        case R.TK_IF: {  /* stat -> ifstat */ +            ifstat(ls, line); +            break; +        } +        case R.TK_WHILE: {  /* stat -> whilestat */ +            whilestat(ls, line); +            break; +        } +        case R.TK_DO: {  /* stat -> DO block END */ +            llex.luaX_next(ls);  /* skip DO */ +            block(ls); +            check_match(ls, R.TK_END, R.TK_DO, line); +            break; +        } +        case R.TK_FOR: {  /* stat -> forstat */ +            forstat(ls, line); +            break; +        } +        case R.TK_REPEAT: {  /* stat -> repeatstat */ +            repeatstat(ls, line); +            break; +        } +        case R.TK_FUNCTION: {  /* stat -> funcstat */ +            funcstat(ls, line); +            break; +        } +        case R.TK_LOCAL: {  /* stat -> localstat */ +            llex.luaX_next(ls);  /* skip LOCAL */ +            if (testnext(ls, R.TK_FUNCTION))  /* local function? */ +                localfunc(ls); +            else +                localstat(ls); +            break; +        } +        case R.TK_DBCOLON: {  /* stat -> label */ +            llex.luaX_next(ls);  /* skip double colon */ +            labelstat(ls, str_checkname(ls), line); +            break; +        } +        case R.TK_RETURN: {  /* skip double colon */ +            llex.luaX_next(ls);  /* skip RETURN */ +            retstat(ls); +            break; +        } +        case R.TK_BREAK:   /* stat -> breakstat */ +        case R.TK_GOTO: {  /* stat -> 'goto' NAME */ +            gotostat(ls, lcode.luaK_jump(ls.fs)); +            break; +        } +        default: {  /* stat -> func | assignment */ +            exprstat(ls); +            break; +        } +    } + +    assert(ls.fs.f.maxstacksize >= ls.fs.freereg && ls.fs.freereg >= ls.fs.nactvar); +    ls.fs.freereg = ls.fs.nactvar;  /* free registers */ +    leavelevel(ls); +}; + +/* +** compiles the main function, which is a regular vararg function with an +** upvalue named LUA_ENV +*/ +const mainfunc = function(ls, fs) { +    let bl = new BlockCnt(); +    let v = new expdesc(); +    open_func(ls, fs, bl); +    fs.f.is_vararg = true;  /* main function is always declared vararg */ +    init_exp(v, expkind.VLOCAL, 0);  /* create and... */ +    newupvalue(fs, ls.envn, v);  /* ...set environment upvalue */ +    llex.luaX_next(ls);  /* read first token */ +    statlist(ls);  /* parse main body */ +    check(ls, R.TK_EOS); +    close_func(ls); +}; + +const luaY_parser = function(L, z, buff, dyd, name, firstchar) { +    let lexstate = new llex.LexState(); +    let funcstate = new FuncState(); +    let cl = lfunc.luaF_newLclosure(L, 1);  /* create main closure */ +    L.stack[L.top++] = cl; +    lexstate.h = new Table();  /* create table for scanner */ +    L.stack[L.top++] = lexstate.h; +    funcstate.f = cl.p = new Proto(L); +    funcstate.f.source = new TValue(lua.CT.LUA_TLNGSTR, name); +    lexstate.buff = buff; +    lexstate.dyd = dyd; +    dyd.actvar.n = dyd.gt.n = dyd.label.n = 0; +    llex.luaX_setinput(L, lexstate, z, funcstate.f.source, firstchar); +    mainfunc(lexstate, funcstate); +    assert(!funcstate.prev && funcstate.nups === 1 && !lexstate.fs); +    /* all scopes should be correctly finished */ +    assert(dyd.actvar.n === 0 && dyd.gt.n === 0 && dyd.label.n === 0); +    L.top--;  /* remove scanner's table */ +    return cl;  /* closure is on the stack, too */ +}; + + +module.exports.Dyndata     = Dyndata; +module.exports.expkind     = expkind; +module.exports.luaY_parser = luaY_parser; +module.exports.vkisinreg   = vkisinreg;
\ No newline at end of file diff --git a/src/lstate.js b/src/lstate.js index 9a9c9e0..1fc7765 100644 --- a/src/lstate.js +++ b/src/lstate.js @@ -105,7 +105,7 @@ const init_registry = function(L, g) {  /*  ** open parts of the state that may cause memory-allocation errors. -** ('g->version' != NULL flags that the state was completely build) +** ('g->version' !== NULL flags that the state was completely build)  */  const f_luaopen = function(L) {      let g = L.l_G; diff --git a/src/ltablib.js b/src/ltablib.js index fa43523..a7963d3 100644 --- a/src/ltablib.js +++ b/src/ltablib.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */  "use strict";  const assert  = require('assert'); @@ -5,6 +5,9 @@ const assert  = require('assert');  const lualib  = require('./lualib.js');  const luaconf = require('./luaconf.js'); +/* mark for precompiled code ('<esc>Lua') */ +const LUA_SIGNATURE           = "\x1bLua"; +  const LUA_VERSION_MAJOR       = "5";  const LUA_VERSION_MINOR       = "3";  const LUA_VERSION_NUM         = 503; @@ -25,8 +28,10 @@ const FENGARI_RELEASE         = FENGARI_VERSION + "." + FENGARI_VERSION_RELEASE;  const FENGARI_COPYRIGHT       = FENGARI_RELEASE + "  Copyright (C) 2017 BenoƮt Giannangeli\nBased on: " + LUA_COPYRIGHT;  const FENGARI_AUTHORS         = "B. Giannangeli"; +const LUA_VERSUFFIX           = "_" + LUA_VERSION_MAJOR + "_" + LUA_VERSION_MINOR; +  const LUA_INIT_VAR            = "LUA_INIT"; -const LUA_INITVARVERSION      = LUA_INIT_VAR + lualib.LUA_VERSUFFIX; +const LUA_INITVARVERSION      = LUA_INIT_VAR + LUA_VERSUFFIX;  const thread_status = {      LUA_OK:        0, @@ -52,6 +57,8 @@ const constant_types = {      LUA_NUMTAGS:        9  }; +const CT = constant_types; +  constant_types.LUA_TSHRSTR = constant_types.LUA_TSTRING | (0 << 4);  /* short strings */  constant_types.LUA_TLNGSTR = constant_types.LUA_TSTRING | (1 << 4);  /* long strings */ @@ -62,6 +69,25 @@ constant_types.LUA_TLCL = constant_types.LUA_TFUNCTION | (0 << 4);  /* Lua closu  constant_types.LUA_TLCF = constant_types.LUA_TFUNCTION | (1 << 4);  /* light C function */  constant_types.LUA_TCCL = constant_types.LUA_TFUNCTION | (2 << 4);  /* C closure */ +/* +** Comparison and arithmetic functions +*/ + +const LUA_OPADD  = 0;   /* ORDER TM, ORDER OP */ +const LUA_OPSUB  = 1; +const LUA_OPMUL  = 2; +const LUA_OPMOD  = 3; +const LUA_OPPOW  = 4; +const LUA_OPDIV  = 5; +const LUA_OPIDIV = 6; +const LUA_OPBAND = 7; +const LUA_OPBOR  = 8; +const LUA_OPBXOR = 9; +const LUA_OPSHL  = 10; +const LUA_OPSHR  = 11; +const LUA_OPUNM  = 12; +const LUA_OPBNOT = 13; +  const LUA_OPEQ = 0;  const LUA_OPLT = 1;  const LUA_OPLE = 2; @@ -87,25 +113,26 @@ const print_version = function() {  class lua_Debug {      constructor() { -        // int event; -        // const char *name;   /* (n) */ -        // const char *namewhat;   /* (n) 'global', 'local', 'field', 'method' */ -        // const char *what;   /* (S) 'Lua', 'C', 'main', 'tail' */ -        // const char *source; /* (S) */ -        // int currentline;    /* (l) */ -        // int linedefined;    /* (S) */ -        // int lastlinedefined;    /* (S) */ -        // unsigned char nups; /* (u) number of upvalues */ -        // unsigned char nparams;/* (u) number of parameters */ -        // char isvararg;        /* (u) */ -        // char istailcall;    /* (t) */ -        // char short_src[LUA_IDSIZE]; /* (S) */ +        this.event = NaN; +        this.name = null;           /* (n) */ +        this.namewhat = null;       /* (n) 'global', 'local', 'field', 'method' */ +        this.what = null;           /* (S) 'Lua', 'C', 'main', 'tail' */ +        this.source = null;         /* (S) */ +        this.currentline = NaN;     /* (l) */ +        this.linedefined = NaN;     /* (S) */ +        this.lastlinedefined = NaN; /* (S) */ +        this.nups = NaN;            /* (u) number of upvalues */ +        this.nparams = NaN;         /* (u) number of parameters */ +        this.isvararg = NaN;        /* (u) */ +        this.istailcall = NaN;      /* (t) */ +        this.short_src = null;      /* (S) */          /* private part */ -        // struct CallInfo *i_ci;  /* active function */ +        this.i_ci = null;           /* active function */      }  } +module.exports.CT                      = CT;  module.exports.FENGARI_AUTHORS         = FENGARI_AUTHORS;  module.exports.FENGARI_COPYRIGHT       = FENGARI_COPYRIGHT;  module.exports.FENGARI_RELEASE         = FENGARI_RELEASE; @@ -121,19 +148,35 @@ module.exports.LUA_INIT_VAR            = LUA_INIT_VAR;  module.exports.LUA_MINSTACK            = LUA_MINSTACK;  module.exports.LUA_MULTRET             = -1;  module.exports.LUA_NUMTAGS             = LUA_NUMTAGS; +module.exports.LUA_OPADD               = LUA_OPADD; +module.exports.LUA_OPBAND              = LUA_OPBAND; +module.exports.LUA_OPBNOT              = LUA_OPBNOT; +module.exports.LUA_OPBOR               = LUA_OPBOR; +module.exports.LUA_OPBXOR              = LUA_OPBXOR; +module.exports.LUA_OPDIV               = LUA_OPDIV;  module.exports.LUA_OPEQ                = LUA_OPEQ; +module.exports.LUA_OPIDIV              = LUA_OPIDIV;  module.exports.LUA_OPLE                = LUA_OPLE;  module.exports.LUA_OPLT                = LUA_OPLT; +module.exports.LUA_OPMOD               = LUA_OPMOD; +module.exports.LUA_OPMUL               = LUA_OPMUL; +module.exports.LUA_OPPOW               = LUA_OPPOW; +module.exports.LUA_OPSHL               = LUA_OPSHL; +module.exports.LUA_OPSHR               = LUA_OPSHR; +module.exports.LUA_OPSUB               = LUA_OPSUB; +module.exports.LUA_OPUNM               = LUA_OPUNM;  module.exports.LUA_REGISTRYINDEX       = LUA_REGISTRYINDEX;  module.exports.LUA_RELEASE             = LUA_RELEASE;  module.exports.LUA_RIDX_GLOBALS        = LUA_RIDX_GLOBALS;  module.exports.LUA_RIDX_LAST           = LUA_RIDX_LAST;  module.exports.LUA_RIDX_MAINTHREAD     = LUA_RIDX_MAINTHREAD; +module.exports.LUA_SIGNATURE           = LUA_SIGNATURE;  module.exports.LUA_VERSION             = LUA_VERSION;  module.exports.LUA_VERSION_MAJOR       = LUA_VERSION_MAJOR;  module.exports.LUA_VERSION_MINOR       = LUA_VERSION_MINOR;  module.exports.LUA_VERSION_NUM         = LUA_VERSION_NUM;  module.exports.LUA_VERSION_RELEASE     = LUA_VERSION_RELEASE; +module.exports.LUA_VERSUFFIX           = LUA_VERSUFFIX;  module.exports.constant_types          = constant_types;  module.exports.lua_Debug               = lua_Debug;  module.exports.lua_upvalueindex        = lua_upvalueindex; diff --git a/src/lualib.js b/src/lualib.js index d87cfbc..da9b290 100644 --- a/src/lualib.js +++ b/src/lualib.js @@ -2,21 +2,17 @@  "use strict";  const assert = require('assert'); -const lua    = require('./lua.js'); - - -const LUA_VERSUFFIX = "_" + lua.LUA_VERSION_MAJOR + "_" + lua.LUA_VERSION_MINOR;  const LUA_COLIBNAME   = "coroutine"; -const LUA_TABLIBNAME  = "table" -const LUA_IOLIBNAME   = "io" -const LUA_OSLIBNAME   = "os" -const LUA_STRLIBNAME  = "string" -const LUA_UTF8LIBNAME = "utf8" -const LUA_BITLIBNAME  = "bit32" -const LUA_MATHLIBNAME = "math" -const LUA_DBLIBNAME   = "debug" -const LUA_LOADLIBNAME = "package" +const LUA_TABLIBNAME  = "table"; +const LUA_IOLIBNAME   = "io"; +const LUA_OSLIBNAME   = "os"; +const LUA_STRLIBNAME  = "string"; +const LUA_UTF8LIBNAME = "utf8"; +const LUA_BITLIBNAME  = "bit32"; +const LUA_MATHLIBNAME = "math"; +const LUA_DBLIBNAME   = "debug"; +const LUA_LOADLIBNAME = "package";  module.exports.LUA_BITLIBNAME  = LUA_BITLIBNAME; @@ -28,5 +24,4 @@ module.exports.LUA_MATHLIBNAME = LUA_MATHLIBNAME;  module.exports.LUA_OSLIBNAME   = LUA_OSLIBNAME;  module.exports.LUA_STRLIBNAME  = LUA_STRLIBNAME;  module.exports.LUA_TABLIBNAME  = LUA_TABLIBNAME; -module.exports.LUA_UTF8LIBNAME = LUA_UTF8LIBNAME; -module.exports.LUA_VERSUFFIX   = LUA_VERSUFFIX;
\ No newline at end of file +module.exports.LUA_UTF8LIBNAME = LUA_UTF8LIBNAME;
\ No newline at end of file diff --git a/src/lundump.js b/src/lundump.js index b79e6ea..5a6d8c4 100644 --- a/src/lundump.js +++ b/src/lundump.js @@ -5,6 +5,7 @@ const DataView       = require('buffer-dataview');  const fs             = require('fs');  const assert         = require('assert'); +const lua            = require('./lua.js');  const LClosure       = require('./lobject.js').LClosure;  const TValue         = require('./lobject.js').TValue;  const Proto          = require('./lfunc.js').Proto; @@ -256,7 +257,7 @@ class BytecodeParser {      }      checkHeader() { -        if (this.readString(4) !== "\x1bLua") +        if (this.readString(4) !== lua.LUA_SIGNATURE)              throw new Error("bad LUA_SIGNATURE, expected '<esc>Lua'");          if (this.readByte() !== 0x53) @@ -792,6 +792,10 @@ const luaV_equalobj = function(L, t1, t2) {      return L.stack[L.top].l_isfalse() ? 0 : 1;  }; +const luaV_rawequalobj = function(t1, t2) { +    return luaV_equalobj(null, t1, t2); +}; +  const forlimit = function(obj, step) {      let stopnow = false;      let ilimit = luaV_tointeger(obj, step < 0 ? 2 : 1); @@ -818,9 +822,9 @@ const forlimit = function(obj, step) {  /*  ** try to convert a value to an integer, rounding according to 'mode': -** mode == 0: accepts only integral values -** mode == 1: takes the floor of the number -** mode == 2: takes the ceil of the number +** mode === 0: accepts only integral values +** mode === 1: takes the floor of the number +** mode === 2: takes the ceil of the number  */  const luaV_tointeger = function(obj, mode) {      if (obj.ttisfloat()) { @@ -831,7 +835,7 @@ const luaV_tointeger = function(obj, mode) {              if (mode === 0)                  return false;  /* fails if mode demands integral value */              else if (mode > 1)  /* needs ceil? */ -                f += 1;  /* convert floor to ceil (remember: n != f) */ +                f += 1;  /* convert floor to ceil (remember: n !== f) */          }          return f|0; @@ -1072,29 +1076,30 @@ const luaV_finishset = function(L, t, key, val, slot, recur) {  } -module.exports.dojump         = dojump; -module.exports.donextjump     = donextjump; -module.exports.forlimit       = forlimit; -module.exports.gettable       = gettable; -module.exports.l_strcmp       = l_strcmp; -module.exports.LEintfloat     = LEintfloat; -module.exports.LEnum          = LEnum; -module.exports.LTintfloat     = LTintfloat; -module.exports.LTnum          = LTnum; -module.exports.luaV_concat    = luaV_concat; -module.exports.luaV_equalobj  = luaV_equalobj; -module.exports.luaV_execute   = luaV_execute; -module.exports.luaV_finishOp  = luaV_finishOp; -module.exports.luaV_finishset = luaV_finishset; -module.exports.luaV_lessequal = luaV_lessequal; -module.exports.luaV_lessthan  = luaV_lessthan; -module.exports.luaV_objlen    = luaV_objlen; -module.exports.luaV_tointeger = luaV_tointeger; -module.exports.RA             = RA; -module.exports.RB             = RB; -module.exports.RC             = RC; -module.exports.RKB            = RKB; -module.exports.RKC            = RKC; -module.exports.settable       = settable; -module.exports.tointeger      = tointeger; -module.exports.tonumber       = tonumber;
\ No newline at end of file +module.exports.LEintfloat        = LEintfloat; +module.exports.LEnum             = LEnum; +module.exports.LTintfloat        = LTintfloat; +module.exports.LTnum             = LTnum; +module.exports.RA                = RA; +module.exports.RB                = RB; +module.exports.RC                = RC; +module.exports.RKB               = RKB; +module.exports.RKC               = RKC; +module.exports.dojump            = dojump; +module.exports.donextjump        = donextjump; +module.exports.forlimit          = forlimit; +module.exports.gettable          = gettable; +module.exports.l_strcmp          = l_strcmp; +module.exports.luaV_concat       = luaV_concat; +module.exports.luaV_equalobj     = luaV_equalobj; +module.exports.luaV_execute      = luaV_execute; +module.exports.luaV_finishOp     = luaV_finishOp; +module.exports.luaV_finishset    = luaV_finishset; +module.exports.luaV_lessequal    = luaV_lessequal; +module.exports.luaV_lessthan     = luaV_lessthan; +module.exports.luaV_objlen       = luaV_objlen; +module.exports.luaV_rawequalobj = luaV_rawequalobj; +module.exports.luaV_tointeger    = luaV_tointeger; +module.exports.settable          = settable; +module.exports.tointeger         = tointeger; +module.exports.tonumber          = tonumber;
\ No newline at end of file  | 
