'use strict'; // ------------------------------------------------------------------ // Definitions // ------------------------------------------------------------------ // 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 // ------------------------------------------------------------------ // Parsing // ------------------------------------------------------------------ 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); } // ------------------------------------------------------------------ // Optimization passes // ------------------------------------------------------------------ // ([commandObjects]) → [commandObjects] function joinAdjacentOps(parsed) { // ([commandObjects], commandType) → [commandObjects] function worker(parsed, type) { let optimized = []; let prevOfType = false, value = 0; for(let command of parsed) { if(prevOfType && command.type == type) { // Update value, don't add to optimized yet value += command.value; } else if(!prevOfType && command.type == type) { // Start of a possible run of commands prevOfType = true; value = command.value; } else if(prevOfType && command.type != type) { // A run has ended, add it to optimized // However, skip it if value is 0 if(value != 0) { optimized.push({type: type, value: value}); } // Also add the command for this round optimized.push(command); prevOfType = false; } else { optimized.push(command); prevOfType = false; } } // Did we end with a command of given type if(prevOfType) { // Yes, add it to optimized (unless value is 0) if(value != 0) { optimized.push({type: type, value: value}); } } return optimized; } return worker(worker(parsed, moveHead), add); } // ([commandObjects]) → [commandObjects] function optimize(parsed) { return joinAdjacentOps(parsed); }