Based on Brainfuck language. This project has the double purpose to
- Execute BF programs
- Compile and execute BF Macro Assembler programs
The Macro Assembler aim is to make easier the design of BF program.
There is sample programs in folder samples
This project is a simple java console project.
It was designed with J2SE 1.7
It runs also with OpenJDK 1.8 on Linux CentOS 7
It was not tested on Windows platform, but should work.
this project is jarred with a MANIFEST indicating
dvx.lang.brainfuck.Main
as main class of the jar.
launch
java -jar bf.jar
It will show the usage
Usage: java -jar bf.jar [OPTION]... FILE
Execute brainfuck FILE or compile and execute brainfuck assembler FILE.
-b, --build-folder=FOLDER location where build files are generated
by default it use the folder that
contains FILE.
-c, --compile-only compile FILE and don't execute it.
-d, --keep-debug-bf do not delete debug generated bf file.
The file is generated at FOLDER designated
by option -b or by default to foder
that contain FILE
-D, --remove-debug-bf (default) do not keep debug generated BF
-h, --help show this current help
-i, --inputbf=FILE instead of STDIN, use FILE as input for
BF execution
-I, --include=FOLDER Assembler include folder for 'include' directive.
by default use the folder containing FILE.
-k, --keep-build-file Do not remove build files.
by default there are located in folder containing
FILE or under FOLDER specified by option -b
-v, --verbose verbose compilation (on stderr).
-K, --delete-build-file (default) do not keep build files.
--check-unused-variable No compile error if variable not used.
-o, --outputbf=FILE instead of STDOUT, use FILE as output for
BF execution
-s, --bf-line-size=SIZE generated BF file is limited by SIZE characters
for each line (default 80)
-z, --disable-optimize-compile do not optimize generated bf
-Z, --enable-optimize-compile (default) optimzed generated bf result
-r, --disable-optimize-run do not optimize bf run
-R, --enable-optimize-run (default) optimze bf run
-X, --debug run in debug
To launch a BF file named sample.bf
java -jar bf.jar sample.bf
To compile and run a BF Assembler file
java -jar bf.jar sample.asm
To only compile and keep building files
java -jar bf.jar -cdk sample.asm
then by keeping the building files, we have got the following files
- sample.js : javascript (macro) that do generate sample.pc.asm
- sample.pc.asm : result of macro execution (pre-compilation result)
- sample.deb.bf : result with comments, of the compilation of sample.pc.asm
- sample.bf : result without comment of the compilation of sample.pc.asm
The compilation process is :
From sample.asm it generates sample.js : Produce the precompiler javascript program
By evaluating sample.js it generates sample.pc.asm : The file to compile into BF
By compiling sample.pc.asm it generates sample.deb.bf : final BF code with comments
By sample.deb.bf it generate sample.bf : final BF code without comment
If you have compilation error you may investigate
- firstly in sample.pc.asm
- secondly in sample.js
BF debugger is not yet fully featured
You can execute step by step your BF program
java -jar bf.jar -X sample.deb.bf
It is advised to use the debugger with option -i and -o to use file for input/output
If current debugger is not efficient enough you may try some online BF debuggers
For instance this one is nicely designed.
- code
- data (memory)
- ip : instruction pointer in code
- mp : data pointer in memory
- 1 input : read a byte
- 1 output : write a byte
Code | Pseudo code | Description |
---|---|---|
+ | [mp]++ ; ip++ | increment current data at mp |
- | [mp]-- ; ip++ | decrement current data at mp |
> | mp++ ; ip++ | move data pointer right |
< | mp-- ; ip++ | move data pointer left |
. | write( [mp] ); ip++ | write output with byte at memory mp |
, | [mp] = read() ; ip++ | read byte input and store it at memory mp |
[ | ip = [mp]>0?ip+1:ip(matched)] +1 | if byte at memory mp is zero go to matching ] |
] | ip = [mp]==0?ip+1:ip(matched)[ | if byte at memory mp is not zero go to matching [ |
This project implement an assembler language that use the BF memory as a stack.
Do an addition
ADD /immediate/
[mp] += /immediate/
PUSH 1 # mp++; [mp] = 1
ADD 'A' # [mp] += 65; [mp] == 66 is true
ADD
[mp] = [mp] + [mp-1] ; mp--
PUSH 2 # mp++; [mp] = 2
PUSH 3 # mp--
ADD # [mp] = [mp] + [mp-1] ; mp-- ; [mp] == 5 is true
Do a boolean 'and'
AND
[mp] = [mp] and [mp-1] ; mp--;
PUSH 0 # mp++; [mp] = 0
PUSH 1 # mp++; [mp] = 1
AND # [mp] = [mp] and [mp-1] ; mp-- ; [mp] == 0 is true
PUSH 1 # mp++; [mp] = 1
PUSH 1 # mp++; [mp] = 1
AND # [mp] = [mp] and [mp-1] ; mp-- ; [mp] == 1 is true
PUSH 3 # mp++; [mp] = 3
PUSH 4 # mp++; [mp] = 4
AND # [mp] = [mp] and [mp-1] ; mp-- ; [mp] == 1 is true
Do one rotation left of an array global variable
AROTL /gVar/
tmp = [gVar+ 0]
[gVar + 0] = [gVar + 1]
[gVar + 1] = [gVar + 2]
...
[gVar + gVar.size -1] = tmp
gVar.pos = (gVar.pos + 1) modulus gVar.size
VAR var1*5 # variable array of 5 bytes
# assume we have 'Hello' at var1
PUSH var1 # mp++ ; [mp] = 'H'
PUSH var1.pos # mp++ ; [mp] = 0
AROTL var1 # var1 is 'elloH' now
DROP # mp--
DROP # mp--
PUSH var1 # mp++ ; [mp] = 'e'
PUSH var1.pos # mp++ ; [mp] = 1
Do one rotation right of an array global variable
AROTR /gVar/
tmp = [gVar+ gVar.size -1]
[gVar + gVar.size -1] = [gVar + gVar.size -2]
[gVar + gVar.size -2] = [gVar + gVar.size -3]
...
[gVar + 0] = tmp
gVar.pos = (gVar.pos -1) ; if <0 then gvar.pos = gvar.size -1
VAR var1*5 # variable array of 5 bytes
# assume we have 'Hello' at var1
PUSH var1.size # mp++ ; [mp] = 5
PUSH var1 # mp++ ; [mp] = 'H'
PUSH var1.pos # mp++ ; [mp] = 0
AROTR var1 # var1 is 'oHell' now
DROP # mp--
DROP # mp--
DROP # mp--
PUSH var1 # mp++ ; [mp] = 'o'
PUSH var1.pos # mp++ ; [mp] = 4
Do one or several single operation for a global variable or a stack bookmark
The valid single operations are
- RESET
- IN
- OUT
- INC
- DEC
- SET /immediate/
- ADD /immediate/
- SUB /immediate/
- BF /BFCode/ !! Use with caution
Doing
AT var1
SET 2
TA
is the same as
PUSH 2
POP var1
but AT is more efficient (less BF code and less execution steps, less stack consumption )
AT /gVarOrSbm/
/single Operation/
...
TA
tmp = mp
mp = gVarOrSbm
/single Operation/
... other single operation
mp = tmp
VAR var1
AT var1 # tmp = mp ; mp = var1
SET 10 # [mp] = 10
TA # mp = tmp
PUSH 'A' # mp++ ; [mp] = 65 (same as 'A')
SBM var1 # STACKBM(var1) = mp
PUSH 'B' # mp++ ; [mp] = 66 ( same as 'B')
SBM var2 # STACKBM(var2) = mp
AT var1 # tmp = mp ; mp = STACKBM(var1)
ADD 2 # [mp] += 2 ; [mp] == 67 is true
OUT # write([mp]) ; output 'C' (67)
TA
AT var2 # tmp = mp ; mp = STACKBM(var1)
OUT # write([mp]) ; output 'B' (65)
TA
DROP # mp-- ; release var2
DROP # mp-- ; release var1
Place inline BF code
Beware that with BF you can break the stack balance at runtime
BF /BFcode/
Any pseudo code ... as provided by /BFcode/
BF ,[.-] # [mp] = readChar ; while [mp]>0 { write([mp]); [mp]-- }
Transform head of stack value into a valid boolean value
BOOL
[mp] = [mp] == 0 ? 0 : 1
PUSH 'A' # mp++; [mp] = 65 (same as 'A')
BOOL # [mp] = [mp] == 0 ? 0 : 1 ; [mp] == 1 is true
Decrement head of stack value
DEC
[mp]--
PUSH 10 # mp++ ; [mp] = 10
DEC # [mp]-- ; [mp] == 9 is true
Does two values in stack are different
DIFF
[mp-1] = [mp] != [mp-1] ; mp--
PUSH 2 # mp++ ; [mp] = 2
PUSH 3 # mp++ ; [mp] = 3
DIFF # [mp-1] = [mp] != [mp-1] ; mp-- ; [mp] == 1 is true
PUSH 'A' # mp++ ; [mp] = 65
PUSH 65 # mp++ ; [mp] = 65
DIFF # [mp-1] = [mp] != [mp-1] ; mp-- ; [mp] == 0 is true
Divide two values in stack.
no division by zero check. Do your check before
DIV
d = [mp-1] div [mp] ; r = [mp-1] mod [mp] ; [mp] = d ; [mp-1] = r
VAR d # will receive 5 div 2
VAR r # will receive 5 mod 2
PUSH 5 # mp++ ; [mp] = 5
PUSH 2 # mp++ ; [mp] = 2
DIV # [mp] = 5 div 2; [mp-1] = 5 mod 2
POP d # [d] = [mp] ; mp-- ; [d] == 2 is true
POP r # [r] = [mp] ; mp-- ; [r] == 1 is true
Drop head of stack value
DROP
mp--
PUSH 10 # mp++; [mp] = 10
PUSH 20 # mp++; [mp] = 20
DROP # mp--; [mp] == 10 is true
Duplicate head of stack value
DUP
mp++; [mp] = [mp-1]
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
DUP # mp++ ; [mp] = [mp-1] ; [mp] == 20 is true
DROP # mp-- ; [mp] == 20 is true
Does two values in stack are equal
EQUAL
[mp-1] = [mp] == [mp-1] ; mp--
PUSH 2 # mp++ ; [mp] = 2
PUSH 3 # mp++ ; [mp] = 3
EQUAL # [mp-1] = [mp] != [mp-1] ; mp-- ; [mp] == 0 is true
PUSH 'A' # mp++ ; [mp] = 65
PUSH 65 # mp++ ; [mp] = 65
EQUAL # [mp-1] = [mp] != [mp-1] ; mp-- ; [mp] == 1 is true
Conditional instruction codes execution
IF
/code/+
FI or ENDIF
IF
/code/+
ELSE
/code/+
FI or ENDIF
if [mp] > 0 { /otherPseudoCodes/ }
if [mp] > 0 { /otherPseudoCodes/ } else { /otherPseudoCdes/ }
PUSH 0 # mp++ ; [mp] = 0
IN # [mp]=readbyte
PUSH 'A' # mp++ ; [mp] = 'A'
EQUAL # [mp] = readbyte == 'A'
IF # if [mp] != 0
PUSH 'A' # mp++ ; [mp] = 'A'
POP OUT # write([mp]) ; mp--
ELSE
PUSH '_' # mp++ ; [mp] = '_'
POP OUT # write([mp]) ; mp --
FI
Read byte from input port and set it on head of stack
IN
[mp] = readbyte
VAR var1
AT var1
IN
TA
Increment head of stack value
INC
[mp]++
PUSH 10 # mp++ ; [mp] = 10
INC # [mp]++ ; [mp] == 11 is true
Does previous stack value is less than head of stack value
INF
[mp-1] = [mp-1] < [mp] ; mp--
PUSH 3 # mp++ ; [mp] = 3
PUSH 2 # mp++ ; [mp] = 2
INF # [mp-1] = [mp] < [mp-1] ; mp-- ; [mp] == 1 is true
PUSH 'A' # mp++ ; [mp] = 65
PUSH 65 # mp++ ; [mp] = 65
INF # [mp-1] = [mp] < [mp-1] ; mp-- ; [mp] == 0 is true
Basically repeat "head of stack value" times a set of code
LOOP
/code/+
ENDLOOP
while [mp]>0 { /code/+ ; [mp]-- } mp--
PUSH 10 # mp++ ; [mp] = 10
LOOP # while [mp]>0 {
OUT # write([mp]) ; will write 10,9,8 ... 1
ENDLOOP # [mp]-- } mp--
Multiply two values in stack.
no overflow check
MUL
[mp-1] = [mp] * [mp-1] ; mp--
VAR mult # will receive 5 * 2 (10)
PUSH 5 # mp++ ; [mp] = 5
PUSH 2 # mp++ ; [mp] = 2
MUL # [mp-1] = 5 *2; mp--
POP mult # [mult] = [mp] ; mp-- ; [mult] == 10 is true
Do a boolean not operation on the head of stack value
NOT
[mp] = [mp] != 0 ? 0 : 1
PUSH 1 # mp++ ; [mp] = 1
NOT # [mp] = [mp] != 0 ? 0 : 1 ; [mp] == 0 is true
PUSH 0 # mp++ ; [mp] = 0
NOT # [mp] = [mp] != 0 ? 0 : 1 ; [mp] == 1 is true
PUSH 4 # mp++ ; [mp] = 4
NOT # [mp] = [mp] != 0 ? 0 : 1 ; [mp] == 0 is true
Do a boolean 'or'
OR
[mp] = [mp] or [mp-1] ; mp--;
PUSH 0 # mp++; [mp] = 0
PUSH 1 # mp++; [mp] = 1
OR # [mp] = [mp] or [mp-1] ; mp-- ; [mp] == 1 is true
PUSH 0 # mp++; [mp] = 0
PUSH 0 # mp++; [mp] = 0
OR # [mp] = [mp] or [mp-1] ; mp-- ; [mp] == 0 is true
PUSH 3 # mp++; [mp] = 3
PUSH 4 # mp++; [mp] = 4
OR # [mp] = [mp] or [mp-1] ; mp-- ; [mp] == 1 is true
Write out the head of stack value
OUT
write([mp])
PUSH 'A' # mp++ ; [mp] = 65 ( same as 'A')
OUT # write([mp])
Consume the stack head and place the value elsewhere in the stack.
"Elsewhere in the stack" means
- relative to the stack expressed by an immediate negative integer
- To a named stack book mark
- To a global variable name
- From stack to output
POP -/Integer/
[mp-/Integer/] = [mp] ; mp--
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
PUSH 30 # mp++ ; [mp] = 30
PUSH 40 # mp++ ; [mp] = 40
# before in stack we've got 40,30,20,10
POP -2 # [mp-2] = [mp] ; mp--
# after in stack we've got 30,40,10
POP -/SBMName/
[STACKBM(SBMName)] = [mp] ; mp--
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
SBM sbm1
PUSH 30 # mp++ ; [mp] = 30
PUSH 40 # mp++ ; [mp] = 40
# before in stack we've got 40,30,20,10
POP -sbm1 # [STACKBM(sbm1)] = [mp] ; mp--
# after in stack we've got 30,40,10
POP /Name/
If name exist global variable
[Name] = [mp] ; mp--
Else if name exist in Stack book mark
[STACKBM(Name)] = [mp] ; mp--
VAR var1
PUSH 100
POP var1
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
SBM var1
PUSH 30 # mp++ ; [mp] = 30
PUSH 40 # mp++ ; [mp] = 40
# before in stack we've got 40,30,20,10
POP var1 # [STACKBM(sbm1)] = [mp] ; mp--
# after in stack we've got 30,20,10, and var1 == 40 (before 100)
POP OUT
write([mp]) ; mp--
PUSH IN # mp++ ; [mp] = readbyte
INC # [mp]++
POP OUT # write([mp]) ; mp--;
push a value onto the stack, from an immediate or somewhere else value in the stack.
"somewhere else" means
- relative to the stack expressed by an immediate negative integer
- To a named stack book mark
- To a global variable name
- From input to stack
PUSH /Integer/ or /singleCharacter/
mp++ ; [mp] = /Immediate/ or /singleCharacter/
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
PUSH 30 # mp++ ; [mp] = 30
PUSH 40 # mp++ ; [mp] = 40
# before in stack we've got 40,30,20,10
POP -2 # [mp-2] = [mp] ; mp--
# after in stack we've got 30,40,10
PUSH -/Integer/
[mp+1] = [mp-/Integer/] ; mp++
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
PUSH -1 # [mp+1] = [mp-/Integer/] ; mp++; [mp] == 10 is true
PUSH -/SBMName/
mp++ ; [mp] = [STACKBM(SBMName)]
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
SBM sbm1
PUSH 30 # mp++ ; [mp] = 30
PUSH 40 # mp++ ; [mp] = 40
# before in stack we've got 40,30,20,10
PUSH -sbm1 # [STACKBM(sbm1)] = [mp] ; mp--
# after in stack we've got 20,40,30,20,10
PUSH /Name/
If name exist global variable
mp++; [mp] = [Name]
Else if name exist in Stack book mark
m++; [mp] = [STACKBM(Name)]
VAR var1
PUSH 100
POP var1
PUSH 10 # mp++ ; [mp] = 10
PUSH 20 # mp++ ; [mp] = 20
SBM var1
PUSH 30 # mp++ ; [mp] = 30
PUSH 40 # mp++ ; [mp] = 40
# before in stack we've got 40,30,20,10
PUSH var1 # [STACKBM(sbm1)] = [mp] ; mp--
# after in stack we've got 100,40,30,20,10 and not 20,40,30,20,10
PUSH IN
mp++ ; [mp] = readbyte
PUSH IN # mp++ ; [mp] = readbyte
INC # [mp]++
POP OUT # write([mp]) ; mp--;
Set head of stack value to zero
Same as SET 0
Same as BF [-]
RESET
[mp] = 0
PUSH 10 # mp++ ; [mp] = 10
RESET # [mp] = 0
Stack Book Mark : keep the stack position by a name
SBM /SBMName/ [- offset]
STACKBM(/SBMName/) = mp - offset
VAR var1
AT var1 # tmp = mp ; mp = var1
SET 10 # [mp] = 10
TA # mp = tmp
PUSH 'A' # mp++ ; [mp] = 65 (same as 'A')
SBM var1 -0 # STACKBM(var1) = mp
PUSH 'B' # mp++ ; [mp] = 66 ( same as 'B')
SBM var2 # STACKBM(var2) = mp
AT var1 # tmp = mp ; mp = STACKBM(var1)
ADD 2 # [mp] += 2 ; [mp] == 67 is true
OUT # write([mp]) ; output 'C' (67)
TA
AT var2 # tmp = mp ; mp = STACKBM(var1)
OUT # write([mp]) ; output 'B' (65)
TA
DROP # mp-- ; release var2
DROP # mp-- ; release var1
Set head of stack value to an immediate value or single byte character
SET /immediate/ or /single byte character/
[mp] = /immediate/ or /single byte character/
PUSH 10 # mp++ ; [mp] = 10
SET 5 # [mp] = 5 (before was 10)
Do a subtraction
SUB /immediate/
[mp] -= /immediate/
PUSH 'B' # mp++; [mp] = 66
SUB 'A' # [mp] -= 65; [mp] == 1 is true
SUB
[mp] = [mp-1] - [mp] ; mp--
PUSH 5 # mp++; [mp] = 5
PUSH 3 # mp++; [mp] = 3
SUB # [mp] = [mp-1] - [mp] ; mp-- ; [mp] == 2 is true
Does previous stack value is greater than head of stack value
SUP
[mp-1] = [mp-1] > [mp] ; mp--
PUSH 2 # mp++ ; [mp] = 2
PUSH 3 # mp++ ; [mp] = 3
SUP # [mp-1] = [mp] > [mp-1] ; mp-- ; [mp] == 1 is true
PUSH 'A' # mp++ ; [mp] = 65
PUSH 65 # mp++ ; [mp] = 65
SUP # [mp-1] = [mp] > [mp-1] ; mp-- ; [mp] == 0 is true
Exchange the two value in the head of stack
SWAP
tmp = [mp]
[mp] = [mp-1]
[mp-1] = tmp
PUSH 10
PUSH 5
SWAP
Declare a global named variable for a single byte or a byte array
For an array ther is 2 sub variables
- POS current position (see AROTL and AROTF)
- SIZE size of the variable
VAR /VarName/
N/A
VAR foo
PUSH 10 # mp++ ; [mp] = 10
POP foo # [foo] = [mp] ; mp--
VAR /VarName/*/Size/
N/A
VAR var1*5 # variable array of 5 bytes
# assume we have 'Hello' at var1
PUSH var1 # mp++ ; [mp] = 'H'
PUSH var1.pos # mp++ ; [mp] = 0
AROTL var1 # var1 is 'elloH' now
DROP # mp--
DROP # mp--
PUSH var1 # mp++ ; [mp] = 'e'
PUSH var1.pos # mp++ ; [mp] = 1
PUSH var.size # mp++ ; [mp] = 5 (size of var1)
Basically repeat a set of code as long as "head of stack value" is not zero
WHILE
/code/+
WEND
while [mp]>0 { /code/+ } mp--
PUSH 10 # mp++ ; [mp] = 10
SBM cnt
WHILE # while [cnt]>0 {
AT cnt
OUT # write([cnt]) ; will write 10,9,8 ... 1
DEC # [cnt]--
TA
WEND # }
BF Macro Assembler is a pre-compilation language. Macro template can be considered as a particular template mechanism.
PUSH 10
POP Var1
PUSH 20
POP Var2
PUSH 30
POP Var3
MACRO ASSIGN(variableName,value)
PUSH {value}
POP {variableName}
ENDMACRO
ASSIGN Var1, 10
ASSIGN Var2, 20
ASSIGN Var3, 30
Alternative syntax (provide parameters the same way as a Javascript function)
MACRO ASSIGN(variableName,value)
PUSH {value}
POP {variableName}
ENDMACRO
ASSIGN("Var1", 10)
ASSIGN("Var2", 20)
ASSIGN("Var3", 30)
PUSH 10
POP Var1
PUSH 20
POP Var2
PUSH 30
POP Var3
DEFINE LOW 10
DEFINE MIDDLE 20
DEFINE HIGH 30
PUSH {LOW}
POP Var1
PUSH {MIDDLE}
POP Var2
PUSH {HIGH}
POP Var3
DEFINE LOW 10
DEFINE MIDDLE 20
DEFINE HIGH 30
PUSH {LOW}
POP Var1
PUSH {MIDDLE}
POP Var2
PUSH {HIGH}
POP Var3
Content of file constant.inc
DEFINE LOW 10
DEFINE MIDDLE 20
DEFINE HIGH 30
Content of file main.asm
INCLUDE constant.inc
PUSH {LOW}
POP Var1
PUSH {MIDDLE}
POP Var2
PUSH {HIGH}
POP Var3
PUSH 10
POP Var1
PUSH 20
POP Var2
PUSH 30
POP Var3
JS for (var idx=1; idx < 4 ; idx++) {
PUSH {(idx*10)}
POP Var{idx}
JS }