First commit

This commit is contained in:
Juhani Krekelä 2018-05-21 23:55:48 +03:00
commit 2cbc9be4e3
5 changed files with 209 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.swp

9
README.md Normal file
View File

@ -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.

24
UNLICENSE Normal file
View File

@ -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]

11
gir.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Gir testbench</title>
<script type="text/javascript" src="gir.js"></script>
</head>
<html>
<p>Open the JavaScript console</p>
<!-- TODO: Implement some kind of html interface -->
</html>
</html>

164
gir.js Normal file
View File

@ -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 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: 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]) <io>
function prettyPrint(parsed) {
// ([commandObjects], string) <io>
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);
}