diff --git a/.gitignore b/.gitignore index ea8c4bf..6167658 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target + +wapm_packages \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index affbda4..a907b10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,21 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + [[package]] name = "cc" version = "1.0.73" @@ -129,6 +144,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "cpufeatures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" +dependencies = [ + "libc", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "either" version = "1.8.0" @@ -141,6 +174,16 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -148,8 +191,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -177,11 +222,14 @@ dependencies = [ "anyhow", "clap", "console", + "getrandom", "itertools", "num-parse", "radix_fmt", "rand", "rhexdump", + "rust-embed", + "unescape", ] [[package]] @@ -212,12 +260,30 @@ dependencies = [ "either", ] +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.5.0" @@ -333,6 +399,12 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "os_str_bytes" version = "6.3.0" @@ -429,12 +501,68 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847" +[[package]] +name = "rust-embed" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a17e5ac65b318f397182ae94e532da0ba56b88dd1200b774715d36c4943b1c3" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e763e24ba2bf0c72bc6be883f967f794a019fafd1b86ba1daff9c91a7edd30" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756feca3afcbb1487a1d01f4ecd94cf8ec98ea074c55a69e7136d29fb6166029" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "strsim" version = "0.10.0" @@ -477,6 +605,18 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unescape" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" + [[package]] name = "unicode-ident" version = "1.0.3" @@ -495,12 +635,77 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index f4dd277..7187d97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ name = "hence" version = "0.1.0" edition = "2021" +authors = ["Dominic Grimm "] +repository = "https://git.dergrimm.net/dergrimm/hence.git" [lib] name = "hence" @@ -19,7 +21,9 @@ num-parse = "0.1.2" clap = { version = "3.2.16", features = ["derive"] } rhexdump = "0.1.1" radix_fmt = "1" +getrandom = { version = "0.2", features = ["js"] } rand = "0.8.5" console = "0.15.1" anyhow = { version = "1.0.62", features = ["backtrace"] } - +rust-embed = "6.4.0" +unescape = "0.1.0" diff --git a/README.md b/README.md index c20ecdc..44bad58 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,3 @@ | `0x0b` | `alu` | Runs ALU with `tmp`'s value as operator | | | `0x0c` | `get` | Sets `tmp` to memory at address in `tmp` | | | `0x0d` | `set` | Sets memory to value at specific address | | - diff --git a/examples/forth.asm b/examples/forth.asm deleted file mode 100644 index 75e3ee6..0000000 --- a/examples/forth.asm +++ /dev/null @@ -1,74 +0,0 @@ -; hence core lib -core: - core_mem: - .define CORE_MEM_PRG, (0 * 1024) - .define CORE_MEM_ST, (32 * 1024) - .define CORE_MEM_MEM, (40 * 1024) - .define CORE_MEM_OUT, (56 * 1024) - .define CORE_MEM_CHR, (56 * 1024 + 1) - .define CORE_MEM_KEY, (56 * 1024 + 2) - - core_reg: - .define CORE_REG_PC, 0x0 - .define CORE_REG_OPC, 0x1 - .define CORE_REG_ARG, 0x2 - .define CORE_REG_S, 0x3 - .define CORE_REG_SP, 0x4 - .define CORE_REG_A, 0x5 - .define CORE_REG_B, 0x6 - .define CORE_REG_C, 0x7 - .define CORE_REG_D, 0x8 - - core_alu: - .define CORE_ALU_NOT, 0x00 - .define CORE_ALU_AND, 0x01 - .define CORE_ALU_OR, 0x02 - .define CORE_ALU_XOR, 0x03 - .define CORE_ALU_LSH, 0x04 - .define CORE_ALU_RSH, 0x05 - .define CORE_ALU_ADD, 0x06 - .define CORE_ALU_SUB, 0x07 - .define CORE_ALU_MUL, 0x08 - .define CORE_ALU_DIV, 0x09 - .define CORE_ALU_CMP, 0x0a - .define CORE_ALU_EQ, 0x0b - .define CORE_ALU_LT, 0x0c - .define CORE_ALU_GT, 0x0d - .define CORE_ALU_LEQ, 0x0e - .define CORE_ALU_GEQ, 0x0f - .define CORE_ALU_BOL, 0x10 - .define CORE_ALU_INV, 0x11 - .define CORE_ALU_RND, 0x12 - -; hence standard lib -STD: - .define STD_U8_MAX, 0xff - .define STD_U16_MAX, 0xffff - - .macro std_stop - ts 0xffff - tlr CORE_REG_PC - .macroend - -forth: - .define FORTH_MEM_INPUT_SIZE, 16 - .define FORTH_MEM_INPUT_DYN_SIZE, CORE_MEM_MEM - .define FORTH_MEM_INPUT_START, (FORTH_MEM_INPUT_DYN_SIZE + 1) - .define FORTH_MEM_INPUT_END, (FORTH_MEM_INPUT_START + FORTH_MEM_INPUT_SIZE) - -.define jump_main, (CORE_MEM_ST - 3 - 1) - -ts jump_main -tlr CORE_REG_PC - -main: - dbg - -; set PC to maximum for u16 and therefore stops program execution -ts 0xffff ; load 0xffff into TMP -tlr CORE_REG_PC ; store value of TMP into register PC - -.org jump_main - ts main - tlr CORE_REG_PC - diff --git a/examples/forth.bin b/examples/forth.bin deleted file mode 100644 index 12f3be4..0000000 Binary files a/examples/forth.bin and /dev/null differ diff --git a/forth/constants.asm b/forth/constants.asm new file mode 100644 index 0000000..aa3d4d5 --- /dev/null +++ b/forth/constants.asm @@ -0,0 +1,6 @@ +.requires "$lib/core.asm" + +.define MEM_INPUT_SIZE, 16 +.define MEM_INPUT_DYN_END, CORE_MEM_MEM +.define MEM_INPUT_START, (MEM_INPUT_DYN_END + 1) +.define MEM_INPUT_END, (MEM_INPUT_START + MEM_INPUT_SIZE) diff --git a/forth/main.asm b/forth/main.asm new file mode 100644 index 0000000..ab18f2a --- /dev/null +++ b/forth/main.asm @@ -0,0 +1,64 @@ +.include "$lib/core.asm" +.include "$lib/std.asm" +.include "$lib/main.asm" + +.include "constants.asm" + +.jump_main + +data: + +.main + .std_rset CORE_REG_C, MEM_INPUT_START + get_input_loop: + .std_get CORE_MEM_KEY + tlr CORE_REG_A + @ tlr CORE_REG_D + .std_set CORE_MEM_CHR + tsr CORE_REG_C + set + + @ .std_rset CORE_REG_B, ' ' + @ .std_alu CORE_ALU_EQ + @ tlr CORE_REG_A + @ .std_cond_jump get_input_loop + @ .std_cp CORE_REG_D, CORE_REG_A + + tlr CORE_REG_A + .std_rset CORE_REG_B, 1 + .std_alu CORE_ALU_ADD + tlr CORE_REG_A + tlr CORE_REG_C + + .std_rset CORE_REG_B, MEM_INPUT_END + .std_alu CORE_ALU_LT + tlr CORE_REG_A + .std_cond_jump get_input_loop + + .std_cp CORE_REG_C, CORE_REG_A + .std_set MEM_INPUT_DYN_END + + .std_rset CORE_REG_A, '\n' + .std_set CORE_MEM_CHR + + .std_rset CORE_REG_B, MEM_INPUT_START + .std_get MEM_INPUT_DYN_END + tlr CORE_REG_D + print_loop: + tsr CORE_REG_B + get + tlr CORE_REG_A + .std_set CORE_MEM_CHR + + .std_rset CORE_REG_A, 1 + .std_alu CORE_ALU_ADD + tlr CORE_REG_B + + .std_cp CORE_REG_D, CORE_REG_A + .std_alu CORE_ALU_GT + tlr CORE_REG_A + .std_cond_jump print_loop + + .std_rset CORE_REG_A, '\n' + .std_set CORE_MEM_CHR +.std_stop diff --git a/lib/core.asm b/lib/core.asm new file mode 100644 index 0000000..d87919d --- /dev/null +++ b/lib/core.asm @@ -0,0 +1,52 @@ +; hence core lib +core: + .define NULL, 0x0000 + .define VOID, NULL + + .define FALSE, NULL + .define TRUE, 0x0001 + + .define CORE_U8_MAX, 0x00ff + .define CORE_U16_MAX, 0xffff + + .define CORE_KB, 1024 + + core_mem: + .define CORE_MEM_PRG, (0 * CORE_KB) + .define CORE_MEM_ST, (32 * CORE_KB) + .define CORE_MEM_MEM, (40 * CORE_KB) + .define CORE_MEM_OUT, (56 * CORE_KB) + .define CORE_MEM_CHR, (56 * CORE_KB + 1) + .define CORE_MEM_KEY, (56 * CORE_KB + 2) + + core_reg: + .define CORE_REG_PC, 0x0 + .define CORE_REG_OPC, 0x1 + .define CORE_REG_ARG, 0x2 + .define CORE_REG_S, 0x3 + .define CORE_REG_SP, 0x4 + .define CORE_REG_A, 0x5 + .define CORE_REG_B, 0x6 + .define CORE_REG_C, 0x7 + .define CORE_REG_D, 0x8 + + core_alu: + .define CORE_ALU_NOT, 0x00 + .define CORE_ALU_AND, 0x01 + .define CORE_ALU_OR, 0x02 + .define CORE_ALU_XOR, 0x03 + .define CORE_ALU_LSH, 0x04 + .define CORE_ALU_RSH, 0x05 + .define CORE_ALU_ADD, 0x06 + .define CORE_ALU_SUB, 0x07 + .define CORE_ALU_MUL, 0x08 + .define CORE_ALU_DIV, 0x09 + .define CORE_ALU_CMP, 0x0a + .define CORE_ALU_EQ, 0x0b + .define CORE_ALU_LT, 0x0c + .define CORE_ALU_GT, 0x0d + .define CORE_ALU_LEQ, 0x0e + .define CORE_ALU_GEQ, 0x0f + .define CORE_ALU_BOL, 0x10 + .define CORE_ALU_INV, 0x11 + .define CORE_ALU_RND, 0x12 diff --git a/lib/main.asm b/lib/main.asm new file mode 100644 index 0000000..a71f062 --- /dev/null +++ b/lib/main.asm @@ -0,0 +1,19 @@ +; "main function" like definition macro + +.requires "$lib/core.asm" +.requires "$lib/std.asm" + +.define main_local_jump_main, (CORE_MEM_ST - 3 - 1) + +.macro jump_main + .std_jump main_local_jump_main +.endmacro + +.macro main + main: + + .org main_local_jump_main + ts main + tlr CORE_REG_PC + .org main +.endmacro diff --git a/lib/std.asm b/lib/std.asm new file mode 100644 index 0000000..ed7d8d1 --- /dev/null +++ b/lib/std.asm @@ -0,0 +1,62 @@ +; hence standard lib + +.requires "$lib/core.asm" + +std: + .macro std_tclr + ts NULL + .endmacro + + .macro std_rclr, std_rclr_arg_0_reg + ts NULL + tlr std_rclr_arg_0_reg + .endmacro + + .macro std_alu, std_alu_arg_0_op + ts std_alu_arg_0_op + alu + .endmacro + + .macro std_get, std_get_arg_0_addr + ts std_get_arg_0_addr + get + .endmacro + + .macro std_set, std_set_arg_0_addr + ts std_set_arg_0_addr + set + .endmacro + + .macro std_cp, std_cp_arg_0_from, std_cp_arg_1_to + tsr std_cp_arg_0_from + tlr std_cp_arg_1_to + .endmacro + + .macro std_mv, std_mv_arg_0_from, std_cp_arg_1_to + tsr std_cp_arg_0_from + tlr std_cp_arg_1_to + .std_rclr std_cp_arg_1_to + .endmacro + + .macro std_rset, std_init_arg_0_reg, std_init_arg_1_val + ts std_init_arg_1_val + tlr std_init_arg_0_reg + .endmacro + + .macro std_jump, std_jump_arg_0_label + .std_rset CORE_REG_PC, std_jump_arg_0_label + .endmacro + + .macro std_cond_jump, std_cond_jump_arg_0_label + ts std_cond_jump_arg_0_label + tlrc CORE_REG_PC + .endmacro + + .macro std_stop + .std_rset CORE_REG_PC, 0xffff + .endmacro + + .macro std_inc, std_inc_arg_0_reg + .std_rset std_inc_arg_0_reg, 1 + .std_alu CORE_ALU_ADD + .endmacro diff --git a/src/bin/main.rs b/src/bin/main.rs index e2bcda6..783479a 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,3 +1,4 @@ +use anyhow::{bail, Result}; use clap::{Parser, Subcommand}; use std::fs; use std::fs::File; @@ -41,54 +42,67 @@ enum Commands { }, } -fn main() { +fn main() -> Result<()> { let args = Cli::parse(); match args.commands { Commands::Lex { src } => { - let assembly = fs::read_to_string(src).unwrap(); - let tokens = lexer::lex(assembly).unwrap(); + let assembly = fs::read_to_string(src)?; + let tokens = lexer::lex(assembly)?; dbg!(tokens); + + Ok(()) } Commands::Parse { src } => { - let assembly = fs::read_to_string(src).unwrap(); - let tokens = lexer::lex(assembly).unwrap(); - let ast = parser::parse(tokens).unwrap(); + let assembly = fs::read_to_string(src)?; + let tokens = lexer::lex(assembly)?; + let ast = parser::parse(tokens)?; dbg!(ast); + + Ok(()) } Commands::Assemble { src, bin, dump } => { - let assembly = fs::read_to_string(&src).unwrap(); - let tokens = lexer::lex(assembly).unwrap(); - let ast = parser::parse(tokens).unwrap(); + let assembly = fs::read_to_string(&src)?; + let tokens = lexer::lex(assembly)?; + let ast = parser::parse(tokens)?; let mut data = assembler::Data::new( - match Path::new(&src).parent().unwrap().to_str() { - Some(x) => x.to_string(), - _ => panic!("Could not get directory in which source code resides"), + match match Path::new(&src).parent() { + Some(x) => x.to_str().map(|y| y.to_string()), + None => None, + } { + Some(s) => s, + None => bail!("Could not get directory in which source code resides"), }, ast, ); - // assembler::assemble(&mut data).unwrap(); - data.assemble().unwrap(); + data.assemble()?; if let Some(x) = bin { - File::create(x).unwrap().write_all(&data.program).unwrap(); + File::create(x)?.write_all(&data.program)?; } if dump { println!("{}", rhexdump::hexdump(&data.program)); } + + Ok(()) } Commands::Run { bin } => { - let mut program_buf: Vec = Vec::new(); - let file = File::open(bin).unwrap(); - BufReader::new(file).read_to_end(&mut program_buf).unwrap(); + let mut program_buf: Vec = vec![]; + let file = File::open(bin)?; + BufReader::new(file).read_to_end(&mut program_buf)?; if program_buf.len() < (32 * 1024) { program_buf.append(&mut vec![0; 32 * 1024 - program_buf.len()]); } - let program: [u8; 32 * 1024] = (&program_buf[0..(32 * 1024)]).try_into().unwrap(); + let program: [u8; 32 * 1024] = (&program_buf[0..(32 * 1024)]).try_into()?; let mut data = emulator::Data::new(program); - emulator::emulate(&mut data).unwrap(); + emulator::emulate(&mut data)?; + + Ok(()) } } } + +#[cfg(test)] +mod tests {} diff --git a/src/lib/arg.rs b/src/lib/arg.rs index bd707a4..467270d 100644 --- a/src/lib/arg.rs +++ b/src/lib/arg.rs @@ -1,11 +1,22 @@ use anyhow::{bail, Result}; +use rand; +use std::cmp::Ordering; +use unescape::unescape; use crate::arg; use crate::assembler; use crate::lexer; +pub fn unescape_string(string: &str) -> String { + match unescape(string) { + Some(x) => x, + None => "".to_string(), + } +} + #[derive(Debug, Clone)] pub enum Arg { + Char(char), String(String), Number(u16), Variable(String), @@ -19,15 +30,16 @@ pub enum Arg { impl assembler::ToAssembly for Arg { fn to_assembly(&self) -> String { match self { - Arg::String(x) => format!("\"{x}\""), + Arg::Char(x) => format!("'{}'", x), + Arg::String(x) => format!("\"{}\"", x), Arg::Number(x) => x.to_string(), Arg::Variable(x) => x.clone(), Arg::BinaryExpression { left, right, op } => { format!( - "({left} {op} {right})", - left = left.to_assembly(), - op = op.to_assembly(), - right = right.to_assembly() + "({} {} {})", + left.to_assembly(), + op.to_assembly(), + right.to_assembly() ) } } @@ -35,11 +47,11 @@ impl assembler::ToAssembly for Arg { } impl assembler::ByteResolvable for Arg { - fn resolve_number(&self, data: &mut assembler::Data) -> Result { + fn resolve_number(&self, data: &mut assembler::Data) -> Result { match self { + Arg::Char(x) => Ok(*x as u16), Arg::String(x) => { - let y = x.replace("\\n", "\n"); - + let y = unescape_string(x); if y.len() == 1 { Ok(y.as_bytes()[0] as u16) } else { @@ -58,36 +70,59 @@ impl assembler::ByteResolvable for Arg { match arg { Some(a) => { - // arg = Some(a.clone()); match a { arg::Arg::Variable(n) => { name = n; } - _ => break Ok(a.resolve_number(data).unwrap()), + _ => break Ok(a.resolve_number(data)?), }; } - _ => return Err(format!("Variable does not exist: '{name}'")), + _ => bail!("Variable does not exist: {}", name), } } } Arg::BinaryExpression { left, right, op } => { - let left_val = left.resolve_number(data).unwrap(); - let right_val = right.resolve_number(data).unwrap(); + let left_val = left.resolve_number(data)?; + let right_val = right.resolve_number(data)?; Ok(match op { + BinaryExpressionOperator::Not => !left_val, + BinaryExpressionOperator::And => left_val & right_val, + BinaryExpressionOperator::Nand => !(left_val & right_val), + BinaryExpressionOperator::Or => left_val | right_val, + BinaryExpressionOperator::Nor => !(left_val | right_val), + BinaryExpressionOperator::Xor => left_val ^ right_val, + BinaryExpressionOperator::Xnor => !(left_val ^ right_val), + BinaryExpressionOperator::Lsh => left_val << right_val, + BinaryExpressionOperator::Rsh => left_val >> right_val, BinaryExpressionOperator::Add => left_val.wrapping_add(right_val), BinaryExpressionOperator::Sub => left_val.wrapping_sub(right_val), BinaryExpressionOperator::Mul => left_val.wrapping_mul(right_val), BinaryExpressionOperator::Div => left_val.wrapping_div(right_val), BinaryExpressionOperator::Pow => left_val.wrapping_pow(right_val as u32), + BinaryExpressionOperator::Cmp => match left_val.cmp(&right_val) { + Ordering::Equal => 0, + Ordering::Less => 1, + Ordering::Greater => 2, + }, + BinaryExpressionOperator::Eq => (left_val == right_val) as u16, + BinaryExpressionOperator::Neq => (left_val != right_val) as u16, + BinaryExpressionOperator::Lt => (left_val < right_val) as u16, + BinaryExpressionOperator::Gt => (left_val > right_val) as u16, + BinaryExpressionOperator::Leq => (left_val <= right_val) as u16, + BinaryExpressionOperator::Geq => (left_val >= right_val) as u16, + BinaryExpressionOperator::Bol => (left_val & 1 == 1) as u16, + BinaryExpressionOperator::Inv => (left_val & 1 != 1) as u16, + BinaryExpressionOperator::Rnd => rand::random(), }) } } } - fn resolve_bytes(&self, data: &mut assembler::Data) -> Result, String> { + fn resolve_bytes(&self, data: &mut assembler::Data) -> Result> { match self { - Arg::String(x) => Ok(x.replace("\\n", "\n").bytes().collect()), + Arg::Char(x) => Ok(vec![*x as u8]), + Arg::String(x) => Ok(unescape_string(x).bytes().collect()), Arg::Number(x) => Ok(vec![(*x >> 8) as u8, *x as u8]), Arg::Variable(x) => { let mut name = x.clone(); @@ -98,15 +133,14 @@ impl assembler::ByteResolvable for Arg { match arg { Some(a) => { - // arg = Some(a.clone()); match a { arg::Arg::Variable(n) => { name = n; } - _ => break Ok(a.resolve_bytes(data).unwrap()), + _ => break Ok(a.resolve_bytes(data)?), }; } - _ => return Err(format!("Variable does not exist: '{name}'")), + _ => bail!("Variable could not be resolved: {}", name), } } } @@ -115,74 +149,145 @@ impl assembler::ByteResolvable for Arg { right: _, op: _, } => { - let x = self.resolve_number(data).unwrap(); + let x = self.resolve_number(data)?; Ok(vec![(x >> 8) as u8, x as u8]) } } } + + fn resolve_string(&self, data: &mut assembler::Data) -> Result { + match self { + Arg::Char(x) => Ok(x.to_string()), + Arg::String(x) => Ok(x.clone()), + Arg::Number(_) => bail!("Number cannot be resolved as string"), + Arg::Variable(_) => self.resolve_string(data), + Arg::BinaryExpression { + left: _, + right: _, + op: _, + } => bail!("Binary expression cannot be resolved as string"), + } + } } #[derive(Debug, Clone)] pub enum BinaryExpressionOperator { + Not, + And, + Nand, + Or, + Nor, + Xor, + Xnor, + Lsh, + Rsh, Add, Sub, Mul, Div, Pow, + Cmp, + Eq, + Neq, + Lt, + Gt, + Leq, + Geq, + Bol, + Inv, + Rnd, } impl assembler::ToAssembly for BinaryExpressionOperator { fn to_assembly(&self) -> String { match self { + BinaryExpressionOperator::Not => "~".to_string(), + BinaryExpressionOperator::And => "&".to_string(), + BinaryExpressionOperator::Nand => "~&".to_string(), + BinaryExpressionOperator::Or => "|".to_string(), + BinaryExpressionOperator::Nor => "~|".to_string(), + BinaryExpressionOperator::Xor => "^".to_string(), + BinaryExpressionOperator::Xnor => "~^".to_string(), + BinaryExpressionOperator::Lsh => "<<".to_string(), + BinaryExpressionOperator::Rsh => ">>".to_string(), BinaryExpressionOperator::Add => "+".to_string(), BinaryExpressionOperator::Sub => "-".to_string(), BinaryExpressionOperator::Mul => "*".to_string(), BinaryExpressionOperator::Div => "/".to_string(), BinaryExpressionOperator::Pow => "**".to_string(), + BinaryExpressionOperator::Cmp => "<=>".to_string(), + BinaryExpressionOperator::Eq => "==".to_string(), + BinaryExpressionOperator::Neq => "!=".to_string(), + BinaryExpressionOperator::Lt => "<".to_string(), + BinaryExpressionOperator::Gt => ">".to_string(), + BinaryExpressionOperator::Leq => "<=".to_string(), + BinaryExpressionOperator::Geq => ">=".to_string(), + BinaryExpressionOperator::Bol => "!!".to_string(), + BinaryExpressionOperator::Inv => "!".to_string(), + BinaryExpressionOperator::Rnd => "?".to_string(), } } } pub fn parse_binary_operation(token: &lexer::Token) -> Result { match token { + lexer::Token::Not => Ok(BinaryExpressionOperator::Not), + lexer::Token::And => Ok(BinaryExpressionOperator::And), + lexer::Token::Nand => Ok(BinaryExpressionOperator::Nand), + lexer::Token::Or => Ok(BinaryExpressionOperator::Or), + lexer::Token::Nor => Ok(BinaryExpressionOperator::Nor), + lexer::Token::Xor => Ok(BinaryExpressionOperator::Xor), + lexer::Token::Xnor => Ok(BinaryExpressionOperator::Xnor), + lexer::Token::Lsh => Ok(BinaryExpressionOperator::Lsh), + lexer::Token::Rsh => Ok(BinaryExpressionOperator::Rsh), lexer::Token::Add => Ok(BinaryExpressionOperator::Add), lexer::Token::Sub => Ok(BinaryExpressionOperator::Sub), lexer::Token::Mul => Ok(BinaryExpressionOperator::Mul), lexer::Token::Div => Ok(BinaryExpressionOperator::Div), lexer::Token::Pow => Ok(BinaryExpressionOperator::Pow), + lexer::Token::Cmp => Ok(BinaryExpressionOperator::Cmp), + lexer::Token::Eq => Ok(BinaryExpressionOperator::Eq), + lexer::Token::Neq => Ok(BinaryExpressionOperator::Neq), + lexer::Token::Lt => Ok(BinaryExpressionOperator::Lt), + lexer::Token::Gt => Ok(BinaryExpressionOperator::Gt), + lexer::Token::Leq => Ok(BinaryExpressionOperator::Leq), + lexer::Token::Geq => Ok(BinaryExpressionOperator::Geq), + lexer::Token::Bol => Ok(BinaryExpressionOperator::Bol), + lexer::Token::Inv => Ok(BinaryExpressionOperator::Inv), + lexer::Token::Rnd => Ok(BinaryExpressionOperator::Rnd), _ => bail!("Invalid binary expression operator"), } } -pub fn parse_binary_expression_arg(tokens: &mut Vec<&&lexer::Token>) -> Result { +pub fn parse_binary_expression_arg(tokens: &mut Vec<&lexer::Token>) -> Result { if tokens.is_empty() { bail!("Malformed binary expression"); } - let mut args: Vec<&&lexer::Token> = tokens.drain(..3).collect(); + let mut args: Vec<_> = tokens.drain(..3).collect(); let mut bin_expr = Arg::BinaryExpression { - left: Box::new((&parse_args(vec![args[0]]).unwrap()[0]).clone()), - right: Box::new((&parse_args(vec![args[2]]).unwrap()[0]).clone()), - op: parse_binary_operation(args[1]).unwrap(), + left: Box::new((&parse_args(vec![args[0]])?[0]).clone()), + right: Box::new((&parse_args(vec![args[2]])?[0]).clone()), + op: parse_binary_operation(args[1])?, }; while !tokens.is_empty() { args = tokens.drain(..2).collect(); bin_expr = Arg::BinaryExpression { left: Box::new(bin_expr), - right: Box::new((&parse_args(vec![args[1]]).unwrap()[0]).clone()), - op: parse_binary_operation(args[0]).unwrap(), + right: Box::new((&parse_args(vec![args[1]])?[0]).clone()), + op: parse_binary_operation(args[0])?, } } Ok(bin_expr) } -pub fn parse_args(tokens: Vec<&lexer::Token>) -> Result, &str> { - let mut iter = tokens.iter(); - let mut args: Vec = Vec::new(); +pub fn parse_args(tokens: Vec<&lexer::Token>) -> Result> { + let mut iter = tokens.into_iter(); + let mut args: Vec = vec![]; let mut last_was_comma = true; while let Some(token) = iter.next() { @@ -192,11 +297,21 @@ pub fn parse_args(tokens: Vec<&lexer::Token>) -> Result, &str> { } _ => { if !last_was_comma { - return Err("Invalid argument separation"); + bail!("Invalid argument separation"); } match token { lexer::Token::Comment(_) => {} + lexer::Token::CharLiteral(x) => { + let y = unescape_string(x); + if y.len() > 1 { + bail!("Char cannot hold string") + } else if let Some(c) = y.chars().next() { + args.push(Arg::Char(c)); + } else { + bail!("Char cannot be empty"); + } + } lexer::Token::StringLiteral(x) => { args.push(Arg::String(x.clone())); } @@ -206,33 +321,30 @@ pub fn parse_args(tokens: Vec<&lexer::Token>) -> Result, &str> { lexer::Token::Number(x) => { args.push(Arg::Number(match num_parse::parse_uint(x) { Some(y) => y, - _ => return Err("Error parsing number"), + None => bail!("Error parsing number"), })); } lexer::Token::LParen => { let mut depth: usize = 1; - args.push( - parse_binary_expression_arg( - &mut iter - .by_ref() - .take_while(|t| match t { - lexer::Token::LParen => { - depth += 1; - true - } - lexer::Token::RParen => { - depth -= 1; - depth != 0 - } - _ => true, - }) - .collect(), - ) - .unwrap(), - ); + args.push(parse_binary_expression_arg( + &mut iter + .by_ref() + .take_while(|t| match t { + lexer::Token::LParen => { + depth += 1; + true + } + lexer::Token::RParen => { + depth -= 1; + depth != 0 + } + _ => true, + }) + .collect(), + )?); } - _ => return Err("Unexpected token for argument"), + _ => bail!("Unexpected token for argument"), } last_was_comma = false; @@ -242,3 +354,14 @@ pub fn parse_args(tokens: Vec<&lexer::Token>) -> Result, &str> { Ok(args) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_unescape_string() { + assert_eq!(unescape_string("\\n test"), "\n test".to_string()); + assert_eq!(unescape_string(""), "".to_string()); + } +} diff --git a/src/lib/assembler.rs b/src/lib/assembler.rs index 867e104..2eb749b 100644 --- a/src/lib/assembler.rs +++ b/src/lib/assembler.rs @@ -1,277 +1,14 @@ -// use itertools::Itertools; -// use radix_fmt::radix; -// use std::collections::HashMap; -// -// use crate::arg; -// use crate::parser; -// -// pub trait ToCode { -// fn to_code(&self) -> String; -// } -// -// pub trait ByteResolvable { -// fn resolve_number(&self, data: &mut T) -> Result; -// -// fn resolve_bytes(&self, data: &mut T) -> Result, String>; -// } -// -// #[derive(Debug)] -// pub struct Macro { -// pub args: Vec, -// pub body: Vec, -// } -// -// #[derive(Debug)] -// pub enum State { -// Default, -// Macro { name: String, depth: usize }, -// } -// -// #[derive(Debug)] -// pub struct Data { -// pub dir: String, -// pub body: Vec, -// pub program: [u8; 32 * 1024], -// pub offset: u16, -// pub contants: HashMap, -// pub macros: HashMap, -// pub state: State, -// } -// -// impl Data { -// pub fn new(dir: String, body: Vec) -> Self { -// Self { -// dir, -// body, -// program: [0; 32 * 1024], -// offset: 0, -// contants: HashMap::new(), -// macros: HashMap::new(), -// state: State::Default, -// } -// } -// } -// -// pub fn assemble(data: &mut Data) -> Result<(), String> { -// for node in data.body.clone() { -// data.contants -// .insert("OFFSET".to_string(), arg::Arg::Number(data.offset)); -// -// match &data.state { -// State::Default => { -// match node { -// parser::ast::Node::Comment(_) => {} -// parser::ast::Node::Label(x) => { -// if data.contants.contains_key(&x) { -// return Err(format!("Label already exists: '{x}'")); -// } -// -// data.contants -// .insert(x.to_string(), arg::Arg::Number(data.offset)); -// } -// parser::ast::Node::Call { name, arg } => { -// let arg_num = match arg { -// Some(x) => x.resolve_number(data).unwrap(), -// _ => 0, -// }; -// -// data.program[data.offset as usize] = match name.as_str() { -// "nop" => 0x00, -// "push" => 0x01, -// "pop" => 0x02, -// "ts" => 0x03, -// "tsr" => 0x04, -// "tss" => 0x05, -// "tlr" => 0x06, -// "tlrc" => 0x07, -// "tls" => 0x08, -// "ld" => 0x09, -// "dbg" => 0x0a, -// "alu" => 0x0b, -// "get" => 0x0c, -// "set" => 0x0d, -// _ => return Err(format!("Unknown opcode: '{name}'")), -// }; -// if arg_num == 0 { -// data.program[data.offset as usize] |= 0b10000000; -// } -// data.offset += 1; -// -// if arg_num != 0 { -// data.program[data.offset as usize] = (arg_num >> 8) as u8; -// data.offset += 1; -// -// data.program[data.offset as usize] = arg_num as u8; -// data.offset += 1; -// } -// } -// parser::ast::Node::MacroCall { name, args } => { -// match name.as_str() { -// "debug" => { -// for arg in args { -// let bytes = arg.resolve_bytes(data).unwrap(); -// -// println!("{}", arg.to_code().replace('\n', "\\n")); -// println!(" => {}", arg.resolve_number(data).unwrap()); -// println!( -// " => [{}]", -// bytes -// .iter() -// .map(|n| { -// let num = radix(*n, 16).to_string(); -// format!( -// "0x{}{}", -// "00".chars() -// .take(2 - num.len()) -// .collect::(), -// num -// ) -// }) -// .join(", ") -// ); -// println!( -// " => \"{}\"", -// String::from_utf8(bytes).unwrap().replace('\n', "\\n") -// ); -// } -// println!("=========="); -// } -// "define" => { -// let name = match &args[0] { -// arg::Arg::Variable(x) | arg::Arg::String(x) => x, -// _ => return Err( -// "First argument of define macro needs to be a literal-like" -// .to_string(), -// ), -// }; -// -// data.contants.insert(name.to_string(), (&args[1]).clone()); -// } -// "macro" => { -// let name = match &args[0] { -// arg::Arg::Variable(x) | arg::Arg::String(x) => x, -// _ => return Err( -// "First argument of define macro needs to be a literal-like" -// .to_string(), -// ), -// }; -// let args = match (&args[1..]) -// .iter() -// // .map(|a| match a { -// // arg::Arg::Variable(x) => Ok(x.clone()), -// // __ => { -// // Err("Macro arguments need to be variables".to_string()) -// // } -// // }) -// .map(|a| { -// if let arg::Arg::Variable(x) = a { -// Ok(x.clone()) -// } else { -// Err("Macro arguments need to be variables".to_string()) -// } -// }) -// .collect::, _>>() -// { -// Ok(x) => x, -// Err(x) => return Err(x), -// }; -// -// data.macros.insert( -// name.clone(), -// Macro { -// args, -// body: Vec::new(), -// }, -// ); -// data.state = State::Macro { -// name: name.clone(), -// depth: 1, -// }; -// } -// "macroend" => return Err("Unexpected macro end".to_string()), -// "org" => { -// data.offset = args[0].resolve_number(data).unwrap(); -// } -// "org_add" => { -// data.offset += args[0].resolve_number(data).unwrap(); -// } -// "org_sub" => { -// data.offset -= args[0].resolve_number(data).unwrap(); -// } -// "bytes" => { -// for arg in args { -// for n in arg.resolve_bytes(data).unwrap() { -// data.program[data.offset as usize] = n; -// data.offset += 1; -// } -// } -// } -// "bw" => { -// let string_arg = args[0].resolve_bytes(data).unwrap(); -// let string = -// String::from_utf8(string_arg).unwrap().replace("\\n", "\n"); -// -// for n in string.bytes() { -// data.program[data.offset as usize] = n; -// data.offset += 1; -// } -// } -// _ => match data.macros.get(&name) { -// Some(m) => { -// dbg!(name, m); -// } -// None => return Err(format!("Unknown macro: '{name}'")), -// }, -// }; -// } -// } -// } -// State::Macro { name, depth } => match &node { -// parser::ast::Node::MacroCall { -// name: node_name, -// args: _, -// } => match node_name.as_str() { -// "macro" => { -// data.state = State::Macro { -// name: name.clone(), -// depth: depth + 1, -// }; -// } -// "macroend" => { -// if *depth - 1 == 0 { -// data.state = State::Default; -// } else { -// data.state = State::Macro { -// name: name.clone(), -// depth: depth - 1, -// }; -// } -// } -// _ => { -// data.macros.get_mut(name).unwrap().body.push(node.clone()); -// } -// }, -// _ => { -// data.macros.get_mut(name).unwrap().body.push(node.clone()); -// } -// }, -// } -// -// if data.offset > (32 * 1024) { -// return Err(format!( -// "Offset out of bounds: 0x{} > 0x8000", -// radix(data.offset, 16), -// )); -// } -// } -// -// Ok(()) -// } - -use anyhow::Result; -use std::collections::HashMap; +use anyhow::{bail, Result}; +use itertools::Itertools; +use radix_fmt::radix; +use rust_embed::RustEmbed; +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::Path; +use std::vec; use crate::arg; +use crate::lexer; use crate::parser; pub trait ToAssembly { @@ -279,27 +16,28 @@ pub trait ToAssembly { } pub trait ByteResolvable { - fn resolve_number(&self, data: &mut T) -> Result; + fn resolve_number(&self, data: &mut T) -> Result; - fn resolve_bytes(&self, data: &mut T) -> Result, String>; -} + fn resolve_bytes(&self, data: &mut T) -> Result>; -#[derive(Debug)] -pub enum ResolveResult { - Resolved(Vec), - Partial(u16), - Unresolved, -} - -impl ResolveResult { - pub fn empty() -> Self { - ResolveResult::Resolved(vec![]) - } + fn resolve_string(&self, data: &mut T) -> Result; } pub const PROGRAM_SIZE: u16 = 32 * 1024; pub type Program = [u8; PROGRAM_SIZE as usize]; +#[derive(RustEmbed)] +#[folder = "lib/"] +#[prefix = "$lib/"] +pub struct Lib; + +#[derive(Debug)] +pub struct Macro { + pub args: Vec, + pub body: parser::ast::Body, +} + +#[derive(Debug)] pub struct Data { pub dir: String, pub ast: parser::ast::AST, @@ -307,6 +45,11 @@ pub struct Data { pub offset: u16, pub body_stack: Vec, pub constants: HashMap, + pub macros: HashMap, + pub includes: HashSet, + pub tmp: Vec, + pub tmp_offset: u16, + pub tmp_enabled: bool, } impl Data { @@ -318,37 +61,342 @@ impl Data { program: [0; PROGRAM_SIZE as usize], offset: 0, constants: HashMap::new(), + macros: HashMap::new(), + includes: HashSet::new(), + tmp: vec![], + tmp_offset: 0, + tmp_enabled: false, } } - pub fn resolve_node(&mut self, node: parser::ast::Node) -> ResolveResult { + pub fn resolve_node(&mut self) -> Result>> { + let node = match self.body_stack.pop() { + Some(x) => x, + None => bail!("Body stack empty. No more nodes to resolve"), + }; + match node { - parser::ast::Node::Comment(_) => ResolveResult::empty(), + parser::ast::Node::Comment(_) => Ok(None), parser::ast::Node::Label(x) => { self.constants.insert(x, arg::Arg::Number(self.offset)); - ResolveResult::empty() + Ok(None) } parser::ast::Node::Call { name, arg } => { - dbg!(name, arg); + let opcode: u8 = match name.as_str() { + "nop" => 0x00, + "push" => 0x01, + "pop" => 0x02, + "ts" => 0x03, + "tsr" => 0x04, + "tss" => 0x05, + "tlr" => 0x06, + "tlrc" => 0x07, + "tls" => 0x08, + "ld" => 0x09, + "dbg" => 0x0a, + "alu" => 0x0b, + "get" => 0x0c, + "set" => 0x0d, + _ => bail!("Unknown opcode: {}", name), + }; + let a = match arg { + Some(x) => x.resolve_number(self)?, + None => 0, + }; - ResolveResult::empty() + if a == 0 { + Ok(Some(vec![opcode | 0b10000000])) + } else { + Ok(Some(vec![opcode, (a >> 8) as u8, a as u8])) + } } - parser::ast::Node::MacroCall { name, args } => { - dbg!(name, args); + parser::ast::Node::MacroCall { name, args } => match name.as_str() { + "debug" => { + for arg in args { + let assembly = arg.to_assembly().replace('\n', "\\n"); + let num = arg.resolve_number(self)?; + let bytes = arg.resolve_bytes(self)?; - ResolveResult::empty() - } + println!("{}", assembly); + let bin_num = radix(num, 2).to_string(); + println!( + " => 0b{}{}", + "0000000000000000"[..16 - bin_num.len()].to_owned(), + bin_num + ); + println!(" => {}", num); + let hex_num = radix(num, 16).to_string(); + println!( + " => 0x{}{}", + "0000"[..4 - hex_num.len()].to_owned(), + hex_num + ); + println!( + " => [{}]", + bytes + .iter() + .map(|n| { + let num = radix(*n, 16).to_string(); + + format!( + "0x{}{}", + "00".chars().take(2 - num.len()).collect::(), + num + ) + }) + .join(", ") + ); + } + + Ok(None) + } + "define" => { + let name = match &args[0] { + arg::Arg::Variable(x) | arg::Arg::String(x) => x, + _ => { + bail!("First argument of define macro needs to be a variable") + } + }; + self.constants.insert(name.clone(), (&args[1]).clone()); + + Ok(None) + } + "define_num_nl" => { + let name = match &args[0] { + arg::Arg::Variable(x) => x, + _ => { + bail!("First argument of define macro needs to be a variable") + } + }; + let a = arg::Arg::Number(args[1].resolve_number(self)?); + self.constants.insert(name.clone(), a); + + Ok(None) + } + "macro" => { + let name = match &args[0] { + arg::Arg::Variable(x) | arg::Arg::String(x) => x, + _ => { + bail!("First argument of define macro needs to be a literal-like value") + } + }; + let args = (&args[1..]) + .iter() + .map(|a| match a { + arg::Arg::Variable(x) => Ok(x.clone()), + _ => bail!("Macro arguments need to be variables"), + }) + .collect::>>()?; + + let mut depth: usize = 1; + let body: Vec<_> = self + .body_stack + .iter() + .rev() + .take_while(|n| match n { + parser::ast::Node::MacroCall { name, args: _ } => match name.as_str() { + "macro" => { + depth += 1; + true + } + "endmacro" => { + depth -= 1; + depth != 0 + } + _ => true, + }, + _ => true, + }) + .cloned() + .collect(); + self.body_stack + .drain((self.body_stack.len() - body.len() - 1)..) + .for_each(drop); + self.macros.insert(name.clone(), Macro { args, body }); + + Ok(None) + } + "if" => { + let cond = (args[0].resolve_number(self)? & 1) == 1; + dbg!(cond); + + let mut if_depth: usize = 1; + let body: Vec<_> = self + .body_stack + .iter() + .rev() + .take_while(|n| match n { + parser::ast::Node::MacroCall { name, args: _ } => match name.as_str() { + "if" => { + if_depth += 1; + true + } + "endif" => { + if_depth -= 1; + if_depth != 0 + } + _ => true, + }, + _ => true, + }) + .cloned() + .collect(); + self.body_stack + .drain((self.body_stack.len() - body.len() - 1)..) + .for_each(drop); + dbg!(&body); + + let mut iter = body.iter().enumerate(); + if let Some(pos) = loop { + if let Some(pos) = iter.next() { + if let parser::ast::Node::MacroCall { name, args: _ } = pos.1 { + match name.as_str() { + "if" => { + break None; + } + "else" => { + break Some(pos); + } + _ => {} + } + } + } else { + break None; + } + } { + self.body_stack.extend(if cond { + body[..pos.0].to_vec() + } else { + body[(pos.0 + 1)..].to_vec() + }); + } else if cond { + self.body_stack.extend(body.into_iter().rev()); + } + + Ok(None) + } + "org" => { + self.offset = args[0].resolve_number(self)?; + + Ok(None) + } + "org_add" => { + self.offset = self.offset.wrapping_add(args[0].resolve_number(self)?); + + Ok(None) + } + "org_sub" => { + self.offset = self.offset.wrapping_sub(args[0].resolve_number(self)?); + + Ok(None) + } + "bytes" => { + let mut data: Vec = vec![]; + for arg in args { + data.append(&mut arg.resolve_bytes(self)?); + } + + if data.is_empty() { + Ok(None) + } else { + Ok(Some(data)) + } + } + "embed" => { + let arg = args[0].resolve_string(self)?; + let data = if Path::new(&arg).starts_with("$lib") { + match Lib::get(arg.as_str()) { + Some(x) => x.data.to_vec(), + None => bail!("Virtual lib file couldn't be found with path: {}", arg), + } + } else { + let path = Path::new(&self.dir).join(arg); + fs::read(path)? + }; + + Ok(Some(data)) + } + "requires" => { + let arg = args[0].resolve_string(self)?; + if !self.includes.contains(&arg) { + bail!("File requires include of not included file: {}", arg); + } + Ok(None) + } + "include" => { + let arg = args[0].resolve_string(self)?; + let source = if Path::new(&arg).starts_with("$lib") { + match Lib::get(arg.as_str()) { + Some(x) => std::str::from_utf8(&x.data)?.to_string(), + None => bail!("Virtual lib file couldn't be found with path: {}", arg), + } + } else { + let path = Path::new(&self.dir).join(&arg); + fs::read_to_string(path)? + }; + + self.includes.insert(arg); + let body = parser::parse(lexer::lex(source)?)?.body; + self.body_stack + .extend(body.into_iter().rev().collect::>()); + + Ok(None) + } + _ => match self.macros.get(&name) { + Some(m) => { + if m.args.len() != args.len() { + bail!( + "Macro call argument signature does not match: (.macro {name}{macro_args}) != (.{name}{call_args})", + macro_args = if m.args.is_empty() { + "".to_string() + } else { + format!(" {}", m.args.join(", ")) + }, + call_args = if args.is_empty() { + "".to_string() + } else { + format!(" {}", args.iter().map(|a| a.to_assembly()).join(", ")) + } + ); + } + // dbg!(&args); + for (i, arg) in args.iter().enumerate() { + self.constants.insert(m.args[i].clone(), arg.clone()); + } + self.body_stack + .append(&mut m.body.iter().rev().cloned().collect()); + + Ok(None) + } + None => bail!("Unknown macro: {}", name), + }, + }, } } pub fn assemble(&mut self) -> Result { - while let Some(node) = self.body_stack.pop() { - let res = self.resolve_node(node); - dbg!(res); + let offset_name = "OFFSET".to_string(); + while !self.body_stack.is_empty() { + self.constants + .insert(offset_name.clone(), arg::Arg::Number(self.offset)); + match self.resolve_node()? { + Some(x) => { + if self.offset + (x.len() as u16) > 32 * 1024 { + bail!( + "Offset out of bounds: 0x{} > 0x8000", + radix(self.offset, 16) + ); + } + for byte in x { + self.program[self.offset as usize] = byte; + self.offset += 1; + } + } + None => {} + } } Ok(self.program) } } - diff --git a/src/lib/emulator.rs b/src/lib/emulator.rs index 77e5d5d..0a059b8 100644 --- a/src/lib/emulator.rs +++ b/src/lib/emulator.rs @@ -1,3 +1,4 @@ +use anyhow::{bail, Result}; use itertools::Itertools; use radix_fmt::radix; use std::cmp::Ordering; @@ -6,9 +7,7 @@ use std::io::{self, Write}; #[derive(Debug)] pub struct Data { program: [u8; 32 * 1024], - pub tmp: u16, - pub reg_pc: u16, pub reg_opc: u8, pub reg_arg: u16, @@ -18,10 +17,8 @@ pub struct Data { pub reg_b: u16, pub reg_c: u16, pub reg_d: u16, - stack: [u16; 8 * 1024], memory: [u16; 16 * 1024], - term: console::Term, } @@ -29,9 +26,7 @@ impl Data { pub fn new(program: [u8; 32 * 1024]) -> Self { Self { program, - tmp: 0, - reg_pc: 0, reg_opc: 0, reg_arg: 0, @@ -41,44 +36,43 @@ impl Data { reg_b: 0, reg_c: 0, reg_d: 0, - stack: [0; 8 * 1024], memory: [0; 16 * 1024], - term: console::Term::stdout(), } } - pub fn get_memory(&self, address: u16) -> u16 { + pub fn get_memory(&self, address: u16) -> Result { if address < (32 * 1024) { match self.program.get(address as usize) { - Some(val) => *val as u16, - None => 0, + Some(val) => Ok(*val as u16), + None => Ok(0), } } else if address < (40 * 1024) { - self.stack[(address - (32 * 1024)) as usize] + Ok(self.stack[(address - (32 * 1024)) as usize]) } else if address < (56 * 1024) { - self.memory[(address - (40 * 1024)) as usize] + Ok(self.memory[(address - (40 * 1024)) as usize]) } else if address == (56 * 1024 + 2) { - self.term.read_char().unwrap() as u16 + Ok(self.term.read_char()? as u16) } else { - 0 + Ok(0) } } - pub fn set_memory(&mut self, address: u16, value: u16) { - // if address >= (32 * 1024) && address < (40 * 1024) { + pub fn set_memory(&mut self, address: u16, value: u16) -> Result<()> { if ((32 * 1024)..(40 * 1024)).contains(&address) { self.stack[(address - (32 * 1024)) as usize] = value; - } else if address < (40 * 1024) { + } else if address < (56 * 1024) { self.memory[(address - (40 * 1024)) as usize] = value; } else if address == (56 * 1024) { print!("{value}"); - io::stdout().flush().unwrap(); + io::stdout().flush()?; } else if address == (56 * 1024 + 1) { print!("{}", char::from(value as u8)); - io::stdout().flush().unwrap(); + io::stdout().flush()?; } + + Ok(()) } pub fn get_register(&self, register: u8) -> u16 { @@ -100,54 +94,45 @@ impl Data { match register { 0x0 => { self.reg_pc = value; - value } 0x1 => { self.reg_opc = value as u8; - value & 0xff } 0x2 => { self.reg_arg = value; - value } 0x3 => { self.reg_s = value as u8; - value & 0xff } 0x4 => { self.reg_sp = value; - value } 0x5 => { self.reg_a = value; - value } 0x6 => { self.reg_b = value; - value } 0x7 => { self.reg_c = value; - value } 0x8 => { self.reg_d = value; - value } _ => 0, } } - fn alu(&mut self, operation: u8) -> Result<(), String> { + fn alu(&mut self, operation: u8) -> Result<()> { match operation { 0x00 => { self.tmp = !self.reg_a; @@ -203,57 +188,49 @@ impl Data { } } 0x0b => { - self.tmp = bool_to_num(self.reg_a == self.reg_b); + self.tmp = (self.reg_a == self.reg_b) as u16; } 0x0c => { - self.tmp = bool_to_num(self.reg_a < self.reg_b); + self.tmp = (self.reg_a < self.reg_b) as u16; } 0x0d => { - self.tmp = bool_to_num(self.reg_a > self.reg_b); + self.tmp = (self.reg_a > self.reg_b) as u16; } 0x0e => { - self.tmp = bool_to_num(self.reg_a <= self.reg_b); + self.tmp = (self.reg_a <= self.reg_b) as u16; } 0x0f => { - self.tmp = bool_to_num(self.reg_a >= self.reg_b); + self.tmp = (self.reg_a >= self.reg_b) as u16; } 0x10 => { - self.tmp = bool_to_num(self.reg_a == 1); + self.tmp = (self.reg_a & 1 == 1) as u16; } 0x11 => { - self.tmp = bool_to_num(self.reg_a != 1); + self.tmp = (self.reg_a & 1 != 1) as u16; } 0x12 => { self.tmp = rand::random(); } - _ => return Err(format!("Invalid ALU operation: 0x{}", radix(operation, 16))), + _ => bail!("Invalid ALU operation: 0x{}", radix(operation, 16)), } Ok(()) } } -pub fn bool_to_num(x: bool) -> u16 { - if x { - 1 - } else { - 0 - } -} - -pub fn emulate(data: &mut Data) -> Result<(), String> { +pub fn emulate(data: &mut Data) -> Result<()> { while data.reg_pc != 0xffff { - data.reg_opc = data.get_memory(data.reg_pc) as u8; + data.reg_opc = data.get_memory(data.reg_pc)? as u8; data.reg_pc = data.reg_pc.wrapping_add(1); if data.reg_opc >> 7 == 1 { data.reg_opc &= 0b0111111; data.reg_arg = 0; } else { - data.reg_arg = data.get_memory(data.reg_pc) << 8; + data.reg_arg = data.get_memory(data.reg_pc)? << 8; data.reg_pc = data.reg_pc.wrapping_add(1); - data.reg_arg |= data.get_memory(data.reg_pc); + data.reg_arg |= data.get_memory(data.reg_pc)?; data.reg_pc = data.reg_pc.wrapping_add(1); } @@ -299,19 +276,19 @@ pub fn emulate(data: &mut Data) -> Result<(), String> { data.stack.iter().take(data.reg_sp as usize).join(", ") ); print!("Press enter to continue execution...",); - io::stdout().flush().unwrap(); - data.term.read_line().unwrap(); + io::stdout().flush()?; + data.term.read_line()?; } 0x0b => { - data.alu(data.tmp as u8).unwrap(); + data.alu(data.tmp as u8)?; } 0x0c => { - data.tmp = data.get_memory(data.tmp); + data.tmp = data.get_memory(data.tmp)?; } 0x0d => { - data.set_memory(data.tmp, data.reg_a); + data.set_memory(data.tmp, data.reg_a)?; } - _ => return Err(format!("Invalid opcode: 0x{}", radix(data.reg_opc, 16))), + _ => bail!("Invalid opcode: 0x{}", radix(data.reg_opc, 16)), } } diff --git a/src/lib/lexer.rs b/src/lib/lexer.rs index 02402b9..9f319b2 100644 --- a/src/lib/lexer.rs +++ b/src/lib/lexer.rs @@ -1,10 +1,13 @@ -use crate::assembler; -use itertools::{Itertools, PeekingNext}; +use anyhow::{bail, Result}; +use itertools::Itertools; -#[derive(Debug)] +use crate::assembler; + +#[derive(Debug, PartialEq)] pub enum Token { Comment(String), + CharLiteral(String), StringLiteral(String), MacroLiteral(String), Literal(String), @@ -15,13 +18,30 @@ pub enum Token { LParen, RParen, - Assign, - + Not, + And, + Nand, + Or, + Nor, + Xor, + Xnor, + Lsh, + Rsh, Add, Sub, Mul, Div, Pow, + Cmp, + Eq, + Neq, + Lt, + Gt, + Leq, + Geq, + Bol, + Inv, + Rnd, Newline(String), Whitespace(String), @@ -30,8 +50,9 @@ pub enum Token { impl assembler::ToAssembly for Token { fn to_assembly(&self) -> String { match self { - Token::Comment(x) => format!(";{x}"), - Token::StringLiteral(x) => format!("\"{x}\""), + Token::Comment(x) => format!(";{}", x), + Token::CharLiteral(x) => format!("'{}'", x), + Token::StringLiteral(x) => format!("\"{}\"", x), Token::MacroLiteral(x) => x.clone(), Token::Literal(x) => x.clone(), Token::Number(x) => x.clone(), @@ -39,127 +60,290 @@ impl assembler::ToAssembly for Token { Token::Colon => ":".to_string(), Token::LParen => "(".to_string(), Token::RParen => ")".to_string(), - Token::Assign => "=".to_string(), + Token::Not => "~".to_string(), + Token::And => "&".to_string(), + Token::Nand => "~&".to_string(), + Token::Or => "|".to_string(), + Token::Nor => "~|".to_string(), + Token::Xor => "^".to_string(), + Token::Xnor => "~^".to_string(), + Token::Lsh => "<<".to_string(), + Token::Rsh => ">>".to_string(), Token::Add => "+".to_string(), Token::Sub => "-".to_string(), Token::Mul => "*".to_string(), Token::Div => "/".to_string(), Token::Pow => "**".to_string(), + Token::Cmp => "<=>".to_string(), + Token::Eq => "==".to_string(), + Token::Neq => "!=".to_string(), + Token::Lt => "<".to_string(), + Token::Gt => ">".to_string(), + Token::Leq => "<=".to_string(), + Token::Geq => ">=".to_string(), + Token::Bol => "!!".to_string(), + Token::Inv => "!".to_string(), + Token::Rnd => "?".to_string(), Token::Newline(x) | Token::Whitespace(x) => x.clone(), } } } -pub fn lex(source: String) -> Result, String> { +pub fn lex(source: String) -> Result> { let mut chars = source.chars().peekable(); - let mut tokens = Vec::::new(); + let mut tokens: Vec = vec![]; while let Some(&ch) = chars.peek() { - match ch { - // ';' => { - // chars.next(); - // chars.next_if(|c| *c == ';'); - - // tokens.push(Token::Comment( - // chars.peeking_take_while(|c| *c != '\n').collect::(), - // )); - // } - ';' | '@' => { + tokens.push(match ch { + ';' => { chars.next(); - chars.next_if(|c| *c == ';' || *c == '@'); - - tokens.push(Token::Comment( - chars.peeking_take_while(|c| *c != '\n').collect::(), - )); + chars.next_if(|c| *c == ';'); + Token::Comment(chars.peeking_take_while(|c| *c != '\n').collect()) + } + '@' => { + chars.next(); + Token::Comment(chars.peeking_take_while(|c| *c != '\n').collect()) + } + '#' => { + chars.next(); + Token::Comment(chars.peeking_take_while(|c| *c != '\n').collect()) + } + '\'' => { + chars.next(); + Token::CharLiteral(chars.by_ref().take_while(|c| *c != '\'').collect()) } '"' => { chars.next(); - tokens.push(Token::StringLiteral( - chars.by_ref().take_while(|c| *c != '"').collect::(), - )); + Token::StringLiteral(chars.by_ref().take_while(|c| *c != '"').collect()) } '.' => { chars.next(); - tokens.push(Token::MacroLiteral(format!( + Token::MacroLiteral(format!( ".{}", chars .peeking_take_while(|c| c.is_alphabetic() || c.is_numeric() || *c == '_') .collect::() - ))); + )) } ch if ch.is_alphabetic() => { let name: String = chars .peeking_take_while(|c| c.is_alphabetic() || c.is_numeric() || *c == '_') .collect(); - tokens.push(Token::Literal(name)); - } - ch if ch.is_numeric() => { - tokens.push(Token::Number( - chars - .peeking_take_while(|c| c.is_alphanumeric() || *c == '_') - .collect::(), - )); + Token::Literal(name) } + ch if ch.is_numeric() => Token::Number( + chars + .peeking_take_while(|c| c.is_alphanumeric() || *c == '_') + .collect(), + ), ',' => { - tokens.push(Token::Comma); chars.next(); + Token::Comma } ':' => { - tokens.push(Token::Colon); chars.next(); + Token::Colon } '(' => { - tokens.push(Token::LParen); chars.next(); + Token::LParen } ')' => { - tokens.push(Token::RParen); chars.next(); + Token::RParen } - '=' => { - tokens.push(Token::Assign); + '~' => { chars.next(); + if let Some(c) = chars.peek() { + match c { + '&' => { + chars.next(); + Token::Nand + } + '|' => { + chars.next(); + Token::Nor + } + '^' => { + chars.next(); + Token::Xnor + } + _ => Token::Not, + } + } else { + Token::Not + } + } + '&' => { + chars.next(); + Token::And + } + '|' => { + chars.next(); + Token::Or + } + '^' => { + chars.next(); + Token::Xor + } + '<' => { + chars.next(); + match chars.peek() { + Some('<') => { + chars.next(); + Token::Lsh + } + Some('=') => { + chars.next(); + match chars.peek() { + Some('>') => { + chars.next(); + Token::Cmp + } + _ => Token::Leq, + } + } + _ => Token::Lt, + } + } + '>' => { + chars.next(); + match chars.peek() { + Some('>') => { + chars.next(); + Token::Rsh + } + Some('=') => { + chars.next(); + Token::Geq + } + _ => Token::Gt, + } } '+' => { - tokens.push(Token::Add); chars.next(); + Token::Add } '-' => { - tokens.push(Token::Sub); chars.next(); + Token::Sub } '*' => { chars.next(); - tokens.push(if chars.peeking_next(|c| *c == '*').is_some() { + if let Some('*') = chars.peek() { + chars.next(); Token::Pow } else { Token::Mul - }); + } } '/' => { - tokens.push(Token::Div); chars.next(); + Token::Div } - '\n' => { - tokens.push(Token::Newline( - chars.peeking_take_while(|c| *c == '\n').collect::(), - )); + '=' => { + chars.next(); + if let Some('=') = chars.peek() { + chars.next(); + } + Token::Eq } - ch if ch.is_whitespace() => { - tokens.push(Token::Whitespace( - chars - .peeking_take_while(|c| c.is_whitespace() && *c != '\n') - .collect::(), - )); + '!' => { + chars.next(); + match chars.peek() { + Some('!') => { + chars.next(); + Token::Bol + } + Some('=') => { + chars.next(); + Token::Neq + } + _ => Token::Inv, + } } - _ => { - // tokens.push(Token::Error(ch.to_string())); - // chars.next(); - return Err(format!("Unexpected token: '{ch}'")); + '?' => { + chars.next(); + Token::Rnd } - } + '\n' => Token::Newline(chars.peeking_take_while(|c| *c == '\n').collect()), + ch if ch.is_whitespace() => Token::Whitespace( + chars + .peeking_take_while(|c| c.is_whitespace() && *c != '\n') + .collect(), + ), + _ => bail!("Unexpected token: {}", ch), + }); } Ok(tokens) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::assembler::ToAssembly; + + #[test] + fn test_token_to_assembly() { + assert_eq!( + Token::Comment(" \"main function\" like definition macro".to_string()).to_assembly(), + "; \"main function\" like definition macro".to_string() + ); + assert_eq!( + Token::CharLiteral("\\n".to_string()).to_assembly(), + "'\\n'".to_string() + ); + assert_eq!( + Token::MacroLiteral("xyz".to_string()).to_assembly(), + "xyz".to_string() + ); + assert_eq!( + Token::Literal("xkcd".to_string()).to_assembly(), + "xkcd".to_string() + ); + assert_eq!( + Token::Newline("\n".to_string()).to_assembly(), + "\n".to_string() + ); + assert_eq!( + Token::Whitespace(" ".to_string()).to_assembly(), + " ".to_string() + ); + } + + #[test] + fn test_lex() -> Result<()> { + assert_eq!( + lex(";; test".to_string())?, + vec![Token::Comment(" test".to_string())] + ); + assert_eq!( + lex("@ test".to_string())?, + vec![Token::Comment(" test".to_string())] + ); + assert_eq!( + lex("# test".to_string())?, + vec![Token::Comment(" test".to_string())] + ); + assert_eq!( + lex("'\\n'".to_string())?, + vec![Token::CharLiteral("\\n".to_string())] + ); + assert_eq!( + lex("\"test\"".to_string())?, + vec![Token::StringLiteral("test".to_string())] + ); + assert_eq!( + lex(".debug CORE_REG_PC".to_string())?, + vec![ + Token::MacroLiteral(".debug".to_string()), + Token::Whitespace(" ".to_string()), + Token::Literal("CORE_REG_PC".to_string()) + ] + ); + + Ok(()) + } +} diff --git a/src/lib/lib.rs b/src/lib/lib.rs index fb1b1c8..43db010 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -1,5 +1,5 @@ +pub mod lexer; +pub mod parser; pub mod arg; pub mod assembler; pub mod emulator; -pub mod lexer; -pub mod parser; diff --git a/src/lib/parser.rs b/src/lib/parser.rs index cf87310..01a2f16 100644 --- a/src/lib/parser.rs +++ b/src/lib/parser.rs @@ -26,8 +26,7 @@ pub fn parse(tokens: Vec) -> Result { .take_while(|t| !matches!(t, lexer::Token::Newline(_))) .filter(|t| !matches!(t, lexer::Token::Whitespace(_))) .collect(), - ) - .unwrap(), + )?, }); } lexer::Token::Literal(x) => { diff --git a/src/lib/parser/ast.rs b/src/lib/parser/ast.rs index fd5367b..0042917 100644 --- a/src/lib/parser/ast.rs +++ b/src/lib/parser/ast.rs @@ -27,7 +27,10 @@ impl assembler::ToAssembly for Node { if args.is_empty() { format!(".{name}") } else { - format!(".{name} {}", args.iter().map(|a| a.to_assembly()).join(", ")) + format!( + ".{name} {}", + args.iter().map(|a| a.to_assembly()).join(", ") + ) } } }