Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 112 additions & 141 deletions structured.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

if (typeof module !== "undefined" && module.exports) {
exports = module.exports = {};
esprima = require("esprima");
esprima = require("./external/esprima.js");
_ = require("underscore");
} else {
exports = this.Structured = {};
Expand Down Expand Up @@ -54,102 +54,110 @@
fn: callback
};
}

/*
* return true if n2 < n1 (according to relatively arbitrary criteria)
*/
function shouldSwap(n1, n2) {
if (n1.type < n2.type) { //Sort by node type if different
return false;
} else if (n1.type > n2.type) {
return true;
} else if (n1.type === "Literal") { //Sort by value if they're literals
return n1.raw > n2.raw
} else { //Otherwise, loop through the properties until a difference is found and sort by that
for (var k in n1) {
if (n1[k].hasOwnProperty("type") && n1[k] !== n2[k]) {
return shouldSwap(n1[k], n2[k]);
}
}
}
if (n1.type !== n2.type) { //Sort by node type if different
return n1.type > n2.type;
} else if (n1.type === "Literal") { //Sort by value if they're literals
return n1.raw > n2.raw;
} else { //Otherwise, loop through the properties until a difference is found and sort by that
for (var k in n1) {
if (n1[k].hasOwnProperty("type") && n1[k] !== n2[k]) {
return shouldSwap(n1[k], n2[k]);
}
}
}
}
function standardizeTree(tree) {
if (!tree) {return tree;}
var r = deepClone(tree);
switch (tree.type) {
case "BinaryExpression":
if (_.contains(["*", "+", "===", "!==", "==", "!=", "&", "|", "^"], tree.operator)) {
if (shouldSwap(tree.left, tree.right)) {
r.left = standardizeTree(tree.right);
r.right = standardizeTree(tree.left);
}
} else if (tree.operator[0] === ">") {
r.operator = "<" + tree.operator.slice(1);
r.left = standardizeTree(tree.right);
r.right = standardizeTree(tree.left);
} break;
case "LogicalExpression":
if (_.contains(["&&", "||"], tree.operator) &&
shouldSwap(tree.left, tree.right)) {
r.left = standardizeTree(tree.right);
r.right = standardizeTree(tree.left);
} break;
case "AssignmentExpression":
if (_.contains(["+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|="], tree.operator)) {
var l = standardizeTree(tree.left);
r = {type: "AssignmentExpression",
operator: "=",
left: l,
right: {type: "BinaryExpression",
operator: tree.operator.slice(0,-1),
left: l,
right: standardizeTree(tree.right)}};
} break;
case "UpdateExpression":
if (_.contains(["++", "--"], tree.operator)) {
var l = standardizeTree(tree.argument);
r = {type: "AssignmentExpression",
operator: "=",
left: l,
right: {type: "BinaryExpression",
operator: tree.operator[0],
left: l,
right: {type: "Literal",
value: 1,
raw: "1"}}};
} break;
case "VariableDeclaration":
if (tree.kind === "var") {
r = [deepClone(tree)];
for (var i in tree.declarations) {
if (tree.declarations[i].type === "VariableDeclarator" &&
tree.declarations[i].init !== null) {
r.push({type: "ExpressionStatement",
expression: {type: "AssignmentExpression",
operator: "=",
left: tree.declarations[i].id,
right: standardizeTree(tree.declarations[i].init)}});
r[0].declarations[i].init = null;
}
}
} break;
default:
for (var key in tree) {
if (!tree.hasOwnProperty(key) || !_.isObject(tree[key])) {
continue;
}
if (_.isArray(tree[key])) {
var ar = [];
for (var i in tree[key]) {
ar = ar.concat(standardizeTree(tree[key][i]));
}
r[key] = ar;
} else {
r[key] = standardizeTree(tree[key]);
}
}
}
return r;
if (!tree) { return tree; }
for (var k in tree) {
if (tree.hasOwnProperty(k) && _.isObject(tree[k])) {
if (_.isArray(tree[k])) {
var ar = [];
for (var i = 0; i < tree[k].length; i++) {
if (!tree[k][i] ||
!tree[k][i].hasOwnProperty("type") ||
tree[k][i].type !== "EmptyStatement") {
ar = ar.concat(standardizeTree(tree[k][i]));
}
}
tree[k] = ar;
} else {
tree[k] = standardizeTree(tree[k]);
}
}
}
switch (tree.type) {
case "LogicalExpression":
case "BinaryExpression":
var l = tree.left;
if (_.contains(["*", "+", "===", "!==", "==", "!=", "&", "|", "^", "&&", "||"], tree.operator)) {
if (shouldSwap(tree.left, tree.right)) {
tree.left = tree.right;
tree.right = l;
}
} else if (tree.operator[0] === ">") {
tree.operator = "<" + tree.operator.slice(1);
tree.left = tree.right;
tree.right = l;
} break;
case "AssignmentExpression":
if (_.contains(["+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|="], tree.operator)) {
tree = {type: "AssignmentExpression",
operator: "=",
left: tree.left,
right: {type: "BinaryExpression",
operator: tree.operator.slice(0,-1),
left: tree.left,
right: tree.right}};
} break;
case "UpdateExpression":
if (_.contains(["++", "--"], tree.operator)) {
tree = {type: "AssignmentExpression",
operator: "=",
left: tree.argument,
right: {type: "BinaryExpression",
operator: tree.operator[0],
left: tree.argument,
right: {type: "Literal",
value: 1,
raw: "1"}}};
} break;
case "VariableDeclaration":
if (tree.kind === "var") {
tree = [tree];
for (var i = 0; i < tree[0].declarations.length; i++) {
if (tree[0].declarations[i].type === "VariableDeclarator" &&
tree[0].declarations[i].init !== null) {
tree.push({type: "ExpressionStatement",
expression: {type: "AssignmentExpression",
operator: "=",
left: tree[0].declarations[i].id,
right: tree[0].declarations[i].init}});
tree[0].declarations[i].init = null;
}
}
} break;
case "UnaryExpression":
if (tree.argument.type === "Literal" && _.isNumber(tree.argument.value)) {
/*
* Currently, we only fold + and - applied to a number literal.
* This is easy to extend, but it means we lose the ability to match
* potentially useful expressions like 5 + 5 with a pattern like _ + _.
*/
if (tree.operator === "-") {
tree.argument.value = -tree.argument.value;
tree = tree.argument;
} else if (tree.operator === "+") {
tree.argument.value = +tree.argument.value;
tree = tree.argument;
}
}
}
return tree;
}

