waforth/tests/index.js
2018-06-03 21:49:59 +02:00

1251 lines
30 KiB
JavaScript

import WAForth from "../src/shell/WAForth";
import { mocha } from "mocha";
import { expect } from "chai";
import sieve from "../src/shell/sieve";
mocha.setup("bdd");
describe("WAForth", () => {
let forth, stack, output, core, memory, memory8;
beforeEach(() => {
forth = new WAForth();
forth.onEmit = c => {
output = output + String.fromCharCode(c);
};
const x = forth.start({ skipPrelude: true }).then(
() => {
core = forth.core.exports;
output = "";
memory = new Int32Array(core.memory.buffer, 0, 0x30000);
memory8 = new Uint8Array(core.memory.buffer, 0, 0x30000);
// dictionary = new Uint8Array(core.memory.buffer, 0x1000, 0x1000);
stack = new Int32Array(core.memory.buffer, core.tos(), 0x100);
},
err => {
console.error(err);
}
);
return x;
});
// eslint-disable-next-line no-unused-vars
function dumpTable() {
for (let i = 0; i < core.table.length; ++i) {
console.log("table", i, core.table.get(i));
}
}
function getString(p, n) {
let name = [];
for (let i = 0; i < n; ++i) {
name.push(String.fromCharCode(memory8[p + i]));
}
return name.join("");
}
function getCountedString(p) {
return getString(p + 4, memory[p / 4]);
}
function loadString(s) {
run("HERE");
run(`${s.length} ,`);
for (let i = 0; i < s.length; ++i) {
run(`${s.charCodeAt(i)} C,`);
}
}
// eslint-disable-next-line no-unused-vars
function dumpWord(w) {
let end = here();
let p = latest();
while (p != w) {
console.log("SEARCH", p, w);
end = p;
p = memory[p / 4];
}
const previous = memory[p / 4];
const length = memory8[p + 4];
const name = getString(p + 4 + 1, length);
const codeP = (p + 4 + 1 + length + 3) & ~3;
const code = memory[codeP / 4];
const data = [];
for (let i = codeP + 4; i < end; ++i) {
data.push(memory8[i]);
}
console.log("Entry:", p, previous, length, name, code, data, end);
}
function run(s) {
const r = forth.run(s);
expect(r).to.not.be.below(0);
output = output.substr(0, output.length - 3); // Strip 'ok\n' from output
return r;
}
function here() {
run("HERE");
const result = memory[core.tos() / 4 - 1];
run("DROP");
return result;
}
function latest() {
run("LATEST");
const result = memory[core.tos() / 4 - 1];
run("DROP");
return result;
}
describe("leb128", () => {
it("should convert 0x0", () => {
const r = core.leb128(0x0, 0x0);
expect(r).to.eql(0x1);
expect(memory8[0]).to.eql(0x0);
});
it("should convert 0x17", () => {
const r = core.leb128(0x0, 0x17);
expect(r).to.eql(0x1);
expect(memory8[0]).to.eql(0x17);
});
it("should convert 0x80", () => {
const r = core.leb128(0x0, 0x80);
expect(r).to.eql(0x2);
expect(memory8[0]).to.eql(0x80);
expect(memory8[1]).to.eql(0x01);
});
it("should convert 0x12345", () => {
const r = core.leb128(0x0, 0x12345);
expect(r).to.eql(0x3);
expect(memory8[0]).to.eql(0xc5);
expect(memory8[1]).to.eql(0xc6);
expect(memory8[2]).to.eql(0x04);
});
it("should convert -1", () => {
const r = core.leb128(0x0, -1);
expect(r).to.eql(0x1);
expect(memory8[0]).to.eql(0x7f);
});
it("should convert -0x12345", () => {
const r = core.leb128(0x0, -0x12345);
expect(r).to.eql(0x3);
expect(memory8[0]).to.eql(0xbb);
expect(memory8[1]).to.eql(0xb9);
expect(memory8[2]).to.eql(0x7b);
});
});
describe("leb128-4p", () => {
it("should convert 0x0", () => {
expect(core.leb128_4p(0x0)).to.eql(0x808080);
});
it("should convert 0x17", () => {
expect(core.leb128_4p(0x17)).to.eql(0x808097);
});
it("should convert 0x80", () => {
expect(core.leb128_4p(0x80)).to.eql(0x808180);
});
it("should convert 0xBADF00D", () => {
expect(core.leb128_4p(0xbadf00d)).to.eql(0x5db7e08d);
});
it("should convert 0xFFFFFFF", () => {
expect(core.leb128_4p(0xfffffff)).to.eql(0x7fffffff);
});
});
describe("interpret", () => {
it("should return an error when word is not found", () => {
forth.read("BADWORD");
expect(core.interpret()).to.eql(-1);
});
it("should interpret a positive number", () => {
forth.read("123");
expect(core.interpret()).to.eql(0);
expect(stack[0]).to.eql(123);
});
it("should interpret a negative number", () => {
forth.read("-123");
expect(core.interpret()).to.eql(0);
expect(stack[0]).to.eql(-123);
});
it("should interpret a hex", () => {
forth.read("16 BASE ! DF");
expect(core.interpret()).to.eql(0);
expect(stack[0]).to.eql(223);
});
it("should not interpret hex in decimal mode", () => {
forth.read("DF");
expect(core.interpret()).to.eql(-1);
});
it("should fail on half a word", () => {
forth.read("23FOO");
expect(core.interpret()).to.eql(-1);
});
});
describe("DUP", () => {
it("should work", () => {
run("121");
run("DUP");
expect(stack[0]).to.eql(121);
expect(stack[1]).to.eql(121);
});
});
describe("?DUP", () => {
it("should duplicate when not 0", () => {
run("121");
run("?DUP 5");
expect(stack[0]).to.eql(121);
expect(stack[1]).to.eql(121);
expect(stack[2]).to.eql(5);
});
it("should not duplicate when 0", () => {
run("0");
run("?DUP 5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(5);
});
});
describe("2DUP", () => {
it("should work", () => {
run("222");
run("173");
run("2DUP");
run("5");
expect(stack[0]).to.eql(222);
expect(stack[1]).to.eql(173);
expect(stack[2]).to.eql(222);
expect(stack[3]).to.eql(173);
expect(stack[4]).to.eql(5);
});
});
describe("ROT", () => {
it("should work", () => {
run("1 2 3 ROT 5");
expect(stack[0]).to.eql(2);
expect(stack[1]).to.eql(3);
expect(stack[2]).to.eql(1);
expect(stack[3]).to.eql(5);
});
});
describe("*", () => {
it("should multiply", () => {
run("3");
run("4");
run("*");
run("5");
expect(stack[0]).to.eql(12);
expect(stack[1]).to.eql(5);
});
it("should multiply negative", () => {
run("-3");
run("4");
run("*");
run("5");
expect(stack[0]).to.eql(-12);
expect(stack[1]).to.eql(5);
});
});
describe("+", () => {
it("should add", () => {
run("3");
run("4");
run("+");
run("5");
expect(stack[0]).to.eql(7);
expect(stack[1]).to.eql(5);
});
});
describe("-", () => {
it("should subtract", () => {
run("8 5 - 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(5);
});
it("should subtract to negative", () => {
run("8 13 - 5");
expect(stack[0]).to.eql(-5);
expect(stack[1]).to.eql(5);
});
it("should subtract negative", () => {
run("8 -3 - 5");
expect(stack[0]).to.eql(11);
expect(stack[1]).to.eql(5);
});
});
describe("/", () => {
it("should divide", () => {
run("15 5 / 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(5);
});
it("should divide negative", () => {
run("15 -5 / 5");
expect(stack[0]).to.eql(-3);
expect(stack[1]).to.eql(5);
});
});
describe("/MOD", () => {
it("should work", () => {
run("15 6 /MOD 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(2);
expect(stack[2]).to.eql(5);
});
});
describe("*/", () => {
it("should work with small numbers", () => {
run("10 3 5 */ 5");
expect(stack[0]).to.eql(6);
expect(stack[1]).to.eql(5);
});
it("should work with large numbers", () => {
run("268435455 1000 5000 */");
expect(stack[0]).to.eql(53687091);
});
});
describe("*/MOD", () => {
it("should work with small numbers", () => {
run("9 3 5 */MOD 7");
expect(stack[0]).to.eql(2);
expect(stack[1]).to.eql(5);
expect(stack[2]).to.eql(7);
});
it("should work with large numbers", () => {
run("268435455 1000 3333 */MOD");
expect(stack[0]).to.eql(1230);
expect(stack[1]).to.eql(80538690);
});
});
describe("1+", () => {
it("should work with positive numbers", () => {
run("3");
run("1+");
run("5");
expect(stack[0]).to.eql(4);
expect(stack[1]).to.eql(5);
});
it("should work with negative numbers", () => {
run("-3");
run("1+");
run("5");
expect(stack[0]).to.eql(-2);
expect(stack[1]).to.eql(5);
});
});
describe("1-", () => {
it("should work with positive numbers", () => {
run("3");
run("1-");
run("5");
expect(stack[0]).to.eql(2);
expect(stack[1]).to.eql(5);
});
it("should work with negative numbers", () => {
run("-3");
run("1-");
run("5");
expect(stack[0]).to.eql(-4);
expect(stack[1]).to.eql(5);
});
});
describe(">", () => {
it("should test true when greater", () => {
run("5");
run("3");
run(">");
run("5");
expect(stack[0]).to.eql(-1);
expect(stack[1]).to.eql(5);
});
it("should test false when smaller", () => {
run("3");
run("5");
run(">");
run("5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(5);
});
it("should test false when equal", () => {
run("5");
run("5");
run(">");
run("5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(5);
});
it("should work with negative numbers", () => {
run("5");
run("-3");
run(">");
run("5");
expect(stack[0]).to.eql(-1);
expect(stack[1]).to.eql(5);
});
});
describe("NEGATE", () => {
it("should negate positive number", () => {
run("7 NEGATE 5");
expect(stack[0]).to.eql(-7);
expect(stack[1]).to.eql(5);
});
it("should negate negative number", () => {
run("-7 NEGATE 5");
expect(stack[0]).to.eql(7);
expect(stack[1]).to.eql(5);
});
it("should negate negative zero", () => {
run("0 NEGATE 5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(5);
});
});
describe("0=", () => {
it("should test true", () => {
run("0");
run("0=");
run("5");
expect(stack[0]).to.eql(-1);
expect(stack[1]).to.eql(5);
});
it("should test false", () => {
run("23");
run("0=");
run("5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(5);
});
});
describe("0>", () => {
it("should test true", () => {
run("2");
run("0>");
run("5");
expect(stack[0]).to.eql(-1);
expect(stack[1]).to.eql(5);
});
it("should test false", () => {
run("-3");
run("0>");
run("5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(5);
});
});
describe("OVER", () => {
it("should work", () => {
run("12");
run("34");
run("OVER");
run("5");
expect(stack[0]).to.eql(12);
expect(stack[1]).to.eql(34);
expect(stack[2]).to.eql(12);
expect(stack[3]).to.eql(5);
});
});
describe("SWAP", () => {
it("should work", () => {
run("12");
run("34");
run("SWAP");
run("5");
expect(stack[0]).to.eql(34);
expect(stack[1]).to.eql(12);
expect(stack[2]).to.eql(5);
});
});
describe("EMIT", () => {
it("should work once", () => {
run("87");
run("EMIT");
expect(output).to.eql("W");
});
it("should work twice", () => {
run("97");
run("87");
run("EMIT");
run("EMIT");
expect(output).to.eql("Wa");
});
});
describe("DROP", () => {
it("should drop", () => {
run("222");
run("173");
run("DROP");
run("190");
expect(stack[0]).to.eql(222);
expect(stack[1]).to.eql(190);
});
});
describe("ERASE", () => {
it("should erase", () => {
const ptr = here();
memory8[ptr] = 222;
memory8[ptr + 1] = 173;
memory8[ptr + 2] = 190;
memory8[ptr + 3] = 239;
run((ptr + 1).toString(10));
run("2 ERASE 5");
expect(memory8[ptr + 0]).to.eql(222);
expect(memory8[ptr + 1]).to.eql(0x00);
expect(memory8[ptr + 2]).to.eql(0x00);
expect(memory8[ptr + 3]).to.eql(239);
expect(stack[0]).to.eql(5);
});
});
describe("IF/ELSE/THEN", () => {
it("should take the then branch without else", () => {
run(`: FOO IF 8 THEN ;`);
run("1 FOO 5");
expect(stack[0]).to.eql(8);
expect(stack[1]).to.eql(5);
});
it("should not take the then branch without else", () => {
run(": FOO");
run("IF");
run("8");
run("THEN");
run("0");
run(";");
run("FOO");
run("5");
expect(stack[0]).to.eql(5);
});
it("should take the then branch with else", () => {
run(": FOO");
run("IF");
run("8");
run("ELSE");
run("9");
run("THEN");
run(";");
run("1");
run("FOO");
run("5");
expect(stack[0]).to.eql(8);
expect(stack[1]).to.eql(5);
});
it("should take the else branch with else", () => {
run(": FOO");
run("IF");
run("8");
run("ELSE");
run("9");
run("THEN");
run(";");
run("0");
run("FOO");
run("5");
expect(stack[0]).to.eql(9);
expect(stack[1]).to.eql(5);
});
it("should support nested if", () => {
run(`: FOO
IF
IF 8 ELSE 9 THEN
10
ELSE
11
THEN
;`);
run("0 1 FOO 5");
expect(stack[0]).to.eql(9);
expect(stack[1]).to.eql(10);
expect(stack[2]).to.eql(5);
});
});
describe("DO/LOOP", () => {
it("should run a loop", () => {
run(`: FOO 4 0 DO 3 LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(3);
expect(stack[2]).to.eql(3);
expect(stack[3]).to.eql(3);
expect(stack[4]).to.eql(5);
});
it("should run a nested loop", () => {
run(`: FOO 3 0 DO 2 0 DO 3 LOOP LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(3);
expect(stack[2]).to.eql(3);
expect(stack[3]).to.eql(3);
expect(stack[4]).to.eql(3);
expect(stack[5]).to.eql(3);
expect(stack[6]).to.eql(5);
});
});
describe("LEAVE", () => {
it("should leave", () => {
run(`: FOO 4 0 DO 3 LEAVE 6 LOOP 4 ;`);
run("FOO 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(4);
expect(stack[2]).to.eql(5);
});
});
describe("+LOOP", () => {
it("should increment a loop", () => {
run(`: FOO 10 0 DO 3 2 +LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(3);
expect(stack[2]).to.eql(3);
expect(stack[3]).to.eql(3);
expect(stack[4]).to.eql(3);
expect(stack[5]).to.eql(5);
});
it("should increment a loop beyond the index", () => {
run(`: FOO 10 0 DO 3 8 +LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(3);
expect(stack[2]).to.eql(5);
});
});
describe("I", () => {
it("should work", () => {
run(`: FOO 4 0 DO I LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(1);
expect(stack[2]).to.eql(2);
expect(stack[3]).to.eql(3);
expect(stack[4]).to.eql(5);
});
it("should work in a nested loop", () => {
run(`: FOO 3 0 DO 2 0 DO I LOOP LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(1);
expect(stack[2]).to.eql(0);
expect(stack[3]).to.eql(1);
expect(stack[4]).to.eql(0);
expect(stack[5]).to.eql(1);
expect(stack[6]).to.eql(5);
});
});
describe("J", () => {
it("should work", () => {
run(`: FOO 3 0 DO 2 0 DO J LOOP LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(0);
expect(stack[2]).to.eql(1);
expect(stack[3]).to.eql(1);
expect(stack[4]).to.eql(2);
expect(stack[5]).to.eql(2);
expect(stack[6]).to.eql(5);
});
it("should work in a nested loop", () => {
run(`: FOO 3 0 DO 2 0 DO J LOOP LOOP ;`);
run("FOO 5");
expect(stack[0]).to.eql(0);
expect(stack[1]).to.eql(0);
expect(stack[2]).to.eql(1);
expect(stack[3]).to.eql(1);
expect(stack[4]).to.eql(2);
expect(stack[5]).to.eql(2);
expect(stack[6]).to.eql(5);
});
});
describe("BEGIN / WHILE / REPEAT", () => {
it("should work", () => {
run(`: FOO BEGIN DUP 2 * DUP 16 < WHILE DUP REPEAT 7 ;`);
run("1 FOO 5");
expect(stack[0]).to.eql(1);
expect(stack[1]).to.eql(2);
expect(stack[2]).to.eql(2);
expect(stack[3]).to.eql(4);
expect(stack[4]).to.eql(4);
expect(stack[5]).to.eql(8);
expect(stack[6]).to.eql(8);
expect(stack[7]).to.eql(16);
expect(stack[8]).to.eql(7);
expect(stack[9]).to.eql(5);
});
});
describe("BEGIN / UNTIL", () => {
it("should work", () => {
run(`: FOO BEGIN DUP 2 * DUP 16 > UNTIL 7 ;`);
run("1 FOO 5");
expect(stack[0]).to.eql(1);
expect(stack[1]).to.eql(2);
expect(stack[2]).to.eql(4);
expect(stack[3]).to.eql(8);
expect(stack[4]).to.eql(16);
expect(stack[5]).to.eql(32);
expect(stack[6]).to.eql(7);
expect(stack[7]).to.eql(5);
});
});
describe("EXIT", () => {
it("should work", () => {
run(`: FOO IF 3 EXIT 4 THEN 5 ;`);
run("1 FOO 6");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(6);
});
});
describe("( / )", () => {
beforeEach(() => {
core.loadPrelude();
});
it("should work", () => {
run(": FOO ( bad -- x ) 7 ;");
run("1 FOO 5");
expect(stack[0]).to.eql(1);
expect(stack[1]).to.eql(7);
expect(stack[2]).to.eql(5);
});
});
describe("CHAR", () => {
it("should work with a single character", () => {
run("CHAR A 5");
expect(stack[0]).to.eql(65);
expect(stack[1]).to.eql(5);
});
it("should work with multiple characters", () => {
run("CHAR ABC 5");
expect(stack[0]).to.eql(65);
expect(stack[1]).to.eql(5);
});
});
// describe("word", () => {
// it("should read a word", () => {
// forth.read(" FOO BAR BAZ ");
// core.WORD();
// expect(getCountedString(stack[0])).to.eql("FOO");
// });
//
// it("should read two words", () => {
// forth.read(" FOO BAR BAZ ");
// core.WORD();
// core.WORD();
// expect(getCountedString(stack[1])).to.eql("BAR");
// });
//
// it("should skip comments", () => {
// forth.read(" \\ FOO BAZ\n BART BAZ");
// core.WORD();
// expect(getCountedString(stack[0])).to.eql("BART");
// });
//
// it("should stop at end of buffer while parsing word", () => {
// forth.read("FOO");
// core.WORD();
// expect(getCountedString(stack[0])).to.eql("FOO");
// });
//
// it("should stop at end of buffer while parsing comments", () => {
// forth.read(" \\FOO");
// core.WORD();
// expect(getCountedString()).to.eql("");
// });
//
// it("should stop when parsing empty line", () => {
// forth.read(" ");
// core.WORD();
// expect(getCountedString()).to.eql("");
// });
//
// it("should stop when parsing nothing", () => {
// forth.read("");
// core.WORD();
// expect(getCountedString()).to.eql("");
// });
// });
describe("FIND", () => {
it("should find a word", () => {
loadString("DUP");
run("FIND");
expect(stack[0]).to.eql(131768);
expect(stack[1]).to.eql(-1);
});
it("should find a short word", () => {
loadString("!");
run("FIND");
expect(stack[0]).to.eql(131072);
expect(stack[1]).to.eql(-1);
});
it("should find an immediate word", () => {
loadString("+LOOP");
run("FIND");
expect(stack[0]).to.eql(131160);
expect(stack[1]).to.eql(1);
});
it("should not find an unexisting word", () => {
loadString("BADWORD");
run("FIND");
expect(stack[1]).to.eql(0);
});
it("should not find a very long unexisting word", () => {
loadString("VERYVERYVERYBADWORD");
run("FIND");
expect(stack[1]).to.eql(0);
});
});
describe("BASE", () => {
it("should contain the base", () => {
run("BASE @ 5");
expect(stack[0]).to.eql(10);
expect(stack[1]).to.eql(5);
});
});
describe("KEY", () => {
it("should read a key", () => {
run("KEY F");
run("5");
expect(stack[0]).to.eql(70);
expect(stack[1]).to.eql(5);
});
});
describe("LITERAL", () => {
it("should put a literal on the stack", () => {
run("20 : FOO LITERAL ;");
run("5 FOO");
expect(stack[0]).to.eql(5);
expect(stack[1]).to.eql(20);
});
});
describe("[ ]", () => {
it("should work", () => {
run(": FOO [ 20 5 * ] LITERAL ;");
run("5 FOO 6");
expect(stack[0]).to.eql(5);
expect(stack[1]).to.eql(100);
expect(stack[2]).to.eql(6);
});
});
describe("C@", () => {
it("should fetch an aligned character", () => {
const ptr = here();
memory8[ptr] = 222;
memory8[ptr + 1] = 173;
run(ptr.toString());
run("C@");
expect(stack[0]).to.eql(222);
});
it("should fetch an unaligned character", () => {
const ptr = here();
memory8[ptr] = 222;
memory8[ptr + 1] = 173;
run((ptr + 1).toString());
run("C@");
expect(stack[0]).to.eql(173);
});
});
describe("C!", () => {
it("should store an aligned character", () => {
const ptr = here();
memory8[ptr] = 222;
memory8[ptr + 1] = 173;
run("190");
run(ptr.toString());
run("C! 5");
expect(stack[0]).to.eql(5);
expect(memory8[ptr]).to.eql(190);
expect(memory8[ptr + 1]).to.eql(173);
});
it("should store an unaligned character", () => {
const ptr = here();
memory8[ptr] = 222;
memory8[ptr + 1] = 173;
run("190");
run((ptr + 1).toString());
run("C! 5");
expect(stack[0]).to.eql(5);
expect(memory8[ptr]).to.eql(222);
expect(memory8[ptr + 1]).to.eql(190);
});
});
describe("@", () => {
it("should fetch", () => {
const ptr = here();
memory[ptr / 4] = 123456;
run(ptr.toString());
run("@ 5");
expect(stack[0]).to.eql(123456);
expect(stack[1]).to.eql(5);
});
});
describe("!", () => {
it("should store", () => {
const ptr = here();
run("12345");
run(ptr.toString());
run("! 5");
expect(stack[0]).to.eql(5);
expect(memory[ptr / 4]).to.eql(12345);
});
});
describe(",", () => {
it("should add word", () => {
run("HERE");
run("1234");
run(",");
run("HERE");
expect(stack[1] - stack[0]).to.eql(4);
expect(memory[stack[0] / 4]).to.eql(1234);
});
});
describe('S"', () => {
it("should work", () => {
run(': FOO S" Foo Bar" ;');
run("FOO");
expect(stack[1]).to.eql(7);
expect(getString(stack[0], stack[1])).to.eql("Foo Bar");
});
it("should work with 2 strings", () => {
run(': FOO S" Foo Bar" ;');
run(': BAR S" Baz Ba" ;');
run("FOO BAR 5");
expect(stack[1]).to.eql(7);
expect(getString(stack[0], stack[1])).to.eql("Foo Bar");
expect(stack[3]).to.eql(6);
expect(getString(stack[2], stack[3])).to.eql("Baz Ba");
});
});
describe('."', () => {
it("should work", () => {
run(': FOO ." Foo Bar" ;');
run("FOO 5");
expect(stack[0]).to.eql(5);
expect(output).to.eql("Foo Bar");
});
});
describe("MOVE", () => {
it("should work with non-overlapping regions", () => {
const ptr = here();
memory8[ptr] = 1;
memory8[ptr + 1] = 2;
memory8[ptr + 2] = 3;
memory8[ptr + 3] = 4;
memory8[ptr + 4] = 5;
run("HERE HERE 10 + 4 MOVE 5");
expect(stack[0]).to.eql(5);
expect(memory8[ptr + 10]).to.eql(1);
expect(memory8[ptr + 11]).to.eql(2);
expect(memory8[ptr + 12]).to.eql(3);
expect(memory8[ptr + 13]).to.eql(4);
expect(memory8[ptr + 14]).to.eql(0);
});
it("should work with begin-overlapping regions", () => {
const ptr = here();
memory8[ptr] = 1;
memory8[ptr + 1] = 2;
memory8[ptr + 2] = 3;
memory8[ptr + 3] = 4;
memory8[ptr + 4] = 5;
run("HERE HERE 2 + 4 MOVE 5");
expect(stack[0]).to.eql(5);
expect(memory8[ptr + 0]).to.eql(1);
expect(memory8[ptr + 1]).to.eql(2);
expect(memory8[ptr + 2]).to.eql(1);
expect(memory8[ptr + 3]).to.eql(2);
expect(memory8[ptr + 4]).to.eql(3);
expect(memory8[ptr + 5]).to.eql(4);
expect(memory8[ptr + 6]).to.eql(0);
});
it("should work with end-overlapping regions", () => {
const ptr = here();
memory8[ptr + 10] = 1;
memory8[ptr + 11] = 2;
memory8[ptr + 12] = 3;
memory8[ptr + 13] = 4;
memory8[ptr + 14] = 5;
run("HERE 10 + DUP 2 - 4 MOVE 5");
expect(stack[0]).to.eql(5);
expect(memory8[ptr + 8]).to.eql(1);
expect(memory8[ptr + 9]).to.eql(2);
expect(memory8[ptr + 10]).to.eql(3);
expect(memory8[ptr + 11]).to.eql(4);
expect(memory8[ptr + 12]).to.eql(3);
expect(memory8[ptr + 13]).to.eql(4);
expect(memory8[ptr + 14]).to.eql(5);
});
});
describe("RECURSE", () => {
it("should recurse", () => {
run(": FOO DUP 4 < IF DUP 1+ RECURSE ELSE 12 THEN 13 ;");
run("1 FOO 5");
expect(stack[0]).to.eql(1);
expect(stack[1]).to.eql(2);
expect(stack[2]).to.eql(3);
expect(stack[3]).to.eql(4);
expect(stack[4]).to.eql(12);
expect(stack[5]).to.eql(13);
expect(stack[6]).to.eql(13);
expect(stack[7]).to.eql(13);
expect(stack[8]).to.eql(13);
expect(stack[9]).to.eql(5);
});
});
describe("CREATE", () => {
it("should create words", () => {
run("HERE");
run("LATEST");
run("CREATE DUP");
run("HERE");
run("LATEST");
expect(stack[2] - stack[0]).to.eql(4 + 4 + 4);
expect(stack[3]).to.eql(stack[0]);
expect(stack[3]).to.not.eql(stack[1]);
});
it("should create findable words", () => {
run("CREATE FOOBAR");
run("LATEST");
run("CREATE BAM");
loadString("FOOBAR");
run("FIND");
expect(stack[1]).to.eql(stack[0]);
expect(stack[2]).to.eql(-1);
});
it("should align unaligned words", () => {
run("CREATE DUPE");
run("HERE");
expect(stack[0] % 4).to.eql(0);
});
it("should align aligned words", () => {
run("CREATE DUP");
run("HERE");
expect(stack[0] % 4).to.eql(0);
});
});
describe("IMMEDIATE", () => {
it("should make words immediate", () => {
run("CREATE FOOBAR IMMEDIATE");
loadString("FOOBAR");
run("FIND");
expect(stack[1]).to.eql(1);
});
});
describe(":", () => {
it("should compile multiple instructions", () => {
run(": FOOBAR 4 * ;");
run("3 FOOBAR");
expect(stack[0]).to.eql(12);
});
it("should compile negative numbers", () => {
run(": FOOBAR -4 * ;");
run("3 FOOBAR");
expect(stack[0]).to.eql(-12);
});
it("should compile large numbers", () => {
run(": FOOBAR 111111 * ;");
run("3 FOOBAR");
expect(stack[0]).to.eql(333333);
});
it("should skip comments", () => {
run(": FOOBAR\n\n \\ Test string \n 4 * ;");
run("3 FOOBAR 5");
expect(stack[0]).to.eql(12);
expect(stack[1]).to.eql(5);
});
it("should override", () => {
run(": FOOBAR 3 ;");
run(": FOOBAR FOOBAR 4 ;");
run("FOOBAR 5");
expect(stack[0]).to.eql(3);
expect(stack[1]).to.eql(4);
expect(stack[2]).to.eql(5);
});
it("should compile a name with an illegal WASM character", () => {
run(': F" 3 0 DO 2 LOOP ;');
});
});
describe("VARIABLE", () => {
it("should work with one variable", () => {
run("VARIABLE FOO");
run("12 FOO !");
run("FOO @ 5");
expect(stack[0]).to.eql(12);
expect(stack[1]).to.eql(5);
});
it("should work with two variables", () => {
run("VARIABLE FOO VARIABLE BAR");
run("12 FOO ! 13 BAR !");
run("FOO @ BAR @ 5");
expect(stack[0]).to.eql(12);
expect(stack[1]).to.eql(13);
expect(stack[2]).to.eql(5);
});
});
describe("CONSTANT", () => {
it("should work", () => {
run("12 CONSTANT FOO");
run("FOO 5");
expect(stack[0]).to.eql(12);
expect(stack[1]).to.eql(5);
});
});
describe("VALUE", () => {
it("should store a value", () => {
run("12 VALUE FOO");
run("FOO 5");
expect(stack[0]).to.eql(12);
expect(stack[1]).to.eql(5);
});
it("should update a value", () => {
run("12 VALUE FOO");
run("13 TO FOO");
run("FOO 5");
expect(stack[0]).to.eql(13);
expect(stack[1]).to.eql(5);
});
});
// describe.only("DOES>", () => {
// it("should work", () => {
// run(": ID CREATE 1 , DOES> @");
// });
// });
describe("UWIDTH", () => {
beforeEach(() => {
core.loadPrelude();
});
it("should work with 3 digits", () => {
run("123 UWIDTH");
expect(stack[0]).to.eql(3);
});
it("should work with 4 digits", () => {
run("1234 UWIDTH");
expect(stack[0]).to.eql(4);
});
});
describe("system", () => {
beforeEach(() => {
core.loadPrelude();
});
it("should run sieve", () => {
run(sieve);
run("100 sieve");
expect(output.trim()).to.eql("97");
});
it("should run direct sieve", () => {
run(sieve);
run("100 sieve_direct");
expect(stack[0]).to.eql(97);
});
});
});
// mocha.checkLeaks();
mocha.globals(["jQuery"]);
mocha.run();