Files
RustIJVM/files/bonus/bfi.jas

530 lines
10 KiB
Plaintext

// 888 .d888d8b
// 888 d88P" Y8P
// 888 888
// 88888b. 888888888
// 888 "88b888 888
// 888 888888 888
// 888 d88P888 888
// 88888P" 888 888
//
// bfi.jas, the brainfuck interpreter.
// by Arthur de Fluiter, 2017
// https://en.wikipedia.org/wiki/Brainfuck
//
// REQUIRES: Heap opeartions & simple invokevirtual
//
// Note: Since brainfuck has the ',' operator which
// reads from input and we only have one input for IJVM
// programs, we need to somehow pass both the program and
// input via stdin. This is done by delimiting the input
// with a ':'. Aka the following program will output 'd'
// ,+++.:a
//
// Note 2: This might be a difficult program to debug, especially
// given the fact that you interpret (brainfuck) on an interpreted (IJVM) platform.
.constant
OBJREF 0xdead
STACK_SIZE 0x100 // support for 160 levels of nested []
MEMORY_SIZE 0x1000 // size of the bfi's memory (needs to be a power of 2)
INIT_TEXT_SIZE 0x4 // initial size of the bfi program buffer
INIT_INPUT_SIZE 0x4 // initial size of the bfi input buffer
// configurable to conform with indecisive bf community
EOF 0
CELL_MIN 0 // adjustable to not upset the brainfuck community
CELL_MAX 255 // (they're not entirely sure whether signed/unsigned word/quadword)
// delimiter for seperating bf program and bf input
DELIMITER 0x3a // :
// characters used in brainfuck
ASCII_PLUS 0x2b // +
ASCII_MINUS 0x2d // -
ASCII_LT 0x3c // <
ASCII_GT 0x3e // >
ASCII_BO 0x5b // [
ASCII_BC 0x5d // ]
ASCII_DOT 0x2e // .
ASCII_COMMA 0x2c // ,
.end-constant
.main
.var
text // the bf program buffer
textcap // capacity of the program buffer
textsize // size of of the program buffer
input // the bf program's input
inputcap // capacity of the input
inputsize // size of of the input
char // read in char
.end-var
// first setting up the variables
BIPUSH 0x0 // textsize = 0
ISTORE textsize
LDC_W INIT_TEXT_SIZE // textcap = INIT_TEXT_SIZE
ISTORE textcap
LDC_W INIT_TEXT_SIZE // text = new array[INIT_TEXT_SIZE]
NEWARRAY
ISTORE text
BIPUSH 0x0 // inputsize = 0
ISTORE inputsize
LDC_W INIT_INPUT_SIZE // inputcap = INIT_TEXT_SIZE
ISTORE inputcap
LDC_W INIT_INPUT_SIZE // input = new array[INIT_INPUT_SIZE]
NEWARRAY
ISTORE input
slurp_program_loop:
// if (textsize == text capacity) -> realloc text
ILOAD textsize
ILOAD textcap
ISUB
IFEQ text_grow
// char = IN;
IN
ISTORE char
// if (char == 0) -> we're done reading
ILOAD char
IFEQ done
// if (char == DELIMETER) -> read input
ILOAD char
LDC_W DELIMITER
IF_ICMPEQ slurp_input_loop
// if (!checkBF(char)) goto next char, aka eliminating unnecessary chars
LDC_W OBJREF
ILOAD char
INVOKEVIRTUAL checkBF
IFEQ slurp_program_loop
// text[textsize++] = char
ILOAD char
ILOAD textsize
IINC textsize 0x1
ILOAD text
IASTORE
GOTO slurp_program_loop
slurp_input_loop:
// if (input size == input capacity) -> realloc input
ILOAD inputsize
ILOAD inputcap
ISUB
IFEQ input_grow
// char = IN; if (char == 0) -> we're done reading
IN
DUP
ISTORE char
IFEQ done
// input[inputsize++] = char
ILOAD char
ILOAD inputsize
IINC inputsize 0x1
ILOAD input
IASTORE
GOTO slurp_input_loop
text_grow:
// text = realloc(text, textcap, (textcap *= 2))
LDC_W OBJREF
ILOAD text
ILOAD textcap
DUP
DUP
IADD
DUP
ISTORE textcap
INVOKEVIRTUAL realloc
ISTORE text
GOTO slurp_program_loop
input_grow:
// input = realloc(input, inputcap, (inputcap *= 2))
LDC_W OBJREF
ILOAD input
ILOAD inputcap
DUP
DUP
IADD
DUP
ISTORE inputcap
INVOKEVIRTUAL realloc
ISTORE input
GOTO slurp_input_loop
done:
LDC_W OBJREF
ILOAD text
ILOAD textsize
ILOAD input
ILOAD inputsize
INVOKEVIRTUAL exec
POP
HALT
.end-main
// checkBF whether a char is a brainfuck symbol or not
// @param symbol
// @return 0 if not bf 1 if bf
.method checkBF(symbol)
ILOAD symbol
LDC_W ASCII_PLUS
IF_ICMPEQ ret1
ILOAD symbol
LDC_W ASCII_MINUS
IF_ICMPEQ ret1
ILOAD symbol
LDC_W ASCII_LT
IF_ICMPEQ ret1
ILOAD symbol
LDC_W ASCII_GT
IF_ICMPEQ ret1
ILOAD symbol
LDC_W ASCII_BO
IF_ICMPEQ ret1
ILOAD symbol
LDC_W ASCII_BC
IF_ICMPEQ ret1
ILOAD symbol
LDC_W ASCII_DOT
IF_ICMPEQ ret1
ILOAD symbol
LDC_W ASCII_COMMA
IF_ICMPEQ ret1
BIPUSH 0
IRETURN
ret1:
BIPUSH 1
IRETURN
.end-method
// realloc, 'reallocating' a buffer
// @param buffer, original buffer
// @param curSize, size to be copied
// @param newSize, size that it should become
//
// @return new buffer of newSize, with the contents of the last buffer
.method realloc(buffer, curSize, newSize)
.var
newBuffer
i
.end-var
ILOAD newSize
NEWARRAY
ISTORE newBuffer
BIPUSH 0
ISTORE i
loop:
ILOAD i
ILOAD curSize
IF_ICMPEQ end
ILOAD i
ILOAD buffer
IALOAD
ILOAD i
ILOAD newBuffer
IASTORE
IINC i 0x1
GOTO loop
end:
ILOAD newBuffer
IRETURN
.end-method
// exec, runs a brainfuck program
// @param text, program text (not containing anything but raw bf)
// @param textsize, program size
// @param input, the stdin of the program
// @param size of input
// @return garbage
.method exec(text, textsize, input, inputsize)
.var
pc // keeps track of where we are in the bf program
memory // the memory array
memptr // points to offset in the memory array
stack // keeps track of where to return bcs of []
stackptr // pointer to last one
instr // holds current instruction
inputptr // points to what input symbol we're at
tmp // used as temporary value in seeking operation
.end-var
BIPUSH 0 // pc = 0
ISTORE pc
BIPUSH 0 // inputptr = 0
ISTORE inputptr
BIPUSH 0 // memptr = 0
ISTORE memptr
LDC_W MEMORY_SIZE // memory = NEWARRAY(MEMORY_SIZE)
NEWARRAY
ISTORE memory
BIPUSH -1 // stackptr = -1
ISTORE stackptr
LDC_W STACK_SIZE // stack = NEWARRAY(STACK_SIZE)
NEWARRAY
ISTORE stack
// since the memory technically doesn't have to be initialised to 0, I put this in for safety
init_mem_loop:
LDC_W MEMORY_SIZE
ILOAD memptr
IF_ICMPEQ done_init_mem
BIPUSH 0
ILOAD memptr
ILOAD memory
IASTORE
IINC memptr 1
GOTO init_mem_loop
done_init_mem:
BIPUSH 0
ISTORE memptr
exec_loop:
// if we're at the end of the program, return
ILOAD pc
ILOAD textsize
IF_ICMPEQ done
// instr = text[pc++]
ILOAD pc
IINC pc 1
ILOAD text
IALOAD
ISTORE instr
ILOAD instr
LDC_W ASCII_PLUS
IF_ICMPEQ plus
ILOAD instr
LDC_W ASCII_MINUS
IF_ICMPEQ minus
ILOAD instr
LDC_W ASCII_DOT
IF_ICMPEQ dot
ILOAD instr
LDC_W ASCII_COMMA
IF_ICMPEQ comma
ILOAD instr
LDC_W ASCII_LT
IF_ICMPEQ lessthan
ILOAD instr
LDC_W ASCII_GT
IF_ICMPEQ greaterthan
ILOAD instr
LDC_W ASCII_BO
IF_ICMPEQ blockopen
ILOAD instr
LDC_W ASCII_BC
IF_ICMPEQ blockclose
GOTO exec_loop
plus:
ILOAD memptr
ILOAD memory
IALOAD
DUP
LDC_W CELL_MAX
IF_ICMPEQ plus_overflow
BIPUSH 1
IADD
ILOAD memptr
ILOAD memory
IASTORE
GOTO exec_loop
plus_overflow:
POP
LDC_W CELL_MIN
ILOAD memptr
ILOAD memory
IASTORE
GOTO exec_loop
minus:
ILOAD memptr
ILOAD memory
IALOAD
DUP
LDC_W CELL_MIN
IF_ICMPEQ minus_underflow
BIPUSH -1
IADD
ILOAD memptr
ILOAD memory
IASTORE
GOTO exec_loop
minus_underflow:
POP
LDC_W CELL_MAX
ILOAD memptr
ILOAD memory
IASTORE
GOTO exec_loop
dot:
ILOAD memptr
ILOAD memory
IALOAD
OUT
GOTO exec_loop
comma:
ILOAD inputptr
ILOAD inputsize
IF_ICMPEQ comma_noinput
// memory[memptr] = input[inputptr++]
ILOAD inputptr
IINC inputptr 1
ILOAD input
IALOAD
ILOAD memptr
ILOAD memory
IASTORE
GOTO exec_loop
comma_noinput:
LDC_W EOF
ILOAD memptr
ILOAD memory
IASTORE
GOTO exec_loop
lessthan:
ILOAD memptr
BIPUSH -1
IADD
LDC_W MEMORY_SIZE
BIPUSH -1
IADD
IAND
ISTORE memptr
GOTO exec_loop
greaterthan:
ILOAD memptr
BIPUSH 1
IADD
LDC_W MEMORY_SIZE
BIPUSH -1
IADD
IAND
ISTORE memptr
GOTO exec_loop
blockopen:
// let's first check if we should seek to after the closing ]
ILOAD memptr
ILOAD memory
IALOAD
IFEQ blockopen_seek
// if not, we'll add pc to the return stack and continue normal execution
IINC stackptr 1
// push the address of the [ on stack, we'll jump back to blockopen
ILOAD pc // pc counter was already updated
BIPUSH -1 // so we need to subtract 1
IADD
ILOAD stackptr
ILOAD stack
IASTORE
// go back to normal execution loop
GOTO exec_loop
blockopen_seek:
// we increase tmp when a [ is encountered and decrease if a ] is encountered
// when tmp reaches 0, we'll have reached the correct one
BIPUSH 1
ISTORE tmp
blockopen_seekloop:
//
ILOAD pc
IINC pc 1
ILOAD program
IALOAD
ISTORE instr
// if instr == '[' goto inc
ILOAD instr
LDC_W ASCII_BO
IF_ICMP blockopen_inc
// if instr == ']' goto dec
ILOAD instr
LDC_W ASCII_BC
IF_ICMP blockopen_dec
// if it was neither a [ or ], just go to next instruction
GOTO blockopen_seekloop
blockopen_inc:
IINC tmp 1
GOTO blockopen_seekloop
blockopen_dec:
IINC tmp -1
IFEQ exec_loop
GOTO blockopen_seekloop
blockclose:
// we have to return to the last [
ILOAD stackptr
ILOAD stack
IALOAD
ISTORE pc
GOTO exec_loop
done:
LDC_W OBJREF
IRETURN
.end-method