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_test.go (6109B)


      1 package gbfy
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"io"
      7 	"os"
      8 	"testing"
      9 
     10 	"github.com/google/go-cmp/cmp"
     11 )
     12 
     13 func TestEval(t *testing.T) {
     14 	tests := []struct {
     15 		// Program to evaluate.
     16 		cmds string
     17 		// Expected value of instruction and Data pointer.
     18 		i, d int
     19 		// Expected values of *specific* cells; mapping between cell
     20 		// index and value expected there. Since the cell region is
     21 		// large (3e4) and sparse (0 by default) only specific cells
     22 		// are checked.
     23 		cells map[int]byte
     24 		// Similar as above, but for the internal loop jump lookup array.
     25 		// Internally this is a dense array (len(loops) == len(cmds)) with
     26 		// nonzero elements corresponding to the ['s or ]'s index in bf.cmds
     27 		loops map[int]int
     28 		// Expected output data bytes.
     29 		out []byte
     30 	}{
     31 		// Check < and > move data pointer around circular cell region.
     32 		{"<", 1, 29999, nil, nil, nil},
     33 		{">", 2, 0, nil, nil, nil},
     34 		// Check that + and - modify cell values.
     35 		{"+", 3, 0, map[int]byte{0: 1}, nil, nil},
     36 		{"-", 4, 0, map[int]byte{0: 0}, nil, nil},
     37 		// Check that , and . read input and write output bytes.
     38 		{",", 5, 0, map[int]byte{0: 4}, nil, nil},
     39 		{">", 6, 1, map[int]byte{0: 4}, nil, nil},
     40 		{",", 7, 1, map[int]byte{0: 4, 1: 8}, nil, nil},
     41 		{",", 8, 1, map[int]byte{0: 4, 1: 15}, nil, nil},
     42 		{">", 9, 2, map[int]byte{0: 4, 1: 15}, nil, nil},
     43 		{",", 10, 2, map[int]byte{0: 4, 1: 15, 2: 16}, nil, nil},
     44 		{".", 11, 2, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16}},
     45 		{"<", 12, 1, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16}},
     46 		{".", 13, 1, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16, 15}},
     47 		{"<", 14, 0, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16, 15}},
     48 		{".", 15, 0, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16, 15, 4}},
     49 		{"[->+<]", 21, 0, map[int]byte{0: 0, 1: 19, 2: 16}, map[int]int{15: 20, 20: 15}, []byte{16, 15, 4}},
     50 		{">", 22, 1, map[int]byte{0: 0, 1: 19, 2: 16}, map[int]int{15: 20, 20: 15}, []byte{16, 15, 4}},
     51 		{".", 23, 1, map[int]byte{0: 0, 1: 19, 2: 16}, map[int]int{15: 20, 20: 15}, []byte{16, 15, 4, 19}},
     52 	}
     53 
     54 	var out bytes.Buffer
     55 	bf := New(bytes.NewBuffer([]byte{4, 8, 15, 16, 23, 41}), &out)
     56 	if err := checkInterpreter(bf, 0, 0, nil, nil); err != nil {
     57 		t.Fatalf("[0] Unexpected interpreter state: %v", err)
     58 	}
     59 
     60 	for i, test := range tests {
     61 		if err := bf.Eval(test.cmds); err != nil {
     62 			t.Fatalf("[%d] Eval(%q) failed with error: %v", i, test.cmds, err)
     63 		}
     64 		if err := checkInterpreter(bf, test.i, test.d, test.cells, test.loops); err != nil {
     65 			t.Fatalf("[%d] Unexpected interpreter state: %v", i, err)
     66 		}
     67 		if diff := cmp.Diff(test.out, out.Bytes()); diff != "" {
     68 			t.Fatalf("[%d] output diff (-want +got): %s", i, diff)
     69 		}
     70 	}
     71 }
     72 
     73 func checkInterpreter(bf *Brainfuck, i, d int, cells map[int]byte, loops map[int]int) error {
     74 	if bf.i != i {
     75 		return fmt.Errorf("instruction pointer; got %d, want %d", bf.i, i)
     76 	}
     77 	if bf.d != d {
     78 		return fmt.Errorf("data pointer; got %d, want %d", bf.d, d)
     79 	}
     80 	// bf.cells \subseteq cells.
     81 	for idx, got := range bf.cells {
     82 		if want := cells[idx]; got != want {
     83 			return fmt.Errorf("cell value at index %d; got %d, want %d", idx, got, want)
     84 		}
     85 	}
     86 	// cells \subseteq bf.cells.
     87 	for idx, want := range cells {
     88 		if got := bf.cells[idx]; got != want {
     89 			return fmt.Errorf("cell value at index %d; got %d, want %d", idx, got, want)
     90 		}
     91 	}
     92 	// bf.loops \subseteq loops.
     93 	for idx, got := range bf.loops {
     94 		if want := loops[idx]; got != want {
     95 			return fmt.Errorf("loop value at index %d; got %d, want %d", idx, got, want)
     96 		}
     97 	}
     98 	// loops \subseteq bf.loops.
     99 	for idx, want := range loops {
    100 		if got := bf.loops[idx]; got != want {
    101 			return fmt.Errorf("loop value at index %d; got %d, want %d", idx, got, want)
    102 		}
    103 	}
    104 	return nil
    105 }
    106 
    107 func TestInvalidLoopHandling(t *testing.T) {
    108 	tests := []string{"[", "]", "[]]", "[][", "[][]]"}
    109 	for _, test := range tests {
    110 		if _, err := Run([]byte(test), nil); err == nil {
    111 			t.Errorf("Parsed invalid program %q; expected error", test)
    112 		}
    113 	}
    114 }
    115 
    116 func TestHelloWorld(t *testing.T) {
    117 	program := `
    118 		>++++++++[<+++++++++>-]<.
    119 		>++++[<+++++++>-]<+.
    120 		+++++++..
    121 		+++.
    122 		>>++++++[<+++++++>-]<++.
    123 		------------.
    124 		>++++++[<+++++++++>-]<+.
    125 		<.
    126 		+++.
    127 		------.
    128 		--------.
    129 		>>>++++[<++++++++>-]<+.`
    130 
    131 	out, err := Run([]byte(program), nil)
    132 	if err != nil {
    133 		t.Fatalf("Run failed: %v", err)
    134 	}
    135 	if diff := cmp.Diff("Hello, World!", string(out)); diff != "" {
    136 		t.Errorf("Mismatched output data (-want +got):\n%s", diff)
    137 	}
    138 }
    139 
    140 func TestCellWrapping(t *testing.T) {
    141 	bf := New(nil, nil)
    142 	// Move to the right .... 3e4 - 1 times.
    143 	for i := 0; i < len(bf.cells)-1; i++ {
    144 		if err := bf.Eval(">"); err != nil {
    145 			t.Fatalf("Eval(>) failed with error %v", err)
    146 		}
    147 	}
    148 	if err := checkInterpreter(bf, 29999, 29999, nil, nil); err != nil {
    149 		t.Fatalf("Unexpected interpreter state: %v", err)
    150 	}
    151 	// Move to the right once more -- should wrap!
    152 	if err := bf.Eval(">"); err != nil {
    153 		t.Fatalf("Eval(>) failed with error %v", err)
    154 	} else if err := checkInterpreter(bf, 30000, 0, nil, nil); err != nil {
    155 		t.Fatalf("Unexpected interpreter state: %v", err)
    156 	}
    157 	// Move to the left -- should wrap!
    158 	if err := bf.Eval("<"); err != nil {
    159 		t.Fatalf("Eval(<) failed with error %v", err)
    160 	}
    161 	if err := checkInterpreter(bf, 30001, 29999, nil, nil); err != nil {
    162 		t.Fatalf("Unexpected interpreter state: %v", err)
    163 	}
    164 }
    165 
    166 func Run(cmds []byte, in io.Reader) ([]byte, error) {
    167 	var out bytes.Buffer
    168 	bf := New(in, &out)
    169 	if err := bf.Eval(string(cmds)); err != nil {
    170 		return nil, err
    171 	}
    172 	return out.Bytes(), nil
    173 }
    174 
    175 // Find your own source files!
    176 func BenchmarkLong(b *testing.B)       { runBenchmark(b, "long.bf", nil) }
    177 func BenchmarkMandelbrot(b *testing.B) { runBenchmark(b, "mandelbrot.bf", nil) }
    178 func BenchmarkFactor(b *testing.B) {
    179 	runBenchmark(b, "factor.bf", bytes.NewReader([]byte("418151632\n")))
    180 }
    181 
    182 func runBenchmark(b *testing.B, path string, in io.Reader) {
    183 	b.Helper()
    184 	src, err := os.ReadFile(path)
    185 	if err != nil {
    186 		b.Fatalf("Failed to read benchmark file %q: %v", path, err)
    187 	}
    188 	if _, err := Run(src, in); err != nil {
    189 		b.Fatalf("Failed to run benchmark program %q: %v", path, err)
    190 	}
    191 }