From 0efe241f3ec38f2b404410522e5f0ab6de4d825a Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Tue, 1 Feb 2022 21:43:02 +0530 Subject: [PATCH 01/13] chore: clean slate --- .github/workflows/codeql-analysis.yml | 71 ---------- .gitignore | 24 +++- LICENSE | 195 +++++++++++++++++++++++--- README.md | 48 ------- cmd/brainfuck/main.go | 84 ----------- pkg/errors/errors.go | 36 ----- pkg/errors/types/types.go | 18 --- pkg/help/help.go | 27 ---- pkg/parser/parser.go | 128 ----------------- pkg/vm/types/types.go | 8 -- pkg/vm/vm.go | 81 ----------- pkg/vm/wrappers.go | 27 ---- 12 files changed, 193 insertions(+), 554 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 README.md delete mode 100644 cmd/brainfuck/main.go delete mode 100644 pkg/errors/errors.go delete mode 100644 pkg/errors/types/types.go delete mode 100644 pkg/help/help.go delete mode 100644 pkg/parser/parser.go delete mode 100644 pkg/vm/types/types.go delete mode 100644 pkg/vm/vm.go delete mode 100644 pkg/vm/wrappers.go diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index fad9f6b..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '30 22 * * 2' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index ae4955d..3cd2452 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,21 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib -test* -build/ -*.diff +# Test binary, built with `go test -c` +*.test -# Under development: -repl/ \ No newline at end of file +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories +vendor/ + +# Go workspace file +go.work + +# Ignore binary directory +bin/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 036e5b0..dd5b3a5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,174 @@ -MIT License - -Copyright (c) 2021 Rak Laptudirm - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/README.md b/README.md deleted file mode 100644 index 9ab86e2..0000000 --- a/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# brainfuck -A Go-based Brainfuck interpreter and REPL. - -# Installation -```bash -$ git clone https://github.com/raklaptudirm/brainfuck.git -$ cd brainfuck -$ go build -``` -Add the generated executable file to your `$PATH`. - -# Usage: -## Help: -Get help about the cli app. -``` -$ brainfuck -``` -## Command help: -Get help about a specific command. -``` -$ brainfuck help -``` -## Start the `REPL`: -Start the `brainfuck` repl provided by the cli. -``` -$ brainfuck repl [flags] -``` -## Run a `brainfuck` file: -Parse and execute a `brainfuck` file. -``` -$ brainfuck run [flags] -``` -## Test a `brainfuck` file: -Parse a `brainfuck` file and check for errors. -``` -$ brainfuck test [flags] -``` -## Transpile a `brainfuck` file: `Not implemented` -``` -$ brainfuck transpile -``` -### Supported languages: -- `Javascript` -- `Python` -- `C` -- `C++` -- `Go` -- `Rust` diff --git a/cmd/brainfuck/main.go b/cmd/brainfuck/main.go deleted file mode 100644 index f597b33..0000000 --- a/cmd/brainfuck/main.go +++ /dev/null @@ -1,84 +0,0 @@ -// brainfuck -// https://github.com/raklaptudirm/brainfuck -// Copyright (c) 2021 Rak Laptudirm. -// Licensed under the MIT license. - -// Brainfuck is a cli compiler and repl -// for the brainfuck language. -// -// Usage: -// -// brainfuck [flags] -// -// The commands are: -// run run a brainfuck file -// test test a brainfuck file -// repl start the brainfuck repl -// -// Example: -// -// Run a simple hello world program from -// the examples directory. -// -// $ brainfuck run examples/hello_world.bf -// Hello World! -// -// Test a brainfuck file for parse errors. -// -// $ brainfuck test examples/hello_world.bf -// examples/hello_world.bf: No errors found. -// -package main - -import ( - "fmt" - "io/ioutil" - "os" - - "github.com/raklaptudirm/brainfuck/pkg/help" - - "github.com/raklaptudirm/brainfuck/pkg/errors" - "github.com/raklaptudirm/brainfuck/pkg/parser" - "github.com/raklaptudirm/brainfuck/pkg/vm" -) - -func main() { - brainfuck := vm.Default - args := os.Args[1:] - - // assert function to check wether the expected number - // of arguments was received or not. - assert := func(length int) { - if len(args) != length+1 { - fmt.Printf("Expected %v arg(s), received %v.", length, len(args)-1) - os.Exit(0) - } - } - - if len(args) == 0 { - fmt.Println(help.Default) - } else { - // args[0] contains the command name. - switch args[0] { - case "run": - assert(1) - brainfuck.RunFile(args[1], os.Stdout) - case "help": - assert(1) - help.Get(args[1]) - case "repl": - fmt.Println("The new REPL is a work in progress.") - case "test": - assert(1) - file, fileError := ioutil.ReadFile(args[1]) - errors.StrictCheck(fileError) - - _, parseError, _ := parser.Parse(string(file)) - errors.StrictCheck(parseError) - - fmt.Printf("%v: No errors found.", args[1]) - default: - fmt.Printf("error: unknown command %v.", args[0]) - } - } -} diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go deleted file mode 100644 index 1ac174e..0000000 --- a/pkg/errors/errors.go +++ /dev/null @@ -1,36 +0,0 @@ -// brainfuck -// https://github.com/raklaptudirm/brainfuck -// Copyright (c) 2021 Rak Laptudirm. -// Licensed under the MIT license. - -// Package errors provides functions to detect errors. -// -// The following methods are used to detect, print and -// exit when an error occurs. -// -package errors - -import ( - "fmt" - "os" -) - -// StrictCheck checks in an error has occurred in -// the argument, and exits if it has. -func StrictCheck(e error) { - if e != nil { - fmt.Print(e) - os.Exit(0) - } -} - -// Check checks if an error has occurred or not, -// and returns true if it has not. -func Check(e error) bool { - if e != nil { - fmt.Println(e) - return false - } - - return true -} diff --git a/pkg/errors/types/types.go b/pkg/errors/types/types.go deleted file mode 100644 index 9c36404..0000000 --- a/pkg/errors/types/types.go +++ /dev/null @@ -1,18 +0,0 @@ -/* -Package types provides types for error codes. -*/ -package types - -// ErrorCode is the datatype for brainfuck -// parse error codes. -type ErrorCode uint8 - -// Error codes returned by the parser on -// a parsing error. -const ( - NO_ERROR ErrorCode = iota - LOOP_UNCLOSED - LOOP_UNOPNED - MEM_OUT_OF_RANGE - MEM_ROLL_OVER -) diff --git a/pkg/help/help.go b/pkg/help/help.go deleted file mode 100644 index a5792bb..0000000 --- a/pkg/help/help.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Package help provides help string for all commands. -*/ -package help - -import ( - "fmt" -) - -// Default is the the default help string, -// when no command name is provided. -const Default string = "Usage: brainfuck [flags]\n\nThe commands are:\n run run a brainfuck file\n test test a brainfuck file\n repl start the brainfuck repl\n\nUse \"brainfuck help \" for more information about a command." - -// Get returns the help string for the given command, -// and prints an error if it does not exist. -func Get(query string) { - switch query { - case "run": - fmt.Println("Usage: brinfuck run [flags]\n\nRuns a brainfuck file.\nIt tries to parse the file and,\nif successful, runs it.\n\nFlags:\n to be added") - case "test": - fmt.Println("Usage: brainfuck test [flags]\n\nTests if a file is parsable.\nIt tries to parse the file, and then exits.\n\nFlags:\n to be added") - case "repl": - fmt.Println("Usage: brainfuck repl [flags]\n\nStarts the brainfuck repl.\n\nFlags:\n to be added") - default: - fmt.Printf("error: unknown command %v.", query) - } -} diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go deleted file mode 100644 index b595240..0000000 --- a/pkg/parser/parser.go +++ /dev/null @@ -1,128 +0,0 @@ -// brainfuck -// https://github.com/raklaptudirm/brainfuck -// Copyright (c) 2021 Rak Laptudirm. -// Licensed under the MIT license. - -// Package parser provides parsing functions for a brainfuck source. -// -// The provided method and types is used for parsing a brainfuck source -// string into an Instruction slice. -// -package parser - -import ( - "errors" - "fmt" - - . "github.com/raklaptudirm/brainfuck/pkg/errors/types" -) - -// Instruction type represents a single brainfuck instuction -type Instruction uint8 - -// LoopIndexes type represents the index of a loop, -// utilized to improve runtime speeds. -type LoopIndexes int - -// Instructions representing brainfuck commands. -const ( - GO_LEFT Instruction = iota - GO_RIGHT - INCREMENT - DECREMENT - INPUT - OUTPUT - LOOP_START - LOOP_END -) - -type Bytecode struct { - Instructions []Instruction - Indexes []LoopIndexes -} - -// Parse parses the given brainfuck source, -// and returns the instruction codes, loop indexes, -//and errors(if any) found in the source string. -func Parse(code string) (Bytecode, error, ErrorCode) { - var parseError error = nil - var errorCode ErrorCode = NO_ERROR - - bytecode := []Instruction{} - indexes := []LoopIndexes{} - loops := []LoopIndexes{} - - length := len(code) - lines := 1 - column := 0 - - last := func(item []LoopIndexes) int { - return len(item) - 1 - } - - elements := func() int { - return len(bytecode) - 1 - } - - for i := 0; i < length; i += 1 { - column += 1 - - indexes = append(indexes, 0) - - switch string(code[i]) { - case "<": - bytecode = append(bytecode, GO_LEFT) - case ">": - bytecode = append(bytecode, GO_RIGHT) - case ".": - bytecode = append(bytecode, OUTPUT) - case ",": - bytecode = append(bytecode, INPUT) - case "+": - bytecode = append(bytecode, INCREMENT) - case "-": - bytecode = append(bytecode, DECREMENT) - case "[": - bytecode = append(bytecode, LOOP_START) - - // The loops start's index is the number of elements in bytecode - 1 - // since the command has already been pushed into the array. - // The index is converted to parser/types.LoopIndexes, - // and appended to the loops array. - loops = append(loops, LoopIndexes(elements())) - case "]": - if len(loops) == 0 { - parseError = errors.New(fmt.Sprintf("error %v:%v : Illeagal \"]\".", lines, column)) - errorCode = LOOP_UNOPNED - } else { - bytecode = append(bytecode, LOOP_END) - - // The index of the starting brace of this loop, - // will be the last element of loops - loopStart := loops[last(loops)] - - // Switch the indexes of the starting and ending brace, - // and add them to indexes. - indexes[loopStart] = LoopIndexes(elements()) - indexes[elements()] = loopStart - - loops = loops[:last(loops)] - } - case "\n": - lines += 1 - column = 0 - indexes = indexes[:last(indexes)] - default: - indexes = indexes[:last(indexes)] - } - } - - if len(loops) != 0 { - parseError = errors.New(fmt.Sprintf("error: %v unclosed \"[\".", len(loops))) - errorCode = LOOP_UNCLOSED - } - - ret := Bytecode{Instructions: bytecode, Indexes: indexes} - - return ret, parseError, errorCode -} diff --git a/pkg/vm/types/types.go b/pkg/vm/types/types.go deleted file mode 100644 index 643e332..0000000 --- a/pkg/vm/types/types.go +++ /dev/null @@ -1,8 +0,0 @@ -/* -Package types provides data used by the vm. -*/ -package types - -// TAPE_LENGTH represents the number of memory cells -// present in the VM struct's memory. -const TAPE_LENGTH uint16 = 30000 diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go deleted file mode 100644 index d7a650c..0000000 --- a/pkg/vm/vm.go +++ /dev/null @@ -1,81 +0,0 @@ -// brainfuck -// https://github.com/raklaptudirm/brainfuck -// Copyright (c) 2021 Rak Laptudirm. -// Licensed under the MIT license. - -// Package vm provides methods and structures to run -// parsed brainfuck code. It uses the Instruction -// slice received from parsing the source and runs -// each instruction individually. -// -package vm - -import ( - "fmt" - "io" - - "github.com/raklaptudirm/brainfuck/pkg/parser" - . "github.com/raklaptudirm/brainfuck/pkg/vm/types" -) - -// VM struct to store memory information during runtime. -type VM struct { - DataPointer uint16 // Index of the currently active cell. - Memory [TAPE_LENGTH]byte // The array of cells. -} - -// Default represents the default configuration for a VM. -var Default VM = VM{DataPointer: 0, Memory: [TAPE_LENGTH]byte{}} - -func (vm *VM) execute(out io.Writer, instruction parser.Instruction) { - switch instruction { - case parser.GO_LEFT: - // If statement for roll over if pointer is at 0. - if vm.DataPointer == 0 { - vm.DataPointer = TAPE_LENGTH - 1 - } else { - vm.DataPointer -= 1 - } - case parser.GO_RIGHT: - // If statement for roll over if pointer is at TAPE_LENGTH. - if vm.DataPointer == TAPE_LENGTH-1 { - vm.DataPointer = 0 - } else { - vm.DataPointer += 1 - } - case parser.INPUT: - _, _ = fmt.Scanf("%c", &vm.Memory[vm.DataPointer]) - case parser.OUTPUT: - fmt.Fprint(out, string(vm.Memory[vm.DataPointer])) - case parser.INCREMENT: - vm.Memory[vm.DataPointer] += 1 - case parser.DECREMENT: - vm.Memory[vm.DataPointer] -= 1 - } -} - -// RunCode runs a parsed brainfuck source, consisting of -// the command array and the loop index array. The output -// is written to the provided io.Writer. -func (vm *VM) RunCode(out io.Writer, bytecode parser.Bytecode) { - instructions := bytecode.Instructions - indexes := bytecode.Indexes - length := len(instructions) - - for i := 0; i < length; i += 1 { - switch instructions[i] { - case parser.LOOP_START: - if vm.Memory[vm.DataPointer] == 0 { - // Get end brace index from indexes array. - i = int(indexes[i]) - } - case parser.LOOP_END: - if vm.Memory[vm.DataPointer] != 0 { - // Get sart brace index from indexes array. - i = int(indexes[i]) - } - default: - vm.execute(out, instructions[i]) - } - } -} diff --git a/pkg/vm/wrappers.go b/pkg/vm/wrappers.go deleted file mode 100644 index 2b71502..0000000 --- a/pkg/vm/wrappers.go +++ /dev/null @@ -1,27 +0,0 @@ -package vm - -import ( - "io" - "io/ioutil" - - "github.com/raklaptudirm/brainfuck/pkg/errors" - "github.com/raklaptudirm/brainfuck/pkg/parser" -) - -// RunString runs a given brainfuck source string, -// by parsing it and then using VM.RunCode. -func (vm *VM) RunString(str string, out io.Writer) { - bytecode, parseError, _ := parser.Parse(str) - errors.StrictCheck(parseError) - - vm.RunCode(out, bytecode) -} - -// RunFile runs a brainfuck source file, by reading it, -// parsing it, and then using VM.RunCode. -func (vm *VM) RunFile(fileName string, out io.Writer) { - file, fileError := ioutil.ReadFile(fileName) - errors.StrictCheck(fileError) - - vm.RunString(string(file), out) -} From a9048a6138ea0d03af7726cb312ad55cdb208fd4 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Tue, 1 Feb 2022 22:24:02 +0530 Subject: [PATCH 02/13] feat: add language tokens --- pkg/tokens/tokens.go | 63 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 pkg/tokens/tokens.go diff --git a/pkg/tokens/tokens.go b/pkg/tokens/tokens.go new file mode 100644 index 0000000..e2a7170 --- /dev/null +++ b/pkg/tokens/tokens.go @@ -0,0 +1,63 @@ +package token + +import "strconv" + +type Token int + +const ( + // special tokens + ILLEGAL Token = iota + COMMENT + EOF + + // value operators + INC_VAL // + + DEC_VAL // - + + // memory address + INC_PTR // > + DEC_PTR // < + + // looping + SLOOP // [ + ELOOP // ] + + // io operators + INPUT // , + PRINT // . +) + +var tokens = [...]string{ + ILLEGAL: "ILLEGAL", + COMMENT: "COMMENT", + EOF: "EOF", + + INC_VAL: "+", + DEC_VAL: "-", + + INC_PTR: ">", + DEC_PTR: "<", + + SLOOP: "[", + ELOOP: "]", + + INPUT: ",", + PRINT: ".", +} + +// String returns the string corresponding to the token tok. +// For operators, delimiters, and keywords the string is the actual +// token character sequence (e.g., for the token ADD, the string is +// "+"). For all other tokens the string corresponds to the token +// constant name (e.g. for the token IDENT, the string is "IDENT"). +// +func (tok Token) String() string { + s := "" + if 0 <= tok && tok < Token(len(tokens)) { + s = tokens[tok] + } + if s == "" { + s = "token(" + strconv.Itoa(int(tok)) + ")" + } + return s +} From f2d35fea4d04dd1a687416acf1c48ba1d5aa8bc2 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Tue, 1 Feb 2022 22:29:44 +0530 Subject: [PATCH 03/13] chore: rename tokens to token --- pkg/{tokens/tokens.go => token/token.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/{tokens/tokens.go => token/token.go} (100%) diff --git a/pkg/tokens/tokens.go b/pkg/token/token.go similarity index 100% rename from pkg/tokens/tokens.go rename to pkg/token/token.go From 3d67428ca2e384da3e3015dd2191294ede64c52d Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 11:07:44 +0530 Subject: [PATCH 04/13] feat: working lexer --- pkg/lexer/lexer.go | 123 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 pkg/lexer/lexer.go diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go new file mode 100644 index 0000000..ea9cbd7 --- /dev/null +++ b/pkg/lexer/lexer.go @@ -0,0 +1,123 @@ +package lexer + +import ( + "unicode/utf8" + + "github.com/raklaptudirm/brainfuck/pkg/token" +) + +type lexer struct { + src string + ch rune + + offset int + rdOffset int + + line int + col int +} + +const eof = 0 + +func (l *lexer) Next() (pos int, tok token.Token, lit string) { + switch l.peek() { + case eof: + l.consume() + tok = token.EOF + case '+': + l.consume() + tok = token.INC_VAL + case '-': + l.consume() + tok = token.DEC_VAL + case '>': + l.consume() + tok = token.INC_PTR + case '<': + l.consume() + tok = token.DEC_PTR + case ',': + l.consume() + tok = token.INPUT + case '.': + l.consume() + tok = token.PRINT + case '[': + l.consume() + tok = token.SLOOP + case ']': + l.consume() + tok = token.ELOOP + default: + tok = l.lexComment() + } + + pos = l.offset + lit = l.src[l.offset:l.rdOffset] + + l.offset = l.rdOffset + return +} + +func (l *lexer) lexComment() token.Token { + for ch := l.peek(); !isOperator(ch) && !l.atEnd(); ch = l.peek() { + l.consume() + } + + return token.COMMENT +} + +func (l *lexer) consume() { + if l.ch == '\n' { + l.line++ + l.col = 0 + } + + if l.atEnd() { + l.ch = eof + return + } + + r, w := rune(l.src[l.rdOffset]), 1 + if r >= utf8.RuneSelf { + r, w = utf8.DecodeRuneInString(l.src[l.rdOffset:]) + } + + l.ch = r + + l.rdOffset += w + l.col += w +} + +func (l *lexer) peek() byte { + if l.atEnd() { + return eof + } + + return l.src[l.rdOffset] +} + +func (l *lexer) atEnd() bool { + return l.rdOffset >= len(l.src) +} + +func New(s string) *lexer { + return &lexer{ + src: s, + + offset: 0, + rdOffset: 0, + + line: 0, + col: 0, + } +} + +func isOperator(b byte) bool { + switch b { + case '+', '-', '>', '<', '[', ']', ',', '.': + return true + default: + return false + } +} From f346c5b58a7092b0f23c594860b1483a0a56fe2f Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 11:54:36 +0530 Subject: [PATCH 05/13] feat: add error handeling to lexer --- pkg/lexer/lexer.go | 76 +++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go index ea9cbd7..144781b 100644 --- a/pkg/lexer/lexer.go +++ b/pkg/lexer/lexer.go @@ -1,15 +1,18 @@ package lexer import ( + "fmt" "unicode/utf8" "github.com/raklaptudirm/brainfuck/pkg/token" ) -type lexer struct { +type Lexer struct { src string ch rune + err ErrorHandler + offset int rdOffset int @@ -17,9 +20,23 @@ type lexer struct { col int } -const eof = 0 +const ( + eof = -1 // end of file + bom = 0xFEFF // byte order mark +) + +func (l *Lexer) Init(src string, handler ErrorHandler) { + l.src = src + l.err = handler + + l.offset = 0 + l.rdOffset = 0 + + l.line = 0 + l.col = 0 +} -func (l *lexer) Next() (pos int, tok token.Token, lit string) { +func (l *Lexer) Next() (pos int, tok token.Token, lit string) { switch l.peek() { case eof: l.consume() @@ -59,7 +76,7 @@ func (l *lexer) Next() (pos int, tok token.Token, lit string) { return } -func (l *lexer) lexComment() token.Token { +func (l *Lexer) lexComment() token.Token { for ch := l.peek(); !isOperator(ch) && !l.atEnd(); ch = l.peek() { l.consume() } @@ -67,7 +84,7 @@ func (l *lexer) lexComment() token.Token { return token.COMMENT } -func (l *lexer) consume() { +func (l *Lexer) consume() { if l.ch == '\n' { l.line++ l.col = 0 @@ -79,42 +96,59 @@ func (l *lexer) consume() { } r, w := rune(l.src[l.rdOffset]), 1 + if r == 0 { + l.error("illegal character NUL") + goto advance + } + if r >= utf8.RuneSelf { r, w = utf8.DecodeRuneInString(l.src[l.rdOffset:]) + + if r == utf8.RuneError && w == 1 { + l.error("illegal UTF-8 encoding") + goto advance + } + + if r == bom && l.offset > 0 { + l.error("illegal byte order mark") + goto advance + } } +advance: l.ch = r l.rdOffset += w l.col += w } -func (l *lexer) peek() byte { +func (l *Lexer) error(err string) { + if l.err != nil { + l.err(l.line, l.col, err) + } +} + +func (l *Lexer) errorf(format string, a ...interface{}) { + err := fmt.Sprintf(format, a...) + l.error(err) +} + +func (l *Lexer) peek() rune { if l.atEnd() { return eof } - return l.src[l.rdOffset] + return rune(l.src[l.rdOffset]) } -func (l *lexer) atEnd() bool { +func (l *Lexer) atEnd() bool { return l.rdOffset >= len(l.src) } -func New(s string) *lexer { - return &lexer{ - src: s, - - offset: 0, - rdOffset: 0, - - line: 0, - col: 0, - } -} +type ErrorHandler func(int, int, string) -func isOperator(b byte) bool { - switch b { +func isOperator(r rune) bool { + switch r { case '+', '-', '>', '<', '[', ']', ',', '.': return true default: From c26ef9ae3a890f79ed90f9a5e15500da466064d9 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 12:26:20 +0530 Subject: [PATCH 06/13] chore: wrap line and col into single position struct --- pkg/lexer/lexer.go | 30 +++++++++++++++--------------- pkg/token/position.go | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 pkg/token/position.go diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go index 144781b..04bda47 100644 --- a/pkg/lexer/lexer.go +++ b/pkg/lexer/lexer.go @@ -16,8 +16,7 @@ type Lexer struct { offset int rdOffset int - line int - col int + pos token.Position } const ( @@ -32,11 +31,15 @@ func (l *Lexer) Init(src string, handler ErrorHandler) { l.offset = 0 l.rdOffset = 0 - l.line = 0 - l.col = 0 + l.pos = token.Position{ + Line: 1, + Col: 1, + } } -func (l *Lexer) Next() (pos int, tok token.Token, lit string) { +func (l *Lexer) Next() (pos token.Position, tok token.Token, lit string) { + pos = l.pos + switch l.peek() { case eof: l.consume() @@ -69,9 +72,7 @@ func (l *Lexer) Next() (pos int, tok token.Token, lit string) { tok = l.lexComment() } - pos = l.offset lit = l.src[l.offset:l.rdOffset] - l.offset = l.rdOffset return } @@ -85,11 +86,6 @@ func (l *Lexer) lexComment() token.Token { } func (l *Lexer) consume() { - if l.ch == '\n' { - l.line++ - l.col = 0 - } - if l.atEnd() { l.ch = eof return @@ -119,12 +115,16 @@ advance: l.ch = r l.rdOffset += w - l.col += w + l.pos.Col += w + + if r == '\n' { + l.pos.NextLine() + } } func (l *Lexer) error(err string) { if l.err != nil { - l.err(l.line, l.col, err) + l.err(l.pos, err) } } @@ -145,7 +145,7 @@ func (l *Lexer) atEnd() bool { return l.rdOffset >= len(l.src) } -type ErrorHandler func(int, int, string) +type ErrorHandler func(token.Position, string) func isOperator(r rune) bool { switch r { diff --git a/pkg/token/position.go b/pkg/token/position.go new file mode 100644 index 0000000..75a6685 --- /dev/null +++ b/pkg/token/position.go @@ -0,0 +1,17 @@ +package token + +import "fmt" + +type Position struct { + Line int + Col int +} + +func (p *Position) String() string { + return fmt.Sprintf("%v:%v", p.Line, p.Col) +} + +func (p *Position) NextLine() { + p.Line++ + p.Col = 1 +} From fa504550dcea4d119f7be6082a5b77c1847f0ace Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 12:28:47 +0530 Subject: [PATCH 07/13] chore: remove unused lexer.errorf --- pkg/lexer/lexer.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go index 04bda47..748d3fe 100644 --- a/pkg/lexer/lexer.go +++ b/pkg/lexer/lexer.go @@ -1,7 +1,6 @@ package lexer import ( - "fmt" "unicode/utf8" "github.com/raklaptudirm/brainfuck/pkg/token" @@ -128,11 +127,6 @@ func (l *Lexer) error(err string) { } } -func (l *Lexer) errorf(format string, a ...interface{}) { - err := fmt.Sprintf(format, a...) - l.error(err) -} - func (l *Lexer) peek() rune { if l.atEnd() { return eof From 85c4434846a21bb652c0775677e8b7125754262d Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 13:16:02 +0530 Subject: [PATCH 08/13] chore: add ast structs and interfaces --- pkg/ast/ast.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pkg/ast/ast.go diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go new file mode 100644 index 0000000..773ad29 --- /dev/null +++ b/pkg/ast/ast.go @@ -0,0 +1,38 @@ +package ast + +import "github.com/raklaptudirm/brainfuck/pkg/token" + +type Node interface { + TokenLiteral() string +} + +type Program struct { + Operations []Operation +} + +func (p *Program) TokenLiteral() string { + return "program" +} + +type Operation interface { + Node + operationNode() +} + +type Operator token.Token + +func (o *Operator) TokenLiteral() string { + return token.Token(*o).String() +} + +func (o *Operator) operationNode() {} + +type Loop struct { + Operators []Operation +} + +func (l *Loop) TokenLiteral() string { + return "loop" +} + +func (l *Loop) operationNode() {} From e54f53efecdfe74745ee6fd5ee8b33c04fb566da Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 13:39:05 +0530 Subject: [PATCH 09/13] chore: add comment struct to ast --- pkg/ast/ast.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go index 773ad29..f5b07ad 100644 --- a/pkg/ast/ast.go +++ b/pkg/ast/ast.go @@ -19,10 +19,22 @@ type Operation interface { operationNode() } -type Operator token.Token +type Comment struct { + Literal string +} + +func (c *Comment) TokenLiteral() string { + return c.Literal +} + +func (c *Comment) operationNode() {} + +type Operator struct { + Token token.Token +} func (o *Operator) TokenLiteral() string { - return token.Token(*o).String() + return o.Token.String() } func (o *Operator) operationNode() {} From 3ac00950d9303fa8fde18f8e0baf9d4951d9dcca Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 14:19:17 +0530 Subject: [PATCH 10/13] chore: make TokenLiteral funcs better --- pkg/ast/ast.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go index f5b07ad..0461d10 100644 --- a/pkg/ast/ast.go +++ b/pkg/ast/ast.go @@ -11,7 +11,12 @@ type Program struct { } func (p *Program) TokenLiteral() string { - return "program" + s := "" + for _, op := range p.Operations { + s += op.TokenLiteral() + } + + return s } type Operation interface { @@ -44,7 +49,12 @@ type Loop struct { } func (l *Loop) TokenLiteral() string { - return "loop" + s := "[" + for _, op := range l.Operators { + s += op.TokenLiteral() + } + + return s + "]" } func (l *Loop) operationNode() {} From 91d15b77e67264dc5fef9efe2f3af678d5cbc1c7 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 14:27:28 +0530 Subject: [PATCH 11/13] chore: add ErrorCount to lexer --- pkg/lexer/lexer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go index 748d3fe..68d156d 100644 --- a/pkg/lexer/lexer.go +++ b/pkg/lexer/lexer.go @@ -16,6 +16,8 @@ type Lexer struct { rdOffset int pos token.Position + + ErrorCount int } const ( @@ -122,6 +124,7 @@ advance: } func (l *Lexer) error(err string) { + l.ErrorCount++ if l.err != nil { l.err(l.pos, err) } From fbd3d652fc0179345de040c8a183736d3286d938 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 14:30:18 +0530 Subject: [PATCH 12/13] feat: parser for brainfuck --- pkg/parser/parser.go | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 pkg/parser/parser.go diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go new file mode 100644 index 0000000..eda6644 --- /dev/null +++ b/pkg/parser/parser.go @@ -0,0 +1,76 @@ +package parser + +import ( + "github.com/raklaptudirm/brainfuck/pkg/ast" + "github.com/raklaptudirm/brainfuck/pkg/lexer" + "github.com/raklaptudirm/brainfuck/pkg/token" +) + +type Parser struct { + l *lexer.Lexer + + err lexer.ErrorHandler + + tok token.Token + pos token.Position + lit string + + ErrorCount int +} + +func (p *Parser) Init(l *lexer.Lexer, err lexer.ErrorHandler) { + p.l = l + p.err = err +} + +func (p *Parser) ParseProgram() *ast.Program { + program := &ast.Program{} + + for p.next(); p.tok != token.EOF; p.next() { + op := p.parseOperation() + program.Operations = append(program.Operations, op) + } + + return program +} + +func (p *Parser) parseOperation() ast.Operation { + switch p.tok { + case token.COMMENT: + return &ast.Comment{Literal: p.lit} + case token.SLOOP: + return p.parseLoop() + case token.ELOOP: + p.error(p.pos, "unexpected ]") + return nil + default: + return &ast.Operator{Token: p.tok} + } +} + +func (p *Parser) parseLoop() *ast.Loop { + loop := &ast.Loop{} + pos := p.pos + + for p.next(); p.tok != token.ELOOP && p.tok != token.EOF; p.next() { + op := p.parseOperation() + loop.Operators = append(loop.Operators, op) + } + + if p.tok == token.EOF { + p.error(pos, `unexpected EOF, expected ]`) + } + + return loop +} + +func (p *Parser) error(pos token.Position, err string) { + p.ErrorCount++ + if p.err != nil { + p.err(pos, err) + } +} + +func (p *Parser) next() { + p.pos, p.tok, p.lit = p.l.Next() +} From d754b804bc42e19442887b4f089aa20483b235f2 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 2 Feb 2022 14:40:39 +0530 Subject: [PATCH 13/13] chore: refactor lexer --- pkg/lexer/lexer.go | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go index 68d156d..7decfdb 100644 --- a/pkg/lexer/lexer.go +++ b/pkg/lexer/lexer.go @@ -41,33 +41,24 @@ func (l *Lexer) Init(src string, handler ErrorHandler) { func (l *Lexer) Next() (pos token.Position, tok token.Token, lit string) { pos = l.pos - switch l.peek() { + switch l.consume(); l.ch { case eof: - l.consume() tok = token.EOF case '+': - l.consume() tok = token.INC_VAL case '-': - l.consume() tok = token.DEC_VAL case '>': - l.consume() tok = token.INC_PTR case '<': - l.consume() tok = token.DEC_PTR case ',': - l.consume() tok = token.INPUT case '.': - l.consume() tok = token.PRINT case '[': - l.consume() tok = token.SLOOP case ']': - l.consume() tok = token.ELOOP default: tok = l.lexComment() @@ -98,18 +89,19 @@ func (l *Lexer) consume() { goto advance } - if r >= utf8.RuneSelf { - r, w = utf8.DecodeRuneInString(l.src[l.rdOffset:]) + if r < utf8.RuneSelf { + goto advance + } - if r == utf8.RuneError && w == 1 { - l.error("illegal UTF-8 encoding") - goto advance - } + r, w = utf8.DecodeRuneInString(l.src[l.rdOffset:]) + + if r == utf8.RuneError && w == 1 { + l.error("illegal UTF-8 encoding") + goto advance + } - if r == bom && l.offset > 0 { - l.error("illegal byte order mark") - goto advance - } + if r == bom && l.offset > 0 { + l.error("illegal byte order mark") } advance: