From 4bf190d1b51c8c2d3f5ab0b6355ecd971b735adc Mon Sep 17 00:00:00 2001
From: Benoit Giannangeli <giann008@gmail.com>
Date: Sat, 11 Feb 2017 22:22:38 +0100
Subject: TFORCALL, TFORLOOP, luaD_call, tag methods

---
 src/ldo.js     |  81 +++++++--------
 src/lfunc.js   |   4 +-
 src/lobject.js |  85 +++++++++-------
 src/ltm.js     |  97 +++++++++++++++++-
 src/lvm.js     | 310 ++++++++++++++++++++++++++++++++++++---------------------
 5 files changed, 378 insertions(+), 199 deletions(-)

(limited to 'src')

diff --git a/src/ldo.js b/src/ldo.js
index 488bc86..85bd3d3 100644
--- a/src/ldo.js
+++ b/src/ldo.js
@@ -1,23 +1,21 @@
 /*jshint esversion: 6 */
 "use strict";
 
-const OC             = require('./lopcodes.js');
 const lua            = require('./lua.js');
 const CT             = lua.constant_types;
 const LUA_MULTRET    = lua.LUA_MULTRET;
 const lobject        = require('./lobject.js');
 const TValue         = lobject.TValue;
-const Table          = lobject.Table;
-const LClosure       = lobject.LClosure;
-const lfunc          = require('./lfunc.js');
-const UpVal          = lfunc.UpVal;
 const lstate         = require('./lstate.js');
 const CallInfo       = lstate.CallInfo;
 const llimit         = require('./llimit.js');
+const ltm            = require('./ltm.js');
+const TMS            = ltm.TMS;
 
 const nil            = new TValue(CT.LUA_TNIL, null);
 
-const precall = function(L, off, func, nresults) {
+const luaD_precall = function(L, off, nresults) {
+    let func = L.stack[off];
     let ci;
 
     switch(func.type) {
@@ -56,28 +54,30 @@ const precall = function(L, off, func, nresults) {
             }
             ci.nresults = nresults;
             ci.func = func;
+            ci.funcOff = off;
             ci.u.l.base = base;
             ci.top = base + fsize;
             L.top = ci.top;
             ci.u.l.savedpc = p.code;
+            ci.pcOff = 0;
             ci.callstatus = lstate.CIST_LUA;
 
             return false;
             break;
         }
         default:
-            // __call
-            return false;
+            tryfuncTM(L, off, func);
+            return luaD_precall(L, off, nresults);
     }
-}
+};
 
