Watch
0
0
Fork
You've already forked echse
0
mirror of synced 2026-11-05 13:16:16 +01:00

initial commit

This commit is contained in:
Vinzenz Schroeter 2024-12-13 21:49:55 +01:00
commit 5ad6baa30d
35 changed files with 2877 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target
tarpaulin-report.html

456
Cargo.lock generated Normal file
View file

@ -0,0 +1,456 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "ariadne"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31beedec3ce83ae6da3a79592b3d8d7afd146a5b15bb9bb940279aced60faa89"
dependencies = [
"unicode-width",
"yansi",
]
[[package]]
name = "cc"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
dependencies = [
"hashbrown",
"stacker",
]
[[package]]
name = "clap"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "echse"
version = "0.1.0"
dependencies = [
"ariadne",
"chumsky",
]
[[package]]
name = "echse_cute"
version = "0.1.0"
dependencies = [
"clap",
"echse",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "krabbeltier"
version = "0.1.0"
dependencies = [
"clap",
"echse",
"shellwords",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "psm"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
dependencies = [
"cc",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "shellwords"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e515aa4699a88148ed5ef96413ceef0048ce95b43fbc955a33bde0a70fcae6"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "stacker"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"windows-sys",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

8
Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[workspace]
members = ["echse_cute", "krabbeltier", "echse"]
resolver = "2"
[workspace.dependencies]
clap = { version = "4.5.23", features = ["derive"] }
shellwords = { version = "1.1.0" }
ariadne = { version = "0.5.0", features = ["auto-color"] }

48
README.md Normal file
View file

@ -0,0 +1,48 @@
# echse
The cutest assembly-like language!
This is for learning or teaching purposes only - and is probably not useful for anything else.
## From code to running
1. Code with macros (.es)
2. Preprocessor applies macros and resolves jump labels (.ei)
3. Transform into binary representation (.eo)
4. Run the binary
Currently, you have to write and execute a .ei file.
## Ideas and ToDos
- a cute manual for the provided functionality
- preprocessor that supports jumping to labels and unrolled loops
- separate machine configuration and program in files
- register custom instructions before parsing
- optional stack with pus, pop, ret
- small games built upon this:
- battle multiple programs against each other
- rustlings-like exercises to learn the instructions
- put multiple echsen into an environment, interact with environment
## Limitations
List of constraints that I want to keep:
- general-purpose registers
- are always interpreted as native signed integers
- are numbered r0...rn, without gaps
- all base instruction
- have names with 3 letters
- all test instructions start with i (after prefixes) and always set r0
- all conditional jumps act based upon r0
- when interpreting numbers as booleans: 0 is false, everything else is true. The name of instructions should reflect that.
- all extension instructions
- have names longer than 3 letters
- have a extension prefix
- the executed instructions do not know anything about the preprocessor and all formatting and jump marks are lost
- there should be no hard-coded calling convention
## License
The code in this repository is licensed under GPL v2 or later, unless stated otherwise in the file.

13
all_instructions.ei Normal file
View file

@ -0,0 +1,13 @@
add 1 2 r0
add r0 r1 r0
mod r0 10 r1
div 100 r1 r1
nop
put 42 r1
put r1 r0
sub 10 5 r1
sub r1 r1 r1
teq r1 r1
tlt r0 r1
tze r1
jze r0 r1

14
echse/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "echse"
version = "0.1.0"
edition = "2021"
[lib]
[dependencies]
chumsky = "0.9.3"
ariadne = {version = "0.5.0", optional = true}
[features]
default = ["error-report"]
error-report = ["dep:ariadne"]

View file

@ -0,0 +1,135 @@
use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::Display;
use std::rc::Rc;
#[derive(Debug)]
pub struct Addition {
/// first input
pub a: RegisterOrValue,
/// second input
pub b: RegisterOrValue,
/// result register
pub r: RegisterIndex,
}
impl Instruction for Addition {
fn execute(&self, machine: &mut Machine) {
let value = self.a.read(machine) + self.b.read(machine);
self.r.write(machine, value);
}
}
impl InstructionName for Addition {
const INSTRUCTION_NAME: &'static str = "add";
}
impl Display for Addition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"{} {} {} {}",
Self::INSTRUCTION_NAME,
self.a,
self.b,
self.r
)
}
}
impl TryLoadInstruction for Addition {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
let (a, b, r) = try_load_binary_op(params)?;
Ok(Rc::new(Self { a, b, r }))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Addition, Instruction, ParametersError, TryLoadInstruction};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn execute() {
let r0 = RegisterIndex::new(0);
let r1 = RegisterIndex::new(1);
let mut machine = MachineBuilder::new()
.with_registers(2)
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0, 0]);
Instruction::execute(
&Addition {
a: RegisterOrValue::Value(42),
b: RegisterOrValue::Value(23),
r: r0,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![65, 0]);
Instruction::execute(
&Addition {
a: RegisterOrValue::Register(r0),
b: RegisterOrValue::Value(-23),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![65, 42]);
Instruction::execute(
&Addition {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(r1),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![65, 1379]);
}
#[test]
fn load() {
assert!(matches!(
Addition::try_load(&[]),
Err(ParametersError::UnexpectedCount {
found: 0,
expected: 3
})
));
assert!(matches!(
Addition::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Value(1337),
]),
Err(ParametersError::UnexpectedType { index: 2 })
));
assert!(matches!(
Addition::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Register(RegisterIndex::R0),
]),
Ok(_)
));
}
#[test]
fn print() {
assert_eq!(
"add 1337 r0 r1",
format!(
"{}",
Addition {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(RegisterIndex::R0),
r: RegisterIndex(1),
}
)
);
}
}

