v / examples / brainvuck.v
165 lines · 152 sloc · 4.58 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1import os
2import term
3
4// For a more detailed description of the brainfuck language, see:
5// https://en.wikipedia.org/wiki/Brainfuck
6// http://brainfuck.org/brainfuck.html ,
7// http://brainfuck.org/epistle.html ,
8// http://www.hevanet.com/cristofd/brainfuck/ .
9
10const show_state = os.getenv('VERBOSE') != ''
11
12struct BFState {
13mut:
14 pc u16 // program counter (PC) register
15 address u16 // a 16-bit address register, serving as an index to the memory below
16 program string // the BF program
17 memory []u8 = []u8{len: 65536} // we have 2^16 bytes of memory
18 targets map[int]int // a mapping for the program address of a `[` to its corresponding `]`, and from a `]` to its corresponding opening `[`.
19}
20
21fn BFState.new(program string) &BFState {
22 mut state := &BFState{
23 program: program
24 }
25 state.find_matching_pairs()
26 return state
27}
28
29// show the current state of an BF interpreter. Useful for debugging.
30fn (state &BFState) show(suffix string) {
31 mut max_non_zero_address := -1
32 for i := state.memory.len - 1; i >= 0; i-- {
33 if state.memory[i] != 0 {
34 max_non_zero_address = i
35 break
36 }
37 }
38 println('PC: ${state.pc:04} | Address: ${state.address:04} | Memory: ${state.memory#[0..
39 max_non_zero_address + 1]:-40s} | Memory[Address]: ${state.memory#[state.address..
40 state.address + 1]:-10s} | ${suffix}')
41}
42
43// find_matching_pairs fills in the `targets` mapping for all pairs of `[` and `]`,
44// so that when interpreting, we would not have to search for them anymore.
45fn (mut state BFState) find_matching_pairs() {
46 mut stack := []int{}
47 for i in 0 .. state.program.len {
48 pi := state.program[i]
49 match pi {
50 `[` {
51 stack << i
52 }
53 `]` {
54 if stack.len == 0 {
55 eprintln('> unmatched `]` found in the program, at position: ${i}')
56 eprintln('program so far:')
57 eprintln(state.program#[0..i + 1])
58 exit(1)
59 }
60 pc := stack.pop()
61 state.targets[pc] = i + 1
62 state.targets[i] = pc + 1
63 // eprintln('>>> found `[` at i ${i}; pc: ${pc}')
64 }
65 else {}
66 }
67 }
68 if stack.len > 0 {
69 eprintln('> found ${stack.len} unmatched `[`:')
70 for i in stack {
71 eprintln(' `[` at position: ${i}, program so far: `${state.program#[0..i + 1]}`')
72 }
73 exit(1)
74 }
75}
76
77@[noreturn]
78fn (state &BFState) panic_for_bracket(b1 rune, b2 rune) {
79 panic('unbalanced `${b1}` found, its target `${b2}` is not known; address: ${state.address}, pc: ${state.pc}')
80}
81
82fn (mut state BFState) run() ? {
83 mut i := 0
84 // the BF interpreter starts here:
85 for state.pc < state.program.len {
86 // get the current program character (corresponding to our program counter), and interpret it according to BF's rules:
87 instruction := state.program[state.pc]
88 $if trace_execution ? {
89 state.show('instruction ${i:08}: ${instruction:02x}, ${rune(instruction)}')
90 }
91 match instruction {
92 `>` {
93 state.address++ // increment the address
94 }
95 `<` {
96 state.address-- // decrement the address
97 }
98 `+` {
99 state.memory[state.address]++ // increment the value at the address
100 }
101 `-` {
102 state.memory[state.address]-- // decrement the value at the address
103 }
104 `.` {
105 print(rune(state.memory[state.address])) // print the value at the address
106 flush_stdout() // ensure that even single characters are printed immediately, and not buffered
107 }
108 `,` {
109 inp := u8(term.utf8_getchar() or { 0 }) // read a character value from the standard input/terminal
110 state.memory[state.address] = inp
111 }
112 `[` {
113 if state.memory[state.address] == 0 {
114 state.pc = u16(state.targets[state.pc])
115 continue
116 }
117 }
118 `]` {
119 if state.memory[state.address] != 0 {
120 state.pc = u16(state.targets[state.pc])
121 continue
122 }
123 }
124 `#` {
125 state.show('#')
126 }
127 else {
128 // The interpreter should ignore characters that are not part of the language.
129 // I.e. they are treated like programmer comments.
130 }
131 }
132
133 i++
134 // increment the program counter to go to the next instruction
135 state.pc++
136 // go back to the line `for state.pc < state.program.len {`
137 }
138}
139
140@[noreturn]
141fn show_usage() {
142 eprintln('you need to supply a brainfuck program/expression as a string argument,')
143 eprintln('or filename.b, if it is located in a file (note the `.b` or `.bf` extension).')
144 exit(1) // exit with non-zero exit code if there is no program to run
145}
146
147fn main() {
148 if os.args.len < 2 {
149 show_usage()
150 }
151 mut program := os.args[1] // our program is fed in as a string
152 if program.ends_with('.b') || program.ends_with('.bf') {
153 program = os.read_file(program) or {
154 eprintln('error reading file ${program}: ${err}')
155 show_usage()
156 }
157 }
158
159 mut state := BFState.new(program)
160 state.run()
161
162 if show_state {
163 state.show('FINAL')
164 }
165}
166