gir/gir.js

165 lines
4.3 KiB
JavaScript

'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);
}