View file

@ -0,0 +1,47 @@
use crate::instructions::*;
use std::fmt::Display;
use std::ops::Index;
use std::rc::Rc;
#[derive(Debug)]
pub struct Instructions(Vec<Rc<dyn Instruction>>);
impl Instructions {
pub fn empty() -> Self {
Self(vec![])
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Rc<dyn Instruction>> {
self.0.iter()
}
}
impl From<Vec<Rc<dyn Instruction>>> for Instructions {
fn from(instructions: Vec<Rc<dyn Instruction>>) -> Self {
Self(instructions)
}
}
impl Index<usize> for Instructions {
type Output = Rc<dyn Instruction>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl Display for Instructions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (index, instr) in self.iter().enumerate() {
f.write_fmt(format_args!("{index:03}: {instr}\n"))?;
}
Ok(())
}
}

View file

@ -0,0 +1,135 @@
use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct Division {
/// first input
pub a: RegisterOrValue,
/// second input
pub b: RegisterOrValue,
/// result register
pub r: RegisterIndex,
}
impl Instruction for Division {
fn execute(&self, machine: &mut Machine) {
let value = self.a.read(machine) / self.b.read(machine);
self.r.write(machine, value)
}
}
impl InstructionName for Division {
const INSTRUCTION_NAME: &'static str = "div";
}
impl Display for Division {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"{} {} {} {}",
Self::INSTRUCTION_NAME,
self.a,
self.b,
self.r
)
}
}
impl TryLoadInstruction for Division {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
let (a, b, r) = try_load_binary_op(params)?;
Ok(Rc::new(Self { a, b, r }))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Division, Instruction, ParametersError, TryLoadInstruction};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn execute() {
let r0 = RegisterIndex::new(0);
let r1 = RegisterIndex::new(1);
let mut machine = MachineBuilder::new()
.with_registers(2)
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0, 0]);
Instruction::execute(
&Division {
a: RegisterOrValue::Value(42),
b: RegisterOrValue::Value(10),
r: r0,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![4, 0]);
Instruction::execute(
&Division {
a: RegisterOrValue::Register(r0),
b: RegisterOrValue::Value(-1),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![4, -4]);
Instruction::execute(
&Division {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(r1),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![4, -334]);
}
#[test]
fn load() {
assert!(matches!(
Division::try_load(&[]),
Err(ParametersError::UnexpectedCount {
found: 0,
expected: 3
})
));
assert!(matches!(
Division::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Value(1337),
]),
Err(ParametersError::UnexpectedType { index: 2 })
));
assert!(matches!(
Division::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Register(RegisterIndex::R0),
]),
Ok(_)
));
}
#[test]
fn print() {
assert_eq!(
"div 1337 r0 r1",
format!(
"{}",
Division {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(RegisterIndex::R0),
r: RegisterIndex(1),
}
)
);
}
}

