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:
| A | cmd/gbfy/main.go | | | 89 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | gbfy.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 {