diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..2245a57 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,35 @@ +on: [push, pull_request] + +name: CI + +jobs: + build: + name: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Build project + run: cargo build --locked + + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Run tests + run: cargo test + + fmt: + name: fmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + run: cargo fmt --all -- --check + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index d2779e2..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,45 +0,0 @@ -on: [push, pull_request] - -name: CI - -jobs: - build: - name: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Build project - run: cargo build --verbose - - test: - name: test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Run tests - run: cargo test --verbose - - fmt: - name: fmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Check formatting - run: | - rustup component add rustfmt - cargo fmt --all -- --check diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..b211749 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,57 @@ +name: Release + +on: + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Create new release + run: | + cargo install cargo-extract + VERSION=v$(cargo extract package.version) + echo "Creating release: ${VERSION}" + gh release create ${VERSION} --title ${VERSION} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + upload-assets: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: windows-latest + extension: ".exe" + + name: Upload assets for ${{ matrix.os }} + runs-on: ${{ matrix.os }} + needs: release + + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Build + run: cargo build --release --locked + - name: Upload binary + shell: bash + run: | + cargo install cargo-extract + CRATE_NAME=$(cargo extract package.name) + CRATE_VERSION=v$(cargo extract package.version) + ARCH_TRIPLE=$(cargo extract --arch) + + OUTPUT_FILENAME="${CRATE_NAME}-${ARCH_TRIPLE}${{ matrix.extension }}" + mv "target/release/${CRATE_NAME}${{ matrix.extension }}" "$OUTPUT_FILENAME" + + echo "${CRATE_NAME}@{CRATE_VERSION} (${ARCH_TRIPLE})" + echo "Uploading ${OUTPUT_FILENAME}..." + + gh release upload ${CRATE_VERSION} ${OUTPUT_FILENAME} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index eabad96..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Publish - -on: - release: - types: published - -jobs: - publish: - - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - include: - # TODO: Fix duplication of binary name - - os: ubuntu-latest - artifact_name: brainrust-cli # File name of cargo generated binary - asset_name: brainrust-cli-linux-amd64 # Name of asset uploaded to Github - - os: windows-latest - artifact_name: brainrust-cli.exe # File name of cargo generated binary - asset_name: brainrust-cli-windows-amd64 # Name of asset uploaded to Github - - os: macos-latest - artifact_name: brainrust-cli # File name of cargo generated binary - asset_name: brainrust-cli-macos-amd64 # Name of asset uploaded to Github - - name: Publish for ${{ matrix.os }} - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v2 - - name: Install latest stable - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Build - run: cargo build --release - - name: Upload binary to release - uses: svenstaro/upload-release-action@v1-release - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: target/release/${{ matrix.artifact_name }} - asset_name: ${{ matrix.asset_name }} - tag: ${{ github.ref }} diff --git a/.gitignore b/.gitignore index 53eaa21..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock index 4afab4d..83568a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,163 +1,258 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 4 + [[package]] -name = "ansi_term" -version = "0.11.0" +name = "anstream" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "atty" -version = "0.2.13" +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8parse", ] [[package]] -name = "bitflags" -version = "1.2.1" +name = "anstyle-query" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] [[package]] -name = "brainrust-cli" -version = "0.1.0" +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ - "brainrust-engine 0.1.0", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "anstyle", + "once_cell", + "windows-sys", ] [[package]] -name = "brainrust-engine" -version = "0.1.0" +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "brainrust" +version = "0.1.4" dependencies = [ - "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap", + "itertools", ] [[package]] name = "clap" -version = "2.33.0" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "clap_builder", ] +[[package]] +name = "clap_builder" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "either" -version = "1.5.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "itertools" -version = "0.8.2" +name = "errno" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "windows-sys", ] [[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either", ] [[package]] name = "libc" -version = "0.2.66" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] [[package]] name = "strsim" -version = "0.8.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "term_size" -version = "0.3.1" +name = "terminal_size" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rustix", + "windows-sys", ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "windows-targets", ] [[package]] -name = "unicode-width" -version = "0.1.6" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] -name = "vec_map" -version = "0.8.1" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "winapi" -version = "0.2.8" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "winapi" -version = "0.3.8" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "winapi-build" -version = "0.1.1" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[metadata] -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" -"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index c7b050e..e19db89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,10 @@ -[workspace] +[package] +name = "brainrust" +version = "0.1.4" +authors = ["Emil Englesson "] +edition = "2024" +publish = false -members = [ - "brainrust-engine", - "brainrust-cli" -] +[dependencies] +clap = { version = "4.5", features = ["cargo", "color", "wrap_help", "suggestions"] } +itertools = "0.14" diff --git a/README.md b/README.md index 7133092..b7fd503 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ -![](https://github.com/LimeEng/brainrust/workflows/CI/badge.svg) +[![CI status](https://github.com/LimeEng/brainrust/actions/workflows/ci.yaml/badge.svg)](https://github.com/LimeEng/brainrust/actions/workflows/ci.yaml) # Brainrust -Brainrust is a simple Brainfuck interpreter written in Rust. [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck) is a very simple but still Turing complete language with just eight instructions. - -Brainrust consists of two parts, the CLI `brainrust-cli` and the engine `brainrust-engine`. The engine exposes methods to lex, parse, optimize and interpret Brainfuck code. The CLI is using the engine to interpret Brainfuck programs and is currently quite minimalistic. - -This repository is a [Cargo workspace](https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html) which allows the library to evolve more independently from the CLI and vice versa. +Brainrust is a Brainfuck interpreter. [Brainfuck](https://en.wikipedia.org/wiki/Brainfuck) is a very simple but still Turing complete language with just eight instructions. - [Installation](#installation) - [Usage](#usage) @@ -15,24 +11,16 @@ This repository is a [Cargo workspace](https://doc.rust-lang.org/book/ch14-03-ca ## Installation -Install the CLI by running. -``` +Install brainrust by either grabbing a [pre-built binary](https://github.com/LimeEng/brainrust/releases) or by running one of these commands. + +```sh +cargo install brainrust cargo install --git https://github.com/LimeEng/brainrust ``` -It is also possible to download a pre-built binary for either Windows, Linux or macOS from the [releases-page](https://github.com/LimeEng/brainrust/releases). - ## Usage -If you installed the CLI with cargo or downloaded a pre-built binary, just invoke the binary with a file as argument. -``` -brainrust-cli run file.b -``` - -If not, use cargo to run the CLI -``` -cargo run --release run file.b -``` +Run a brainfuck program by simply running `brainrust run program.b` ## Optimizations @@ -79,8 +67,8 @@ Implementing optimized interpreters/compilers for Brainfuck is certainly nothing - http://calmerthanyouare.org/2015/01/07/optimizing-brainfuck.html - http://www.hevanet.com/cristofd/brainfuck/ - https://github.com/Wilfred/bfc -- https://www.nayuki.io/page/optimizing-brainfuck-compiler - http://www.wilfred.me.uk/blog/2015/08/29/an-optimising-bf-compiler/ +- https://www.nayuki.io/page/optimizing-brainfuck-compiler Additionally, here are some resources on where to find runnable Brainfuck programs. diff --git a/brainrust-cli/.gitignore b/brainrust-cli/.gitignore deleted file mode 100644 index 53eaa21..0000000 --- a/brainrust-cli/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -**/*.rs.bk diff --git a/brainrust-cli/Cargo.toml b/brainrust-cli/Cargo.toml deleted file mode 100644 index b6e9758..0000000 --- a/brainrust-cli/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "brainrust-cli" -version = "0.1.0" -authors = ["Emil Englesson "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -brainrust-engine = { path = "../brainrust-engine" } -clap = {version = "2.33.0", features = ["color", "wrap_help", "suggestions"]} diff --git a/brainrust-engine/.gitignore b/brainrust-engine/.gitignore deleted file mode 100644 index 6936990..0000000 --- a/brainrust-engine/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -**/*.rs.bk -Cargo.lock diff --git a/brainrust-engine/Cargo.toml b/brainrust-engine/Cargo.toml deleted file mode 100644 index da52b2f..0000000 --- a/brainrust-engine/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "brainrust-engine" -version = "0.1.0" -authors = ["Emil Englesson "] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -itertools = "0.8.2" diff --git a/brainrust-engine/tests/integration_test.rs b/brainrust-engine/tests/integration_test.rs deleted file mode 100644 index 4b559d1..0000000 --- a/brainrust-engine/tests/integration_test.rs +++ /dev/null @@ -1,89 +0,0 @@ -extern crate brainrust_engine; -use brainrust_engine::interpreter; -use brainrust_engine::interpreter::Interpreter; -use brainrust_engine::lexer; -use brainrust_engine::optimizer; -use brainrust_engine::parser; - -use std::fs; -use std::str; - -const PROGRAM_PREFIX: &str = "tests/programs/"; -const PROGRAM_EXTENSION: &str = ".b"; -const INPUT_EXTENSION: &str = ".input"; -const OUTPUT_EXTENSION: &str = ".output"; - -const MEMORY_SIZE: usize = 32768; - -macro_rules! test_programs { - ($($test_name:ident: $program_name:expr,)*) => { - $( - #[test] - fn $test_name() -> Result<(), TestError> { - let program = format!("{}{}{}", PROGRAM_PREFIX, $program_name, PROGRAM_EXTENSION); - let input = format!("{}{}{}", PROGRAM_PREFIX, $program_name, INPUT_EXTENSION); - let output = format!("{}{}{}", PROGRAM_PREFIX, $program_name, OUTPUT_EXTENSION); - - let program = fs::read(program)?; - let input = fs::read(input)?; - let output = fs::read(output)?; - - let program = str::from_utf8(&program)?; - let input = str::from_utf8(&input)?; - - let result = run_program(program, input)?; - - assert_eq!(result, output); - Ok(()) - } - )* - } -} - -test_programs! { - monty: "monty", -} - -fn run_program(file: &str, input: &str) -> Result, TestError> { - let tokens = lexer::lex(&file); - let parsed = parser::parse(tokens)?; - let optimized = optimizer::optimize(parsed); - let mut interpreter = Interpreter::new(MEMORY_SIZE); - - let mut output: Vec = vec![]; - - interpreter.run(optimized, &mut input.as_bytes(), &mut output)?; - Ok(output) -} - -#[derive(Debug)] -enum TestError { - Io(std::io::Error), - Parsing(parser::Error), - Interpreter(interpreter::Error), - ConversationError(std::str::Utf8Error), -} - -impl From for TestError { - fn from(io_error: std::io::Error) -> Self { - TestError::Io(io_error) - } -} - -impl From for TestError { - fn from(parser_error: parser::Error) -> Self { - TestError::Parsing(parser_error) - } -} - -impl From for TestError { - fn from(interpreter_error: interpreter::Error) -> Self { - TestError::Interpreter(interpreter_error) - } -} - -impl From for TestError { - fn from(utf8_error: std::str::Utf8Error) -> Self { - TestError::ConversationError(utf8_error) - } -} diff --git a/brainrust-engine/src/interpreter.rs b/src/interpreter.rs similarity index 92% rename from brainrust-engine/src/interpreter.rs rename to src/interpreter.rs index d0aa367..1e247d1 100644 --- a/brainrust-engine/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,5 +1,4 @@ use std::io; -use std::io::Error as IoError; #[derive(Debug, Copy, Clone, PartialEq)] pub enum Instruction { @@ -16,14 +15,14 @@ pub enum Instruction { #[derive(Debug)] pub enum Error { - Io(IoError), + Io(io::Error), PointerOverflow, PointerUnderflow, } -impl From for Error { - fn from(io_error: IoError) -> Self { - Error::Io(io_error) +impl From for Error { + fn from(error: io::Error) -> Self { + Error::Io(error) } } @@ -33,6 +32,7 @@ pub struct Interpreter { } impl Interpreter { + #[must_use] pub fn new(memory_size: usize) -> Self { Interpreter { memory: vec![0; memory_size], @@ -45,12 +45,12 @@ impl Interpreter { } fn write_current_cell(&mut self, value: u8) { - self.memory[self.pointer] = value + self.memory[self.pointer] = value; } pub fn run( &mut self, - program: Vec, + program: &[Instruction], input: &mut dyn io::Read, output: &mut dyn io::Write, ) -> Result<(), Error> { diff --git a/brainrust-engine/src/lexer.rs b/src/lexer.rs similarity index 100% rename from brainrust-engine/src/lexer.rs rename to src/lexer.rs diff --git a/brainrust-engine/src/lib.rs b/src/lib.rs similarity index 100% rename from brainrust-engine/src/lib.rs rename to src/lib.rs diff --git a/brainrust-cli/src/main.rs b/src/main.rs similarity index 57% rename from brainrust-cli/src/main.rs rename to src/main.rs index 10dcdc7..79d2326 100644 --- a/brainrust-cli/src/main.rs +++ b/src/main.rs @@ -1,18 +1,9 @@ -use std::env; -use std::fs; -use std::io; -use std::process; -use std::time::Instant; - -use brainrust_engine::interpreter; -use brainrust_engine::interpreter::Interpreter; -use brainrust_engine::lexer; -use brainrust_engine::optimizer; -use brainrust_engine::parser; - -#[macro_use] -extern crate clap; -use clap::{App, AppSettings, Arg}; +use brainrust::{ + interpreter::{self, Interpreter}, + lexer, optimizer, parser, +}; +use clap::{Arg, ArgAction, ArgMatches, Command, crate_name, crate_version, value_parser}; +use std::{env, fs, io, time::Instant}; const DEFAULT_MEMORY_SIZE: usize = 32768; const CLI_SUB_CMD_RUN: &str = "run"; @@ -48,94 +39,86 @@ impl From for CliError { } fn main() -> Result<(), CliError> { - let matches = App::new("brainrust") + let matches = Command::new(crate_name!()) + .version(crate_version!()) .long_version(crate_version!()) - .version_short("v") - .about("Interprets Brainfuck efficiently") - .setting(AppSettings::ArgRequiredElseHelp) + .about("Brainfuck interpreter") + .arg_required_else_help(true) .subcommand( - App::new(CLI_SUB_CMD_RUN) + Command::new(CLI_SUB_CMD_RUN) .about("Parses Brainfuck in the specified file and interprets it") .arg( - Arg::with_name(CLI_ARG_INPUT_FILE) + Arg::new(CLI_ARG_INPUT_FILE) .help("The file to parse") .index(1) .required(true), ) .arg( - Arg::with_name(CLI_SUB_CMD_RUN_MEMORY) - .help(&format!( - "Sets the number of memory cells, defaults to {:?}", - DEFAULT_MEMORY_SIZE + Arg::new(CLI_SUB_CMD_RUN_MEMORY) + .help(format!( + "Sets the number of memory cells, defaults to {DEFAULT_MEMORY_SIZE:?}" )) .long(CLI_SUB_CMD_RUN_MEMORY) - .takes_value(true), + .value_parser(value_parser!(u32)), ) .arg( - Arg::with_name(CLI_SUB_CMD_RUN_TIME) - .help(&"Prints time of various metrics".to_string()) + Arg::new(CLI_SUB_CMD_RUN_TIME) + .help("Prints time of various metrics") .long(CLI_SUB_CMD_RUN_TIME) - .possible_values(&CLI_SUB_CMD_RUN_TIME_OPTIONS) - // .number_of_values(1) - .multiple(true) - .takes_value(true), + .value_parser(CLI_SUB_CMD_RUN_TIME_OPTIONS) + .action(ArgAction::Append), ), ) .get_matches(); match matches.subcommand() { - (CLI_SUB_CMD_RUN, Some(run)) => handle_run(run), + Some((CLI_SUB_CMD_RUN, matches)) => handle_run(matches), _ => Err(CliError::ValidationError(String::from( "Invalid subcommand", ))), } } -fn handle_run(matches: &clap::ArgMatches) -> Result<(), CliError> { - match matches.value_of(CLI_ARG_INPUT_FILE) { +fn handle_run(matches: &ArgMatches) -> Result<(), CliError> { + match matches.get_one::(CLI_ARG_INPUT_FILE) { Some(input) => { - let mut memory = DEFAULT_MEMORY_SIZE; - if let Some(exists) = matches.value_of(CLI_SUB_CMD_RUN_MEMORY) { - if let Ok(value) = exists.parse::() { - memory = value; - } else { - println!( - "Argument of {{{}}} could not be parsed", - CLI_SUB_CMD_RUN_MEMORY - ); - process::exit(1); - } - } + let memory = *matches + .get_one(CLI_SUB_CMD_RUN_MEMORY) + .unwrap_or(&DEFAULT_MEMORY_SIZE); + + let time_metrics: Vec<&str> = matches + .get_many::(CLI_SUB_CMD_RUN_TIME) + .unwrap_or_default() + .map(String::as_str) + .collect(); let total_start = Instant::now(); - let parse_start = Instant::now(); let contents = fs::read_to_string(input)?; let tokens = lexer::lex(&contents); - let parsed = parser::parse(tokens)?; + let parsed = parser::parse(&tokens)?; let optimized = optimizer::optimize(parsed); - let parse_elapsed = parse_start.elapsed(); + let parse_elapsed = total_start.elapsed(); let mut interpreter = Interpreter::new(memory); let exec_start = Instant::now(); - interpreter.run(optimized, &mut io::stdin(), &mut io::stdout())?; + interpreter.run(&optimized, &mut io::stdin(), &mut io::stdout())?; let exec_elapsed = exec_start.elapsed(); let total_elapsed = total_start.elapsed(); - if let Some(values) = matches.values_of(CLI_SUB_CMD_RUN_TIME) { - let values: Vec<&str> = values.collect(); + if !time_metrics.is_empty() { println!(); - if values.contains(&"total") { + if time_metrics.contains(&"total") { println!("Total time: {}s", total_elapsed.as_secs()); println!(" {}ms", total_elapsed.as_millis()); println!(" {}µs", total_elapsed.as_micros()); } - if values.contains(&"exec") { + if time_metrics.contains(&"exec") { println!("Execution time: {}s", exec_elapsed.as_secs()); println!(" {}ms", exec_elapsed.as_millis()); println!(" {}µs", exec_elapsed.as_micros()); } - if values.contains(&"parse") { + if time_metrics.contains(&"parse") { println!("Parsing time: {}s", parse_elapsed.as_secs()); println!(" {}ms", parse_elapsed.as_millis()); println!(" {}µs", parse_elapsed.as_micros()); diff --git a/brainrust-engine/src/optimizer.rs b/src/optimizer.rs similarity index 97% rename from brainrust-engine/src/optimizer.rs rename to src/optimizer.rs index 25b08b1..b279571 100644 --- a/brainrust-engine/src/optimizer.rs +++ b/src/optimizer.rs @@ -1,16 +1,18 @@ -use std::ops::RangeInclusive; - +use crate::{ + interpreter::Instruction::{ + self, Add, Clear, JumpIfNotZero, JumpIfZero, MoveLeft, MoveRight, Sub, + }, + parser, +}; use itertools::Itertools; +use std::ops::RangeInclusive; -use crate::interpreter::Instruction; -use crate::interpreter::Instruction::*; -use crate::parser; - +#[must_use] pub fn optimize(instructions: Vec) -> Vec { let iter = instructions.into_iter(); let iter = combine_instructions(iter); let iter = optimize_clear_loop(iter); - let mut optimized = iter.collect(); + let mut optimized: Vec<_> = iter.collect(); parser::link_loops(&mut optimized).unwrap() } diff --git a/brainrust-engine/src/parser.rs b/src/parser.rs similarity index 89% rename from brainrust-engine/src/parser.rs rename to src/parser.rs index 49e1e9d..173d5d9 100644 --- a/brainrust-engine/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,6 @@ -use crate::interpreter::Instruction; -use crate::lexer::Command; +use crate::{interpreter::Instruction, lexer::Command}; -pub fn parse(commands: Vec) -> Result, Error> { +pub fn parse(commands: &[Command]) -> Result, Error> { let mut instructions: Vec = commands .iter() .map(|cmd| match cmd { @@ -19,7 +18,7 @@ pub fn parse(commands: Vec) -> Result, Error> { link_loops(&mut instructions) } -pub fn link_loops(program: &mut Vec) -> Result, Error> { +pub fn link_loops(program: &mut [Instruction]) -> Result, Error> { let mut jump_stack = Vec::new(); for i in 0..program.len() { @@ -78,7 +77,7 @@ mod tests { Instruction::Print, Instruction::Read, ]; - assert_eq!(parse(input).unwrap(), expected); + assert_eq!(parse(&input).unwrap(), expected); } #[test] @@ -103,7 +102,7 @@ mod tests { Instruction::Sub(1), Instruction::Read, ]; - assert_eq!(parse(input).unwrap(), expected); + assert_eq!(parse(&input).unwrap(), expected); } #[test] @@ -130,18 +129,18 @@ mod tests { Instruction::JumpIfNotZero(1), Instruction::JumpIfNotZero(0), ]; - assert_eq!(parse(input).unwrap(), expected); + assert_eq!(parse(&input).unwrap(), expected); } #[test] fn test_missing_closing_bracket() { let input = vec![Command::JumpIfZero, Command::Add]; - assert!(parse(input).is_err()); + assert!(parse(&input).is_err()); } #[test] fn test_missing_opening_bracket() { let input = vec![Command::Add, Command::JumpIfNotZero]; - assert!(parse(input).is_err()); + assert!(parse(&input).is_err()); } } diff --git a/tests/integration_test.rs b/tests/integration_test.rs new file mode 100644 index 0000000..e36c4a6 --- /dev/null +++ b/tests/integration_test.rs @@ -0,0 +1,83 @@ +use brainrust::{ + interpreter::{self, Interpreter}, + lexer, optimizer, parser, +}; +use std::{fs, io, str}; + +const PROGRAM_PREFIX: &str = "tests/programs/"; +const PROGRAM_EXTENSION: &str = ".b"; +const INPUT_EXTENSION: &str = ".input"; +const OUTPUT_EXTENSION: &str = ".output"; + +const MEMORY_SIZE: usize = 32768; + +macro_rules! test_programs { + ($($test_name:ident: $program_name:expr,)*) => { + $( + #[test] + fn $test_name() -> Result<(), TestError> { + let program_name = $program_name; + let program = format!("{PROGRAM_PREFIX}{program_name}{PROGRAM_EXTENSION}"); + let input = format!("{PROGRAM_PREFIX}{program_name}{INPUT_EXTENSION}"); + let output = format!("{PROGRAM_PREFIX}{program_name}{OUTPUT_EXTENSION}"); + + let program = fs::read_to_string(program)?; + let input = fs::read_to_string(input)?; + let output = fs::read(output)?; + + let result = run_program(&program, &input)?; + + assert_eq!(result, output); + Ok(()) + } + )* + } +} + +test_programs! { + monty: "monty", +} + +fn run_program(file: &str, input: &str) -> Result, TestError> { + let tokens = lexer::lex(file); + let parsed = parser::parse(&tokens)?; + let optimized = optimizer::optimize(parsed); + let mut interpreter = Interpreter::new(MEMORY_SIZE); + + let mut output: Vec = vec![]; + + interpreter.run(&optimized, &mut input.as_bytes(), &mut output)?; + Ok(output) +} + +#[derive(Debug)] +enum TestError { + Io, + Parsing, + Interpreter, + ConversationError, +} + +impl From for TestError { + fn from(_error: io::Error) -> Self { + TestError::Io + } +} + +impl From for TestError { + fn from(_error: parser::Error) -> Self { + TestError::Parsing + } +} + +impl From for TestError { + fn from(_error: interpreter::Error) -> Self { + TestError::Interpreter + } +} + +impl From for TestError { + fn from(_error: str::Utf8Error) -> Self { + TestError::ConversationError + } +} diff --git a/brainrust-engine/tests/programs/monty.b b/tests/programs/monty.b similarity index 100% rename from brainrust-engine/tests/programs/monty.b rename to tests/programs/monty.b diff --git a/brainrust-engine/tests/programs/monty.input b/tests/programs/monty.input similarity index 100% rename from brainrust-engine/tests/programs/monty.input rename to tests/programs/monty.input diff --git a/brainrust-engine/tests/programs/monty.output b/tests/programs/monty.output similarity index 100% rename from brainrust-engine/tests/programs/monty.output rename to tests/programs/monty.output