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

View file

@ -0,0 +1,62 @@
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 TestZero {
a: RegisterOrValue,
}
impl Instruction for TestZero {
fn execute(&self, machine: &mut Machine) {
let result = self.a.read(machine) == 0;
machine.registers[0].write(if result { 1 } else { 0 });
}
}
impl InstructionName for TestZero {
const INSTRUCTION_NAME: &'static str = "tze";
}
impl Display for TestZero {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{} {}", Self::INSTRUCTION_NAME, self.a)
}
}
impl TryLoadInstruction for TestZero {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError> {
check_param_count(params, 1)?;
Ok(Rc::new(TestZero { a: params[0] }))
}
}
#[cfg(test)]
mod tests {
use crate::instructions::{Instruction, TestZero};
use crate::machine::RegisterOrValue::{Register, Value};
use crate::machine::{MachineBuilder, RegisterIndex};
#[test]
fn execute() {
let mut machine = MachineBuilder::new()
.minimal_registers()
.without_instructions()
.build()
.unwrap();
assert_eq!(0, machine.registers[0].read());
Instruction::execute(&TestZero { a: Value(42) }, &mut machine);
assert_eq!(0, machine.registers[0].read());
Instruction::execute(
&TestZero {
a: Register(RegisterIndex::R0),
},
&mut machine,
);
assert_eq!(1, machine.registers[0].read());
}
}

7
echse/src/lib.rs Normal file
View file

@ -0,0 +1,7 @@
pub mod instructions;
pub mod machine;
pub mod parser;
pub(crate) mod sealed {
pub trait Sealed {}
}

View file

@ -0,0 +1,159 @@
use crate::instructions::Instructions;
use crate::machine::Registers;
use crate::sealed::Sealed;
use std::fmt::Display;
#[derive(Debug)]
pub struct Machine {
pub ip: usize,
pub registers: Registers,
pub instructions: Instructions,
}
impl Machine {
pub fn step(&mut self) -> bool {
if self.ip >= self.instructions.len() {
return false;
}
let instruction = &self.instructions[self.ip].clone();
instruction.execute(self);
self.ip += 1;
true
}
}
impl Display for Machine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str("- exe-cute table -\n")?;
f.write_str("registers")?;
f.write_fmt(format_args!("ip: {}\n", self.ip))?;
f.write_fmt(format_args!("{}", self.registers))?;
f.write_fmt(format_args!("instructions\n{}", self.instructions))?;
Ok(())
}
}
pub trait MachineBuilderState: Sealed {}
pub struct MachineBuilder<T: MachineBuilderState> {
state: T,
}
impl MachineBuilder<SetRegisters> {
pub fn new() -> MachineBuilder<SetRegisters> {
MachineBuilder {
state: SetRegisters {},
}
}
}
impl Default for MachineBuilder<SetRegisters> {
fn default() -> Self {
Self::new()
}
}
pub struct SetRegisters {}
impl Sealed for SetRegisters {}
impl MachineBuilderState for SetRegisters {}
impl MachineBuilder<SetRegisters> {
/// Sets the amount of registers to the specified amount, but at least 1.
pub fn with_registers(self, registers: usize) -> MachineBuilder<LoadInstructions> {
MachineBuilder {
state: LoadInstructions {
registers: Registers::with_count(usize::max(1, registers)),
},
}
}
/// Sets the amount of registers to 1.
pub fn minimal_registers(self) -> MachineBuilder<LoadInstructions> {
self.with_registers(1)
}
}
pub struct LoadInstructions {
registers: Registers,
}
impl MachineBuilderState for LoadInstructions {}
impl Sealed for LoadInstructions {}
impl MachineBuilder<LoadInstructions> {
pub fn with_instructions(self, instructions: Instructions) -> MachineBuilder<Done> {
MachineBuilder {
state: Done {
registers: self.state.registers,
instructions,
},
}
}
pub fn without_instructions(self) -> MachineBuilder<Done> {
self.with_instructions(Instructions::empty())
}
}
pub struct Done {
registers: Registers,
instructions: Instructions,
}
impl MachineBuilderState for Done {}
impl Sealed for Done {}
impl MachineBuilder<Done> {
pub fn build(self) -> Result<Machine, MachineBuilderError> {
Ok(Machine {
ip: 0,
registers: self.state.registers,
instructions: self.state.instructions,
})
}
}
#[derive(Debug)]
pub enum MachineBuilderError {
NoRegisters,
}
#[cfg(test)]
mod tests {
use crate::instructions::{Addition, Instructions};
use crate::instructions::{Instruction, Put};
use crate::machine::register::{RegisterIndex, RegisterOrValue};
use crate::machine::MachineBuilder;
use std::rc::Rc;
#[test]
fn it_works() {
let reg_a = RegisterIndex::R0;
let instructions: Vec<Rc<dyn Instruction>> = vec![
Rc::new(Put {
a: RegisterOrValue::Value(42),
r: reg_a,
}),
Rc::new(Addition {
a: RegisterOrValue::Register(reg_a),
b: RegisterOrValue::Value(23),
r: reg_a,
}),
];
let mut machine = MachineBuilder::new()
.minimal_registers()
.with_instructions(Instructions::from(instructions))
.build()
.unwrap();
assert_eq!(machine.ip, 0);
assert_eq!(reg_a.read(&machine), 0);
assert!(machine.step());
assert_eq!(machine.ip, 1);
assert_eq!(reg_a.read(&machine), 42);
assert!(machine.step());
assert_eq!(machine.ip, 2);
assert_eq!(reg_a.read(&machine), 65);
assert!(!machine.step());
assert!(!machine.step());
}
}

