diff --git a/README.md b/README.md index 28e6907..5883ed3 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,4 @@ Gir supports following optimizations: * Turn runs of +- or <> into one command * Turn [-] or [+] into one command +* Add offsets to commands that modify tape, to reduce moving tape head diff --git a/gir.js b/gir.js index 4e58a69..7163ecc 100644 --- a/gir.js +++ b/gir.js @@ -8,17 +8,22 @@ // Unsure if this helps in any way over strings, but I feel it neater // +++++ → {type: add, value: 5} +// Can have offset property const add = Symbol('add'); // > → {type: moveHead, value: 1} const moveHead = Symbol('moveHead'); // . → {type: writeByte} +// Can have offset property const writeByte = Symbol('writeByte'); // , → {type: readByte} +// Can have offset property const readByte = Symbol('readByte'); // [-] → {type: loop, contents: [{type: add, value: -1}]} +// Can have isBalanced property const loop = Symbol('loop'); // [-] → {type: clear} +// Can have offset property const clear = Symbol('clear'); // TODO: Add extensions from Eldis @@ -149,30 +154,45 @@ function parse(program) { return parsed; } -// ([commandObjects]) +// ([commandObjects/offsetCommandObjects]) function prettyPrint(parsed) { - // ([commandObjects], string) + // ([commandObjects/offsetCommandObjects], string) function printIndented(parsed, indent = '') { for(let command of parsed) { let line = indent; if(command.type == add) { line += `add ${command.value}`; + if('offset' in command) { + line += ` (${command.offset})`; + } console.log(line); } else if(command.type == moveHead) { line += `moveHead ${command.value}`; console.log(line); } else if(command.type == writeByte) { line += 'writeByte'; + if('offset' in command) { + line += ` (${command.offset})`; + } console.log(line); } else if(command.type == readByte) { line += 'readByte'; + if('offset' in command) { + line += ` (${command.offset})`; + } console.log(line); } else if(command.type == loop) { line += 'loop'; + if('isBalanced' in command) { + line += ` (balanced: ${command.isBalanced})`; + } console.log(line); printIndented(command.contents, indent + ' '); } else if(command.type == clear) { line += 'clear'; + if('offset' in command) { + line += ` (${command.offset})`; + } console.log(line); } else { line += `unknown ${command.type}`; @@ -187,6 +207,8 @@ function prettyPrint(parsed) { // Optimization passes // ------------------------------------------------------------------ +class UnknownIRError extends Error {} + // ([commandObjects]) → [commandObjects] function joinAdjacentOps(parsed) { // ([commandObjects], commandType) → [commandObjects] @@ -255,9 +277,83 @@ function transformClearLoops(parsed) { return optimized; } -// ([commandObjects]) → [commandObjects] +// ([commandObjects]) → [offsetCommandObjects] +function addOffsetProperties(parsed) { + // ([commandObjects]) → {offsetted: [offsetCommandObjects], isBalanced: bool} + function worker(parsed) { + let offsetted = []; + let isBalanced = true; + let headChange = 0; + + let offset = 0; + for(let command of parsed) { + if(command.type == add) { + offsetted.push({type: add, + value: command.value, + offset: offset}); + } else if(command.type == moveHead) { + offset += command.value; + } else if(command.type == writeByte) { + offsetted.push({type: writeByte, + offset: offset}); + } else if(command.type == readByte) { + offsetted.push({type: readByte, + offset: offset}); + } else if(command.type == clear) { + offsetted.push({type: clear, + offset: offset}); + } else if(command.type == loop) { + // A loop should be self-contained + // If offset is not 0, add a moveHead + if(offset != 0) { + offsetted.push({type: moveHead, + value: offset}); + // Mark we've moved the head + headChange += offset; + } + offset = 0; + + // Run optimization on the loop + let result = worker(command.contents); + // We're only balanced if our loops are + isBalanced = isBalanced && result.isBalanced; + offsetted.push({type: loop, + contents: result.offsetted, + isBalanced: result.isBalanced}); + // headChange's value becomes invalid if the + // loop is not balanced. However, we only + // care about its value when figuring out + // our isBalanced, which will be forced to + // false if any inner loop is not balanced + } else { + throw new UnknownIRError(); + } + } + + // We need to move the tape head in the end anyways, so + // generate moveHead is offseet is not 0 + if(offset != 0) { + offsetted.push({type: moveHead, + value: offset}); + } + + // We're only balanced if relative to start of the loop ends + // up as 0 + isBalanced = isBalanced && offset + headChange == 0; + + return {offsetted, isBalanced}; + } + + return worker(parsed).offsetted; +} + +// ([commandObjects]) → [offsetCommandObjects] function optimize(parsed) { - const optimizations = [joinAdjacentOps, transformClearLoops]; + const optimizations = [ + joinAdjacentOps, + transformClearLoops, + addOffsetProperties + ] return optimizations.reduce((IR, optimization) => optimization(IR), parsed); }