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)