5
echse/src/machine/mod.rs Normal file
View file

@ -0,0 +1,5 @@
mod machine_struct;
mod register;
pub use machine_struct::*;
pub use register::*;

View file

@ -0,0 +1,130 @@
use crate::machine::Machine;
use std::fmt::Debug;
use std::fmt::Display;
use std::ops::{Index, IndexMut};
#[derive(Debug, Default, Clone)]
pub struct Register<T: Copy + Sized + Default> {
value: T,
}
impl<T: Copy + Sized + Default> Register<T> {
pub fn read(&self) -> T {
self.value
}
pub fn write(&mut self, value: T) {
self.value = value;
}
}
pub type GpRegister = Register<isize>;
#[derive(Debug)]
pub struct Registers {
rs: Vec<GpRegister>,
}
impl Registers {
pub fn new(registers: Vec<GpRegister>) -> Self {
assert!(!registers.is_empty());
Self { rs: registers }
}
pub fn with_count(count: usize) -> Self {
assert!(count > 0);
Self::new(vec![GpRegister::default(); count])
}
pub fn read_all(&self) -> Vec<isize> {
self.iter().map(|r| r.read()).collect()
}
pub fn iter(&self) -> impl Iterator<Item = &GpRegister> {
self.rs.iter()
}
pub fn len(&self) -> usize {
self.rs.len()
}
pub fn is_empty(&self) -> bool {
self.rs.is_empty()
}
}
impl Index<usize> for Registers {
type Output = GpRegister;
fn index(&self, index: usize) -> &Self::Output {
&self.rs[index]
}
}
impl IndexMut<usize> for Registers {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.rs[index]
}
}
impl Display for Registers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
for (index, register) in self.iter().enumerate() {
f.write_fmt(format_args!("r{index}: {}\n", register.read()))?;
}
Ok(())
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct RegisterIndex(pub usize);
impl RegisterIndex {
pub const R0: RegisterIndex = RegisterIndex(0);
pub fn new_checked(index: usize, machine: &Machine) -> Self {
assert!(machine.registers.rs.len() > index);
Self::new(index)
}
pub fn new(index: usize) -> Self {
Self(index)
}
pub fn read(&self, machine: &Machine) -> isize {
machine.registers.rs[self.0].read()
}
pub fn write(&self, machine: &mut Machine, value: isize) {
machine.registers.rs[self.0].write(value)
}
}
impl Display for RegisterIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_fmt(format_args!("r{}", self.0))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RegisterOrValue {
Register(RegisterIndex),
Value(isize),
}
impl RegisterOrValue {
pub fn read(&self, machine: &Machine) -> isize {
match self {
RegisterOrValue::Register(r) => r.read(machine),
RegisterOrValue::Value(v) => *v,
}
}
}
impl Display for RegisterOrValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
RegisterOrValue::Register(r) => Display::fmt(&r, f),
RegisterOrValue::Value(v) => Display::fmt(&v, f),
}
}
}

