From 5126c9ed83fe6169463566e74f966a4a63e57ca0 Mon Sep 17 00:00:00 2001 From: Sophie Forrest Date: Fri, 30 Aug 2024 23:35:45 +1200 Subject: feat: initial commit of brainf interpreter --- .gitignore | 1 + .rustfmt.toml | 13 + .taplo.toml | 10 + Cargo.lock | 552 +++++++++++++++++++++++++++++++++ Cargo.toml | 15 + README.md | 3 + clippy.toml | 1 + src/constants.rs | 4 + src/executor.rs | 89 ++++++ src/lexer.rs | 79 +++++ src/lib.rs | 154 +++++++++ src/main.rs | 125 ++++++++ src/parser.rs | 164 ++++++++++ src/utility.rs | 47 +++ test_programs/hello_world.bf | 43 +++ test_programs/hello_world_from_hell.bf | 263 ++++++++++++++++ 16 files changed, 1563 insertions(+) create mode 100644 .gitignore create mode 100644 .rustfmt.toml create mode 100644 .taplo.toml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 clippy.toml create mode 100644 src/constants.rs create mode 100644 src/executor.rs create mode 100644 src/lexer.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/parser.rs create mode 100644 src/utility.rs create mode 100644 test_programs/hello_world.bf create mode 100644 test_programs/hello_world_from_hell.bf 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 { + 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 = 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 = 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 = 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), +} + +/// 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), +} + +/// 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, Error> { + let mut program: Vec = 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, 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 -- cgit 1.4.1