diff --git a/examples/brainvuck.v b/examples/brainvuck.v index a165ade52a..858f36e914 100644 --- a/examples/brainvuck.v +++ b/examples/brainvuck.v @@ -1,66 +1,159 @@ import os +import term -fn main() { - if os.args.len < 2 { - eprintln('you need to supply a brainfuck program as a string argument') - exit(1) // exit with non-zero exit code if there is no program to run +// 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 } - program := os.args[1] // our program is fed in as a string + state.find_matching_pairs() + return state +} - mut memory := []u8{len: 256} // we have 256 bytes of memory - mut address := u8(0) // as well as an 8-bit address register +// 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]}') +} - mut stack := []int{} // our stack does not need a maximum length - - mut program_counter := 0 // program counter - - // interpreter starts here - for program_counter < program.len { - // we look at what the current character our program counter is seeing - match program[program_counter] { - `>` { - address++ // increment the address - } - `<` { - address-- // decrement the address - } - `+` { - memory[address]++ // increment the value at the address - } - `-` { - memory[address]-- // decrement the value at the address - } - `.` { - print(memory[address].ascii_str()) // print the value at the address - } - `,` { - input := os.input_opt('') or { '' } // read value and account for errors - memory[address] = input[0] // this is so we can ignore newlines - // because strings are 0-terminated, it also gives us a default value for free! - } +// 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 << program_counter // add loop start address to the call stack + stack << i } `]` { - if memory[address] != 0 { - // set the program counter to the last loop start - // so it jumps back and loops again - program_counter = stack[stack.len - 1] - } else { - // otherwise remove the address from the stack and continue - stack.pop() + 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 + // 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 - program_counter++ - // back to line 20! + 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() + } } - // print the state of the interpreter at the end - println('Address: ${address}') - println('Memory: ${memory}') + mut state := BFState.new(program) + state.run() + + if show_state { + state.show() + } } diff --git a/examples/wasm_codegen/bf_compiler.v b/examples/wasm_codegen/bf_compiler.v index 3a4b47f28a..5cddb1db72 100644 --- a/examples/wasm_codegen/bf_compiler.v +++ b/examples/wasm_codegen/bf_compiler.v @@ -104,13 +104,19 @@ fn generate_code(mut start wasm.Function, bf_expr string) { @[noreturn] fn usage() { - eprintln('Usage: bf ') + eprintln('Usage: bf ') + eprintln(' or: bf (note the `.b` extension)') exit(1) } fn main() { - bf_expr := os.args[1] or { usage() } - + mut bf_expr := os.args[1] or { usage() } + if bf_expr.ends_with('.b') { + bf_expr = os.read_file(bf_expr) or { + eprintln('file ${bf_expr} could not be read, error: ${err}') + usage() + } + } outfile := os.args[2] or { usage() } mut m := wasm.Module{}