Skip to content

🏴 An optimizing Brainfuck to Javascript compiler

Notifications You must be signed in to change notification settings

graypegg/unfuck

Repository files navigation

Unf*ck. Build Status codecov

This module exposes a simple API to convert Brainf*ck to a Javascript function.

Installation

$ npm install --save unfuck
var uf = require('unfuck');

API

uf.compiler({ Settings Object })

Returns a compiler object preloaded with the settings provided.

{
	type: <Uint8Array | Uint16Array | Int8Array ... etc>,
	width: <Any Integer>,
	in: <Number | String>,
	out: <Number | String>,
	target: <'simple-es6' | 'interactive-es6' | 'promise-es6'>,
}

type

Takes: An array constructor
Default: Uint8Array
Dictates the type of the array representing the 'tape'. I suggest using typed arrays because most versions of Brainfuck require some kind of bounded integer, but you can use the Array constructor as a value here for full 64bit integers. (Uint8Array is the most common, this bounds the values in the tape between 0 and 254)

width

Takes: Number
Default: 10240
The length of the 'tape'. Values at cells beyond this number, or less than 0, result in a error. You'll want this to be high, but not too high, as all cells are initiated with the value 0, so 10240 8bit cells uses 10kB of memory at start-up. (The size of each cell is determined by the type setting)

in

Takes: A type constructor (String or Number)
Default: String
The type of data the resulting javascript function will take as input. (The target option determines the source of input.)

  • If it's set to String, the function will insert each charCode sequentially when , is used.
  • If it's set to Number, the function will insert the number supplied as input sequentially when , is used.

out

Takes: A type constructor (String or Number)
Default: String
The type of data the resulting javascript function will return or include in it's output callback. (The target option determines the mode of output.)

  • If it's set to String, the function will convert the number on the tape to a character via fromCharCode
  • If it's set to Number, the function will return the numerical value of the cell on the tape.

target

Takes: String
Default: 'simple-es6'
Unfuck comes with a couple different compilation targets which affect how the outputted javascript can be used.

Name Description Example Usage
'simple-es6' Input is taken as an Array or String (depending input type, Number and String respectively) in the first and only parameter of the outputted javascript function. Each instance of , in Brainfuck will take the first element off of this array and insert it onto the tape. Every instance of . will add the current cell's value to either an Array or a String (depending output type, Number and String respectively) that is returned after halting. c.use(',-.')([4,3])
c.use(',-.')('abc')
'interactive-es6' Input is received synchronously from the first parameter to the outputted javascript function, it should be a function which can take the current cell value as it's first parameter. The output is also processed synchronously by the second parameter which should be a function that takes the current cell as it's first parameter. c.use(',-.')((x)=>{
  getLine('Number?')
}, (x)=>{
  console.log(x)
})
'promise-es6' Input is taken as an Array or String (depending input type, Number and String respectively) in the first and only parameter of the outputted javascript function. Each instance of , in Brainfuck will take the first element off of this array and insert it onto the tape. Every instance of . will add the current cell's value to either an Array or a String (depending output type, Number and String respectively) that is returned in a Promise that will resolve after the brainfuck program halts. (The promise is returned immediately.) Any errors are caught inside the promise and get rejected. f(out){ console.log(out) }
c.use(',-.')([4,3]).then(f)
c.use(',-.')('abc').then(f)

compiler.compile( Brainfuck String )

Returns an object containing the sanitized brainfuck code, a copy of the Abstract Syntax Tree, and the outputted compiled code in a string. See example below for example output of this function.


compiler.use( Brainfuck String )

Returns a real Javascript function which takes input as it's only parameter and returns output, both in the type specified by the compiler object.


compiler.run( Brainfuck String, Input )

Executes the brainfuck function with input. Input should be pre-formatted to the compilers specs. (I.E. Number => [Int], String => "String")

Examples

Basic Use

If you just want to test out the compiler, you can run a simple hello world program using this:

// Import Unfuck module.
var uf = require('unfuck');

// Create a compiler using the default configuration.
var compiler = uf.compiler();

// Output 'Hello World!' to the console. 
console.log(
	compiler.run( // Runs the compiled JS function right after compiling.
		'++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.', // The brainfuck program itself.
		'' // This is for program input, which the hello world program doesn't utilize
	)
);

Compilation output with Abstract Syntax Tree

Using the .compile() method lets you access a copy of the generated AST, for whatever reason you need it for.

var uf = require('unfuck');

var compiler = uf.compiler({
	type: Uint16Array,
	in: Number,
	out: String,
	width: 9999
});

console.log( compiler.compile('++++++[>++++++++++<-]>+++++.') );

Which outputs the following:

{
    "bf": "++++++[>++++++++++<-]>+++++.",
    "ast": [
        {
            "is": "SFT",
            "body": 6
        },
        {
            "is": "MUL",
            "body": {
                "factors": [
                    {
                        "move": 1,
                        "factor": 10
                    }
                ]
            }
        },
        {
            "is": "RELSFT",
            "body": {
                "value": 5,
                "move": 1
            }
        },
        {
            "is": "MOV",
            "body": 1
        },
        {
            "is": "OUT"
        }
    ],
    "out": "(function(i){var i=i.split('').map(x=>x.charCodeAt())||[];var o=[];var t=new Uint8Array(30000);var p=0;t[p]+=6;t[p+1]+=t[p]*10;t[p]=0;t[p+1]+=5;p+=1;o.push(t[p]);return o.map(x=>String.fromCharCode(x)).join('');})"
}