const a = require('./function-ast');
class Node {
constructor(o) {
Object.assign(this, o);
}
}
class Token extends Node {
toString() {
return this.token;
}
}
class Local extends Node {
constructor(...args) {
super(...args);
if (typeof this.name !== 'string') {
throw new Error('assertion: Local "name" must be a string.');
}
}
toString() {
return this.name;
}
}
class Declare extends Node {
toString() {
return `var ${this.local}`;
}
}
class Constant extends Node {
toString() {
return `var ${this.local.toString()} = ${this.value.toString()};`;
}
}
class Expr extends Node {
toString() {
try {
return this.expr.join('');
}
catch(e) {
// return '"bad expression"';
throw e;
}
}
}
class Line extends Expr {
toString() {
if (this.expr.length === 0) {
return '';
}
return super.toString() + ';';
}
toJSON() {
return Object.assign({
constructor: 'Line',
}, this);
}
}
class Binary extends Expr {
constructor(op, left, right) {
super({
type: 'expression',
expr: [token('('), left, op, right, token(')')],
});
}
get op() {
return this.expr[2];
}
get left() {
return this.expr[1];
}
get right() {
return this.expr[3];
}
}
class Literal extends Node {
toString() {
return this.value;
}
}
class Result extends Expr {}
Result.hasResult = ({expr}) => {
if (!expr) {return false;}
for (let i = expr.length - 1; i >= 0; --i) {
if (isResult(expr[i])) {
return true;
}
else if (expr[i].type === 'expression') {
const result = Result.hasResult(expr[i]);
if (result) {
return true;
}
}
}
return false;
};
const isResult = _expr => _expr.expr &&
(_expr instanceof Result || _expr.result);
Result.take = _expr => {
if (!_expr.expr) {
return expr([_expr]);
}
if (isResult(_expr)) {
return _expr;
}
for (let i = _expr.expr.length - 1; i >= 0; --i) {
if (isResult(_expr.expr[i])) {
if (_expr.expr[i] instanceof Result) {
return resultless(expr(_expr.expr.splice(i, 1)[0].expr));
}
else {
return resultless(_expr.expr.splice(i, 1)[0]);
}
}
else if (_expr.expr[i].type === 'expression') {
const result = Result.take(_expr.expr[i]);
if (result) {
return result;
}
}
}
return null;
};
Result.taken = _expr => {
if (!_expr.expr) {
return expr([]);
}
if (isResult(_expr)) {
return expr([]);
}
return _expr;
};
const token = str => new Token({type: 'token', token: str});
const local = name => new Local({type: 'local', name});
const declare = local => new Declare({type: 'declare', local});
const constant = (local, value) => new Constant({type: 'constant', local, value});
const line = item => new Line({type: 'expression', expr: [item]});
const expr = ary => new Expr({type: 'expression', expr: ary});
const binary = (op, left, right) => {
let _leftTaken = [];
let _left = left;
if (Result.hasResult(left)) {
_leftTaken = Result.taken(left);
_left = Result.take(left);
}
let _rightTaken = [];
let _right = right;
if (Result.hasResult(right)) {
_rightTaken = Result.taken(right);
_right = Result.take(right);
}
if (_left !== left || _right !== right) {
return expr([
_leftTaken,
_rightTaken,
result([
new Binary(op, lineless(_left), lineless(_right)),
])
]);
}
return new Binary(op, left, right);
};
const literal = value => new Literal({type: 'literal', value});
const block = ary => new Block({type: 'expression', expr: ary});
const result = ary => new Result({type: 'expression', expr: ary});
const resultless = _r => {
if (_r instanceof Result) {
any(_r, node => {node.result = false;});
return expr(_r.expr);
}
else {
any(_r, node => {node.result = false;});
pop(_r, node => node instanceof Result ? expr(node.expr) : false);
return _r;
}
}
const lineless = _r => {
if (_r instanceof Line) {
return expr(_r.expr);
}
else {
pop(_r, node => node instanceof Line ? expr(node.expr) : false);
return _r;
}
}
const resultLast = ary => Result.transformLast(expr(ary));
const lift = args => {
if (args.map(Result.hasResult).filter(Boolean).length > 0) {
const taken = args.map(Result.taken).filter(Boolean);
const take = args.map(Result.take).filter(Boolean);
return expr([
...taken,
result([
...take,
]),
]);
}
else {
return expr(args);
}
};
const _compile_literal = ({compile, pointer, stack, scope}) => {
if (pointer.value && pointer.value.op === 'read') {
if (
_compile_lookup(stack, scope, pointer.value.name)[pointer.value.name] &&
_compile_lookup(stack, scope, pointer.value.name)[pointer.value.name].op === 'literal'
) {
return _compile_lookup(stack, scope, pointer.value.name)[pointer.value.name];
}
else if (
typeof _compile_lookup(stack, scope, pointer.value.name)[pointer.value.name] === 'string'
) {
return local(_compile_lookup(stack, scope, pointer.value.name)[pointer.value.name]);
}
return literal(pointer.value.name);
// return compile(pointer.value);
}
if (pointer.value && pointer.value.op === 'func') {
return compile(pointer.value);
}
else if (typeof pointer.value === 'string') {
return literal(JSON.stringify(pointer.value));
}
else if (Array.isArray(pointer.value)) {
return expr([token('['), token(']')]);
}
else if (typeof pointer.value === 'object') {
const entries = Object.entries(pointer.value);
return expr(
[token('{')]
.concat([token('}')])
);
}
else {
return literal(pointer.value);
}
}
const _new_local_name = ({names}, name) => {
let _name;
if (!names[name]) {
names[name] = [];
}
if (names[name].length === 0) {
_name = name;
}
else {
_name = `_${name}${names[name].length}`;
}
names[name].push(_name);
return _name;
};
const _set_local_name = ({locals, scope}, name, _name) => {
if (!locals[name]) {
locals[name] = _name;
scope[name] = _name;
}
};
const _set_scope_name = ({scope}, name, _name) => {
scope[name] = _name;
};
const _get_scope_name = ({scope}, name) => {
return scope[name];
};
const _get_local_name = ({locals, context}, name) => {
if (!locals[name]) {
return _new_local_name(context, name);
}
else {
return _get_scope_name(context, name);
}
};
const _compile_write = ({write, compile, pointer, locals, scope, names, vars}) => {
let _name;
if (!locals[pointer.name]) {
if (!names[pointer.name]) {
names[pointer.name] = [];
}
const name = `_${pointer.name}${names[pointer.name].length}`;
names[pointer.name].push(name);
_name = name;
}
else {
_name = scope[pointer.name];
}
const value = compile(pointer.value);
let _result;
if (Result.hasResult(value)) {
_result = expr([
Result.taken(value),
result([
declare(local(_name)),
local(_name),
token(' = '),
Result.take(value),
]),
]);
}
else {
_result = expr([
declare(local(_name)),
local(_name),
token(' = '),
value,
]);
}
if (!locals[pointer.name]) {
locals[pointer.name] = _name;
scope[pointer.name] = _name;
}
return _result;
};
const _compile_read = ({write, compile, pointer, scope, stack}) => {
let _scope = scope;
let stackIndex = stack.length - 1;
while (stackIndex >= 0 && !_scope[pointer.name]) {
_scope = stack[stackIndex][1];
stackIndex -= 1;
}
if (_scope[pointer.name] && _scope[pointer.name].op === 'literal') {
return compile(_scope[pointer.name]);
}
else {
try {
return local(_scope[pointer.name]);
}
catch (e) {
console.log(pointer.name, stack);
throw e;
}
}
};
const _compile_lookup = (stack, scope, name) => {
let _scope = scope;
let stackIndex = stack.length - 1;
while (stackIndex >= 0 && !_scope[name]) {
_scope = stack[stackIndex][1];
stackIndex -= 1;
}
return _scope;
};
const _compile_store = ({write, compile, pointer, stack, scope}) => {
const _ref = compile(pointer.ref);
const _member = pointer.member;
let _deref;
if (_member.op === 'literal' && _member.value.op) {
_deref = expr([token('['), compile(_member.value), token(']')]);
}
else if (_member.op === 'literal') {
_deref = expr([token('.'), literal(_member.value)]);
}
else if (_member.op === 'read' && _compile_lookup(stack, scope, _member.name)[_member.name].op === 'literal') {
const found = _compile_lookup(stack, scope, _member.name)[_member.name].value;
_deref = expr([token('.'), literal(found)]);
}
else {
_deref = expr([token('['), compile(_member), token(']')]);
}
const _value = compile(pointer.value);
let _refTaken = expr([]);
let _refTake = _ref;
let _derefTaken = expr([]);
let _derefTake = _deref;
let _valueTaken = expr([]);
let _valueTake = _value;
if (Result.hasResult(_ref)) {
_refTaken = Result.taken(_ref);
_refTake = Result.take(_ref);
}
if (Result.hasResult(_deref)) {
_derefTaken = Result.taken(_deref);
_derefTake = Result.take(_deref);
}
if (Result.hasResult(_value)) {
_valueTaken = Result.taken(_value);
_valueTake = Result.take(_value);
}
if (
_refTake !== _ref ||
_derefTake !== _deref ||
_valueTake !== _value
) {
return expr([
...(_refTaken.expr ? _refTaken.expr : [_refTaken]),
...(_derefTaken.expr ? _derefTaken.expr : [_derefTaken]),
...(_valueTaken.expr ? _valueTaken.expr : [_valueTaken]),
line(lineless(result([
_refTake,
_derefTake,
token(' = '),
_valueTake,
]))),
]);
}
return expr([
_ref,
_deref,
token(' = '),
_value,
]);
};
const _compile_load = ({write, compile, pointer, stack, scope}) => {
const _ref = compile(pointer.ref);
const _member = pointer.member;
let _deref;
if (_member.op === 'literal' && _member.value === 'methods') {
_deref = expr([]);
}
else if (_member.op === 'literal' && _member.value && _member.value.op === 'read' && _compile_lookup(stack, scope, _member.name)[_member.name]) {
_deref = expr([token('['), literal(_compile_lookup(stack, scope, _member.name)[_member.name]), token(']')]);
}
else if (_member.op === 'literal' && _member.value && _member.value.op === 'read') {
_deref = expr([token('['), compile(_member), token(']')]);
}
else if (_member.op === 'literal' && typeof _member.value === 'number') {
_deref = expr([token('['), literal(_member.value), token(']')]);
}
else if (_member.op === 'literal') {
_deref = expr([token('.'), literal(_member.value)]);
}
else if (_member.op === 'read' && _compile_lookup(stack, scope, _member.name)[_member.name].op === 'literal') {
const found = _compile_lookup(stack, scope, _member.name)[_member.name].value;
_deref = expr([token('.'), literal(found)]);
}
else {
_deref = expr([token('['), compile(_member), token(']')]);
}
if (Result.hasResult(_ref)) {
return expr([
Result.taken(_ref),
result([
Result.take(_ref),
_deref,
]),
]);
}
return expr([_ref, _deref]);
};
const _compile_ops = {
'==': (a, b) => expr([token('('), binary(token(' == '), a, b), token(')')]),
'!=': (a, b) => lift([token('('), a, token(' != '), b, token(')')]),
'<': (a, b) => lift([token('('), a, token(' < '), b, token(')')]),
'<=': (a, b) => lift([token('('), a, token(' <= '), b, token(')')]),
'>': (a, b) => lift([token('('), a, token(' > '), b, token(')')]),
'>=': (a, b) => lift([token('('), a, token(' >= '), b, token(')')]),
'&&': (a, b) => lift([token('('), a, token(' && '), b, token(')')]),
'||': (a, b) => lift([token('('), a, token(' || '), b, token(')')]),
'+': (a, b) => binary(token(' + '), a, b),
'-': (a, b) => binary(token(' - '), a, b),
'*': (a, b) => binary(token(' * '), a, b),
'/': (a, b) => binary(token(' / '), a, b),
'%': (a, b) => binary(token(' % '), a, b),
'min': (a, b) => lift([token('Math.min('), a, token(', '), b, token(')')]),
'max': (a, b) => lift([token('Math.max('), a, token(', '), b, token(')')]),
'abs': a => lift([token('Math.abs('), a, token(')')]),
};
const _compile_unary = ({write, compile, pointer}) => (
_compile_ops[pointer.operator](compile(pointer.right))
);
const _compile_binary = ({write, compile, pointer}) => (
_compile_ops[pointer.operator](
compile(pointer.left),
compile(pointer.right)
)
);
const _compile_for_of = ({compile, pointer, context, scope, locals, names}) => {
if (pointer.iterable.op === 'read') {
const i = '_for_of_index';
const _i = _new_local_name(context, i);
const _i_last_locals = locals[i];
const _i_last_scope = scope[i];
locals[i] = _i;
scope[i] = _i;
const len = '_for_of_length';
const _len = _new_local_name(context, len);
const _len_last_locals = locals[len];
const _len_last_scope = scope[len];
locals[len] = _len;
scope[len] = _len;
const _setup = compile(a.w(i, a.l(0)));
const _test = compile(a.lt(a.r(i), a.r(len)));
const _setupHasResult = Result.hasResult(_setup);
const _testHasResult = Result.hasResult(_test);
const _result = expr([
compile(a.body([a.w('constant_entries', a.call(a.l('Object.entries'), [pointer.iterable]))])),
resultless(expr([
compile(a.w(len, a.lo(a.r('constant_entries'), a.l('length')))),
token(';'),
])),
_setupHasResult ? Result.taken(_setup) : expr([]),
_testHasResult ? Result.taken(_test) : expr([]),
expr([token('for '), token('(')]),
expr([_setupHasResult ? Result.take(_setup) : _setup, token('; ')]),
expr([_testHasResult ? Result.take(_test) : _test, token('; ')]),
expr([compile(a.r(i)), token('++')]),
expr([token(') ')]),
token('{'),
resultless(compile(a.body([
pointer.keys[0] ?
a.w(pointer.keys[0],
a.lo(a.lo(a.r('constant_entries'), a.r(i)), a.l(0))
) :
a.body([]),
a.w(pointer.keys[1],
a.lo(a.lo(a.r('constant_entries'), a.r(i)), a.l(1))
),
...pointer.body.body
]))),
token('}'),
result([]),
]);
names[i].pop();
locals[i] = _i_last_locals;
scope[i] = _i_last_scope;
names[len].pop();
locals[len] = _len_last_locals;
scope[len] = _len_last_scope;
return _result;
}
else {
const i = '_for_of_index';
const _i = _new_local_name(context, i);
const _i_last_locals = locals[i];
const _i_last_scope = scope[i];
locals[i] = _i;
const len = '_for_of_length';
const _len = _new_local_name(context, len);
const _len_last_locals = locals[len];
const _len_last_scope = scope[len];
locals[len] = _len;
scope[len] = a.l(Object.entries(pointer.iterable).length);
const _result = expr([
...Object.entries(pointer.iterable)
.map(([key, value], index) => {
scope[i] = a.l(index);
const _key = _new_local_name(context, pointer.keys[0]);
const _key_last_locals = locals[pointer.keys[0]];
const _key_last_scope = scope[pointer.keys[0]];
const _value = _new_local_name(context, pointer.keys[1]);
const _value_last_locals = locals[pointer.keys[0]];
const _value_last_scope = scope[pointer.keys[0]];
locals[pointer.keys[0]] = _key;
scope[pointer.keys[0]] = a.l(key);
locals[pointer.keys[1]] = _value;
scope[pointer.keys[1]] = value;
const _body = compile(pointer.body);
const _result = resultless(expr([
compile(pointer.body),
]));
names[pointer.keys[0]].pop();
locals[pointer.keys[0]] = _key_last_locals;
scope[pointer.keys[0]] = _key_last_scope;
names[pointer.keys[1]].pop();
locals[pointer.keys[1]] = _value_last_locals;
scope[pointer.keys[1]] = _value_last_scope;
return _result;
}),
result([]),
]);
names[i].pop();
locals[i] = _i_last_locals;
scope[i] = _i_last_scope;
names[len].pop();
locals[len] = _len_last_locals;
scope[len] = _len_last_scope;
return _result;
}
};
const _compile_not_last = ({compile, pointer, scope}) => {
if (scope._for_of_index.op && scope._for_of_index.op === 'literal') {
return compile(
scope._for_of_index.value < scope._for_of_length.value - 1 ?
pointer.not_last :
pointer.last
);
}
else {
return compile(a.call(a.func([], [
a.w('not_last', pointer.not_last),
a.branch(a.gte(a.r('_for_of_index'), a.sub(a.r('_for_of_length'), a.l(1))), [
a.w('not_last', pointer.last),
]),
a.r('not_last'),
]), []));
}
};
const _compile_branch = ({compile, pointer, context}) => {
if (
pointer.test.op === 'load' &&
_call_search(pointer.test, context) &&
!_call_search(pointer.test, context).notFound &&
!!~['func', 'methods'].indexOf(_call_search(pointer.test, context).op)
) {
return compile(pointer.body);
}
else if (
pointer.test.op === 'load' &&
_call_search(pointer.test.ref, context) &&
!_call_search(pointer.test.ref, context).notFound &&
!!~['func', 'methods'].indexOf(_call_search(pointer.test.ref, context).op)
) {
return compile(pointer.else);
}
else {
const _test = compile(pointer.test);
let _if;
if (Result.hasResult(_test)) {
_if = expr([
Result.taken(_test),
expr([
token('if '), token('('),
lineless(Result.take(_test)),
token(') '),
]),
token('{'),
resultless(compile(pointer.body)),
token('}'),
]);
}
else {
_if = expr([
expr([
token('if '),
token('('),
lineless(_test),
token(') '),
]),
token('{'),
resultless(compile(pointer.body)),
token('}'),
]);
}
if (pointer.else.body.length) {
return expr([
_if,
token('else '),
token('{'),
resultless(compile(pointer.else)),
token('}'),
]);
}
return _if;
}
};
const _compile_loop = ({compile, pointer}) => {
return expr([
token('while ('),
compile(pointer.test),
token(')'), token('{'),
compile(pointer.body),
token('}'),
]);
};
const _compile_body = ({write, compile, pointer}) => {
const b = pointer.body
.map(compile)
.filter(Boolean)
.map(line);
if (b.length === 0) {
return expr([]);
}
else if (b.length === 1) {
if (Result.hasResult(b[0])) {
return expr(b);
}
else {
return result(b);
}
}
if (Result.hasResult(b[b.length - 1])) {
return expr([
...b.slice(0, b.length - 1).map(resultless),
Result.taken(b[b.length - 1]),
result([Result.take(b[b.length - 1])]),
]);
}
return expr([
...b.slice(0, b.length - 1).map(resultless),
result([b[b.length - 1]]),
]);
};
const _call_search = (ref, context) => {
if (ref.op === 'func' || typeof ref === 'function') {
return ref;
}
else if (ref.op === 'literal' && ref.value && ref.value.op === 'read') {
return ref.value;
}
else if (ref.op === 'literal') {
return ref.value;
}
else if (
ref.op === 'binary' && ref.operator === '||' && (
ref.left.op !== 'load' &&
_call_search(ref.left, context) &&
!_call_search(ref.left, context).notFound && (
_call_search(ref.left, context).op === 'func' ||
_call_search(ref.left, context).op === 'methods' ||
typeof _call_search(ref.left, context) === 'function' ||
Array.isArray(_call_search(ref.left, context))
) ||
ref.left.op === 'load' && ref.left.ref.op !== 'load' &&
_call_search(ref.left.ref, context) &&
!_call_search(ref.left.ref, context).notFound && (
_call_search(ref.left.ref, context).op === 'func' ||
_call_search(ref.left.ref, context).op === 'methods' ||
typeof _call_search(ref.left.ref, context) === 'function' ||
Array.isArray(_call_search(ref.left.ref, context))
) ||
ref.left.op === 'load' && ref.left.ref.op === 'load' &&
ref.left.ref.ref.op !== 'load' &&
_call_search(ref.left.ref.ref, context) &&
!_call_search(ref.left.ref.ref, context).notFound (
_call_search(ref.left.ref.ref, context).op === 'func' ||
_call_search(ref.left.ref.ref, context).op === 'methods' ||
typeof _call_search(ref.left.ref.ref, context) === 'function' ||
Array.isArray(_call_search(ref.left.ref.ref, context))
) ||
false
// ref.left.op === 'load' && ref.left.ref.op === 'load' &&
// ref.left.ref.ref.op === 'load' && ref.left.ref.ref.ref.op !== 'load' &&
// _call_search(ref.left.ref.ref.ref, context) && (
// _call_search(ref.left.ref.ref.ref, context).op === 'func' ||
// _call_search(ref.left.ref.ref.ref, context).op === 'methods' ||
// typeof _call_search(ref.left.ref.ref.ref, context) === 'function' ||
// Array.isArray(_call_search(ref.left.ref.ref.ref, context))
// )
)
) {
const _left = _call_search(ref.left, context);
if (_left && !_left.notFound) {
return _left;
}
else {
const _right = _call_search(ref.right, context);
if (typeof _right === 'string') {
return null;
}
// return _right;
else if (
_right && !_right.notFound
// _right.op === 'func' || typeof _right === 'function'
) {
return _right;
}
return null;
}
}
else if (
ref.op === 'binary' && ref.operator === '||'
) {
return null;
}
else if (
ref.op === 'binary' &&
ref.operator === '-' &&
typeof _call_search(ref.left, context) === 'number' &&
typeof _call_search(ref.right, context) === 'number'
) {
const _left = _call_search(ref.left, context);
const _right = _call_search(ref.right, context);
return _left - _right;
}
else if (
ref.op === 'binary' &&
ref.operator === '+' &&
typeof _call_search(ref.left, context) === 'number' &&
typeof _call_search(ref.right, context) === 'number'
) {
const _left = _call_search(ref.left, context);
const _right = _call_search(ref.right, context);
return _left + _right;
}
else if (ref.op === 'read') {
if (
_compile_lookup(context.stack, context.scope, ref.name)[ref.name] &&
_compile_lookup(context.stack, context.scope, ref.name)[ref.name].op === 'literal'
) {
let maybeName = _compile_lookup(context.stack, context.scope, ref.name)[ref.name].value;
if (typeof maybeName === 'string') {
return local(maybeName);
}
return maybeName;
}
else {
let maybeName = _compile_lookup(context.stack, context.scope, ref.name)[ref.name];
if (typeof maybeName === 'string') {
return local(maybeName);
}
return maybeName;
}
}
else if (
ref.op === 'load' &&
_call_search(ref.ref, context) &&
(
ref.member.op === 'literal' ||
typeof _call_search(ref.member, context) === 'number' ||
typeof _call_search(ref.member, context) === 'string' ||
_call_search(ref.member, context) &&
!_call_search(ref.member, context).notFound
)
) {
let _ref = _call_search(ref.ref, context);
if (ref.member.op === 'literal' && ref.member.value === 'methods') {
return _ref;
}
if (_ref.op === 'methods') {
_ref = _ref.methods;
}
if (ref.member.op === 'literal') {
return _ref[ref.member.value];
}
else {
return _ref[_call_search(ref.member, context)];
}
}
else if (ref.op === 'methods') {
return ref.methods[context.func];
}
else {
return Object.assign(a.func([], [a.l(undefined)]), {notFound: true});
}
};
const _commas = (value, index, ary) => {
if (index < ary.length - 1) {
return expr([value, token(', ')]);
}
else {
return expr([value]);
}
};
const _semicolon = _expr => {
if (
!_expr.expr ||
_expr.expr &&
_expr.expr[_expr.expr.length - 1] &&
_expr.expr[_expr.expr.length - 1].token !== ';'
) {
return expr([_expr, token(';')]);
}
return _expr;
};
const _semicolons = (value, index, ary) => {
if (index < ary.length - 1) {
return expr([value, token(';')]);
}
else {
return expr([value]);
}
};
const _compile_call = ({write, compile, pointer, context}) => {
let func = _call_search(pointer.func, context);
if (func && func.op === 'methods') {
func = func.methods.main;
}
let _result;
if (
!func ||
typeof func === 'function' ||
typeof func === 'string' ||
func.op === 'literal' &&
func.value.op && func.value.op !== 'func' ||
func.op === 'literal' && !func.value.op ||
func.op === 'read' ||
func.type === 'local'
) {
let _func;
if (!func) {
_func = compile(pointer.func);
}
else if (func.op === 'literal' && func.value.op) {
_func = compile(func.value);
}
else if (func.op === 'literal') {
_func = literal(func.value);
}
else if (func.op === 'read' && pointer.func.op !== 'literal') {
_func = compile(pointer.func);
}
else if (func.op === 'read') {
_func = compile(func);
}
else if (func.type) {
_func = func;
}
else {
_func = literal(func.toString());
}
const _args = pointer.args.map(compile);
const _argsTaken = [];
const _argsTake = [];
_args.forEach(a => {
if (Result.hasResult(a)) {
_argsTaken.push(Result.taken(a));
_argsTake.push(Result.take(a));
}
else {
_argsTake.push(a);
}
});
if (_argsTaken.length) {
_result = expr([
..._argsTaken,
result([
token('('), _func, token(')('),
..._argsTake.map(_commas),
token(')'),
]),
]);
}
else {
_result = expr([
token('('), _func, token(')('),
..._argsTake.map(_commas),
token(')'),
]);
}
}
else {
if (func.op === 'methods') {
func = func.methods.main;
}
context.args = pointer.args.slice();
_result = compile(func);
context.args = null;
}
return _result;
};
const _push_stack = ({locals, scope, context}) => {
context.stack.push([locals, scope]);
context.locals = {};
context.scope = {};
};
const _pop_stack = ({context}) => {
const [locals, scope] = context.stack.pop();
context.locals = locals;
context.scope = scope;
};
const _pop_names = ({names}, _names) => {
_names.forEach(key => {
names[key].pop();
});
};
const _compile_func = ({write, compile, pointer, context, lookupScope}) => {
const args = context.args;
context.args = null;
if (!args) {
const oldLocals = context.locals;
const oldScope = context.scope;
const args = context.args;
_push_stack(context);
const body = pointer.body;
const _head = expr([
token('function('), ...pointer.args.map(arg => {
const name = _get_local_name(context, arg);
_set_scope_name(context, arg, name);
return local(name);
}).map(_commas), token(') '),
]);
let _body = compile(body);
_body.args = pointer.args;
const {options} = context;
const {passes = 10} = options;
const {rules: _rules = Object.keys(rules)} = options;
const ruleKeys = _rules
.concat(...(options._ruleSets || []).map(set => ruleSets[set]))
.filter((r, i, rules) => rules.indexOf(r, i + 1) === -1);
for (let i = 0; i < passes; ++i) {
for (let r = 0; r < ruleKeys.length; ++r) {
rules[ruleKeys[r]](_body);
}
}
// pop out all local declarations
const declares = pop(_body, node => node && node.type === 'declare');
// Make one large var a, b, ... declaration
if (declares.length) {
const oneDeclare = declares
.filter((d, i) => (
declares.slice(i + 1)
.findIndex(_d => d.local.name === _d.local.name) === -1
))
.reduce((carry, d) => {
carry.expr.push(d.local, token(', '));
return carry;
}, expr([]));
oneDeclare.expr.pop();
_body.expr.splice(0, 0, expr([token('var '), oneDeclare, token(';')]));
if (_body.expr.length > 1) {
_body.expr.splice(1, 0, token(' '));
}
}
// pop out all constants
const constants = pop(_body, node => node && node.type === 'constant');
if (constants.length) {
_body.expr.splice(0, 0, expr(constants.map(_semicolons)));
}
rules.markResults(_body);
if (Result.hasResult(_body)) {
const _bodyTaken = Result.taken(_body);
const _bodyTake = Result.take(_body);
_body = expr(
(_bodyTaken.expr ? _bodyTaken.expr : [_bodyTaken])
.concat(line(expr([token('return '), lineless(_bodyTake)])))
);
}
else if (_body.expr && _body.expr.length > 0) {
_body = expr(
_body.expr.slice(0, _body.expr.length - 1)
.concat(line(expr([token('return '), _body.expr[_body.expr.length - 1]])))
);
}
resultless(_head);
resultless(_body);
const _result = expr([
_head, token('{'),
_body,
token('}'),
]);
_pop_names(context, Object.keys(context.locals));
_pop_names(context, pointer.args);
_pop_stack(context);
return _result;
}
const oldScope = context.scope;
_push_stack(context);
const _args = [];
pointer.args.forEach((name, index) => {
let maybeCall;
try {
maybeCall = _call_search(args[index], context);
}
catch (e) {}
if (args[index].op === 'literal' && args[index].value.op === 'read') {
context.scope[name] =
lookupScope(args[index].value.name)[args[index].value.name];
}
else if (args[index].op === 'literal') {
context.scope[name] = args[index];
}
else if (args[index].op === 'read') {
context.scope[name] = oldScope[args[index].name];
}
else if (maybeCall && !maybeCall.notFound) {
if (maybeCall.type === 'local') {
context.scope[name] = maybeCall.name;
}
else {
context.scope[name] = maybeCall;
}
}
else {
// console.log(name, args[index]);
if (
args[index].op === 'binary' &&
args[index].operator === '||' &&
args[index].left.op === 'load' &&
args[index].left.ref.op === 'literal' &&
Array.isArray(args[index].left.ref.value)
) {
_args.push(a.write(name), args[index].right);
}
else {
_args.push(a.write(name, args[index]));
}
}
// console.log(name, context.scope[name], maybeCall, args[index]);
});
const _expr = compile(a.body(_args.concat(pointer.body.body)));
_pop_names(context, Object.keys(context.locals));
_pop_stack(context);
return _expr;
};
const _compile_methods = ({write, compile, pointer, func, options}) => (
pointer.methods.op ?
compile(pointer.methods) :
pointer.methods.main ?
compile(a.call(a.l(a.func([], [
a.w('f', pointer.methods.main),
...(Object.keys(pointer.methods).filter(m => m !== 'main'))
.filter(m => (options.methods || Object.keys(pointer.methods)).indexOf(m) !== -1)
.map(m => (
a.st(a.r('f'), a.l(m), pointer.methods[m])
)),
a.r('f'),
])), [])) :
compile(a.call(a.l(a.func([], [
a.w('f', a.l({})),
...Object.keys(pointer.methods)
.map(m => (
a.st(a.r('f'), a.l(m), pointer.methods[m])
)),
a.r('f'),
])), []))
);
const _compile_instr = {
literal: _compile_literal,
write: _compile_write,
read: _compile_read,
store: _compile_store,
load: _compile_load,
unary: _compile_unary,
binary: _compile_binary,
for_of: _compile_for_of,
not_last: _compile_not_last,
branch: _compile_branch,
loop: _compile_loop,
body: _compile_body,
call: _compile_call,
func: _compile_func,
methods: _compile_methods,
};
const _compile = (context, next) => {
context._pointer.push(context.pointer);
context.pointer = next;
const result = _compile_instr[next.op](context);
context.pointer = context._pointer.pop();
return result;
};
function any(node, fn, stack = []) {
if (node.type === 'expression') {
stack.push(node);
for (let i = node.expr.length - 1; i >= 0; --i) {
if (any(node.expr[i], fn, stack)) {
return true;
}
else if (fn(node.expr[i], i, node, stack)) {
return true;
}
}
stack.pop();
}
return false;
}
function pop(node, fn, stack = []) {
let result = [];
if (node && node.type === 'expression') {
stack.push(node);
for (let i = node.expr.length - 1; i >= 0; --i) {
result = result.concat(pop(node.expr[i], fn, stack));
const replace = fn(node.expr[i], i, node, stack);
if (replace) {
result.push(node.expr[i]);
if (typeof replace === 'object' && replace.type) {
node.expr.splice(i, 1, replace);
}
else {
node.expr.splice(i, 1);
}
}
}
stack.pop();
}
return result;
}
const rules = {
markResults: ast => {
ast.result = ast instanceof Result;
any(ast, (node, i, parent, stack) => {
node.result = Boolean(stack.find(n => n.result || n instanceof Result)) || node.result || node instanceof Result;
});
},
flattenLines: ast => (
pop(ast, (node, i, parent) => (
parent instanceof Line &&
parent.expr.length === 1 &&
node.expr &&
node.expr.length === 1 &&
node.expr[0]
)),
pop(ast, (node, i, parent) => (
node instanceof Line &&
node.expr.length === 1 &&
node.expr[0].expr &&
!any(node.expr[0], (n, j, p) => (
p === node.expr[0] && node instanceof Line
)) &&
new Line({type: 'expression', expr: node.expr[0].expr})
)),
pop(ast, (node, i, parent) => (
node instanceof Line &&
node.expr.length === 1 &&
node.expr[0].expr &&
!node.expr[0].expr.find(n => !(n instanceof Line)) &&
node.expr[0]
)),
pop(ast, (node, i, parent) => (
node.expr &&
!(node instanceof Line) &&
node.expr.find(n => n instanceof Line) &&
node.expr.find(n => (
n.expr && !(n instanceof Line) && n.expr.find(_n => _n instanceof Line)
)) &&
expr(node.expr.reduce((carry, n) => {
if (n.expr && !(n instanceof Line)) {
carry.push(...n.expr);
}
else {
carry.push(n);
}
return carry;
}, []))
)),
pop(ast, (node, i, parent) => (
node.expr && !(node instanceof Line) &&
node.expr.length === 1 &&
node.expr[0].expr && !(node.expr[0] instanceof Line) &&
!(node.expr[0].expr.find(n => !(n instanceof Line))) &&
node.expr[0]
)),
pop(ast, node => (
node instanceof Line &&
!node.expr.find(n => !n.expr) &&
!node.expr.find(n => n instanceof Line) &&
node.expr.find(n => n.expr.find(_n => !_n instanceof Line)) &&
expr(node.expr.reduce((carry, n) => {
carry.push(...n.expr);
return carry;
}, []))
))
),
collapseEmpty: ast => (
pop(ast, (node, i, parent) => (
node.expr && node.expr.length === 0
))
),
collapse1LenExprAfterWrite: ast => (
pop(ast, (node, i, parent) => (
node.token === ' = ' &&
parent[i + 1] &&
parent[i + 1].expr &&
parent[i + 1].expr.length === 1 &&
parent[i + 1].expr[0]
))
),
collapseLines: ast => (
pop(ast, node => (
node.expr &&
node.expr.length === 1 &&
node.expr[0] instanceof Line &&
node.expr[0]
))
),
collapseExprAroundLocal: ast => (
pop(ast, node => (
node.expr &&
node.expr.length === 1 &&
node.expr[0].name &&
!isResult(node) &&
node.expr[0]
))
),
collapseExprAroundLiteral: ast => (
pop(ast, node => (
node.expr &&
node.expr.length === 1 &&
node.expr[0].type === 'literal' &&
!isResult(node) &&
node.expr[0]
))
),
eliminateMul0: ast => (
pop(ast, node => (
node.op &&
node.op.token === ' * ' && (
node.left.value === 0 ||
node.right.value === 0
) &&
literal(0)
))
),
reduceMul1: ast => (
pop(ast, node => (
node.op &&
node.op.token === ' * ' && (
node.left.value === 1 &&
node.right ||
node.right.value === 1 &&
node.left
)
))
),
reduceAdd0: ast => (
pop(ast, node => (
node.type === 'expression' &&
node.expr.find(n => n.token === ' + ') &&
node.expr.find(n => n.value === 0) &&
node.expr.find(n => n.value !== 0 && n.type !== 'token')
))
),
reduceOpLiteral: ast => (
pop(ast, node => (
node.type === 'expression' &&
node.expr.length === 5 &&
node.expr[1].type === 'literal' &&
node.expr[3].type === 'literal' && (
node.expr[2].token === ' + ' &&
literal(JSON.stringify(JSON.parse(node.expr[1].value) + JSON.parse(node.expr[3].value))) ||
node.expr[2].token === ' - ' &&
literal(JSON.stringify(JSON.parse(node.expr[1].value) - JSON.parse(node.expr[3].value))) ||
node.expr[2].token === ' * ' &&
literal(JSON.stringify(JSON.parse(node.expr[1].value) * JSON.parse(node.expr[3].value))) ||
node.expr[2].token === ' / ' &&
literal(JSON.stringify(JSON.parse(node.expr[1].value) / JSON.parse(node.expr[3].value)))
)
))
),
emulate: ast => {
// - track top and inner args
// - if an expression uses only tokens, literals, top and inner args, lift
// it to a new variable and replace its use with that variable.
class EmulateScopeInner {
constructor(parent = null, depth = 0) {
this.map = {};
this.args = {};
this.parent = parent;
this.child = null;
this.depth = depth;
}
read(name, direction = 0) {
if (this.map[name] && this.map[name][1]) {
this.map[name][1]._read = (this.map[name][1]._read || 0) + 1;
}
if (this.parent && direction !== 1) {
this.parent.read(name, -1);
}
if (this.child && direction !== -1) {
this.child.read(name, 1);
}
}
get(name) {
return this.map[name] ?
this.map[name][0] :
null;
}
set(name, value, parent, direction = 0) {
if (direction === 0 || this.map[name]) {
this.map[name] = [
value,
parent || this.map[name][1]
];
}
if (this.parent && direction !== 1) {
this.parent.set(name, null, null, -1);
}
if (this.child && direction !== -1) {
this.child.set(name, null, null, 1);
}
}
_nullAll(direction = 0) {
for (const name in this.map) {
this.map[name] = [null, this.map[name][1]];
}
if (this.parent && direction !== 1) {
this.parent._nullAll(-1);
}
if (this.child && direction !== -1) {
this.child._nullAll(1);
}
}
isArg(name) {
if (Boolean(this.args[name])) {
return true;
}
if (this.parent) {
return this.parent.isArg(name);
}
return false;
}
setArg(name) {
this.args[name] = true;
}
push() {
if (!this.child) {
this.child = new EmulateScopeInner(this, this.depth + 1);
}
else {
for (const name in this.child.map) {
this.child.map[name][1]._read = (this.child.map[name][1]._read || 0) + 1;
this.child.map[name] = [null, this.child.map[name][1]];
}
}
return this.child;
}
pop() {
return this.parent;
}
}
class EmulateScope {
constructor() {
this.root = this.inner = new EmulateScopeInner();
}
read(name) {
this.inner.read(name);
}
get(name) {
return this.inner.get(name);
}
set(name, value, parent) {
return this.inner.set(name, value, parent);
}
isArg(name) {
return this.inner.isArg(name);
}
setArg(name) {
this.inner.setArg(name);
}
push() {
this.inner = this.inner.push();
}
pop() {
this.inner = this.inner.pop();
}
}
let scope = new EmulateScope();
let readScope = {};
for (let i = 0; ast.args && i < ast.args.length; i++) {
scope.setArg(ast.args[i]);
}
const anyLocal = function(node) {
if (node.name) {
return true;
}
if (node.expr) {
for (let i = 0; i < node.expr.length; i++) {
if (anyLocal(node.expr[i])) {
return true;
}
}
}
return false;
};
const hasLocal = function(name, node) {
if (node.name === name) {
return true;
}
if (node.expr) {
for (let i = 0; i < node.expr.length; i++) {
if (hasLocal(name, node.expr[i])) {
return true;
}
}
}
return false;
};
const distinctLocals = function(node, locals = []) {
if (node && node.name) {
if (locals.indexOf(node.name) === -1) {
locals.push(node.name);
return 1;
}
return 0;
}
if (node && node.expr) {
let nLocals = 0;
for (let i = 0; i < node.expr.length; i++) {
if (node.expr[i] === node) {throw new Error('circular');}
nLocals += distinctLocals(node.expr[i], locals);
}
return nLocals;
}
return 0;
};
const markRead = function(node, i, parent) {
if (node.name) {
if (
(
parent.expr[i + 1] && parent.expr[i + 1].token !== ' = ' ||
!parent.expr[i + 1]
)
) {
scope.read(node.name);
}
}
};
const _argConstant = (node, scope, tokenWhitelist) => {
if (!node) {
return false;
}
if (node.constant) {
return false;
}
if (
tokenWhitelist.indexOf(node.token) !== -1 ||
node.name && scope.isArg(node.name) ||
typeof node.value !== 'undefined' && !(node instanceof Constant) ||
node.name && node.name.indexOf('_arg_constant') !== -1
) {
return true;
}
if (node.expr) {
for (let i = 0; i < node.expr.length; i++) {
if (_argConstant(node.expr[i], scope, tokenWhitelist) === false) {
return false;
}
}
return true;
}
return false;
};
const argConstant = (node, scope) => {
if (node.expr && node.expr[0].token === 'Math.min(') {
return _argConstant(node, scope, ['Math.min(', ', ', ')']);
}
if (node.expr && node.expr.find(n => n.token === ')(')) {
return _argConstant(node, scope, ['(', ')', ')(', ', ']);
}
return _argConstant(node, scope, ['(', ')', ')(']);
};
const count = (node, fn) => {
let n = 0;
if (fn(node)) {
n += 1;
}
if (node.expr) {
for (let i = 0; i < node.expr.length; i++) {
n += count(node.expr[i], fn);
}
}
return n;
};
const constants = [];
let nextConstant = count(ast, node => node.constant);
const run = function(node, i, parent) {
if (!node) {return;}
markRead(node, i, parent);
if (node.expr) {
if (node.expr.find(n => n.token === 'function(')) {
for (let j = 0; j < node.expr.length; j++) {
if (node.expr[j].name) {
scope.setArg(node.expr[j].name);
}
}
}
else {
for (let j = 0; j < node.expr.length; j++) {
markRead(node.expr[j], j, node);
}
}
}
if (node.token === '{') {
scope.push();
}
else if (node.token === '}') {
scope.pop();
}
else if (node.type === 'local') {
if (scope.get(node.name)) {
return lineless(scope.get(node.name));
}
return node;
}
if (node.type === 'literal') {
return node;
}
if (node.expr) {
if (
argConstant(node, scope) &&
count(node, n => (
!n.name && !n.expr
)) &&
count(node, n => (
n.name && scope.isArg(n.name)
))
) {
if (constants.find(c => String(node) === c[1])) {
const c = constants.find(c => String(node) === c[1]);
node.expr = [c[0]];
return c[0];
}
const name = local(`_arg_constant${nextConstant++}`);
const constant = [name, String(node), line(expr([declare(name), name, token(' = '), expr(node.expr)]))];
any(constant[2], node => {
node.constant = true;
});
constants.push(constant);
node.expr = [name]
return name;
}
if (
node.expr[0] && node.expr[0].name &&
node.expr[1] && node.expr[1].token === ' = '
) {
const _result = run(node.expr[2], 2, node);
if (scope.get(node.expr[0].name) === _result) {
node.expr = [];
return;
}
else if (_result && !hasLocal(node.expr[0].name, _result)) {
if (scope.get(node.expr[0].name) === _result) {
throw new Error('setting identical value already at variable');
}
node.lastRead = node._read;
node.read = 0;
node._read = 0;
scope.set(node.expr[0].name, _result, node);
}
else {
node.lastRead = node._read;
node.read = 0;
node._read = 0;
scope.set(node.expr[0].name, null, node);
}
}
else if (
node.expr[1] && node.expr[1].name &&
node.expr[2] && node.expr[2].token === ' = '
) {
if (node._read === 0) {
node.expr = [];
return;
}
const _result = run(node.expr[3], 3, node);
if (scope.get(node.expr[1].name) === _result) {
node.expr = [];
return;
}
else if (_result && !hasLocal(node.expr[1].name, _result)) {
if (scope.get(node.expr[1].name) === _result) {
throw new Error('setting identical value already at variable');
}
else {
node.lastRead = node._read;
node.read = 0;
node._read = 0;
scope.set(node.expr[1].name, _result, node);
}
}
else {
node.lastRead = node._read;
node.read = 0;
node._read = 0;
scope.set(node.expr[1].name, null, node);
}
}
else if (node.expr[2] && node.expr[2].token === ' = ') {
run(node.expr[0], 0, node);
const _result = run(node.expr[3], 3, node);
if (_result) {
node.expr[3] = _result;
}
}
else if (
node.expr.length === 3 &&
node.expr[0].token === '(' && node.expr[2].token === ')'
) {
return run(node.expr[1], 1, node);
}
else if (node.expr.length === 1 && node.expr[0].type === 'local') {
return run(node.expr[0], 0, node);
}
else if (node.expr.length === 1 && node.expr[0].type === 'literal') {
return node.expr[0];
}
else if (node.expr.length === 1) {
return run(node.expr[0], 0, node);
}
else if (
node.expr[2] && (
node.expr[2].token === ' - ' ||
node.expr[2].token === ' * ' ||
node.expr[2].token === ' / '
)
) {
let run1, run3;
if (node.expr[1].type === 'expression') {
if (node.expr[1] === node) {throw new Error('circular expression');}
run1 = run(node.expr[1], 1, node);
if (run1 === node) {throw new Error('returned circular expr1')}
if (run1) {
node.expr[1] = run1;
}
}
else if (node.expr[1].type === 'local') {
if (node.expr[1].name && scope.get(node.expr[1].name)) {
run1 = run(node.expr[1], 1, node);
if (run1 === node) {throw new Error('returned circular local1')}
if (run1 && run1 !== node) {
node.expr[1] = run1;
}
}
}
if (node.expr[3].type === 'expression') {
run3 = run(node.expr[3], 3, node);
if (run3 === node) {throw new Error('returned circular expr3')}
if (run3) {
node.expr[3] = run3;
}
}
else if (node.expr[3].type === 'local') {
if (node.expr[3].name && scope.get(node.expr[3].name)) {
run3 = run(node.expr[3], 3, node);
if (run3 === node) {throw new Error('returned circular local3')}
if (run3) {
node.expr[3] = run3;
}
}
}
if (
node.expr[1] && node.expr[1].type === 'literal' &&
node.expr[3] && node.expr[3].type === 'literal'
) {
if (node.expr[2].token === ' - ') {
return literal(
JSON.stringify(
JSON.parse(node.expr[1].value) -
JSON.parse(node.expr[3].value)
)
);
}
if (node.expr[2].token === ' * ') {
return literal(
JSON.stringify(
JSON.parse(node.expr[1].value) *
JSON.parse(node.expr[3].value)
)
);
}
if (node.expr[2].token === ' / ') {
return literal(
JSON.stringify(
JSON.parse(node.expr[1].value) /
JSON.parse(node.expr[3].value)
)
);
}
}
if (
node.expr[1] && node.expr[1].type === 'literal'
) {
if (
node.expr[2].token === ' * ' &&
JSON.parse(node.expr[1].value) === 1
) {
return node.expr[3];
}
if (
node.expr[2].token === ' * ' &&
JSON.parse(node.expr[1].value) === 0
) {
return node.expr[1];
}
}
if (
node.expr[3] && node.expr[3].type === 'literal'
) {
if (
node.expr[2].token === ' * ' &&
JSON.parse(node.expr[3].value) === 1
) {
return node.expr[1];
}
if (
node.expr[2].token === ' * ' &&
JSON.parse(node.expr[3].value) === 0
) {
return node.expr[3];
}
}
}
else if (node.expr[2] && node.expr[2].token === ' + ') {
let run1, run3;
if (node.expr[1].type === 'expression') {
if (node.expr[1] === node) {throw new Error('circular expression');}
run1 = run(node.expr[1], 1, node);
if (run1 === node) {throw new Error('returned circular expr1')}
if (run1) {
node.expr[1] = run1;
}
}
else if (node.expr[1].type === 'local') {
if (node.expr[1].name && scope.get(node.expr[1].name)) {
run1 = run(node.expr[1], 1, node);
if (run1 === node) {throw new Error('returned circular local1')}
if (run1 && run1 !== node) {
node.expr[1] = run1;
}
}
}
if (node.expr[3].type === 'expression') {
run3 = run(node.expr[3], 3, node);
if (run3 === node) {throw new Error('returned circular expr3')}
if (run3) {
node.expr[3] = run3;
}
}
else if (node.expr[3].type === 'local') {
if (node.expr[3].name && scope.get(node.expr[3].name)) {
run3 = run(node.expr[3], 3, node);
if (run3 === node) {throw new Error('returned circular local3')}
if (run3) {
node.expr[3] = run3;
}
}
}
if (
node.expr[1] && node.expr[1].type === 'literal' &&
node.expr[3] && node.expr[3].type === 'literal'
) {
return literal(
JSON.stringify(
JSON.parse(node.expr[1].value) +
JSON.parse(node.expr[3].value)
)
);
}
if (
node.expr[1] && node.expr[1].type === 'expression' &&
node.expr[3] && node.expr[3].type === 'literal'
) {
if (
node.expr[1].expr[2] && node.expr[1].expr[2].token === ' + ' &&
node.expr[1].expr[3] && node.expr[1].expr[3].type === 'literal'
) {
return expr([
node.expr[1].expr[0],
node.expr[1].expr[1],
node.expr[1].expr[2],
run(expr([
node.expr[1].expr[0],
node.expr[1].expr[3],
node.expr[1].expr[2],
node.expr[3],
node.expr[1].expr[4],
]), 3, node.expr[1]),
node.expr[1].expr[4],
]);
}
if (node.expr[3].value === '""' && node.expr[1]) {
return node.expr[1];
}
}
if (
node.expr[1] && node.expr[1].type === 'literal' &&
node.expr[3] && node.expr[3].type === 'expression'
) {
if (
node.expr[3].expr[2] && node.expr[3].expr[2].token === ' + ' &&
node.expr[3].expr[1] && node.expr[3].expr[1].type === 'literal'
) {
return expr([
node.expr[3].expr[0],
run(expr([
node.expr[3].expr[0],
node.expr[1],
node.expr[3].expr[2],
node.expr[3].expr[1],
node.expr[3].expr[4],
]), 1, node.expr[3]),
node.expr[3].expr[2],
node.expr[3].expr[3],
node.expr[3].expr[4],
]);
}
if (node.expr[1].value === '""' && node.expr[3]) {
return node.expr[3];
}
}
return node;
}
else if (node.type !== 'declare') {
for (let i = 0; i < node.expr.length; i++) {
try {
run(node.expr[i], i, node);
}
catch (e) {
console.error(String(node.expr[i]));
console.error(e.stack || e);
throw e;
}
}
}
}
};
run(ast);
ast.expr.splice(0, 0, ...constants.map(c => c[2]));
},
eliminateCanceledTerms: ast => (
pop(ast, node => (
node.op &&
node.op.token === ' + ' &&
node.left.op &&
node.left.op.token === ' - ' &&
node.left.right.name === node.right.name &&
node.left.left
))
),
collapseParantheses: ast => (
// flatten extra parantheses
pop(ast, node => (
node.type === 'expression' &&
node.expr.length === 3 &&
node.expr[0].token === '(' &&
node.expr[2].token === ')' &&
node.expr[1].type === 'expression' &&
node.expr[1].expr.length >= 3 &&
node.expr[1].expr[0].token === '(' &&
node.expr[1].expr[node.expr[1].expr.length - 1].token === ')' &&
expr(node.expr[1].expr.slice(1, node.expr[1].expr.length - 1))
))
),
combineCommaWithExpr: ast => (
// flatten expr([expr([...]), token(,)]) into expr([..., token(,)])
pop(ast, node => (
node.type === 'expression' &&
node.expr.length === 2 &&
node.expr[0].type === 'expression' &&
node.expr[1].token === ', ' && (
node.expr[0].expr.length === 1 &&
node.expr[0].expr[0].type === 'expression' && (
node.expr[0].expr[0].length === 1 &&
node.expr[0].expr[0].expr[0].type === 'expression' &&
expr(node.expr[0].expr[0].expr[0].expr.concat(token(', '))) ||
expr(node.expr[0].expr[0].expr.concat(token(', ')))
) ||
expr(node.expr[0].expr.concat(token(', ')))
)
))
),
replaceMirrorAssignments: ast => {
// remove assignments that are directly set from another local variable
ast.depth = 0;
any(ast, (node, i, parent) => {
node.depth = parent.depth + 1;
});
pop(ast, node => (
node.type === 'declare' &&
any(ast, n => (
n.type === 'declare' &&
n.local === node.local &&
n.depth < node.depth
))
));
any(ast, (node, i, parent) => {
if (node.expr && node.expr.length === 1 && node.expr[0].expr && node.expr[0].expr.find(n => n.type === 'declare')) {
const declare = node.expr[0].expr && node.expr[0].expr.find(n => n.type === 'declare');
let writes = 0;
any(parent, (n, j, p) => {
if (
n.name === declare.local.name &&
p.expr[j + 1] && p.expr[j + 1].token === ' = '
) {
writes += 1;
}
});
if (
writes === 1 &&
node.expr[0].expr.length === 4 && node.expr[0].expr[3].type === 'local'
) {
const right = node.expr[0].expr[3].name;
pop(parent, n => (
n.name === declare.local.name &&
local(right)
));
}
}
});
},
removeEqualAssignments: ast => (
pop(ast, (node, i, parent) => (
node.type === 'expression' && (
node.expr.length === 4 &&
node.expr[1].type === 'local' &&
node.expr[2].token === ' = ' &&
node.expr[3].type === 'local' &&
node.expr[1].name === node.expr[3].name ||
node.expr.length === 3 &&
node.expr[0].type === 'local' &&
node.expr[1].token === ' = ' &&
node.expr[2].type === 'local' &&
node.expr[0].name === node.expr[2].name
)
))
),
};
const ruleSets = {
symantics: [
'collapseExpressions',
'collapseParantheses',
'combineCommaWithExpr',
'removeEqualAssignments'
],
math: [
'eliminateCanceledTerms',
'eliminateMul0',
'reduceAdd0',
'reduceMul1',
],
locals: [
'removeUnusedLocals',
'replaceMirrorAssignments',
],
};
/**
*
* @param options {object}
* @param options.passes {number}
* @param options.rules {string[]}
* @param options.ruleSets {string[]}
*/
const compile = (node, options = {}) => {
// given a function generator. call it with the stated arguments and compile
// of a function that will generate a function that works the same as a fully
// declared and inline-able function.
if (typeof node === 'function' && node.args) {
node = a.func(node.args[0], [
node(...node.args.slice(1)),
]);
}
else {
node = a.func([], [node]);
}
const context = {
_body: '',
_pointer: [],
pointer: null,
stack: [],
args: null,
scope: {},
locals: {},
names: {},
vars: [],
options,
};
context.context = context;
context.compile = _compile.bind(null, context);
context.lookupScope = name => _compile_lookup(context.stack, context.scope, name);
const ast = context.compile(node);
// finally, create the function
let f;
try {
f = new Function('return ' + ast.toString() + ';')();
}
catch (e) {
console.log(e.stack || e);
console.log(`${ast.toString()}`);
// console.log(JSON.stringify(ast));
throw e;
}
f.toAst = () => ast;
return f;
};
module.exports = compile;