diff --git a/cmd/brainfuck/main.go b/cmd/brainfuck/main.go new file mode 100644 index 0000000..90a7026 --- /dev/null +++ b/cmd/brainfuck/main.go @@ -0,0 +1,67 @@ +// Copyright © 2021 Rak Laptudirm +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "os" + + "laptudirm.com/x/brainfuck/pkg/lexer" + "laptudirm.com/x/brainfuck/pkg/parser" + "laptudirm.com/x/brainfuck/pkg/token" + "laptudirm.com/x/brainfuck/pkg/vm" +) + +const Memory = 30000 + +func main() { + if len(os.Args) != 2 { + fmt.Fprintln(os.Stderr, "usage: brainfuck [filename]") + os.Exit(1) + } + + filename := os.Args[1] + file, err := os.ReadFile(filename) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } + + var l lexer.Lexer + l.Init(string(file), handleErr) + + var p parser.Parser + p.Init(&l, handleErr) + + program := p.ParseProgram() + + if l.ErrorCount > 0 || p.ErrorCount > 0 { + os.Exit(1) + } + + m := vm.New(Memory) + err = m.Execute(program) + if err != nil { + printErr(err.Error()) + os.Exit(1) + } +} + +func handleErr(p token.Position, e string) { + printErr("%s: %v\n", p, e) +} + +func printErr(format string, a ...interface{}) error { + _, err := fmt.Fprintf(os.Stderr, format, a...) + return err +} diff --git a/go.mod b/go.mod index 0406944..d539a3c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/raklaptudirm/brainfuck +module laptudirm.com/x/brainfuck go 1.16 diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go index 0461d10..11bbcf5 100644 --- a/pkg/ast/ast.go +++ b/pkg/ast/ast.go @@ -1,6 +1,19 @@ +// Copyright © 2021 Rak Laptudirm +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package ast -import "github.com/raklaptudirm/brainfuck/pkg/token" +import "laptudirm.com/x/brainfuck/pkg/token" type Node interface { TokenLiteral() string @@ -45,12 +58,12 @@ func (o *Operator) TokenLiteral() string { func (o *Operator) operationNode() {} type Loop struct { - Operators []Operation + Operations []Operation } func (l *Loop) TokenLiteral() string { s := "[" - for _, op := range l.Operators { + for _, op := range l.Operations { s += op.TokenLiteral() } diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go index 7decfdb..4c862b1 100644 --- a/pkg/lexer/lexer.go +++ b/pkg/lexer/lexer.go @@ -1,9 +1,22 @@ +// Copyright © 2021 Rak Laptudirm +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package lexer import ( "unicode/utf8" - "github.com/raklaptudirm/brainfuck/pkg/token" + "laptudirm.com/x/brainfuck/pkg/token" ) type Lexer struct { diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index eda6644..18c7a0b 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -1,9 +1,22 @@ +// Copyright © 2021 Rak Laptudirm +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package parser import ( - "github.com/raklaptudirm/brainfuck/pkg/ast" - "github.com/raklaptudirm/brainfuck/pkg/lexer" - "github.com/raklaptudirm/brainfuck/pkg/token" + "laptudirm.com/x/brainfuck/pkg/ast" + "laptudirm.com/x/brainfuck/pkg/lexer" + "laptudirm.com/x/brainfuck/pkg/token" ) type Parser struct { @@ -54,7 +67,7 @@ func (p *Parser) parseLoop() *ast.Loop { for p.next(); p.tok != token.ELOOP && p.tok != token.EOF; p.next() { op := p.parseOperation() - loop.Operators = append(loop.Operators, op) + loop.Operations = append(loop.Operations, op) } if p.tok == token.EOF { diff --git a/pkg/token/position.go b/pkg/token/position.go index 75a6685..f88ae62 100644 --- a/pkg/token/position.go +++ b/pkg/token/position.go @@ -1,3 +1,16 @@ +// Copyright © 2021 Rak Laptudirm +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package token import "fmt" @@ -7,7 +20,7 @@ type Position struct { Col int } -func (p *Position) String() string { +func (p Position) String() string { return fmt.Sprintf("%v:%v", p.Line, p.Col) } diff --git a/pkg/token/token.go b/pkg/token/token.go index e2a7170..fe6d925 100644 --- a/pkg/token/token.go +++ b/pkg/token/token.go @@ -1,3 +1,16 @@ +// Copyright © 2021 Rak Laptudirm +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package token import "strconv" diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go new file mode 100644 index 0000000..3e91c15 --- /dev/null +++ b/pkg/vm/vm.go @@ -0,0 +1,98 @@ +// Copyright © 2021 Rak Laptudirm +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package vm + +import ( + "errors" + "fmt" + + "laptudirm.com/x/brainfuck/pkg/ast" + "laptudirm.com/x/brainfuck/pkg/token" +) + +type Machine struct { + Length int + Memory []byte + Pointer int +} + +func New(l int) *Machine { + return &Machine{ + Length: l, + Memory: make([]byte, l), + } +} + +var ErrAst = errors.New("invalid ast structure") + +func (m *Machine) Execute(n ast.Node) error { + switch v := n.(type) { + case *ast.Program: + return m.executeOperations(v.Operations) + case *ast.Comment: + return nil // ignore comments + case *ast.Loop: + if m.Memory[m.Pointer] == 0 { + return nil + } + + for { + err := m.executeOperations(v.Operations) + if err != nil { + return err + } + + if m.Memory[m.Pointer] == 0 { + return nil + } + } + case *ast.Operator: + return m.executeOperator(v) + default: + return ErrAst + } +} + +func (m *Machine) executeOperations(operations []ast.Operation) error { + for _, operation := range operations { + err := m.Execute(operation) + if err != nil { + return err + } + } + + return nil +} + +func (m *Machine) executeOperator(o *ast.Operator) error { + switch o.Token { + case token.INC_VAL: + m.Memory[m.Pointer]++ + case token.DEC_VAL: + m.Memory[m.Pointer]-- + case token.INC_PTR: + m.Pointer++ + case token.DEC_PTR: + m.Pointer-- + case token.INPUT: + _, err := fmt.Scanf("%c", &m.Memory[m.Pointer]) + return err + case token.PRINT: + fmt.Print(string(m.Memory[m.Pointer])) + default: + return fmt.Errorf("invalid operator %s", o.Token) + } + + return nil +}