FRAK is an assembler for the esoteric language brainfuck. It works by simulating an 8-bit conventional computer on the brainfuck machine. The simulated architecture is a RISC whose instruction set is inspired by Processor/1. The word "Frak" is the offensive word used in Battlestar Galactica instead of fuck. It was selected to represent the fact that an assembler 'fracked' the goal of brainfuck by allowing the writing of brainfuck program in an intuitive and not clever way.
Since FRAK's goal is to produce a proof of concept, it has many limitations: programs cannot address more than 256 bytes of memory (8-bit addresses), some operations are slow (those which are unknown to native brainfuck such as bitwise manipulations, if-then-else, etc.) and the instruction set is limited (no floating point support, etc.). FRAK/ASA introduces new macro instructions to overcome some of these limitations (see below).
FRAK could be improved by widening registers (16-bit would be enough to map the standard 30 000 byte memory, but 64 or 128 would taste more esoteric), extending the instruction set (a CISC would allow more efficient instructions on brainfuck 'hardware').
FRAK has General Purpose Registers (GPR) accessible by the programmer, a System Register (SR) hidden from the programmer, Control Registers (CR) accessible to the programmer but altered as a side effect of operations, and Scratch Cells that are used by the system.
||ADD||Adds register r1 to register r0.||1 if carry, 0 otherwise||
||Adds r1 to r3|
||BITWISE AND||Performs a bitwise AND between register r0 and register r1 and stores the result into register r0.||
||r1 AND r3|
||BOOLEAN AND||Performs a boolean AND between register r0 and register r1 and stores the result into register r0.||
||r1 and r3|
||BOOLEAN NOT||Applies a boolean NOT to register r0.||
||BOOLEAN OR||Performs a boolean OR between register r0 and register r1 and stores the result into register r0.||
||r1 and r3|
||DECREMENT||Subtracts 1 from register r0.||1 if wrap around, 0 otherwise||
||Subtracts 1 from r3|
||LOOP STATEMENT||While r0 is not null, execution will continue. Otherwise, execution jumps to after the matching OD. When the matching OD is encountered, execution jumps back to the matching DO and r0 is tested again.||
Sets r1 to 1
||DIVIDE||Divides register r0 by register r1.||1 if the remainder is not 0, 0 otherwise||
||Divides r2 by r0|
||CONDITIONAL STATEMENT||If the value of register r0 is null, execution will continue. Otherwise, execution will jump to after the matching ESLE instruction.||
If r3 = 0
||INPUT||Inputs the next byte into register r0.||
||Inputs the next byte into r1.|
||CONDITIONAL STATEMENT||If the value of register r0 is not zero, execution will continue. Otherwise, execution will jump to after the matching FI instruction.||
If r3 is not null
||LOAD IMMEDIATE||Sets the value of register r0 to the immediate value i0.||
||Sets r3 to 15|
||INCREMENT||Adds 1 to register r0.||1 if carry, 0 otherwise||
||Adds 1 to r3|
||LOAD||Loads the byte at the address pointed by register r1 into register r0.||
||Loads the byte at the address pointed to by r3 into r0.|
||LOAD REGISTER||Copies register r1 to register r0.||
||Copies r0 to r2|
||MODULO||Divides register r0 by register r1 and sets register r0 to the remainder.||1 if the remainder is not 0, 0 otherwise||
||Divides r2 by r0 and sets r2 to the remainder|
||MULTIPLY||Multiplies register r0 by register r1.||1 if overflow, 0 otherwise||
||Multiplies r2 by r0|
||BITWISE NOT||Applies a bitwise NOT to the value of register r0.||
||BITWISE OR||Performs a bitwise OR between register r0 and register r1 and stores the result into register r0.||
||r2 OR r1|
||OUTPUT||Outputs the byte in register r0.||
||SUBTRACT||Subtracts register r1 from register r0.||1 if wrap around, 0 otherwise||
||Subtracts r1 to r3|
||SHIFT LEFT||Shifts register r0 to the left.||1 if the leftmost bit was on||
||Shift left r3|
||SHIFT RIGHT||Shifts register r0 to the right.||1 if the rightmost bit was on||
||Shift right r3|
||STORE||Stores register r0 at the address pointed to by register r1.||
||Store r1 at the address pointed to by r3|
This program adds 100 to 200 and sets register 2 to 255 if the result overflows (which is always the case ...):
LI 0,200 LOADS IMMEDIATE VALUE 200 INTO GPR 0 LI 1,100 LOADS IMMEDIATE VALUE 100 INTO GPR 1 A 0,1 ADDS GPR 1 TO GPR 0 IF c0 IF CR 0 IS NULL JUMP TO THE MATCHING FI LI 2,X'FF' LOADS IMMEDIATE VALUE 0xFF (255) INTO GPR 2 FI c0
In 2009, FRAK was updated to allow the programmer to access more than 256 bytes of RAM. ASA allow a program to access a larger, but still finite, quantity of memory through distinct 'address spaces' of 256 bytes. The new architecture adds the SPACE macro instruction to the FRAK instruction set to support this feature. ASA maintains upward compatibility and is able to assemble unmodified any FRAK programs.
New features of the FRAK/ASA assembler
Tells the assembler to use the specified address space until another SPACE instruction is encountered.
LI 0,0 LOADS 0 INTO GPR 0 LI 1,1 LOADS 1 INTO GPR 1 SPACE 1 USE ADDRESS SPACE 1 INSTEAD OF ADDRESS SPACE 0 (DEFAULT ADDRESS SPACE) ST 1,0 STORES THE CONTENT OF GPR 1 INTO THE BYTE AT ADDRESS 0 (GPR 0 CONTAINS 0) OF ADDRESS SPACE 1 SPACE 0 USE ADDRESS SPACE 0 INSTEAD OF ADDRESS SPACE 1 LI 2,2 LOADS 2 INTO GPR 2 ST 2,0 STORES THE CONTENT OF GPR 2 INTO THE BYTE AT ADDRESS 0 (GPR 0 CONTAINS 0) OF ADDRESS SPACE 0 (DEFAULT ADDRESS SPACE)
SPACE 4095 MOVE TO THE LAST ADDRESS SPACE OF A MEGABYTE GET 1 READS A BYTE FROM THE INPUT STREAM INTO REGISTER 1 LI 0,255 STORES THE ADDRESS OF THE LAST BYTE IN REGISTER 0 ST 1,0 STORES THE CONTENT OF REGISTER 1 INTO THE BYTE POINTED BY REGISTER 0
Tells the assembler to move the pointer at the specified displacement from the beginning of the current address space. Used in conjunction with literals.
ADDRESS 3 MOVE TO THE FOURTH CELL '+' INCREMENT THIS CELL
Literals are strings of brainfuck code to be included as-is in the assembled program or sequences of numerical values to be added to the cells pointed py the pointer. Be careful to move the pointer back to its original position at the end of the execution of the code when coding a code literal because the assembler is dependent of its knowledge of the pointer position. Also, you may have to use the macro instruction ADDRESS to move the pointer to a specific address. By default, the pointer is placed at the beginning of the current address space.
'you can include anything including comments' ADDRESS 3 MOVE TO THE FOURTH CELL '[->+<]' MOVE THE CONTENT OF THE CELL (CODE LITERAL)
C'Hello World' WRITE THE STRING TO MEMORY (DATA LITERAL) ADDRESS 0 MOVE THE POINTER TO THE BEGINNING OF THE STRING '[.>]' PRINT THE STRING (CODE LITERAL)
SPACE 4095 MOVE TO THE LAST ADDRESS SPACE OF A MEGABYTE ADDRESS 255 MOVE TO THE LAST BYTE OF A MEGABYTE '+' WRITE A 1 AT THE LAST BYTE OF A MEGABYTE (CODE LITERAL)
Tells the assembler to set the specified number of contiguous cells to the specified value, starting at the current pointer position.
C'BRAINFUCK' WRITE THE STRING TO MEMORY ADDRESS 6 MOVE THE POINTER TO THE CELL THAT HOLDS U SET 2,C'*' ERASE 2 CELLS (UC) ADDRESS 0 MOVE THE POINTER TO THE BEGINNING OF THE STRING '[.>]' PRINT THE EXPURGATED STRING
C'BRAINFUCK' WRITE THE STRING TO MEMORY SET -3,C'*' ERASE THE 3 PREVIOUS CELLS (UCK) ADDRESS 0 MOVE THE POINTER TO THE BEGINNING OF THE STRING '[.>]' PRINT THE EXPURGATED STRING