├── .gitignore ├── go.mod ├── README.md ├── .vscode └── launch.json ├── main.go ├── lexer └── lexer.go ├── parser └── parser.go ├── interpreter └── interpreter.go └── token └── token.go /.gitignore: -------------------------------------------------------------------------------- 1 | testfiles -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kriticalflare/go-brainfuck-yourself 2 | 3 | go 1.22.5 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brainfuck Interpreter in Go 2 | 3 | Brainfuck interpreter implemented in Go. [Brainfuck](http://brainfuck.org/) is an esoteric programming language 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}", 13 | "console": "integratedTerminal" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/kriticalflare/go-brainfuck-yourself/interpreter" 9 | "github.com/kriticalflare/go-brainfuck-yourself/lexer" 10 | "github.com/kriticalflare/go-brainfuck-yourself/parser" 11 | ) 12 | 13 | func main() { 14 | fileContents, err := os.ReadFile("./testfiles/helloworld.b") 15 | // fileContents, err := os.ReadFile("./testfiles/krithik.b") 16 | // fileContents, err := os.ReadFile("./testfiles/bsort.b") 17 | // fileContents, err := os.ReadFile("./testfiles/test2.b") 18 | 19 | source := string(fileContents) 20 | 21 | if err != nil { 22 | log.Fatalf("failed to read source file: %v\n", err) 23 | } 24 | // fmt.Printf("source: %s \n", source) 25 | 26 | l := lexer.New(source) 27 | // fmt.Printf("%s \n", l.Tokens) 28 | p := parser.New(l) 29 | p.ParseProgram() 30 | if p.CheckParserErrors() { 31 | fmt.Printf("Error parsing the file\n") 32 | for _, err := range p.Errors { 33 | fmt.Printf("%s\n", err) 34 | os.Exit(1) 35 | } 36 | } 37 | // fmt.Printf("Parser: \n") 38 | // fmt.Printf("%s\n", p.Tokens) 39 | i := interpreter.New(p.Tokens, 30_000) 40 | i.Run() 41 | } 42 | -------------------------------------------------------------------------------- /lexer/lexer.go: -------------------------------------------------------------------------------- 1 | package lexer 2 | 3 | import "github.com/kriticalflare/go-brainfuck-yourself/token" 4 | 5 | type Lexer struct { 6 | Tokens []token.Token 7 | source string 8 | } 9 | 10 | func New(source string) Lexer { 11 | 12 | l := Lexer{ 13 | Tokens: []token.Token{}, 14 | source: source, 15 | } 16 | l.lex() 17 | return l 18 | } 19 | 20 | func (l *Lexer) lex() { 21 | for idx, char := range l.source { 22 | switch char { 23 | case token.PLUS: 24 | { 25 | l.Tokens = append(l.Tokens, token.NewAdd(idx)) 26 | } 27 | case token.MINUS: 28 | { 29 | l.Tokens = append(l.Tokens, token.NewSubtract(idx)) 30 | } 31 | case token.GT: 32 | { 33 | l.Tokens = append(l.Tokens, token.NewNext(idx)) 34 | } 35 | case token.LT: 36 | { 37 | l.Tokens = append(l.Tokens, token.NewPrev(idx)) 38 | } 39 | case token.LBRACKET: 40 | { 41 | l.Tokens = append(l.Tokens, token.NewJumpZero(idx)) 42 | } 43 | case token.RBRACKET: 44 | { 45 | l.Tokens = append(l.Tokens, token.NewJumpNonZero(idx)) 46 | } 47 | case token.DOT: 48 | { 49 | l.Tokens = append(l.Tokens, token.NewOutput(idx)) 50 | } 51 | case token.COMMA: 52 | { 53 | l.Tokens = append(l.Tokens, token.NewInput(idx)) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kriticalflare/go-brainfuck-yourself/lexer" 7 | "github.com/kriticalflare/go-brainfuck-yourself/token" 8 | ) 9 | 10 | type Parser struct { 11 | Tokens []token.Token 12 | stack []stackElement 13 | Errors []string 14 | } 15 | 16 | type stackElement struct { 17 | token *token.JumpZero 18 | position int 19 | } 20 | 21 | func New(l lexer.Lexer) Parser { 22 | p := Parser{ 23 | Tokens: l.Tokens, 24 | stack: []stackElement{}, 25 | } 26 | return p 27 | } 28 | 29 | func (p *Parser) CheckParserErrors() bool { 30 | return len(p.Errors) != 0 31 | } 32 | 33 | func (p *Parser) ParseProgram() { 34 | for idx, tok := range p.Tokens { 35 | switch t := tok.(type) { 36 | case *token.JumpZero: 37 | { 38 | p.stack = append(p.stack, stackElement{token: t, position: idx}) 39 | } 40 | case *token.JumpNonZero: 41 | { 42 | stackLen := len(p.stack) 43 | if stackLen == 0 { 44 | p.Errors = append(p.Errors, fmt.Sprintf("char:%d Matching [ not found\n", t.SourcePosition())) 45 | continue 46 | } 47 | 48 | top := p.stack[stackLen-1] 49 | jz := top.token 50 | jz.Target = uint(idx) 51 | t.Target = uint(top.position) 52 | 53 | p.stack = p.stack[:len(p.stack)-1] 54 | } 55 | } 56 | } 57 | 58 | for _, ele := range p.stack { 59 | p.Errors = append(p.Errors, fmt.Sprintf("char:%d Matching ] not found\n", ele.token.SourcePosition())) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /interpreter/interpreter.go: -------------------------------------------------------------------------------- 1 | package interpreter 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | 10 | "github.com/kriticalflare/go-brainfuck-yourself/token" 11 | ) 12 | 13 | type Interpreter struct { 14 | Memory []uint8 15 | ptr int 16 | program []token.Token 17 | reader *bufio.Reader 18 | } 19 | 20 | func New(program []token.Token, memorySize uint) *Interpreter { 21 | reader := bufio.NewReader(os.Stdin) 22 | return &Interpreter{Memory: make([]uint8, memorySize), ptr: 0, program: program, reader: reader} 23 | } 24 | 25 | func (i *Interpreter) Run() { 26 | // fmt.Printf("memory: %v\n", i.Memory) 27 | commandIdx := 0 28 | for { 29 | if commandIdx >= len(i.program) { 30 | break 31 | } 32 | tok := i.program[commandIdx] 33 | 34 | switch t := tok.(type) { 35 | case *token.Add: 36 | { 37 | i.Memory[i.ptr]++ 38 | } 39 | case *token.Subtract: 40 | { 41 | i.Memory[i.ptr]-- 42 | } 43 | case *token.Next: 44 | { 45 | i.ptr++ 46 | } 47 | case *token.Prev: 48 | { 49 | i.ptr-- 50 | } 51 | case *token.JumpZero: 52 | { 53 | if i.Memory[i.ptr] == 0 { 54 | commandIdx = int(t.Target) 55 | } 56 | } 57 | case *token.JumpNonZero: 58 | { 59 | if i.Memory[i.ptr] != 0 { 60 | commandIdx = int(t.Target) 61 | } 62 | } 63 | case *token.Output: 64 | { 65 | fmt.Printf("%c", i.Memory[i.ptr]) 66 | } 67 | case *token.Input: 68 | { 69 | // fmt.Printf("waiting for input %v\n", commandIdx) 70 | input, err := i.reader.ReadByte() 71 | if errors.Is(err, io.EOF) { 72 | fmt.Printf("EOF\n") 73 | i.Memory[i.ptr] = 0 74 | continue 75 | } 76 | if err != nil { 77 | fmt.Printf("failed to read input:%d. Error: %v\n", t.SourcePosition(), err) 78 | } 79 | if input == 13 { 80 | // skip carriage return in windows 81 | input, err = i.reader.ReadByte() 82 | } 83 | if err != nil { 84 | fmt.Printf("failed to read input:%d. Error: %v\n", t.SourcePosition(), err) 85 | } 86 | // fmt.Printf("read '%d'\n", input) 87 | i.Memory[i.ptr] = input 88 | } 89 | } 90 | commandIdx++ 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | type TokenKind rune 4 | 5 | const ( 6 | PLUS = '+' 7 | MINUS = '-' 8 | GT = '>' 9 | LT = '<' 10 | LBRACKET = '[' 11 | RBRACKET = ']' 12 | DOT = '.' 13 | COMMA = ',' 14 | ) 15 | 16 | type Token interface { 17 | Kind() TokenKind 18 | SourcePosition() uint 19 | } 20 | 21 | type Add struct { 22 | sourcePosition uint 23 | } 24 | 25 | func NewAdd(sourcePosition int) *Add { 26 | return &Add{sourcePosition: uint(sourcePosition)} 27 | } 28 | 29 | func (a *Add) Kind() TokenKind { 30 | return PLUS 31 | } 32 | 33 | func (a *Add) SourcePosition() uint { 34 | return a.sourcePosition 35 | } 36 | 37 | type Subtract struct { 38 | sourcePosition uint 39 | } 40 | 41 | func NewSubtract(sourcePosition int) *Subtract { 42 | return &Subtract{sourcePosition: uint(sourcePosition)} 43 | } 44 | 45 | func (s *Subtract) Kind() TokenKind { 46 | return MINUS 47 | } 48 | 49 | func (s *Subtract) SourcePosition() uint { 50 | return s.sourcePosition 51 | } 52 | 53 | type Next struct { 54 | sourcePosition uint 55 | } 56 | 57 | func NewNext(sourcePosition int) *Next { 58 | return &Next{sourcePosition: uint(sourcePosition)} 59 | } 60 | 61 | func (n *Next) Kind() TokenKind { 62 | return LT 63 | } 64 | 65 | func (n *Next) SourcePosition() uint { 66 | return n.sourcePosition 67 | } 68 | 69 | type Prev struct { 70 | sourcePosition uint 71 | } 72 | 73 | func NewPrev(sourcePosition int) *Prev { 74 | return &Prev{sourcePosition: uint(sourcePosition)} 75 | } 76 | 77 | func (p *Prev) Kind() TokenKind { 78 | return MINUS 79 | } 80 | 81 | func (p *Prev) SourcePosition() uint { 82 | return p.sourcePosition 83 | } 84 | 85 | type JumpZero struct { 86 | Target uint 87 | sourcePosition uint 88 | } 89 | 90 | func NewJumpZero(sourcePosition int) *JumpZero { 91 | return &JumpZero{sourcePosition: uint(sourcePosition)} 92 | } 93 | 94 | func (jz *JumpZero) Kind() TokenKind { 95 | return LBRACKET 96 | } 97 | 98 | func (jz *JumpZero) SourcePosition() uint { 99 | return jz.sourcePosition 100 | } 101 | 102 | type JumpNonZero struct { 103 | Target uint 104 | sourcePosition uint 105 | } 106 | 107 | func NewJumpNonZero(sourcePosition int) *JumpNonZero { 108 | return &JumpNonZero{sourcePosition: uint(sourcePosition)} 109 | } 110 | 111 | func (jnz *JumpNonZero) Kind() TokenKind { 112 | return RBRACKET 113 | } 114 | 115 | func (jnz *JumpNonZero) SourcePosition() uint { 116 | return jnz.sourcePosition 117 | } 118 | 119 | type Output struct { 120 | sourcePosition uint 121 | } 122 | 123 | func NewOutput(sourcePosition int) *Output { 124 | return &Output{sourcePosition: uint(sourcePosition)} 125 | } 126 | 127 | func (o *Output) Kind() TokenKind { 128 | return DOT 129 | } 130 | 131 | func (o *Output) SourcePosition() uint { 132 | return o.sourcePosition 133 | } 134 | 135 | type Input struct { 136 | sourcePosition uint 137 | } 138 | 139 | func NewInput(sourcePosition int) *Input { 140 | return &Input{sourcePosition: uint(sourcePosition)} 141 | } 142 | 143 | func (i *Input) Kind() TokenKind { 144 | return COMMA 145 | } 146 | 147 | func (i *Input) SourcePosition() uint { 148 | return i.sourcePosition 149 | } 150 | --------------------------------------------------------------------------------