Compare commits

...

3 Commits

Author SHA1 Message Date
Juhani Krekelä 2b6b6a96fb Add offsets to commands 2018-05-22 15:08:23 +03:00
Juhani Krekelä 39890cdc5a Turn [-] or [+] into a clear command 2018-05-22 14:26:40 +03:00
Juhani Krekelä b945657163 Fix typo 2018-05-22 14:22:21 +03:00
2 changed files with 144 additions and 7 deletions

View File

@ -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
View File

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