BFC compiles human-readable code to brainfuck, an esoteric programming language. It works much like a compiler, but instead of producing bytecode, it outputs brainfuck code. You may call it a transpiler.
BFC is written in JavaScript for the Bun runtime. Porting it to Node or Deno shouldn't be hard, but why would you do that?
The compiler features something resembling a tokenizer, an AST, and a stack, though in a very primitive form. You don't need much for brainfuck.
Download the appropriate executable for your system from Releases, then run it on the command line. Alternatively, clone the project and use bun run main.js.
BFC uses an in-house programming language as input, which is also called BFC due to a lack of creativity. The language syntax can be likened to the C family of languages.
Here are the tokens that make up the BFC language:
DECLARE(let <name>) - creates a global variable slot under the given name. Currently, all variables in BFC are globals, there is no scope distinction.ASSIGN(= <right>) - assigns the value atrightto the address currently pointed to.EQ(== <right>) - pushes a truthy value to the stack if the value atrightis equal to the value at the current address or the most recently encountered number literal.NE(!= <right>) - pushes 0 to the stack if the value atrightis equal to the value at the current address or the most recently encountered number literal.ADD(+ <right>) - pushes the result of the value atrightplus the value at the current address or the most recently encountered number literal to the stack.SUB(- <right>) - pushes the result of the value atrightminus the value at the current address or the most recently encountered number literal to the stack.IF(if <head> <block>) - evaluates the expression at theheadblock and, if truthy, runs the code inblock.WHILE(while <head> <block>) - begins a loop which evaluates the expression at theheadblock and, if truthy, runs the code atblockand continues the loop.READ(>> <right>) - saves the next character at stdin toright.WRITE(<< <right>) - outputs the value ofrightor most recently encountered number literal to stdout as a character.CHAR('<char>) - expects a single ASCII character in place ofchar, pushes its ASCII index to the stack of number literals.
Special tokens:
- Upon encountering a known variable name, the compiler will run to that variable's address.
- Upon encountering a number literal, the compiler will push it to its stack of number literals. They do not consume memory on their own.
- Blocks are defined with either
()or{}brackets, with both types being semantically identical. Most expressions accept literals, variables, and blocks equally. - Upon encountering a free-standing block, the compiler will evaluate the code in that block.
Tokens can be separated by various separators, all of which function identically to each other:
- Semicolons (
;) - Spaces and tabs
- Line feed, carriage return
let i = 5
while (i != 0) {
i = (i - 1)
<<('H)<<('i)<<10
}This program will produce brainfuck code that prints 5 lines of "Hi" to stdout:
>>>>+++++<+[-<[-]>>[-<<+<<+>>>>]<<<<[->>>>+<<<<]>[-][->-<]>[[-]>>[-<<<+<+>>>>]<<<<[->>>>+<<<<]>->>>[-]<<<[->>>+<<<<+>]<[->+<]>[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++.[-]>>+<[-]]>]<