View file

@ -0,0 +1,72 @@
use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct Jump {
/// first in
pub to: RegisterOrValue,
}
impl Instruction for Jump {
fn execute(&self, machine: &mut Machine) {
machine.ip = self.to.read(machine) as usize;
}
}
impl InstructionName for Jump {
const INSTRUCTION_NAME: &'static str = "jmp";
}
impl Display for Jump {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{} {}", Self::INSTRUCTION_NAME, self.to)
}
}
impl TryLoadInstruction for Jump {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
check_param_count(params, 1)?;
Ok(Rc::new(Jump { to: params[0] }))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, Jump};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn jump() {
let r0 = RegisterIndex::new(0);
let mut machine = MachineBuilder::new()
.minimal_registers()
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0]);
assert_eq!(machine.ip, 0);
Instruction::execute(
&Jump {
to: RegisterOrValue::Value(42),
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![0]);
assert_eq!(machine.ip, 42);
machine.registers[0].write(19);
assert_eq!(machine.registers.read_all(), vec![19]);
Instruction::execute(
&Jump {
to: RegisterOrValue::Register(r0),
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19]);
assert_eq!(machine.ip, 19);
}
}

View file

@ -0,0 +1,80 @@
use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct JumpIfZero {
pub a: RegisterOrValue,
pub to: RegisterOrValue,
}
impl Instruction for JumpIfZero {
fn execute(&self, machine: &mut Machine) {
if self.a.read(machine) == 0 {
machine.ip = self.to.read(machine) as usize;
}
}
}
impl InstructionName for JumpIfZero {
const INSTRUCTION_NAME: &'static str = "jze";
}
impl Display for JumpIfZero {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.to)
}
}
impl TryLoadInstruction for JumpIfZero {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
check_param_count(params, 2)?;
Ok(Rc::new(JumpIfZero {
a: params[0],
to: params[1],
}))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, JumpIfZero};
use crate::machine::RegisterOrValue::Value;
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn execute() {
let r0 = RegisterIndex::new(0);
let mut machine = MachineBuilder::new()
.minimal_registers()
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0]);
assert_eq!(machine.ip, 0);
Instruction::execute(
&JumpIfZero {
a: Value(0),
to: Value(42),
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![0]);
assert_eq!(machine.ip, 42);
machine.registers[0].write(19);
assert_eq!(machine.registers.read_all(), vec![19]);
Instruction::execute(
&JumpIfZero {
a: RegisterOrValue::Register(r0),
to: RegisterOrValue::Register(r0),
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19]);
assert_eq!(machine.ip, 42);
}
}

View file

@ -0,0 +1,29 @@
mod add_instruction;
mod collection;
mod div_instruction;
mod jmp_instruction;
mod jze_instruction;
mod mod_instruction;
mod nop_instruction;
mod put_instruction;
mod sub_instruction;
mod teq_instruction;
mod tlt_instruction;
mod traits;
mod try_load;
mod tze_instruction;
pub use add_instruction::*;
pub use collection::*;
pub use div_instruction::*;
pub use jmp_instruction::*;
pub use jze_instruction::*;
pub use mod_instruction::*;
pub use nop_instruction::*;
pub use put_instruction::*;
pub use sub_instruction::*;
pub use teq_instruction::*;
pub use tlt_instruction::*;
pub use traits::*;
pub use try_load::*;
pub use tze_instruction::*;

