From 2cbc9be4e3e4441470374e282fb80c60288df9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Mon, 21 May 2018 23:55:48 +0300 Subject: [PATCH] First commit --- .gitignore | 1 + README.md | 9 +++ UNLICENSE | 24 ++++++++ gir.html | 11 ++++ gir.js | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 209 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 UNLICENSE create mode 100644 gir.html create mode 100644 gir.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..e343869 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +Gir is an optimizing brainfuck interpreter in Javascript. + +Name +---- +𒌋𒀜 gir (Sumerian) - n. kiln + +License +------- +Everything in the repo is under Unlicense / CC0. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..69843e4 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to [http://unlicense.org] diff --git a/gir.html b/gir.html new file mode 100644 index 0000000..1638181 --- /dev/null +++ b/gir.html @@ -0,0 +1,11 @@ + + + + Gir testbench + + + +

Open the JavaScript console

+ + + diff --git a/gir.js b/gir.js new file mode 100644 index 0000000..7c24ac1 --- /dev/null +++ b/gir.js @@ -0,0 +1,164 @@ +'use strict'; + +// Use symbols for the names of the instructions +// Unsure if this helps in any way over strings, but I feel it neater + +// +++++ → {type: add, value: 5} +const add = Symbol('add'); +// > → {type: moveHead, value: 1} +const moveHead = Symbol('moveHead'); +// . → {type: writeByte} +const writeByte = Symbol('writeByte'); +// , → {type: readByte} +const readByte = Symbol('readByte'); +// [-] → {type: loop, contents: [{type: add, value: -1}]} +const loop = Symbol('loop'); + +// TODO: Add extensions from Eldis + +class ParsingError extends Error {} + +// (string) → [commandObjects] +// May throw +function parse(program) { + // (string, int, bool) → {parsed: [commandObjects], lastIndex: int} + // index is the index of the next character to consume + // inLoop tells whether we're parsing a loop or the top level + // lastIndex is the last index the function consumed + function constructTree(program, index = 0, inLoop = false) { + let commands = []; + + // Move this out of the loop body since we need to return + // the index of the last character we parsed + let i = index; + for(;;) { + if(i >= program.length) { + // If we're parsing a loop, we have a + // missing ] + // If we're parsing the top level, this is + // where we should exit + if(inLoop) { + throw new ParsingError('Missing ]'); + } else { + break; + } + i++; + + } else if(program[i] == ']') { + // If we're parsing a loop, this is where we + // should exit + // If we're parsing the top level, we have a + // missing [ + if(inLoop) { + break; + } else { + throw new ParsingError('Missing ['); + } + i++; + + } else if(program[i] == '+' || program[i] == '-') { + // Fold a run of +s and -s into one node + let value = 0; + for(; i < program.length; i++) { + if(program[i] == '+') { + value++; + } else if(program[i] == '-') { + value--; + } else { + // Reached end of the run + break; + } + } + + commands.push({type: add, value: value}); + // i is not incremented, since it already + // points to a location containig a char we + // have not yet handled + + } else if(program[i] == '<' || program[i] == '>') { + // Fold a run of s into one node + let value = 0; + for(; i < program.length; i++) { + if(program[i] == '>') { + value++; + } else if(program[i] == '<') { + value--; + } else { + // Reached end of the run + break; + } + } + + commands.push({type: moveHead, value: value}); + // see +/- for why we don't increment i + + } else if(program[i] == '.') { + commands.push({type: writeByte}); + i++; + + } else if(program[i] == ',') { + commands.push({type: readByte}); + i++; + + } else if(program[i] == '[') { + // Parse a loop. This is done by calling the + // same parser function recursively + // Due to this the loop appears as one node + // in the parsed result + let {parsed, lastIndex} = constructTree( + program, // Same program data + i + 1, // Start at the next char + true // We're parsing a loop + ); + commands.push({type: loop, contents: parsed}); + // Since lastIndex was consumed by the inner + // function, we don't want to consume it a + // second time + i = lastIndex + 1; + } else { + // All others characters are comments, + // ignore them + i++; + } + } + + return {parsed: commands, lastIndex: i}; + } + + // We only care about the parsed contents, since under normal + // operarion we only get out of the loop if we've reached the end + // of the program + let {parsed} = constructTree(program); + return parsed; +} + +// ([commandObjects]) +function prettyPrint(parsed) { + // ([commandObjects], string) + function printIndented(parsed, indent = '') { + for(let command of parsed) { + let line = indent; + if(command.type == add) { + line += `add ${command.value}`; + console.log(line); + } else if(command.type == moveHead) { + line += `moveHead ${command.value}`; + console.log(line); + } else if(command.type == writeByte) { + line += 'writeByte'; + console.log(line); + } else if(command.type == readByte) { + line += 'readByte'; + console.log(line); + } else if(command.type == loop) { + line += 'loop'; + console.log(line); + printIndented(command.contents, indent + ' '); + } else { + line += `unknown ${command.type}`; + console.log(line); + } + } + } + printIndented(parsed); +}