https://github.com/inspiaaa/bfinterpreter
Optimising interpreter for the esoteric Brainf#@% programming language written in Nim.
https://github.com/inspiaaa/bfinterpreter
Last synced: about 1 month ago
JSON representation
Optimising interpreter for the esoteric Brainf#@% programming language written in Nim.
- Host: GitHub
- URL: https://github.com/inspiaaa/bfinterpreter
- Owner: Inspiaaa
- License: mit
- Created: 2021-11-05T14:55:26.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2022-01-23T20:12:48.000Z (over 4 years ago)
- Last Synced: 2025-03-01T21:52:24.108Z (over 1 year ago)
- Language: Nim
- Homepage:
- Size: 134 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Nim BFI
Optimising interpreter for the esoteric [Brainf#@%](https://en.wikipedia.org/wiki/Brainfuck) programming language written in Nim.
### CLI Usage:
```shell
Usage:
bfi {SUBCMD} [sub-command options & parameters]
where {SUBCMD} is one of:
help print comprehensive or per-cmd help
r Runs the interpreter.
inspect Prints the first n instructions of the optimized code.
Usage:
bfi r [optional-params]
Runs the interpreter.
Options:
-h, --help print this cligen-erated help
--help-syntax advanced: prepend,plurals,..
-f=, --file= string "" Path to the file with the BF code
-c=, --code= string "" Directly enter BF code as a string
-n, --noOpt bool false Flag to disable optimizations
-i=, --input= string "" Path to the input data; Uses stdin if none specified
-o=, --output= string "" Path to the output file; Uses stdout if none specified
-s, --silent bool false Flag that, when provided, prevents the interpreter from outputting any text
-t=, --tapeSize= int 30000 Number of cells in the tape. Dynamically grows the tape for tapeSize=-1
Usage:
bfi inspect [optional-params]
Prints the first n instructions of the optimized code.
Options:
-h, --help print this cligen-erated help
--help-syntax advanced: prepend,plurals,..
-f=, --file= string "" set file
-c=, --code= string "" set code
-o=, --output= string "" set output
-n=, --number= int -1 set number
```
### CLI Examples:
```shell
# Execute code from a file
bfi r -f examples/mandelbrot.bf
# Write the output to a file
bfi r -f examples/mandelbrot.bf -o mandelbrot.txt
# Disable optimization
bfi r -f examples/mandelbrot.bf -n
bfi r -f examples/mandelbrot.bf --noOpt
# Execute a string directly, prints "Hello World!"
bfi r -c "->+>>>+>>-[++++++[>+++++++++>+++++>+<<<-]<+]>>.>--.->++..>>+.>-[>.<<]>[>]<<+."
# Silent: Prevents the program from outputting any text, useful for benchmarking
bfi r -f examples/hanoi.bf -s
# Set the size of the tape
bfi r -f examples/hanoi.bf -t 1000
bfi r -f examples/hanoi.bf -tapeSize 1000
# Let the tape dynamically grow as it needs more cells
bfi r -f examples/hanoi.bf -t -1
# Prints the instructions of the optimized code in the console.
bfi inspect -c ">>>+++[->+<]"
# Prints the first 100 instructions of mandelbrot.bf
bfi inspect -f examples/mandelbrot.bf -n 100
```
### Compilation
```batch
nimble install cligen
nim c --out:bfi.exe -d:danger --gc:markAndSweep src/cli.nim
```
The `-d:danger` flag makes the program run faster at the cost of runtime checks. This flag can of course be omitted when compiling.
It uses `cligen` for making the command line interface.
### Using the Nim API
See the `example.nim` file for a full example.
```nim
import std/streams
import ./src/optimizing_interpreter
let code = """
"->+>>>+>>-[++++++[>+++++++++>+++++>+<<<-]<+]>>.>--.->++..>>+.>-[>.<<]>[>]<<+."
"""
# Option A:
run(code, newStringStream(""), newFileStream(stdout))
# Option B:
import ./src/ir
var instructions: seq[Instr] = parse(code)
instructions = optimize(instructions)
addJumpInformation(instructions)
# Inspect the optimized instructions
for i in instructions[0..` | Move the pointer to the right by 1 block. |
| `<` | Move the pointer to the left by 1 block. |
| `+` | Increment the current block. |
| `-` | Decrement the current block. |
| `[` | Jump to the corresponding `]` if the current block is `0`. |
| `]` | Jump back to the corresponding `[` if the current block is not `0`. |
| `.` | Write the current block as an ASCII character. |
| `,` | Read one byte from the input stream into the current block. |
### Expanded instruction set ([ir.nim](https://github.com/Inspiaaa/BfInterpreter/blob/master/src/ir.nim))
| Instruction | BF Example | Meaning |
| ------------- | -------------------- | ------------------------------------------------------------------------ |
| opAdd | `+`, `+++` | Performs multiple `+` instructions at once. |
| opSub | `-`, `---` | ... |
| opMove | `>`, `<`, `>>>` | Performs multiple `>` or `<` instructions at once. |
| opLoopStart | `[` | |
| opLoopEnd | `]` | |
| opWrite | `.` | |
| opRead | `,` | |
| opClear | `[-]`,`[+]` | Clears the current cell. |
| opSet | `[-]+++` | Sets the current cell to a value. |
| opScan | `[>]`,`[<]`, `[>>>]` | Moves to the next empty cell by jumping certain increments. |
| opCopyAdd | `[->+<]` | Adds the current cell to another cell. |
| opCopySub | `[->-<]` | Subtracts ... |
| opMulAdd | `[->+++<]` | Adds the current cell times a multiplication factor to another cell. |
| opMulSub | `[->---<]` | Subtracts ... |
| opAddAtOffset | `>>>+`, `<+++` | Adds the current cell to another cell without changing the cell pointer. |
| opSubAtOffset | `>>>-`, `<---` | Subtracts ... |
### Optimisation phases
1. **Parsing**
Converts the string instructions into `Instr` objects. Already fuses multiple `+`,`-`, `>` and `<` instructions into one instruction (opAdd, opSub, and opMove, respectively). This makes the subsequent optimisation phase faster and simpler.
2. **Advanced optimisations**
1. Clear loops
2. Scan loops
3. Copy loops
4. Multiplication loops
5. "Lazy movements" (Operation at an offset)
3. **Jump table creation**
In order to not have to search for the corresponding bracket of a `[` and `]` instruction, precompute the target jump locations before executing the program. E.g. First `[` instruction jumps to index 10...
4. **Execution**
### Example
Start of mandelbrot.bf (with comments):
```
+++++++++++++ # A
[->++>>>+++++>++>+<<<<<<] # B
>>>>>++++++>--->>>>>>>>>>+++++++++++++++ # C
[[>>>>>>>>>] # D
# ...
```
First 14 instructions of the optimised code (`bfi inspect -f examples/mandelbrot.bf -n 14`) (with comments):
```
# A
(opAdd, value: 13)
# B
(opLoopStart, pos: 7)
(opMulAdd, offset: 1, factor: 2)
(opMulAdd, offset: 4, factor: 5)
(opMulAdd, offset: 5, factor: 2)
(opCopyAdd, offset: 6)
(opClear)
(opLoopEnd, pos: 1)
# C
(opAddAtOffset, offset: 5, value: 6)
(opSubAtOffset, offset: 6, value: 3)
(opAddAtOffset, offset: 16, value: 15)
(opMove, offset: 16)
# D
(opLoopStart, pos: 18)
(opScan, offset: 9)
```
---
## Unscientific Benchmarks
`bench/bench.nim` (`nim c -r -d:danger --gc:markAndSweep bench/bench.nim`)
Runs the interpreter in silent mode, i.e. doesn't print anything to the console.
| Input program | Unoptimised Time / s | Optimised Time / s |
| ----------------- | -------------------- | ------------------ |
| **mandelbrot.bf** | 13.081 | **2.722** |
| **hanoi.bf** | 8.123 | **0.138** |
---
## Further reading
https://www.nayuki.io/page/optimizing-brainfuck-compiler
http://calmerthanyouare.org/2015/01/07/optimizing-brainfuck.html
[Basics of BrainFuck · GitHub](https://gist.github.com/roachhd/dce54bec8ba55fb17d3a)