Rewrite everything in rust
This commit is contained in:
parent
db5aeb333c
commit
ffad349a41
46 changed files with 1698 additions and 2410 deletions
11
.drone.yml
11
.drone.yml
|
@ -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
|
|
@ -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
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1,8 +1 @@
|
|||
/docs/
|
||||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
||||
*.dwarf
|
||||
./examples/*.bin
|
||||
examples/*.bin
|
||||
/.vscode/
|
||||
/target
|
||||
|
|
422
Cargo.lock
generated
Normal file
422
Cargo.lock
generated
Normal file
|
@ -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"
|
2
Cargo.toml
Normal file
2
Cargo.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[workspace]
|
||||
members = ["hence", "hencelisp"]
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Dominic Grimm <dominic.grimm@gmail.com>
|
||||
|
||||
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.
|
28
README.md
28
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 | |
|
||||
|
|
|
@ -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
|
|
@ -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
|
22
examples/test.asm
Normal file
22
examples/test.asm
Normal file
|
@ -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
|
6
examples/test.lisp
Normal file
6
examples/test.lisp
Normal file
|
@ -0,0 +1,6 @@
|
|||
(module test
|
||||
"Main module"
|
||||
(defun main ()
|
||||
"Program entrypoint"
|
||||
(let ((x (+ 40 2))))
|
||||
(debug x)))
|
1
hence/.gitignore
vendored
Normal file
1
hence/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
22
hence/Cargo.toml
Normal file
22
hence/Cargo.toml
Normal file
|
@ -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"
|
93
hence/src/bin/main.rs
Normal file
93
hence/src/bin/main.rs
Normal file
|
@ -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<String>,
|
||||
#[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<u8> = 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();
|
||||
}
|
||||
}
|
||||
}
|
249
hence/src/lib/arg.rs
Normal file
249
hence/src/lib/arg.rs
Normal file
|
@ -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<Self>,
|
||||
right: Box<Self>,
|
||||
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<u16, String> {
|
||||
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<arg::Arg> = 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<Vec<u8>, 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<arg::Arg> = 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<BinaryExpressionOperator, &'static str> {
|
||||
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<Arg, &'static str> {
|
||||
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<Vec<Arg>, &str> {
|
||||
let mut iter = tokens.iter();
|
||||
let mut args: Vec<Arg> = 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)
|
||||
}
|
186
hence/src/lib/assembler.rs
Normal file
186
hence/src/lib/assembler.rs
Normal file
|
@ -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<u16, String>;
|
||||
|
||||
fn resolve_bytes(&self, data: &mut Data) -> Result<Vec<u8>, String>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Data {
|
||||
pub dir: String,
|
||||
pub program: [u8; 32 * 1024],
|
||||
pub offset: u16,
|
||||
pub contants: HashMap<String, arg::Arg>,
|
||||
}
|
||||
|
||||
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::<String>(),
|
||||
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(())
|
||||
}
|
328
hence/src/lib/emulator.rs
Normal file
328
hence/src/lib/emulator.rs
Normal file
|
@ -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(())
|
||||
}
|
165
hence/src/lib/lexer.rs
Normal file
165
hence/src/lib/lexer.rs
Normal file
|
@ -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<Vec<Token>, String> {
|
||||
let mut chars = source.chars().peekable();
|
||||
let mut tokens = Vec::<Token>::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::<String>(),
|
||||
));
|
||||
}
|
||||
'@' => {
|
||||
chars.next();
|
||||
chars.next_if(|c| *c == '@');
|
||||
|
||||
tokens.push(Token::Comment(
|
||||
chars.peeking_take_while(|c| *c != '\n').collect::<String>(),
|
||||
));
|
||||
}
|
||||
'"' => {
|
||||
chars.next();
|
||||
tokens.push(Token::StringLiteral(
|
||||
chars.by_ref().take_while(|c| *c != '"').collect::<String>(),
|
||||
));
|
||||
}
|
||||
'.' => {
|
||||
chars.next();
|
||||
tokens.push(Token::MacroLiteral(format!(
|
||||
".{}",
|
||||
chars
|
||||
.peeking_take_while(|c| c.is_alphabetic() || c.is_numeric())
|
||||
.collect::<String>()
|
||||
)));
|
||||
}
|
||||
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::<String>(),
|
||||
));
|
||||
}
|
||||
',' => {
|
||||
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::<String>(),
|
||||
));
|
||||
}
|
||||
ch if ch.is_whitespace() => {
|
||||
tokens.push(Token::Whitespace(
|
||||
chars
|
||||
.peeking_take_while(|c| c.is_whitespace() && *c != '\n')
|
||||
.collect::<String>(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
// tokens.push(Token::Error(ch.to_string()));
|
||||
// chars.next();
|
||||
return Err(format!("Unexpected token: '{ch}'"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
5
hence/src/lib/lib.rs
Normal file
5
hence/src/lib/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod arg;
|
||||
pub mod assembler;
|
||||
pub mod emulator;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
68
hence/src/lib/parser.rs
Normal file
68
hence/src/lib/parser.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use itertools::PeekingNext;
|
||||
|
||||
use crate::arg;
|
||||
use crate::lexer;
|
||||
|
||||
pub mod ast;
|
||||
|
||||
pub fn parse(tokens: Vec<lexer::Token>) -> Result<ast::AST, &'static str> {
|
||||
let mut iter = tokens.iter().peekable();
|
||||
let mut body: Vec<ast::Node> = 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 })
|
||||
}
|
49
hence/src/lib/parser/ast.rs
Normal file
49
hence/src/lib/parser/ast.rs
Normal file
|
@ -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<arg::Arg> },
|
||||
MacroCall { name: String, args: Vec<arg::Arg> },
|
||||
}
|
||||
|
||||
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<Node>,
|
||||
}
|
||||
|
||||
impl assembler::ToAssembly for AST {
|
||||
fn to_assembly(&self) -> String {
|
||||
self.body.iter().map(|n| n.to_assembly()).join("\n")
|
||||
}
|
||||
}
|
1
hencelisp/.gitignore
vendored
Normal file
1
hencelisp/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
17
hencelisp/Cargo.toml
Normal file
17
hencelisp/Cargo.toml
Normal file
|
@ -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"] }
|
29
hencelisp/src/bin/main.rs
Normal file
29
hencelisp/src/bin/main.rs
Normal file
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
1
hencelisp/src/lib/lexer.rs
Normal file
1
hencelisp/src/lib/lexer.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub fn lex(source: String) {}
|
1
hencelisp/src/lib/lib.rs
Normal file
1
hencelisp/src/lib/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod lexer;
|
3
hencelisp/src/main.rs
Normal file
3
hencelisp/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
\usepackage{sectsty}
|
||||
\sectionfont{\underline\clearpage}
|
20
pdf/pdf.sh
20
pdf/pdf.sh
|
@ -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"
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
10
shard.lock
10
shard.lock
|
@ -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
|
||||
|
19
shard.yml
19
shard.yml
|
@ -1,19 +0,0 @@
|
|||
name: hence
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Dominic Grimm <dominic.grimm@gmail.com>
|
||||
|
||||
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
|
201
src/cli/hence.cr
201
src/cli/hence.cr
|
@ -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 <firmware> [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 <firmware> [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 <src>"
|
||||
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 <src> [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 <bin>"
|
||||
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)
|
|
@ -1 +0,0 @@
|
|||
require "./hence/*"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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._
|
|
@ -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
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
print Dir[Path[ARGV[0], "*.yml"]].map { |f| File.basename(f, ".yml") }.join("\n")
|
|
@ -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
|
|
@ -1,9 +0,0 @@
|
|||
module Hence
|
||||
struct Opcode
|
||||
property name
|
||||
property microcode
|
||||
|
||||
def initialize(@name : String, @microcode : Parser::AST)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||
require "version_from_shard"
|
||||
|
||||
module Hence
|
||||
VersionFromShard.declare
|
||||
end
|
Loading…
Reference in a new issue