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