unfuck
Version:
A brainfuck to Javascript compiler with optimizations.
970 lines (791 loc) • 24.3 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["unfuck"] = factory();
else
root["unfuck"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // identity function for calling harmony imports with the correct context
/******/ __webpack_require__.i = function(value) { return value; };
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 10);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
;
var langStandard = __webpack_require__(11);
function analyse(settings, program) {
var ast = [];
var i = 0;
while (i < program.length) {
var ins = program[i];
var action = langStandard[ins];
if (action !== undefined) {
i = action(settings, i, program, ast);
} else {
i++;
};
}
return ast;
}
module.exports = analyse;
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
;
/**
* -- Unfuck Compilation Target: `interactive-es6` --
*/
module.exports = {
output: function output(parent) {
return {
/**
* Increment/Decrement current cell by `body`.
*/
SFT: function SFT(settings, ins, program) {
if (ins.body > 0) program.push("t[p]+=" + ins.body);else if (ins.body < 0) program.push("t[p]-=" + Math.abs(ins.body));else return;
},
/**
* Increment/Decrement a relativly-specified cell by `body.value`.
*/
RELSFT: function RELSFT(settings, ins, program) {
var partOne = '';
var partTwo = '';
if (ins.body.move > 0) partOne = 't[p+' + ins.body.move + ']';else if (ins.body.move < 0) partOne = 't[p-' + Math.abs(ins.body.move) + ']';else partOne = 't[p]';
if (ins.body.value > 0) partTwo = "+=" + ins.body.value;else if (ins.body.value < 0) partTwo = "-=" + Math.abs(ins.body.value);
program.push(partOne + partTwo);
},
/**
* Set current cell to `body`.
*/
SET: function SET(settings, ins, program) {
program.push("t[p]=" + ins.body);
},
/**
* Set a relativly-specified cell to `body.value`.
*/
RELSET: function RELSET(settings, ins, program) {
var part = '';
if (ins.body.move > 0) part = 't[p+' + ins.body.move + ']';else if (ins.body.move < 0) part = 't[p-' + Math.abs(ins.body.move) + ']';else part = 't[p]';
program.push(part + "=" + ins.body.value);
},
/**
* Multiply current cell by factors, and add to cells.
*/
MUL: function MUL(settings, ins, program) {
ins.body.factors.forEach(function (factor) {
var part = '';
if (factor.move > 0) part = 't[p+' + factor.move + ']';else if (factor.move < 0) part = 't[p-' + Math.abs(factor.move) + ']';else part = 't[p]';
if (factor.factor === 1) program.push(part + '+=t[p]');else program.push(part + '+=t[p]*' + factor.factor);
});
program.push('t[p]=0');
},
/**
* Use the output function to post current cell
*/
OUT: function OUT(settings, ins, program) {
program.push("o(t[p])");
},
/**
* Use the input function to query for input
*/
INP: function INP(settings, ins, program) {
program.push("t[p]=i(t[p])");
},
/**
* Move the current cell left or right by `body`.
*/
MOV: function MOV(settings, ins, program) {
if (ins.body > 0) program.push("p+=" + ins.body);else if (ins.body < 0) program.push("p-=" + Math.abs(ins.body));else return;
},
/**
* Conditionally loop `body` if current cell is not 0 and the begining of each loop.
* Skip entirly if current cell is 0.
*/
IF: function IF(settings, ins, program) {
program.push("while(t[p]!=0){" + parent(settings, ins.body).join(';') + "}");
}
};
},
context: function context(settings) {
var preHeader = '';
switch (settings.in) {
case String:
preHeader += 'var i=(c)=>{let x=iFn(c); return (x ? x.charCodeAt(0) : 0)};';
break;
case Number:
preHeader += 'var i=iFn;';
break;
}
switch (settings.out) {
case String:
preHeader += 'var o=(c)=>oFn(String.fromCharCode(c));';
break;
case Number:
preHeader += 'var o=oFn;';
break;
}
var params = ['iFn', 'oFn'];
if (settings.type == Array) {
var type = "Array(" + settings.width + ").fill(0)";
} else {
var type = settings.type.name + "(" + settings.width + ")";
}
var header = "(function(" + params.join(',') + "){" + preHeader + "var t=new " + type + ";var p=0;";
var footer = "})";
return { header: header, footer: footer, lineEnding: ';' };
}
};
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
;
/**
* -- Unfuck Compilation Target: `simple-es6` --
*/
module.exports = {
output: function output(parent) {
return {
/**
* Increment/Decrement current cell by `body`.
*/
SFT: function SFT(settings, ins, program) {
if (ins.body > 0) program.push("t[p]+=" + ins.body);else if (ins.body < 0) program.push("t[p]-=" + Math.abs(ins.body));else return;
},
/**
* Increment/Decrement a relativly-specified cell by `body.value`.
*/
RELSFT: function RELSFT(settings, ins, program) {
var partOne = '';
var partTwo = '';
if (ins.body.move > 0) partOne = 't[p+' + ins.body.move + ']';else if (ins.body.move < 0) partOne = 't[p-' + Math.abs(ins.body.move) + ']';else partOne = 't[p]';
if (ins.body.value > 0) partTwo = "+=" + ins.body.value;else if (ins.body.value < 0) partTwo = "-=" + Math.abs(ins.body.value);
program.push(partOne + partTwo);
},
/**
* Set current cell to `body`.
*/
SET: function SET(settings, ins, program) {
program.push("t[p]=" + ins.body);
},
/**
* Set a relativly-specified cell to `body.value`.
*/
RELSET: function RELSET(settings, ins, program) {
var part = '';
if (ins.body.move > 0) part = 't[p+' + ins.body.move + ']';else if (ins.body.move < 0) part = 't[p-' + Math.abs(ins.body.move) + ']';else part = 't[p]';
program.push(part + "=" + ins.body.value);
},
/**
* Multiply current cell by factors, and add to cells.
*/
MUL: function MUL(settings, ins, program) {
ins.body.factors.forEach(function (factor) {
var part = '';
if (factor.move > 0) part = 't[p+' + factor.move + ']';else if (factor.move < 0) part = 't[p-' + Math.abs(factor.move) + ']';else part = 't[p]';
if (factor.factor === 1) program.push(part + '+=t[p]');else program.push(part + '+=t[p]*' + factor.factor);
});
program.push('t[p]=0');
},
/**
* Output current cell to output array.
*/
OUT: function OUT(settings, ins, program) {
program.push("o.push(t[p])");
},
/**
* Pop top value off input array and set to current cell.
*/
INP: function INP(settings, ins, program) {
program.push("t[p]=(i.length<1?0:i.shift())");
},
/**
* Move the current cell left or right by `body`.
*/
MOV: function MOV(settings, ins, program) {
if (ins.body > 0) program.push("p+=" + ins.body);else if (ins.body < 0) program.push("p-=" + Math.abs(ins.body));else return;
},
/**
* Conditionally loop `body` if current cell is not 0 and the begining of each loop.
* Skip entirly if current cell is 0.
*/
IF: function IF(settings, ins, program) {
program.push("while(t[p]!=0){" + parent(settings, ins.body).join(';') + "}");
}
};
},
context: function context(settings) {
var preHeader = '';
var preFooter = '';
switch (settings.in) {
case String:
preHeader += 'var i=i.split(\'\').map(x=>x.charCodeAt())||[];';
break;
case Number:
preHeader += 'var i=i||[];';
break;
}
switch (settings.out) {
case String:
preHeader += 'var o=[];';
preFooter += 'return o.map(x=>String.fromCharCode(x)).join(\'\');';
break;
case Number:
preHeader += 'var o=[];';
preFooter += "return o;";
break;
}
var params = ['i'];
if (settings.type == Array) {
var type = "Array(" + settings.width + ").fill(0)";
} else {
var type = settings.type.name + "(" + settings.width + ")";
}
var header = "(function(" + params.join(',') + "){" + preHeader + "var t=new " + type + ";var p=0;";
var footer = preFooter + "})";
return { header: header, footer: footer, lineEnding: ';' };
}
};
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
var map = {
"./interactive-es6": 1,
"./interactive-es6.js": 1,
"./simple-es6": 2,
"./simple-es6.js": 2
};
function webpackContext(req) {
return __webpack_require__(webpackContextResolve(req));
};
function webpackContextResolve(req) {
var id = map[req];
if(!(id + 1)) // check for number or string
throw new Error("Cannot find module '" + req + "'.");
return id;
};
webpackContext.keys = function webpackContextKeys() {
return Object.keys(map);
};
webpackContext.resolve = webpackContextResolve;
module.exports = webpackContext;
webpackContext.id = 3;
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
;
function convert(settings, ast) {
if (typeof settings.target === 'string') {
var target = __webpack_require__(3)("./" + settings.target);
} else {
var target = settings.target;
}
var output = target.output(convert);
var program = [];
ast.forEach(function (ins) {
var insMatch = output[ins.is];
if (insMatch) insMatch(settings, ins, program);
});
return program;
}
module.exports = convert;
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
;
var optimisers = [__webpack_require__(12), __webpack_require__(15), __webpack_require__(16), __webpack_require__(13), __webpack_require__(14)];
function optimise(settings, ast) {
return optimisers.reduce(function (acc, optimiser) {
return optimiser(settings, acc);
}, ast);
}
module.exports = optimise;
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
;
/* WEBPACK VAR INJECTION */(function(BrainfuckError) {
var valid = ['+', '-', '>', '<', '[', ']', '.', ','];
function prepare(settings, bf) {
var program = bf.split('').filter(function (x) {
return valid.indexOf(x) > -1;
}).join('');
if (program[0] === "[") {
var i = 0;
var length = 0;
var prev = 0;
var open = 1;
while (open > 0) {
for (var j = i + 1; program[j] !== ']' && j < program.length; j++) {
if (program[j] == "[") open++;
length++;
}
i += length - prev + 1;
prev = length - prev + 1 + prev;
open--;
length++;
}
if (length >= program.length) {
throw new BrainfuckError(program, { start: 0, end: 1 }, 'Comment loop is never closed!');
}
program = program.slice(length + 1);
}
return program;
}
module.exports = prepare;
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)))
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
;
function wrap(settings, program) {
if (typeof settings.target === 'string') {
var target = __webpack_require__(3)("./" + settings.target);
} else {
var target = settings.target;
}
var context = target.context(settings);
return context.header + program.join(context.lineEnding || '') + (context.lineEnding || '') + context.footer;
}
module.exports = wrap;
/***/ }),
/* 8 */,
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var contextExtend = 15;
function highlight(inp) {
return '\x1b[46m\x1b[30m ' + inp + ' \x1b[0m';
}
function heading(inp) {
return '\x1b[41m\x1b[37m ' + inp + ' \x1b[0m';
}
var BrainfuckError = function BrainfuckError(program, section, message, type) {
_classCallCheck(this, BrainfuckError);
var start = section.start - contextExtend < 0 ? 0 : section.start - contextExtend;
var end = section.start + contextExtend >= program.length ? program.length : section.start + contextExtend;
this.message = message;
this.section = section;
this.type = type || 'Syntax';
this.highlight = program.slice(section.start, section.end);
this.context = {
start: program.slice(start, section.start),
end: program.slice(section.end, end)
};
var out = '\n\n' + heading(this.type) + ' ' + highlight(this.message) + '\n';
out += 'character: ' + (this.section.start + 1) + '\n';
out += '↳ ' + this.context.start;
out += highlight(this.highlight);
out += this.context.end + '\n';
return {
name: 'Unfuck Error',
message: out
};
};
module.exports = BrainfuckError;
/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
;
var prepare = __webpack_require__(6);
var analyse = __webpack_require__(0);
var optimise = __webpack_require__(5);
var convert = __webpack_require__(4);
var wrap = __webpack_require__(7);
var initSettings = {
language: "standard",
target: "simple-es6",
type: Uint8Array,
width: 10240,
in: String,
out: String
};
module.exports = {
compiler: function compiler(settings) {
this.settings = Object.assign({}, initSettings);
this.settings = Object.assign(this.settings, settings);
this.compile = function (rawBf) {
var bf = prepare(this.settings, rawBf);
var ast = analyse(this.settings, bf);
ast = optimise(this.settings, ast);
var raw = convert(this.settings, ast);
var out = wrap(this.settings, raw);
return {
bf: bf, ast: ast, out: out
};
};
this.use = function (bf) {
return eval(this.compile(bf).out);
};
this.run = function (bf) {
for (var _len = arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
params[_key - 1] = arguments[_key];
}
return this.use(bf).apply(undefined, params);
};
return this;
}
};
/***/ }),
/* 11 */
/***/ (function(module, exports, __webpack_require__) {
;
/* WEBPACK VAR INJECTION */(function(BrainfuckError) {
module.exports = {
// Addition Operator //
'+': function _(settings, i, program, ast) {
ast.push({
is: "SFT",
body: 1
});
return ++i;
},
// Subtraction Operator //
'-': function _(settings, i, program, ast) {
ast.push({
is: "SFT",
body: -1
});
return ++i;
},
// Output Operator //
'.': function _(settings, i, program, ast) {
ast.push({
is: "OUT"
});
return ++i;
},
// Input Operator //
',': function _(settings, i, program, ast) {
ast.push({
is: "INP"
});
return ++i;
},
// Move Right Operator //
'>': function _(settings, i, program, ast) {
ast.push({
is: "MOV",
body: 1
});
return ++i;
},
// Move Left Operator //
'<': function _(settings, i, program, ast) {
ast.push({
is: "MOV",
body: -1
});
return ++i;
},
// Loop/If Operator //
'[': function _(settings, i, program, ast) {
var analyse = __webpack_require__(0);
if (program[i + 1] === ']') {
throw new BrainfuckError(program, { start: i, end: i + 2 }, 'Empty loops run forever!');
}
var init = "";
var open = 1;
var prev = 0;
while (open > 0) {
for (var j = i + 1; program[j] != ']'; j++) {
if (program[j] == "[") open++;
init += program[j];
if (j >= program.length) {
throw new BrainfuckError(program, { start: i - prev, end: i - prev + 1 }, 'Loop is never closed!');
}
}
i += init.length - prev + 1;
prev = init.length - prev + 1 + prev;
open--;
init += "]";
}
init = init.slice(0, -1);
ast.push({
is: "IF",
body: analyse(settings, init)
});
return i;
}
};
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(9)))
/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {
;
var collapsable = ['SFT', 'MOV'];
function collapse(settings, ast) {
return ast.reduce(function (acc, ins) {
if (ins.is === 'IF') {
acc.push({
is: 'IF',
body: collapse(settings, ins.body)
});
} else {
var last = acc.pop();
if (last) {
if (collapsable.indexOf(last.is) !== -1 && last.is === ins.is) {
acc.push({
is: ins.is,
body: last.body + ins.body
});
} else {
acc = acc.concat([last, ins]);
}
} else {
acc.push(ins);
}
}
return acc;
}, []);
}
module.exports = collapse;
/***/ }),
/* 13 */
/***/ (function(module, exports, __webpack_require__) {
;
var fusible = ['SFT', 'MOV', 'SET'];
var fuseTriggers = ['SFT', 'MOV', 'SET'];
function fuseTemp(temp, causeMove) {
if (temp.length > 0) {
if (temp.filter(function (ins) {
return ins.is === 'MOV';
}).length > 0) {
var p = 0;
var rel = temp.reduce(function (acc, ins) {
if (ins.is === 'MOV') {
p += ins.body;
} else if (fusible.indexOf(ins.is) !== -1) {
acc.push({
is: 'REL' + ins.is,
body: {
value: ins.body,
move: p
}
});
}
return acc;
}, []);
if (causeMove && p !== 0) {
rel.push({
is: 'MOV',
body: p
});
}
return rel;
} else {
return temp;
}
} else {
return [];
}
}
function fuse(settings, ast, inIf) {
var temp = [];
var out = ast.reduce(function (acc, ins) {
if (fusible.indexOf(ins.is) !== -1 && (temp.length === 0 ? fuseTriggers.indexOf(ins.is) !== -1 : true)) {
temp.push(ins);
} else if (ins.is === 'IF') {
acc = acc.concat(fuseTemp(temp, true));
temp = [];
acc.push({
is: 'IF',
body: fuse(settings, ins.body, true)
});
} else {
acc = acc.concat(fuseTemp(temp, true));
temp = [];
acc.push(ins);
}
return acc;
}, []);
out = out.concat(fuseTemp(temp, inIf ? true : false));
temp = [];
return out;
}
module.exports = fuse;
/***/ }),
/* 14 */
/***/ (function(module, exports, __webpack_require__) {
;
function checkForLeftPattern(ast) {
var first = ast[0].is === 'RELSFT';
first = first && ast[0].body.move === 0;
first = first && ast[0].body.value === -1;
var tail = ast.slice(1).map(function (ins) {
var out = ins.is === 'RELSFT';
out = out && ins.body.move !== 0;
out = out && ins.body.value > 0;
return out;
});
return first && tail.reduce(function (acc, res) {
return acc && res;
}, true);
}
function checkForRightPattern(ast) {
var head = ast.slice(0, -1).map(function (ins) {
var out = ins.is === 'RELSFT';
out = out && ins.body.move !== 0;
out = out && ins.body.value > 0;
return out;
});
var last = ast.slice(-1)[0].is === 'RELSFT';
last = last && ast.slice(-1)[0].body.move === 0;
last = last && ast.slice(-1)[0].body.value === -1;
return last && head.reduce(function (acc, res) {
return acc && res;
}, true);
}
function makeMul(ast, direction) {
var slice = direction === 'left' ? ast.slice(1) : direction === 'right' ? ast.slice(0, -1) : [];
var factors = slice.reduce(function (acc, ins) {
acc.push({
move: ins.body.move,
factor: ins.body.value
});
return acc;
}, []);
return {
is: 'MUL',
body: {
factors: factors
}
};
}
function multiplication(settings, ast) {
return ast.reduce(function (acc, ins) {
if (ins.is === 'IF') {
if (checkForLeftPattern(ins.body)) {
acc.push(makeMul(ins.body, 'left'));
} else if (checkForRightPattern(ins.body)) {
acc.push(makeMul(ins.body, 'right'));
} else {
ins.body = multiplication(settings, ins.body);
acc.push(ins);
}
} else {
acc.push(ins);
}
return acc;
}, []);
}
module.exports = multiplication;
/***/ }),
/* 15 */
/***/ (function(module, exports, __webpack_require__) {
;
var nullable = ['SFT', 'MOV'];
function nullify(settings, ast) {
return ast.reduce(function (acc, ins) {
if (ins.is === 'IF') {
acc.push({
is: 'IF',
body: nullify(settings, ins.body)
});
} else if (nullable.indexOf(ins.is) !== -1) {
if (ins.body !== 0) {
acc.push(ins);
}
} else {
acc.push(ins);
}
return acc;
}, []);
}
module.exports = nullify;
/***/ }),
/* 16 */
/***/ (function(module, exports, __webpack_require__) {
;
function setZero(settings, ast) {
return ast.reduce(function (acc, ins) {
if (ins.is === 'IF') {
if (ins.body.length === 1 && ins.body[0].is === 'SFT' && (ins.body[0].body === -1 || ins.body[0].body === 1)) {
acc.push({
is: 'SET',
body: 0
});
} else {
acc.push({
is: 'IF',
body: setZero(settings, ins.body)
});
}
} else {
acc.push(ins);
}
return acc;
}, []);
}
module.exports = setZero;
/***/ })
/******/ ]);
});