commit 5ad6baa30d74986600406b3c0a1b642bdb675eb7 Author: Vinzenz Schroeter Date: Fri Dec 13 21:49:55 2024 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7bdedd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +tarpaulin-report.html diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..07cc970 --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f6b8926 --- /dev/null +++ b/Cargo.toml @@ -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"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6da6ae --- /dev/null +++ b/README.md @@ -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. \ No newline at end of file diff --git a/all_instructions.ei b/all_instructions.ei new file mode 100644 index 0000000..38d135f --- /dev/null +++ b/all_instructions.ei @@ -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 diff --git a/echse/Cargo.toml b/echse/Cargo.toml new file mode 100644 index 0000000..9f64a65 --- /dev/null +++ b/echse/Cargo.toml @@ -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"] diff --git a/echse/src/instructions/add_instruction.rs b/echse/src/instructions/add_instruction.rs new file mode 100644 index 0000000..afe1463 --- /dev/null +++ b/echse/src/instructions/add_instruction.rs @@ -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, 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), + } + ) + ); + } +} diff --git a/echse/src/instructions/collection.rs b/echse/src/instructions/collection.rs new file mode 100644 index 0000000..a069c27 --- /dev/null +++ b/echse/src/instructions/collection.rs @@ -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>); + +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> { + self.0.iter() + } +} + +impl From>> for Instructions { + fn from(instructions: Vec>) -> Self { + Self(instructions) + } +} + +impl Index for Instructions { + type Output = Rc; + 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(()) + } +} diff --git a/echse/src/instructions/div_instruction.rs b/echse/src/instructions/div_instruction.rs new file mode 100644 index 0000000..ce0b659 --- /dev/null +++ b/echse/src/instructions/div_instruction.rs @@ -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, 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), + } + ) + ); + } +} diff --git a/echse/src/instructions/jmp_instruction.rs b/echse/src/instructions/jmp_instruction.rs new file mode 100644 index 0000000..5fc2247 --- /dev/null +++ b/echse/src/instructions/jmp_instruction.rs @@ -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, 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); + } +} diff --git a/echse/src/instructions/jze_instruction.rs b/echse/src/instructions/jze_instruction.rs new file mode 100644 index 0000000..a34aee1 --- /dev/null +++ b/echse/src/instructions/jze_instruction.rs @@ -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, 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); + } +} diff --git a/echse/src/instructions/mod.rs b/echse/src/instructions/mod.rs new file mode 100644 index 0000000..d95e290 --- /dev/null +++ b/echse/src/instructions/mod.rs @@ -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::*; diff --git a/echse/src/instructions/mod_instruction.rs b/echse/src/instructions/mod_instruction.rs new file mode 100644 index 0000000..596dc6d --- /dev/null +++ b/echse/src/instructions/mod_instruction.rs @@ -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, 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), + } + ) + ); + } +} diff --git a/echse/src/instructions/nop_instruction.rs b/echse/src/instructions/nop_instruction.rs new file mode 100644 index 0000000..45f33bc --- /dev/null +++ b/echse/src/instructions/nop_instruction.rs @@ -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, 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) + } +} diff --git a/echse/src/instructions/put_instruction.rs b/echse/src/instructions/put_instruction.rs new file mode 100644 index 0000000..d975ff8 --- /dev/null +++ b/echse/src/instructions/put_instruction.rs @@ -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, 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]); + } +} diff --git a/echse/src/instructions/sub_instruction.rs b/echse/src/instructions/sub_instruction.rs new file mode 100644 index 0000000..2a1e43b --- /dev/null +++ b/echse/src/instructions/sub_instruction.rs @@ -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, 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), + } + ) + ); + } +} diff --git a/echse/src/instructions/teq_instruction.rs b/echse/src/instructions/teq_instruction.rs new file mode 100644 index 0000000..85012b2 --- /dev/null +++ b/echse/src/instructions/teq_instruction.rs @@ -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, 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), + } + ) + ); + } +} diff --git a/echse/src/instructions/tlt_instruction.rs b/echse/src/instructions/tlt_instruction.rs new file mode 100644 index 0000000..8f32d06 --- /dev/null +++ b/echse/src/instructions/tlt_instruction.rs @@ -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, 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), + } + ) + ); + } +} diff --git a/echse/src/instructions/traits.rs b/echse/src/instructions/traits.rs new file mode 100644 index 0000000..eb0ad28 --- /dev/null +++ b/echse/src/instructions/traits.rs @@ -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; +} diff --git a/echse/src/instructions/try_load.rs b/echse/src/instructions/try_load.rs new file mode 100644 index 0000000..f20d527 --- /dev/null +++ b/echse/src/instructions/try_load.rs @@ -0,0 +1,39 @@ +use crate::machine::{RegisterIndex, RegisterOrValue}; +use std::rc::Rc; + +pub(crate) trait TryLoadInstruction { + fn try_load(params: &[RegisterOrValue]) -> Result, 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)) +} diff --git a/echse/src/instructions/tze_instruction.rs b/echse/src/instructions/tze_instruction.rs new file mode 100644 index 0000000..19ee519 --- /dev/null +++ b/echse/src/instructions/tze_instruction.rs @@ -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, 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()); + } +} diff --git a/echse/src/lib.rs b/echse/src/lib.rs new file mode 100644 index 0000000..2e17d8c --- /dev/null +++ b/echse/src/lib.rs @@ -0,0 +1,7 @@ +pub mod instructions; +pub mod machine; +pub mod parser; + +pub(crate) mod sealed { + pub trait Sealed {} +} diff --git a/echse/src/machine/machine_struct.rs b/echse/src/machine/machine_struct.rs new file mode 100644 index 0000000..d1a0a7b --- /dev/null +++ b/echse/src/machine/machine_struct.rs @@ -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 { + state: T, +} + +impl MachineBuilder { + pub fn new() -> MachineBuilder { + MachineBuilder { + state: SetRegisters {}, + } + } +} + +impl Default for MachineBuilder { + fn default() -> Self { + Self::new() + } +} + +pub struct SetRegisters {} +impl Sealed for SetRegisters {} +impl MachineBuilderState for SetRegisters {} +impl MachineBuilder { + /// Sets the amount of registers to the specified amount, but at least 1. + pub fn with_registers(self, registers: usize) -> MachineBuilder { + MachineBuilder { + state: LoadInstructions { + registers: Registers::with_count(usize::max(1, registers)), + }, + } + } + + /// Sets the amount of registers to 1. + pub fn minimal_registers(self) -> MachineBuilder { + self.with_registers(1) + } +} + +pub struct LoadInstructions { + registers: Registers, +} +impl MachineBuilderState for LoadInstructions {} +impl Sealed for LoadInstructions {} +impl MachineBuilder { + pub fn with_instructions(self, instructions: Instructions) -> MachineBuilder { + MachineBuilder { + state: Done { + registers: self.state.registers, + instructions, + }, + } + } + + pub fn without_instructions(self) -> MachineBuilder { + self.with_instructions(Instructions::empty()) + } +} + +pub struct Done { + registers: Registers, + instructions: Instructions, +} +impl MachineBuilderState for Done {} +impl Sealed for Done {} +impl MachineBuilder { + pub fn build(self) -> Result { + 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> = 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()); + } +} diff --git a/echse/src/machine/mod.rs b/echse/src/machine/mod.rs new file mode 100644 index 0000000..9af239c --- /dev/null +++ b/echse/src/machine/mod.rs @@ -0,0 +1,5 @@ +mod machine_struct; +mod register; + +pub use machine_struct::*; +pub use register::*; diff --git a/echse/src/machine/register.rs b/echse/src/machine/register.rs new file mode 100644 index 0000000..7ae0d1c --- /dev/null +++ b/echse/src/machine/register.rs @@ -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 { + value: T, +} + +impl Register { + pub fn read(&self) -> T { + self.value + } + + pub fn write(&mut self, value: T) { + self.value = value; + } +} + +pub type GpRegister = Register; + +#[derive(Debug)] +pub struct Registers { + rs: Vec, +} + +impl Registers { + pub fn new(registers: Vec) -> 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 { + self.iter().map(|r| r.read()).collect() + } + + pub fn iter(&self) -> impl Iterator { + self.rs.iter() + } + + pub fn len(&self) -> usize { + self.rs.len() + } + + pub fn is_empty(&self) -> bool { + self.rs.is_empty() + } +} + +impl Index for Registers { + type Output = GpRegister; + fn index(&self, index: usize) -> &Self::Output { + &self.rs[index] + } +} + +impl IndexMut 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), + } + } +} diff --git a/echse/src/parser/ei.rs b/echse/src/parser/ei.rs new file mode 100644 index 0000000..27ce327 --- /dev/null +++ b/echse/src/parser/ei.rs @@ -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 { + 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 { + 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, + ) -> Result { + 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, TryLoadErrorReason> { + let params = line + .parameters + .iter() + .map(move |(value, _)| *value) + .collect::>(); + + Ok(match &*line.instruction_name { + Addition::INSTRUCTION_NAME => Addition::try_load(¶ms)?, + Subtraction::INSTRUCTION_NAME => Subtraction::try_load(¶ms)?, + Modulus::INSTRUCTION_NAME => Modulus::try_load(¶ms)?, + Division::INSTRUCTION_NAME => Division::try_load(¶ms)?, + Jump::INSTRUCTION_NAME => Jump::try_load(¶ms)?, + NoOperation::INSTRUCTION_NAME => NoOperation::try_load(¶ms)?, + TestZero::INSTRUCTION_NAME => TestZero::try_load(¶ms)?, + Put::INSTRUCTION_NAME => Put::try_load(¶ms)?, + TestEqual::INSTRUCTION_NAME => TestEqual::try_load(¶ms)?, + TestLessThan::INSTRUCTION_NAME => TestLessThan::try_load(¶ms)?, + JumpIfZero::INSTRUCTION_NAME => JumpIfZero::try_load(¶ms)?, + illegal => return Err(TryLoadErrorReason::IllegalInstruction(illegal.to_string())), + }) + } +} + +#[cfg(feature = "error-report")] +impl EiParser { + pub fn write_error_report( + &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( + &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( + &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::>() + .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), + 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 for TryLoadErrorReason { + fn from(reason: ParametersError) -> Self { + TryLoadErrorReason::InvalidParameters(reason) + } +} diff --git a/echse/src/parser/ei_ast.rs b/echse/src/parser/ei_ast.rs new file mode 100644 index 0000000..47a229f --- /dev/null +++ b/echse/src/parser/ei_ast.rs @@ -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)>, + pub span: Range, + pub instruction_name_span: Range, + pub parameters_span: Range, +} + +fn radix_number( + prefix: Option<&'static str>, + radix: u32, +) -> impl Parser { + 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 { + 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 { + just(' ') + .ignored() + .repeated() + .at_least(1) + .ignored() + .labelled("required-spaces") +} + +fn optional_spaces() -> impl Parser { + just(' ') + .ignored() + .repeated() + .ignored() + .labelled("optional-spaces") +} + +fn register() -> impl Parser { + just("r") + .ignore_then(text::int(10)) + .map(move |o: String| RegisterIndex(usize::from_str(&o).unwrap())) + .labelled("register") +} + +fn instruction() -> impl Parser { + 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, 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> { + 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::>::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, + } + ] + ); + } +} diff --git a/echse/src/parser/mod.rs b/echse/src/parser/mod.rs new file mode 100644 index 0000000..0e456db --- /dev/null +++ b/echse/src/parser/mod.rs @@ -0,0 +1,7 @@ +mod ei; +mod ei_ast; + +use chumsky::error::Simple; +pub use ei::*; + +pub type ParseError = Simple; diff --git a/echse_cute/Cargo.toml b/echse_cute/Cargo.toml new file mode 100644 index 0000000..8d1af8c --- /dev/null +++ b/echse_cute/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "echse_cute" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap.workspace = true +echse = { path = "../echse" } diff --git a/echse_cute/src/main.rs b/echse_cute/src/main.rs new file mode 100644 index 0000000..3281f37 --- /dev/null +++ b/echse_cute/src/main.rs @@ -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}"); +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..12fd0e4 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..5ca8557 --- /dev/null +++ b/flake.nix @@ -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); + }; +} diff --git a/krabbeltier/Cargo.toml b/krabbeltier/Cargo.toml new file mode 100644 index 0000000..dc59b78 --- /dev/null +++ b/krabbeltier/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "krabbeltier" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap.workspace = true +shellwords.workspace = true +echse = { path = "../echse" } diff --git a/krabbeltier/src/main.rs b/krabbeltier/src/main.rs new file mode 100644 index 0000000..e05f1df --- /dev/null +++ b/krabbeltier/src/main.rs @@ -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 }, + + #[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, +} + +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(); +} diff --git a/the_answer.ei b/the_answer.ei new file mode 100755 index 0000000..121145a --- /dev/null +++ b/the_answer.ei @@ -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