Rewrite everything in rust

This commit is contained in:
Dominic Grimm 2022-07-17 20:24:49 +02:00
parent db5aeb333c
commit ffad349a41
No known key found for this signature in database
GPG key ID: A6C051C716D2CE65
46 changed files with 1698 additions and 2410 deletions

View file

@ -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

View file

@ -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
View file

@ -1,8 +1 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf
./examples/*.bin
examples/*.bin
/.vscode/
/target

422
Cargo.lock generated Normal file
View 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
View file

@ -0,0 +1,2 @@
[workspace]
members = ["hence", "hencelisp"]

21
LICENSE
View file

@ -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.

View file

@ -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 | |

View file

@ -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

View file

@ -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
View 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
View 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
View file

@ -0,0 +1 @@
/target

22
hence/Cargo.toml Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 })
}

View 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
View file

@ -0,0 +1 @@
/target

17
hencelisp/Cargo.toml Normal file
View 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
View 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}");
}
}
}

View file

@ -0,0 +1 @@
pub fn lex(source: String) {}

1
hencelisp/src/lib/lib.rs Normal file
View file

@ -0,0 +1 @@
pub mod lexer;

3
hencelisp/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View file

@ -1,2 +0,0 @@
\usepackage{sectsty}
\sectionfont{\underline\clearpage}

View file

@ -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"

View file

@ -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
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -1 +0,0 @@
require "./hence/*"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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._

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
print Dir[Path[ARGV[0], "*.yml"]].map { |f| File.basename(f, ".yml") }.join("\n")

View file

@ -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

View file

@ -1,9 +0,0 @@
module Hence
struct Opcode
property name
property microcode
def initialize(@name : String, @microcode : Parser::AST)
end
end
end

View file

@ -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

View file

@ -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

View file

@ -1,5 +0,0 @@
require "version_from_shard"
module Hence
VersionFromShard.declare
end