View file

@ -0,0 +1,135 @@
use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct Modulus {
/// first input
pub a: RegisterOrValue,
/// second input
pub b: RegisterOrValue,
/// result register
pub r: RegisterIndex,
}
impl Instruction for Modulus {
fn execute(&self, machine: &mut Machine) {
let value = self.a.read(machine) % self.b.read(machine);
self.r.write(machine, value)
}
}
impl InstructionName for Modulus {
const INSTRUCTION_NAME: &'static str = "mod";
}
impl Display for Modulus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"{} {} {} {}",
Self::INSTRUCTION_NAME,
self.a,
self.b,
self.r
)
}
}
impl TryLoadInstruction for Modulus {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
let (a, b, r) = try_load_binary_op(params)?;
Ok(Rc::new(Self { a, b, r }))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, Modulus, ParametersError, TryLoadInstruction};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn execute() {
let r0 = RegisterIndex::new(0);
let r1 = RegisterIndex::new(1);
let mut machine = MachineBuilder::new()
.with_registers(2)
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0, 0]);
Instruction::execute(
&Modulus {
a: RegisterOrValue::Value(42),
b: RegisterOrValue::Value(23),
r: r0,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19, 0]);
Instruction::execute(
&Modulus {
a: RegisterOrValue::Register(r0),
b: RegisterOrValue::Value(-23),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19, 19]);
Instruction::execute(
&Modulus {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(r1),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19, 7]);
}
#[test]
fn load() {
assert!(matches!(
Modulus::try_load(&[]),
Err(ParametersError::UnexpectedCount {
found: 0,
expected: 3
})
));
assert!(matches!(
Modulus::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Value(1337),
]),
Err(ParametersError::UnexpectedType { index: 2 })
));
assert!(matches!(
Modulus::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Register(RegisterIndex::R0),
]),
Ok(_)
));
}
#[test]
fn print() {
assert_eq!(
"mod 1337 r0 r1",
format!(
"{}",
Modulus {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(RegisterIndex::R0),
r: RegisterIndex(1),
}
)
);
}
}

View file

@ -0,0 +1,32 @@
use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct NoOperation {}
impl Instruction for NoOperation {
fn execute(&self, _: &mut Machine) {
// intentionally left empty as this is the no operation instruction
}
}
impl InstructionName for NoOperation {
const INSTRUCTION_NAME: &'static str = "nop";
}
impl TryLoadInstruction for NoOperation {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
check_param_count(params, 0)?;
Ok(Rc::new(NoOperation {}))
}
}
impl Display for NoOperation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(Self::INSTRUCTION_NAME)
}
}

View file

@ -0,0 +1,79 @@
use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct Put {
/// input
pub a: RegisterOrValue,
/// result register
pub r: RegisterIndex,
}
impl Instruction for Put {
fn execute(&self, machine: &mut Machine) {
let value = self.a.read(machine);
self.r.write(machine, value);
}
}
impl InstructionName for Put {
const INSTRUCTION_NAME: &'static str = "put";
}
impl Display for Put {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.r)
}
}
impl TryLoadInstruction for Put {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
check_param_count(params, 2)?;
let r = match params[1] {
RegisterOrValue::Register(r) => r,
RegisterOrValue::Value(_) => return Err(ParametersError::UnexpectedType { index: 2 }),
};
Ok(Rc::new(Self { a: params[0], r }))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, Put};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn put() {
let r0 = RegisterIndex::new(0);
let r1 = RegisterIndex::new(1);
let mut machine = MachineBuilder::new()
.with_registers(2)
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0, 0]);
Instruction::execute(
&Put {
a: RegisterOrValue::Value(42),
r: r0,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![42, 0]);
Instruction::execute(
&Put {
a: RegisterOrValue::Register(r0),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![42, 42]);
}
}

View file