-const postcall = function(L, ci, firstResult, nres) {
+const luaD_poscall = function(L, ci, firstResult, nres) {
     let wanted = ci.nresults;
     let res = ci.funcOff;
     L.ci = ci.previous;
     L.ciOff--;
     return moveresults(L, firstResult, res, nres, wanted);
-}
+};
 
 const moveresults = function(L, firstResult, res, nres, wanted) {
     switch (wanted) {
@@ -98,7 +98,11 @@ const moveresults = function(L, firstResult, res, nres, wanted) {
         default: {
             let i;
             if (wanted <= nres) {
-                for (i = 0; i < wanted; i++)
+                for (i = 0; i < wanted; i++) {
+                    L.stack[res + i] = L.stack[firstResult + i];
+                }
+            } else {
+                for (i = 0; i < nres; i++)
                     L.stack[res + i] = L.stack[firstResult + i];
                 for (; i < wanted; i++)
                     L.stack[res + i] = nil;
@@ -107,8 +111,9 @@ const moveresults = function(L, firstResult, res, nres, wanted) {
         }
     }
 
+    L.top = res + wanted; /* top points after the last result */
     return true;
-}
+};
 
 const adjust_varargs = function(L, p, actual) {
     let nfixargs = p.numparams;
@@ -126,38 +131,24 @@ const adjust_varargs = function(L, p, actual) {
         L.stack[L.top++] = nil;
 
     return base;
+};
+
+const tryfuncTM = function(L, off, func) {
+    let tm = ltm.luaT_gettmbyobj(L, func, TMS.TM_CALL);
+    if (!tm.ttisfunction(tm))
+        throw new Error("__call metatable member is not a function") // TODO: luaG_typeerror
+    /* Open a hole inside the stack at 'func' */
+    for (p = L.top; p > off; p--)
+        L.stack[p] = L.stack[p-1];
+    L.top++; /* slot ensured by caller */
+    L.stack[off] = tm; /* tag method is the new function to be called */       
 }
 
-
-/*
-** Check appropriate error for stack overflow ("regular" overflow or
-** overflow while handling stack overflow). If 'nCalls' is larger than
-** LUAI_MAXCCALLS (which means it is handling a "regular" overflow) but
-** smaller than 9/8 of LUAI_MAXCCALLS, does not report an error (to
-** allow overflow handling to work)
-*/
-const stackerror = function(L) {
-    if (L.nCcalls === LUAI_MAXCCALLS)
-        throw new Error("JS stack overflow");
-    else if (L.nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) /* error while handing stack error */
-        throw new Error("stack overflow") // TODO: luaD_throw(L, LUA_ERRERR);
-}
-
-/*
-** Call a function (JS or Lua). The function to be called is at func.
-** The arguments are on the stack, right after the function.
-** When returns, all the results are on the stack, starting at the original
-** function position.
-*/
-// const luaD_call = function(L, func, nResults) {
-//     if (++L->nCcalls >= LUAI_MAXCCALLS)
-//         stackerror(L);
-//     if ()
-// }
-
 module.exports = {
-    precall:        precall,
-    postcall:       postcall,
-    moveresults:    moveresults,
-    adjust_varargs: adjust_varargs
-}
\ No newline at end of file
+    nil:              nil,
+    luaD_precall:     luaD_precall,
+    luaD_poscall:     luaD_poscall,
+    moveresults:      moveresults,
+    adjust_varargs:   adjust_varargs,
+    tryfuncTM:        tryfuncTM
+};
\ No newline at end of file
diff --git a/src/lfunc.js b/src/lfunc.js
index 77eb853..2e0a70a 100644
--- a/src/lfunc.js
+++ b/src/lfunc.js
@@ -47,7 +47,7 @@ class UpVal {
         return this.v !== null;
     }
 
-}
+};
 
 const findupval = function(L, level) {
     let pp = L.openupval;
@@ -73,7 +73,7 @@ const findupval = function(L, level) {
     // Thread with upvalue list business ? lfunc.c:75
 
     return uv;
-}
+};
 
 const luaF_close = function(L, level) {
     while (L.openupval !== null && L.openupval.v >= level) {
diff --git a/src/lobject.js b/src/lobject.js
index 4c3290d..713a734 100644
--- a/src/lobject.js
+++ b/src/lobject.js
@@ -104,6 +104,7 @@ class TValue {
 
 }
 
+const nil   = new TValue(CT.LUA_TNIL, null);
 
 class LClosure extends TValue {
 
@@ -138,6 +139,17 @@ class TString extends TValue {
 }
 
 
+class Userdata extends TValue {
+
+    constructor(jsObject) {
+        super(CT.LUA_TUSERDATA, jsObject);
+
+        this.metatable = null;
+    }
+
+}
+
+
 class Table extends TValue {
 
     constructor(array, hash) {
@@ -146,43 +158,44 @@ class Table extends TValue {
             hash: new Map(hash)
         });
 
-        this.usermetatable = null;
-
-        this.metatable = {
-            __newindex: function (table, key, value) {
-                if (key instanceof TValue) {
-                    // Those lua values are used by value, tables and functions by reference
-                    if ([CT.LUA_TNUMBER, CT.LUA_TSTRING, CT.LUA_TSHRSTR, CT.LUA_TLNGSTR, CT.LUA_TNUMFLT, CT.LUA_TNUMINT].indexOf(key.type) > -1) {
-                        key = key.value;
-                    }
-                }
-
-                if (typeof key === 'number') {
-                    table.value.array[key] = value;
-                } else {
-                    table.value.hash.set(key, value);
-                }
-            },
-
-            __index: function (table, key) {
-                if (key instanceof TValue) {
-                    // Those lua values are used by value, tables and functions by reference
-                    if ([CT.LUA_TNUMBER, CT.LUA_TSTRING, CT.LUA_TSHRSTR, CT.LUA_TLNGSTR, CT.LUA_TNUMFLT, CT.LUA_TNUMINT].indexOf(key.type) > -1) {
-                        key = key.value;
-                    }
-                }
-
-                if (typeof key === 'number') {
-                    return table.value.array[key];
-                } else {
-                    return table.value.hash.get(key);
-                }
-            },
-
-            __len: function (table) {
-                return t.value.array.length;
+        this.metatable = null;
+    }
+
+    __newindex(table, key, value) {
+        if (key instanceof TValue) {
+            // Those lua values are used by value, tables and functions by reference
+            if ([CT.LUA_TNUMBER, CT.LUA_TSTRING, CT.LUA_TSHRSTR, CT.LUA_TLNGSTR, CT.LUA_TNUMFLT, CT.LUA_TNUMINT].indexOf(key.type) > -1) {
+                key = key.value;
+            }
+        }
+
+        if (typeof key === 'number') {
+            table.value.array[key] = value;
+        } else {
+            table.value.hash.set(key, value);
+        }
+    }
+
+    __index(table, key) {
+        if (key instanceof TValue) {
+            // Those lua values are used by value, tables and functions by reference
+            if ([CT.LUA_TNUMBER, CT.LUA_TSTRING, CT.LUA_TSHRSTR, CT.LUA_TLNGSTR, CT.LUA_TNUMFLT, CT.LUA_TNUMINT].indexOf(key.type) > -1) {
+                key = key.value;
             }
-        };
+        }
+
+        let v = nil;
+        if (typeof key === 'number') {
+            v = table.value.array[key];
+        } else {
+            v = table.value.hash.get(key);
+        }
+
+        return v ? v : nil;
+    }
+
+    __len(table) {
+        return t.value.array.length;
     }
 
 }
diff --git a/src/ltm.js b/src/ltm.js
index ebc0348..5d93e28 100644
--- a/src/ltm.js
+++ b/src/ltm.js
@@ -4,7 +4,102 @@
 const lobject = require('./lobject.js');
 const TValue  = lobject.TValue;
 const Table   = lobject.Table;
+const ldo     = require('./ldo.js');
+const lstate  = require('./lstate.js');
+const lua     = require('./lua.js');
+const CT      = lua.constant_types;
 
 
+const TMS = {
+    TM_INDEX:    "__index",
+    TM_NEWINDEX: "__newindex",
+    TM_GC:       "__gc",
+    TM_MODE:     "__mode",
+    TM_LEN:      "__len",
+    TM_EQ:       "__eq",  /* last tag method with fast access */
+    TM_ADD:      "__add",
+    TM_SUB:      "__sub",
+    TM_MUL:      "__mul",
+    TM_MOD:      "__mod",
+    TM_POW:      "__pow",
+    TM_DIV:      "__div",
+    TM_IDIV:     "__idiv",
+    TM_BAND:     "__band",
+    TM_BOR:      "__bor",
+    TM_BXOR:     "__bxor",
+    TM_SHL:      "__shl",
+    TM_SHR:      "__shr",
+    TM_UNM:      "__unm",
+    TM_BNOT:     "__bnot",
+    TM_LT:       "__lt",
+    TM_LE:       "__le",
+    TM_CONCAT:   "__concat",
+    TM_CALL:     "__call",
+    TM_N:        26
+};
 
-module.exports = {}
\ No newline at end of file
+const luaT_callTM = function(L, f, p1, p2, p3, hasres) {
+    let result = p3;
+    let func = L.top;
+
+    L.stack[L.top] = f;      /* push function (assume EXTRA_STACK) */
+    L.stack[L.top + 1] = p1; /* 1st argument */
+    L.stack[L.top + 2] = p2; /* 2nd argument */
+    L.top += 3;
+
+    if (!hasres)  /* no result? 'p3' is third argument */
+        L.stack[L.top++] = p3;  /* 3rd argument */
+
+    if (ci.callstatus & lstate.CIST_LUA)
+        ldo.luaD_call(L, func, hasres);
+    else
+        ldo.luaD_callnoyield(L, func, hasres);
+
+    if (hasres) {
+        L.stack[result] = L.stack[--L.top];
+    }
+};
+
+const luaT_callbinTM = function(L, p1, p2, res, event) {
+    let tm = luaT_gettmbyobj(L, p1, event);
+    if (tm.ttisnil())
+        tm = luaT_gettmbyobj(L, p2, event);
+    if (tm.ttisnil()) return false;
+    luaT_callTM(L, tm, p1, p2, res, 1);
+    return true;
+}
+
+const luaT_trybinTM = function(L, p1, p2, res, event) {
+    if (!luaT_gettmbyobj(L, p1, p2, res, event)) {
+        throw new Error("TM error"); // TODO: luaG_error
+    }
+}
+
+const luaT_callorderTM = function(L, p1, p2, event) {
+    if (!luaT_callbinTM(L, p2, p2, L.top, event))
+        return -1;
+    else
+        return !l_isfalse(L.stack[L.top]) ? 1 : 0;
+}
+
+const luaT_gettmbyobj = function(L, o, event) {
+    let mt;
+    switch(o.ttnov()) {
+        case CT.LUA_TTABLE:
+        case CT.LUA_TTUSERDATA:
+            mt = o.value.metatable;
+        default:
+            // TODO: mt = G(L)->mt[ttnov(o)];
+    }
+
+    return mt ? mt.__index(mt, event) : ldo.nil;
+}
+
+module.exports = {
+    TMS:              TMS,
+    luaT_callTM:      luaT_callTM,
+    luaT_callbinTM:   luaT_callbinTM,
+    luaT_trybinTM:    luaT_trybinTM,
+    luaT_callorderTM: luaT_callorderTM,
+    luaT_gettmbyobj:  luaT_gettmbyobj
+}
\ No newline at end of file
diff --git a/src/lvm.js b/src/lvm.js
index 6523482..c2cef1b 100644
--- a/src/lvm.js
+++ b/src/lvm.js
@@ -18,41 +18,54 @@ const lstate         = require('./lstate.js');
 const CallInfo       = lstate.CallInfo;
 const llimit         = require('./llimit.js');
 const ldo            = require('./ldo.js');
+const ltm            = require('./ltm.js');
+const TMS            = ltm.TMS;
 
 const RA = function(L, base, i) {
    return base + i.A;
-}
+};
 
 const RB = function(L, base, i) {
    return base + i.B;
-}
+};
 
 const RC = function(L, base, i) {
    return base + i.C;
-}
+};
 
 const RKB = function(L, base, k, i) {
     return OC.ISK(i.B) ? k[OC.INDEXK(i.B)] : L.stack[base + i.B];
-}
+};
 
 const RKC = function(L, base, k, i) {
     return OC.ISK(i.C) ? k[OC.INDEXK(i.C)] : L.stack[base + i.C];
-}
+};
 
 const luaV_execute = function(L) {
     let ci = L.ci;
+    let specialCase = null; // To enable jump to specific opcode without reading current op/ra
+    let opcode, k, base, i, ra;
+    var cl;
+    
     ci.callstatus |= lstate.CIST_FRESH;
     newframe:
     for (;;) {
-        ci = L.ci;
-        var cl = ci.func;
-        let k = cl.p.k;
-        let base = ci.u.l.base
+        if (specialCase) {
+            opcode = specialCase;
+            specialCase = null;
+        } else {
+            ci = L.ci;
+            cl = ci.func;
+            k = cl.p.k;
+            base = ci.u.l.base
+
+            i = ci.u.l.savedpc[ci.pcOff++];
+            ra = RA(L, base, i);
 
-        let i = ci.u.l.savedpc[ci.pcOff++];
-        let ra = RA(L, base, i);
+            opcode = OC.OpCodes[i.opcode];
+        }
 
-        switch (OC.OpCodes[i.opcode]) {
+        switch (opcode) {
             case "OP_MOVE": {
                 L.stack[ra] = L.stack[RB(L, base, i)];
                 break;
@@ -91,10 +104,10 @@ const luaV_execute = function(L) {
                 let table = cl.upvals[i.B].val(L);
                 let key = RKC(L, base, k, i);
 
-                // if (!table.ttistable() || !table.metatable.__index(table, key)) {
+                // if (!table.ttistable() || !table.__index(table, key)) {
                 //     // __index
                 // } else {
-                    L.stack[ra] = table.metatable.__index(table, key);
+                    L.stack[ra] = table.__index(table, key);
                 // }
                 break;
             }
@@ -103,10 +116,10 @@ const luaV_execute = function(L) {
                 let key = RKB(L, base, k, i);
                 let v = RKC(L, base, k, i);
 
-                // if (!table.ttistable() || !table.metatable.__index(table, key)) {
+                // if (!table.ttistable() || !table.__index(table, key)) {
                 //     // __index
                 // } else {
-                    table.metatable.__newindex(table, key, v);
+                    table.__newindex(table, key, v);
                 // }
 
                 break;
@@ -115,10 +128,10 @@ const luaV_execute = function(L) {
                 let table = RKB(L, base, k, i);
                 let key = RKC(L, base, k, i);
 
-                // if (!table.ttistable() || !table.metatable.__index(table, key)) {
+                // if (!table.ttistable() || !table.__index(table, key)) {
                 //     // __index
                 // } else {
-                    L.stack[ra] = table.metatable.__index(table, key);
+                    L.stack[ra] = table.__index(table, key);
                 // }
                 break;
             }
@@ -127,10 +140,10 @@ const luaV_execute = function(L) {
                 let key = RKB(L, base, k, i);
                 let v = RKC(L, base, k, i);
 
-                // if (!table.ttistable() || !table.metatable.__index(table, key)) {
+                // if (!table.ttistable() || !table.__index(table, key)) {
                 //     // __index
                 // } else {
-                    table.metatable.__newindex(table, key, v);
+                    table.__newindex(table, key, v);
                 // }
 
                 break;
@@ -145,10 +158,10 @@ const luaV_execute = function(L) {
 
                 L.stack[ra + 1] = table;
 
-                // if (!table.ttistable() || !table.metatable.__index(table, key)) {
+                // if (!table.ttistable() || !table.__index(table, key)) {
                 //     // __index
                 // } else {
-                    L.stack[ra] = table.metatable.__index(table, key);
+                    L.stack[ra] = table.__index(table, key);
                 // }
 
                 break;
@@ -164,8 +177,8 @@ const luaV_execute = function(L) {
                 } else if (numberop1 !== false && numberop2 !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, op1.value + op2.value);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${op1.value} and ${op2.value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_ADD);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -180,8 +193,8 @@ const luaV_execute = function(L) {
                 } else if (numberop1 !== false && numberop2 !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, op1.value - op2.value);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${op1.value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_SUB);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -196,8 +209,8 @@ const luaV_execute = function(L) {
                 } else if (numberop1 !== false && numberop2 !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, k[i.B].value * op2.value);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_MUL);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -212,8 +225,8 @@ const luaV_execute = function(L) {
                 } else if (numberop1 !== false && numberop2 !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, k[i.B].value % op2.value);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_MOD);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -226,8 +239,8 @@ const luaV_execute = function(L) {
                 if (numberop1 !== false && numberop2 !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, Math.pow(op1.value, op2.value));
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_POW);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -240,8 +253,8 @@ const luaV_execute = function(L) {
                 if (numberop1 !== false && numberop2 !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, k[i.B].value / op2.value);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_DIV);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -256,8 +269,8 @@ const luaV_execute = function(L) {
                 } else if (numberop1 !== false && numberop2 !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, (op1.value / op2.value)|0);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_IDIV);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -270,8 +283,8 @@ const luaV_execute = function(L) {
                 if (op1.ttisinteger() && op2.ttisinteger()) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value & op2.value)|0);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_BAND);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -284,8 +297,8 @@ const luaV_execute = function(L) {
                 if (op1.ttisinteger() && op2.ttisinteger()) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value | op2.value)|0);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_BOR);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -298,8 +311,8 @@ const luaV_execute = function(L) {
                 if (op1.ttisinteger() && op2.ttisinteger()) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value ^ op2.value)|0);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_BXOR);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -312,8 +325,8 @@ const luaV_execute = function(L) {
                 if (op1.ttisinteger() && op2.ttisinteger()) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value << op2.value)|0);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_SHL);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -326,8 +339,8 @@ const luaV_execute = function(L) {
                 if (op1.ttisinteger() && op2.ttisinteger()) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value >> op2.value)|0);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform binary operation on ${k[i.B].value} and ${k[i.C].value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_SHR);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -340,8 +353,8 @@ const luaV_execute = function(L) {
                 } else if (numberop !== false) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMFLT, -op.value);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform unary operation on ${op.value}`);
+                    ltm.luaT_trybinTM(L, op1, op2, ra, TMS.TM_UNM);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -352,8 +365,8 @@ const luaV_execute = function(L) {
                 if (op.ttisinteger()) {
                     L.stack[ra] = new TValue(CT.LUA_TNUMINT, ~op.value);
                 } else {
-                    // Metamethod
-                    throw new Error(`Can't perform unary operation on ${op.value}`);
+                    ltm.luaT_trybinTM(L, op, op, ra, TMS.TM_BNOT);
+                    base = ci.u.l.base;
                 }
                 break;
             }
