import os import term // For a more detailed description of the brainfuck language, see: // https://en.wikipedia.org/wiki/Brainfuck // http://brainfuck.org/brainfuck.html , // http://brainfuck.org/epistle.html , // http://www.hevanet.com/cristofd/brainfuck/ . const show_state = os.getenv('VERBOSE') != '' struct BFState { mut: pc u16 // program counter (PC) register address u16 // a 16-bit address register, serving as an index to the memory below program string // the BF program memory []u8 = []u8{len: 65536} // we have 2^16 bytes of memory targets map[int]int // a mapping for the program address of a `[` to its corresponding `]`, and from a `]` to its corresponding opening `[`. } fn BFState.new(program string) &BFState { mut state := &BFState{ program: program } state.find_matching_pairs() return state } // show the current state of an BF interpreter. Useful for debugging. fn (state &BFState) show() { println('PC: ${state.pc}') println('Address: ${state.address}') mut max_non_zero_address := -1 for i := state.memory.len - 1; i >= 0; i-- { if state.memory[i] != 0 { max_non_zero_address = i break } } println('Memory: ${state.memory#[0..max_non_zero_address + 1]}') println('Memory[Address]: ${state.memory#[state.address..state.address + 1]}') } // find_matching_pairs fills in the `targets` mapping for all pairs of `[` and `]`, // so that when interpreting, we would not have to search for them anymore. fn (mut state BFState) find_matching_pairs() { mut stack := []int{} for i in 0 .. state.program.len { pi := state.program[i] match pi { `[` { stack << i } `]` { if stack.len == 0 { eprintln('> unmatched `]` found in the program, at position: ${i}') eprintln('program so far:') eprintln(state.program#[0..i + 1]) exit(1) } pc := stack.pop() state.targets[pc] = i + 1 state.targets[i] = pc + 1 // eprintln('>>> found `[` at i $i; pc: $pc') } else {} } } if stack.len > 0 { eprintln('> found ${stack.len} unmatched `[`:') for i in stack { eprintln(' `[` at position: ${i}, program so far: `${state.program#[0..i + 1]}`') } exit(1) } } @[noreturn] fn (state &BFState) panic_for_bracket(b1 rune, b2 rune) { panic('unbalanced `${b1}` found, its target `${b2}` is not known; address: ${state.address}, pc: ${state.pc}') } fn (mut state BFState) run() ? { // the BF interpreter starts here: for state.pc < state.program.len { // get the current program character (corresponding to our program counter), and interpret it according to BF's rules: match state.program[state.pc] { `>` { state.address++ // increment the address } `<` { state.address-- // decrement the address } `+` { state.memory[state.address]++ // increment the value at the address } `-` { state.memory[state.address]-- // decrement the value at the address } `.` { print(rune(state.memory[state.address])) // print the value at the address flush_stdout() // ensure that even single characters are printed immediately, and not buffered } `,` { inp := u8(term.utf8_getchar() or { 0 }) // read a character value from the standard input/terminal state.memory[state.address] = inp } `[` { if state.memory[state.address] == 0 { state.pc = u16(state.targets[state.pc]) continue } } `]` { if state.memory[state.address] != 0 { state.pc = u16(state.targets[state.pc]) continue } } `#` { state.show() } else { // The interpreter should ignore characters that are not part of the language. // I.e. they are treated like programmer comments. } } // increment the program counter to go to the next instruction state.pc++ // go back to the line `for state.pc < state.program.len {` } } @[noreturn] fn show_usage() { eprintln('you need to supply a brainfuck program/expression as a string argument,') eprintln('or filename.b, if it is located in a file (note the `.b` extension).') exit(1) // exit with non-zero exit code if there is no program to run } fn main() { if os.args.len < 2 { show_usage() } mut program := os.args[1] // our program is fed in as a string if program.ends_with('.b') { program = os.read_file(program) or { eprintln('error reading file ${program}: ${err}') show_usage() } } mut state := BFState.new(program) state.run() if show_state { state.show() } }