ben-sb-shift-codegen
Version:
code generator for Shift format ASTs (forked for use in https://github.com/ben-sb/javascript-deobfuscator)
399 lines (354 loc) • 7.76 kB
JavaScript
const Precedence = {
Sequence: 0,
Yield: 1,
Assignment: 1,
Conditional: 2,
ArrowFunction: 2,
LogicalOR: 3,
LogicalAND: 4,
BitwiseOR: 5,
BitwiseXOR: 6,
BitwiseAND: 7,
Equality: 8,
Relational: 9,
BitwiseSHIFT: 10,
Additive: 11,
Multiplicative: 12,
Exponential: 13,
Prefix: 14,
Postfix: 15,
New: 16,
Call: 17,
TaggedTemplate: 18,
Member: 19,
Primary: 20,
};
const BinaryPrecedence = {
',': Precedence.Sequence,
'||': Precedence.LogicalOR,
'&&': Precedence.LogicalAND,
'|': Precedence.BitwiseOR,
'^': Precedence.BitwiseXOR,
'&': Precedence.BitwiseAND,
'==': Precedence.Equality,
'!=': Precedence.Equality,
'===': Precedence.Equality,
'!==': Precedence.Equality,
'<': Precedence.Relational,
'>': Precedence.Relational,
'<=': Precedence.Relational,
'>=': Precedence.Relational,
'in': Precedence.Relational,
'instanceof': Precedence.Relational,
'<<': Precedence.BitwiseSHIFT,
'>>': Precedence.BitwiseSHIFT,
'>>>': Precedence.BitwiseSHIFT,
'+': Precedence.Additive,
'-': Precedence.Additive,
'*': Precedence.Multiplicative,
'%': Precedence.Multiplicative,
'/': Precedence.Multiplicative,
'**': Precedence.Exponential,
};
function getPrecedence(node) {
switch (node.type) {
case 'ArrayExpression':
case 'FunctionExpression':
case 'ClassExpression':
case 'IdentifierExpression':
case 'AssignmentTargetIdentifier':
case 'NewTargetExpression':
case 'Super':
case 'LiteralBooleanExpression':
case 'LiteralNullExpression':
case 'LiteralNumericExpression':
case 'LiteralInfinityExpression':
case 'LiteralRegExpExpression':
case 'LiteralStringExpression':
case 'ObjectExpression':
case 'ThisExpression':
case 'SpreadElement':
case 'FunctionBody':
return Precedence.Primary;
case 'ArrowExpression':
case 'AssignmentExpression':
case 'CompoundAssignmentExpression':
case 'YieldExpression':
case 'YieldGeneratorExpression':
return Precedence.Assignment;
case 'ConditionalExpression':
return Precedence.Conditional;
case 'ComputedMemberExpression':
case 'StaticMemberExpression':
case 'ComputedMemberAssignmentTarget':
case 'StaticMemberAssignmentTarget':
switch (node.object.type) {
case 'CallExpression':
case 'ComputedMemberExpression':
case 'StaticMemberExpression':
case 'TemplateExpression':
return getPrecedence(node.object);
default:
return Precedence.Member;
}
case 'TemplateExpression':
if (node.tag == null) return Precedence.Member;
switch (node.tag.type) {
case 'CallExpression':
case 'ComputedMemberExpression':
case 'StaticMemberExpression':
case 'TemplateExpression':
return getPrecedence(node.tag);
default:
return Precedence.Member;
}
case 'BinaryExpression':
return BinaryPrecedence[node.operator];
case 'CallExpression':
return Precedence.Call;
case 'NewExpression':
return node.arguments.length === 0 ? Precedence.New : Precedence.Member;
case 'UpdateExpression':
return node.isPrefix ? Precedence.Prefix : Precedence.Postfix;
case 'AwaitExpression':
case 'UnaryExpression':
return Precedence.Prefix;
default:
throw new Error('unreachable: ' + node.type);
}
}
function escapeStringLiteral(stringValue) {
let result = '';
let nSingle = 0, nDouble = 0;
for (let i = 0, l = stringValue.length; i < l; ++i) {
let ch = stringValue[i];
if (ch === '"') {
++nDouble;
} else if (ch === '\'') {
++nSingle;
}
}
let delim = nDouble > nSingle ? '\'' : '"';
result += delim;
for (let i = 0; i < stringValue.length; i++) {
let ch = stringValue.charAt(i);
switch (ch) {
case delim:
result += '\\' + delim;
break;
case '\n':
result += '\\n';
break;
case '\r':
result += '\\r';
break;
case '\\':
result += '\\\\';
break;
case '\u2028':
result += '\\u2028';
break;
case '\u2029':
result += '\\u2029';
break;
default:
result += ch;
break;
}
}
result += delim;
return result;
}
class CodeRep {
constructor() {
this.containsIn = false;
this.containsGroup = false;
// restricted lookaheads: {, function, class, let, let [
this.startsWithCurly = false;
this.startsWithFunctionOrClass = false;
this.startsWithLet = false;
this.startsWithLetSquareBracket = false;
this.endsWithMissingElse = false;
}
forEach(f) {
// Call a function on every CodeRep represented by this node. Always calls f on a node and then its children, so if you're careful you can modify a node's children online.
f(this);
}
}
class Empty extends CodeRep {
constructor() {
super();
}
emit() {}
}
class Token extends CodeRep {
constructor(token, isRegExp = false) {
super();
this.token = token;
this.isRegExp = isRegExp;
}
emit(ts) {
ts.put(this.token, this.isRegExp);
}
}
class RawToken extends CodeRep {
constructor(token) {
super();
this.token = token;
}
emit(ts) {
ts.putRaw(this.token);
}
}
class NumberCodeRep extends CodeRep {
constructor(number) {
super();
this.number = number;
}
emit(ts) {
ts.putNumber(this.number);
}
}
class Paren extends CodeRep {
constructor(expr) {
super();
this.expr = expr;
}
emit(ts) {
ts.put('(');
this.expr.emit(ts, false);
ts.put(')');
}
forEach(f) {
f(this);
this.expr.forEach(f);
}
}
class Bracket extends CodeRep {
constructor(expr) {
super();
this.expr = expr;
}
emit(ts) {
ts.put('[');
this.expr.emit(ts, false);
ts.put(']');
}
forEach(f) {
f(this);
this.expr.forEach(f);
}
}
class Brace extends CodeRep {
constructor(expr) {
super();
this.expr = expr;
}
emit(ts) {
ts.put('{');
this.expr.emit(ts, false);
ts.put('}');
}
forEach(f) {
f(this);
this.expr.forEach(f);
}
}
class NoIn extends CodeRep {
constructor(expr) {
super();
this.expr = expr;
}
emit(ts) {
this.expr.emit(ts, true);
}
forEach(f) {
f(this);
this.expr.forEach(f);
}
}
class ContainsIn extends CodeRep {
constructor(expr) {
super();
this.expr = expr;
}
emit(ts, noIn) {
if (noIn) {
ts.put('(');
this.expr.emit(ts, false);
ts.put(')');
} else {
this.expr.emit(ts, false);
}
}
forEach(f) {
f(this);
this.expr.forEach(f);
}
}
class Seq extends CodeRep {
constructor(children) {
super();
this.children = children;
}
emit(ts, noIn) {
this.children.forEach(cr => cr.emit(ts, noIn));
}
forEach(f) {
f(this);
this.children.forEach(x => x.forEach(f));
}
}
class Semi extends Token {
constructor() {
super(';');
}
}
class CommaSep extends CodeRep {
constructor(children) {
super();
this.children = children;
}
emit(ts, noIn) {
let first = true;
this.children.forEach(cr => {
if (first) {
first = false;
} else {
ts.put(',');
}
cr.emit(ts, noIn);
});
}
forEach(f) {
f(this);
this.children.forEach(x => x.forEach(f));
}
}
class SemiOp extends CodeRep {
constructor() {
super();
}
emit(ts) {
ts.putOptionalSemi();
}
}
module.exports = {
Precedence,
getPrecedence,
escapeStringLiteral,
CodeRep,
Empty,
Token,
RawToken,
NumberCodeRep,
Paren,
Bracket,
Brace,
NoIn,
ContainsIn,
Seq,
Semi,
CommaSep,
SemiOp,
};