n4th tests work again
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-undefined",
|
||||
"-C", "link-arg=dynamic_lookup",
|
||||
]
|
||||
Generated
+76
-68
@@ -190,11 +190,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -243,11 +244,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -452,7 +456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libloading 0.7.3",
|
||||
"libloading 0.8.9",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -478,7 +482,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading 0.7.3",
|
||||
"libloading 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -493,6 +497,12 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -509,6 +519,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@@ -714,7 +730,7 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"com",
|
||||
"libc",
|
||||
"libloading 0.7.3",
|
||||
"libloading 0.8.9",
|
||||
"thiserror",
|
||||
"widestring",
|
||||
"winapi",
|
||||
@@ -767,15 +783,6 @@ dependencies = [
|
||||
"hashbrown 0.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.5"
|
||||
@@ -898,11 +905,10 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
@@ -915,24 +921,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lua-src"
|
||||
version = "544.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "luajit-src"
|
||||
version = "210.3.4+resty073ac54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "640b09e99575a442b4da0ef406a78188a1a4313bb9ead7b5b20ec12cc480130f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@@ -980,30 +968,41 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "mlua"
|
||||
version = "0.8.0-beta.4"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2403730bdaac13f5e84034a324b61723d5d540b85aebc0f237e0028eb58053"
|
||||
checksum = "d96e5d00f19d8c46c71ceaced99593b90c31c57aa1fe2cb3e93a8b1698eedba9"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"cc",
|
||||
"lua-src",
|
||||
"luajit-src",
|
||||
"either",
|
||||
"libc",
|
||||
"mlua-sys",
|
||||
"mlua_derive",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rustc-hash 2.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua-sys"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b806d7ade031f5d6607eae3e283fb034cb795a76247dd0d1ba753c8c42debccf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua_derive"
|
||||
version = "0.8.0-beta.1"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd3dfb62b95873e3d7aa9a691905287992628e3c10cadce5ed88f15134e3b0ce"
|
||||
checksum = "87de1e4bdfaa4bb99d8c1449d8a34b0f19e649ef68eb5d4e8dbf1e9ec6f4a084"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.92",
|
||||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1019,7 +1018,7 @@ dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"log",
|
||||
"num-traits",
|
||||
"rustc-hash",
|
||||
"rustc-hash 1.1.0",
|
||||
"spirv",
|
||||
"termcolor",
|
||||
"thiserror",
|
||||
@@ -1368,27 +1367,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.5"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.13",
|
||||
"redox_syscall 0.5.18",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1640,15 +1637,6 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
@@ -1696,6 +1684,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
@@ -1830,6 +1824,12 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
@@ -1994,6 +1994,12 @@ dependencies = [
|
||||
"strict-num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyjson"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ab95735ea2c8fd51154d01e39cf13912a78071c2d89abc49a7ef102a7dd725a"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
@@ -2127,6 +2133,8 @@ name = "vlua"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"mlua",
|
||||
"novaforth",
|
||||
"tinyjson",
|
||||
"vcore",
|
||||
]
|
||||
|
||||
@@ -2409,7 +2417,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"rustc-hash",
|
||||
"rustc-hash 1.1.0",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"web-sys",
|
||||
@@ -2441,7 +2449,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"khronos-egl",
|
||||
"libc",
|
||||
"libloading 0.7.3",
|
||||
"libloading 0.8.9",
|
||||
"log",
|
||||
"metal",
|
||||
"naga",
|
||||
@@ -2453,7 +2461,7 @@ dependencies = [
|
||||
"range-alloc",
|
||||
"raw-window-handle",
|
||||
"renderdoc-sys",
|
||||
"rustc-hash",
|
||||
"rustc-hash 1.1.0",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
|
||||
+3
-1
@@ -10,4 +10,6 @@ crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
vcore = { path = "../vcore" }
|
||||
mlua = { version = "0.8.0-beta.4", features = ["lua54", "vendored", "module"] }
|
||||
novaforth = { path = "../novaforth" }
|
||||
tinyjson = "2.5.1"
|
||||
mlua = { version = "0.12.1", features = ["lua55", "module"] }
|
||||
|
||||
+39
-3
@@ -1,21 +1,48 @@
|
||||
use std::collections::HashMap;
|
||||
use mlua::prelude::*;
|
||||
use vcore::{ CPU, Word };
|
||||
use mlua::{UserData, UserDataMethods};
|
||||
use mlua::{Error, UserData, UserDataMethods};
|
||||
use vcore::memory::{PeekPoke, PeekPokeExt};
|
||||
use std::iter::FromIterator;
|
||||
use tinyjson::JsonValue;
|
||||
use novaforth::{ROM, PRELUDE, SYMBOLS};
|
||||
use vcore::opcodes::Opcode;
|
||||
|
||||
#[mlua::lua_module]
|
||||
fn libvlua(lua: &Lua) -> LuaResult<LuaTable<'_>> {
|
||||
fn libvlua(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let exports = lua.create_table()?;
|
||||
|
||||
let cpu_constructor = lua.create_function(|_, _: ()| Ok(LuaCPU(CPU::new_random())))?;
|
||||
exports.set("new", cpu_constructor)?;
|
||||
|
||||
let symbols: HashMap<_, _> = {
|
||||
let symbols: JsonValue = SYMBOLS.parse().unwrap();
|
||||
symbols.try_into().unwrap()
|
||||
};
|
||||
|
||||
let symbol_addr = lua.create_function(move |_, sym: String| -> Result<i32, Error> {
|
||||
if let Some(JsonValue::Number(num)) = symbols.get(&sym) {
|
||||
return Ok(*num as i32);
|
||||
}
|
||||
Err(Error::runtime("Symbol not found"))
|
||||
})?;
|
||||
exports.set("symbol", symbol_addr)?;
|
||||
|
||||
let opcode_for = lua.create_function(|_, mnemonic: String| -> Result<u8, _> {
|
||||
if let Ok(op) = Opcode::try_from(mnemonic.as_str()) {
|
||||
return Ok(op.into())
|
||||
}
|
||||
Err(Error::runtime("Invalid opcode"))
|
||||
})?;
|
||||
exports.set("opcode_for", opcode_for)?;
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
struct LuaCPU(CPU);
|
||||
|
||||
impl UserData for LuaCPU {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
fn add_methods<'lua, M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method_mut("reset", |_, lcpu, ()| {
|
||||
lcpu.0.reset();
|
||||
Ok(())
|
||||
@@ -129,5 +156,14 @@ impl UserData for LuaCPU {
|
||||
lcpu.0.interrupt(irq as usize, arg.map(|n| Word::from(n)));
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_method_mut("load_rom", |_, lcpu, ()| {
|
||||
for (i, b) in ROM.iter().enumerate() {
|
||||
lcpu.0.poke(Word::from(0x400 + i), *b)
|
||||
}
|
||||
|
||||
lcpu.0.set_pc(0x400.into());
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,904 @@
|
||||
package.cpath = package.cpath .. ';./target/release/?.dylib'
|
||||
Vlua = require('libvlua')
|
||||
|
||||
TIB = 80000 -- Just a convenient place to stick a terminal input buffer for tests. Could be any number.
|
||||
|
||||
function init_cpu()
|
||||
local cpu = Vlua.new()
|
||||
cpu:load_rom()
|
||||
--cpu:init_serial(2)
|
||||
--cpu:poke24(Vlua.symbol('emit_hook'), Vlua.symbol('test_emit'))
|
||||
|
||||
return cpu
|
||||
end
|
||||
|
||||
function call(cpu, symbol)
|
||||
cpu:push_call(Vlua.symbol('stop'))
|
||||
cpu:set_pc(Vlua.symbol(symbol))
|
||||
cpu:run()
|
||||
end
|
||||
|
||||
function test_fn(name, setup, check)
|
||||
local cpu = init_cpu()
|
||||
setup(cpu)
|
||||
call(cpu, name)
|
||||
local st = cpu:stack()
|
||||
local rst = cpu:r_stack()
|
||||
check(st, get_output(cpu), cpu, rst)
|
||||
end
|
||||
|
||||
function get_output(cpu)
|
||||
local start = 0x10000
|
||||
local len = cpu:peek24(Vlua.symbol('emit_cursor'))
|
||||
local str = ''
|
||||
for a = start, len+start-1 do
|
||||
str = str .. string.char(cpu:peek(a))
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function array_eq(a1, a2)
|
||||
local eq = true
|
||||
for i, n in ipairs(a1) do
|
||||
if n ~= a2[i] then eq = false end
|
||||
end
|
||||
if #a1 ~= #a2 then eq = false end
|
||||
|
||||
if eq then return true end
|
||||
|
||||
local lt = ''
|
||||
for i, n in ipairs(a1) do
|
||||
lt = lt .. string.format('0x%x ', n)
|
||||
end
|
||||
|
||||
local rt = ''
|
||||
for i, n in ipairs(a2) do
|
||||
rt = rt .. string.format('0x%x ', n)
|
||||
end
|
||||
|
||||
print(string.format('Arrays not equal!\nlt: { %s }\nrt: { %s }', lt, rt))
|
||||
return false
|
||||
end
|
||||
|
||||
function given_stack(contents)
|
||||
return function(cpu) for _, n in ipairs(contents) do cpu:push_data(n) end end
|
||||
end
|
||||
|
||||
function given_memory(at, contents)
|
||||
if type(contents) == 'string' then
|
||||
contents = { contents:byte(1, #contents) }
|
||||
table.insert(contents, 0)
|
||||
elseif type(contents) == 'number' then
|
||||
contents = { contents }
|
||||
end
|
||||
return function(cpu)
|
||||
for i, b in ipairs(contents) do cpu:poke(at + i - 1, b) end
|
||||
end
|
||||
end
|
||||
|
||||
function given_word(at, word)
|
||||
return function(cpu) cpu:poke24(at, word) end
|
||||
end
|
||||
|
||||
function expect_stack(expected)
|
||||
return function(actual) assert(array_eq(expected, actual)) end
|
||||
end
|
||||
|
||||
function expect_r_stack(expected)
|
||||
return function(_st, _out, _cpu, actual) assert(array_eq(expected, actual)) end
|
||||
end
|
||||
|
||||
function expect_output(expected)
|
||||
return function(_s, actual) assert(expected == actual, string.format('exp %q, act %q', expected, actual)) end
|
||||
end
|
||||
|
||||
function expect_memory(start, ...)
|
||||
local mem = { ... }
|
||||
local expanded_mem = {}
|
||||
for _, el in ipairs(mem) do
|
||||
if type(el) == 'string' then table.insert(expanded_mem, el:byte())
|
||||
elseif type(el) == 'table' then
|
||||
for _, b in ipairs(el) do table.insert(expanded_mem, b) end
|
||||
else
|
||||
table.insert(expanded_mem, el)
|
||||
end
|
||||
end
|
||||
|
||||
return function(_s, _o, cpu)
|
||||
for i, b in ipairs(expanded_mem) do
|
||||
local actual = cpu:peek(start + i - 1)
|
||||
assert(actual == b, string.format('0x%x: exp %d, act %d', start + i - 1, b, actual))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function word(val)
|
||||
val = val & 0xffffff
|
||||
return { val & 0xff, (val & 0xff00) / 256, (val & 0xff0000) / 65536 }
|
||||
end
|
||||
|
||||
function op(mnemonic, args)
|
||||
if not args then args = 0 end
|
||||
return Vlua.opcode_for(mnemonic) * 4 + args
|
||||
end
|
||||
|
||||
function inst(mnemonic, arg)
|
||||
local o = op(mnemonic, 3)
|
||||
local w = word(arg)
|
||||
return { o, w[1], w[2], w[3] }
|
||||
end
|
||||
|
||||
function call_inst(symbol)
|
||||
return inst('call', Symbols[symbol])
|
||||
end
|
||||
|
||||
function expect_string(start, str)
|
||||
return function(_s, _o, cpu)
|
||||
for i = 1, #str do
|
||||
local actual = cpu:peek(start + i - 1)
|
||||
assert(actual == str:byte(i), string.format('%x: exp %q, act %q (%d)', start + i - 1, str:sub(i,i), string.char(actual), actual))
|
||||
end
|
||||
assert(cpu:peek(start + #str - 1), string.format('%x exp 0, act %d', start + #str - 1, cpu:peek(start + #str - 1)))
|
||||
end
|
||||
end
|
||||
|
||||
function expect_word(addr, val)
|
||||
return function(_s, _o, cpu)
|
||||
local actual = cpu:peek24(addr)
|
||||
assert(actual == val, string.format('exp %xh, act %xh', val, actual))
|
||||
end
|
||||
end
|
||||
|
||||
function expect_heap_advance(n)
|
||||
return function(_s, _o, cpu)
|
||||
local expected = heap(0) + n
|
||||
local actual = cpu:peek24(Vlua.symbol('heap'))
|
||||
assert(actual == expected, string.format('heap should advance %d, actual %d', n, actual - heap(0)))
|
||||
end
|
||||
end
|
||||
|
||||
function expect_cursor(n)
|
||||
return function(_s, _o, cpu)
|
||||
local expected = TIB + n
|
||||
local actual = cpu:peek24(Vlua.symbol('cursor'))
|
||||
assert(actual == expected, string.format('cursor should advance %d, actual %d', n, actual - TIB))
|
||||
end
|
||||
end
|
||||
|
||||
function expect_4th_rstack(stack)
|
||||
local words = {}
|
||||
for _, v in ipairs(stack) do table.insert(words, word(v)) end
|
||||
return all(
|
||||
expect_memory(Vlua.symbol('r_stack'), table.unpack(words)),
|
||||
expect_word(Vlua.symbol('r_stack_ptr'), Vlua.symbol('r_stack') + #stack * 3))
|
||||
end
|
||||
|
||||
function dump_memory(addr, len)
|
||||
return function(_s, _o, cpu)
|
||||
for a = addr, addr + len do
|
||||
local b = cpu:peek(a)
|
||||
local c = string.char(b)
|
||||
local op = Opcodes.mnemonic_for(math.floor(b / 4))
|
||||
local args = b & 3
|
||||
if b < 32 then c = '' end
|
||||
print(string.format('[%d]\t%xh:\t%xh\t(%d)\t%q\t%q/%d', a - addr, a, b, b, c, op, args))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function all(...)
|
||||
local fns = { ... }
|
||||
return function(...)
|
||||
for i, f in ipairs(fns) do f(...) end
|
||||
end
|
||||
end
|
||||
|
||||
function test_line(line, ...)
|
||||
test_fn('eval', all(given_stack{TIB}, given_memory(TIB, line)), all(...))
|
||||
end
|
||||
|
||||
function test_lines(lines, ...)
|
||||
local cpu = init_cpu()
|
||||
|
||||
for _, line in ipairs(lines) do
|
||||
cpu:push_data(TIB)
|
||||
local contents = { line:byte(1, #line) }
|
||||
table.insert(contents, 0)
|
||||
for i, b in ipairs(contents) do cpu:poke(TIB + i - 1, b) end
|
||||
call(cpu, 'eval')
|
||||
end
|
||||
|
||||
local st = cpu:stack()
|
||||
local rst = cpu:r_stack()
|
||||
local check = all(...)
|
||||
check(st, get_output(cpu), cpu, rst)
|
||||
end
|
||||
|
||||
PRELUDE = 34 -- How many bytes the prelude adds to the heap
|
||||
function test_prelude_line(line, ...)
|
||||
-- local prelude1 = ": cont ' $ jmp #asm ; immediate"
|
||||
local prelude1 = 'create :: ] create continue ] ['
|
||||
local prelude2 = ':: ;; postpone exit continue [ [ immediate'
|
||||
test_lines({ prelude1, prelude2, line }, ...)
|
||||
end
|
||||
|
||||
function heap(offset)
|
||||
return Vlua.symbol('heap_start') + offset
|
||||
end
|
||||
|
||||
function dump_symbols()
|
||||
reverse = {}
|
||||
addrs = {}
|
||||
for sym, addr in pairs(Symbols) do reverse[addr] = sym; table.insert(addrs, addr) end
|
||||
table.sort(addrs)
|
||||
for _, addr in ipairs(addrs) do
|
||||
print(string.format('0x%x\t%s', addr, reverse[addr]))
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
test_fn('dupnz',
|
||||
given_stack{ 3 },
|
||||
expect_stack{ 3, 3 })
|
||||
|
||||
test_fn('dupnz',
|
||||
given_stack{ 0 },
|
||||
expect_stack{ 0 })
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
test_line('10 ?dup', expect_stack{10, 10})
|
||||
test_line('0 ?dup', expect_stack{0})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
test_line('10', expect_stack{10})
|
||||
test_line('10 20 30',
|
||||
expect_stack{10, 20, 30},
|
||||
expect_r_stack{})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Evaluating gibberish
|
||||
test_line('notaword', expect_output('Not a word: notaword\n'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
test_line('create blah',
|
||||
expect_word(Vlua.symbol('heap'), heap(11)), -- Heap ptr is advanced by the entry length
|
||||
expect_memory(heap(0), 'blah\0'), -- New dict entry has the name
|
||||
expect_word(heap(5), heap(11)), -- Followed by the new heap ptr
|
||||
expect_word(heap(8), Vlua.symbol('dict_start')), -- Next ptr is the old dict head
|
||||
expect_word(Vlua.symbol('dictionary'), heap(0))) -- Dict has had the new entry consed on to it
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Exiting and entering immediate mode
|
||||
test_line(']', expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('compile_handleword')))
|
||||
test_line('] [', all(
|
||||
expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('immediate_handleword')),
|
||||
expect_r_stack{}))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Compiling a number
|
||||
test_line('] 122773',
|
||||
expect_word(Vlua.symbol('heap'), heap(4)), -- Advance heap by the length of an instruction
|
||||
expect_memory(heap(0), { 3, 149, 223, 1})) -- A push instruction for 122773
|
||||
|
||||
-- Compiling a call to a word
|
||||
test_line('] create',
|
||||
expect_word(Vlua.symbol('heap'), heap(4)), -- Advance heap by the length of an instruction
|
||||
expect_memory(heap(0), { Vlua.opcode_for('call') * 4 + 3 }), -- A call instruction
|
||||
expect_word(heap(1), Vlua.symbol('nova_create'))) -- ...to nova_create
|
||||
|
||||
-- Compiling gibberish
|
||||
test_line('] stillnotaword', expect_output('Not a word: stillnotaword\n'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- -- Continue word (compiles a jmp)
|
||||
test_line('] continue ]',
|
||||
expect_word(Vlua.symbol('heap'), heap(4)), -- Advance heap by the length of an instruction
|
||||
expect_memory(heap(0), { Vlua.opcode_for('jmp') * 4 + 3 }), -- A call instruction
|
||||
expect_word(heap(1), Vlua.symbol('nova_close_bracket'))) -- ...to nova_close_bracket
|
||||
|
||||
-- Continue compile word
|
||||
test_line('] continue [',
|
||||
expect_word(Vlua.symbol('heap'), heap(4)), -- Advance heap by the length of an instruction
|
||||
expect_memory(heap(0), { Vlua.opcode_for('jmp') * 4 + 3 }), -- A call instruction
|
||||
expect_word(heap(1), Vlua.symbol('nova_open_bracket'))) -- ...to nova_close_bracket
|
||||
|
||||
-- Continue gibberish
|
||||
test_line('] continue supernotword', expect_output('Not a word: supernotword\n'))
|
||||
|
||||
-- Implement continue with #asm!
|
||||
test_lines({ ": cont ' $ jmp #asm ; immediate",
|
||||
'] cont ]' },
|
||||
expect_memory(heap(11), -- Just skip cont's header
|
||||
inst('call', Vlua.symbol('nova_tick')), -- Call tick to see what we're continuing to
|
||||
inst('push', Vlua.opcode_for('jmp')), -- Push a jmp
|
||||
inst('call', Vlua.symbol('compile_instruction_arg')), -- Compile a jmp to that word
|
||||
op('ret'), -- Return from cont
|
||||
inst('jmp', Vlua.symbol('nova_close_bracket')))) -- Cont gives us a jmp to `]`
|
||||
|
||||
-- Prelude continue with a runtime word
|
||||
test_lines({ ": cont ' $ jmp #asm ; immediate",
|
||||
'] cont print' },
|
||||
expect_memory(heap(11 + 13), -- Just skip cont's header and impl
|
||||
inst('jmp', Vlua.symbol('print')))) -- Cont gives us a jmp to `print`
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Prelude colon definition
|
||||
test_lines({ ": cont ' $ jmp #asm ; immediate",
|
||||
'create :: ] create cont ] [' },
|
||||
expect_memory(heap(24), '::\0'), -- New dict entry has the name (24 bytes for cont)
|
||||
expect_word(heap(24 + 3), heap(24 + 9)), -- Followed by the ptr to the fn
|
||||
expect_memory(heap(24 + 9), inst('call', Vlua.symbol('nova_create'))), -- Which is a call to create...
|
||||
expect_memory(heap(24 + 13), inst('jmp', Vlua.symbol('nova_close_bracket'))), -- Followed by jmping to close_bracket
|
||||
expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('immediate_handleword')), -- And now we're back in immediate mode
|
||||
expect_r_stack{}) -- And haven't leaked a stack frame
|
||||
|
||||
-- Using prelude colon
|
||||
test_lines({ "create cont ] ' $ jmp #asm ; immediate",
|
||||
'create :: ] create cont ] [ :: foo 35' },
|
||||
expect_memory(heap(24 + 17), 'foo\0'), -- A new entry for foo
|
||||
expect_word(heap(24 + 21), heap(24 + 27)), -- Defn ptr is the new heap
|
||||
expect_memory(heap(24 + 27), inst('push', 35)), -- fn begins with pushing a 35
|
||||
expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('compile_handleword')), -- We're still in compile mode
|
||||
expect_r_stack{}) -- And haven't leaked a stack frame
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Postponing normal words
|
||||
test_line('] postpone create',
|
||||
expect_memory(heap(0), inst('push', Vlua.symbol('nova_create'))),
|
||||
expect_memory(heap(4), inst('push', Vlua.opcode_for('call'))),
|
||||
expect_memory(heap(8), inst('call', Vlua.symbol('compile_instruction_arg'))))
|
||||
|
||||
-- Postponing compile words
|
||||
test_line('] postpone [', expect_memory(heap(0), inst('call', Vlua.symbol('nova_open_bracket'))))
|
||||
|
||||
-- Postponing gibberish
|
||||
test_line('] postpone reallynotaword', expect_output('Not a word: reallynotaword\n'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Compile a ret
|
||||
test_line('] exit', expect_memory(heap(0), { op('ret') }))
|
||||
|
||||
--------------------------------------------------
|
||||
--- Prelude stuff: -------------------------------
|
||||
--------------------------------------------------
|
||||
|
||||
-- This was a fun intellectual exercise and makes a nice torture test for NovaForth, but it violates the
|
||||
-- "optimize for understandability" principle and so colon and semicolon are now both written in asm. The
|
||||
-- tests remain here because they're good, very exhaustive, tests.
|
||||
|
||||
-- Prelude semicolon definition
|
||||
test_line('create :: ] create continue ] [ :: ;; postpone exit continue [ [ immediate',
|
||||
expect_memory(heap(17), ';', ';', 0), -- A new entry for semicolon
|
||||
expect_memory(heap(26),
|
||||
inst('call', Vlua.symbol('nova_exit')), -- Which compiles a ret
|
||||
inst('jmp', Vlua.symbol('nova_open_bracket'))), -- And then returns to immediate mode
|
||||
expect_word(Vlua.symbol('compile_dictionary'), heap(17)), -- Semicolon is in the compile dict
|
||||
expect_word(heap(23), Vlua.symbol('compile_dict_start')), -- Semicolon points at old compile_dict head
|
||||
expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('immediate_handleword'))) -- In immediate mode again
|
||||
|
||||
-- Using prelude semicolon
|
||||
test_prelude_line('] ;;',
|
||||
expect_memory(heap(PRELUDE), op('ret')), -- Compiled our ret
|
||||
expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('immediate_handleword')), -- In immediate mode again
|
||||
expect_r_stack{}) -- And haven't leaked a stack frame
|
||||
|
||||
-- Defining a word and calling it, with the prelude
|
||||
test_prelude_line(':: fives 5 5 5 ;; fives',
|
||||
expect_stack{ 5, 5, 5 },
|
||||
expect_r_stack{})
|
||||
|
||||
-- Testing create / does> without compile-time behavior, with the prelude
|
||||
test_prelude_line(':: blah create does> 2 3 ;; blah fnord fnord',
|
||||
-- We're creating a new word fnord and then running it, the new word gets passed the address
|
||||
-- of its heap stuff and then pushes a couple numbers. Its heap area is the heap ptr when we
|
||||
-- called does>, so, PRELUDE + 11 (blah's entry) + 21 (blah's body, part of which is fnord's) + 12 (fnord's entry)
|
||||
expect_stack{ heap(PRELUDE + 11 + 22 + 12), 2, 3 }, --
|
||||
-- Body of blah:
|
||||
expect_memory(heap(PRELUDE + 11),
|
||||
inst('call', Vlua.symbol('nova_create')), -- After blah's header, we have a call to create
|
||||
inst('push', heap(PRELUDE + 11 + 13)), -- push the address of after the does>
|
||||
inst('jmp', Vlua.symbol('does_at_runtime')), -- And a call to does@runtime, to start compiling it
|
||||
op('ret'), -- blah's return
|
||||
inst('push', 2), -- The runtime behavior of fnord (the "mold"):
|
||||
inst('push', 3),
|
||||
op('ret')), -- fnord's runtime return
|
||||
-- Header of fnord:
|
||||
expect_memory(heap(PRELUDE + 11 + 22),
|
||||
'f', 'n', 'o', 'r', 'd', 0, -- the new word's header
|
||||
word(heap(PRELUDE + 11 + 22 + 12)), -- pointer to the trampoline
|
||||
-- and pointer to the next dictionary entry. By this point the front of the
|
||||
-- dictionary is blah, which has its entry at heap(PRELUDE), right after the prelude:
|
||||
word(heap(PRELUDE))),
|
||||
-- Body (trampoline) of fnord:
|
||||
expect_memory(heap(PRELUDE + 11 + 22 + 12),
|
||||
inst('push', heap(PRELUDE + 11 + 22 + 12)), -- Push the old value, which was right
|
||||
-- after the header (because of the null compile-time behavior)
|
||||
inst('jmp', heap(PRELUDE + 11 + 13)))) -- jmp to the runtime behavior, after the does> call
|
||||
|
||||
-- Testing create / does> when there's compile-time behavior, with the prelude
|
||||
test_prelude_line(':: blah create 15 , does> 3 ;; blah fnord fnord',
|
||||
-- We're creating a new word fnord and then running it, the new word gets passed the address
|
||||
-- of its heap stuff and then pushes a three. Its heap area is the heap ptr when we
|
||||
-- called does>, so, PRELUDE + 11 (blah's entry) + 26 (blah's body, part of which is fnord's) + 12 (fnord's entry)
|
||||
expect_stack{ heap(PRELUDE + 11 + 26 + 12), 3 },
|
||||
-- Body of blah:
|
||||
expect_memory(heap(PRELUDE + 11),
|
||||
inst('call', Vlua.symbol('nova_create')), -- After blah's header, we have a call to create
|
||||
inst('push', 15),
|
||||
inst('call', Vlua.symbol('nova_comma')),
|
||||
inst('push', heap(PRELUDE + 11 + 21)), -- push the address of after the does>
|
||||
inst('jmp', Vlua.symbol('does_at_runtime')), -- And a call to does@runtime, to start compiling it
|
||||
op('ret'), -- blah's return
|
||||
inst('push', 3), -- After the does>; the runtime behavior of fnord (the "mold"):
|
||||
op('ret')), -- fnord's runtime return
|
||||
-- Header of fnord:
|
||||
expect_memory(heap(PRELUDE + 11 + 26),
|
||||
'f', 'n', 'o', 'r', 'd', 0, -- the new word's header
|
||||
word(heap(PRELUDE + 11 + 26 + 15)), -- pointer to the trampoline
|
||||
-- and pointer to the next dictionary entry. By this point the front of the
|
||||
-- dictionary is blah, which has its entry right after the prelude at heap(PRELUDE):
|
||||
word(heap(PRELUDE))),
|
||||
expect_memory(heap(PRELUDE + 11 + 26 + 12),
|
||||
word(15)), -- The compile time behavior compiled this 15
|
||||
-- Body (trampoline) of fnord:
|
||||
expect_memory(heap(PRELUDE + 11 + 26 + 15),
|
||||
inst('push', heap(PRELUDE + 11 + 26 + 12)), -- Push the old value, which was right
|
||||
-- after the header, the 15 we compiled
|
||||
inst('jmp', heap(PRELUDE + 11 + 21)))) -- jmp to the runtime behavior, after the does> call
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Defining a word and calling it, with the normal colon / semicolon words
|
||||
test_line(': fives 5 5 5 ; fives',
|
||||
expect_stack{ 5, 5, 5 },
|
||||
expect_r_stack{})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Basic use of asm
|
||||
test_line('create execute $ jmp asm',
|
||||
expect_heap_advance(15),
|
||||
expect_word(heap(8), heap(14)),
|
||||
expect_memory(heap(14), op('jmp')))
|
||||
|
||||
-- Asm with args
|
||||
test_line('45 $ push #asm',
|
||||
expect_memory(heap(0), inst('push', 45)),
|
||||
expect_word(Vlua.symbol('heap'), heap(4)))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Compile-mode asm
|
||||
test_line('] $ jmp asm',
|
||||
expect_heap_advance(8),
|
||||
expect_memory(heap(0),
|
||||
inst('push', Vlua.opcode_for('jmp')),
|
||||
inst('call', Vlua.symbol('compile_instruction'))))
|
||||
|
||||
-- Compile-mode asm with args
|
||||
test_line('] 45 $ xor #asm',
|
||||
expect_heap_advance(12),
|
||||
expect_memory(heap(0),
|
||||
inst('push', 45),
|
||||
inst('push', Vlua.opcode_for('xor'),
|
||||
inst('call', Vlua.symbol('compile_instruction_arg')))))
|
||||
|
||||
test_line(': foo 34 $ xor #asm ; immediate ] foo',
|
||||
expect_memory(heap(0),
|
||||
-- foo's header
|
||||
'f', 'o', 'o', 0, word(heap(10)), word(Vlua.symbol('compile_dict_start')),
|
||||
inst('push', 34), -- Push an arg
|
||||
inst('push', Vlua.opcode_for('xor')), -- Push an opcode
|
||||
inst('call', Vlua.symbol('compile_instruction_arg')), -- Compile that with an arg
|
||||
op('ret'), -- Return from foo
|
||||
-- Foo is now an immediate word, and when we call it in compile mode...
|
||||
inst('xor', 34))) -- It compiles a xor 34
|
||||
|
||||
test_line('$ xor 3', expect_stack{9, 3})
|
||||
test_line('$ blah 3', expect_stack{}, expect_output('Invalid mnemonic: blah\n'))
|
||||
|
||||
test_line('] $ xor 3',
|
||||
expect_stack{},
|
||||
expect_memory(heap(0),
|
||||
inst('push', 9),
|
||||
inst('push', 3)),
|
||||
expect_heap_advance(8))
|
||||
|
||||
test_line('] $ blah 3',
|
||||
expect_stack{},
|
||||
expect_output('Invalid mnemonic: blah\n'),
|
||||
expect_heap_advance(0)) -- It hits quit right after the error
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Comma compile a number
|
||||
test_line('1234 ,',
|
||||
expect_word(heap(0), 1234),
|
||||
expect_heap_advance(3))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Tick a word
|
||||
test_line("' print", expect_stack{ Vlua.symbol('print') })
|
||||
|
||||
-- Bracket-tick a word
|
||||
test_line("] ['] print",
|
||||
expect_memory(heap(0), inst('push', Vlua.symbol('print'))),
|
||||
expect_heap_advance(4))
|
||||
|
||||
-- Tick gibberish
|
||||
test_line("' bananas",
|
||||
expect_stack{},
|
||||
expect_r_stack{},
|
||||
expect_output('Not a word: bananas\n'))
|
||||
|
||||
-- Bracket-tick gibberish
|
||||
test_line("] ['] penguin",
|
||||
expect_stack{},
|
||||
expect_r_stack{},
|
||||
expect_output('Not a word: penguin\n'))
|
||||
|
||||
-- Tick a compile word
|
||||
test_line("' [", expect_stack{ Vlua.symbol('nova_open_bracket') })
|
||||
|
||||
-- Bracket-tick a compile word
|
||||
test_line("] ['] does>",
|
||||
expect_memory(heap(0), inst('push', Vlua.symbol('does_word'))),
|
||||
expect_heap_advance(4))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Fetch the pad address
|
||||
test_line(' pad ', expect_stack{ Vlua.symbol('pad') })
|
||||
|
||||
-- Read a word to the pad
|
||||
test_line('word mango',
|
||||
expect_output(''),
|
||||
expect_stack{ Vlua.symbol('pad') },
|
||||
expect_memory(Vlua.symbol('pad'), 'm', 'a', 'n', 'g', 'o', 0))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Literal, compiles a push instruction
|
||||
test_line('1234 ] literal',
|
||||
expect_stack{},
|
||||
expect_memory(heap(0), inst('push', 1234)),
|
||||
expect_heap_advance(4))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Paren comments
|
||||
test_line('1 2 ( 3 4 5 ) 6', expect_stack{1, 2, 6})
|
||||
|
||||
-- Nested paren comments
|
||||
test_line('1 2 ( ( 3 4 ) 5 6', expect_stack{1, 2})
|
||||
|
||||
-- Compiled paren comments
|
||||
test_line('] 1 2 ( 3 4 5 ) 6', expect_heap_advance(12))
|
||||
|
||||
-- Compiled nested paren comments
|
||||
test_line('] 1 2 ( ( 3 4 ) 5 6', expect_heap_advance(8))
|
||||
|
||||
-- Backslash comments
|
||||
test_lines({ '1 2 \\ 3 4', '5 6' }, expect_stack{1, 2, 5, 6})
|
||||
|
||||
-- Compiled backslash comments
|
||||
test_lines({ '] 1 2 \\ 3 4', '5 6' }, expect_heap_advance(16))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Parse numbers from words
|
||||
test_line('number 17', expect_stack{ 17, 1 })
|
||||
test_line('number blah', expect_stack{ 0 })
|
||||
test_line('number -23', expect_stack{ (-23 & 0xffffff), 1 })
|
||||
|
||||
-- Parse hex numbers from words
|
||||
test_line('hex number a4', expect_stack{ 164, 1 })
|
||||
test_line('hex number blah', expect_stack{ 0 })
|
||||
|
||||
-- Switch between hex and dec
|
||||
test_line('hex number a4 dec number 23', expect_stack{ 164, 1, 23, 1 })
|
||||
test_line('hex a4 dec 23', expect_stack{ 164, 23 })
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Output in hex and dec
|
||||
test_line('hex a4 . dec 23 .', expect_output('a423')) -- Yeah, no separator
|
||||
test_line('hex a4 dec .', expect_output('164'))
|
||||
test_line('dec 525 hex .', expect_output('20d'))
|
||||
test_line('-15 .', expect_output('-15'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Compiling strings to the heap
|
||||
test_line('s" foo"',
|
||||
expect_stack{heap(0)},
|
||||
expect_cursor(7),
|
||||
expect_memory(heap(0), 'f', 'o', 'o', 0),
|
||||
expect_heap_advance(4))
|
||||
|
||||
-- Compiling empty string
|
||||
test_line('s" "',
|
||||
expect_stack{heap(0)},
|
||||
expect_memory(heap(0), 0),
|
||||
expect_heap_advance(1))
|
||||
|
||||
-- Unterminated string
|
||||
test_line('s" foo',
|
||||
expect_stack{},
|
||||
expect_heap_advance(0),
|
||||
expect_cursor(6),
|
||||
expect_output('Unclosed string'))
|
||||
|
||||
-- Compile move squote
|
||||
test_line('] s" blah"',
|
||||
expect_memory(heap(0),
|
||||
inst('jmpr', 9), -- length of the jmpr itself + 'blah\0'
|
||||
'b', 'l', 'a', 'h', 0, -- The actual string
|
||||
inst('push', heap(4))), -- Push the addr of the string
|
||||
expect_heap_advance(13))
|
||||
|
||||
-- Compile mode unterminated string
|
||||
test_line('] s" foo',
|
||||
expect_heap_advance(0),
|
||||
expect_output('Unclosed string'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Basic output
|
||||
test_line('." foo"',
|
||||
expect_stack{}, expect_heap_advance(0),
|
||||
expect_output('foo'))
|
||||
|
||||
-- Compile output
|
||||
test_line('] ." foo"',
|
||||
expect_heap_advance(16),
|
||||
expect_memory(heap(0),
|
||||
inst('jmpr', 8),
|
||||
'f', 'o', 'o', 0,
|
||||
inst('push', heap(4)),
|
||||
inst('call', Vlua.symbol('print'))))
|
||||
|
||||
-- Unterminated output
|
||||
test_line('." foo',
|
||||
expect_stack{}, expect_heap_advance(0),
|
||||
expect_output('Unclosed string'))
|
||||
|
||||
-- Compile output
|
||||
test_line('] ." foo',
|
||||
expect_heap_advance(0),
|
||||
expect_output('Unclosed string'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Test print fn
|
||||
test_line('s" foo" print',
|
||||
expect_cursor(13),
|
||||
expect_heap_advance(4),
|
||||
expect_output('foo'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Test compare
|
||||
test_line('s" foo" s" bar" compare', expect_stack{0})
|
||||
test_line('s" foo" s" foo" compare', expect_stack{1})
|
||||
test_line('s" foo" ?dup compare', expect_stack{1}) -- There's no simple dup...
|
||||
test_line('s" foo" s" foo234" compare', expect_stack{0})
|
||||
test_line('s" foo123" s" foo" compare', expect_stack{0})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Print the stack
|
||||
test_line('10 20 30 .s',
|
||||
expect_stack{ 10, 20, 30 },
|
||||
expect_output('<< 10 20 30 >>'))
|
||||
|
||||
-- Print the stack in hex
|
||||
test_line('10 20 30 hex .s',
|
||||
expect_stack{ 10, 20, 30 },
|
||||
expect_output('<< a 14 1e >>'))
|
||||
|
||||
-- Print nothing
|
||||
test_line('.s',
|
||||
expect_stack{},
|
||||
expect_output('<< >>'))
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- pushr, peekr
|
||||
test_line('3 >r r@',
|
||||
expect_stack{3},
|
||||
expect_4th_rstack{3})
|
||||
|
||||
-- popr
|
||||
test_line('3 >r 5 r>',
|
||||
expect_stack{5, 3},
|
||||
expect_4th_rstack{})
|
||||
|
||||
-- rpick
|
||||
test_line('10 20 30 >r >r >r 2 rpick',
|
||||
expect_stack{30},
|
||||
expect_4th_rstack{30, 20, 10})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Heap ptr stuff
|
||||
test_line('&heap', expect_stack{Vlua.symbol('heap')})
|
||||
test_line('here', expect_stack{heap(0)})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- To-asm
|
||||
test_line('$ brnz >asm',
|
||||
expect_stack{},
|
||||
expect_heap_advance(4),
|
||||
expect_memory(heap(0), inst('brnz', 0)),
|
||||
expect_4th_rstack{heap(1)})
|
||||
|
||||
-- Resolve
|
||||
test_line('$ brnz >asm resolve',
|
||||
expect_heap_advance(4),
|
||||
expect_4th_rstack{},
|
||||
expect_memory(heap(0),
|
||||
inst('brnz', 4))) -- brnz 12 ahead
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- An 'if' implementation
|
||||
test_line(': if $ brz >asm ; immediate ] if',
|
||||
expect_stack{},
|
||||
expect_heap_advance(9 + 9 + 4), -- Entry 'if', body of 'if' (push, call, ret), and the brnz we just compiled
|
||||
expect_4th_rstack{heap(9 + 9 + 1)}, -- Address of said brnz' arg
|
||||
expect_memory(heap(9), -- Skipping if's entry
|
||||
inst('push', Vlua.opcode_for('brz')), inst('call', Vlua.symbol('nova_asm_to')), op('ret'), -- if's body
|
||||
inst('brz', 0))) -- The unresolved brnz 'if' compiled
|
||||
|
||||
-- If / then
|
||||
test_lines({ ': if $ brz >asm ; immediate',
|
||||
': then resolve ; immediate',
|
||||
': foo if 2 then ;',
|
||||
'1 foo 10 0 foo' },
|
||||
expect_stack{2, 10})
|
||||
|
||||
-- If / else / then
|
||||
test_lines({ ': if $ brz >asm ; immediate',
|
||||
': then resolve ; immediate',
|
||||
': else r> $ jmpr >asm >r resolve ; immediate',
|
||||
': foo if 2 else 3 then ;',
|
||||
'1 foo 10 0 foo' },
|
||||
expect_stack{2, 10, 3})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Begin / until loops
|
||||
test_lines({ ': begin here >r ; immediate', -- Begin just marks a point in the program we'll brnz back to
|
||||
-- Here's the fun part.
|
||||
-- Pull the address stored by 'begin' off the rstack and subtract `here` from it
|
||||
-- Then compile a brz to that address
|
||||
': until r> here - $ brz #asm ; immediate',
|
||||
-- This ought to loop from 5..0, leaving each one on the stack
|
||||
': foo 5 begin dup 1 - dup not until ; foo' },
|
||||
expect_stack{5, 4, 3, 2, 1, 0})
|
||||
|
||||
-- do / loop counted loops
|
||||
test_lines({ 'create 1+ 1 $ add #asm ] ;',
|
||||
': do postpone swap postpone >r postpone >r here >r ; immediate',
|
||||
': _loop_test r> 1+ dup r@ < swap >r ;', -- pull off and inc the cntr, dup, peek at the limit, compare them, put the new cntr back
|
||||
': unloop r> r> pop pop ;',
|
||||
': loop postpone _loop_test r> here - $ brnz #asm postpone unloop ; immediate',
|
||||
': foo 3 0 do 33 loop ; foo' },
|
||||
expect_stack{33, 33, 33})
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Testing quit as called by an error
|
||||
test_line('2 3 : foo nooope ; 7',
|
||||
expect_heap_advance(10), -- It does the header but that's it
|
||||
expect_output('Not a word: nooope\n'), -- Spits out an error message
|
||||
expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('immediate_handleword')), -- Back in immediate mode
|
||||
expect_stack{}) -- Clobbers the stack
|
||||
|
||||
-- Testing quit as called manually
|
||||
test_lines({ ': low 3 quit 65 emit ;',
|
||||
': med 2 low 66 emit ;',
|
||||
': high 1 med 67 emit ;',
|
||||
'high' },
|
||||
expect_output(''), -- This isn't an error, we just quit
|
||||
expect_stack{}) -- We quit partway through 'low', so skip all the frames above that
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Testing immediate-mode lambdas
|
||||
test_line('{ 3 5 }',
|
||||
expect_heap_advance(0), -- It does not move the heap
|
||||
expect_output(''),
|
||||
expect_word(Vlua.symbol('handleword_hook'), Vlua.symbol('immediate_handleword')), -- Back in immediate mode
|
||||
expect_stack{Vlua.symbol('heap_start')}, -- Leaves the address of the lambda on the stack
|
||||
expect_memory(heap(0),
|
||||
inst('push', 3),
|
||||
inst('push', 5),
|
||||
op('ret')))
|
||||
|
||||
test_line('{ 3 5 } execute',
|
||||
expect_stack{ 3, 5 }) -- Runs the anonymous fn
|
||||
|
||||
-- Compile-mode lambda, non-nested
|
||||
test_line(': foo 1 { 2 } ; foo',
|
||||
expect_output(''),
|
||||
expect_heap_advance(10 + 4 + 4 + 4 + 1 + 4 + 1), -- header, push, jmpr, push, ret, push, ret
|
||||
expect_memory(heap(10),
|
||||
inst('push', 1),
|
||||
inst('jmpr', 4 + 4 + 1), -- jmpr, push, ret
|
||||
inst('push', 2),
|
||||
op('ret'),
|
||||
inst('push', heap(10 + 4 + 4)), -- header, push(1), jmpr
|
||||
op('ret')),
|
||||
expect_stack{ 1, heap(10 + 4 + 4) })
|
||||
|
||||
-- Compile-mode lambda, nested
|
||||
test_line(': foo 1 { 2 { 3 } } ; foo',
|
||||
expect_output(''),
|
||||
expect_heap_advance(10 + 4 + 4 + 4 + 4 + 4 + 1 + 4 + 1 + 4 + 1), -- header, push, jmpr, push, ret, push, ret
|
||||
expect_memory(heap(10),
|
||||
inst('push', 1),
|
||||
inst('jmpr', 4 + 4 + 4 + 4 + 1 + 4 + 1), -- jmpr, push(2), jmpr, push(3), ret, push(inner-lambda), ret
|
||||
inst('push', 2),
|
||||
inst('jmpr', 4 + 4 + 1), -- inner lambda: jmpr, push, ret
|
||||
inst('push', 3),
|
||||
op('ret'),
|
||||
inst('push', heap(10 + 4 + 4 + 4 + 4)), -- push the inner-lambda addr
|
||||
op('ret'),
|
||||
inst('push', heap(10 + 4 + 4)),
|
||||
op('ret')
|
||||
),
|
||||
expect_stack{ 1, heap(10 + 4 + 4) },
|
||||
expect_word(Vlua.symbol('lambda_nesting_level'), 0))
|
||||
|
||||
-- Executing nested compile-mode lambdas
|
||||
test_line(': foo 1 { 2 { 3 } } ; foo execute execute',
|
||||
expect_stack{ 1, 2, 3 })
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- A Graham accumulator
|
||||
test_lines({ ': accum create 0 , does> dup >r @ + dup r> ! ;',
|
||||
'accum foo 1 foo 2 foo 3 foo' },
|
||||
expect_output(''),
|
||||
expect_stack{ 1, 3, 6 })
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
-- Test that single-opcode words exist, at least:
|
||||
test_line(': test + - / * % ^ & | not < > = @ ! c@ c! pop dup swap pick rot ;',
|
||||
expect_output('')) -- If it didn't recognize any of these then it would error
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
--[==[
|
||||
TODOs
|
||||
- `quit` should clear the rstack but not the data stack, new opcode probably
|
||||
- refactor test assert fns to be shorter / in a different file
|
||||
|
||||
Later TODOs
|
||||
- Remove 'continue', we can implement it ourselves easily
|
||||
- Prelude of simple words
|
||||
- Rewrite / macro-ize string fns
|
||||
--]==]
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
print('Bytes available: ' .. 131072 - heap(0))
|
||||
print('Text size: ' .. Vlua.symbol('data_start') - 0x400)
|
||||
print('Including dictionaries: ' .. Vlua.symbol('heap') - 0x400)
|
||||
print('Remaining in 4k: ' .. 4096 - (Vlua.symbol('heap') - 0x400))
|
||||
Reference in New Issue
Block a user