summary refs log tree commit diff
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
Diffstat (limited to '')
-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