#!/usr/bin/env lua local pprint = require('pprint') local assemble = require('tyche-as') local VM = require('tyche-vm') ---------------------- -- -- -- SUPPORT -- -- -- ---------------------- function TEST(name) print("### " .. name) end function assert_eq(found, expected, key) assert(type(found) == type(expected), 'Types not matching , expected "' .. pprint.pformat(expected) .. '", found "' .. pprint.pformat(found) .. '".' .. ((key ~= nil) and ('(key: ' .. key .. ')') or '')) if type(found) == 'table' then assert(#found == #expected, "Tables are of different sizes " .. ((key ~= nil) and ('(key: ' .. key .. ')') or '')) for k,v in pairs(found) do assert_eq(v, expected[k], k) end for k,v in pairs(expected) do assert_eq(v, found[k], k) end else assert(found == expected, 'Assertion failed, expected "' .. pprint.pformat(expected) .. '", found "' .. pprint.pformat(found) .. '".') end end ---------------------- -- -- -- PARSER -- -- -- ---------------------- do TEST "Parser" local source = [[ .const 0: 3.14 1: "Hello world" .func 0 pushi 2 ; this is a comment pushi 3 sum ret .func 1 pushi 5000 ret ]] local expected = { constants = { [0] = 3.14, [1] = "Hello world" }, functions = { [0] = { { "pushi", 2 }, { "pushi", 3 }, { "sum" }, { "ret" }, }, [1] = { { "pushi", 5000 }, { "ret" }, } } } local found = assemble(source) -- pprint(expected) -- pprint(found) assert_eq(found, expected) end do TEST "Parser: labels" local source = [[ .func 0 jmp @my_label pushi 3 @my_label: ret ]] local expected = { constants = {}, functions = { [0] = { { "jmp", "@my_label" }, { "pushi", 3 }, { "ret", labels = { "@my_label" } }, } } } local found = assemble(source) assert_eq(found, expected) end ---------------------- -- -- -- STACK -- -- -- ---------------------- do TEST "Stack" local stack = VM.new().stack stack:push({ type='integer', value=10 }) stack:push({ type='integer', value=20 }) stack:push({ type='integer', value=30 }) assert_eq(#stack, 3) assert_eq(stack[0].value, 10) assert_eq(stack[1].value, 20) assert_eq(stack[-1].value, 30) assert_eq(stack[-2].value, 20) stack:pop() stack:pop() assert_eq(stack[-1].value, 10) stack:pop() assert_eq(#stack, 0) end do TEST "Stack with frame pointer" local stack = VM.new().stack stack:push({ type='integer', value=10 }) stack:push({ type='integer', value=20 }) stack:push_fp() stack:push({ type='integer', value=30 }) stack:push({ type='integer', value=40 }) stack:push({ type='integer', value=50 }) assert_eq(#stack, 3) assert_eq(stack[0].value, 30) assert_eq(stack[1].value, 40) assert_eq(stack[-1].value, 50) assert_eq(stack[-2].value, 40) stack:pop_fp() assert_eq(#stack, 2) assert_eq(stack[0].value, 10) assert_eq(stack[1].value, 20) assert_eq(stack[-1].value, 20) assert_eq(stack[-2].value, 10) end ---------------------- -- -- -- VM -- -- -- ---------------------- local function arith(a, b, op) return VM.new():load(assemble(string.format([[ .func 0 pushi %d pushi %d %s ret ]], a, b, op))):call(0) end do TEST "VM: basic" local vm = VM.new() -- vm.debug = true local bytecode = assemble [[ .func 0 pushi 2 pushi 3 sum ret ]] vm:load(bytecode) assert_eq(vm:stack_sz(), 1) assert_eq(vm:is(-1, 'function'), true) vm:call(0) assert_eq(vm:stack_sz(), 1) assert_eq(vm:is(-1, 'integer'), true) assert_eq(vm:to_integer(-1), 5) end do TEST "VM: logic/arithmetic" assert_eq(arith(2, 5, 'sum'):to_integer(-1), 7) assert_eq(arith(2, 5, 'sub'):to_integer(-1), -3) assert_eq(arith(2, 5, 'mul'):to_integer(-1), 10) assert_eq(arith(20, 3, 'idiv'):to_integer(-1), 6) assert_eq(arith(5, 5, 'eq'):to_integer(-1), 1) assert_eq(arith(5, 5, 'neq'):to_integer(-1), 0) assert_eq(arith(4, 5, 'lt'):to_integer(-1), 1) assert_eq(arith(5, 5, 'lt'):to_integer(-1), 0) assert_eq(arith(4, 5, 'lte'):to_integer(-1), 1) assert_eq(arith(5, 5, 'lte'):to_integer(-1), 1) assert_eq(arith(5, 5, 'gt'):to_integer(-1), 0) assert_eq(arith(5, 5, 'gte'):to_integer(-1), 1) assert_eq(arith(20, 5, 'and'):to_integer(-1), 4) assert_eq(arith(20, 5, 'or'):to_integer(-1), 21) assert_eq(arith(20, 5, 'xor'):to_integer(-1), 17) assert_eq(arith(2, 5, 'pow'):to_integer(-1), 32) assert_eq(arith(2, 5, 'shl'):to_integer(-1), 64) assert_eq(arith(20, 3, 'shr'):to_integer(-1), 2) assert_eq(arith(20, 3, 'mod'):to_integer(-1), 2) end do TEST "VM: local variables" local vm = VM.new():load(assemble([[ .func 0 pushv 2 ; local a, b pushi 3 ; a = 3 set 0 pushi 4 ; b = 4 set 1 dupv 0 ; return a ret ]])):call(0) assert_eq(vm:stack_sz(), 1) assert_eq(vm:to_integer(-1), 3) end do TEST "VM: functions" local vm = VM.new():load(assemble([[ .func 0 pushf 1 pushi 2 pushi 3 call 2 ret .func 1 dupv 0 dupv 1 sub ret ]])):call(0) assert_eq(vm:stack_sz(), 1) assert_eq(vm:to_integer(-1), -1) end do TEST "VM: jumps (jmp + bnz)" local vm = VM.new():load(assemble [[ .func 0 jmp @x1 pushi 5 @x1: pushi 1 bnz @x2 pushi 1 bz @x3 @x2: pushi 6 ret @x3: pushi 7 ret ]]):call(0) assert_eq(vm:to_integer(-1), 6) end do TEST "VM: jumps (bz)" local vm = VM.new():load(assemble [[ .func 0 jmp @x1 pushi 5 @x1: pushi 0 bnz @x2 pushi 0 bz @x3 @x2: pushi 6 ret @x3: pushi 7 ret ]]):call(0) assert_eq(vm:to_integer(-1), 7) end do TEST "VM: string from const" local vm = VM.new():load(assemble [[ .const 0: "Hello" .func 0 pushc 0 ret ]]):call(0) assert_eq(vm:to_string(-1), "Hello") end do TEST "VM: managed strings" local vm = VM.new():push_string("Hello") -- print(vm:debug_heap()) assert_eq(vm:to_string(-1), "Hello") end do TEST "VM: concatenate strings (GC won't delete)" local vm = VM.new():load(assemble [[ .const 0: "Hello " 1: "world" .func 0 pushc 0 pushc 1 sum gc ret ]]):call(0) -- print(vm:debug_heap()) assert_eq(vm:to_string(-1), "Hello world") assert_eq(vm.heap:size(), 1) end do TEST "VM: GC strings" local vm = VM.new():load(assemble [[ .const 0: "Hello " 1: "world" .func 0 pushn pushc 0 pushc 1 sum pop gc ret ]]):call(0) -- print(vm:debug_heap()) assert_eq(vm:is(-1, 'nil'), true) assert_eq(vm.heap:size(), 0) end do TEST "VM: arrays" local vm = VM.new():set_debug(true):load(assemble [ .func 0 newa pushi 10 seta 0 pushi 20 seta 1 pushi 30 seta 2 geta 1 ]):call(0) print(vm:debug_heap()) assert_eq(vm:to_integer(-1), 20) assert_eq(vm:is(-2, 'array'), true) assert_eq(vm.heap:size(), 1) end print('End.')