diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | .rustfmt.toml | 13 | ||||
| -rw-r--r-- | .taplo.toml | 10 | ||||
| -rw-r--r-- | Cargo.lock | 552 | ||||
| -rw-r--r-- | Cargo.toml | 15 | ||||
| -rw-r--r-- | README.md | 3 | ||||
| -rw-r--r-- | clippy.toml | 1 | ||||
| -rw-r--r-- | src/constants.rs | 4 | ||||
| -rw-r--r-- | src/executor.rs | 89 | ||||
| -rw-r--r-- | src/lexer.rs | 79 | ||||
| -rw-r--r-- | src/lib.rs | 154 | ||||
| -rw-r--r-- | src/main.rs | 125 | ||||
| -rw-r--r-- | src/parser.rs | 164 | ||||
| -rw-r--r-- | src/utility.rs | 47 | ||||
| -rw-r--r-- | test_programs/hello_world.bf | 43 | ||||
| -rw-r--r-- | test_programs/hello_world_from_hell.bf | 263 |
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 |