forth-stuff/tests.lua

565 lines
22 KiB
Lua

local istream = require("InputStream")
local ds = require("DataStructures")
local Stack, Dictionary, WordInfo, Environment = ds.Stack, ds.Dictionary, ds.WordInfo, ds.Environment
local CoreHelpers = require("CoreHelpers")
local CoreWords = require("CoreWords")
local interpreters = require("Interpreters")
local TEST_REPEAT_COUNT = 200
local NUM_RANGE = 100000000000000
local STACK_RANGE = 100
function pushThree(s)
s:push(1)
s:push(2)
s:push(3)
end
math.randomseed( os.time())
describe("Stack Tests", function()
local stack = Stack:new()
it("fails on stack underflow", function()
stack = Stack:new()
assert.has.errors(function()
stack:pop()
end)
end)
it("pushes three items properly", function()
stack = Stack:new()
pushThree(stack)
assert.are.same(stack.contents, {1,2,3})
end)
it("pops three items properly", function()
stack = Stack:new()
pushThree(stack)
assert.are.same(stack.contents, {1,2,3})
assert(3 == stack:pop())
assert.are.same(stack.contents, {1,2})
assert(2 == stack:pop())
assert.are.same(stack.contents, {1})
assert(1 == stack:pop())
assert.are.same(stack.contents, {})
assert.has.errors(function()
stack:pop()
end)
end)
it("PUSH 1, 2, 3, POP = 3, PUSH 4 = {1,2,4}", function()
stack = Stack:new()
pushThree(stack)
assert(3 == stack:pop())
assert.are.same(stack.contents, {1, 2})
stack:push(4)
assert.are.same(stack.contents, {1, 2, 4})
end)
end)
local function dummyInput()
return "1234567890"
end
describe("InputStream tests", function()
local input = istream:new(dummyInput)
it("gets a new refill accurately", function()
input = istream:new(dummyInput)
assert.are.equal(input.refill, dummyInput)
end)
it("reads text properly", function()
input = istream:new(dummyInput)
local inputString = dummyInput()
for i = 0, 100 do
local j = i % #inputString + 1
assert.are.same(inputString:sub(j, j), input:next())
end
end)
describe("Tests with ends of input", function()
it("handles a single line", function()
local inputstr = "TEST TEST TEST"
input = CoreHelpers.oneReadInputStream(inputstr)
for i = 1, #inputstr do
assert.are.same(inputstr:sub(i, i), input:next())
end
for i = 1, TEST_REPEAT_COUNT do
assert.are.same(nil, input:next())
end
end)
it("handles multiple lines", function()
local inputstr = "TEST\nTEST\nTEST\nTEST\nTEST\nTEST51515151"
input = CoreHelpers.oneReadInputStream(inputstr)
input:curr()
for i = 1, #inputstr do
assert.are.same(inputstr:sub(i, i), input:next())
end
for i = 1, TEST_REPEAT_COUNT do
assert.are.same(nil, input:next())
end
end)
it("can do files", function()
local fname = "./filetest.txt"
input = CoreHelpers.fileStream(fname)
local expected_output = io.open(fname, "r"):read("*all")
for i = 1, #expected_output do
assert.are.same(expected_output:sub(i,i), input:next())
end
for i = 1, TEST_REPEAT_COUNT do
assert.are.same(nil, input:next())
end
end)
end)
end)
describe("WordInfo tests", function()
local Environment = ds.Environment
local WordInfo = ds.WordInfo
it("creates and doesn't crash", function()
local env = Environment:new()
wi = WordInfo:new(function(env) end, false)
end)
end)
describe("Dictionary tests", function()
local Dictionary = ds.Dictionary
local WordInfo = ds.WordInfo
local Environment = ds.Environment
it("defines without breaking",function()
local env = Environment:new()
local dict = Dictionary:new()
local wi = WordInfo:new(function(env) end, false)
dict:define("TEST",wi)
end)
it("can get WordInfo back after it being defined", function()
local env = Environment:new()
local dict = Dictionary:new()
local wi = WordInfo:new(function(env) end, false)
dict:define("TEST",wi)
assert.are.same(dict:lookup("TEST"),wi)
end)
it("keeps track of the number of words in the dictionary", function()
local env = Environment:new()
local dict = Dictionary:new()
local wi = WordInfo:new(function(env) end, false)
dict:define("TEST", wi)
assert.are.equal(dict.wordCount, 1)
end)
end)
function buildEnvironment(testString)
local testEnv = Environment:new(CoreHelpers.oneReadInputStream(testString), {CoreWords})
return testEnv
end
local twoRandom = function() return math.random(-NUM_RANGE, NUM_RANGE), math.random(-NUM_RANGE, NUM_RANGE) end
local function randomDataStackEnv(lowerRange, upperRange)
local stackDepth = math.random(lowerRange, upperRange)
local stackTop = 0
local testEnv = Environment:new(nil, {CoreWords})
for j = 1, stackDepth do
stackTop = math.random(-NUM_RANGE,NUM_RANGE)
testEnv.activeDataStack:push(stackTop)
end
return testEnv, stackDepth
end
describe("Interpreter tests", function()
describe("Arithmetic tests", function()
it("can add two numbers, and leave the sum on the stack", function()
for i=1, TEST_REPEAT_COUNT do
local a, b = twoRandom()
local expected_sum = a + b
local testString = a.." "..b.." +"
local testEnv = buildEnvironment(testString)
interpreters.start(testEnv)
assert.are.same(testEnv.activeDataStack.contents[1], expected_sum)
end
end)
it("can subtract two numbers, and leave the difference on the stack", function()
for i=1, TEST_REPEAT_COUNT do
local a, b = twoRandom()
local expected_diff = a -b
local testString = a.." "..b.." -"
local testEnv = buildEnvironment(testString)
interpreters.start(testEnv)
assert.are.same(testEnv.activeDataStack.contents[1], expected_diff)
end
end)
it("can multiply two numbers, leaving product on the stack", function()
for i = 1, TEST_REPEAT_COUNT do
local a, b = twoRandom()
local expected_prod = a*b
local testString = a.." "..b.." *"
local testEnv = buildEnvironment(testString)
interpreters.start(testEnv)
assert.are.same(testEnv.activeDataStack.contents[1], expected_prod)
end
end)
it("can divide two numbers, leaving the quotient on the stack", function()
for i = 1, TEST_REPEAT_COUNT do
local a, b = twoRandom()
local expected_quo = a/b
local testString = a.." "..b.." /"
local testEnv = buildEnvironment(testString)
interpreters.start(testEnv)
assert.are.same(testEnv.activeDataStack.contents[1], expected_quo)
end
end)
it("can evaluate a arithmetic expression with multiple operations", function()
for i = 1, TEST_REPEAT_COUNT do
operands = {math.random(-NUM_RANGE,NUM_RANGE), math.random(-NUM_RANGE,NUM_RANGE), math.random(-NUM_RANGE,NUM_RANGE), math.random(-NUM_RANGE,NUM_RANGE)}
local operations = {math.random(4), math.random(4), math.random(4)}
local testString = ""..operands[1]
local expectedResult = operands[1]
for j, op in ipairs(operations) do
if op == 1 then
testString = testString.." "..operands[j + 1].." +"
expectedResult = expectedResult + operands[j+1]
elseif op == 2 then
testString = testString.." "..operands[j + 1].." -"
expectedResult = expectedResult - operands[j+1]
elseif op == 3 then
testString = testString.." "..operands[j + 1].." *"
expectedResult = expectedResult * operands[j+1]
else
testString = testString.." "..operands[j + 1].." /"
expectedResult = expectedResult / operands[j+1]
end
end
local testEnv = buildEnvironment(testString)
interpreters.start(testEnv)
assert.are.same(expectedResult,testEnv.activeDataStack.contents[1])
end
end)
it("does not push anything when a garbage input is given", function()
local testEnv = buildEnvironment("BOGUS")
interpreters.start(testEnv)
assert.are.same({}, testEnv.activeDataStack.contents)
end)
end)
describe("Core word tests", function()
describe("DUP tests", function()
it("DUPs the top item of a stack with one item on it.", function()
local testEnv = buildEnvironment("1 DUP")
interpreters.start(testEnv)
assert.are.same(1, testEnv.activeDataStack.contents[2])
end)
it("DUPs the top item of a stack with random quantities of random numbers on it.", function()
for i = 1, TEST_REPEAT_COUNT do
local stackDepth = math.random(STACK_RANGE)
local stackTop = 0
local testEnv = buildEnvironment("DUP")
for j = 1, stackDepth do
stackTop = math.random(-NUM_RANGE,NUM_RANGE)
testEnv.activeDataStack:push(stackTop)
end
interpreters.start(testEnv)
assert.are.same(stackTop, testEnv.activeDataStack.contents[stackDepth+1])
end
end)
end)
describe("SWAP tests", function()
it("SWAPS the top two items of a stack with two items on it.", function()
local testEnv = buildEnvironment("1 2 SWAP")
interpreters.start(testEnv)
assert.are.same(2, testEnv.activeDataStack.contents[1])
assert.are.same(1, testEnv.activeDataStack.contents[2])
end)
it("SWAPS the top two items of a stack with random quantities of random numbers on it.", function()
for i = 1, TEST_REPEAT_COUNT do
local testEnv, stackDepth = randomDataStackEnv(2, STACK_RANGE)
testEnv.activeInputStream = CoreHelpers.oneReadInputStream("SWAP")
local a = testEnv.activeDataStack.contents[stackDepth]
local b = testEnv.activeDataStack.contents[stackDepth-1]
interpreters.start(testEnv)
assert.are.equal(testEnv.activeDataStack.contents[stackDepth-1], a)
assert.are.equal(testEnv.activeDataStack.contents[stackDepth], b)
end
end)
end)
describe("ROT tests", function()
it("ROTs the top three items of a stack with three items on it.", function()
local testEnv = buildEnvironment("1 2 3 ROT")
interpreters.start(testEnv)
assert.are.same({2, 3, 1}, testEnv.activeDataStack.contents)
end)
end)
describe("DROP tests", function()
it("Drops the top item of the stack, when stack size is 1.", function()
local testEnv = buildEnvironment("1 DROP")
interpreters.start(testEnv)
assert.are.same(nil, testEnv.activeDataStack.contents[1])
end)
-- TODO: add better tests
end)
describe("OVER tests", function()
it("Places a copy of the item below the top of the stack onto the top, when stack size is 2.", function()
local testEnv = buildEnvironment("1 2 OVER")
interpreters.start(testEnv)
assert.are.same({1, 2, 1}, testEnv.activeDataStack.contents)
end)
end)
-- TODO: test output of DOT.
describe("DOT tests", function()
it("Drops the top item of the stack.", function()
local testEnv = buildEnvironment("1 .")
interpreters.start(testEnv)
assert.are.same({}, testEnv.activeDataStack.contents)
end)
end)
describe("2DUP tests", function()
it("Duplicates the top two items on the stack.", function()
local testEnv = buildEnvironment("1 2 2DUP")
interpreters.start(testEnv)
assert.are.same({1, 2, 1, 2}, testEnv.activeDataStack.contents)
end)
end)
describe("2SWAP tests", function()
it("Exchanges the top two cell pairs.", function()
local testEnv = buildEnvironment("1 2 3 4 2SWAP")
interpreters.start(testEnv)
assert.are.same({3, 4, 1, 2}, testEnv.activeDataStack.contents)
end)
end)
describe("2OVER tests", function()
it("copies the two items below the top two items onto the top of the stack.", function()
local testEnv = buildEnvironment("1 2 3 4 2OVER")
interpreters.start(testEnv)
assert.are.same({1, 2, 3, 4, 1, 2}, testEnv.activeDataStack.contents)
end)
end)
describe("NIP tests", function()
it("drops the first item below the top of stack, when stack size == 2", function()
local testEnv = buildEnvironment("1 2 NIP")
interpreters.start(testEnv)
assert.are.same({2}, testEnv.activeDataStack.contents)
end)
end)
describe("TUCK tests", function()
it("Copies the top below the item just below the top, in a stack of size 2.", function()
local testEnv = buildEnvironment("1 2 TUCK")
interpreters.start(testEnv)
assert.are.same({2, 1, 2}, testEnv.activeDataStack.contents)
end)
end)
describe("ROLL tests", function()
it("is NOOP when u = 0", function()
local testEnv = buildEnvironment("1 2 4 1 1 0 ROLL")
interpreters.start(testEnv)
assert.are.same({1, 2, 4, 1, 1}, testEnv.activeDataStack.contents)
end)
it("is SWAP when u = 1", function()
local testEnv = buildEnvironment("1 2 3 4 5 1 ROLL")
interpreters.start(testEnv)
assert.are.same({1, 2, 3, 5, 4}, testEnv.activeDataStack.contents)
end)
it("is ROT when u = 2", function()
local testEnv = buildEnvironment("1 2 3 4 5 2 ROLL")
interpreters.start(testEnv)
assert.are.same({1, 2, 4, 5, 3}, testEnv.activeDataStack.contents)
end)
it("works properly for u = [3, 100]", function()
local testStr = ""
local expected_output = {}
for i = 3, 100 do
for j = 1, i+1 do
testStr = testStr.." "..j
table.insert(expected_output, j)
end
testStr = testStr.." "..i.." ROLL"
local bottom = table.remove(expected_output, #expected_output - i)
table.insert(expected_output, bottom)
local testEnv = buildEnvironment(testStr)
interpreters.start(testEnv)
assert.are.same(expected_output, testEnv.activeDataStack.contents)
end
end)
end)
describe("' tests", function()
it("places an xt on top of the stack replacing the next word", function()
local cw = CoreWords
local testFunc = function(env) local a, b return a + b end
local customWord = CoreHelpers.defineWord(cw, "TEST", testFunc, false)
local testEnv = buildEnvironment("' TEST")
interpreters.start(testEnv)
assert.are.same(testFunc, testEnv.activeDataStack.contents[1])
end)
end)
describe("EXECUTE tests", function()
it("Given an `add` XT on top of the stack with two numbers beneath it, execute it.", function()
local testEnv = buildEnvironment("1 2 ' + EXECUTE")
interpreters.start(testEnv)
assert.are.equal(3, testEnv.activeDataStack.contents[1])
end)
end)
describe("[ tests", function()
it("Switches the environment's state to interpretation mode.", function()
local testEnv = buildEnvironment("[")
testEnv.compileState = true
interpreters.start(testEnv)
assert.are.equal(false, testEnv.compileState)
end)
end)
describe("] tests", function()
it("Switches the environment's state to compilation mode.", function()
local testEnv = buildEnvironment("]")
testEnv.compileState = false
interpreters.start(testEnv)
assert.are.equal(true, testEnv.compileState)
end)
end)
describe(": tests", function()
it("Places the name to be defined in currentDefinitionName",function()
local testEnv = buildEnvironment(": TESTWORD")
interpreters.start(testEnv)
assert.are.equal("TESTWORD", testEnv.currentDefinitionName)
end)
it("Gives us a XT when we compile a word.", function()
local cw = CoreWords
local testEnv = buildEnvironment(": TESTWORD +")
interpreters.start(testEnv)
assert.are.equal(type(function() end), type(testEnv.currentDefinition[1]))
end)
end)
describe("; tests", function()
it("Gives us a word we can then use in the future", function()
local testEnv = buildEnvironment(": SQR DUP * ; 4 SQR")
interpreters.start(testEnv)
assert.are.equal(16, testEnv.activeDataStack.contents[1])
end)
end)
describe(">C tests", function()
it("Consumes from datastack, produces onto compilerstack", function()
local testEnv = buildEnvironment("4 >C")
interpreters.start(testEnv)
assert.are.same({4}, testEnv.compilerStack.contents)
end)
end)
describe("C> tests", function()
it("Consumes from compilerstack, produces that value onto datastack", function()
local testEnv = buildEnvironment("C>")
testEnv.compilerStack:push(4)
interpreters.start(testEnv)
assert.are.same({4}, testEnv.activeDataStack.contents)
end)
end)
describe("DP tests", function()
it("Produces the current dictionary pointer", function()
local testEnv = buildEnvironment(": WORT 1 1 + DROP [ DP ]")
interpreters.start(testEnv)
assert.are.same({4}, testEnv.activeDataStack.contents)
end)
end)
describe("IF tests", function()
it("Pushes the DP to the compile stack.", function()
local testEnv = buildEnvironment(": TEST 1 . IF")
interpreters.start(testEnv)
assert.are.same({3}, testEnv.compilerStack.contents)
end)
it("Pushes any function to the currentDefinition.", function()
local testEnv = buildEnvironment(": TEST 1 . IF")
interpreters.start(testEnv)
assert.are.same(type(function() end), type(testEnv.currentDefinition[3]))
end)
end)
describe("THEN tests", function()
it("Removes one item from the compile stack.", function()
local testEnv = buildEnvironment(": WORT THEN")
testEnv.compilerStack:push(1)
interpreters.start(testEnv)
assert.are.same({}, testEnv.compilerStack.contents)
end)
end)
describe("IF... then tests", function()
it("does CLAMP properly", function()
local testEnv = buildEnvironment(": CLAMP 2DUP > IF ROT 2DUP < IF SWAP THEN THEN DROP NIP ; 1 3 2 CLAMP 1 3 4 CLAMP 1 3 0 CLAMP")
interpreters.start(testEnv)
assert.are.same({2, 3, 1}, testEnv.activeDataStack.contents)
end)
end)
describe("IF...ELSE...THEN tests", function()
it("Handles the eggsize properly", function()
local testEnv = buildEnvironment(": EGGSIZE DUP 18 < IF 1 ELSE DUP 21 < IF 2 ELSE DUP 24 < IF 3 ELSE DUP 27 < IF 4 ELSE DUP 30 < IF 5 ELSE -1 THEN THEN THEN THEN THEN NIP ; 23 EGGSIZE 29 EGGSIZE 40 EGGSIZE")
interpreters.start(testEnv)
assert.are.same({3,5,-1}, testEnv.activeDataStack.contents)
end)
end)
describe("< tests", function()
it("Pushes true for 1 < 2", function()
local testEnv = buildEnvironment("1 2 <")
interpreters.start(testEnv)
assert.are.same({true}, testEnv.activeDataStack.contents)
end)
it("Pushes false for 2 < 1", function()
local testEnv = buildEnvironment("2 1 <")
interpreters.start(testEnv)
assert.are.same({false}, testEnv.activeDataStack.contents)
end)
end)
describe("IMMEDIATE tests", function()
it("Marks a defined word as immediate", function()
local testEnv = buildEnvironment(": BOGUSNUTS 1 . ; IMMEDIATE")
interpreters.start(testEnv)
assert.are.same(true, CoreHelpers.searchDictionaries(testEnv, "BOGUSNUTS").immediate)
end)
end)
describe("BEGIN ... UNTIL tests", function()
it("GI4", function()
local testEnv = buildEnvironment([[: GI4 BEGIN DUP 1 + DUP 5 > UNTIL ;
3 GI4
5 GI4
6 GI4]])
interpreters.start(testEnv)
assert.are.same({3,4,5,6,5,6,6,7}, testEnv.activeDataStack.contents)
end)
end)
describe("BEGIN ... WHILE tests", function()
it("Puts a new orig under the existing dest.", function()
local testEnv = buildEnvironment([[: TEST BEGIN 1 2 3 WHILE]])
interpreters.start(testEnv)
assert.are.same({4, 0}, testEnv.compilerStack.contents)
end)
end)
describe("BEGIN ... WHILE ... REPEAT tests", function()
it("Does GI3. Single level loop", function()
local testEnv = buildEnvironment([[
: GI3 BEGIN DUP 5 < WHILE DUP 1 + REPEAT ;
0 GI3
4 GI3
5 GI3
6 GI3]])
interpreters.start(testEnv)
assert.are.same({0, 1, 2, 3, 4, 5, 4, 5, 5, 6}, testEnv.activeDataStack.contents)
end)
it("Does GI5. Two loops, with some weird logic using THEN and ELSE...?",function()
local testEnv = buildEnvironment([[
: GI5 BEGIN DUP 2 > WHILE
DUP 5 < WHILE DUP 1 + REPEAT
123 ELSE 345 THEN ;
1 GI5
2 GI5
3 GI5
4 GI5
5 GI5]])
interpreters.start(testEnv)
assert.are.same({1, 345, 2, 345, 3, 4, 5, 123, 4, 5, 123, 5, 123}, testEnv.activeDataStack.contents)
end)
end)
end)
end)