diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index bd2bae1..0000000 --- a/.drone.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -kind: pipeline -type: docker -name: default - -steps: - - name: build - image: crystallang/crystal:1.5-alpine - commands: - - shards install --production - - shards build --release --static diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 163eb75..0000000 --- a/.editorconfig +++ /dev/null @@ -1,9 +0,0 @@ -root = true - -[*.cr] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore index d22cc77..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1 @@ -/docs/ -/lib/ -/bin/ -/.shards/ -*.dwarf -./examples/*.bin -examples/*.bin -/.vscode/ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f4258b0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,422 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "3.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8b79fe3946ceb4a0b1c080b4018992b8d27e9ff363644c1c9b6387c854614d" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hence" +version = "0.1.0" +dependencies = [ + "clap", + "itertools", + "num-parse", + "radix_fmt", + "rand", + "rhexdump", +] + +[[package]] +name = "hencelisp" +version = "0.1.0" +dependencies = [ + "clap", + "hence", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-parse" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c794eedf4b22ca525c2c4602ea17ccd71f69eaaacf546551aba127b2c396a94" +dependencies = [ + "num", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "os_str_bytes" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_fmt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rhexdump" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..327d52a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["hence", "hencelisp"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d380e85..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Dominic Grimm - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index b2e0b1f..31b3076 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,29 @@ # hence -A stack based CPU with microcode and custom firmware support. +# Registers + +| Name | Description | Size | +| ----- | --------------- | ---- | +| `pc` | Program counter | | +| `opc` | | | +| | | | + +# Opcodes + +| Index | Name | Description | Arguments | +| ------ | ------ | ------------------------------------------ | --------- | +| `0x00` | `nop` | No operation | | +| `0x01` | `push` | Push to stack | | +| `0x02` | `pop` | Pops top of stack | | +| `0x03` | `ts` | Store value into `tmp` | | +| `0x04` | `tsr` | Store register's value into `tmp` | | +| `0x05` | `tss` | Stores top of stack into `tmp` | | +| `0x06` | `tlr` | Load `tmp`'s value into register | | +| `0x07` | `tls` | Push value of `tmp` to stack | | +| `0x08` | `ld` | Loads top of stack into register | | +| `0x09` | `st` | Loads registers's value onto top of stack | | +| `0x0a` | `dbg` | Debug | | +| `0x0b` | `alu` | Runs ALU | | +| `0x0c` | `at` | Runs ALU with `tmp`'s value as operatorion | | +| `0x0d` | `get` | Sets `tmp` to memory at address in `tmp` | | +| `0x0e` | `set` | Sets memory to value at specific address | | diff --git a/examples/add.asm b/examples/add.asm deleted file mode 100644 index a2489d6..0000000 --- a/examples/add.asm +++ /dev/null @@ -1,44 +0,0 @@ -PROGRAM_SIZE = (32 * 1024) -INSTRUCTION_SIZE = 3 - -main_jump = (PROGRAM_SIZE - INSTRUCTION_SIZE - INSTRUCTION_SIZE) - -.org 0x0000 - -push main_jump -jmp - -data: - answer: - ANSWER_SIZE = 52 - .bw "The answer to life, the universe, and everything is ", 0, ANSWER_SIZE - -main: - push answer - loop: - dup - load - emit - - inc - dup - push (answer + ANSWER_SIZE) - neq - push loop - jif - drop - - push 40 - push 2 - add - print - - push "!" - emit - push "\n" - emit -stp - -.org main_jump - push main - jmp diff --git a/examples/hello_world.asm b/examples/hello_world.asm deleted file mode 100644 index d6600e4..0000000 --- a/examples/hello_world.asm +++ /dev/null @@ -1,41 +0,0 @@ -; Constants -PROGRAM_SIZE = (32 * 1024) -INSTRUCTION_SIZE = 3 - -main_jump = (PROGRAM_SIZE - INSTRUCTION_SIZE - INSTRUCTION_SIZE) - -; Base organisation / offset -.org 0x0000 - -; Static data -push main_jump -jmp ; Skip data section as following data is not program code - -data: - ; Message data - msg: - MSG_SIZE = 12 ; Define size of message - .bw "Hello World!", 0, MSG_SIZE ; Embed byte words in binary - -; Main label -main: - push msg - loop: - dup - load - emit - - inc - dup - push (msg + MSG_SIZE) - neq - push loop - jif - push "\n" - emit -stp - -; Jump to main label -.org main_jump - push main - jmp diff --git a/examples/test.asm b/examples/test.asm new file mode 100644 index 0000000..8f9baf9 --- /dev/null +++ b/examples/test.asm @@ -0,0 +1,22 @@ +main: + push 40 + push (42 - 40) + + tss + tlr 0x5 + pop + tss + tlr 0x6 + pop + + alu 0x06 + tls + + alu 0x12 + tlr 0x5 + tls + + dbg + +ts 0xffff +tlr 0x0 diff --git a/examples/test.lisp b/examples/test.lisp new file mode 100644 index 0000000..4bb6275 --- /dev/null +++ b/examples/test.lisp @@ -0,0 +1,6 @@ +(module test + "Main module" + (defun main () + "Program entrypoint" + (let ((x (+ 40 2)))) + (debug x))) diff --git a/hence/.gitignore b/hence/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/hence/.gitignore @@ -0,0 +1 @@ +/target diff --git a/hence/Cargo.toml b/hence/Cargo.toml new file mode 100644 index 0000000..5f58bf7 --- /dev/null +++ b/hence/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "hence" +version = "0.1.0" +edition = "2021" + +[lib] +name = "hence" +path = "src/lib/lib.rs" + +[[bin]] +name = "hence" +path = "src/bin/main.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +itertools = "0.10.2" +num-parse = "0.1.2" +clap = { version = "3.2.12", features = ["derive"] } +rhexdump = "0.1.1" +radix_fmt = "1" +rand = "0.8.5" diff --git a/hence/src/bin/main.rs b/hence/src/bin/main.rs new file mode 100644 index 0000000..9d6043c --- /dev/null +++ b/hence/src/bin/main.rs @@ -0,0 +1,93 @@ +use clap::{Parser, Subcommand}; +use std::fs; +use std::fs::File; +use std::io::{BufReader, Read, Write}; +use std::path::Path; + +use hence::*; + +#[derive(Debug, Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + commands: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + #[clap(about = "Lexes source code and outputs tokens")] + Lex { + #[clap(value_parser)] + src: String, + }, + #[clap(about = "Parses source code and outputs AST")] + Parse { + #[clap(value_parser)] + src: String, + }, + #[clap(about = "Compiles binary from source code")] + Assemble { + #[clap(value_parser)] + src: String, + #[clap(value_parser)] + bin: Option, + #[clap(long, action)] + dump: bool, + }, + #[clap(about = "Emulates binary")] + Run { + #[clap(value_parser)] + bin: String, + }, +} + +fn main() { + let args = Cli::parse(); + match args.commands { + Commands::Lex { src } => { + let assembly = fs::read_to_string(src).unwrap(); + let tokens = lexer::lex(assembly).unwrap(); + dbg!(tokens); + } + Commands::Parse { src } => { + let assembly = fs::read_to_string(src).unwrap(); + let tokens = lexer::lex(assembly).unwrap(); + let ast = parser::parse(tokens).unwrap(); + dbg!(ast); + } + 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 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"), + }); + assembler::assemble(ast, &mut data).unwrap(); + + match bin { + Some(x) => { + File::create(x).unwrap().write_all(&data.program).unwrap(); + } + _ => {} + } + if dump { + println!("{}", rhexdump::hexdump(&data.program)); + } + } + 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(); + 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 mut data = emulator::Data::new(program); + + emulator::emulate(&mut data).unwrap(); + } + } +} diff --git a/hence/src/lib/arg.rs b/hence/src/lib/arg.rs new file mode 100644 index 0000000..88531d3 --- /dev/null +++ b/hence/src/lib/arg.rs @@ -0,0 +1,249 @@ +use crate::arg; +use crate::assembler; +use crate::lexer; + +#[derive(Debug, Clone)] +pub enum Arg { + String(String), + Number(u16), + Variable(String), + BinaryExpression { + left: Box, + right: Box, + op: BinaryExpressionOperator, + }, +} + +impl assembler::ToAssembly for Arg { + fn to_assembly(&self) -> String { + match self { + 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() + ) + } + } + } +} + +impl assembler::ByteResolvable for Arg { + fn resolve_number(&self, data: &mut assembler::Data) -> Result { + match self { + Arg::String(x) => { + if x.len() == 1 { + Ok(x.as_bytes()[0] as u16) + } else { + let bytes = x.as_bytes(); + + Ok(((bytes[0] as u16) << 8) | bytes[1] as u16) + } + } + Arg::Number(x) => Ok(*x), + Arg::Variable(x) => { + let mut name = x.clone(); + let mut arg: Option = None; + + loop { + arg = match data.contants.get(&name) { + Some(a) => Some(a.clone()), + _ => None, + }; + + match arg { + Some(a) => { + arg = Some(a.clone()); + match a { + arg::Arg::Variable(n) => { + name = n; + } + _ => break Ok(a.resolve_number(data).unwrap()), + }; + } + _ => return Err(format!("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(); + + Ok(match op { + 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), + }) + } + } + } + + fn resolve_bytes(&self, data: &mut assembler::Data) -> Result, String> { + match self { + Arg::String(x) => Ok(x.bytes().collect()), + Arg::Number(x) => Ok(vec![(*x >> 8) as u8, *x as u8]), + Arg::Variable(x) => { + let mut name = x.clone(); + let mut arg: Option = None; + + loop { + dbg!(&name); + arg = match data.contants.get(&name) { + Some(a) => Some(a.clone()), + _ => None, + }; + dbg!(&arg); + + match arg { + Some(a) => { + arg = Some(a.clone()); + match a { + arg::Arg::Variable(n) => { + name = n; + } + _ => break Ok(a.resolve_bytes(data).unwrap()), + }; + } + _ => return Err(format!("Variable does not exist: '{name}'")), + } + } + } + Arg::BinaryExpression { + left: _, + right: _, + op: _, + } => { + let x = self.resolve_number(data).unwrap(); + + Ok(vec![(x >> 8) as u8, x as u8]) + } + } + } +} + +#[derive(Debug, Clone)] +pub enum BinaryExpressionOperator { + Add, + Sub, + Mul, + Div, + Pow, +} + +impl assembler::ToAssembly for BinaryExpressionOperator { + fn to_assembly(&self) -> String { + match self { + BinaryExpressionOperator::Add => "+".to_string(), + BinaryExpressionOperator::Sub => "-".to_string(), + BinaryExpressionOperator::Mul => "*".to_string(), + BinaryExpressionOperator::Div => "/".to_string(), + BinaryExpressionOperator::Pow => "**".to_string(), + } + } +} + +pub fn parse_binary_operation( + token: &lexer::Token, +) -> Result { + match token { + 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), + _ => Err("Invalid binary expression operator"), + } +} + +pub fn parse_binary_expression_arg(tokens: &mut Vec<&&lexer::Token>) -> Result { + if tokens.is_empty() { + return Err("Malformed binary expression"); + } + + let mut args: Vec<&&lexer::Token> = 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(), + }; + + while tokens.len() != 0 { + 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(), + } + } + + Ok(bin_expr) +} + +pub fn parse_args(tokens: Vec<&lexer::Token>) -> Result, &str> { + let mut iter = tokens.iter(); + let mut args: Vec = Vec::new(); + let mut last_was_comma = true; + + while let Some(token) = iter.next() { + match token { + lexer::Token::Comma => { + last_was_comma = true; + } + _ => { + if !last_was_comma { + return Err("Invalid argument separation"); + } + + match token { + lexer::Token::StringLiteral(x) => { + args.push(Arg::String(x.clone())); + } + lexer::Token::Literal(x) => { + args.push(Arg::Variable(x.clone())); + } + lexer::Token::Number(x) => { + args.push(Arg::Number(match num_parse::parse_uint(x) { + Some(y) => y, + _ => return Err("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(), + ); + } + _ => return Err("Unexpected token for argument"), + } + + last_was_comma = false; + } + } + } + + Ok(args) +} diff --git a/hence/src/lib/assembler.rs b/hence/src/lib/assembler.rs new file mode 100644 index 0000000..66b8dee --- /dev/null +++ b/hence/src/lib/assembler.rs @@ -0,0 +1,186 @@ +use itertools::Itertools; +use radix_fmt::radix; +use std::collections::HashMap; + +use crate::arg; +use crate::parser; + +pub trait ToAssembly { + fn to_assembly(&self) -> String; +} + +pub trait ByteResolvable { + fn resolve_number(&self, data: &mut Data) -> Result; + + fn resolve_bytes(&self, data: &mut Data) -> Result, String>; +} + +#[derive(Debug)] +pub struct Data { + pub dir: String, + pub program: [u8; 32 * 1024], + pub offset: u16, + pub contants: HashMap, +} + +impl Data { + pub fn new(dir: String) -> Self { + Self { + dir, + program: [0; 32 * 1024], + offset: 0, + contants: HashMap::new(), + } + } +} + +pub fn assemble(ast: parser::ast::AST, data: &mut Data) -> Result<(), String> { + for node in ast.body { + data.contants + .insert("OFFSET".to_string(), arg::Arg::Number(data.offset)); + + 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, 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, + "tls" => 0x07, + "ld" => 0x08, + "st" => 0x09, + "dbg" => 0x0a, + "alu" => 0x0b, + "at" => 0x0c, + "get" => 0x0d, + "set" => 0x0e, + _ => 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_assembly().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(), + ) + } + }; + + if data.contants.contains_key(name) { + return Err(format!("Constant already exists: '{name}'")); + } + data.contants.insert(name.to_string(), (&args[1]).clone()); + } + "define_override" => { + 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()); + } + "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; + } + } + _ => return Err(format!("Unknown macro: '{name}'")), + }; + } + } + + if data.offset > (32 * 1024) { + return Err(format!( + "Offset out of bounds: 0x{} > 0x8000", + radix(data.offset, 16), + )); + } + } + + Ok(()) +} diff --git a/hence/src/lib/emulator.rs b/hence/src/lib/emulator.rs new file mode 100644 index 0000000..5c5769e --- /dev/null +++ b/hence/src/lib/emulator.rs @@ -0,0 +1,328 @@ +use itertools::Itertools; +use radix_fmt::radix; +use std::cmp::Ordering; +use std::io; +use std::io::Read; + +#[derive(Debug)] +pub struct Data { + program: [u8; 32 * 1024], + + pub tmp: u16, + + pub reg_pc: u16, + pub reg_opc: u8, + pub reg_arg: u16, + pub reg_s: u8, + pub reg_sp: u16, + pub reg_a: u16, + pub reg_b: u16, + pub reg_c: u16, + pub reg_d: u16, + + stack: [u16; 8 * 1024], + memory: [u16; 16 * 1024], +} + +impl Data { + pub fn new(program: [u8; 32 * 1024]) -> Self { + return Self { + program, + + tmp: 0, + + reg_pc: 0, + reg_opc: 0, + reg_arg: 0, + reg_s: 0, + reg_sp: 0, + reg_a: 0, + reg_b: 0, + reg_c: 0, + reg_d: 0, + + stack: [0; 8 * 1024], + memory: [0; 16 * 1024], + }; + } + + pub fn get_memory(&self, address: u16) -> u16 { + if address < (32 * 1024) { + match self.program.get(address as usize) { + Some(val) => val.clone() as u16, + _ => 0, + } + } else if address < (40 * 1024) { + self.stack[(address - (32 * 1024)) as usize] + } else if address < (56 * 1024) { + self.memory[(address - (40 * 1024)) as usize] + } else { + 0 + } + } + + pub fn set_memory(&mut self, address: u16, value: u16) -> u16 { + if address >= (32 * 1024) && address < (40 * 1024) { + self.stack[(address - (32 * 1024)) as usize] = value; + + value + } else if address < (40 * 1024) { + self.memory[(address - (40 * 1024)) as usize] = value; + + value + } else if address == (56 * 1024) { + print!("{}", value); + + value & 0xff + } else if address == (56 * 1024) { + print!("{value}"); + + 0 + } else if address == (56 * 1024 + 1) { + print!("{}", char::from(value as u8)); + + 0 + } else { + 0 + } + } + + pub fn get_register(&self, register: u8) -> u16 { + match register { + 0x0 => self.reg_pc, + 0x1 => self.reg_opc as u16, + 0x2 => self.reg_arg, + 0x3 => self.reg_s as u16, + 0x4 => self.reg_sp, + 0x5 => self.reg_a, + 0x6 => self.reg_b, + 0x7 => self.reg_c, + 0x8 => self.reg_d, + _ => 0, + } + } + + pub fn set_register(&mut self, register: u8, value: u16) -> u16 { + 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> { + match operation { + 0x00 => { + self.tmp = !self.reg_a; + } + 0x01 => { + self.tmp = self.reg_a & self.reg_b; + } + 0x02 => { + self.tmp = self.reg_a | self.reg_b; + } + 0x03 => { + self.tmp = self.reg_a ^ self.reg_b; + } + 0x04 => { + self.tmp = self.reg_a << self.reg_b; + } + 0x05 => { + self.tmp = self.reg_a >> self.reg_b; + } + 0x06 => { + self.tmp = self + .reg_a + .wrapping_add(self.reg_b) + .wrapping_add((self.reg_s & 0b00000001) as u16); + self.reg_s &= 0b11111110; + if self.tmp < self.reg_a { + self.tmp |= 0b00000001; + } + } + 0x07 => { + self.tmp = self + .reg_a + .wrapping_sub(self.reg_b) + .wrapping_sub((self.reg_s & 0b00000001) as u16); + self.reg_s &= 0b11111110; + if self.tmp > self.reg_a { + self.tmp |= 0b00000001; + } + } + 0x08 => { + self.tmp = self.reg_a.wrapping_mul(self.reg_b); + self.reg_s &= 0b11111110; + } + 0x09 => { + self.tmp = self.reg_a / self.reg_b; + self.reg_s &= 0b11111110; + } + 0x0a => { + self.tmp = match self.reg_a.cmp(&self.reg_b) { + Ordering::Less => 1, + Ordering::Greater => 2, + Ordering::Equal => 0, + } + } + 0x0b => { + self.tmp = bool_to_num(self.reg_a == self.reg_b); + } + 0x0c => { + self.tmp = bool_to_num(self.reg_a < self.reg_b); + } + 0x0d => { + self.tmp = bool_to_num(self.reg_a > self.reg_b); + } + 0x0e => { + self.tmp = bool_to_num(self.reg_a <= self.reg_b); + } + 0x0f => { + self.tmp = bool_to_num(self.reg_a >= self.reg_b); + } + 0x10 => { + self.tmp = bool_to_num(self.reg_a == 1); + } + 0x11 => { + self.tmp = bool_to_num(self.reg_a != 1); + } + 0x12 => { + self.tmp = rand::random(); + } + _ => return Err(format!("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> { + let mut stdin = io::stdin().lock(); + + while data.reg_pc != 0xffff { + 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_pc = data.reg_pc.wrapping_add(1); + + data.reg_arg = data.reg_arg | data.get_memory(data.reg_pc); + data.reg_pc = data.reg_pc.wrapping_add(1); + } + + match data.reg_opc { + 0x00 => {} + 0x01 => { + data.stack[data.reg_sp as usize] = data.reg_arg; + data.reg_sp = data.reg_sp.wrapping_add(1); + } + 0x02 => { + data.reg_sp = data.reg_sp.wrapping_sub(1); + data.stack[data.reg_sp as usize] = 0; + } + 0x03 => { + data.tmp = data.reg_arg; + } + 0x04 => { + data.tmp = data.get_register(data.reg_arg as u8); + } + 0x05 => { + data.tmp = data.stack[data.reg_sp.wrapping_sub(1) as usize]; + } + 0x06 => { + data.set_register(data.reg_arg as u8, data.tmp); + } + 0x07 => { + data.stack[data.reg_sp as usize] = data.tmp; + data.reg_sp = data.reg_sp.wrapping_add(1); + } + 0x08 => { + data.reg_sp = data.reg_sp.wrapping_sub(1); + data.set_register(data.reg_arg as u8, data.stack[data.reg_sp as usize]); + data.stack[data.reg_sp as usize] = 0; + } + 0x09 => { + data.stack[data.reg_sp as usize] = data.get_register(data.reg_arg as u8); + data.reg_sp = data.reg_sp.wrapping_add(1); + } + 0x0a => { + println!( + "[DEBUG]: [{}]", + data.stack.iter().take(data.reg_sp as usize).join(", ") + ); + println!("Press enter to continue execution...",); + stdin.read_exact(&mut [0; 1]).unwrap(); + } + 0x0b => { + data.alu(data.reg_arg as u8).unwrap(); + } + 0x0c => { + data.alu(data.tmp as u8).unwrap(); + } + 0x0d => { + data.tmp = data.get_memory(data.tmp); + } + 0x0e => { + data.set_memory(data.tmp, data.reg_a); + } + _ => return Err(format!("Invalid opcode: 0x{}", radix(data.reg_opc, 16))), + } + } + + Ok(()) +} diff --git a/hence/src/lib/lexer.rs b/hence/src/lib/lexer.rs new file mode 100644 index 0000000..90b3ebe --- /dev/null +++ b/hence/src/lib/lexer.rs @@ -0,0 +1,165 @@ +use crate::assembler; +use itertools::{Itertools, PeekingNext}; + +#[derive(Debug)] +pub enum Token { + Comment(String), + + StringLiteral(String), + MacroLiteral(String), + Literal(String), + Number(String), + + Comma, + Colon, + LParen, + RParen, + + Assign, + + Add, + Sub, + Mul, + Div, + Pow, + + Newline(String), + Whitespace(String), +} + +impl assembler::ToAssembly for Token { + fn to_assembly(&self) -> String { + match self { + Token::Comment(x) => format!(";{x}"), + Token::StringLiteral(x) => format!("\"{x}\""), + Token::MacroLiteral(x) => x.clone(), + Token::Literal(x) => x.clone(), + Token::Number(x) => x.clone(), + Token::Comma => ",".to_string(), + Token::Colon => ":".to_string(), + Token::LParen => "(".to_string(), + Token::RParen => ")".to_string(), + Token::Assign => "=".to_string(), + Token::Add => "+".to_string(), + Token::Sub => "-".to_string(), + Token::Mul => "*".to_string(), + Token::Div => "/".to_string(), + Token::Pow => "**".to_string(), + Token::Newline(x) | Token::Whitespace(x) => x.clone(), + } + } +} + +pub fn lex(source: String) -> Result, String> { + let mut chars = source.chars().peekable(); + let mut tokens = Vec::::new(); + + 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::(), + )); + } + '@' => { + chars.next(); + chars.next_if(|c| *c == '@'); + + tokens.push(Token::Comment( + chars.peeking_take_while(|c| *c != '\n').collect::(), + )); + } + '"' => { + chars.next(); + tokens.push(Token::StringLiteral( + chars.by_ref().take_while(|c| *c != '"').collect::(), + )); + } + '.' => { + chars.next(); + tokens.push(Token::MacroLiteral(format!( + ".{}", + chars + .peeking_take_while(|c| c.is_alphabetic() || c.is_numeric()) + .collect::() + ))); + } + ch if ch.is_alphabetic() => { + let name: String = chars + .peeking_take_while(|c| c.is_alphabetic() || c.is_numeric()) + .collect(); + + tokens.push(Token::Literal(name)); + } + ch if ch.is_numeric() => { + tokens.push(Token::Number( + chars + .peeking_take_while(|c| c.is_alphanumeric()) + .collect::(), + )); + } + ',' => { + tokens.push(Token::Comma); + chars.next(); + } + ':' => { + tokens.push(Token::Colon); + chars.next(); + } + '(' => { + tokens.push(Token::LParen); + chars.next(); + } + ')' => { + tokens.push(Token::RParen); + chars.next(); + } + '=' => { + tokens.push(Token::Assign); + chars.next(); + } + '+' => { + tokens.push(Token::Add); + chars.next(); + } + '-' => { + tokens.push(Token::Sub); + chars.next(); + } + '*' => { + chars.next(); + tokens.push(if chars.peeking_next(|c| *c == '*').is_some() { + Token::Pow + } else { + Token::Mul + }); + } + '/' => { + tokens.push(Token::Div); + chars.next(); + } + '\n' => { + tokens.push(Token::Newline( + chars.peeking_take_while(|c| *c == '\n').collect::(), + )); + } + ch if ch.is_whitespace() => { + tokens.push(Token::Whitespace( + chars + .peeking_take_while(|c| c.is_whitespace() && *c != '\n') + .collect::(), + )); + } + _ => { + // tokens.push(Token::Error(ch.to_string())); + // chars.next(); + return Err(format!("Unexpected token: '{ch}'")); + } + } + } + + Ok(tokens) +} diff --git a/hence/src/lib/lib.rs b/hence/src/lib/lib.rs new file mode 100644 index 0000000..fb1b1c8 --- /dev/null +++ b/hence/src/lib/lib.rs @@ -0,0 +1,5 @@ +pub mod arg; +pub mod assembler; +pub mod emulator; +pub mod lexer; +pub mod parser; diff --git a/hence/src/lib/parser.rs b/hence/src/lib/parser.rs new file mode 100644 index 0000000..9416e11 --- /dev/null +++ b/hence/src/lib/parser.rs @@ -0,0 +1,68 @@ +use itertools::PeekingNext; + +use crate::arg; +use crate::lexer; + +pub mod ast; + +pub fn parse(tokens: Vec) -> Result { + let mut iter = tokens.iter().peekable(); + let mut body: Vec = Vec::new(); + + while let Some(token) = iter.peek() { + match token { + lexer::Token::Comment(x) => { + body.push(ast::Node::Comment(x.trim().to_string())); + iter.next(); + } + lexer::Token::MacroLiteral(x) => { + iter.next(); + + body.push(ast::Node::MacroCall { + name: (&x[1..]).to_string(), + args: arg::parse_args( + iter.by_ref() + .take_while(|t| !matches!(t, lexer::Token::Newline(_))) + .filter(|t| !matches!(t, lexer::Token::Whitespace(_))) + .collect(), + ) + .unwrap(), + }); + } + lexer::Token::Literal(x) => { + iter.next(); + if iter + .peeking_next(|t| matches!(t, lexer::Token::Colon)) + .is_some() + { + body.push(ast::Node::Label(x.clone())); + } else { + let args = arg::parse_args( + iter.by_ref() + .take_while(|t| !matches!(t, lexer::Token::Newline(_))) + .filter(|t| !matches!(t, lexer::Token::Whitespace(_))) + .collect(), + ) + .unwrap(); + if args.len() > 1 { + return Err("Opcode call only accepts one argument"); + } + + body.push(ast::Node::Call { + name: x.clone(), + arg: match args.first() { + Some(x) => Some(x.clone()), + _ => None, + }, + }); + } + } + lexer::Token::Whitespace(_) | lexer::Token::Newline(_) => { + iter.next(); + } + _ => return Err("Unexspected token"), + } + } + + Ok(ast::AST { body }) +} diff --git a/hence/src/lib/parser/ast.rs b/hence/src/lib/parser/ast.rs new file mode 100644 index 0000000..b7c4354 --- /dev/null +++ b/hence/src/lib/parser/ast.rs @@ -0,0 +1,49 @@ +use itertools::Itertools; + +use crate::arg; +use crate::assembler; + +#[derive(Debug)] +pub enum Node { + Comment(String), + Label(String), + Call { name: String, arg: Option }, + MacroCall { name: String, args: Vec }, +} + +impl assembler::ToAssembly for Node { + fn to_assembly(&self) -> String { + match self { + Node::Comment(x) => format!("; {x}"), + Node::Label(x) => format!("{x}:"), + Node::Call { name, arg } => { + if let Some(a) = arg { + format!("{name} {arg}", arg = a.to_assembly()) + } else { + name.clone() + } + } + Node::MacroCall { name, args } => { + if args.is_empty() { + format!(".{name}") + } else { + format!( + ".{name} {}", + args.iter().map(|a| a.to_assembly()).join(", ") + ) + } + } + } + } +} + +#[derive(Debug)] +pub struct AST { + pub body: Vec, +} + +impl assembler::ToAssembly for AST { + fn to_assembly(&self) -> String { + self.body.iter().map(|n| n.to_assembly()).join("\n") + } +} diff --git a/hencelisp/.gitignore b/hencelisp/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/hencelisp/.gitignore @@ -0,0 +1 @@ +/target diff --git a/hencelisp/Cargo.toml b/hencelisp/Cargo.toml new file mode 100644 index 0000000..aa6995e --- /dev/null +++ b/hencelisp/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hencelisp" +version = "0.1.0" +edition = "2021" + +[lib] +name = "hencelisp" +path = "src/lib/lib.rs" + +[[bin]] +name = "hencelisp" +path = "src/bin/main.rs" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hence = { path = "../hence" } +clap = { version = "3.2.12", features = ["derive"] } diff --git a/hencelisp/src/bin/main.rs b/hencelisp/src/bin/main.rs new file mode 100644 index 0000000..87b115e --- /dev/null +++ b/hencelisp/src/bin/main.rs @@ -0,0 +1,29 @@ +use clap::{Parser, Subcommand}; +use hencelisp::*; +use std::fs; + +#[derive(Debug, Parser)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + commands: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + #[clap(about = "Lexes source code and outputs tokens")] + Lex { + #[clap(value_parser)] + src: String, + }, +} + +fn main() { + let args = Cli::parse(); + match args.commands { + Commands::Lex { src } => { + let source = fs::read_to_string(src).unwrap(); + println!("{source}"); + } + } +} diff --git a/hencelisp/src/lib/lexer.rs b/hencelisp/src/lib/lexer.rs new file mode 100644 index 0000000..67e8b91 --- /dev/null +++ b/hencelisp/src/lib/lexer.rs @@ -0,0 +1 @@ +pub fn lex(source: String) {} diff --git a/hencelisp/src/lib/lib.rs b/hencelisp/src/lib/lib.rs new file mode 100644 index 0000000..fc84151 --- /dev/null +++ b/hencelisp/src/lib/lib.rs @@ -0,0 +1 @@ +pub mod lexer; diff --git a/hencelisp/src/main.rs b/hencelisp/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/hencelisp/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/pdf/chapter_break.tex b/pdf/chapter_break.tex deleted file mode 100644 index 1f0b4e3..0000000 --- a/pdf/chapter_break.tex +++ /dev/null @@ -1,2 +0,0 @@ -\usepackage{sectsty} -\sectionfont{\underline\clearpage} diff --git a/pdf/pdf.sh b/pdf/pdf.sh deleted file mode 100755 index 0b157c1..0000000 --- a/pdf/pdf.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# -*- coding: utf-8 -*- - -pandoc "$1" \ - -f gfm \ - -t pdf \ - -H pdf/chapter_break.tex \ - --wrap=none \ - --reference-links \ - -s \ - --toc \ - --toc-depth 4 \ - -V geometry:a4paper \ - -V mainfont="Vollkorn" \ - -V monofont="DejaVu Sans Mono" \ - -V linkcolor:blue \ - --highlight-style pdf/pygments.theme \ - --pdf-engine=xelatex \ - -o "$2" - diff --git a/pdf/pygments.theme b/pdf/pygments.theme deleted file mode 100644 index 8211f85..0000000 --- a/pdf/pygments.theme +++ /dev/null @@ -1,211 +0,0 @@ -{ - "text-color": null, - "background-color": "#f8f8f8", - "line-number-color": "#aaaaaa", - "line-number-background-color": null, - "text-styles": { - "Other": { - "text-color": "#007020", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Attribute": { - "text-color": "#7d9029", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "SpecialString": { - "text-color": "#bb6688", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Annotation": { - "text-color": "#60a0b0", - "background-color": null, - "bold": true, - "italic": true, - "underline": false - }, - "Function": { - "text-color": "#06287e", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "String": { - "text-color": "#4070a0", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "ControlFlow": { - "text-color": "#007020", - "background-color": null, - "bold": true, - "italic": false, - "underline": false - }, - "Operator": { - "text-color": "#666666", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Error": { - "text-color": "#ff0000", - "background-color": null, - "bold": true, - "italic": false, - "underline": false - }, - "BaseN": { - "text-color": "#40a070", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Alert": { - "text-color": "#ff0000", - "background-color": null, - "bold": true, - "italic": false, - "underline": false - }, - "Variable": { - "text-color": "#19177c", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "BuiltIn": { - "text-color": null, - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Extension": { - "text-color": null, - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Preprocessor": { - "text-color": "#bc7a00", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Information": { - "text-color": "#60a0b0", - "background-color": null, - "bold": true, - "italic": true, - "underline": false - }, - "VerbatimString": { - "text-color": "#4070a0", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Warning": { - "text-color": "#60a0b0", - "background-color": null, - "bold": true, - "italic": true, - "underline": false - }, - "Documentation": { - "text-color": "#ba2121", - "background-color": null, - "bold": false, - "italic": true, - "underline": false - }, - "Import": { - "text-color": null, - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Char": { - "text-color": "#4070a0", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "DataType": { - "text-color": "#902000", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Float": { - "text-color": "#40a070", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Comment": { - "text-color": "#60a0b0", - "background-color": null, - "bold": false, - "italic": true, - "underline": false - }, - "CommentVar": { - "text-color": "#60a0b0", - "background-color": null, - "bold": true, - "italic": true, - "underline": false - }, - "Constant": { - "text-color": "#880000", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "SpecialChar": { - "text-color": "#4070a0", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "DecVal": { - "text-color": "#40a070", - "background-color": null, - "bold": false, - "italic": false, - "underline": false - }, - "Keyword": { - "text-color": "#007020", - "background-color": null, - "bold": true, - "italic": false, - "underline": false - } - } -} diff --git a/shard.lock b/shard.lock deleted file mode 100644 index 19fac7d..0000000 --- a/shard.lock +++ /dev/null @@ -1,10 +0,0 @@ -version: 2.0 -shards: - commander: - git: https://github.com/mrrooijen/commander.git - version: 0.4.0 - - version_from_shard: - git: https://github.com/hugopl/version_from_shard.git - version: 1.2.5 - diff --git a/shard.yml b/shard.yml deleted file mode 100644 index 741cfb5..0000000 --- a/shard.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: hence -version: 0.1.0 - -authors: - - Dominic Grimm - -targets: - hence: - main: src/cli/hence.cr - -crystal: 1.5.0 - -license: MIT - -dependencies: - commander: - github: mrrooijen/commander - version_from_shard: - github: hugopl/version_from_shard diff --git a/src/cli/hence.cr b/src/cli/hence.cr deleted file mode 100644 index ea71fcf..0000000 --- a/src/cli/hence.cr +++ /dev/null @@ -1,201 +0,0 @@ -require "commander" -require "../hence" - -CLI = Commander::Command.new do |cmd| - cmd.use = "hence" - cmd.long = "Hence assembler and emulator" - - cmd.run do - puts cmd.help - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "version" - cmd.short = "Prins current hence version" - cmd.long = cmd.short - - cmd.run do - puts Hence::VERSION - end - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "firmware" - cmd.short = "Firmware utility" - cmd.long = cmd.short - - cmd.flags.add do |flag| - flag.name = "file" - flag.long = "--file" - flag.default = false - flag.description = "Load firmware from file" - flag.persistent = true - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "ls" - cmd.short = "Lists available firmwares" - cmd.long = cmd.short - - cmd.run do - puts Hence::Firmware::FIRMWARES.keys.join("\n") - end - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "compile [out]" - cmd.short = "Compiles a firmware" - cmd.long = cmd.short - - cmd.flags.add do |flag| - flag.name = "dump" - flag.long = "--dump" - flag.default = false - flag.description = "Hexdumps the firmware" - end - - cmd.run do |opts, args| - bin = if opts.bool["file"] - Hence::Firmware.from_yaml(File.read(args[0])) - else - Hence::Firmware::FIRMWARES[args[0]] - end.compile(Hence::Microcode::MICROCODE_NAMES) - File.write(args[1], bin) if args[1]? - - puts bin.hexdump if opts.bool["dump"] - end - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "doc [out]" - cmd.short = "Generates documentation for a firmware" - cmd.long = cmd.short - - cmd.flags.add do |flag| - flag.name = "dump" - flag.long = "--dump" - flag.default = false - flag.description = "Hexdumps the firmware" - end - - cmd.run do |opts, args| - doc = if opts.bool["file"] - Hence::Firmware.from_yaml(File.read(args[0])) - else - Hence::Firmware::FIRMWARES[args[0]] - end.to_s - File.write(args[1], doc) if args[1]? - - puts doc if opts.bool["dump"] - end - end - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "parse " - cmd.short = "Parses source code" - cmd.long = cmd.short - - cmd.run do |_opts, args| - # ameba:disable Lint/DebugCalls - pp Hence::Parser.parse(File.read(args[0])) - end - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "assemble [out]" - cmd.short = "Compiles assembly code into a binary" - cmd.long = cmd.short - - cmd.flags.add do |flag| - flag.name = "firmware" - flag.short = "-f" - flag.long = "--firmware" - flag.default = "default" - flag.description = "Custom firmware" - end - - cmd.flags.add do |flag| - flag.name = "file" - flag.long = "--file" - flag.default = false - flag.description = "Load firmware from YAML definition file" - end - - cmd.flags.add do |flag| - flag.name = "dump" - flag.long = "--dump" - flag.default = false - flag.description = "Hexdumps the binary" - end - - cmd.run do |opts, args| - bin = Hence::Assembler.assemble( - Hence::Parser.parse(File.read(args[0])), - if opts.bool["file"] - Hence::Firmware.from_yaml(File.read(opts.string["file"])) - else - Hence::Firmware::FIRMWARES[opts.string["firmware"]] - end.build_opcodes[:names], - Hence::Assembler::Data.new(Path[args[0]]) - ) - - File.write(args[1], bin) if args[1]? - puts bin.hexdump if opts.bool["dump"] - end - end - - # ameba:disable Lint/ShadowingOuterLocalVar - cmd.commands.add do |cmd| - cmd.use = "run " - cmd.short = "Runs a binary" - cmd.long = cmd.short - - cmd.flags.add do |flag| - flag.name = "firmware" - flag.short = "-f" - flag.long = "--firmware" - flag.default = "default" - flag.description = "Custom firmware" - end - - cmd.flags.add do |flag| - flag.name = "file" - flag.long = "--file" - flag.default = false - flag.description = "Load firmware from file" - end - - cmd.run do |opts, args| - bin = Bytes.new({{ 32 * 1024 }}) - File.open(args[0]) do |file| - file.gets_to_end.to_slice.copy_to(bin) - end - - Hence::Emulator.emulate( - Hence::Emulator::Data.new( - bin, - if opts.bool["file"] - buffer = Bytes.new({{ 32 * 1024 }}) - File.open(opts.string["firmware"]) do |file| - file.gets_to_end.to_slice.copy_to(buffer) - end - - buffer - else - Hence::Firmware::FIRMWARES[opts.string["firmware"]].compile(Hence::Microcode::MICROCODE_NAMES) - end - ) - ) - end - end -end - -Commander.run(CLI, ARGV) diff --git a/src/hence.cr b/src/hence.cr deleted file mode 100644 index de617e7..0000000 --- a/src/hence.cr +++ /dev/null @@ -1 +0,0 @@ -require "./hence/*" diff --git a/src/hence/assembler.cr b/src/hence/assembler.cr deleted file mode 100644 index 9b3b55d..0000000 --- a/src/hence/assembler.cr +++ /dev/null @@ -1,120 +0,0 @@ -require "colorize" - -module Hence - module Assembler - extend self - - def assemble(ast : Parser::AST, opcode_names : Hash(String, UInt8), data : Data) : Bytes - ast.body.each do |node| - data.constants["OFFSET"] = Parser::AST::NumberArg.new(data.offset) - - case node - when Parser::AST::ConstantDeclarationNode - raise "Constants can't be redefined" if data.constants[node.name]? - - data.constants[node.name] = Parser::AST::BytesArg.new(node.value.resolve(data)) - when Parser::AST::LabelNode - raise "Labels can't be redefined" if data.constants[node.name]? - - data.constants[node.name] = Parser::AST::NumberArg.new(data.offset) - when Parser::AST::MacroCallNode - MACROS[node.name].call(data, node.args) - when Parser::AST::CallNode - data.bin[data.offset] = opcode_names[node.name] - arg = node.arg.try(&.resolve(data)) || Bytes.new(2) - data.bin[data.offset + 1] = arg[0] - data.bin[data.offset + 2] = arg[1] - - data.offset += 3 - else - raise "Unexpected node type: #{typeof(node)}" - end - - raise "Offset out of bounds: 0x#{data.offset.to_s(16).rjust(4, '0')} > 0x#{data.bin.size.to_s(16).rjust(4, '0')}" if data.offset > data.bin.size - end - - data.bin - end - - class RawData - property bin - property offset - property constants - - def initialize( - @bin = Bytes.new({{ 32 * 1024 }}), - @offset = 0_u16, - @constants = {} of String => Parser::AST::Arg - ) - end - end - - class Data < RawData - property path - - def initialize( - @path : Path, - @bin = Bytes.new({{ 32 * 1024 }}), - @offset = 0_u16, - @constants = {} of String => Parser::AST::Arg - ) - end - end - - MACROS = { - "debug" => ->(data : Data, args : Array(Parser::AST::Arg)) do - args.each do |arg| - arg_s = arg.to_s.gsub(/\n/, "\\n") - - num = arg.resolve_as_number(data) - slice = arg.resolve(data) - - puts arg_s.colorize(:green) - puts " => #{"0x#{num.to_s(16).rjust(4, '0')}".colorize(:magenta)}" - puts " => #{num.colorize(:magenta)}" - puts " => [#{slice.map { |n| "0x#{n.to_s(16).rjust(2, '0')}".colorize(:magenta) }.join(", ")}]" - puts " => #{"\"#{String.new(slice).gsub(/\n/, "\\n")}\"".colorize(:light_green)}" - puts - end - end, - "org" => ->(data : Data, args : Array(Parser::AST::Arg)) do - data.offset = args[0].resolve_as_number(data) - end, - "org_add" => ->(data : Data, args : Array(Parser::AST::Arg)) do - data.offset += args[0].resolve_as_number(data) - end, - "org_sub" => ->(data : Data, args : Array(Parser::AST::Arg)) do - data.offset -= args[0].resolve_as_number(data) - end, - "bytes" => ->(data : Data, args : Array(Parser::AST::Arg)) do - args.each do |a| - a.resolve(data).each do |b| - data.bin[data.offset] = b - data.offset += 1 - end - end - end, - "bw" => ->(data : Data, args : Array(Parser::AST::Arg)) do - string_arg = String.new(args[0].resolve(data)).gsub(/\\n/, '\n').to_slice - pos_start = args[1]?.try(&.resolve_as_number(data)) || 0_u16 - pos_end = args[2]?.try(&.resolve_as_number(data)) || string_arg.size.to_u16 - - string_arg[pos_start...pos_end].each do |b| - data.bin[data.offset] = b - data.offset += 1 - end - end, - "embed" => ->(data : Data, args : Array(Parser::AST::Arg)) do - File.open(Path[String.new(args[0].resolve(data))].expand(data.path.dirname)) do |io| - io.getb_to_end.each_with_index do |val, i| - data.bin[data.offset + i] = val - end - data.offset += io.size - end - end, - "include" => ->(_data : Data, _args : Array(Parser::AST::Arg)) do - raise "Unimplemented" - end, - } - end -end diff --git a/src/hence/emulator.cr b/src/hence/emulator.cr deleted file mode 100644 index 5fa5a4e..0000000 --- a/src/hence/emulator.cr +++ /dev/null @@ -1,265 +0,0 @@ -module Hence - module Emulator - extend self - - def emulate(data : Data) : Data - until data.reg_pc == 0xffff_u16 - data.reg_opc = (data.get_memory(data.reg_pc) & 0xff_u8).to_u8 - data.reg_pc &+= 1 - data.reg_arg = data.get_memory(data.reg_pc) << 8 - data.reg_pc &+= 1 - data.reg_arg = data.reg_arg | (data.get_memory(data.reg_pc) & 0xff_u8) - data.reg_pc &+= 1 - - base_mcc = data.reg_opc &* 4 - data.reg_mccs = data.firmware[base_mcc].to_u16 << 8 - data.reg_mccs = data.reg_mccs | data.firmware[base_mcc &+ 1] - data.reg_mcce = data.firmware[base_mcc &+ 2].to_u16 << 8 - data.reg_mcce = data.reg_mcce | data.firmware[base_mcc &+ 3] - - until data.reg_mccs &+ data.reg_mccpc >= data.reg_mcce - base_mccpc = data.reg_mccs &+ data.reg_mccpc - data.reg_mcc = data.firmware[base_mccpc] - data.reg_mccarg = data.firmware[base_mccpc &+ 1].to_u16 << 8 - data.reg_mccarg = data.reg_mccarg | data.firmware[base_mccpc &+ 2] - data.reg_mccpc &+= 3 - - data.microcodes[data.reg_mcc].run(data) - end - - data.reg_mccpc = 0_u16 - data.reg_tmp = 0_u16 - data.reg_a = 0_u16 - data.reg_b = 0_u16 - data.reg_c = 0_u16 - end - - data - end - - class Data - property firmware - property microcodes - property reg_pc - property reg_opc - property reg_arg - property reg_mccpc - property reg_mccs - property reg_mcce - property reg_mcc - property reg_mccarg - property reg_tmp - property reg_sp - property reg_a - property reg_b - property reg_c - property reg_s - property reg_inp - property reg_out - - def initialize( - @program : Bytes, - @firmware : Bytes, - @microcodes = Microcode::MICROCODES, - @reg_pc = 0_u16, - @reg_opc = 0_u8, - @reg_arg = 0_u16, - @reg_mccpc = 0_u16, - @reg_mccs = 0_u16, - @reg_mcce = 0_u16, - @reg_mcc = 0_u8, - @reg_mccarg = 0_u16, - @reg_tmp = 0_u16, - @reg_sp = 0_u16, - @reg_a = 0_u16, - @reg_b = 0_u16, - @reg_c = 0_u16, - @reg_s = 0_u8, - @reg_inp = 0_u16, - @reg_out = 0_u16, - @stack = Slice(UInt16).new({{ 8 * 1024 }}), - @memory = Slice(UInt16).new({{ 16 * 1024 }}) - ) - end - - # ameba:disable Metrics/CyclomaticComplexity - def get_register(index : UInt8) : UInt16 - case index - when 0x0_u8 - @reg_pc - when 0x1_u8 - @reg_opc.to_u16 - when 0x2_u8 - @reg_arg - when 0x3_u8 - @reg_mccpc - when 0x4_u8 - @reg_mccs - when 0x5_u8 - @reg_mcce - when 0x6_u8 - @reg_mcc.to_u16 - when 0x7_u8 - @reg_mccarg - when 0x8_u8 - @reg_tmp - when 0x9_u8 - @reg_sp - when 0xa_u8 - @reg_a - when 0xb_u8 - @reg_b - when 0xc_u8 - @reg_c - when 0xd_u8 - @reg_s.to_u16 - when 0xe_u8 - @reg_inp - when 0xf_u8 - @reg_out - else - raise "Invalid register index: #{index}" - end - end - - # ameba:disable Metrics/CyclomaticComplexity - def set_register(index : UInt8, value : UInt16) : UInt16 - case index - when 0x0_u8 - @reg_pc = value - when 0x1_u8 - @reg_opc = value - when 0x2_u8 - @reg_arg = value - when 0x3_u8 - @reg_mccpc = value - when 0x4_u8 - @reg_mccs = value - when 0x5_u8 - @reg_mcce = value - when 0x6_u8 - @reg_mcc = value - when 0x7_u8 - @reg_mccarg = value - when 0x8_u8 - @reg_tmp = value - when 0x9_u8 - @reg_sp = value - when 0xa_u8 - @reg_a = value - when 0xb_u8 - @reg_b = value - when 0xc_u8 - @reg_c = value - when 0xd_u8 - @reg_s = value - when 0xe_u8 - @reg_inp = value - when 0xf_u8 - @reg_out = value - else - raise "Invalid register index: #{index}" - end - end - - # ameba:disable Metrics/CyclomaticComplexity - def alu(op : UInt8) : UInt16 - case op - when 0x00 # not - @reg_tmp = ~@reg_a - when 0x01 # and - @reg_tmp = @reg_a & @reg_b - when 0x02 # nand - @reg_tmp = ~(@reg_a & @reg_b) - when 0x03 # or - @reg_tmp = @reg_a | @reg_b - when 0x04 # nor - @reg_tmp = ~(@reg_a | @reg_b) - when 0x05 # xor - @reg_tmp = @reg_a ^ @reg_b - when 0x06 # xnor - @reg_tmp = ~(@reg_a ^ @reg_b) - when 0x07 # shl - @reg_tmp = @reg_a << @reg_b - when 0x08 # shr - @reg_tmp = @reg_a >> @reg_b - when 0x09 # add - @reg_tmp = @reg_a &+ @reg_b &+ (@reg_s & 0b00000001_u8) - @reg_s &= 0b11111110_u8 - if @reg_tmp < @reg_a - @reg_s |= 0b00000001_u8 - end - when 0x0a # sub - @reg_tmp = @reg_a &- @reg_b &- (@reg_s & 0b00000001_u8) - @reg_s &= 0b11111110_u8 - if @reg_tmp > @reg_a - @reg_s |= 0b00000001_u8 - end - when 0x0b # mul - @reg_tmp = @reg_a &* @reg_b - @reg_s &= 0b11111110_u8 - when 0x0c # div - @reg_tmp = @reg_a // @reg_b - @reg_s &= 0b11111110_u8 - when 0x0d # mod - @reg_tmp = @reg_a % @reg_b - @reg_s &= 0b11111110_u8 - when 0x0e # cmp - @reg_tmp = if @reg_a < @reg_b - 1_u16 - elsif @reg_a > @reg_b - 2_u16 - else - 0_u16 - end - when 0x0f # eq - @reg_tmp = @reg_a == @reg_b ? 1_u16 : 0_u16 - when 0x10 # neq - @reg_tmp = @reg_a != @reg_b ? 1_u16 : 0_u16 - when 0x11 # lt - @reg_tmp = @reg_a < @reg_b ? 1_u16 : 0_u16 - when 0x12 # gt - @reg_tmp = @reg_a > @reg_b ? 1_u16 : 0_u16 - when 0x13 # leq - @reg_tmp = @reg_a <= @reg_b ? 1_u16 : 0_u16 - when 0x14 # geq - @reg_tmp = @reg_a >= @reg_b ? 1_u16 : 0_u16 - when 0x15 # bol - @reg_tmp = @reg_a == 1 ? 1_u16 : 0_u16 - when 0x16 # neg - @reg_tmp = @reg_a == 1 ? 0_u16 : 1_u16 - else - raise "Invalid ALU operation: 0x#{op.to_s(16).rjust(2, '0')}" - end - - @reg_tmp - end - - def get_memory(address : UInt16) : UInt16 - if address < {{ 32 * 1024 }} - @program[address].to_u16 - elsif address < {{ 40 * 1024 }} - @stack[address - {{ 32 * 1024 }}] - elsif address < {{ 56 * 1024 }} - @memory[address - {{ 40 * 1024 }}] - else - 0_u16 - end - end - - def set_memory(address : UInt16, value : UInt16) : UInt16 - if address < {{ 32 * 1024 }} - 0_u16 - elsif address < {{ 40 * 1024 }} - @stack[address - {{ 32 * 1024 }}] = value - - value - elsif address < {{ 56 * 1024 }} - @memory[address - {{ 40 * 1024 }}] = value - else - 0_u16 - end - end - end - end -end diff --git a/src/hence/firmware.cr b/src/hence/firmware.cr deleted file mode 100644 index a8ff266..0000000 --- a/src/hence/firmware.cr +++ /dev/null @@ -1,181 +0,0 @@ -require "yaml" -require "ecr" - -require "./firmware/*" - -module Hence - class Firmware - include YAML::Serializable - - @[YAML::Field] - property name - - @[YAML::Field(emit_null: true)] - property description - - @[YAML::Field] - property authors - - @[YAML::Field] - property date - - @[YAML::Field] - property version - - @[YAML::Field] - property sections - - def initialize( - @name : String, - @description : String?, - @authors : Array(String), - @date : Time, - @version : String, - @sections : Array(Section) - ) - end - - def full_name : String - "#{@name}:#{@version}" - end - - ECR.def_to_s "#{__DIR__}/firmware/documentation.md.ecr" - - def build_opcodes : {names: Hash(String, UInt8), opcodes: Hash(UInt8, Opcode)} - names = {} of String => UInt8 - opcodes = {} of UInt8 => Opcode - i = 0_u8 - @sections.each do |s| - s.opcodes.each do |opc| - j = if opc.opcode - opc.opcode.not_nil! - else - x = i - i += 1 - - x - end - raise "Duplicate opcode: #{j}" if opcodes.has_key?(j) - - names[opc.name] = j - opcodes[j] = opc.to_opcode(opc.opcode ? nil : j) - end - end - - {names: names, opcodes: opcodes} - end - - def compile(microcode_names : Hash(String, UInt8), data = Assembler::RawData.new) : Bytes - sorted_opcodes = build_opcodes[:opcodes].to_a.sort_by!(&.[0]) - data.offset = (sorted_opcodes.last[0].to_u16 + 1) * 4 - sorted_opcodes.each do |opc| - end_offset = data.offset + opc[1].microcode.body.size * 3 - base = opc[0] * 4 - data.bin[base] = (data.offset >> 8).to_u8 - data.bin[base + 1] = (data.offset & 0xff).to_u8 - data.bin[base + 2] = (end_offset >> 8).to_u8 - data.bin[base + 3] = (end_offset & 0xff).to_u8 - - i = 0_u16 - opc[1].microcode.body.each do |node| - j = data.offset + i - - case node - when Parser::AST::LabelNode - data.constants[node.name] = Parser::AST::NumberArg.new(data.offset &- 3) - when Parser::AST::CallNode - data.bin[j] = microcode_names[node.name] - if node.arg - arg = node.arg.not_nil!.resolve(data) - - data.bin[j + 1] = arg.not_nil![0] - data.bin[j + 2] = arg.not_nil![1] - end - - i += 3 - else - raise "Unexpected node type: #{typeof(node)}" - end - end - - data.offset = end_offset - end - - data.bin - end - - class Section - include YAML::Serializable - - @[YAML::Field] - property name - - @[YAML::Field(emit_null: true)] - property description - - @[YAML::Field] - property opcodes - - def initialize(@name : String, @description : String?, @opcodes : Array(Opcode)) - end - - class Opcode - include YAML::Serializable - - @[YAML::Field] - property name - - @[YAML::Field(emit_null: true)] - property description - - @[YAML::Field(emit_null: true)] - property example - - @[YAML::Field(emit_null: true)] - property opcode - - @[YAML::Field(emit_null: true)] - property arg - - @[YAML::Field] - property stack - - @[YAML::Field] - property microcode - - def initialize( - @name : String, - @description : String?, - @example : String?, - @opcode : UInt8?, - @arg : String?, - @stack : Stack, - @microcode : String - ) - end - - def to_opcode(opcode : UInt8? = nil) : ::Hence::Opcode - raise "Opcode already defined" if opcode && @opcode - - Hence::Opcode.new( - @name, - Parser.parse(@microcode) - ) - end - - class Stack - include YAML::Serializable - - @[YAML::Field] - property input - - @[YAML::Field] - property output - - def initialize(@input : Array(String), @output : Array(String)) - end - end - end - end - end -end diff --git a/src/hence/firmware/documentation.md.ecr b/src/hence/firmware/documentation.md.ecr deleted file mode 100644 index 3dbc482..0000000 --- a/src/hence/firmware/documentation.md.ecr +++ /dev/null @@ -1,48 +0,0 @@ -<%- sorted_opcodes = build_opcodes[:opcodes].to_a.sort_by!(&.[0]) -%> -# `<%= full_name %>` - -<%= @description %> - -## Opcodes - -Opcode | Name ----|--- -<%- sorted_opcodes.each do |opcode| -%> -`0x<%= opcode[0].to_s(16).rjust(2, '0') %>` | [`<%= opcode[1].name %>`](#<%= opcode[1].name %>) -<%- end -%> - -## Sections - -<%- @sections.each do |section| -%> -### <%= section.name %> - -<%= section.description %> - -<%- section.opcodes.each do |opcode| -%> -#### `<%= opcode.name %>` - -<%= opcode.description %> - -<% input = opcode.stack.input.join(", ") -%> -<% input += " " unless input.blank? -%> -<% output = opcode.stack.output.join(", ") -%> -<% output = " " + output unless output.blank? -%> -Opcode | Argument | Stack ----|---|--- -`0x<%= sorted_opcodes.find! { |opc| opc[1].name == opcode.name }[0].to_s(16).rjust(2, '0') %>` | <%= opcode.arg && "`#{opcode.arg}`" %> | <%= (opcode.stack.input.size > 0 || opcode.stack.output.size > 0) ? "`#{input}--#{output}`" : nil %> -<%- if opcode.example -%> - -```assembly -; Example -<%= opcode.example -%> -``` -<%- end -%> - -```assembly -; Microcode -<%= opcode.microcode -%> -``` - -<%- end -%> -<%- end -%> -> _Generated by the [hence](https://git.dergrimm.net/dergrimm/hence) firmware compiler._ diff --git a/src/hence/firmware/firmwares.cr b/src/hence/firmware/firmwares.cr deleted file mode 100644 index cc5edd3..0000000 --- a/src/hence/firmware/firmwares.cr +++ /dev/null @@ -1,13 +0,0 @@ -module Hence - class Firmware - {% begin %} - {% base_dir = "#{__DIR__}/firmwares" %} - - FIRMWARES = { - {% for firmware_name in run("./macros/get_firmwares.cr", base_dir).stringify.lines %} - {{ firmware_name }} => Firmware.from_yaml({{ read_file("#{base_dir.id}/#{firmware_name.id}.yml") }}), - {% end %} - } of String => Firmware - {% end %} - end -end diff --git a/src/hence/firmware/firmwares/default.yml b/src/hence/firmware/firmwares/default.yml deleted file mode 100644 index 51d1cf0..0000000 --- a/src/hence/firmware/firmwares/default.yml +++ /dev/null @@ -1,670 +0,0 @@ ---- -name: default -description: Default hence opcode firmware -authors: - - Dominic Grimm -date: 2022-05-14 -version: 0.1.0 - -sections: - - name: flow-control - description: Flow control - opcodes: - - name: nop - description: No operation - example: | - nop ; literally nothing but wasting precious cycles - opcode: null - arg: null - stack: - input: [] - output: [] - microcode: | - nop - - name: dbg - description: Debugs current stack - example: | - push 40 - push 2 - dbg ; => (2) [40, 2] - opcode: null - arg: null - stack: - input: [] - output: [] - microcode: | - dbg - - name: stp - description: Stops execution - example: | - stp ; program stops execution here. push will not be called - push 1 - opcode: null - arg: null - stack: - input: [] - output: [] - microcode: | - tmps 0xfffc - tmpl 0x0 - - name: jmp - description: Jumps to address - opcode: null - arg: null - stack: - input: [addr] - output: [] - microcode: | - drop - tmpl 0x0 - - name: jif - description: Jumps to address if condition is true (1) - opcode: null - arg: null - stack: - input: [cond, addr] - output: [] - microcode: | - drop - tmpl 0xc - drop - tmpl 0xa - tmpsr 0xc - tmplc 0x0 - - name: sts - description: Pushes value of status register to stack - opcode: null - arg: null - stack: - input: [] - output: ["status"] - microcode: | - tmpsr 0xd - push - - name: cls - description: Clears status register - opcode: null - arg: null - stack: - input: [] - output: [] - microcode: | - tmpl 0xd - - name: car - description: Pushes first bit of status register to stack - opcode: null - arg: null - stack: - input: [] - output: ["carry"] - microcode: | - tmpsr 0xd - tmpl 0xa - tmps 0b00000001 - tmpl 0xb - alu 0x01 - push - - name: clc - description: Clears carry flag - opcode: null - arg: null - stack: - input: [] - output: [] - microcode: | - tmpsr 0xd - tmpl 0xa - tmps 0b11111110 - tmpl 0xb - alu 0x01 - tmpl 0xd - - name: stack-manipulation - description: Stack manipulation - opcodes: - - name: push - description: Pushes value to stack - opcode: null - arg: x - stack: - input: [] - output: [x] - microcode: | - tmpsr 0x2 - push - - name: drop - description: Drops top element from stack - opcode: null - arg: null - stack: - input: [x] - output: [] - microcode: | - drop - - name: depth - description: Returns stack size / depth - opcode: null - arg: null - stack: - input: [] - output: [depth] - microcode: | - tmpsr 0x9 - push - - name: pick - description: Duplicates top nth element stack value - opcode: null - arg: null - stack: - input: ["n"] - output: [x] - microcode: | - drop - pick - push - - name: dup - description: Duplicates top element on stack - opcode: null - arg: null - stack: - input: [x] - output: [x, x] - microcode: | - pick - push - - name: swap - description: Swaps top two elements on stack - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x2, x1] - microcode: | - drop - tmpl 0xa - drop - tmpl 0xb - tmpsr 0xa - push - tmpsr 0xb - push - - name: over - description: Duplicates second top element onto stack - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x1, x2, x1] - microcode: | - tmps 1 - pick - push - - name: rot - description: Rotates top three elements on stack - opcode: null - arg: null - stack: - input: [x1, x2, x3] - output: [x2, x3, x1] - microcode: | - drop - tmpl 0xc - drop - tmpl 0xb - drop - tmpl 0xa - - tmpsr 0xb - push - tmpsr 0xc - push - tmpsr 0xa - push - - name: nip - description: Drops the first item below the top of stack - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x2] - microcode: | - drop - tmpl 0xa - drop - tmpsr 0xa - push - - name: tuck - description: Copies the top stack item below the second stack item - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x2, x1, x2] - microcode: | - drop - tmpl 0xa - drop - tmpl 0xb - tmpsr 0xa - push - tmpsr 0xb - push - tmpsr 0xa - push - - name: memory-access - description: Memory access - opcodes: - - name: load - description: Loads value from memory - opcode: null - arg: null - stack: - input: [addr] - output: [x] - microcode: | - drop - load - push - - name: store - description: Stores value to memory - opcode: null - arg: null - stack: - input: [x, addr] - output: [] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - tmpsr 0xb - store - - name: io - description: IO access - opcodes: - - name: inp - description: Gets input from general IO - opcode: null - arg: null - stack: - input: [] - output: [x] - microcode: | - tmpsr 0xe - push - - name: out - description: Outputs value to general IO - opcode: null - arg: null - stack: - input: [x] - output: [] - microcode: | - drop - tmpl 0xf - - name: print - description: Prints number to STDOUT / LCD display - opcode: null - arg: null - stack: - input: [x] - output: [] - microcode: | - drop - print - - name: emit - description: Prints input as unicode char to STDOUT / LCD display - opcode: null - arg: null - stack: - input: [x] - output: [] - microcode: | - drop - emit - - name: arithmetic - description: Arithmetic - opcodes: - - name: not - description: Logical invert of top stack element - opcode: null - arg: null - stack: - input: [x] - output: [x] - microcode: | - drop - tmpl 0xa - alu 0x00 - push - - name: and - description: Logical and of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x01 - push - - name: nand - description: Logical nand of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x02 - push - - name: or - description: Logical or of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x03 - push - - name: nor - description: Logical nor of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x04 - push - - name: xor - description: Logical xor of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x05 - push - - name: xnor - description: Logical xnor of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x06 - push - - name: shl - description: Shifts top stack element left by n bits - opcode: null - arg: null - stack: - input: [x, n] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x07 - push - - name: shr - description: Shifts top stack element right by n bits - opcode: null - arg: null - stack: - input: [x, n] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x08 - push - - name: add - description: Adds top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x09 - push - - name: inc - description: Increments top stack element by 1 - opcode: null - arg: null - stack: - input: [x] - output: [x] - microcode: | - tmps 1 - tmpl 0xb - drop - tmpl 0xa - alu 0x09 - push - - name: sub - description: Subtracts top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x0a - push - - name: dec - description: Decrements top stack element by 1 - opcode: null - arg: null - stack: - input: [x] - output: [x] - microcode: | - tmps 1 - tmpl 0xb - drop - tmpl 0xa - alu 0x0a - push - - name: mul - description: Multiplies top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x0b - push - - name: div - description: Divides top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x0c - push - - name: mod - description: Modulo of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x0d - push - - name: cmp - description: Compares top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x0e - push - - name: eq - description: Logical equality of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x0f - push - - name: neq - description: Logical inequality of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x10 - push - - name: lt - description: Logical less than of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x11 - push - - name: gt - description: Logical greater than of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x12 - push - - name: leq - description: Logical less than or equal to of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x13 - push - - name: geq - description: Logical greater than or equal to of top two stack elements - opcode: null - arg: null - stack: - input: [x1, x2] - output: [x] - microcode: | - drop - tmpl 0xb - drop - tmpl 0xa - alu 0x14 - push - - name: bol - description: Boolean of top stack element - opcode: null - arg: null - stack: - input: [x] - output: [x] - microcode: | - drop - tmpl 0xa - alu 0x15 - push - - name: neg - description: Negates top stack element - opcode: null - arg: null - stack: - input: [x] - output: [x] - microcode: | - drop - tmpl 0xa - alu 0x16 - push diff --git a/src/hence/firmware/macros/get_firmwares.cr b/src/hence/firmware/macros/get_firmwares.cr deleted file mode 100644 index 679aa0b..0000000 --- a/src/hence/firmware/macros/get_firmwares.cr +++ /dev/null @@ -1 +0,0 @@ -print Dir[Path[ARGV[0], "*.yml"]].map { |f| File.basename(f, ".yml") }.join("\n") diff --git a/src/hence/microcode.cr b/src/hence/microcode.cr deleted file mode 100644 index 6ef2ed6..0000000 --- a/src/hence/microcode.cr +++ /dev/null @@ -1,98 +0,0 @@ -require "colorize" -require "string_scanner" - -module Hence - struct Microcode - def initialize(@runner : Emulator::Data ->) - end - - def initialize(&@runner : Emulator::Data ->) - end - - def run(data : Emulator::Data) : Emulator::Data - @runner.call(data) - - data - end - - MICROCODES = { - 0x00_u8 => Microcode.new { }, # nop - 0x01_u8 => Microcode.new do |d| # dbg - stack = if d.reg_sp == 0 - [] of UInt16 - else - d.reg_sp.times.map_with_index { |_, i| d.get_memory({{ 32_u16 * 1024 }} + i) } - end - - puts "#{"[DEBUG]".colorize(:red).mode(:bold)}: (#{d.reg_sp}) #{stack}" - print "Press enter to continue execution...".colorize.mode(:dim) - gets - end, - 0x02_u8 => Microcode.new do |d| # jmp - d.reg_mccpc = d.reg_tmp - end, - 0x03_u8 => Microcode.new do |d| # jif - if d.reg_tmp == 1 - d.reg_mccpc = d.reg_a - end - end, - 0x04_u8 => Microcode.new do |d| # tmpl - d.set_register((d.reg_mccarg & 0xff).to_u8, d.reg_tmp) - end, - 0x05_u8 => Microcode.new do |d| # tmplc - d.set_register((d.reg_mccarg & 0xff).to_u8, d.reg_tmp) if d.reg_a == 1 - end, - 0x06_u8 => Microcode.new do |d| # tmps - d.reg_tmp = d.reg_mccarg - end, - 0x07_u8 => Microcode.new do |d| # tmpsr - d.reg_tmp = d.get_register((d.reg_mccarg & 0xff).to_u8) - end, - 0x08_u8 => Microcode.new do |d| # push - d.set_memory({{ 32_u16 * 1024 }} + d.reg_sp, d.reg_tmp) - d.reg_sp += 1 - end, - 0x09_u8 => Microcode.new do |d| # drop - d.reg_tmp = d.get_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1) - d.set_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1, 0_u16) - d.reg_sp -= 1 if d.reg_sp.positive? - end, - 0x0a_u8 => Microcode.new do |d| # pick - d.reg_tmp = d.get_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1 - d.reg_tmp) - end, - 0x0b_u8 => Microcode.new do |d| # alu - d.alu((d.reg_mccarg & 0xff).to_u8) - end, - 0x0c_u8 => Microcode.new do |d| # load - d.reg_tmp = d.get_memory(d.reg_tmp) - end, - 0x0d_u8 => Microcode.new do |d| # store - d.set_memory(d.reg_tmp, d.reg_a) - end, - 0x0e_u8 => Microcode.new do |d| # print - print d.reg_tmp - end, - 0x0f_u8 => Microcode.new do |d| # emit - print String.new(Bytes[d.reg_tmp]) - end, - } of UInt8 => Microcode - MICROCODE_NAMES = { - "nop" => 0x00_u8, - "dbg" => 0x01_u8, - "jmp" => 0x02_u8, - "jif" => 0x03_u8, - "tmpl" => 0x04_u8, - "tmplc" => 0x05_u8, - "tmps" => 0x06_u8, - "tmpsr" => 0x07_u8, - "push" => 0x08_u8, - "drop" => 0x09_u8, - "pick" => 0x0a_u8, - "alu" => 0x0b_u8, - "load" => 0x0c_u8, - "store" => 0x0d_u8, - "print" => 0x0e_u8, - "emit" => 0x0f_u8, - } of String => UInt8 - end -end diff --git a/src/hence/opcode.cr b/src/hence/opcode.cr deleted file mode 100644 index b5d21be..0000000 --- a/src/hence/opcode.cr +++ /dev/null @@ -1,9 +0,0 @@ -module Hence - struct Opcode - property name - property microcode - - def initialize(@name : String, @microcode : Parser::AST) - end - end -end diff --git a/src/hence/parser.cr b/src/hence/parser.cr deleted file mode 100644 index dd1d20e..0000000 --- a/src/hence/parser.cr +++ /dev/null @@ -1,385 +0,0 @@ -require "string_scanner" - -module Hence - module Parser - extend self - - # ameba:disable Metrics/CyclomaticComplexity - def parse(source : String) : AST - s = StringScanner.new(source.gsub(/;.*|@.*|#.*/, nil)) - body = [] of AST::Node - loop do - s.skip(/\s*/) - break if s.eos? - - body << - if constant_dirty_name = s.scan(/\S+[ \t]*=/) - constant_name = constant_dirty_name[..-2].rstrip - - s.skip(/[ \t]*/) - - constant_values = parse_args(s).not_nil! - raise "Constant can't have more than one value" if constant_values.size > 1 - - AST::ConstantDeclarationNode.new(constant_name, constant_values.first) - elsif label_dirty_name = s.scan(/\S+:/) - label_name = label_dirty_name.not_nil![..-2] - raise "Label name must be lowercase" if label_name != label_name.downcase - - AST::LabelNode.new(label_name) - # else - # call_name = s.scan(/\S+/).not_nil! - - # s.skip(/[ \t]*/) - - # if call_name.starts_with?('.') - # AST::MacroCallNode.new(call_name[1..], self.parse_args(s) || [] of AST::Arg) - # else - # call_args = self.parse_args(s) - # raise "Op code can't be called with more than one argument" if call_args && call_args.size > 1 - - # AST::CallNode.new(call_name, call_args.try(&.first?)) - # end - elsif macro_name_raw = s.scan(/\.\S+/) - s.skip(/[ \t]*/) - - AST::MacroCallNode.new(macro_name_raw[1..], parse_args(s) || [] of AST::Arg) - else - call_name = s.scan(/\S+/).not_nil! - - s.skip(/[ \t]*/) - - call_args = parse_args(s) || [] of AST::Arg - raise "Op code can't be called with more than one argument" if call_args && call_args.size > 1 - - AST::CallNode.new(call_name, call_args.first?) - end - end - - AST.new(body) - end - - def parse_binary_operation(op : String) : AST::BinaryOperation - case op - when "+" - AST::AddBinaryOperation - when "-" - AST::SubBinaryOperation - when "*" - AST::MulBinaryOperation - when "/" - AST::DivBinaryOperation - when "**" - AST::PowBinaryOperation - else - raise "Unexpected binary operation: #{op}" - end.new - end - - def parse_binary_expression_arg(scanner : StringScanner) : AST::BinaryExpressionArg? - scanner.skip(/[ \t]*/) - return unless scanner.scan(/\(/) - - content = scanner.scan_until(/\)/).not_nil![..-2].split(/\s/, remove_empty: true) - raise "Malformed binary expression" if content.size < 3 || content.size % 2 == 0 - - first_args = content.shift(3) - bin_expr = AST::BinaryExpressionArg.new( - parse_args(first_args[0] + "\n").not_nil!.first, - parse_args(first_args[2] + "\n").not_nil!.first, - parse_binary_operation(first_args[1]) - ) - - until content.empty? - args = content.shift(2).not_nil! - bin_expr = AST::BinaryExpressionArg.new( - bin_expr, - parse_args(args[1] + "\n").not_nil!.first, - parse_binary_operation(args[0]) - ) - end - - bin_expr - end - - def parse_binary_expression_arg(string : String) : AST::BinaryExpressionArg? - parse_binary_expression_arg(StringScanner.new(string)) - end - - def parse_args(scanner : StringScanner) : Array(AST::Arg)? - args_s = scanner.scan_until(/\n/) - - if args_s.try(&.blank?) - [] of AST::Arg - else - s = StringScanner.new(args_s.not_nil!.strip) - args = [] of AST::Arg - - until s.eos? - s.skip(/[ \t]*/) - raise "Can't seperate arguments because of no comma" if s.skip(/,/).nil? && args.size > 0 - s.skip(/[ \t]*/) - - args << - if s.check(/\(/) - parse_binary_expression_arg(s).not_nil! - elsif string_raw = s.scan(/".*"/) - AST::StringArg.new(string_raw[1..-2].gsub(/\\n/, '\n')) - elsif number = s.scan(/\d[\d\w]*/) - AST::NumberArg.new(number.to_u16(prefix: true, underscore: true, strict: true)) - else - AST::ConstantArg.new(s.scan(/\w+/).not_nil!) - end - end - - args - end - end - - def parse_args(string : String) : Array(AST::Arg)? - parse_args(StringScanner.new(string)) - end - - struct AST - property body - - def initialize(@body : Body) - end - - def to_s : String - @body.map(&.to_s).join('\n') - end - - abstract struct Node - abstract def to_s : String - end - - alias Body = Array(Node) - - struct LabelNode < Node - property name - - def initialize(@name : String) - end - - def to_s : String - "#{@name}:" - end - end - - abstract class Arg - abstract def resolve(data : Assembler::RawData) : Bytes - - abstract def resolve_as_number(data : Assembler::RawData) : UInt16 - - abstract def to_s : String - end - - class NumberArg < Arg - property number - - def initialize(@number : UInt16) - end - - def resolve(data : Assembler::RawData) : Bytes - n = Utils.split_u16(@number) - Bytes[n[0], n[1]] - end - - def resolve_as_number(data : Assembler::RawData) : UInt16 - @number - end - - def to_s : String - @number.to_s - end - end - - class ConstantArg < Arg - property name - - def initialize(@name : String) - end - - def resolve(data : Assembler::RawData) : Bytes - data.constants[@name].resolve(data) - end - - def resolve_as_number(data : Assembler::RawData) : UInt16 - data.constants[@name].resolve_as_number(data) - end - - def to_s : String - @name - end - end - - class BytesArg < Arg - property bytes - - def initialize(@bytes : Bytes) - end - - def resolve(data : Assembler::RawData) : Bytes - @bytes - end - - def resolve_as_number(data : Assembler::RawData) : UInt16 - if @bytes.size == 1 - @bytes[0].to_u16 - else - Utils.merge_u8(@bytes[0], @bytes[1]) - end - end - - def to_s : String - "\"#{String.new(@bytes).gsub(/\n/, "\\n")}\"" - end - end - - class StringArg < Arg - property string - - def initialize(@string : String) - end - - def resolve(data : Assembler::RawData) : Bytes - if @string.bytesize == 1 - Bytes[0_u8, @string.bytes[0]] - else - Bytes.new(@string.bytes.to_unsafe, @string.bytesize) - end - end - - def resolve_as_number(data : Assembler::RawData) : UInt16 - slice = resolve(data) - - Utils.merge_u8(slice[0], slice[1]) - end - - def to_s : String - "\"#{@string}\"" - end - end - - abstract struct BinaryOperation - abstract def resolve(left : Arg, right : Arg, data : Assembler::RawData) : Arg - - abstract def to_s : String - end - - struct AddBinaryOperation < BinaryOperation - def resolve(left : Arg, right : Arg, data : Assembler::RawData) : Arg - NumberArg.new(left.resolve_as_number(data) &+ right.resolve_as_number(data)) - end - - def to_s : String - "+" - end - end - - struct SubBinaryOperation < BinaryOperation - def resolve(left : Arg, right : Arg, data : Assembler::RawData) : Arg - NumberArg.new(left.resolve_as_number(data) &- right.resolve_as_number(data)) - end - - def to_s : String - "-" - end - end - - struct MulBinaryOperation < BinaryOperation - def resolve(left : Arg, right : Arg, data : Assembler::RawData) : Arg - NumberArg.new(left.resolve_as_number(data) &* right.resolve_as_number(data)) - end - - def to_s : String - "*" - end - end - - struct DivBinaryOperation < BinaryOperation - def resolve(left : Arg, right : Arg, data : Assembler::RawData) : Arg - NumberArg.new(left.resolve_as_number(data) // right.resolve_as_number(data)) - end - - def to_s : String - "/" - end - end - - struct PowBinaryOperation < BinaryOperation - def resolve(left : Arg, right : Arg, data : Assembler::RawData) : Arg - NumberArg.new(left.resolve_as_number(data) &** right.resolve_as_number(data)) - end - - def to_s : String - "**" - end - end - - class BinaryExpressionArg < Arg - property left - property right - property op - - def initialize(@left : Arg, @right : Arg, @op : BinaryOperation) - end - - def resolve(data : Assembler::RawData) : Bytes - @op.resolve(@left, @right, data).resolve(data) - end - - def resolve_as_number(data : Assembler::RawData) : UInt16 - @op.resolve(@left, @right, data).resolve_as_number(data) - end - - def to_s : String - # ameba:disable Lint/RedundantStringCoercion - "(#{@left.to_s} #{@op.to_s} #{@right.to_s})" - end - end - - struct ConstantDeclarationNode < Node - property name - property value - - def initialize(@name : String, @value : Arg) - end - - def to_s : String - "#{@name}" - end - end - - struct MacroCallNode < Node - property name - property args - - def initialize(@name : String, @args : Array(Arg)) - end - - def to_s : String - ".#{@name}" - end - end - - struct CallNode < Node - property name - property arg - - def initialize(@name : String, @arg : Arg? = nil) - end - - def to_s : String - if @arg - # ameba:disable Lint/RedundantStringCoercion - "#{@name} #{@arg.to_s}" - else - @name - end - end - end - end - end -end diff --git a/src/hence/utils.cr b/src/hence/utils.cr deleted file mode 100644 index b735c87..0000000 --- a/src/hence/utils.cr +++ /dev/null @@ -1,16 +0,0 @@ -module Hence - module Utils - extend self - - macro split_u16(x) - { - ({{ x }} >> 8).to_u8, - ({{ x }} & 0xff_u8).to_u8 - } - end - - macro merge_u8(x, y) - ({{ x }}.to_u16 << 8) | {{ y }} - end - end -end diff --git a/src/hence/version.cr b/src/hence/version.cr deleted file mode 100644 index f65670a..0000000 --- a/src/hence/version.cr +++ /dev/null @@ -1,5 +0,0 @@ -require "version_from_shard" - -module Hence - VersionFromShard.declare -end