@ -0,0 +1,135 @@
use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct Subtraction {
/// first input
pub a: RegisterOrValue,
/// second input
pub b: RegisterOrValue,
/// result register
pub r: RegisterIndex,
}
impl Instruction for Subtraction {
fn execute(&self, machine: &mut Machine) {
let value = self.a.read(machine) - self.b.read(machine);
self.r.write(machine, value);
}
}
impl InstructionName for Subtraction {
const INSTRUCTION_NAME: &'static str = "sub";
}
impl Display for Subtraction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"{} {} {} {}",
Self::INSTRUCTION_NAME,
self.a,
self.b,
self.r
)
}
}
impl TryLoadInstruction for Subtraction {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
let (a, b, r) = try_load_binary_op(params)?;
Ok(Rc::new(Self { a, b, r }))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, ParametersError, Subtraction, TryLoadInstruction};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn execute() {
let r0 = RegisterIndex::new(0);
let r1 = RegisterIndex::new(1);
let mut machine = MachineBuilder::new()
.with_registers(2)
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0, 0]);
Instruction::execute(
&Subtraction {
a: RegisterOrValue::Value(42),
b: RegisterOrValue::Value(23),
r: r0,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19, 0]);
Instruction::execute(
&Subtraction {
a: RegisterOrValue::Register(r0),
b: RegisterOrValue::Value(-23),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19, 42]);
Instruction::execute(
&Subtraction {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(r1),
r: r1,
},
&mut machine,
);
assert_eq!(machine.registers.read_all(), vec![19, 1295]);
}
#[test]
fn load() {
assert!(matches!(
Subtraction::try_load(&[]),
Err(ParametersError::UnexpectedCount {
found: 0,
expected: 3
})
));
assert!(matches!(
Subtraction::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Value(1337),
]),
Err(ParametersError::UnexpectedType { index: 2 })
));
assert!(matches!(
Subtraction::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Value(23),
RegisterOrValue::Register(RegisterIndex::R0),
]),
Ok(_)
));
}
#[test]
fn print() {
assert_eq!(
"sub 1337 r0 r1",
format!(
"{}",
Subtraction {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(RegisterIndex::R0),
r: RegisterIndex(1),
}
)
);
}
}

View file

@ -0,0 +1,107 @@
use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct TestEqual {
pub a: RegisterOrValue,
pub b: RegisterOrValue,
}
impl Instruction for TestEqual {
fn execute(&self, machine: &mut Machine) {
let result = self.a.read(machine) == self.b.read(machine);
machine.registers[0].write(if result { 1 } else { 0 });
}
}
impl InstructionName for TestEqual {
const INSTRUCTION_NAME: &'static str = "teq";
}
impl Display for TestEqual {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.b)
}
}
impl TryLoadInstruction for TestEqual {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
check_param_count(params, 2)?;
Ok(Rc::new(Self {
a: params[0],
b: params[1],
}))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, ParametersError, TestEqual, TryLoadInstruction};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn execute() {
let r0 = RegisterIndex::new(0);
let mut machine = MachineBuilder::new()
.minimal_registers()
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers[0].read(), 0);
Instruction::execute(
&TestEqual {
a: RegisterOrValue::Value(42),
b: RegisterOrValue::Value(23),
},
&mut machine,
);
assert_eq!(machine.registers[0].read(), 0);
machine.registers[0].write(-23);
Instruction::execute(
&TestEqual {
a: RegisterOrValue::Register(r0),
b: RegisterOrValue::Value(-23),
},
&mut machine,
);
assert_eq!(machine.registers[0].read(), 1);
}
#[test]
fn load() {
assert!(matches!(
TestEqual::try_load(&[]),
Err(ParametersError::UnexpectedCount {
found: 0,
expected: 2
})
));
assert!(matches!(
TestEqual::try_load(&[
RegisterOrValue::Value(42),
RegisterOrValue::Register(RegisterIndex::R0),
]),
Ok(_)
));
}
#[test]
fn print() {
assert_eq!(
"teq 1337 r0",
format!(
"{}",
TestEqual {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(RegisterIndex::R0),
}
)
);
}
}

