Compare commits
3 commits
61e836afcf
...
2b6b6a96fb
Author | SHA1 | Date | |
---|---|---|---|
|
2b6b6a96fb | ||
|
39890cdc5a | ||
|
b945657163 |
2 changed files with 144 additions and 7 deletions
|
@ -13,4 +13,6 @@ Status
|
|||
Gir can parse and prettyprint to Javascript console programs in brainfuck.
|
||||
Gir supports following optimizations:
|
||||
|
||||
* Turn runs or +- or <> into one command
|
||||
* Turn runs of +- or <> into one command
|
||||
* Turn [-] or [+] into one command
|
||||
* Add offsets to commands that modify tape, to reduce moving tape head
|
||||
|
|
147
gir.js
147
gir.js
|
@ -8,16 +8,24 @@
|
|||
// 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
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
@ -78,7 +86,10 @@ function parse(program) {
|
|||
}
|
||||
}
|
||||
|
||||
commands.push({type: add, value: value});
|
||||
// Only add the command is value is not 0
|
||||
if(value != 0) {
|
||||
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
|
||||
|
@ -97,7 +108,10 @@ function parse(program) {
|
|||
}
|
||||
}
|
||||
|
||||
commands.push({type: moveHead, value: value});
|
||||
// Only add the command is value is not 0
|
||||
if(value != 0) {
|
||||
commands.push({type: moveHead, value: value});
|
||||
}
|
||||
// see +/- for why we don't increment i
|
||||
|
||||
} else if(program[i] == '.') {
|
||||
|
@ -140,28 +154,46 @@ function parse(program) {
|
|||
return parsed;
|
||||
}
|
||||
|
||||
// ([commandObjects]) <io>
|
||||
// ([commandObjects/offsetCommandObjects]) <io>
|
||||
function prettyPrint(parsed) {
|
||||
// ([commandObjects], string) <io>
|
||||
// ([commandObjects/offsetCommandObjects], string) <io>
|
||||
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}`;
|
||||
console.log(line);
|
||||
|
@ -175,6 +207,8 @@ function prettyPrint(parsed) {
|
|||
// Optimization passes
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
class UnknownIRError extends Error {}
|
||||
|
||||
// ([commandObjects]) → [commandObjects]
|
||||
function joinAdjacentOps(parsed) {
|
||||
// ([commandObjects], commandType) → [commandObjects]
|
||||
|
@ -219,6 +253,107 @@ function joinAdjacentOps(parsed) {
|
|||
}
|
||||
|
||||
// ([commandObjects]) → [commandObjects]
|
||||
function optimize(parsed) {
|
||||
return joinAdjacentOps(parsed);
|
||||
function transformClearLoops(parsed) {
|
||||
let optimized = [];
|
||||
|
||||
for(let command of parsed) {
|
||||
// Only match loops like [-] or [+]
|
||||
let isClearLoop = command.type == loop &&
|
||||
command.contents.length == 1 &&
|
||||
command.contents[0].type == add &&
|
||||
(command.contents[0].value == 1 ||
|
||||
command.contents[0].value == -1);
|
||||
if(isClearLoop) {
|
||||
optimized.push({type: clear});
|
||||
} else if(command.type == loop) {
|
||||
// Run for inner loops
|
||||
optimized.push({type: loop,
|
||||
contents: transformClearLoops(command.contents)});
|
||||
} else {
|
||||
optimized.push(command);
|
||||
}
|
||||
}
|
||||
|
||||
return optimized;
|
||||
}
|
||||
|
||||
// ([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,
|
||||
addOffsetProperties
|
||||
]
|
||||
return optimizations.reduce((IR, optimization) =>
|
||||
optimization(IR), parsed);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue