bf.go (2914B)
1 package gbfy 2 3 import ( 4 "fmt" 5 "io" 6 ) 7 8 type Brainfuck struct { 9 cells [3e4]byte 10 cmds []byte 11 i, d int // instruction and data pointer 12 in io.Reader // for reading with , 13 out io.Writer // for writing with . 14 loops []int // stores index for matching [ or ] 15 } 16 17 // New returns a new Brainfuck interpreter. 18 func New(in io.Reader, out io.Writer) *Brainfuck { 19 return &Brainfuck{in: in, out: out, loops: []int{}} 20 } 21 22 // Eval evaluates a valid Brainfuck program. 23 func (bf *Brainfuck) Eval(src string) error { 24 cmds, loops, err := parseProgram(src) 25 if err != nil { 26 return fmt.Errorf("failed to parse program: %v", err) 27 } 28 // update loop pairs. 29 off := len(bf.cmds) 30 for i := 1; i < len(loops); i++ { // skip first element; see below! 31 if loops[i] > 0 { 32 loops[i] += off 33 } 34 } 35 // ... and handle special case where program begins with loop. 36 if loops[0] > 0 { 37 loops[loops[0]] += off 38 loops[0] += off 39 } 40 // update interpreter state 41 bf.cmds = append(bf.cmds, cmds...) 42 bf.loops = append(bf.loops, loops...) 43 44 return bf.eval() 45 } 46 47 func (bf *Brainfuck) eval() error { 48 for { 49 if n := len(bf.cmds); n == 0 || n <= bf.i { 50 return nil 51 } 52 53 switch bf.cmds[bf.i] { 54 case '>': 55 bf.d++ 56 if bf.d >= len(bf.cells) { 57 bf.d -= len(bf.cells) 58 } 59 case '<': 60 bf.d-- 61 if bf.d < 0 { 62 bf.d += len(bf.cells) 63 } 64 case '+': 65 bf.cells[bf.d]++ 66 case '-': 67 bf.cells[bf.d]-- 68 case '.': 69 if n, err := bf.out.Write(bf.cells[bf.d : bf.d+1]); n == 0 { 70 return fmt.Errorf("failed to Write ouput: no bytes written") 71 } else if err != nil { 72 return fmt.Errorf("failed to Write output: %v", err) 73 } 74 case ',': 75 if n, err := bf.in.Read(bf.cells[bf.d : bf.d+1]); n == 0 { 76 return fmt.Errorf("failed to Read input: no bytes read") 77 } else if err != nil { 78 return fmt.Errorf("failed to Read input: %v", err) 79 } 80 case '[': 81 if bf.cells[bf.d] == 0 { 82 bf.i = bf.loops[bf.i] 83 } 84 case ']': 85 if bf.cells[bf.d] != 0 { 86 bf.i = bf.loops[bf.i] 87 } 88 } 89 90 bf.i++ 91 } 92 } 93 94 // Dump interpreter state to caller. 95 func (bf *Brainfuck) Dump() (int, []byte, int, []byte) { 96 return bf.d, bf.cells[:], bf.i, bf.cmds 97 } 98 99 func parseProgram(src string) ([]byte, []int, error) { 100 // collect valid Brainfuck commands. 101 var cmds []byte 102 for _, b := range []byte(src) { 103 switch b { 104 case '[', ']', '<', '>', '+', '-', ',', '.': 105 cmds = append(cmds, b) 106 } 107 } 108 // scan program and remember indices of loop pairs. 109 loops := make([]int, len(cmds)) 110 var opens []int 111 for i, cmd := range cmds { 112 switch cmd { 113 case '[': 114 opens = append(opens, i) 115 case ']': 116 if len(opens) == 0 { 117 return nil, nil, fmt.Errorf("mismatched [ at index %d", i) 118 } 119 // pop 120 j := opens[len(opens)-1] 121 opens = opens[:len(opens)-1] 122 // swap 123 loops[i] = j 124 loops[j] = i 125 } 126 } 127 if len(opens) > 0 { 128 return nil, nil, fmt.Errorf("mismatched ] at index %d", opens[0]) 129 } 130 131 return cmds, loops, nil 132 }