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

commit 64d851fab8598d35c4a98bf549d376d8cafabe91
parent 2c51bba3346c3d9d86bf58f85c9ee7a1861adf82
Author: david cochran <about.trout@gmail.com>
Date:   Tue, 24 Jan 2023 04:25:35 +0000

fixes #2 - add gbfy CLI with REPL interface

Diffstat:
Acmd/gbfy/main.go | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgbfy.go | 24++++++++++++++++++++++++
2 files changed, 113 insertions(+), 0 deletions(-)

diff --git a/cmd/gbfy/main.go b/cmd/gbfy/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "log" + "os" + + "github.com/abtrout/gbfy" +) + +var ( + codeFile = flag.String("f", "", "Path to Brainfuck file to interpret") + inputFile = flag.String("i", "", "Path to file containing input data for program") + launchREPL = flag.Bool("repl", false, "Launch interactive interpreter before exit") +) + +func main() { + flag.Parse() + + // Read contents of codeFile and inputFile to get commands + // to execute and any input data the program may expect. + var ( + err error + cmds []byte + input []byte + ) + if *codeFile != "" { + cmds, err = os.ReadFile(*codeFile) + if err != nil { + log.Fatalf("Failed to read code file: %v", err) + } + } + if *inputFile != "" { + input, err = os.ReadFile(*inputFile) + if err != nil { + log.Fatalf("Failed to read input file: %v", err) + } + } + + bf, err := gbfy.New(string(cmds), input) + if err != nil { + log.Fatalf("Failed to initialize interpreter: %v", err) + } + + output, err := bf.Run() + if err != nil { + log.Printf("Run failed with error: %v", err) + } + + log.Printf("Output from execution: %q\n", output) + + // Drop user into interactive "REPL" if requested. Execution + // may continue in the REPL with whatever interpreter state + // state the above program left it in. + if *launchREPL { + log.Println("... starting interactive REPL") + beInteractive(bf) + } +} + +const welcomeMsg = `Welcome! + :q[uit] to exit REPL loop + :d[ump] to dump interpreter state + :f[uck] to reset interpreter state` + +func beInteractive(bf *gbfy.Brainfuck) { + fmt.Println(welcomeMsg) + in := bufio.NewReader(os.Stdin) + for { + fmt.Print("gbfy> ") + line, _ := in.ReadString('\n') + if line[0] == ':' { + // Handle REPL commands. + switch line[1] { + case 'q': // exit REPL loop. + return + case 'd': // dump state to stdout. + fmt.Println(bf.String()) + case 'f': // reset interpreter. + bf.Reset() + fmt.Println("Restarted interpreter...") + } + } else { + // Handle a Brainfuck command(s). + } + } +} diff --git a/gbfy.go b/gbfy.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "strings" ) // Brainfuck is the interpreter state. @@ -42,6 +43,29 @@ func (bf *Brainfuck) Run() ([]byte, error) { return bf.out.Bytes(), nil } +// String dumps the Brainfuck interpreter state for debugging. +func (bf *Brainfuck) String() string { + var s strings.Builder + s.WriteString(fmt.Sprintf("pointers: [d=%d i=%d]\n", bf.d, bf.i)) + s.WriteString("non-zero cells: {\n") + // TODO: Better cell printing. + for i, c := range bf.cells { + if c != 0 { + s.WriteString(fmt.Sprintf(" %d: %d\n", i, c)) + } + } + s.WriteString("}") + return s.String() +} + +// Reset the interpreter state to defaults. +func (bf *Brainfuck) Reset() { + bf.cells = [3e4]byte{} + bf.i = 0 + bf.d = 0 + bf.cmds = nil +} + // eval evaluates a single Brainfuck command. func (bf *Brainfuck) eval(cmd byte) error { switch cmd {