diff --git a/.github/workflows/feature_pr.yml b/.github/workflows/feature_pr.yml index 054f201..67cdffc 100644 --- a/.github/workflows/feature_pr.yml +++ b/.github/workflows/feature_pr.yml @@ -32,7 +32,7 @@ jobs: test: name: Test - needs: [style] + if: always() runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed8b855..d705803 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,31 +10,29 @@ env: CARGO_TERM_COLOR: always jobs: - # style: - # name: Check Style - # runs-on: ubuntu-latest - # steps: - # - name: Checkout - # uses: actions/checkout@v1 - # - # - name: Install rust - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable - # components: rustfmt - # profile: minimal - # override: true - # - # - name: cargo fmt -- --check - # uses: actions-rs/cargo@v1 - # with: - # command: fmt - # args: --all -- --check + style: + name: Check Style + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + profile: minimal + override: true + - name: cargo fmt -- --check + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check test: name: Test - # needs: [style] + if: always() runs-on: ubuntu-latest strategy: @@ -82,21 +80,30 @@ jobs: if: startsWith(github.ref, 'refs/tags/') strategy: matrix: + os: [ ubuntu-latest ] # Default os for build target: [ aarch64-unknown-linux-gnu, - armv7-unknown-linux-gnueabihf, - i686-unknown-linux-gnu, i686-unknown-linux-musl, - mips-unknown-linux-gnu, mips64-unknown-linux-gnuabi64, mips64el-unknown-linux-gnuabi64, mipsel-unknown-linux-gnu, - powerpc-unknown-linux-gnu, powerpc64-unknown-linux-gnu, powerpc64le-unknown-linux-gnu, - arm-unknown-linux-gnueabi, - x86_64-unknown-linux-gnu, x86_64-unknown-linux-musl, x86_64-pc-windows-gnu ] - os: [ ubuntu-latest ] - - include: + i686-pc-windows-gnu, x86_64-pc-windows-gnu, + i686-unknown-linux-gnu, x86_64-unknown-linux-gnu, + x86_64-apple-darwin ] + exclude: # Do not build for macOS on Linux - target: x86_64-apple-darwin + os: ubuntu-latest + include: # List of all targets to build for and the name for common mortals + - target: aarch64-unknown-linux-gnu + name: arm64 + - target: i686-pc-windows-gnu + name: win-i686 + - target: x86_64-pc-windows-gnu + name: win-x86_64 + - target: i686-unknown-linux-gnu + name: linux-i686 + - target: x86_64-unknown-linux-gnu + name: linux-x86_64 + - target: x86_64-apple-darwin + name: macos os: macos-latest - # Runs on latest ubuntu by default except for windows targets runs-on: ${{ matrix.os }} @@ -119,17 +126,16 @@ jobs: args: --release --target ${{ matrix.target }} - name: Rename binary (Linux & macOS) - if: matrix.target != 'x86_64-pc-windows-gnu' - run: mv target/${{ matrix.target }}/release/fbf target/${{ matrix.target }}/release/fbf-${{ matrix.target }} + if: matrix.target != 'x86_64-pc-windows-gnu' && matrix.target != 'i686-pc-windows-gnu' + run: mv target/${{ matrix.target }}/release/fbf target/${{ matrix.target }}/release/fbf-${{ matrix.name }} - name: Rename binary (Windows) - if: matrix.target == 'x86_64-pc-windows-gnu' - run: mv target/${{ matrix.target }}/release/fbf.exe target/${{ matrix.target }}/release/fbf-${{ matrix.target }}.exe + if: matrix.target == 'x86_64-pc-windows-gnu' || matrix.target == 'i686-pc-windows-gnu' + run: mv target/${{ matrix.target }}/release/fbf.exe target/${{ matrix.target }}/release/fbf-${{ matrix.name }}.exe - name: Upload release uses: softprops/action-gh-release@v1 with: - files: target/${{ matrix.target }}/release/fbf-${{ matrix.target }}* - discussion_category_name: Q&A + files: target/${{ matrix.target }}/release/fbf-${{ matrix.name }}* token: ${{ secrets.PAT_GITHUB }} diff --git a/Cargo.lock b/Cargo.lock index 7f9edf3..8218558 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ dependencies = [ [[package]] name = "fuckbrainfuck" -version = "0.1.0" +version = "2.1.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 6c73bbc..6a70454 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fuckbrainfuck" -version = "0.1.0" +version = "2.1.0" edition = "2021" authors = ["Sellig6792 "] description = "The improved Brainfuck" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e000f74 --- /dev/null +++ b/README.md @@ -0,0 +1,175 @@ +
+ + +[![Contributors][contributors-shield]][contributors-url] +[![Forks][forks-shield]][forks-url] +[![Stargazers][stars-shield]][stars-url] +[![Issues][issues-shield]][issues-url] +[![MIT License][license-shield]][license-url] + + + +
+
+ + Logo + + + ### FuckBrainfuck + [Explore documentation][wiki] | [Report Bug][issues] | [Request Feature][issues] +
+
+ + + +1. [About the Project](#about-the-project) +2. [Installation](#installation) + - [Prebuilt Binaries](#1-prebuilt-binaries) + - [Building from Source](#2-building-from-source) +3. [Usage](#usage) +4. [Contributing](#contributing) +5. [License](#license) +6. [Contact](#contact) +7. [Acknowledgements](#acknowledgements) + + + +## About The Project + +It all started on Graven's Discord server on June 26, 2022. +It was a joke that ended up on this shit... + +So FuckBrainfuck was born. It is an improvement of the classic BrainFuck, with some new features and new commands. + +Already implemented: + +- [Functions][wiki-function] +- ["Safe" comments][wiki-comment] +- [Scopes for functions][wiki-scope] +- [Optimization][wiki-optimisation] + +

(back to top)

+ +### Built With +![Rust][rust-shield] ![Cargo][cargo-shield] + +

(back to top)

+ + +## Installation + +### 1. Prebuilt binaries + +You can download the prebuilt binaries from the [releases page][release]. +If your system is not supported, you can [build it yourself](#2-building-from-source). + +### 2. Building from source + +You need to have [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) installed. + +```sh +git clone https://github.com/Sellig6792/FuckBrainfuck.git +cd FuckBrainfuck +cargo build --release +``` + +

(back to top)

+ + + +## Usage +``` +fbf [OPTIONS] +``` + + +### Options +`-O, --optimize` Optimize the code before executing it + +`-V, --version` Prints version information + +`-h, --help` Prints help information + +

(back to top)

+ + + +## Contributing + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. +Any contributions you make are **greatly appreciated**. + +If you have a suggestion that could make this better, please fork the repo and create a pull request. +You can also simply open an issue with the tag "enhancement". +Don't forget to give the project a star! Thanks again! + +1. Fork the Project +2. Checkout the develop branch: `git checkout develop` +3. Create your Branch (feature or fix): `git checkout -b [feature/fix]/[name]` +4. Commit your Changes: `git commit -m 'Add some [...]` +5. Push to the Branch: `git push origin [feature/fix]/[name]` +6. Open a Pull Request to the develop branch and explain your changes + +

(back to top)

+ + + +## License + +Distributed under the MIT License. See [the licence](LICENSE) for more information. + +

(back to top)

+ + + +## Contact + +Sellig6792 - [@Sellig6792](https://twitter.com/Sellig6792) - sellig6792@gmail.com + +

(back to top)

+ + + +## Acknowledgements + +* [Wikipedia - Brainfuck][wikipedia-brainfuck-url] +* [Astremy - Brainfuck Course (French)][astremy-brainfuck-pdf] + +

(back to top)

+ + + + +[contributors-shield]: https://img.shields.io/github/contributors/Sellig6792/FuckBrainfuck.svg?style=for-the-badge +[contributors-url]: https://github.com/Sellig6792/FuckBrainfuck/graphs/contributors + +[forks-shield]: https://img.shields.io/github/forks/Sellig6792/FuckBrainfuck.svg?style=for-the-badge +[forks-url]: https://github.com/Sellig6792/FuckBrainfuck/network/members + +[stars-shield]: https://img.shields.io/github/stars/Sellig6792/FuckBrainfuck.svg?style=for-the-badge +[stars-url]: https://github.com/Sellig6792/FuckBrainfuck/stargazers + +[issues-shield]: https://img.shields.io/github/issues/Sellig6792/FuckBrainfuck.svg?style=for-the-badge +[issues-url]: https://github.com/Sellig6792/FuckBrainfuck/issues + +[license-shield]: https://img.shields.io/github/license/Sellig6792/FuckBrainfuck.svg?style=for-the-badge +[license-url]: https://github.com/Sellig6792/FuckBrainfuck/blob/main/LICENSE +[rust-shield]: https://img.shields.io/badge/-rust-black.svg?style=for-the-badge&logo=rust&colorB=555 +[cargo-shield]: https://img.shields.io/badge/-cargo-black.svg?style=for-the-badge&logo=rust&colorB=555 +[brainFuck-url]: https://en.wikipedia.org/wiki/Brainfuck +[brainFuck-shield]: https://img.shields.io/badge/-BrainFuck-black.svg?style=for-the-badge&logo=brainfuck&colorB=555 + +[graven-discord-url]: https://discord.gg/graven +[astremy-brainfuck-pdf]: https://cdn.discordapp.com/attachments/815331771197030441/824402769397940234/brainfuck.pdf +[wikipedia-brainfuck-url]: https://en.wikipedia.org/wiki/Brainfuck + +[wiki]: https://github.com/Sellig6792/FuckBrainfuck/wiki + +[wiki-function]: https://github.com/Sellig6792/FuckBrainfuck/wiki#functions +[wiki-optimisation]: https://github.com/Sellig6792/FuckBrainfuck/wiki#optimisation +[wiki-scope]: https://github.com/Sellig6792/FuckBrainfuck/wiki#scope +[wiki-comment]: https://github.com/Sellig6792/FuckBrainfuck/wiki#comments + + +[issues]: https://github.com/Sellig6792/FuckBrainfuck/issues +[release]: https://github.com/Sellig6792/FuckBrainfuck/releases diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000..b08d993 Binary files /dev/null and b/assets/logo.png differ diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 4b4688f..a13a8bf 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -6,23 +6,20 @@ pub struct Parser { impl Parser { pub fn new(program: String) -> Parser { - let program = program - .replace(' ', "") - .replace('\t', "") - .replace('\r', "") - .replace('\n', ""); + let program = program.replace([' ', '\t', '\r', '\n'], ""); Parser { program } } pub fn parse(&mut self) -> Vec { let (instructions, _) = self._parse(None, None); - return instructions; + instructions } fn _parse(&self, index: Option, stop_char: Option) -> (Vec, usize) { let mut index = index.unwrap_or(0); let mut instructions = vec![]; + let mut comment = false; while index < self.program.len() { let char = match self.program.chars().nth(index) { @@ -33,6 +30,15 @@ impl Parser { return (instructions, index); } + if char == '#' { + comment = !comment; + } + + if comment { + index += 1; + continue; + } + match char { '+' => instructions.push(Instruction::new(InstructionType::Increment, None)), '-' => instructions.push(Instruction::new(InstructionType::Decrement, None)), @@ -89,6 +95,6 @@ impl Parser { index += 1; } - return (instructions, index); + (instructions, index) } } diff --git a/src/evaluation/cell.rs b/src/evaluation/cell.rs index ba5780c..6015534 100644 --- a/src/evaluation/cell.rs +++ b/src/evaluation/cell.rs @@ -28,7 +28,7 @@ impl Cell { pub fn sub(&mut self, value: T) { let sub: isize = self.value as isize - value.to_isize().unwrap(); self.value = if sub < 0 { - u8::MAX - (sub.abs() as u8 - 1) + u8::MAX - (sub.unsigned_abs() as u8 - 1) } else { sub as u8 }; diff --git a/src/evaluation/evaluator.rs b/src/evaluation/evaluator.rs index e81fca1..6a14b95 100644 --- a/src/evaluation/evaluator.rs +++ b/src/evaluation/evaluator.rs @@ -58,15 +58,22 @@ where } InstructionType::Input => { - if self.input.is_empty() { - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - // Convert the input to a vector of u8 - self.input = input.trim().bytes().collect(); + for _ in 0..instruction.get_amount() { + if self.input.is_empty() { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + // Convert the input to a vector of u8 + self.input = input.trim().bytes().collect(); + } + + if self.input.is_empty() { + self.scopes.get_current_cell_mut().set_value(0); + } else { + self.scopes + .get_current_cell_mut() + .set_value(self.input.remove(0)); + } } - self.scopes - .get_current_cell_mut() - .set_value(self.input.remove(0)); } InstructionType::Output => { for _ in 0..instruction.get_amount() { @@ -153,7 +160,9 @@ where } match show_output { - None | Some(true) => println!("{}", String::from_utf8(self.output_buffer.clone()).unwrap()), + None | Some(true) => { + println!("{}", String::from_utf8(self.output_buffer.clone()).unwrap()) + } _ => (), } } @@ -170,7 +179,7 @@ mod tests { let program = String::from("++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>."); let mut parser = ast::Parser::new(program); let instructions = parser.parse(); - let mut optimizer = optimization::Optimizer::new(instructions.clone()); + let mut optimizer = optimization::Optimizer::new(instructions); let optimized_instructions = optimizer.optimize(); let mut brainfuck = Evaluator::new(optimized_instructions); brainfuck.evaluate(None, Some(false)); @@ -186,7 +195,7 @@ mod tests { let program = String::from("{++++[>++++++++++++<-]>.}{++++[>++++++++++++<-]>+.}="); let mut parser = ast::Parser::new(program); let instructions = parser.parse(); - let mut optimizer = optimization::Optimizer::new(instructions.clone()); + let mut optimizer = optimization::Optimizer::new(instructions); let optimized_instructions = optimizer.optimize(); let mut brainfuck = Evaluator::new(optimized_instructions); brainfuck.evaluate(None, Some(false)); @@ -198,10 +207,29 @@ mod tests { let program = String::from("{+++++[>+++++++++++++<-]>.<}=="); let mut parser = ast::Parser::new(program); let instructions = parser.parse(); - let mut optimizer = optimization::Optimizer::new(instructions.clone()); + let mut optimizer = optimization::Optimizer::new(instructions); let optimized_instructions = optimizer.optimize(); let mut brainfuck = Evaluator::new(optimized_instructions); brainfuck.evaluate(None, Some(false)); assert_eq!(String::from_utf8(brainfuck.output_buffer).unwrap(), "AA"); } + + #[test] + fn test_overflow_scope_pop() { + let program = String::from("{ยด}="); + let mut parser = ast::Parser::new(program); + let instructions = parser.parse(); + let mut evaluator = Evaluator::new(instructions); + evaluator.evaluate(None, Some(false)); + } + + #[test] + fn test_comments() { + let program = String::from("++++++[>++++++++<-]>#+#."); + let mut parser = ast::Parser::new(program); + let instructions = parser.parse(); + let mut evaluator = Evaluator::new(instructions); + evaluator.evaluate(None, Some(false)); + assert_eq!(String::from_utf8(evaluator.output_buffer).unwrap(), "0"); + } } diff --git a/src/evaluation/scope.rs b/src/evaluation/scope.rs index 94f7ced..fb813be 100644 --- a/src/evaluation/scope.rs +++ b/src/evaluation/scope.rs @@ -81,7 +81,7 @@ where let sub: isize = self.index as isize - amount as isize; self.index = if sub < 0 { - 30000 - (sub.abs() as usize) + 30000 - sub.unsigned_abs() } else { sub as usize } @@ -136,7 +136,9 @@ where pub fn pop(&mut self) { self.scopes.pop(); - self.scope_index -= 1; + if self.scope_index > 0 { + self.scope_index -= 1; + } } } diff --git a/src/main.rs b/src/main.rs index ca5f6af..7cf3e6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ fn main() -> Result<(), Box> { // Optimize the program if "optimize" is true if args.optimize { - let mut optimizer = optimization::Optimizer::new(instructions.clone()); + let mut optimizer = optimization::Optimizer::new(instructions); let optimized_instructions = optimizer.optimize(); // Evaluate the optimized program diff --git a/src/optimization/optimizer.rs b/src/optimization/optimizer.rs index f4fb5ec..25260fa 100644 --- a/src/optimization/optimizer.rs +++ b/src/optimization/optimizer.rs @@ -24,7 +24,7 @@ impl Optimizer { optimizer.optimize() } - fn merge_instructions(&self, optimized_instructions: &mut Vec) -> () { + fn merge_instructions(&self, optimized_instructions: &mut Vec) { for instruction in self.instructions.iter() { match optimized_instructions.last_mut() { Some(last_optimized_instruction) => { @@ -76,14 +76,11 @@ impl Optimizer { } } - fn cancel_opposed_instructions( - &self, - optimized_instructions: &mut Vec, - ) -> () { + fn cancel_opposed_instructions(&self, optimized_instructions: &mut Vec) { let mut new_optimized_instructions: Vec = vec![]; for optimized_instruction in optimized_instructions.iter() { - match new_optimized_instructions.last().clone() { + match new_optimized_instructions.last() { Some(last_optimized_instruction) => { if last_optimized_instruction.is_opposed(optimized_instruction) { let last_amount = last_optimized_instruction.get_amount(); @@ -129,7 +126,7 @@ impl Optimizer { *optimized_instructions = new_optimized_instructions; } - fn recognize_patterns(&self, optimized_instructions: &mut Vec) -> () { + fn recognize_patterns(&self, optimized_instructions: &mut Vec) { let mut new_optimized_instructions: Vec = optimized_instructions.clone();