266
echse/src/parser/ei.rs Normal file
View file

@ -0,0 +1,266 @@
use crate::instructions::{
Addition, Division, Instruction, InstructionName, Instructions, Jump, JumpIfZero, Modulus,
NoOperation, ParametersError, Put, Subtraction, TestEqual, TestLessThan, TestZero,
TryLoadInstruction,
};
use crate::parser;
use crate::parser::ei_ast::{Line};
use std::io::Write;
use std::rc::Rc;
use crate::parser::ParseError;
pub struct EiParser {
file_name: String,
file_content: String,
}
impl EiParser {
pub fn from_file(file_name: &str) -> std::io::Result<EiParser> {
Ok(Self {
file_name: file_name.to_string(),
file_content: std::fs::read_to_string(file_name)?,
})
}
pub fn from_string(file_content: &str) -> Self {
Self {
file_name: "input".to_string(),
file_content: file_content.to_string(),
}
}
pub fn parse(&self) -> Result<Instructions, EiParserError> {
let lines = match parser::ei_ast::parse_program(self.file_content.as_str()) {
Ok(p) => p,
Err(e) => return Err(EiParserError::ParseError(e)),
};
Self::try_load_instructions(lines).map_err(EiParserError::InstructionLoadError)
}
pub fn try_load_instructions(
lines: impl IntoIterator<Item = Line>,
) -> Result<Instructions, TryLoadError> {
let lines = lines.into_iter();
let mut vec = Vec::with_capacity(lines.size_hint().0);
for (index, line) in lines.enumerate() {
let instruction = match Self::try_load_instruction(&line) {
Ok(instruction) => instruction,
Err(reason) => {
return Err(TryLoadError {
line,
index,
reason,
})
}
};
vec.push(instruction);
}
Ok(Instructions::from(vec))
}
fn try_load_instruction(line: &Line) -> Result<Rc<dyn Instruction>, TryLoadErrorReason> {
let params = line
.parameters
.iter()
.map(move |(value, _)| *value)
.collect::<Vec<_>>();
Ok(match &*line.instruction_name {
Addition::INSTRUCTION_NAME => Addition::try_load(&params)?,
Subtraction::INSTRUCTION_NAME => Subtraction::try_load(&params)?,
Modulus::INSTRUCTION_NAME => Modulus::try_load(&params)?,
Division::INSTRUCTION_NAME => Division::try_load(&params)?,
Jump::INSTRUCTION_NAME => Jump::try_load(&params)?,
NoOperation::INSTRUCTION_NAME => NoOperation::try_load(&params)?,
TestZero::INSTRUCTION_NAME => TestZero::try_load(&params)?,
Put::INSTRUCTION_NAME => Put::try_load(&params)?,
TestEqual::INSTRUCTION_NAME => TestEqual::try_load(&params)?,
TestLessThan::INSTRUCTION_NAME => TestLessThan::try_load(&params)?,
JumpIfZero::INSTRUCTION_NAME => JumpIfZero::try_load(&params)?,
illegal => return Err(TryLoadErrorReason::IllegalInstruction(illegal.to_string())),
})
}
}
#[cfg(feature = "error-report")]
impl EiParser {
pub fn write_error_report<W: Write>(
&self,
ei_parser_error: EiParserError,
w: &mut W,
) -> std::io::Result<()>
where
for<'a> &'a mut W: Write,
{
match ei_parser_error {
EiParserError::ParseError(es) => {
for e in es {
self.write_parse_error_report(e, &mut *w)?;
}
Ok(())
}
EiParserError::InstructionLoadError(e) => self.write_load_error_report(e, &mut *w),
}
}
pub fn write_load_error_report<W: std::io::Write>(
&self,
e: TryLoadError,
w: &mut W,
) -> std::io::Result<()> {
use ariadne::{Color, Label, Report, ReportKind, Source};
let report = Report::build(ReportKind::Error, (&self.file_name, e.line.span.clone()))
.with_message("Loading of instructions failed")
.with_config(Self::get_ariadne_config());
let report = match e.reason {
TryLoadErrorReason::IllegalInstruction(_) => {
let label = Label::new((&self.file_name, e.line.instruction_name_span))
.with_color(Color::Red)
.with_message("illegal instruction");
report
.with_label(label)
.with_note("instruction names are case sensitive")
}
TryLoadErrorReason::InvalidParameters(param_err) => match param_err {
ParametersError::UnexpectedCount { found, expected } => {
let label = if found > expected {
let extra_args = &e.line.parameters[expected..found];
let span =
extra_args.first().unwrap().1.start..extra_args.last().unwrap().1.end;
Label::new((&self.file_name, span))
.with_color(Color::Red)
.with_message("too many parameters")
} else {
Label::new((&self.file_name, e.line.parameters_span.clone()))
.with_color(Color::Red)
.with_message("not enough parameters")
};
report.with_label(label).with_note(format!(
"the instruction expects {expected} parameters, but {found} were provided."
))
}
ParametersError::UnexpectedType { index } => {
let label = Label::new((&self.file_name, e.line.parameters[index].1.clone()))
.with_color(Color::Red)
.with_message("unexpected type of parameter");
report.with_label(label).with_help(
"check the parameter order and which ones can values and/or registers",
)
}
},
};
report
.finish()
.write((&self.file_name, Source::from(&self.file_content)), w)
}
#[cfg(feature = "error-report")]
pub fn write_parse_error_report<W: std::io::Write>(
&self,
e: ParseError,
w: W,
) -> std::io::Result<()> {
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
let report = Report::build(ReportKind::Error, (&self.file_name, e.span()))
.with_config(Self::get_ariadne_config());
let report = match e.reason() {
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report
.with_message(format!(
"Unclosed delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_label(
Label::new((&self.file_name, span.clone()))
.with_message(format!(
"Unclosed delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_color(Color::Yellow),
)
.with_label(
Label::new((&self.file_name, e.span()))
.with_message(format!(
"Must be closed before this {:?}",
e.found().unwrap_or(&'\0').fg(Color::Red)
))
.with_color(Color::Red),
),
chumsky::error::SimpleReason::Unexpected => report
.with_message(format!(
"{}, expected {}",
if e.found().is_some() {
"Unexpected token in input"
} else {
"Unexpected end of input"
},
if e.expected().len() == 0 {
"something else".to_string()
} else {
e.expected()
.map(|expected| match expected {
Some(expected) => expected.to_string(),
None => "end of input".to_string(),
})
.collect::<Vec<_>>()
.join(", ")
}
))
.with_label(
Label::new((&self.file_name, e.span()))
.with_message(format!(
"Unexpected token {}",
e.found().unwrap_or(&'\0').fg(Color::Red)
))
.with_color(Color::Red),
),
chumsky::error::SimpleReason::Custom(msg) => report.with_message(msg).with_label(
Label::new((&self.file_name, e.span()))
.with_message(format!("{}", msg.fg(Color::Red)))
.with_color(Color::Red),
),
};
report
.finish()
.write((&self.file_name, Source::from(&self.file_content)), w)
}
fn get_ariadne_config() -> ariadne::Config {
ariadne::Config::new().with_compact(true)
}
}
#[derive(Debug)]
pub enum EiParserError {
ParseError(Vec<ParseError>),
InstructionLoadError(TryLoadError),
}
#[derive(Debug)]
pub struct TryLoadError {
pub index: usize,
pub line: Line,
pub reason: TryLoadErrorReason,
}
#[derive(Debug)]
pub enum TryLoadErrorReason {
IllegalInstruction(String),
InvalidParameters(ParametersError),
}
impl From<ParametersError> for TryLoadErrorReason {
fn from(reason: ParametersError) -> Self {
TryLoadErrorReason::InvalidParameters(reason)
}
}

171
echse/src/parser/ei_ast.rs Normal file
View file

@ -0,0 +1,171 @@
use crate::machine::{RegisterIndex, RegisterOrValue};
use crate::parser::ParseError;
use chumsky::prelude::*;
use std::ops::Range;
use std::str::FromStr;
#[derive(Debug, Eq, PartialEq)]
pub struct Line {
pub instruction_name: String,
pub parameters: Vec<(RegisterOrValue, Range<usize>)>,
pub span: Range<usize>,
pub instruction_name_span: Range<usize>,
pub parameters_span: Range<usize>,
}
fn radix_number(
prefix: Option<&'static str>,
radix: u32,
) -> impl Parser<char, isize, Error = ParseError> {
let no_prefix =
text::int(radix).map(move |o: String| isize::from_str_radix(o.as_str(), radix).unwrap());
just(prefix.unwrap_or(""))
.ignore_then(no_prefix)
.labelled("radix-number")
}
fn multi_radix_number() -> impl Parser<char, isize, Error = ParseError> {
let no_sign_num = radix_number(Some("0x"), 16)
.or(radix_number(Some("0b"), 2))
.or(radix_number(None, 10))
.labelled("no-sign-number");
let optional_sign = one_of("-+").labelled("sign").or_not();
optional_sign
.then(no_sign_num)
.map(
move |(sign, num)| {
if matches!(sign, Some('-')) {
-num
} else {
num
}
},
)
.labelled("multi-radix-number")
}
fn required_spaces() -> impl Parser<char, (), Error = ParseError> {
just(' ')
.ignored()
.repeated()
.at_least(1)
.ignored()
.labelled("required-spaces")
}
fn optional_spaces() -> impl Parser<char, (), Error = ParseError> {
just(' ')
.ignored()
.repeated()
.ignored()
.labelled("optional-spaces")
}
fn register() -> impl Parser<char, RegisterIndex, Error = ParseError> {
just("r")
.ignore_then(text::int(10))
.map(move |o: String| RegisterIndex(usize::from_str(&o).unwrap()))
.labelled("register")
}
fn instruction() -> impl Parser<char, Line, Error = ParseError> {
let name = text::ident().labelled("instruction-name");
let value = multi_radix_number()
.map(RegisterOrValue::Value)
.labelled("value");
let register = register()
.map(RegisterOrValue::Register)
.labelled("register");
let value_or_register = value.or(register).labelled("value-or-register");
let params = value_or_register
.map_with_span(move |o, s| (o, s))
.separated_by(required_spaces())
.labelled("params")
.or_not()
.map_with_span(|o, span| (o, span));
optional_spaces()
.ignore_then(name)
.map_with_span(|o, span| (o, span))
.then(required_spaces().or_not().ignore_then(params))
.map_with_span(
|((instruction_name, instruction_name_span), (parameters, parameters_span)), span| {
Line {
instruction_name,
parameters: parameters.unwrap_or_else(Vec::new),
span,
instruction_name_span,
parameters_span,
}
},
)
.labelled("instruction")
}
fn program() -> impl Parser<char, Vec<Line>, Error = ParseError> {
instruction()
.separated_by(text::newline().labelled("newline"))
.allow_trailing()
.collect()
.labelled("program")
.then_ignore(end())
}
pub(crate) fn parse_program(text: &str) -> Result<Vec<Line>, Vec<ParseError>> {
let (result, errors) = program().parse_recovery(text);
if !errors.is_empty() {
Err(errors)
} else {
Ok(result.unwrap())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::machine::RegisterOrValue::{Register, Value};
#[test]
fn it_works() {
const PROG: &str = "add 5 3 r1
put 4 r0
nop
";
let (tokens, errs) = program().parse_recovery(PROG);
assert_eq!(Vec::<Simple<char>>::new(), errs);
let tokens = tokens.unwrap();
assert_eq!(
tokens,
vec![
Line {
instruction_name: "add".to_string(),
parameters: vec![
(Value(5), 4..5),
(Value(3), 6..7),
(Register(RegisterIndex(1)), 8..10)
],
span: 0..10,
instruction_name_span: 0..3,
parameters_span: 4..10,
},
Line {
instruction_name: "put".to_string(),
parameters: vec![(Value(4), 15..16), (Register(RegisterIndex::R0), 17..19)],
span: 11..19,
instruction_name_span: 11..14,
parameters_span: 15..19,
},
Line {
instruction_name: "nop".to_string(),
parameters: vec![],
span: 20..23,
instruction_name_span: 20..23,
parameters_span: 23..24,
}
]
);
}
}

7
echse/src/parser/mod.rs Normal file
View file

@ -0,0 +1,7 @@
mod ei;
mod ei_ast;
use chumsky::error::Simple;
pub use ei::*;
pub type ParseError = Simple<char>;

8
echse_cute/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "echse_cute"
version = "0.1.0"
edition = "2021"
[dependencies]
clap.workspace = true
echse = { path = "../echse" }

43
echse_cute/src/main.rs Normal file
View file

@ -0,0 +1,43 @@
use echse::machine::MachineBuilder;
use echse::parser::EiParser;
#[derive(clap::Parser)]
#[command(version, about, long_about = None)]
struct Cli {
ei_file: String,
#[arg(short, long, default_value_t = 2)]
registers: usize,
#[arg(short, long, default_value_t = false)]
verbose: bool,
}
fn main() {
let args: Cli = clap::Parser::parse();
let parser = EiParser::from_file(&args.ei_file).unwrap();
let instructions = match parser.parse() {
Ok(i) => i,
Err(e) => {
parser
.write_error_report(e, &mut std::io::stderr())
.expect("failed to generate error report");
return;
}
};
let mut machine = MachineBuilder::new()
.with_registers(args.registers)
.with_instructions(instructions)
.build()
.unwrap();
println!("done building machine: \n{machine}");
while machine.step() {
if args.verbose {
println!("executed instruction: {machine:?}");
}
}
println!("final state: \n{machine}");
}

48
flake.lock Normal file
View file

@ -0,0 +1,48 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1733346208,
"narHash": "sha256-a4WZp1xQkrnA4BbnKrzJNr+dYoQr5Xneh2syJoddFyE=",
"owner": "nix-community",
"repo": "naersk",
"rev": "378614f37a6bee5a3f2ef4f825a73d948d3ae921",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1733261153,
"narHash": "sha256-eq51hyiaIwtWo19fPEeE0Zr2s83DYMKJoukNLgGGpek=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "b681065d0919f7eb5309a93cea2cfa84dec9aa88",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"naersk": "naersk",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

60
flake.nix Normal file
View file

@ -0,0 +1,60 @@
{
description = "Flake for servicepoint-rustlings";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
naersk = {
url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
inputs@{
self,
nixpkgs,
naersk,
}:
let
lib = nixpkgs.lib;
supported-systems = [
"x86_64-linux"
"aarch64-linux"
"aarch64-darwin"
"x86_64-darwin"
];
forAllSystems = lib.genAttrs supported-systems;
in
rec {
devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages."${system}";
in
{
default = pkgs.mkShell rec {
packages = [
(pkgs.symlinkJoin {
name = "rust-toolchain";
paths = with pkgs; [
rustc
cargo
rustPlatform.rustcSrc
rustfmt
clippy
cargo-expand
cargo-tarpaulin
rustlings
];
})
];
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath packages}";
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
RUST_BACKTRACE = "1";
};
}
);
formatter = forAllSystems (system: nixpkgs.legacyPackages."${system}".nixfmt-rfc-style);
};
}

9
krabbeltier/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "krabbeltier"
version = "0.1.0"
edition = "2021"
[dependencies]
clap.workspace = true
shellwords.workspace = true
echse = { path = "../echse" }

214
krabbeltier/src/main.rs Normal file
View file

@ -0,0 +1,214 @@
use echse::machine::{Machine, MachineBuilder};
use echse::parser::EiParser;
use std::collections::HashSet;
use std::io::{BufRead, StdinLock, Write};
#[derive(clap::Parser)]
#[command(version, about, long_about = None)]
struct Cli {
ei_file: String,
#[arg(short, long, default_value_t = 2)]
registers: usize,
}
#[derive(clap::Parser)]
#[command(
disable_help_flag = true,
disable_version_flag = true,
no_binary_name = true,
bin_name = ""
)]
enum DebugCommand {
#[command(visible_alias = "q")]
/// exit the debugger
Quit,
#[command(visible_alias = "p")]
/// prints the current state
Print {
#[arg(value_enum, default_value_t = PrintMode::All)]
mode: PrintMode,
},
#[command(visible_alias = "s")]
/// executes a single instruction
Step,
#[command(visible_alias = "j")]
/// set the instruction pointer
Jump { ip: usize },
#[command(visible_alias = "b")]
/// toggle breakpoints
Break { ip: Option<usize> },
#[command(visible_alias = "bl")]
BreakList,
/// continue running instructions
#[command(visible_alias = "c")]
Continue,
/// Write a value into the specified register
#[command(visible_alias = "w")]
Write {
register: usize,
#[arg(allow_negative_numbers = true)]
value: isize,
},
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, Default, Debug)]
enum PrintMode {
#[default]
#[clap(alias = "a")]
All,
#[clap(alias = "r")]
Registers,
#[clap(alias = "i")]
Instructions,
}
struct App {
machine: Machine,
breakpoints: HashSet<usize>,
}
impl App {
pub(crate) fn exec(&mut self) {
println!("Welcome to the debugger. Run 'help' to see available commands.");
let mut stdin = std::io::stdin().lock();
let mut input_buf = String::new();
loop {
Self::read_command_line(&mut stdin, &mut input_buf);
let command = shellwords::split(input_buf.trim()).map(clap::Parser::try_parse_from);
match command {
Ok(Ok(command)) => {
if !self.run_command(command) {
break;
}
}
Err(e) => {
println!("{e}");
}
Ok(Err(e)) => println!("{e}"),
};
}
}
fn read_command_line(stdin: &mut StdinLock, input_buf: &mut String) {
input_buf.clear();
let mut stdout = std::io::stdout().lock();
stdout.write_fmt(format_args!("> ")).unwrap();
stdout.flush().unwrap();
drop(stdout);
stdin.read_line(input_buf).unwrap();
}
fn run_command(&mut self, command: DebugCommand) -> bool {
match command {
DebugCommand::Quit => return false,
DebugCommand::Print { mode } => match mode {
PrintMode::All => {
println!("{}", self.machine);
}
PrintMode::Registers => {
println!("{}", self.machine.registers);
}
PrintMode::Instructions => {
println!("next instruction is {}", self.machine.ip);
println!("{}", self.machine.instructions);
}
},
DebugCommand::Step => {
if self.machine.instructions.len() > self.machine.ip {
println!(
"{:03}: {}\n",
self.machine.ip, self.machine.instructions[self.machine.ip]
);
self.machine.step();
} else {
println!("reached end of program\n");
}
}
DebugCommand::Jump { ip } => {
self.machine.ip = ip;
println!("ip now at {ip}\n");
}
DebugCommand::Break { ip } => {
let ip = ip.unwrap_or(self.machine.ip);
if self.breakpoints.contains(&ip) {
self.breakpoints.remove(&ip);
println!("removed breakpoint at {ip}\n");
} else {
self.breakpoints.insert(ip);
println!("added breakpoint at {ip}\n");
}
}
DebugCommand::BreakList => {
println!("current breakpoints: {:?}\n", self.breakpoints);
}
DebugCommand::Continue => {
let mut count = 0;
while self.machine.step() {
count += 1;
if self.breakpoints.contains(&self.machine.ip) {
println!(
"breakpoint hit before executing {:03}: {}",
self.machine.ip, self.machine.instructions[self.machine.ip]
);
break;
}
}
println!(
"stepped {count} instructions, ip now at {}\n",
self.machine.ip
);
}
DebugCommand::Write { value, register } => {
if self.machine.registers.len() <= register {
eprintln!(
"Cannot write to register r{register}, as this machine only has {}\n",
self.machine.registers.len()
);
} else {
self.machine.registers[register].write(value);
println!("r{register}: {value}\n");
}
}
}
true
}
}
fn main() {
let args: Cli = clap::Parser::parse();
let parser = EiParser::from_file(&args.ei_file).unwrap();
let instructions = match parser.parse() {
Ok(i) => i,
Err(e) => {
parser
.write_error_report(e, &mut std::io::stderr())
.expect("failed to generate error report");
return;
}
};
let machine = MachineBuilder::new()
.with_registers(args.registers)
.with_instructions(instructions)
.build()
.unwrap();
let mut app = App {
machine,
breakpoints: HashSet::new(),
};
app.exec();
}

7
the_answer.ei Executable file
View file

@ -0,0 +1,7 @@
put 23 r0
div r0 10 r1
add r1 r1 r1
add r0 r0 r0
sub r0 r1 r0
mod r1 3 r1
add r1 r0 r1