go-brainfuck-yourself

A Brainfuck interpreter that I wrote myself in Go
git clone git@abtrout.com:go-brainfuck-yourself.git
Log | Files | Refs | README

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 }