summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSophie Forrest <git@sophieforrest.com>2024-08-30 23:35:45 +1200
committerSophie Forrest <git@sophieforrest.com>2024-08-30 23:35:45 +1200
commit5126c9ed83fe6169463566e74f966a4a63e57ca0 (patch)
tree99be8b093f6736eba59c529300519985d9c1c5c6
feat: initial commit of brainf interpreter
-rw-r--r--.gitignore1
-rw-r--r--.rustfmt.toml13
-rw-r--r--.taplo.toml10
-rw-r--r--Cargo.lock552
-rw-r--r--Cargo.toml15
-rw-r--r--README.md3
-rw-r--r--clippy.toml1
-rw-r--r--src/constants.rs4
-rw-r--r--src/executor.rs89
-rw-r--r--src/lexer.rs79
-rw-r--r--src/lib.rs154
-rw-r--r--src/main.rs125
-rw-r--r--src/parser.rs164
-rw-r--r--src/utility.rs47
-rw-r--r--test_programs/hello_world.bf43
-rw-r--r--test_programs/hello_world_from_hell.bf263
16 files changed, 1563 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/.rustfmt.toml b/.rustfmt.toml
new file mode 100644
index 0000000..ebf394c
--- /dev/null
+++ b/.rustfmt.toml
@@ -0,0 +1,13 @@
+edition = "2021"
+
+format_code_in_doc_comments = true
+format_strings = true
+group_imports = "StdExternalCrate"
+hard_tabs = true
+hex_literal_case = "Upper"
+imports_granulatiry = "Crate"
+reorder_impl_items = true
+reorder_modules = true
+use_field_init_shorthand = true
+use_try_shorthand = true
+wrap_comments = true
diff --git a/.taplo.toml b/.taplo.toml
new file mode 100644
index 0000000..88958ed
--- /dev/null
+++ b/.taplo.toml
@@ -0,0 +1,10 @@
+[formatting]
+indent_string = " "
+reorder_keys = false
+
+[[rule]]
+include = ["**/Cargo.toml"]
+keys = ["dependencies"]
+
+[rule.formatting]
+reorder_keys = true
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..ad806f1
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,552 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "anstream"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is-terminal",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "backtrace-ext"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
+
+[[package]]
+name = "brainf_rs"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "fs-err",
+ "miette",
+ "thiserror",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "4.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+ "once_cell",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "errno"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fs-err"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541"
+
+[[package]]
+name = "gimli"
+version = "0.27.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+dependencies = [
+ "hermit-abi",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "is_ci"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "miette"
+version = "5.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
+dependencies = [
+ "backtrace",
+ "backtrace-ext",
+ "is-terminal",
+ "miette-derive",
+ "once_cell",
+ "owo-colors",
+ "supports-color",
+ "supports-hyperlinks",
+ "supports-unicode",
+ "terminal_size",
+ "textwrap",
+ "thiserror",
+ "unicode-width",
+]
+
+[[package]]
+name = "miette-derive"
+version = "5.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "object"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustix"
+version = "0.38.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "smawk"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "supports-color"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
+dependencies = [
+ "is-terminal",
+ "is_ci",
+]
+
+[[package]]
+name = "supports-hyperlinks"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
+name = "supports-unicode"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
+dependencies = [
+ "smawk",
+ "unicode-linebreak",
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+
+[[package]]
+name = "unicode-linebreak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..b9c05cb
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "brainf_rs"
+version = "0.1.0"
+edition = "2021"
+resolver = "2"
+
+[dependencies]
+clap = { features = ["derive"], version = "4.3.21" }
+fs-err = "2.9.0"
+miette = { features = ["fancy"], version = "5.10.0" }
+thiserror = "1.0.44"
+
+[features]
+default = ["utilities"]
+utilities = []
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..17adf48
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# brainf_rs
+
+Brainfuck interpreter written in Rust.
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000..cda8d17
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1 @@
+avoid-breaking-exported-api = false
diff --git a/src/constants.rs b/src/constants.rs
new file mode 100644
index 0000000..6651b22
--- /dev/null
+++ b/src/constants.rs
@@ -0,0 +1,4 @@
+//! Constants and types used throughout the Brainfuck interpreter.
+
+/// The inner type used by a Brainfuck tape.
+pub type TapeInner = u8;
diff --git a/src/executor.rs b/src/executor.rs
new file mode 100644
index 0000000..f368ea9
--- /dev/null
+++ b/src/executor.rs
@@ -0,0 +1,89 @@
+//! Executor implementation for Brainfuck.
+
+use std::io::Read;
+
+use miette::Diagnostic;
+use thiserror::Error;
+
+use crate::{constants::TapeInner, parser::Instruction};
+
+/// Runtime errors that can occur in brainfuck executor.
+#[derive(Debug, Diagnostic, Error)]
+pub enum Error {
+ /// Brainfuck code performed an out of bounds index on the tape during
+ /// runtime.
+ #[error("tape indexed out of bounds, attempted index at `{0}`")]
+ IndexOutOfBounds(usize),
+
+ /// Executor was unable to read an input byte from stdin.
+ #[error("could not read input from stdin")]
+ ReadInput(#[from] std::io::Error),
+}
+
+/// Executes the provided instruction set, utilising the provided tape..
+///
+/// # Errors
+///
+/// This function will return an error if the Brainfuck code indexes out of
+/// bounds of the tape, or if the executor cannot read an input byte from stdin.
+pub fn execute(
+ instructions: &[Instruction],
+ tape: &mut [TapeInner],
+ data_pointer: &mut usize,
+) -> Result<(), Error> {
+ for instruction in instructions {
+ match *instruction {
+ Instruction::IncrementPointer => {
+ let tape_len: usize = tape.len() - 1;
+
+ if *data_pointer == tape_len {
+ *data_pointer = 0;
+ } else {
+ *data_pointer += 1;
+ }
+ }
+ Instruction::DecrementPointer => {
+ *data_pointer = match *data_pointer {
+ 0 => tape.len() - 1,
+ _ => *data_pointer - 1,
+ };
+ }
+ Instruction::IncrementByte => match tape.get_mut(*data_pointer) {
+ Some(value) => *value = value.overflowing_add(1).0,
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ },
+ Instruction::DecrementByte => match tape.get_mut(*data_pointer) {
+ Some(value) => *value = value.overflowing_sub(1).0,
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ },
+ Instruction::OutputByte => print!(
+ "{}",
+ char::from(match tape.get(*data_pointer) {
+ Some(value) => *value,
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ })
+ ),
+ Instruction::InputByte => {
+ let mut input: [TapeInner; 1] = [0; 1];
+
+ std::io::stdin().read_exact(&mut input)?;
+
+ match tape.get_mut(*data_pointer) {
+ Some(value) => *value = input[0],
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ };
+ }
+ Instruction::Loop(ref instructions) => {
+ while match tape.get(*data_pointer) {
+ Some(value) => *value,
+ None => return Err(Error::IndexOutOfBounds(*data_pointer)),
+ } != 0
+ {
+ execute(instructions, tape, data_pointer)?;
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
diff --git a/src/lexer.rs b/src/lexer.rs
new file mode 100644
index 0000000..786c873
--- /dev/null
+++ b/src/lexer.rs
@@ -0,0 +1,79 @@
+//! Lexer for Brainfuck
+
+/// List of operator codes for the lexer
+/// Note: Any input symbol that is not in this list is a comment
+#[derive(Clone, Copy, Debug)]
+pub enum OperatorCode {
+ /// `>`
+ ///
+ /// Increment the data pointer by one (to point to the next cell to the
+ /// right).
+ IncrementPointer,
+
+ /// `<`
+ ///
+ /// Decrement the data pointer by one (to point to the next cell to the
+ /// left).
+ DecrementPointer,
+
+ /// `+`
+ ///
+ /// Increment the byte at the data pointer by one.
+ IncrementByte,
+
+ /// `-`
+ ///
+ /// Decrement the byte at the data pointer by one.
+ DecrementByte,
+
+ /// `.`
+ ///
+ /// Output the byte at the data pointer.
+ OutputByte,
+
+ /// `,`
+ ///
+ /// Accept one byte of input, storing its value in the byte at the data
+ /// pointer.
+ InputByte,
+
+ /// `[`
+ ///
+ /// If the byte at the data pointer is zero, then instead of moving the
+ /// instruction pointer forward to the next command, jump it forward to the
+ /// command after the matching ] command.
+ StartLoop {
+ /// Offset of the bracket in the source.
+ offset: usize,
+ },
+
+ /// `]`
+ ///
+ /// If the byte at the data pointer is nonzero, then instead of moving the
+ /// instruction pointer forward to the next command, jump it back to the
+ /// command after the matching [ command.
+ EndLoop {
+ /// Offset of the bracket in the source.
+ offset: usize,
+ },
+}
+
+/// Perform lexical analysis on the input brainfuck code
+#[must_use]
+pub fn lex(input: &str) -> Vec<OperatorCode> {
+ input
+ .char_indices()
+ .filter_map(|(i, symbol)| match symbol {
+ '>' => Some(OperatorCode::IncrementPointer),
+ '<' => Some(OperatorCode::DecrementPointer),
+ '+' => Some(OperatorCode::IncrementByte),
+ '-' => Some(OperatorCode::DecrementByte),
+ '.' => Some(OperatorCode::OutputByte),
+ ',' => Some(OperatorCode::InputByte),
+ '[' => Some(OperatorCode::StartLoop { offset: i }),
+ ']' => Some(OperatorCode::EndLoop { offset: i }),
+ // Any symbol that does not match one of the above is a comment
+ _ => None,
+ })
+ .collect()
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..725084a
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,154 @@
+#![allow(incomplete_features)]
+#![feature(async_fn_in_trait)]
+#![feature(custom_inner_attributes)]
+#![feature(lint_reasons)]
+#![feature(never_type)]
+#![feature(once_cell)]
+#![feature(test)]
+#![deny(clippy::complexity)]
+#![deny(clippy::nursery)]
+#![deny(clippy::pedantic)]
+#![deny(clippy::perf)]
+#![deny(clippy::suspicious)]
+#![deny(clippy::alloc_instead_of_core)]
+#![deny(clippy::as_underscore)]
+#![deny(clippy::clone_on_ref_ptr)]
+#![deny(clippy::create_dir)]
+#![warn(clippy::dbg_macro)]
+#![deny(clippy::default_numeric_fallback)]
+#![deny(clippy::default_union_representation)]
+#![deny(clippy::deref_by_slicing)]
+#![deny(clippy::empty_structs_with_brackets)]
+#![deny(clippy::exit)]
+#![deny(clippy::expect_used)]
+#![deny(clippy::filetype_is_file)]
+#![deny(clippy::fn_to_numeric_cast)]
+#![deny(clippy::format_push_string)]
+#![deny(clippy::get_unwrap)]
+#![deny(clippy::if_then_some_else_none)]
+#![allow(
+ clippy::implicit_return,
+ reason = "returns should be done implicitly, not explicitly"
+)]
+#![deny(clippy::indexing_slicing)]
+#![deny(clippy::large_include_file)]
+#![deny(clippy::let_underscore_must_use)]
+#![deny(clippy::lossy_float_literal)]
+#![deny(clippy::map_err_ignore)]
+#![deny(clippy::mem_forget)]
+#![deny(clippy::missing_docs_in_private_items)]
+#![deny(clippy::missing_trait_methods)]
+#![deny(clippy::mod_module_files)]
+#![deny(clippy::multiple_inherent_impl)]
+#![deny(clippy::mutex_atomic)]
+#![deny(clippy::needless_return)]
+#![deny(clippy::non_ascii_literal)]
+#![deny(clippy::panic_in_result_fn)]
+#![deny(clippy::pattern_type_mismatch)]
+#![deny(clippy::rc_buffer)]
+#![deny(clippy::rc_mutex)]
+#![deny(clippy::rest_pat_in_fully_bound_structs)]
+#![deny(clippy::same_name_method)]
+#![deny(clippy::separated_literal_suffix)]
+#![deny(clippy::str_to_string)]
+#![deny(clippy::string_add)]
+#![deny(clippy::string_slice)]
+#![deny(clippy::string_to_string)]
+#![allow(
+ clippy::tabs_in_doc_comments,
+ reason = "tabs are preferred for this project"
+)]
+#![deny(clippy::try_err)]
+#![deny(clippy::undocumented_unsafe_blocks)]
+#![deny(clippy::unnecessary_self_imports)]
+#![deny(clippy::unneeded_field_pattern)]
+#![deny(clippy::unwrap_in_result)]
+#![deny(clippy::unwrap_used)]
+#![warn(clippy::use_debug)]
+#![deny(clippy::verbose_file_reads)]
+#![deny(clippy::wildcard_dependencies)]
+#![deny(clippy::wildcard_enum_match_arm)]
+#![deny(missing_copy_implementations)]
+#![deny(missing_debug_implementations)]
+#![deny(missing_docs)]
+#![deny(single_use_lifetimes)]
+#![deny(unsafe_code)]
+#![deny(unused)]
+
+//! Brainfuck RS
+//!
+//! Implementation of a Brainfuck interpreter written in Rust.
+
+#[cfg(test)]
+extern crate test;
+
+mod constants;
+pub mod executor;
+pub mod lexer;
+pub mod parser;
+#[cfg(feature = "utilities")]
+pub mod utility;
+
+pub use executor::execute;
+pub use lexer::{lex, OperatorCode};
+use miette::Diagnostic;
+pub use parser::{parse, Instruction};
+use thiserror::Error;
+
+/// Top-level error type for Brainfuck interpreter.
+#[derive(Debug, Diagnostic, Error)]
+pub enum Error {
+ /// Error occurred when reading input from a file.
+ #[error(transparent)]
+ Io(#[from] std::io::Error),
+
+ /// An error that occurred while parsing Brainfuck code.
+ #[diagnostic(transparent)]
+ #[error(transparent)]
+ Parser(#[from] parser::Error),
+
+ /// An error that occurred during runtime.
+ #[error(transparent)]
+ Runtime(#[from] executor::Error),
+}
+
+#[cfg(test)]
+mod tests {
+ use test::Bencher;
+
+ use super::*;
+ use crate::constants::TapeInner;
+
+ #[test]
+ fn hello_world() -> Result<(), Error> {
+ let mut tape: Vec<TapeInner> = vec![0; 1024];
+
+ utility::execute_from_file("./test_programs/hello_world.bf", &mut tape)?;
+
+ Ok(())
+ }
+
+ #[bench]
+ fn hello_world_from_hell(b: &mut Bencher) {
+ b.iter(|| {
+ let mut tape: Vec<TapeInner> = vec![0; 1024];
+
+ #[allow(clippy::expect_used)]
+ utility::execute_from_file("./test_programs/hello_world.bf", &mut tape)
+ .expect("failed to run");
+ });
+ }
+
+ #[test]
+ fn hello_world_short() -> Result<(), Error> {
+ let mut tape: Vec<TapeInner> = vec![0; 1024];
+
+ utility::execute_from_str(
+ "--[+++++++<---->>-->+>+>+<<<<]<.>++++[-<++++>>->--<<]>>-.>--..>+.<<<.<<-.>>+>->>.\
+ +++[.<]",
+ &mut tape,
+ )?;
+
+ Ok(())
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..5412735
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,125 @@
+#![allow(incomplete_features)]
+#![feature(async_fn_in_trait)]
+#![feature(custom_inner_attributes)]
+#![feature(lint_reasons)]
+#![feature(never_type)]
+#![feature(once_cell)]
+#![deny(clippy::complexity)]
+#![deny(clippy::nursery)]
+#![deny(clippy::pedantic)]
+#![deny(clippy::perf)]
+#![deny(clippy::suspicious)]
+#![deny(clippy::alloc_instead_of_core)]
+#![deny(clippy::as_underscore)]
+#![deny(clippy::clone_on_ref_ptr)]
+#![deny(clippy::create_dir)]
+#![warn(clippy::dbg_macro)]
+#![deny(clippy::default_numeric_fallback)]
+#![deny(clippy::default_union_representation)]
+#![deny(clippy::deref_by_slicing)]
+#![deny(clippy::empty_structs_with_brackets)]
+#![deny(clippy::exit)]
+#![deny(clippy::filetype_is_file)]
+#![deny(clippy::fn_to_numeric_cast)]
+#![deny(clippy::format_push_string)]
+#![deny(clippy::get_unwrap)]
+#![deny(clippy::if_then_some_else_none)]
+#![allow(
+ clippy::implicit_return,
+ reason = "returns should be done implicitly, not explicitly"
+)]
+#![deny(clippy::indexing_slicing)]
+#![deny(clippy::large_include_file)]
+#![deny(clippy::let_underscore_must_use)]
+#![deny(clippy::lossy_float_literal)]
+#![deny(clippy::map_err_ignore)]
+#![deny(clippy::mem_forget)]
+#![deny(clippy::missing_docs_in_private_items)]
+#![deny(clippy::missing_trait_methods)]
+#![deny(clippy::mod_module_files)]
+#![deny(clippy::multiple_inherent_impl)]
+#![deny(clippy::mutex_atomic)]
+#![deny(clippy::needless_return)]
+#![deny(clippy::non_ascii_literal)]
+#![deny(clippy::panic_in_result_fn)]
+#![deny(clippy::pattern_type_mismatch)]
+#![deny(clippy::rc_buffer)]
+#![deny(clippy::rc_mutex)]
+#![deny(clippy::rest_pat_in_fully_bound_structs)]
+#![deny(clippy::same_name_method)]
+#![deny(clippy::separated_literal_suffix)]
+#![deny(clippy::str_to_string)]
+#![deny(clippy::string_add)]
+#![deny(clippy::string_slice)]
+#![deny(clippy::string_to_string)]
+#![allow(
+ clippy::tabs_in_doc_comments,
+ reason = "tabs are preferred for this project"
+)]
+#![deny(clippy::try_err)]
+#![deny(clippy::undocumented_unsafe_blocks)]
+#![deny(clippy::unnecessary_self_imports)]
+#![deny(clippy::unneeded_field_pattern)]
+#![deny(clippy::unwrap_in_result)]
+#![deny(clippy::unwrap_used)]
+#![warn(clippy::use_debug)]
+#![deny(clippy::verbose_file_reads)]
+#![deny(clippy::wildcard_dependencies)]
+#![deny(clippy::wildcard_enum_match_arm)]
+#![deny(missing_copy_implementations)]
+#![deny(missing_debug_implementations)]
+#![deny(missing_docs)]
+#![deny(single_use_lifetimes)]
+#![deny(unsafe_code)]
+#![deny(unused)]
+
+//! Brainfuck RS
+//!
+//! Brainfuck interpreter written in Rust
+
+use std::path::PathBuf;
+
+use brainf_rs::utility::{execute_from_file, execute_from_str};
+use clap::{Parser, Subcommand};
+use miette::Context;
+
+/// Representation of command line application for the Brainfuck interpreter.
+#[derive(Parser)]
+#[command(author, version, about, long_about = None)]
+#[command(propagate_version = true)]
+struct App {
+ /// Subcommands supported by the Brainfuck interpreter
+ #[command(subcommand)]
+ command: Command,
+}
+
+/// Implementation of subcommands for the Brainfuck interpreter.
+#[derive(Subcommand)]
+enum Command {
+ /// Interprets Brainfuck code from a provided file.
+ File {
+ /// Path to the file to interpret with Brainfuck.
+ path: PathBuf,
+ },
+
+ /// Interprets Brainfuck code from a text input on the command line.
+ Text {
+ /// Text input to be interpreted as Brainfuck.
+ input: String,
+ },
+}
+
+fn main() -> miette::Result<()> {
+ let app = App::parse();
+
+ let mut tape = vec![0u8; 1024];
+
+ match app.command {
+ Command::File { ref path } => {
+ execute_from_file(path, &mut tape).wrap_err("when executing from file")?;
+ }
+ Command::Text { ref input } => execute_from_str(input, &mut tape)?,
+ };
+
+ Ok(())
+}
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..cffa6db
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,164 @@
+//! Parser implementation for Brainfuck. Parses operator codes into instruction
+//! sets.
+
+use miette::{Diagnostic, SourceSpan};
+use thiserror::Error;
+
+use crate::lexer::OperatorCode;
+
+/// Parsed instructions for Brainfuck.
+#[derive(Clone, Debug)]
+pub enum Instruction {
+ /// `>`
+ ///
+ /// Increment the data pointer by one (to point to the next cell to the
+ /// right).
+ IncrementPointer,
+
+ /// `<`
+ ///
+ /// Decrement the data pointer by one (to point to the next cell to the
+ /// left).
+ DecrementPointer,
+
+ /// `+`
+ ///
+ /// Increment the byte at the data pointer by one.
+ IncrementByte,
+
+ /// `-`
+ ///
+ /// Decrement the byte at the data pointer by one.
+ DecrementByte,
+
+ /// `.`
+ ///
+ /// Output the byte at the data pointer.
+ OutputByte,
+
+ /// `,`
+ ///
+ /// Accept one byte of input, storing its value in the byte at the data
+ /// pointer.
+ InputByte,
+
+ /// `[]`
+ ///
+ /// Loops through the inner instructions.
+ Loop(Vec<Instruction>),
+}
+
+/// Error type for errors that occur during parsing from [`OperatorCode`]s to
+/// [`Instruction`]s.
+#[derive(Debug, Diagnostic, Error)]
+pub enum Error {
+ /// Parser encountered a loop with no beginning.
+ #[diagnostic(help("try closing the loop"))]
+ #[error("loop closed at {loop_src:?} has no beginning")]
+ LoopWithNoBeginning {
+ /// Source code associated with diagnostic
+ #[source_code]
+ input: String,
+
+ /// SourceSpan of the loop bracket.
+ #[label("loop ending")]
+ loop_src: SourceSpan,
+ },
+
+ /// Parser encountered a loop with no ending.
+ #[diagnostic(help("try closing the loop"))]
+ #[error("loop beginning at {loop_src:?} has no ending")]
+ LoopWithNoEnding {
+ /// Source code associated with diagnostic
+ #[source_code]
+ input: String,
+
+ /// SourceSpan of the loop bracket.
+ #[label("loop beginning")]
+ loop_src: SourceSpan,
+ },
+
+ /// Parser sliced out of bounds.
+ #[error("parser sliced out of bounds")]
+ SliceOutOfBounds(std::ops::Range<usize>),
+}
+
+/// Parses the operator codes into instruction codes.
+///
+/// # Parameters
+///
+/// * `src` - The source the operator codes originate from. This is used for
+/// error reporting.
+/// * `operator_codes` - The operator codes reveiced from the lexer.
+///
+/// # Errors
+///
+/// This function will return an error if a loop is encountered with no
+/// beginning, a loop is encountered with no ending, or if the parser attempts
+/// to slice out of bounds.
+pub fn parse(src: &str, operator_codes: &[OperatorCode]) -> Result<Vec<Instruction>, Error> {
+ let mut program: Vec<Instruction> = Vec::new();
+ let mut loop_stack: i32 = 0;
+ let mut loop_start = 0;
+ let mut loop_source_offset: usize = 0;
+
+ operator_codes
+ .iter()
+ .enumerate()
+ .try_for_each(|(i, operator_code)| -> Result<(), Error> {
+ match (loop_stack, *operator_code) {
+ (0i32, OperatorCode::StartLoop { offset }) => {
+ loop_start = i;
+ loop_source_offset = offset;
+ loop_stack += 1i32;
+ }
+ (0i32, _) => {
+ if let Some(instruction) = match *operator_code {
+ OperatorCode::IncrementPointer => Some(Instruction::IncrementPointer),
+ OperatorCode::DecrementPointer => Some(Instruction::DecrementPointer),
+ OperatorCode::IncrementByte => Some(Instruction::IncrementByte),
+ OperatorCode::DecrementByte => Some(Instruction::DecrementByte),
+ OperatorCode::OutputByte => Some(Instruction::OutputByte),
+ OperatorCode::InputByte => Some(Instruction::InputByte),
+ OperatorCode::EndLoop { offset } => {
+ return Err(Error::LoopWithNoBeginning {
+ input: src.to_owned(),
+ loop_src: (offset, 1).into(),
+ })
+ }
+ // We don't care about this variant as it is handled in a subsequent arm
+ OperatorCode::StartLoop { .. } => None,
+ } {
+ program.push(instruction);
+ }
+ }
+ (_, OperatorCode::StartLoop { .. }) => loop_stack += 1i32,
+ (_, OperatorCode::EndLoop { .. }) => {
+ loop_stack -= 1i32;
+ if loop_stack == 0i32 {
+ let loop_program = parse(
+ src,
+ match operator_codes.get(loop_start + 1..i) {
+ Some(value) => value,
+ None => return Err(Error::SliceOutOfBounds(loop_start + 1..i)),
+ },
+ )?;
+
+ program.push(Instruction::Loop(loop_program));
+ }
+ }
+ _ => (),
+ };
+
+ Ok(())
+ })?;
+
+ if loop_stack == 0i32 {
+ Ok(program)
+ } else {
+ Err(Error::LoopWithNoEnding {
+ input: src.to_owned(),
+ loop_src: (loop_source_offset, 1).into(),
+ })
+ }
+}
diff --git a/src/utility.rs b/src/utility.rs
new file mode 100644
index 0000000..a903273
--- /dev/null
+++ b/src/utility.rs
@@ -0,0 +1,47 @@
+//! Utility functions for working with the Brainfuck interpreter.
+
+use std::path::Path;
+
+use crate::{constants::TapeInner, execute, lex, parse, Error};
+
+/// Utility function to execute a Brainfuck file. Lexes, parses and executes the
+/// input file.
+///
+/// # Errors
+///
+/// This function will return an error if reading the input file, parsing or
+/// execution fails. See documentation for [`crate::parser::parse`] and
+/// [`crate::executor::execute`].
+pub fn execute_from_file(path: impl AsRef<Path>, tape: &mut [TapeInner]) -> Result<(), Error> {
+ let input = fs_err::read_to_string(path.as_ref())?;
+
+ let operator_codes = lex(&input);
+
+ let instructions = parse(&input, &operator_codes)?;
+
+ let mut data_pointer = 0;
+
+ execute(&instructions, tape, &mut data_pointer)?;
+
+ Ok(())
+}
+
+/// Utility function to execute Brainfuck code. Lexes, parses and executes the
+/// input.
+///
+/// # Errors
+///
+/// This function will return an error if parsing or
+/// execution fails. See documentation for [`crate::parser::parse`] and
+/// [`crate::executor::execute`].
+pub fn execute_from_str(input: &str, tape: &mut [TapeInner]) -> Result<(), Error> {
+ let operator_codes = lex(input);
+
+ let instructions = parse(input, &operator_codes)?;
+
+ let mut data_pointer = 0;
+
+ execute(&instructions, tape, &mut data_pointer)?;
+
+ Ok(())
+}
diff --git a/test_programs/hello_world.bf b/test_programs/hello_world.bf
new file mode 100644
index 0000000..1b8ceb3
--- /dev/null
+++ b/test_programs/hello_world.bf
@@ -0,0 +1,43 @@
+[ This program prints "Hello World!" and a newline to the screen, its
+ length is 106 active command characters. [It is not the shortest.]
+
+ This loop is an "initial comment loop", a simple way of adding a comment
+ to a BF program such that you don't have to worry about any command
+ characters. Any ".", ",", "+", "-", "<" and ">" characters are simply
+ ignored, the "[" and "]" characters just have to be balanced. This
+ loop and the commands it contains are ignored because the current cell
+ defaults to a value of 0; the 0 value causes this loop to be skipped.
+]
+++++++++ Set Cell #0 to 8
+[
+ >++++ Add 4 to Cell #1; this will always set Cell #1 to 4
+ [ as the cell will be cleared by the loop
+ >++ Add 2 to Cell #2
+ >+++ Add 3 to Cell #3
+ >+++ Add 3 to Cell #4
+ >+ Add 1 to Cell #5
+ <<<<- Decrement the loop counter in Cell #1
+ ] Loop until Cell #1 is zero; number of iterations is 4
+ >+ Add 1 to Cell #2
+ >+ Add 1 to Cell #3
+ >- Subtract 1 from Cell #4
+ >>+ Add 1 to Cell #6
+ [<] Move back to the first zero cell you find; this will
+ be Cell #1 which was cleared by the previous loop
+ <- Decrement the loop Counter in Cell #0
+] Loop until Cell #0 is zero; number of iterations is 8
+
+The result of this is:
+Cell no : 0 1 2 3 4 5 6
+Contents: 0 0 72 104 88 32 8
+Pointer : ^
+
+>>. Cell #2 has value 72 which is 'H'
+>---. Subtract 3 from Cell #3 to get 101 which is 'e'
++++++++..+++. Likewise for 'llo' from Cell #3
+>>. Cell #5 is 32 for the space
+<-. Subtract 1 from Cell #4 for 87 to give a 'W'
+<. Cell #3 was set to 'o' from the end of 'Hello'
++++.------.--------. Cell #3 for 'rl' and 'd'
+>>+. Add 1 to Cell #5 gives us an exclamation point
+>++. And finally a newline from Cell #6 \ No newline at end of file
diff --git a/test_programs/hello_world_from_hell.bf b/test_programs/hello_world_from_hell.bf
new file mode 100644
index 0000000..64c3645
--- /dev/null
+++ b/test_programs/hello_world_from_hell.bf
@@ -0,0 +1,263 @@
+[
+ This routine is a demonstration of checking for the three cell sizes
+ that are normal for Brainfuck. The demo code also checks for bugs
+ that have been noted in various interpreters and compilers.
+
+ It should print one of three slight variations of "Hello world" followed
+ by an exclamation point then the maximum cell value (if it's less than a
+ few thousand) and a newline.
+
+ If the interpreter is broken in some way it can print a lot of other
+ different strings and frequently causes the interpreter to crash.
+
+ It does work correctly with 'bignum' cells.
+]
++>>
+
+ This code runs at pointer offset two and unknown bit width; don't
+ assume you have more that eight bits
+
+ ======= DEMO CODE =======
+ First just print "Hello"
+
+ Notice that I reset the cells despite knowing that they are zero
+ this is a test for proper functioning of the ability to skip over
+ a loop that's never executed but isn't actually a comment loop
+
+ Secondly there's a NOP movement between the two 'l' characters
+
+ Also there's some commented out code afterwards
+
+ >[-]<[-]++++++++[->+++++++++<]>.----[--<+++>]<-.+++++++.><.+++.
+ [-][[-]>[-]+++++++++[<+++++>-]<+...--------------.>++++++++++[<+
+ ++++>-]<.+++.-------.>+++++++++[<----->-]<.-.>++++++++[<+++++++>
+ -]<++.-----------.--.-----------.+++++++.----.++++++++++++++.>++
+ ++++++++[<----->-]<..[-]++++++++++.[-]+++++++[.,]-]
+
+ ===== END DEMO CODE =====
+<<-
+
+Calculate the value 256 and test if it's zero
+If the interpreter errors on overflow this is where it'll happen
+++++++++[>++++++++<-]>[<++++>-]
++<[>-<
+Multiply by 256 again to get 65536
+[>++++<-]>[<++++++++>-]<[>++++++++<-]
++>[>
+ Cells should be 32bits at this point
+
+ The pointer is at cell two and you can continue your code confident
+ that there are big cells
+
+ ======= DEMO CODE =======
+ This code rechecks that the test cells are in fact nonzero
+ If the compiler notices the above is constant but doesn't
+ properly wrap the values this will generate an incorrect
+ string
+
+ An optimisation barrier; unbalanced loops aren't easy
+ >+[<]>-<
+
+ Print a message
+ ++>[-]++++++[<+++++++>-]<.------------.[-]
+ <[>+<[-]]>
+ ++++++++>[-]++++++++++[<+++++++++++>-]<.--------.+++.------.
+ --------.[-]
+
+ ===== END DEMO CODE =====
+
+<[-]<[-]>] <[>>
+ Cells should be 16bits at this point
+
+ The pointer is at cell two and you can continue your code confident
+ that there are medium sized cells; you can use all the cells on the
+ tape but it is recommended that you leave the first two alone
+
+ If you need 32bit cells you'll have to use a BF doubler
+
+ ======= DEMO CODE =======
+ Space
+ ++>[-]+++++[<++++++>-]<.[-]
+
+ I'm rechecking that the cells are 16 bits
+ this condition should always be true
+
+ +>>++++[-<<[->++++<]>[-<+>]>]< + <[ >>
+
+ Print a message
+ >[-]++++++++++[<+++++++++++>-]<+++++++++.--------.
+ +++.------.--------.[-]
+
+ <[-]<[-] ] >[> > Dead code here
+ This should never be executed because it's in an 8bit zone hidden
+ within a 16bit zone; a really good compiler should delete this
+ If you see this message you have dead code walking
+
+ Print a message
+ [-]>[-]+++++++++[<++++++++++>-]<.
+ >++++[<+++++>-]<+.--.-----------.+++++++.----.
+ [-]
+
+ <<[-]]<
+ ===== END DEMO CODE =====
+
+<<[-]] >[-]< ] >[>
+ Cells should be 8bits at this point
+
+ The pointer is at cell two but you only have 8 bits cells
+ and it's time to use the really big and slow BF quad encoding
+
+ ======= DEMO CODE =======
+
+ A broken wrapping check
+ +++++[>++++<-]>[<+++++++++++++>-]<----[[-]>[-]+++++[<++++++>-]<++.
+ >+++++[<+++++++>-]<.>++++++[<+++++++>-]<+++++.>++++[<---->-]<-.++.
+ ++++++++.------.-.[-]]
+
+ Space
+ ++>[-]+++++[<++++++>-]<.[-]
+
+ An exponent checker for github user btzy
+ >++[>++<-]>[<<+>>[-<<[>++++<-]>[<++++>-]>]]<<[>++++[>---<++++]>++.
+ [<++>+]<.[>+<------]>.+++.[<--->++]<--.[-]<[-]]
+
+ Another dead code check
+ [-]>[-]>[-]<++[>++++++++<-]>[<++++++++>-]<[>++++++++<-]>[<++++++++>-
+ ]<[<++++++++>-]<[[-]>[-]+++++++++[<++++++++++>-]<.>++++[<+++++>-]<+.
+ --.-----------.+++++++.----.>>[-]<+++++[>++++++<-]>++.<<[-]]
+
+ Print a message
+ [-] <[>+<[-]]> +++++>[-]+++++++++[<+++++++++>-]<.
+ >++++[<++++++>-]<.+++.------.--------.
+ [-]
+ ===== END DEMO CODE =====
+
+<[-]]<
+
++[[>]<-] Check unbalanced loops are ok
+
+>>
+ ======= DEMO CODE =======
+ Back out and print the last two characters
+
+ [<[[<[[<[[<[,]]]<]<]<]<][ Deep nesting non-comment comment loop ]]
+
+ Check that an offset of 128 will work
+ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>-[+<-]
+
+ And back
+ +++[->++++++<]>[-<+++++++>]<[->>[>]+[<]<]>>[->]<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+
+ And inside a loop
+ --[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>++<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+]+>----[++
+ ++>----]-[+<-]
+
+ This is a simple multiply loop that looks like it goes off the
+ start of the tape
+ +[>]<- [-
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ ++++
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+ ]
+
+ [ Check there are enough cells. This takes 18569597 steps. ]
+ [
+ >++++++[<+++>-]<+[>+++++++++<-]>+[[->+>+<<]>>
+ [-<<+>>]<[<[->>+<<]+>[->>+<<]+[>]<-]<-]<[-<]
+ ]
+
+ This loop is a bug check for handling of nested loops; it goes
+ round the outer loop twice and the inner loop is skipped on the
+ first pass but run on the second
+
+ BTW: It's unlikely that an optimiser will notice how this works
+
+ >
+ +[>[
+ Print the exclamation point
+ [-]+++>
+ [-]+++++ +#-
+ [<+++2+++>-]<
+ .
+
+ <[-]>[-]]+<]
+ <
+
+ Clean up any debris
+ ++++++++[[>]+[<]>-]>[>]<[[-]<]
+
+ This is a hard optimisation barrier
+ It contains several difficult to 'prove' constructions close together
+ and is likely to prevent almost all forms of optimisation
+ +[[>]<-[,]+[>]<-[]]
+
+ This part finds the actual value that the cell wraps at; even
+ if it's not one of the standard ones; but it gets bored after
+ a few thousand: any higher and we print nothing
+
+ This has a reasonably deep nested loop and a couple of loops
+ that have unbalanced pointer movements
+
+ Find maxint (if small)
+ [-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<++++[->>++++>>++++>>++
+ ++<<<<<<]++++++++++++++>>>>+>>++<<<<<<[->>[->+>[->+>[->+>+[>
+ >>+<<]>>[-<<+>]<-[<<<[-]<<[-]<<[-]<<[-]>>>[-]>>[-]>>[-]>->+]
+ <<<]>[-<+>]<<<]>[-<+>]<<<]>[-<+>]<<<]>+>[[-]<->]<[->>>>>>>[-
+ <<<<<<<<+>>>>>>>>]<<<<<<<]<
+
+ The number is only printed if we found the actual maxint
+ >+<[
+ Space
+ >[-]>[-]+++++[<++++++>-]<++.[-]<
+
+ Print the number
+ [[->>+<<]>>[-<++>[-<+>[-<+>[-<+>[-<+>[-<+>[-<+>[-<+>[-<+>[<[-]+>
+ ->+<[<-]]]]]]]]]]>]<<[>++++++[<++++++++>-]<-.[-]<]]
+
+ ]
+
+ Check if we should have had a value but didn't
+ >[
+ >[-]>[-]++++[<++++++++>-]<[<++++++++>-]>+++[<++++++++>-]<+++++++
+ [<-------->-]<------->+<[[-]>-<]>[>[-]<[-]++++[->++++++++<]>.+++
+ +++[-<++>]<.[-->+++<]>++.<++++[>----<-]>.[-]<]<
+
+ [-]>[-]++++++++[<++++++++>-]<[>++++<-]+>[<->[-]]<[>[-]<[-]++++[-
+ >++++++++<]>.---[-<+++>]<.---.--------------.[-->+<]>--.[-]<]
+ ]<
+
+ Clean up any debris
+ ++++++++[[>]+[<]>-]>[>]<[[-]<]
+
+ One last thing: an exclamation point is not a valid BF instruction!
+
+ Print the newline
+ [-]++++++++++.[-]
+ [
+ Oh, and now that I can use "!" the string you see should be one of:
+ Hello World! 255
+ Hello world! 65535
+ Hello, world!
+
+ And it should be followed by a newline.
+ ]
+
+ ===== END DEMO CODE =====
+
+<< Finish at cell zero \ No newline at end of file