@@ -420,7 +433,7 @@ const luaV_execute = function(L) {
                 if (b !== 0)
                     L.top = ra+b;
 
-                if (ldo.precall(L, ra, L.stack[ra], nresults)) {
+                if (ldo.luaD_precall(L, ra, nresults)) {
                     if (nresults >= 0)
                         L.top = ci.top;
                     base = ci.u.l.base;
@@ -433,7 +446,7 @@ const luaV_execute = function(L) {
             }
             case "OP_TAILCALL": {
                 if (i.B !== 0) L.top = ra + i.B;
-                if (ldo.precall(L, ra, L.stack[ra], LUA_MULTRET)) { // JS function
+                if (ldo.luaD_precall(L, ra, LUA_MULTRET)) { // JS function
                     base = ci.u.l.base;
                 } else {
                     /* tail call: put called frame (n) in place of caller one (o) */
@@ -466,7 +479,7 @@ const luaV_execute = function(L) {
             }
             case "OP_RETURN": {
                 if (cl.p.p.length > 0) lfunc.luaF_close(L, base);
-                let b = ldo.postcall(L, ci, ra, (i.B !== 0 ? i.B - 1 : L.top - ra));
+                let b = ldo.luaD_poscall(L, ci, ra, (i.B !== 0 ? i.B - 1 : L.top - ra));
 
                 if (ci.callstatus & lstate.CIST_FRESH)
                     return; /* external invocation: return */
@@ -540,12 +553,24 @@ const luaV_execute = function(L) {
                 break;
             }
             case "OP_TFORCALL": {
+                let cb = ra + 3 /* call base */
+                L.stack[cb + 2] = L.stack[ra + 2];
+                L.stack[cb + 1] = L.stack[ra + 1];
+                L.stack[cb]     = L.stack[ra];
+                L.top = cb + 3; /* func. + 2 args (state and index) */
+                luaD_call(L, cb, i.C);
+                base = ci.u.l.base;
+                L.top = ci.top;
+                i = ci.u.l.savedpc[ci.pcOff++];
+                ra = RA(L, base, i);
+                assert(OC.OpCodes[i.opcode] === "OP_TFORLOOP");
+                specialCase = "OP_TFORLOOP";
                 break;
             }
             case "OP_TFORLOOP": {
                 if (!L.stack[ra + 1].ttisnil()) { /* continue loop? */
                     L.stack[ra] = L.stack[ra + 1]; /* save control variable */
-                    ci.cpOff += i.sBx; /* jump back */
+                    ci.pcOff += i.sBx; /* jump back */
                 }
                 break;
             }
@@ -616,51 +641,52 @@ const luaV_execute = function(L) {
             }
         }
     }
-}
+};
 
 const dojump = function(L, ci, i, e) {
     let a = i.A;
     if (a != 0) lfunc.luaF_close(L, ci.u.l.base + a - 1);
     ci.pcOff += i.sBx + e;
-}
+};
 
 const donextjump = function(L, ci) {
     dojump(L, ci, ci.u.l.savedpc[ci.pcOff], 1);
-}
+};
 
-const luaV_lessequal = function(l, r) {
-    if (l.ttisnumber() && r.ttisnumber())
-        return LEnum(l, r);
-    else if (l.ttisstring() && r.ttisstring())
-        return l_strcmp(l, r) <= 0;
-    // TODO: metatable
-    // else if  (l.metatable.__le || r.metatable.__le) {
-    //     let res = l.metatable.__le ? l.metatable.__le(l, r) : r.metatable.__le(l, r);
-    //     if (res >= 0)
-    //         return res;
-    // } else {
-    //     L.ci.callstatus |= lstate.CIST_LEQ;
-    //     let res = l.metatable.__lt ? l.metatable.__lt(r, l) : r.metatable.__lt(r, l);
-    //     L.ci.callstatus ^= lstate.CIST_LEQ;
-    //     if (res < 0)
-    //         throw new Error("attempt to compare ...");
-    //     return !res;
-    // }
-}
 
 const luaV_lessthan = function(l, r) {
     if (l.ttisnumber() && r.ttisnumber())
         return LTnum(l, r);
     else if (l.ttisstring() && r.ttisstring())
         return l_strcmp(l, r) < 0;
-    // TODO: metatable
-    // else if  (l.metatable.__lt || r.metatable.__lt) {
-    //     let res = l.metatable.__lt ? l.metatable.__lt(l, r) : r.metatable.__lt(l, r);
-    //     if (res < 0)
-    //         throw new Error("attempt to compare ...")
-    //     return res;
-    // }
-}
+    else {
+        res = ltm.luatT_callorderTM(L, l, r, TMS.TM_LT);
+        if (res < 0)
+            throw new Error("TM order error"); // TODO: luaG_ordererror
+        return res;
+    }
+};
+
+const luaV_lessequal = function(l, r) {
+    let res;
+
+    if (l.ttisnumber() && r.ttisnumber())
+        return LEnum(l, r);
+    else if (l.ttisstring() && r.ttisstring())
+        return l_strcmp(l, r) <= 0;
+    else {
+        res = ltm.luatT_callorderTM(L, l, r, TMS.TM_LE);
+        if (res >= 0)
+            return res;
+    }
+
+    L.ci.callstatus |= CIST_LEQ; /* mark it is doing 'lt' for 'le' */
+    res = ltm.luatT_callorderTM(L, l, r, TMS.TM_LT);
+    L.ci.callstatus ^= CIST_LEQ; /* clear mark */
+    if (res < 0)
+        throw new Error("TM order error"); // TODO: luaG_ordererror
+    return res === 1;
+};
 
 const luaV_equalobj = function(t1, t2) {
     if (t1.ttype() !== t2.ttype()) { /* not the same variant? */
@@ -672,6 +698,8 @@ const luaV_equalobj = function(t1, t2) {
         }
     }
 
+    let tm;
+
     /* values have same type and same variant */
     switch(t1.ttype()) {
         case CT.LUA_TNIL:
@@ -687,11 +715,23 @@ const luaV_equalobj = function(t1, t2) {
         case CT.LUA_TUSERDATA:
         case CT.LUA_TTABLE:
             if (t1 === t2) return 1;
-            // TODO: __eq
+            else if (L === null) return 1;
+
+            // TODO: fasttm ?
+            tm = ltm.luaT_gettmbyobj(L, t1, TMS.TM_EQ);
+            if (tm.ttisnil())
+                tm = ltm.luaT_gettmbyobj(L, t2, TMS.TM_EQ);
+            break
         default:
             return t1.value === t2.value ? 1 : 0;
     }
-}
+
+    if (!tm || tm.ttisnil())
+        return 0;
+
+    ltm.luaT_callTM(L, tm, t1, t2, L.top, 1);
+    return !l_isfalse(L.stack[L.top]);
+};
 
 const forlimit = function(obj, step) {
     let stopnow = false;
@@ -715,7 +755,7 @@ const forlimit = function(obj, step) {
         stopnow: stopnow,
         ilimit: ilimit
     }
-}
+};
 
 /*
 ** try to convert a value to an integer, rounding according to 'mode':
@@ -743,7 +783,7 @@ const luaV_tointeger = function(obj, mode) {
     }
 
     return false;
-}
+};
 
 const tonumber = function(v) {
     if (v.type === CT.LUA_TNUMFLT)
@@ -756,7 +796,7 @@ const tonumber = function(v) {
         return new TValue(CT.LUA_TNUMFLT, parseFloat(v.value)); // TODO: luaO_str2num
 
     return false;
-}
+};
 
 const LTnum = function(l, r) {
     if (l.ttisinteger()) {
@@ -772,7 +812,7 @@ const LTnum = function(l, r) {
         else
             return !LEintfloat(r.value, l.value);
     }
-}
+};
 
 const LEnum = function(l, r) {
     if (l.ttisinteger()) {
@@ -788,46 +828,86 @@ const LEnum = function(l, r) {
         else
             return !LTintfloat(r.value, l.value);
     }
-}
+};
 
 const LEintfloat = function(l, r) {
     // TODO: LEintfloat
     return l <= r ? 1 : 0;
-}
+};
 
 const LTintfloat = function(l, r) {
     // TODO: LTintfloat
     return l < r ? 1 : 0;
-}
+};
 
 const l_strcmp = function(ls, rs) {
     // TODO: lvm.c:248 static int l_strcmp (const TString *ls, const TString *rs)
     return ls.value === rs.value ? 0 : (ls.value < rs.value ? -1 : 1);
-}
+};
 
 const l_isfalse = function(o) {
     return o.ttisnil() || (o.ttisboolean() && o.value === false)
-}
+};
+
+/*
+** Check appropriate error for stack overflow ("regular" overflow or
+** overflow while handling stack overflow). If 'nCalls' is larger than
+** LUAI_MAXCCALLS (which means it is handling a "regular" overflow) but
+** smaller than 9/8 of LUAI_MAXCCALLS, does not report an error (to
+** allow overflow handling to work)
+*/
+const stackerror = function(L) {
+    if (L.nCcalls === llimit.LUAI_MAXCCALLS)
+        throw new Error("JS stack overflow");
+    else if (L.nCcalls >= llimit.LUAI_MAXCCALLS + (llimit.LUAI_MAXCCALLS >> 3)) /* error while handing stack error */
+        throw new Error("stack overflow") // TODO: luaD_throw(L, LUA_ERRERR);
+};
+
+/*
+** Call a function (JS or Lua). The function to be called is at func.
+** The arguments are on the stack, right after the function.
+** When returns, all the results are on the stack, starting at the original
+** function position.
+*/
+const luaD_call = function(L, off, nResults) {
+    if (++L.nCcalls >= llimit.LUAI_MAXCCALLS)
+        stackerror(L);
+    if (!ldo.luaD_precall(L, off, nResults))
+        luaV_execute(L);
+    L.nCcalls--;
+};
+
+/*
+** Similar to 'luaD_call', but does not allow yields during the call
+*/
+const luaD_callnoyield = function(L, off, nResults) {
+  L.nny++;
+  luaD_call(L, off, nResults);
+  L.nny--;
+};
 
 module.exports = {
-    RA:             RA,
-    RB:             RB,
-    RC:             RC,
-    RKB:            RKB,
-    RKC:            RKC,
-    luaV_execute:   luaV_execute,
-    dojump:         dojump,
-    donextjump:     donextjump,
-    luaV_lessequal: luaV_lessequal,
-    luaV_lessthan:  luaV_lessthan,
-    luaV_equalobj:  luaV_equalobj,
-    forlimit:       forlimit,
-    luaV_tointeger: luaV_tointeger,
-    tonumber:       tonumber,
-    LTnum:          LTnum,
-    LEnum:          LEnum,
-    LEintfloat:     LEintfloat,
-    LTintfloat:     LTintfloat,
-    l_strcmp:       l_strcmp,
-    l_isfalse:      l_isfalse
+    RA:               RA,
+    RB:               RB,
+    RC:               RC,
+    RKB:              RKB,
+    RKC:              RKC,
+    luaV_execute:     luaV_execute,
+    dojump:           dojump,
+    donextjump:       donextjump,
+    luaV_lessequal:   luaV_lessequal,
+    luaV_lessthan:    luaV_lessthan,
+    luaV_equalobj:    luaV_equalobj,
+    forlimit:         forlimit,
+    luaV_tointeger:   luaV_tointeger,
+    tonumber:         tonumber,
+    LTnum:            LTnum,
+    LEnum:            LEnum,
+    LEintfloat:       LEintfloat,
+    LTintfloat:       LTintfloat,
+    l_strcmp:         l_strcmp,
+    l_isfalse:        l_isfalse,
+    stackerror:       stackerror,
+    luaD_call:        luaD_call,
+    luaD_callnoyield: luaD_callnoyield,
 };
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2