A BrainF*** (BF) interpreter and REPL, written in TypeScript.
Install from npm with
$ npm i brainscript
Or try it online at npm.runkit.com
var lib = require("brainscript")
lib.bf("++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.")
BrainScript provides a BF interpreter bf
which can be used in interactive or batch mode, as well as a BF REPL brain
.
bf
can be used to batch process BF code, returning any resulting output as a string
const output: string = bf("++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.")
console.log(output) // Hello World!\n
...but it also provides basic interactive capabilities for programs which require user input
// input.ts
console.log(bf(",."))
$ npx ts-node input.ts
Please provide a single character for ',' input:
β: !
!
brain
is an interactive REPL which accepts single- or multi-line programs as input:
// brain.ts
// ... imports, etc. ...
brain()
$ npx ts-node brain.ts
Enter single-line BF code below or
type :paste to paste multiline code
type :classic to toggle classic / default mode
type :numin to toggle numeric input mode
type :numout to toggle numeric output mode
type :quit or enter <CTRL>-C to quit
π§ : ++++++>+++++++[-<[->>+>+<<<]>>[-<<+>>]<]>>.
*
π§ : :paste
Entering multiline input mode.
Enter two blank lines in a row to interpret.
~~~~~~~~~~~~~~~ BEGIN INPUT ~~~~~~~~~~~~~~~
++++ +++
+[>++++ ++[>+<-][
<]< -]> >++ +++
+.- --- --- ---
--.+++++++ +++
+++ .++
+++ +.-
--- -----.--.
~~~~~~~~~~~~~ INTERPRETING... ~~~~~~~~~~~~~
6*7=42
π§ :
When input is required by the BF program, bf
will prompt the user for single-character input (using the UTF32Char
encoding):
// example.ts
// ... imports, etc. ...
const io: string = bf(",.")
console.log(io)
$ npx ts-node example.ts
Please provide a single character for ',' input:
β: π
π
bf
defaults to interactively querying stdin
, but this can be customised by the user:
// customInput.ts
// ... imports, etc. ...
function input(): UTF32Char { return UTF32Char.fromString("hi") }
const memory: UInt32 = UInt32.fromNumber(1)
const custom: string = bf(",.", false, false, false, memory, input)
console.log(custom)
$ npx ts-node customInput.ts
Please provide a single character for ',' input:
hi
bf
provides a few boolean flags which affect how it interprets BF programs. These include:
numin
- whentrue
, treats all input as numeric, rather than as charactersnumout
- whentrue
, outputs raw numeric data, rather than trying to convert them to charactersclassic
- sets maximum cell values to255
; allows "wraparound" in both the cell values and the memory tape
Using bf
, these flags can be set in the function call (they are all false
by default), but when running the interactive REPL brain
, they must be toggled using the special REPL commands :numin
, :numout
, and :classic
, respectively.
numin
and numout
, in particular, can make BF programs much easier to write and use.
By default (when numin
is false
), BF interprets all input as characters. So 63
is not the number 63
, but the character 6
(ASCII #54) followed by the character 3
(ASCII #51), which is interpreted as a 4-byte Unicode character with the higher bytes as 54
and lower bytes as 51
. (An undefined character.)
And the number 42
, when written to the terminal as output, is interpreted as ASCII #42, or the *
character.
These subtleties mean that programs which are written to perform basic arithmetic must be much more complex.
To ease the pain a bit, the numin
and numout
flags allow input and output, respectively, to be interpreted as numeric data, rather than characters. This greatly simplifies calculation. Here is the Wikipedia-prescribed method for addition which is fragile and relies on hard-coded values
π§ : ++>+++++[<+>-]++++++++[<++++++>-]<.
7
π§ : ,>,[<+>-]++++++++[<++++++>-]<.
Please provide a single character for ',' input:
β: 2
Please provide a single character for ',' input:
β: 5
Above, the input 2
is interpreted as the character 2
(ASCII #50) and 5
is interpreted as the character 5
(ASCII #53). Adding them should yield 103
or the ASCII character g
, but something has gone wrong. Try debugging that.
Instead, we can simply set numin
and numout
to true, and then provide any two 1 or 2-digit numbers to add to the following (much simpler) program:
π§ : ,>,[-<+>]<.
Please provide a 1 or 2-digit number for ',' input:
β: 2
Please provide a 1 or 2-digit number for ',' input:
β: 5
7
π§ : ,>,[-<+>]<.
Please provide a 1 or 2-digit number for ',' input:
β: 19
Please provide a 1 or 2-digit number for ',' input:
β: 42
61
We can even write a pretty simple multiplication program:
π§ : ,>,[-<[->>+>+<<<]>>[-<<+>>]<]>>.
Please provide a 1 or 2-digit number for ',' input:
β: 19
Please provide a 1 or 2-digit number for ',' input:
β: 34
646
classic
is the last flag which can be toggled. There are two interpretation modes which bf
and brain
can run in. The default interpretation mode:
- allows cells with values on the range
[0, 2**32)
- throws an error when the user tries to decrement a cell's value below
0
- throws an error when the user tries to increment a cell's value above
2**32 - 1
- throws an error when the user tries to move the memory pointer left when it's already on the cell at index
0
- throws an error when the user tries to move the memory pointer right when it's already on the rightmost cell
The classic
interpolation mode:
- allows cells with values on the range
[0, 256)
- wraps around to
255
when the user decrements a cell with value0
- wraps around to
0
when the user increments a cell with value255
- wraps around to the rightmost cell when the memory pointer is on cell index
0
and the user tries to move it left - wraps around to the cell at index
0
when the memory pointer is already on the rightmost cell and the user tries to move it right
Emoji and other characters beyond 0xFF
will only properly render in the default interpretation mode (classic === false
), but some programs will only run in classic
mode, for instance, this code golfed "Hello World" from StackExchange:
π§ : --<-<<+[+[<+>--->->->-<<<]>]<<--.<++++++.<<-..<<.<+.>>.>>.<<<.+++.>>.>>-.<<<+.
--<-<<+[+[<+>-
Error at ^ char index 0
message: cell 0 is already at minimum allowable value, 0... try setting `classic` to `true`
output:
π§ : :classic
Changed interpretation mode to 'classic'
π§ : --<-<<+[+[<+>--->->->-<<<]>]<<--.<++++++.<<-..<<.<+.>>.>>.<<<.+++.>>.>>-.<<<+.
Hello, World!
π§ :
ANSI escape sequences (arrow keys, etc.) do not work properly in brain
. This is being investigated.
Feel free to open an issue with any bug fixes
or a PR with any performance improvements
.
Support me @ Ko-fi!
Check out my DEV.to blog!