aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenoit Giannangeli <benoit.giannangeli@boursorama.fr>2017-02-08 14:22:54 +0100
committerBenoit Giannangeli <benoit.giannangeli@boursorama.fr>2017-02-08 15:12:34 +0100
commitbd6367e8f3162ff76628fa42948e66ba9e6ae281 (patch)
tree70c2d91779de9fd522bea781ff6af79d73159d98
parent7d63489f2615609e21befc9d21a0e9f8fe2bf61a (diff)
downloadfengari-bd6367e8f3162ff76628fa42948e66ba9e6ae281.tar.gz
fengari-bd6367e8f3162ff76628fa42948e66ba9e6ae281.tar.bz2
fengari-bd6367e8f3162ff76628fa42948e66ba9e6ae281.zip
FORPREP, FORLOOP
-rw-r--r--README.md4
-rw-r--r--src/llimit.js7
-rw-r--r--src/lvm.js137
-rw-r--r--tests/lvm.js79
4 files changed, 201 insertions, 26 deletions
diff --git a/README.md b/README.md
index 0debf84..7d2a5db 100644
--- a/README.md
+++ b/README.md
@@ -44,8 +44,8 @@
- [x] OP_CALL
- [x] OP_TAILCALL
- [x] OP_RETURN
- - [ ] OP_FORLOOP
- - [ ] OP_FORPREP
+ - [x] OP_FORLOOP
+ - [x] OP_FORPREP
- [ ] OP_TFORCALL
- [ ] OP_TFORLOOP
- [ ] OP_SETLIST
diff --git a/src/llimit.js b/src/llimit.js
new file mode 100644
index 0000000..983871f
--- /dev/null
+++ b/src/llimit.js
@@ -0,0 +1,7 @@
+/*jshint esversion: 6 */
+"use strict";
+
+module.exports = {
+ LUA_MAXINTEGER: 2147483647,
+ LUA_MININTEGER: -2147483647
+}; \ No newline at end of file
diff --git a/src/lvm.js b/src/lvm.js
index 2ea2d42..fa79695 100644
--- a/src/lvm.js
+++ b/src/lvm.js
@@ -16,6 +16,7 @@ const lfunc = require('./lfunc.js');
const UpVal = lfunc.UpVal;
const lstate = require('./lstate.js');
const CallInfo = lstate.CallInfo;
+const llimit = require('./llimit.js');
const nil = new TValue(CT.LUA_TNIL, null);
@@ -139,7 +140,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value + op2.value);
+ L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value + op2.value)|0);
} else if (numberop1 !== false && numberop2 !== false) {
L.stack[ra] = new TValue(CT.LUA_TNUMFLT, op1.value + op2.value);
} else {
@@ -155,7 +156,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value - op2.value);
+ L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value - op2.value)|0);
} else if (numberop1 !== false && numberop2 !== false) {
L.stack[ra] = new TValue(CT.LUA_TNUMFLT, op1.value - op2.value);
} else {
@@ -171,7 +172,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value * op2.value);
+ L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value * op2.value)|0);
} else if (numberop1 !== false && numberop2 !== false) {
L.stack[ra] = new TValue(CT.LUA_TNUMFLT, k[i.B].value * op2.value);
} else {
@@ -187,7 +188,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value % op2.value);
+ L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value % op2.value)|0);
} else if (numberop1 !== false && numberop2 !== false) {
L.stack[ra] = new TValue(CT.LUA_TNUMFLT, k[i.B].value % op2.value);
} else {
@@ -231,9 +232,9 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, Math.floor(op1.value / op2.value));
+ L.stack[ra] = new TValue(CT.LUA_TNUMINT, (op1.value / op2.value)|0);
} else if (numberop1 !== false && numberop2 !== false) {
- L.stack[ra] = new TValue(CT.LUA_TNUMFLT, Math.floor(op1.value / op2.value));
+ 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}`);
@@ -247,7 +248,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value & op2.value);
+ 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}`);
@@ -261,7 +262,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value | op2.value);
+ 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}`);
@@ -275,7 +276,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value ^ op2.value);
+ 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}`);
@@ -289,7 +290,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value << op2.value);
+ 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}`);
@@ -303,7 +304,7 @@ class LuaVM {
let numberop2 = LuaVM.tonumber(op2);
if (op1.ttisinteger() && op2.ttisinteger()) {
- L.stack[ra] = new TValue(CT.LUA_TNUMINT, op1.value >> op2.value);
+ 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}`);
@@ -456,15 +457,75 @@ class LuaVM {
break;
}
case "OP_FORLOOP": {
+ if (L.stack[ra].ttisinteger()) { /* integer loop? */
+ let step = L.stack[ra + 2].value;
+ let idx = L.stack[ra].value + step;
+ let limit = L.stack[ra + 1].value;
+
+ if (0 < step ? idx <= limit : limit <= idx) {
+ ci.pcOff += i.sBx;
+ L.stack[ra].value = idx;
+ L.stack[ra + 3] = new TValue(CT.LUA_TNUMINT, idx); // TODO: if tvalue already there, just update it
+ }
+ } else { /* floating loop */
+ let step = L.stack[ra + 2].value;
+ let idx = L.stack[ra].value + step;
+ let limit = L.stack[ra + 1].value;
+
+ // TODO: luai_numlt, luai_numle
+ if (0 < step ? idx <= limit : limit <= idx) {
+ ci.pcOff += i.sBx;
+ L.stack[ra].value = idx;
+ L.stack[ra + 3] = new TValue(CT.LUA_TNUMFLT, idx); // TODO: if tvalue already there, just update it
+ }
+ }
break;
}
case "OP_FORPREP": {
+ let init = L.stack[ra];
+ let plimit = L.stack[ra + 1];
+ let pstep = L.stack[ra + 2];
+ let forlimit = LuaVM.forlimit(plimit, pstep.value);
+
+ if (init.ttisinteger() && pstep.ttisinteger() && forlimit.casted) { /* all values are integer */
+ let initv = forlimit.stopnow ? 0 : init.value;
+ plimit.value = forlimit.ilimit;
+ init.value = initv - pstep.value;
+ } else { /* try making all values floats */
+ let ninit = LuaVM.tonumber(init);
+ let nlimit = LuaVM.tonumber(plimit);
+ let nstep = LuaVM.tonumber(pstep);
+
+ if (nlimit === false)
+ throw new Error("'for' limit must be a number");
+
+ plimit.type = CT.LUA_TNUMFLT;
+ plimit.value = nlimit.value;
+
+ if (nstep === false)
+ throw new Error("'for' step must be a number");
+
+ pstep.type = CT.LUA_TNUMFLT;
+ pstep.value = nstep.value;
+
+ if (ninit === false)
+ throw new Error("'for' initial value must be a number");
+
+ init.type = CT.LUA_TNUMFLT;
+ init.value = ninit.value - nstep.value;
+ }
+
+ ci.pcOff += i.sBx;
break;
}
case "OP_TFORCALL": {
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 */
+ }
break;
}
case "OP_SETLIST": {
@@ -730,6 +791,58 @@ class LuaVM {
}
}
+ static forlimit(obj, step) {
+ let stopnow = false;
+ let ilimit = LuaVM.luaV_tointeger(obj, step < 0 ? 2 : 1);
+ if (ilimit === false) {
+ let n = LuaVM.tonumber(obj);
+ if (n === false)
+ return false;
+
+ if (0 < n) {
+ ilimit = llimit.LUA_MAXINTEGER;
+ if (step < 0) stopnow = true;
+ } else {
+ ilimit = llimit.LUA_MININTEGER;
+ if (step >= 0) stopnow = true;
+ }
+ }
+
+ return {
+ casted: true,
+ stopnow: stopnow,
+ ilimit: ilimit
+ }
+ }
+
+ /*
+ ** 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
+ */
+ static luaV_tointeger(obj, mode) {
+ if (obj.ttisfloat()) {
+ let n = obj.value;
+ let f = n|0;
+
+ if (n !== f) { /* not an integral value? */
+ 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) */
+ }
+
+ return f|0;
+ } else if (obj.ttisinteger()) {
+ return obj.value|0;
+ } else if (obj.ttisstring()) {
+ return LuaVM.luaV_tointeger(parseFloat(obj.value), mode); // TODO: luaO_str2num
+ }
+
+ return false;
+ }
+
static tonumber(v) {
if (v.type === CT.LUA_TNUMFLT)
return new TValue(v.type, v.value);
@@ -738,7 +851,7 @@ class LuaVM {
return new TValue(CT.LUA_TNUMFLT, v.value);
if (v.type === CT.LUA_TSHRSTR || v.type === CT.LUA_TLNGSTR)
- return new TValue(CT.LUA_TNUMFLT, parseFloat(v.value)); // TODO: 0x or other exotic form
+ return new TValue(CT.LUA_TNUMFLT, parseFloat(v.value)); // TODO: luaO_str2num
return false;
}
diff --git a/tests/lvm.js b/tests/lvm.js
index 4c10521..199001f 100644
--- a/tests/lvm.js
+++ b/tests/lvm.js
@@ -453,14 +453,15 @@ test('TEST (false)', function (t) {
});
-test('SETTABLE, GETTABLE', function (t) {
+test('FORPREP, FORLOOP (int)', function (t) {
let luaCode = `
- local t = {}
+ local total = 0
- t[1] = "hello"
- t["two"] = "world"
+ for i = 0, 10 do
+ total = total + i
+ end
- return t[1], t["two"]
+ return total
`, vm;
t.plan(2);
@@ -472,15 +473,69 @@ test('SETTABLE, GETTABLE', function (t) {
vm.execute();
}, "Program executed without errors");
- t.stritEqual(
- vm.L.stack[vm.L.top - 1].value.array[1],
- "hello",
+ t.strictEqual(
+ vm.L.stack[vm.L.top - 1].value,
+ 55,
"Program output is correct"
);
+});
+
+test('FORPREP, FORLOOP (float)', function (t) {
+ let luaCode = `
+ local total = 0
- t.stritEqual(
- vm.L.stack[vm.L.top - 1].value.hash.get("two"),
- "world",
+ for i = 0.5, 10.5 do
+ total = total + i
+ end
+
+ return total
+ `, vm;
+
+ t.plan(1);
+
+ t.comment("Running following code: \n" + luaCode);
+
+ // t.doesNotThrow(function () {
+ vm = getVM(luaCode);
+ vm.execute();
+ // }, "Program executed without errors");
+
+ t.strictEqual(
+ vm.L.stack[vm.L.top - 1].value,
+ 60.5,
"Program output is correct"
);
-}); \ No newline at end of file
+});
+
+
+// test('SETTABLE, GETTABLE', function (t) {
+// let luaCode = `
+// local t = {}
+
+// t[1] = "hello"
+// t["two"] = "world"
+
+// return t[1], t["two"]
+// `, vm;
+
+// t.plan(2);
+
+// t.comment("Running following code: \n" + luaCode);
+
+// t.doesNotThrow(function () {
+// vm = getVM(luaCode);
+// vm.execute();
+// }, "Program executed without errors");
+
+// t.stritEqual(
+// vm.L.stack[vm.L.top - 1].value.array[1],
+// "hello",
+// "Program output is correct"
+// );
+
+// t.stritEqual(
+// vm.L.stack[vm.L.top - 1].value.hash.get("two"),
+// "world",
+// "Program output is correct"
+// );
+// }); \ No newline at end of file