View file

@ -0,0 +1,104 @@
use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction,
};
use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display};
use std::rc::Rc;
#[derive(Debug)]
pub struct TestLessThan {
pub a: RegisterOrValue,
pub b: RegisterOrValue,
}
impl Instruction for TestLessThan {
fn execute(&self, machine: &mut Machine) {
let result = self.a.read(machine) < self.b.read(machine);
machine.registers[0].write(if result { 1 } else { 0 });
}
}
impl InstructionName for TestLessThan {
const INSTRUCTION_NAME: &'static str = "tlt";
}
impl Display for TestLessThan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{} {} {}", Self::INSTRUCTION_NAME, self.a, self.b)
}
}
impl TryLoadInstruction for TestLessThan {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
check_param_count(params, 2)?;
Ok(Rc::new(Self {
a: params[0],
b: params[1],
}))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, ParametersError, TestLessThan, TryLoadInstruction};
use crate::machine::{MachineBuilder, RegisterIndex, RegisterOrValue};
#[test]
fn execute() {
let r0 = RegisterIndex::new(0);
let mut machine = MachineBuilder::new()
.minimal_registers()
.without_instructions()
.build()
.unwrap();
assert_eq!(machine.registers[0].read(), 0);
Instruction::execute(
&TestLessThan {
a: RegisterOrValue::Value(42),
b: RegisterOrValue::Value(23),
},
&mut machine,
);
assert_eq!(machine.registers[0].read(), 0);
machine.registers[0].write(-24);
Instruction::execute(
&TestLessThan {
a: RegisterOrValue::Register(r0),
b: RegisterOrValue::Value(-23),
},
&mut machine,
);
assert_eq!(machine.registers[0].read(), 1);
}
#[test]
fn load() {
assert!(matches!(
TestLessThan::try_load(&[]),
Err(ParametersError::UnexpectedCount {
found: 0,
expected: 2
})
));
assert!(matches!(
TestLessThan::try_load(&[RegisterOrValue::Value(42), RegisterOrValue::Value(23),]),
Ok(_)
));
}
#[test]
fn print() {
assert_eq!(
"tlt 1337 r0",
format!(
"{}",
TestLessThan {
a: RegisterOrValue::Value(1337),
b: RegisterOrValue::Register(RegisterIndex::R0),
}
)
);
}
}

View file

@ -0,0 +1,11 @@
use crate::machine::Machine;
use std::fmt::{Debug, Display};
pub trait Instruction: Debug + Display {
// TODO: this needs to be able to return errors, e.g. for division by zero
fn execute(&self, machine: &mut Machine);
}
pub trait InstructionName {
const INSTRUCTION_NAME: &'static str;
}

View file

@ -0,0 +1,39 @@
use crate::machine::{RegisterIndex, RegisterOrValue};
use std::rc::Rc;
pub(crate) trait TryLoadInstruction {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError>;
}
#[derive(Debug)]
pub enum ParametersError {
UnexpectedCount { expected: usize, found: usize },
UnexpectedType { index: usize },
}
pub(crate) fn check_param_count(
params: &[RegisterOrValue],
expected: usize,
) -> Result<(), ParametersError> {
if params.len() != expected {
Err(ParametersError::UnexpectedCount {
expected,
found: params.len(),
})
} else {
Ok(())
}
}
pub(crate) fn try_load_binary_op(
params: &[RegisterOrValue],
) -> Result<(RegisterOrValue, RegisterOrValue, RegisterIndex), ParametersError> {
check_param_count(params, 3)?;
let r = match params[2] {
RegisterOrValue::Register(r) => r,
RegisterOrValue::Value(_) => return Err(ParametersError::UnexpectedType { index: 2 }),
};
Ok((params[0], params[1], r))
}

Some files were not shown because too many files have changed in this diff Show more