/*
Expand Down Expand Up @@ -195,15 +203,15 @@
options = options || {};
// Many possible inputs formats are accepted for varCallbacks
// Constraints can be:
// 1. a function (from which we will extract the variables)
// 1. a function (from which we will extract the variables)
// 2. an objects (which already has separate .fn and .variables properties)
//
// It will also accept a list of either of the above (or a mix of the two).
// Finally it can accept an object for which the keys are the variables and
// Finally it can accept an object for which the keys are the variables and
// the values are the callbacks (This option is mainly for historical reasons)
var varCallbacks = options.varCallbacks || [];
// We need to keep a hold of the original varCallbacks object because
// When structured first came out it returned the failure message by
// We need to keep a hold of the original varCallbacks object because
// When structured first came out it returned the failure message by
// changing the .failure property on the varCallbacks object and some uses rely on that.
// We hope to get rid of this someday.
// TODO: Change over the code so to have a better API
Expand Down Expand Up @@ -249,7 +257,6 @@
cachedCode = code;
cachedCodeTree = codeTree;

foldConstants(codeTree);
var toFind = structure.body || structure;
var peers = [];
if (_.isArray(structure.body)) {
Expand All @@ -261,7 +268,7 @@
_: [],
vars: {}
};
codeTree = standardizeTree(codeTree);
codeTree = standardizeTree(codeTree);
if (wildcardVars.order.length === 0 || options.single) {
// With no vars to match, our normal greedy approach works great.
result = checkMatchTree(codeTree, toFind, peers, wildcardVars, matchResult, options);
Expand Down Expand Up @@ -376,7 +383,7 @@
function checkUserVarCallbacks(wVars, varCallbacks) {
// Clear old failure message if needed
delete originalVarCallbacks.failure;
for (var key in varCallbacks) {
for (var key in varCallbacks) { /* jshint forin:false */
// Property strings may be "$foo, $bar, $baz" to mimic arrays.
var varNames = varCallbacks[key].variables;
var varValues = _.map(varNames, function(varName) {
Expand Down Expand Up @@ -451,46 +458,10 @@
*/
function parseStructureWithVars(structure, wVars) {
var tree = standardizeTree(parseStructure(structure));
foldConstants(tree);
simplifyTree(tree, wVars);
return tree;
}

/*
* Constant folds the syntax tree
*/
function foldConstants(tree) {
for (var key in tree) {
if (!tree.hasOwnProperty(key)) {
continue; // Inherited property
}

var ast = tree[key];
if (_.isObject(ast)) {
foldConstants(ast);

/*
* Currently, we only fold + and - applied to a number literal.
* This is easy to extend, but it means we lose the ability to match
* potentially useful expressions like 5 + 5 with a pattern like _ + _.
*/
if (ast.type == esprima.Syntax.UnaryExpression) {
var argument = ast.argument;
if (argument.type === esprima.Syntax.Literal &&
_.isNumber(argument.value)) {
if (ast.operator === "-") {
argument.value = -argument.value;
tree[key] = argument;
} else if (ast.operator === "+") {
argument.value = +argument.value;
tree[key] = argument;
}
}
}
}
}
}

/*
* Recursively traverses the tree and sets _ properties to undefined
* and empty bodies to null.
Expand All @@ -513,7 +484,7 @@
*
*/
function simplifyTree(tree, wVars) {
for (var key in tree) {
for (var key in tree) { /* jshint forin:false */
if (!tree.hasOwnProperty(key)) {
continue; // Inherited property
}
Expand Down Expand Up @@ -582,6 +553,7 @@
console.error("toFind should never be an array.");
console.error(toFind);
}
/* jshint -W041, -W116 */
if (currTree == undefined) {
if (toFind == undefined) {
matchResults._.push(currTree);
Expand All @@ -598,7 +570,7 @@
return false;
}
// Check children.
for (var key in currTree) {
for (var key in currTree) { /* jshint forin:false */
if (!currTree.hasOwnProperty(key) || !_.isObject(currTree[key])) {
continue; // Skip inherited properties
}
Expand Down Expand Up @@ -763,7 +735,7 @@
rootToSet = currNode;
}

for (var key in toFind) {
for (var key in toFind) { /* jshint forin:false */
// Ignore inherited properties; also, null properties can be
// anything and do not have to exist.
if (!toFind.hasOwnProperty(key) || toFind[key] === null) {
Expand All @@ -773,6 +745,7 @@
var subCurr = currNode[key];
// Undefined properties can be anything, but they must exist.
if (subFind === undefined) {
/* jshint -W116 */
if (subCurr == undefined) {
return false;
} else {
Expand Down Expand Up @@ -936,9 +909,7 @@
// Store some properties on the addStyling function to maintain the
// styleMap between runs if desired.
// Right now just support 7 different variables. Just add more if needed.
addStyling.styles = ["one", "two", "three", "four", "five", "six",
"seven"
];
addStyling.styles = ["one", "two", "three", "four", "five", "six", "seven"];
addStyling.styleMap = {};
addStyling.counter = 0;

Expand Down Expand Up @@ -993,7 +964,7 @@
return node;
}

for (var prop in node) {
for (var prop in node) { /* jshint forin:false */
if (!node.hasOwnProperty(prop)) {
continue;
}
Expand Down