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

main.go (3120B)


      1 package main
      2 
      3 import (
      4 	"bufio"
      5 	"bytes"
      6 	"flag"
      7 	"fmt"
      8 	"io"
      9 	"log"
     10 	"os"
     11 
     12 	"github.com/abtrout/gbfy"
     13 )
     14 
     15 var (
     16 	codeFile   = flag.String("f", "", "Path to Brainfuck program to execute")
     17 	dataFile   = flag.String("d", "", "Path to file containing input data for program")
     18 	launchRepl = flag.Bool("i", false, "Launch interactive interpreter before exit")
     19 )
     20 
     21 func main() {
     22 	flag.Parse()
     23 
     24 	var in, out bytes.Buffer
     25 	if *dataFile != "" {
     26 		if input, err := os.ReadFile(*dataFile); err != nil {
     27 			log.Fatalf("Failed to read input file: %v", err)
     28 		} else {
     29 			in = *bytes.NewBuffer(input)
     30 		}
     31 	}
     32 	bf := gbfy.New(&in, &out)
     33 
     34 	if *codeFile != "" {
     35 		if err := runCodeFile(bf); err != nil {
     36 			log.Fatalf("Execution failed: %v", err)
     37 		}
     38 	} else if err := runPiped(bf); err != nil {
     39 		log.Fatalf("Execution failed: %v", err)
     40 	}
     41 
     42 	if *launchRepl {
     43 		log.Println("Starting interactive REPL ...")
     44 		repl(bf, &in, &out)
     45 	} else {
     46 		log.Printf("Output from execution: %v\n", out.Bytes())
     47 	}
     48 }
     49 
     50 const welcomeMsg = `Go Brainfuck Yourself!
     51  :d[ump] to dump interpreter state
     52  :f[uck] to reset interpreter state
     53  :q[uit] to exit REPL loop`
     54 
     55 func repl(bf *gbfy.Brainfuck, in, out *bytes.Buffer) {
     56 	fmt.Println(welcomeMsg)
     57 
     58 	f, err := os.Open("/dev/tty")
     59 	if err != nil {
     60 		log.Fatalf("Failed to open TTY: %v", err)
     61 	}
     62 	stdIn := bufio.NewReader(f)
     63 
     64 	// TODO: Switch interpreter input/output here? Continue
     65 	//       reading from original `input` (i.e. from dataFile)
     66 	//       until EOF -- then switch to TTY?
     67 
     68 	for {
     69 		fmt.Print("gbfy> ")
     70 		line, err := stdIn.ReadBytes('\n')
     71 		if err == io.EOF {
     72 			break
     73 		} else if err != nil {
     74 			log.Fatalf("Error! %v", err)
     75 		}
     76 
     77 		if len(line) > 0 && line[0] == ':' {
     78 			switch line[1] {
     79 			case 'd':
     80 				formatDump(bf, out.Bytes())
     81 			case 'f':
     82 				out.Reset() // reset output buffer!
     83 				bf = gbfy.New(in, out)
     84 				fmt.Println("Reset interpreter!")
     85 			case 'q':
     86 				break
     87 			}
     88 		} else {
     89 			r := bufio.NewReader(bytes.NewReader(line))
     90 			if err := runCommands(bf, r); err != nil {
     91 				log.Fatalf("Execution failed: %v", err)
     92 			}
     93 		}
     94 	}
     95 }
     96 
     97 func runCodeFile(bf *gbfy.Brainfuck) error {
     98 	f, err := os.Open(*codeFile)
     99 	if err != nil {
    100 		return err
    101 	}
    102 	in := bufio.NewReader(f)
    103 	if err := runCommands(bf, in); err != nil {
    104 		return err
    105 	}
    106 	return nil
    107 }
    108 
    109 func runPiped(bf *gbfy.Brainfuck) error {
    110 	stat, err := os.Stdin.Stat()
    111 	if err != nil {
    112 		return err
    113 	}
    114 	if (stat.Mode() & os.ModeCharDevice) == 0 {
    115 		in := bufio.NewReader(os.Stdin)
    116 		if err := runCommands(bf, in); err != nil {
    117 			return err
    118 		}
    119 	}
    120 	return nil
    121 }
    122 
    123 func runCommands(bf *gbfy.Brainfuck, r *bufio.Reader) error {
    124 	for {
    125 		cmds, err := r.ReadString('\n')
    126 		if err == io.EOF {
    127 			break
    128 		} else if err != nil {
    129 			return fmt.Errorf("failed to ReadByte: %v", err)
    130 		}
    131 		if err := bf.Eval(cmds); err != nil {
    132 			return fmt.Errorf("failed to Eval %q: %v", cmds, err)
    133 		}
    134 	}
    135 	return nil
    136 }
    137 
    138 func formatDump(bf *gbfy.Brainfuck, out []byte) {
    139 	d, cells, i, cmds := bf.Dump()
    140 	fmt.Printf("CELLS d: %3d, current cell value: %x\n", d, cells[d])
    141 	fmt.Printf("CMDS  i: %3d, current command: %q\n", i, cmds[i-1])
    142 	fmt.Printf("OUT   %v\n", out)
    143 }