mirror of
https://github.com/remko/waforth
synced 2024-12-27 09:59:29 +01:00
1251 lines
30 KiB
JavaScript
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();
|