bean-js
Version:
An esoteric byte-encoded code-golfing language derived from JavaScript
962 lines (737 loc) • 19.1 kB
JavaScript
const globals = require('../config/globals.json');
const identifiers = require('../config/identifiers.json');
const nodeTypes = require('../config/ast.json');
const byteCodeToNodeTypeMap = new Map();
const MAX_LITERALS = 0x7F00;
const Assembler = module.exports = class Assembler {
constructor(binary) {
this.binary = binary;
this.byteIndex = 0;
this.tokens = [];
this.literals = [];
while (!this.decodeByte()) {}
this.binary.substr(this.byteIndex).split('').reduce((literal, character, byteIndex) => {
const code = character.charCodeAt(0);
const last = this.isLast(code);
literal += String.fromCharCode(code & 0x7F);
if (last) {
this.literals.push(literal);
return '';
}
return literal;
}, '');
if (this.literals.length > MAX_LITERALS) {
throw new RangeError(`decoded ${this.literals.length} program-specific literals, only ${MAX_LITERALS} supported`);
}
}
decodeByte(code = this.nextByte(true)) {
const last = this.isLast(code);
const type = byteCodeToNodeTypeMap.get(code & 0x7F);
if (type !== undefined) {
this[type]();
}
return last;
}
decodeLiteral() {
const code = this.nextByte(true);
// global
if (code < 0x80) {
this.tokens.push(globals[code]);
// predefined identifier
} else if (code === 0x80) {
const index = this.nextByte(true);
this.tokens.push(identifiers[index]);
// program-specific identifier
} else {
const index = this.nextByte(true) + (code - 0x81) * 0x100;
// toString invoked in post-processing of tokens when joined
// must parse program-specific literals first
this.tokens.push({
toString: () => this.literals[index]
});
}
}
isLast(code = this.nextByte()) {
return !(code & 0x80);
}
nextByte(postIncrement = false) {
return this.binary.charCodeAt(postIncrement ? this.byteIndex++ : this.byteIndex);
}
lastToken() {
return this.tokens[this.tokens.length - 1] || '';
}
Identifier() {
this.decodeLiteral();
}
RegExpLiteral() {
const code = this.nextByte(true);
this.tokens.push('/');
this.decodeLiteral();
const literal = this.tokens.pop();
this.tokens.push({
toString: () => literal.toString().replace(/\//g, '\\/')
});
this.tokens.push('/');
const [g, i, m, u, y] = [
(code >> 0) & 1,
(code >> 1) & 1,
(code >> 2) & 1,
(code >> 3) & 1,
(code >> 4) & 1,
];
const flags = [
(g ? 'g' : ''),
(i ? 'i' : ''),
(m ? 'm' : ''),
(u ? 'u' : ''),
(y ? 'y' : ''),
].join('');
this.tokens.push(flags);
}
NullLiteral() {
this.tokens.push('null');
}
StringLiteral() {
this.tokens.push('"');
this.decodeLiteral();
const literal = this.tokens.pop();
this.tokens.push({
toString: () => literal.toString().replace(/["\\]/g, '\\$&').replace(/\r?\n/g, '\\n')
});
this.tokens.push('"');
}
BooleanLiteral() {
const code = this.nextByte(true);
this.tokens.push(code ? 'true' : 'false');
}
NumericLiteral() {
this.decodeLiteral();
}
ExpressionStatement() {
this.decodeByte();
this.tokens.push(';');
}
BlockStatement() {
this.tokens.push('{');
while (!this.decodeByte()) {}
this.tokens.push('}');
}
EmptyStatement() {
this.tokens.push(';');
}
DebuggerStatement() {
this.tokens.push('debugger', ';');
}
WithStatement() {
this.tokens.push('with', '(');
this.decodeByte();
this.tokens.push(')');
this.decodeByte();
}
ReturnStatement() {
this.tokens.push('return');
const code = this.nextByte();
if (code !== 0x00) {
this.tokens.push(' ');
}
this.decodeByte();
this.tokens.push(';');
}
LabeledStatement() {
this.decodeByte();
this.tokens.push(':');
this.decodeByte();
}
BreakStatement() {
this.tokens.push('break');
const code = this.nextByte();
if (code !== 0x00) {
this.tokens.push(' ');
}
this.decodeByte();
this.tokens.push(';');
}
ContinueStatement() {
this.tokens.push('continue');
const code = this.nextByte();
if (code !== 0x00) {
this.tokens.push(' ');
}
this.decodeByte();
this.tokens.push(';');
}
IfStatement() {
this.tokens.push('if', '(');
this.decodeByte();
this.tokens.push(')');
if (!this.decodeByte()) {
this.tokens.push('else', ' ');
this.decodeByte();
}
}
SwitchStatement() {
this.tokens.push('switch', '(');
let last = this.decodeByte();
this.tokens.push(')', '{');
while (!last) {
last = this.decodeByte();
}
this.tokens.push('}');
}
SwitchCase() {
const code = this.nextByte();
if (code & 0x7F) {
this.tokens.push('case', ' ');
} else {
this.tokens.push('default');
}
let last = this.decodeByte();
this.tokens.push(':');
while (!last) {
last = this.decodeByte();
}
}
ThrowStatement() {
this.tokens.push('throw', ' ');
this.decodeByte();
this.tokens.push(';');
}
TryStatement() {
this.tokens.push('try');
this.decodeByte();
const last = this.decodeByte();
if (!last) {
this.tokens.push('finally');
this.decodeByte();
}
}
CatchClause() {
this.tokens.push('catch', '(');
this.decodeByte();
this.tokens.push(')');
this.decodeByte();
}
WhileStatement() {
this.tokens.push('while', '(');
this.decodeByte();
this.tokens.push(')');
this.decodeByte();
}
DoWhileStatement() {
this.tokens.push('do', ' ');
this.decodeByte();
this.tokens.push('while', '(');
this.decodeByte();
this.tokens.push(')');
}
ForStatement() {
this.tokens.push('for', '(');
this.decodeByte();
if (this.lastToken() !== ';') {
this.tokens.push(';');
}
this.decodeByte();
this.tokens.push(';');
this.decodeByte();
this.tokens.push(')');
this.decodeByte();
}
ForInStatement() {
this.tokens.push('for', '(');
this.decodeByte();
this.tokens.push(' ', 'in', ' ');
this.decodeByte();
this.tokens.push(')');
this.decodeByte();
}
ForOfStatement() {
this.tokens.push('for', '(');
this.decodeByte();
this.tokens.push(' ', 'of', ' ');
this.decodeByte();
this.tokens.push(')');
this.decodeByte();
}
ForAwaitStatement() {
this.tokens.push('for', '(');
this.decodeByte();
this.tokens.push(' ', 'await', ' ');
this.decodeByte();
this.tokens.push(')');
this.decodeByte();
}
FunctionDeclaration() {
const code = this.nextByte(true);
const [generator, async] = [
(code >> 1) & 1,
(code >> 0) & 1,
];
if (async) {
this.tokens.push('async', ' ');
}
this.tokens.push('function');
if (generator) {
this.tokens.push('*');
}
this.tokens.push(' ');
this.decodeByte();
this.tokens.push('(');
while (!this.isLast()) {
this.decodeByte();
if (!this.isLast()) {
this.tokens.push(',');
}
}
this.tokens.push(')');
this.decodeByte();
}
VariableDeclaration() {
const code = this.nextByte(true);
let kind = Assembler.DECLARE.length;
while (((code >> --kind) & 0x01) === 0x00) {}
this.tokens.push(Assembler.DECLARE[kind], ' ');
while (!this.isLast()) {
this.decodeByte();
this.tokens.push(',');
}
this.decodeByte();
this.tokens.push(';');
}
VariableDeclarator() {
const last = this.decodeByte();
if (!last) {
this.tokens.push('=');
this.decodeByte();
}
}
Super() {
this.tokens.push('super');
}
ThisExpression() {
this.tokens.push('this');
}
ArrowFunctionExpression() {
this.tokens.push('(', '(');
while (!this.isLast()) {
this.decodeByte();
if (!this.isLast()) {
this.tokens.push(',');
}
}
this.tokens.push(')', '=>');
this.decodeByte();
this.tokens.push(')');
}
YieldExpression() {
this.tokens.push('(', 'yield');
const code = this.nextByte(true);
const last = this.isLast(code);
const delegate = (code >> 0) & 1;
if (delegate) {
this.tokens.push('*');
}
this.tokens.push(' ');
if (!last) {
this.decodeByte();
}
this.tokens.push(')');
}
AwaitExpression() {
this.tokens.push('(', 'await');
const code = this.nextByte();
if (code !== 0x00) {
this.tokens.push(' ');
}
this.decodeByte();
this.tokens.push(')');
}
ArrayExpression() {
this.tokens.push('[');
while (!this.isLast()) {
this.decodeByte();
this.tokens.push(',');
}
this.decodeByte();
this.tokens.push(']');
}
ObjectExpression() {
this.tokens.push('(', '{');
while (!this.isLast()) {
this.decodeByte();
this.tokens.push(',');
}
this.decodeByte();
this.tokens.push('}', ')');
}
ObjectProperty() {
const code = this.nextByte(true);
const last = this.isLast(code);
const [shorthand, computed] = [
(code >> 3) & 1,
(code >> 2) & 1,
];
if (!last && !shorthand) {
if (computed) {
this.tokens.push('[');
}
this.decodeByte();
if (computed) {
this.tokens.push(']');
}
this.tokens.push(':');
}
this.decodeByte();
}
ObjectMethod() {
const code = this.nextByte(true);
const [kind, computed, generator, async] = [
(code >> 5) & 3,
(code >> 2) & 1,
(code >> 1) & 1,
(code >> 0) & 1,
];
const type = Assembler.METHOD[kind];
switch (type) {
case 'get':
case 'set':
this.tokens.push(type, ' ');
case 'method':
if (async) {
this.tokens.push('async', ' ');
}
if (generator) {
this.tokens.push('*');
}
if (computed) {
this.tokens.push('[');
}
this.decodeByte();
if (computed) {
this.tokens.push(']');
}
}
this.tokens.push('(');
while (!this.isLast()) {
this.decodeByte();
if (!this.isLast()) {
this.tokens.push(',');
}
}
this.tokens.push(')');
this.decodeByte();
}
RestProperty() {
this.tokens.push('...');
this.decodeByte();
}
SpreadProperty() {
this.tokens.push('...');
this.decodeByte();
}
FunctionExpression() {
const code = this.nextByte(true);
const [generator, async] = [
(code >> 1) & 1,
(code >> 0) & 1,
];
this.tokens.push('(');
if (async) {
this.tokens.push('async', ' ');
}
this.tokens.push('function');
if (generator) {
this.tokens.push('*');
}
this.tokens.push(' ');
this.decodeByte();
this.tokens.push('(');
while (!this.isLast()) {
this.decodeByte();
if (!this.isLast()) {
this.tokens.push(',');
}
}
this.tokens.push(')');
this.decodeByte();
this.tokens.push(')');
}
UnaryExpression() {
const code = this.nextByte(true);
const [prefix, operator] = [
(code >> 6) & 1,
(code >> 0) & 31,
];
const token = Assembler.UNARY[operator];
const space = /^[a-z]+$/.test(token) ? ' ' : '';
this.tokens.push('(');
if (prefix) {
this.tokens.push(token, space);
this.decodeByte();
} else {
this.decodeByte();
this.tokens.push(space, token);
}
this.tokens.push(')');
}
UpdateExpression() {
const code = this.nextByte(true);
const [prefix, operator] = [
(code >> 6) & 1,
(code >> 0) & 31,
];
this.tokens.push('(');
if (prefix) {
this.tokens.push(Assembler.UPDATE[operator]);
this.decodeByte();
} else {
this.decodeByte();
this.tokens.push(Assembler.UPDATE[operator]);
}
this.tokens.push(')');
}
BinaryExpression() {
this.tokens.push('(');
this.decodeByte();
const code = this.nextByte(true);
const operator = (code >> 0) & 31;
const token = Assembler.BINARY[operator];
const space = /^[a-z]+$/.test(token) ? ' ' : '';
this.tokens.push(space, token, space);
this.decodeByte();
this.tokens.push(')');
}
AssignmentExpression() {
this.tokens.push('(');
this.decodeByte();
const code = this.nextByte(true);
const operator = (code >> 0) & 31;
this.tokens.push(Assembler.ASSIGNMENT[operator]);
this.decodeByte();
this.tokens.push(')');
}
LogicalExpression() {
this.tokens.push('(');
this.decodeByte();
const code = this.nextByte(true);
const operator = (code >> 0) & 31;
this.tokens.push(Assembler.LOGICAL[operator]);
this.decodeByte();
this.tokens.push(')');
}
SpreadElement() {
this.tokens.push('...');
this.decodeByte();
}
MemberExpression() {
const code = this.nextByte(true);
const computed = (code >> 2) & 1;
this.decodeByte();
if (computed) {
this.tokens.push('[');
this.decodeByte();
this.tokens.push(']');
} else {
this.tokens.push('.');
this.decodeByte();
}
}
BindExpression() {
if (!this.isLast()) {
this.decodeByte();
}
this.tokens.push('::');
this.decodeByte();
}
ConditionalExpression() {
this.tokens.push('(');
this.decodeByte();
this.tokens.push('?');
this.decodeByte();
this.tokens.push(':');
this.decodeByte();
this.tokens.push(')');
}
CallExpression() {
let last = this.decodeByte();
this.tokens.push('(');
while (!last) {
last = this.decodeByte();
if (!last) {
this.tokens.push(',');
}
}
this.tokens.push(')');
}
NewExpression() {
this.tokens.push('new', ' ');
let last = this.decodeByte();
this.tokens.push('(');
while (!last) {
last = this.decodeByte();
if (!last) {
this.tokens.push(',');
}
}
this.tokens.push(')');
}
SequenceExpression() {
this.tokens.push('(');
while (!this.isLast()) {
this.decodeByte();
this.tokens.push(',');
}
this.decodeByte();
this.tokens.push(')');
}
TemplateLiteral() {
this.tokens.push('`');
while (!this.isLast()) {
this.decodeByte();
this.tokens.push('${');
this.decodeByte();
this.tokens.push('}');
}
this.decodeByte();
this.tokens.push('`');
}
TaggedTemplateExpression() {
this.decodeByte();
this.decodeByte();
}
TemplateElement() {
this.decodeLiteral();
const literal = this.tokens.pop();
this.tokens.push({
toString: () => literal.toString().replace(/[`\\]/g, '\\$&').replace(/\r?\n/g, '\\n')
});
}
ObjectPattern() {
this.tokens.push('{');
while (!this.isLast()) {
this.decodeByte();
this.tokens.push(',');
}
this.decodeByte();
this.tokens.push('}');
}
ArrayPattern() {
this.tokens.push('[');
while (!this.isLast()) {
this.decodeByte();
this.tokens.push(',');
}
this.decodeByte();
this.tokens.push(']');
}
RestElement() {
this.tokens.push('...');
this.decodeByte();
}
AssignmentPattern() {
this.decodeByte();
this.tokens.push('=');
this.decodeByte();
}
ClassBody() {
this.tokens.push('{');
while (!this.isLast()) {
this.decodeByte();
}
this.decodeByte();
this.tokens.push('}');
}
ClassMethod() {
const code = this.nextByte(true);
const [kind, isStatic, computed, generator, async] = [
(code >> 5) & 3,
(code >> 4) & 1,
(code >> 2) & 1,
(code >> 1) & 1,
(code >> 0) & 1,
];
const type = Assembler.METHOD[kind];
if (isStatic) {
this.tokens.push('static', ' ');
}
switch (type) {
case 'get':
case 'set':
this.tokens.push(type, ' ');
case 'method':
case 'constructor':
if (async) {
this.tokens.push('async', ' ');
}
if (generator) {
this.tokens.push('*');
}
if (computed) {
this.tokens.push('[');
}
this.decodeByte();
if (computed) {
this.tokens.push(']');
}
break;
}
this.tokens.push('(');
while (!this.isLast()) {
this.decodeByte();
if (!this.isLast()) {
this.tokens.push(',');
}
}
this.tokens.push(')');
this.decodeByte();
}
ClassProperty() {
const code = this.nextByte(true);
const computed = (code >> 2) & 1;
if (computed) {
this.tokens.push('[');
}
this.decodeByte();
if (computed) {
this.tokens.push(']');
}
this.tokens.push('=');
this.decodeByte();
this.tokens.push(';');
}
ClassDeclaration() {
this.tokens.push('class', ' ');
this.decodeByte();
if (!this.isLast()) {
this.tokens.push(' ', 'extends', ' ');
this.decodeByte();
}
this.decodeByte();
}
ClassExpression() {
const space = this.nextByte() === 0x80 ? '' : ' ';
this.tokens.push('(', 'class', space);
this.decodeByte();
if (!this.isLast()) {
this.tokens.push(' ', 'extends', ' ');
this.decodeByte();
}
this.decodeByte();
this.tokens.push(')');
}
MetaProperty() {
this.decodeByte();
this.tokens.push('.');
this.decodeByte();
}
};
Assembler.DECLARE = ["var", "let", "const"];
Assembler.METHOD = ["get", "set", "method", "constructor"];
Assembler.UNARY = ["-", "+", "!", "~", "typeof", "void", "delete"];
Assembler.UPDATE = ["++", "--"];
Assembler.BINARY = ["==", "!=", "===", "!==", "<", "<=", ">", ">=", "<<", ">>", ">>>", "+", "-", "*", "/", "%", "|", "^", "&", "in", "instanceof", "**"];
Assembler.ASSIGNMENT = ["=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&="];
Assembler.LOGICAL = ["||", "&&"];
nodeTypes.forEach((type, index) => {
const byteCode = index + 32;
byteCodeToNodeTypeMap.set(byteCode, type);
});