commit 61f8144755065e465703f2381b0c07e632dda094
parent bfdbf53a13e919d0c466180ed4a5819a2f659e1d
Author: david cochran <about.trout@gmail.com>
Date: Fri, 29 Dec 2023 09:36:30 -0800
refactor to use io Reader/Writer
Diffstat:
| M | bf.go | | | 45 | +++++++++++++++++++++++++-------------------- |
| M | bf_test.go | | | 25 | ++++++++++++------------- |
| M | cmd/gbfy/main.go | | | 54 | +++++++++++++++++++++++++++--------------------------- |
3 files changed, 64 insertions(+), 60 deletions(-)
diff --git a/bf.go b/bf.go
@@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
+ "io"
)
type Brainfuck struct {
@@ -12,29 +13,18 @@ type Brainfuck struct {
cmds []byte // commands evaluated by the interpreter
i int // instruction pointer for cmds access
- in, out *bytes.Buffer // input and output buffers
+ in io.Reader
+ out io.Writer
loops map[int]int // stores index for matching [ or ]
parLoops []int // indices for not-yet-complete loops
}
// New returns a new Brainfuck interpreter.
-func New(in, out *bytes.Buffer) *Brainfuck {
+func New(in io.Reader, out io.Writer) *Brainfuck {
return &Brainfuck{in: in, out: out, loops: map[int]int{}}
}
-// Run runs a Brainfuck program and returns output bytes or error.
-func Run(program string, in *bytes.Buffer) ([]byte, error) {
- var out bytes.Buffer
- bf := New(in, &out)
- for _, cmd := range []byte(program) {
- if err := bf.Eval(cmd); err != nil {
- return nil, err
- }
- }
- return out.Bytes(), nil
-}
-
// Eval evaluates a single command with the given interpreter.
func (bf *Brainfuck) Eval(cmd byte) error {
switch cmd {
@@ -85,12 +75,15 @@ func (bf *Brainfuck) eval() error {
case '-':
bf.cells[bf.d]--
case '.':
- bf.out.WriteByte(bf.cells[bf.d])
+ if _, err := bf.out.Write([]byte{bf.cells[bf.d]}); err != nil {
+ return fmt.Errorf("failed to Write output: %v", err)
+ }
case ',':
- if b, err := bf.in.ReadByte(); err != nil {
- return fmt.Errorf("failed to readDataVal: %v", err)
+ input := make([]byte, 1)
+ if _, err := bf.in.Read(input); err != nil {
+ return fmt.Errorf("failed to Read input: %v", err)
} else {
- bf.cells[bf.d] = b
+ bf.cells[bf.d] = input[0]
}
case '[':
if bf.cells[bf.d] == 0 {
@@ -107,8 +100,8 @@ func (bf *Brainfuck) eval() error {
}
// Dump interpreter state to caller.
-func (bf *Brainfuck) Dump() (int, []byte, int, []byte, []byte) {
- return bf.d, bf.cells[:], bf.i, bf.cmds, bf.out.Bytes()
+func (bf *Brainfuck) Dump() (int, []byte, int, []byte) {
+ return bf.d, bf.cells[:], bf.i, bf.cmds
}
// Reset interpreter state.
@@ -122,3 +115,15 @@ func (bf *Brainfuck) Reset() {
bf.loops = map[int]int{}
bf.parLoops = nil
}
+
+// Run runs a Brainfuck program and returns output bytes or error.
+func Run(cmds []byte, in io.Reader) ([]byte, error) {
+ var out bytes.Buffer
+ bf := New(in, &out)
+ for _, cmd := range cmds {
+ if err := bf.Eval(cmd); err != nil {
+ return nil, err
+ }
+ }
+ return out.Bytes(), nil
+}
diff --git a/bf_test.go b/bf_test.go
@@ -55,21 +55,24 @@ func TestEval(t *testing.T) {
var out bytes.Buffer
bf := New(bytes.NewBuffer([]byte{4, 8, 15, 16, 23, 41}), &out)
- if err := checkInterpreter(bf, 0, 0, nil, nil); err != nil {
+ if err := checkInterpreter(bf, 0, 0, nil); err != nil {
t.Fatalf("[0] Unexpected interpreter state: %v", err)
}
for i, test := range tests {
if err := bf.Eval(test.cmd); err != nil {
- t.Fatalf("[%d] Eval(%q) failed with error: %v", i+1, test.cmd, err)
+ t.Fatalf("[%d] Eval(%q) failed with error: %v", i, test.cmd, err)
}
- if err := checkInterpreter(bf, test.i, test.d, test.cells, test.out); err != nil {
- t.Errorf("[%d] Unexpected interpreter state: %v", i+1, err)
+ if err := checkInterpreter(bf, test.i, test.d, test.cells); err != nil {
+ t.Fatalf("[%d] Unexpected interpreter state: %v", i, err)
+ }
+ if diff := cmp.Diff(test.out, out.Bytes()); diff != "" {
+ t.Fatalf("[%d] output diff (-want +got): %s", i, diff)
}
}
}
-func checkInterpreter(bf *Brainfuck, i, d int, cells map[int]byte, out []byte) error {
+func checkInterpreter(bf *Brainfuck, i, d int, cells map[int]byte) error {
if bf.i != i {
return fmt.Errorf("instruction pointer; got %d, want %d", bf.i, i)
}
@@ -81,17 +84,13 @@ func checkInterpreter(bf *Brainfuck, i, d int, cells map[int]byte, out []byte) e
return fmt.Errorf("cell value at index %d; got %d, want %d", idx, got, want)
}
}
- if diff := cmp.Diff(out, bf.out.Bytes()); diff != "" {
- return fmt.Errorf("output diff (-want +got): %s", diff)
- }
return nil
}
func TestInvalidLoopHandling(t *testing.T) {
- invalidLoops := []string{"]", "[]]", "[][]]"}
- for _, test := range invalidLoops {
- _, err := Run(test, nil)
- if err == nil {
+ tests := []string{"]", "[]]", "[][]]"}
+ for _, test := range tests {
+ if _, err := Run([]byte(test), nil); err == nil {
t.Errorf("Parsed invalid program %q; expected error", test)
}
}
@@ -112,7 +111,7 @@ func TestHelloWorld(t *testing.T) {
--------.
>>>++++[<++++++++>-]<+.`
- out, err := Run(program, nil)
+ out, err := Run([]byte(program), nil)
if err != nil {
t.Fatalf("Run failed: %v", err)
}
diff --git a/cmd/gbfy/main.go b/cmd/gbfy/main.go
@@ -21,49 +21,55 @@ var (
func main() {
flag.Parse()
- var (
- input []byte
- output bytes.Buffer
- )
+ var in, out bytes.Buffer
if *dataFile != "" {
- var err error
- if input, err = os.ReadFile(*dataFile); err != nil {
+ if input, err := os.ReadFile(*dataFile); err != nil {
log.Fatalf("Failed to read input file: %v", err)
+ } else {
+ in = *bytes.NewBuffer(input)
}
}
+ bf := gbfy.New(&in, &out)
- bf := gbfy.New(bytes.NewBuffer(input), &output)
if *codeFile != "" {
if err := runCodeFile(bf); err != nil {
log.Fatalf("Execution failed: %v", err)
}
- } else if err := runPiped(bf); err != nil {
- log.Fatalf("Execution failed: %v", err)
+ log.Printf("Output from execution: %v\n", out.Bytes())
+ } else {
+ if err := runPiped(bf); err != nil {
+ log.Fatalf("Execution failed: %v", err)
+ }
+ log.Printf("Output from execution: %v\n", out.Bytes())
}
if *endInteractive {
log.Println("Starting interactive REPL ...")
- repl(bf)
+ repl(bf, &out)
}
}
const welcomeMsg = `Go Brainfuck Yourself!
- :q[uit] to exit REPL loop
:d[ump] to dump interpreter state
- :f[uck] to reset interpreter state`
+ :f[uck] to reset interpreter state
+ :q[uit] to exit REPL loop`
-func repl(bf *gbfy.Brainfuck) {
+func repl(bf *gbfy.Brainfuck, out *bytes.Buffer) {
fmt.Println(welcomeMsg)
f, err := os.Open("/dev/tty")
if err != nil {
log.Fatalf("Failed to open TTY: %v", err)
}
- in := bufio.NewReader(f)
+ stdIn := bufio.NewReader(f)
+
+ // TODO: Switch interpreter input/output here? Continue
+ // reading from original `input` (i.e. from dataFile)
+ // until EOF -- then switch to TTY?
for {
fmt.Print("gbfy> ")
- line, err := in.ReadBytes('\n')
+ line, err := stdIn.ReadBytes('\n')
if err == io.EOF {
break
} else if err != nil {
@@ -73,7 +79,7 @@ func repl(bf *gbfy.Brainfuck) {
if len(line) > 0 && line[0] == ':' {
switch line[1] {
case 'd':
- formatDump(bf.Dump())
+ formatDump(bf, out.Bytes())
case 'f':
bf.Reset()
fmt.Println("Reset interpreter!")
@@ -98,7 +104,6 @@ func runCodeFile(bf *gbfy.Brainfuck) error {
if err := runCommands(bf, in); err != nil {
return err
}
- logOutput(bf)
return nil
}
@@ -113,7 +118,6 @@ func runPiped(bf *gbfy.Brainfuck) error {
return err
}
}
- logOutput(bf)
return nil
}
@@ -132,13 +136,9 @@ func runCommands(bf *gbfy.Brainfuck, r *bufio.Reader) error {
return nil
}
-func logOutput(bf *gbfy.Brainfuck) {
- _, _, _, _, out := bf.Dump()
- log.Printf("Output from execution: %v\n", out)
-}
-
-func formatDump(d int, cells []byte, i int, cmds []byte, out []byte) {
- fmt.Printf("Cells; d: %d, current cell value: %x\n", d, cells[d])
- fmt.Printf("Cmds: i: %d, current command: %q\n", i, cmds[i-1])
- fmt.Printf("Out: %v\n", out)
+func formatDump(bf *gbfy.Brainfuck, out []byte) {
+ d, cells, i, cmds := bf.Dump()
+ fmt.Printf("CELLS d: %3d, current cell value: %x\n", d, cells[d])
+ fmt.Printf("CMDS i: %3d, current command: %q\n", i, cmds[i-1])
+ fmt.Printf("OUT %v\n", out)
}