sensortoy/www/libs/bluebird/tools/ast_passes.js
Martin Donnelly 939a7aff4c init
2016-05-20 17:10:40 +01:00

709 lines
22 KiB
JavaScript

//All kinds of conversion passes over the source code
var jsp = require("acorn");
var walk = require("acorn/util/walk.js");
var rnonIdentMember = /[.\-_$a-zA-Z0-9]/g;
var global = new Function("return this")();
function equals( a, b ) {
if( a.type === b.type ) {
if( a.type === "MemberExpression" ) {
return equals( a.object, b.object ) &&
equals( a.property, b.property );
}
else if( a.type === "Identifier" ) {
return a.name === b.name;
}
else if( a.type === "ThisExpression" ) {
return true;
}
else {
console.log("equals", a, b);
unhandled();
}
}
return false;
}
function getReceiver( expr ) {
if( expr.type === "MemberExpression" ) {
return expr.object;
}
return null;
}
function nodeToString( expr ) {
if( expr == null || typeof expr !== "object" ) {
if( expr === void 0 ) {
return "void 0";
}
else if( typeof expr === "string" ) {
return '"' + safeToEmbedString(expr) + '"';
}
return ("" + expr);
}
if( expr.type === "Identifier" ) {
return expr.name;
}
else if( expr.type === "MemberExpression" ) {
if( expr.computed )
return nodeToString( expr.object ) + "[" + nodeToString( expr.property ) + "]";
else
return nodeToString( expr.object ) + "." + nodeToString( expr.property );
}
else if( expr.type === "UnaryExpression" ) {
if( expr.operator === "~" ||
expr.operator === "-" ||
expr.operator === "+" ||
expr.operator === "-" ) {
return expr.operator + nodeToString( expr.argument );
}
return "(" + expr.operator + " " + nodeToString( expr.argument ) + ")";
}
else if( expr.type === "Literal" ) {
return expr.raw;
}
else if( expr.type === "BinaryExpression" || expr.type === "LogicalExpression" ) {
return "("+nodeToString(expr.left) + " " +
expr.operator + " " +
nodeToString(expr.right) + ")";
}
else if( expr.type === "ThisExpression" ) {
return "this";
}
else if( expr.type === "ObjectExpression") {
var props = expr.properties;
var ret = [];
for( var i = 0, len = props.length; i < len; ++i ) {
var prop = props[i];
ret.push( nodeToString(prop.key) + ": " + nodeToString(prop.value));
}
return "({"+ret.join(",\n")+"})";
}
else if( expr.type === "NewExpression" ) {
return "new " + nodeToString(expr.callee) + "(" + nodeToString(expr.arguments) +")";
}
//assuming it is arguments
else if( Array.isArray( expr ) ) {
var tmp = [];
for( var i = 0, len = expr.length; i < len; ++i ) {
tmp.push( nodeToString(expr[i]) );
}
return tmp.join(", ");
}
else if( expr.type === "FunctionExpression" ) {
var params = [];
for( var i = 0, len = expr.params.length; i < len; ++i ) {
params.push( nodeToString(expr.params[i]) );
}
}
else if( expr.type === "BlockStatement" ) {
var tmp = [];
for( var i = 0, len = expr.body.length; i < len; ++i ) {
tmp.push( nodeToString(expr.body[i]) );
}
return tmp.join(";\n");
}
else if( expr.type === "CallExpression" ) {
var args = [];
for( var i = 0, len = expr.arguments.length; i < len; ++i ) {
args.push( nodeToString(expr.arguments[i]) );
}
return nodeToString( expr.callee ) + "("+args.join(",")+")";
}
else {
console.log( "nodeToString", expr );
unhandled()
}
}
function DynamicCall( receiver, fnDereference, arg, start, end ) {
this.receiver = receiver;
this.fnDereference = fnDereference;
this.arg = arg;
this.start = start;
this.end = end;
}
DynamicCall.prototype.toString = function() {
return nodeToString(this.fnDereference) + ".call(" +
nodeToString(this.receiver) + ", " +
nodeToString(this.arg) +
")";
};
function DirectCall( receiver, fnName, arg, start, end ) {
this.receiver = receiver;
this.fnName = fnName;
this.arg = arg;
this.start = start;
this.end = end;
}
DirectCall.prototype.toString = function() {
return nodeToString(this.receiver) + "." + nodeToString(this.fnName) +
"(" + nodeToString(this.arg) + ")"
};
function ConstantReplacement( value, start, end ) {
this.value = value;
this.start = start;
this.end = end;
}
ConstantReplacement.prototype.toString = function() {
return nodeToString(this.value);
};
function Empty(start, end) {
this.start = start;
this.end = end;
}
Empty.prototype.toString = function() {
return "";
};
function Assertion( expr, exprStr, start, end ) {
this.expr = expr;
this.exprStr = exprStr;
this.start = start;
this.end = end;
}
Assertion.prototype.toString = function() {
return 'ASSERT('+nodeToString(this.expr)+',\n '+this.exprStr+')';
};
function BitFieldRead(mask, start, end, fieldExpr) {
if (mask === 0) throw new Error("mask cannot be zero");
this.mask = mask;
this.start = start;
this.end = end;
this.fieldExpr = fieldExpr;
}
BitFieldRead.prototype.getShiftCount = function() {
var b = 1;
var shiftCount = 0;
while ((this.mask & b) === 0) {
b <<= 1;
shiftCount++;
}
return shiftCount;
};
BitFieldRead.prototype.toString = function() {
var fieldExpr = this.fieldExpr ? nodeToString(this.fieldExpr) : "bitField";
var mask = this.mask;
var shiftCount = this.getShiftCount();
return shiftCount === 0
? "(" + fieldExpr + " & " + mask + ")"
: "((" + fieldExpr + " & " + mask + ") >>> " + shiftCount + ")";
};
function BitFieldCheck(value, inverted, start, end, fieldExpr) {
this.value = value;
this.inverted = inverted;
this.start = start;
this.end = end;
this.fieldExpr = fieldExpr;
}
BitFieldCheck.prototype.toString = function() {
var fieldExpr = this.fieldExpr ? nodeToString(this.fieldExpr) : "bitField";
var equality = this.inverted ? "===" : "!==";
return "((" + fieldExpr + " & " + this.value + ") " + equality + " 0)";
};
function InlineSlice(varExpr, collectionExpression, startExpression, endExpression, start, end, isBrowser) {
this.varExpr = varExpr;
this.collectionExpression = collectionExpression;
this.startExpression = startExpression;
this.endExpression = endExpression;
this.start = start;
this.end = end;
this.isBrowser = isBrowser;
}
InlineSlice.prototype.hasSimpleStartExpression =
function InlineSlice$hasSimpleStartExpression() {
return this.startExpression.type === "Identifier" ||
this.startExpression.type === "Literal";
};
InlineSlice.prototype.hasSimpleEndExpression =
function InlineSlice$hasSimpleEndExpression() {
return this.endExpression.type === "Identifier" ||
this.endExpression.type === "Literal";
};
InlineSlice.prototype.hasSimpleCollection = function InlineSlice$hasSimpleCollection() {
return this.collectionExpression.type === "Identifier";
};
InlineSlice.prototype.toString = function InlineSlice$toString() {
var init = this.hasSimpleCollection()
? ""
: "var $_collection = " + nodeToString(this.collectionExpression) + ";";
var collectionExpression = this.hasSimpleCollection()
? nodeToString(this.collectionExpression)
: "$_collection";
init += "var $_len = " + collectionExpression + ".length;";
var varExpr = nodeToString(this.varExpr);
//No offset arguments at all
if( this.startExpression === firstElement ) {
if (this.isBrowser) {
return "var " + varExpr + " = [].slice.call("+collectionExpression+");";
} else {
return init + "var " + varExpr + " = new Array($_len); " +
"for(var $_i = 0; $_i < $_len; ++$_i) {" +
varExpr + "[$_i] = " + collectionExpression + "[$_i];" +
"}";
}
}
else {
if( !this.hasSimpleStartExpression() ) {
init += "var $_start = " + nodeToString(this.startExpression) + ";";
}
var startExpression = this.hasSimpleStartExpression()
? nodeToString(this.startExpression)
: "$_start";
//Start offset argument given
if( this.endExpression === lastElement ) {
if (this.isBrowser) {
return "var " + varExpr + " = [].slice.call("+collectionExpression+", "+startExpression+");";
} else {
return init + "var " + varExpr + " = new Array(Math.max($_len - " +
startExpression + ", 0)); " +
"for(var $_i = " + startExpression + "; $_i < $_len; ++$_i) {" +
varExpr + "[$_i - "+startExpression+"] = " + collectionExpression + "[$_i];" +
"}";
}
}
//Start and end offset argument given
else {
if( !this.hasSimpleEndExpression() ) {
init += "var $_end = " + nodeToString(this.endExpression) + ";";
}
var endExpression = this.hasSimpleEndExpression()
? nodeToString(this.endExpression)
: "$_end";
if (this.isBrowser) {
return "var " + varExpr + " = [].slice.call("+collectionExpression+", "+startExpression+", "+endExpression+");";
} else {
return init + "var " + varExpr + " = new Array(Math.max(" + endExpression + " - " +
startExpression + ", 0)); " +
"for(var $_i = " + startExpression + "; $_i < " + endExpression + "; ++$_i) {" +
varExpr + "[$_i - "+startExpression+"] = " + collectionExpression + "[$_i];" +
"}";
}
}
}
};
var opts = {
ecmaVersion: 5,
strictSemicolons: false,
allowTrailingCommas: true,
forbidReserved: false,
locations: false,
onComment: null,
ranges: false,
program: null,
sourceFile: null
};
var rlineterm = /[\r\n\u2028\u2029]/;
var rhorizontalws = /[ \t]/;
var convertSrc = function( src, results ) {
if( results.length ) {
results.sort(function(a, b){
var ret = a.start - b.start;
if( ret === 0 ) {
ret = a.end - b.end;
}
return ret;
});
for( var i = 1; i < results.length; ++i ) {
var item = results[i];
if( item.start === results[i-1].start &&
item.end === results[i-1].end ) {
results.splice(i++, 1);
}
}
var ret = "";
var start = 0;
for( var i = 0, len = results.length; i < len; ++i ) {
var item = results[i];
ret += src.substring( start, item.start );
ret += item.toString();
start = item.end;
}
ret += src.substring( start );
return ret;
}
return src;
};
var rescape = /[\r\n\u2028\u2029"]/g;
var replacer = function( ch ) {
return "\\u" + (("0000") +
(ch.charCodeAt(0).toString(16))).slice(-4);
};
function safeToEmbedString( str ) {
return str.replace( rescape, replacer );
}
function parse( src, opts, fileName) {
if( !fileName ) {
fileName = opts;
opts = void 0;
}
try {
return jsp.parse(src, opts);
}
catch(e) {
e.message = e.message + " " + fileName;
e.scriptSrc = src;
throw e;
}
}
var inlinedFunctions = Object.create(null);
var lastElement = jsp.parse("___input.length").body[0].expression;
var firstElement = jsp.parse("0").body[0].expression;
inlinedFunctions.INLINE_SLICE = function( node, isBrowser ) {
var statement = node;
node = node.expression;
var args = node.arguments;
if( !(2 <= args.length && args.length <= 4 ) ) {
throw new Error("INLINE_SLICE must have exactly 2, 3 or 4 arguments");
}
var varExpression = args[0];
var collectionExpression = args[1];
var startExpression = args.length < 3
? firstElement
: args[2];
var endExpression = args.length < 4
? lastElement
: args[3];
return new InlineSlice(varExpression, collectionExpression,
startExpression, endExpression, statement.start, statement.end, isBrowser);
};
inlinedFunctions.BIT_FIELD_READ = function(node) {
var statement = node;
var args = node.expression.arguments;
if (args.length !== 1 && args.length !== 2) {
throw new Error("BIT_FIELD must have 1 or 2 arguments");
}
var arg = args[0];
if (arg.type !== "Identifier") {
throw new Error("BIT_FIELD argument must be an identifier");
}
var name = arg.name;
var constant = constants[name];
if (constant === undefined) {
throw new Error(name + " is not a constant");
}
var value = constant.value;
return new BitFieldRead(value, statement.start, statement.end, args[1]);
};
inlinedFunctions.BIT_FIELD_CHECK = function(node) {
var statement = node;
var args = node.expression.arguments;
if (args.length !== 1 && args.length !== 2) {
throw new Error("BIT_FIELD must have 1 or 2 arguments");
}
var arg = args[0];
if (arg.type !== "Identifier") {
throw new Error("BIT_FIELD argument must be an identifier");
}
var name = arg.name;
var constant = constants[name];
if (constant === undefined) {
throw new Error(name + " is not a constant");
}
var value = constant.value;
var inverted = false;
if (name.slice(-4) === "_NEG") {
inverted = true;
}
return new BitFieldCheck(value, inverted, statement.start, statement.end, args[1]);
};
inlinedFunctions.USE = function(node) {
return new Empty(node.start, node.end);
};
var constants = {};
var ignore = [];
Error.stackTraceLimit = 10000;
var astPasses = module.exports = {
inlineExpansion: function( src, fileName, isBrowser ) {
var ast = parse(src, fileName);
var results = [];
var expr = [];
function doInline(node) {
if( node.expression.type !== 'CallExpression' ) {
return;
}
var name = node.expression.callee.name;
if(typeof inlinedFunctions[ name ] === "function" &&
expr.indexOf(node.expression) === -1) {
expr.push(node.expression);
try {
results.push( inlinedFunctions[ name ]( node, isBrowser ) );
}
catch(e) {
e.fileName = fileName;
throw e;
}
}
}
walk.simple(ast, {
ExpressionStatement: doInline,
CallExpression: function(node) {
node.expression = node;
doInline(node);
}
});
var ret = convertSrc( src, results );
return ret;
},
//Parse constants in from constants.js
readConstants: function( src, fileName ) {
var ast = parse(src, fileName);
walk.simple(ast, {
ExpressionStatement: function( node ) {
if( node.expression.type !== 'CallExpression' ) {
return;
}
var start = node.start;
var end = node.end;
node = node.expression;
var callee = node.callee;
if( callee.name === "CONSTANT" &&
callee.type === "Identifier" ) {
if( node.arguments.length !== 2 ) {
throw new Error( "Exactly 2 arguments must be passed to CONSTANT\n" +
src.substring(start, end)
);
}
if( node.arguments[0].type !== "Identifier" ) {
throw new Error( "Can only define identifier as a constant\n" +
src.substring(start, end)
);
}
var args = node.arguments;
var name = args[0];
var nameStr = name.name;
var expr = args[1];
var e = eval;
constants[nameStr] = {
identifier: name,
value: e(nodeToString(expr))
};
walk.simple( expr, {
Identifier: function( node ) {
ignore.push(node);
}
});
global[nameStr] = constants[nameStr].value;
}
}
});
},
//Expand constants in normal source files
expandConstants: function( src, fileName ) {
var results = [];
var identifiers = [];
var ast = parse(src, fileName);
walk.simple(ast, {
Identifier: function( node ) {
identifiers.push( node );
}
});
for( var i = 0, len = identifiers.length; i < len; ++i ) {
var id = identifiers[i];
if( ignore.indexOf(id) > -1 ) {
continue;
}
var constant = constants[id.name];
if( constant === void 0 ) {
continue;
}
if( constant.identifier === id ) {
continue;
}
results.push( new ConstantReplacement( constant.value, id.start, id.end ) );
}
return convertSrc( src, results );
},
removeComments: function( src, fileName ) {
var results = [];
var rnoremove = /^[*\s\/]*(?:@preserve|jshint|global)/;
opts.onComment = function( block, text, start, end ) {
if( rnoremove.test(text) ) {
return;
}
var e = end + 1;
var s = start - 1;
while(rhorizontalws.test(src.charAt(s--)));
while(rlineterm.test(src.charAt(e++)));
results.push( new Empty( s + 2, e - 1 ) );
};
var ast = parse(src, opts, fileName);
return convertSrc( src, results );
},
expandAsserts: function( src, fileName ) {
var ast = parse( src, fileName );
var results = [];
walk.simple(ast, {
CallExpression: function( node ) {
var start = node.start;
var end = node.end;
var callee = node.callee;
if( callee.type === "Identifier" &&
callee.name === "ASSERT" ) {
if( node.arguments.length !== 1 ) {
results.push({
start: start,
end: end,
toString: function() {
return src.substring(start, end);
}
});
return;
}
var expr = node.arguments[0];
var str = src.substring(expr.start, expr.end);
str = '"' + safeToEmbedString(str) + '"'
var assertion = new Assertion( expr, str, start, end );
results.push( assertion );
}
}
});
return convertSrc( src, results );
},
removeAsserts: function( src, fileName ) {
var ast = parse( src, fileName );
var results = [];
walk.simple(ast, {
ExpressionStatement: function( node ) {
if( node.expression.type !== 'CallExpression' ) {
return;
}
var start = node.start;
var end = node.end;
node = node.expression;
var callee = node.callee;
if( callee.type === "Identifier" &&
callee.name === "ASSERT" ) {
var e = end + 1;
var s = start - 1;
while(rhorizontalws.test(src.charAt(s--)));
while(rlineterm.test(src.charAt(e++)));
results.push( new Empty( s + 2, e - 1) );
}
},
VariableDeclaration: function(node) {
var start = node.start;
var end = node.end;
if (node.kind === 'var' && node.declarations.length === 1) {
var decl = node.declarations[0];
if (decl.id.type === "Identifier" &&
decl.id.name === "ASSERT") {
var e = end + 1;
var s = start - 1;
while(rhorizontalws.test(src.charAt(s--)));
while(rlineterm.test(src.charAt(e++)));
results.push( new Empty( s + 2, e - 1) );
}
}
}
});
return convertSrc( src, results );
},
asyncConvert: function( src, objName, fnProp, fileName ) {
var ast = parse( src, fileName );
var results = [];
walk.simple(ast, {
CallExpression: function( node ) {
var start = node.start;
var end = node.end;
if( node.callee.type === "MemberExpression" &&
node.callee.object.name === objName &&
node.callee.property.name === fnProp &&
node.arguments.length === 3
) {
var args = node.arguments;
var fnDereference = args[0];
var dynamicReceiver = args[1];
var arg = args[2];
var receiver = getReceiver(fnDereference);
if( receiver == null || !equals(receiver, dynamicReceiver) ) {
//Have to use fnDereference.call(dynamicReceiver, arg);
results.push(
new DynamicCall( dynamicReceiver, fnDereference, arg, start, end )
);
}
else {
var fnName = fnDereference.property;
results.push(
new DirectCall( receiver, fnName, arg, start, end )
);
//Can use receiver.fnName( arg );
}
}
}
});
return convertSrc( src, results );
}
};