Permalink
Browse files

Split code up

Make the jsgrep code modular and accessible from JavaScript
  • Loading branch information...
1 parent 492d36a commit 3b611fd0b3d29659891af47088d39e1ec879fb36 Ryan Patterson committed Jan 9, 2012
Showing with 603 additions and 552 deletions.
  1. +55 −552 jsgrep
  2. +548 −0 lib/jsgrep.js
View
607 jsgrep
@@ -97,20 +97,20 @@ function usage(hasError) {
}
config.paths = process.argv.slice(parser.optind());
-})();
-if (config.patterns.length == 0 && config.paths.length > 0) {
- config.patterns.push(config.paths.shift());
-}
+ if (config.patterns.length == 0 && config.paths.length > 0) {
+ config.patterns.push(config.paths.shift());
+ }
-if (config.patterns.length == 0) {
- console.error("At least one pattern must be specified.");
- usage(true);
-}
+ if (config.patterns.length == 0) {
+ console.error("At least one pattern must be specified.");
+ usage(true);
+ }
-if (config.filename === null) {
- config.filename = config.paths.length > 1;
-}
+ if (config.filename === null) {
+ config.filename = config.paths.length > 1;
+ }
+})();
var patternsAst = [];
for (var i = 0; i < config.patterns.length; i++) {
@@ -145,53 +145,63 @@ for (var i = 0; i < config.paths.length; i++) {
if (source.substr(0, 2) == '#!') {
source = "// " + source;
}
- var sourceLines = source.split('\n');
+
+ var sourceLines = null;
+ if (config.print == PRINT_FIRST_LINE) {
+ sourceLines = source.split('\n');
+ }
+
try {
var ast = Narcissus.parser.parse(source, config.paths[i], 1);
} catch(e) {
console.warn(e.message);
}
try {
- forEachNode(ast, function(node) {
- _.each(patternsAst, function(patternAst) {
- var variables = {};
- if (astIsEqual(node, patternAst, variables)) {
- var output = false, lineNumber = node.lineno;
-
- fileHasMatch = true;
-
- if (config.print === PRINT_FIRST_LINE) {
- output = sourceLines[node.lineno - 1];
- } else if (config.print === PRINT_ONLY_MATCHING) {
- output = node.tokenizer.source.substring(node.start, node.end);
- } else if (config.print === PRINT_METAVAR) {
- if (!variables[config.printMetavar]) {
- console.error("jsgrep: Metavariable " + config.printMetavar +
- " is not bound to any expression.");
- process.exit(1);
- } else {
- var matchNode = variables[config.printMetavar];
- output = matchNode.tokenizer.source.substring(
- matchNode.start, matchNode.end);
- lineNumber = matchNode.lineno;
- }
+ require('./lib/jsgrep.js').jsgrep({
+ source: ast,
+ patterns: patternsAst,
+ strictMatches: config.strictMatches,
+ callback: function(node, variables) {
+ var output = false, lineNumber = node.lineno;
+
+ fileHasMatch = true;
+
+ if (config.print === PRINT_FIRST_LINE) {
+ output = sourceLines[node.lineno - 1];
+ } else if (config.print === PRINT_ONLY_MATCHING) {
+ output = node.tokenizer.source.substring(node.start, node.end);
+ } else if (config.print === PRINT_METAVAR) {
+ if (!variables[config.printMetavar]) {
+ throw {
+ message: "Metavariable " + config.printMetavar +
+ " is not bound to any expression."
+ };
} else {
- // Print filenames, so bail the search for speed
- throw 'done';
+ var matchNode = variables[config.printMetavar];
+ output = matchNode.tokenizer.source.substring(
+ matchNode.start, matchNode.end);
+ lineNumber = matchNode.lineno;
}
- if (output) {
- if (output.indexOf('\n') > 0) {
- output = output.substr(0, output.indexOf('\n'));
- }
- console.log((config.filename ? config.paths[i] + ":" : "") +
- (config.lineNumber ? lineNumber + ":" : "") +
- output);
+ } else {
+ // Print filenames, so bail the search for speed
+ throw 'done';
+ }
+ if (output) {
+ if (output.indexOf('\n') > 0) {
+ output = output.substr(0, output.indexOf('\n'));
}
+ console.log((config.filename ? config.paths[i] + ":" : "") +
+ (config.lineNumber ? lineNumber + ":" : "") +
+ output);
}
- });
+ }
});
} catch(e) {
+ if (!(e instanceof Error) && e.message) {
+ console.error("jsgrep: " + e.message);
+ process.exit(1);
+ }
if (e !== 'done') {
throw e;
}
@@ -203,511 +213,4 @@ for (var i = 0; i < config.paths.length; i++) {
}
}
-function tokenString(tt) {
- var t = Narcissus.definitions.tokens[tt];
- return /^\W/.test(t) ? Narcissus.definitions.opTypeNames[t] : t.toUpperCase();
-}
-
-function astIsEqual(node, pattern, variables) {
- const tokens = Narcissus.definitions.tokenIds;
-
- if (pattern.type == tokens.IDENTIFIER &&
- /^[A-Z](_.*)?$/.test(pattern.value)) {
- if (pattern.value in variables) {
- // Variable already matched, compare this node to that value
- return astIsEqual(node, variables[pattern.value]);
- } else {
- // Bind variable to this value
- variables[pattern.value] = node;
- return true;
- }
- }
-
- if (node.type != pattern.type) {
- return false;
- }
-
- if (node.type == tokens.OBJECT_INIT && !config.strictMatches) {
- // Strict matching will be handled normally (below).
- if (pattern.children.length > node.children.length) {
- return false;
- }
-
- var keys = _.clone(pattern.children);
- for (var i = 0; i < node.children.length; i++) {
- for (var j = 0; j < keys.length;) {
- if (astIsEqual(node.children[i], keys[j], variables)) {
- keys.splice(j, 1);
- break;
- } else {
- j++;
- }
- }
-
- if (keys.length == 0) {
- break;
- }
- }
-
- // No keys left over -> match.
- return keys.length == 0;
- }
-
- switch(node.type) {
- // Core values
- case tokens.FALSE:
- case tokens.IDENTIFIER:
- case tokens.NULL:
- case tokens.NUMBER:
- case tokens.REGEXP:
- case tokens.STRING:
- case tokens.THIS:
- case tokens.TRUE:
- return node.value == pattern.value;
- break;
-
- // 0-child statements
- case tokens.BREAK:
- case tokens.CONTINUE:
- return true;
- break;
-
- // Unary expressions
- case tokens.BITWISE_NOT:
- case tokens.DECREMENT:
- case tokens.INCREMENT:
- case tokens.NEW:
- case tokens.NEW_WITH_ARGS:
- case tokens.NOT:
- case tokens.TYPEOF:
- case tokens.UNARY_MINUS:
- case tokens.UNARY_PLUS:
- case tokens.VOID:
- // Binary expressions
- case tokens.AND:
- case tokens.BITWISE_AND:
- case tokens.BITWISE_OR:
- case tokens.BITWISE_XOR:
- case tokens.DIV:
- case tokens.EQ:
- case tokens.GE:
- case tokens.GT:
- case tokens.IN:
- case tokens.INSTANCEOF:
- case tokens.LE:
- case tokens.LSH:
- case tokens.LT:
- case tokens.MINUS:
- case tokens.MOD:
- case tokens.MUL:
- case tokens.NE:
- case tokens.OR:
- case tokens.PLUS:
- case tokens.RSH:
- case tokens.STRICT_EQ:
- case tokens.STRICT_NE:
- case tokens.ULSH:
- case tokens.URSH:
- // Other
- case tokens.ASSIGN:
- //case tokens.BLOCK:
- case tokens.CALL:
- case tokens.COMMA:
- case tokens.DELETE:
- case tokens.DOT:
- case tokens.HOOK:
- case tokens.INDEX:
- // Special
- case tokens.OBJECT_INIT:
- case tokens.PROPERTY_INIT:
- //case tokens.SCRIPT:
- if (node.children.length == pattern.children.length) {
- for (var i = 0; i < node.children.length; i++) {
- if (!astIsEqual(node.children[i], pattern.children[i], variables)) {
- return false;
- }
- }
- return true;
- }
- break;
-
- case tokens.ARRAY_INIT:
- case tokens.LIST:
- return astMatchEllipsis(node.children, pattern.children, variables);
- break;
-
- case tokens.LET:
- case tokens.VAR:
- // All of var's children are IDENTIFIERs with name/initializer values
- // TODO: this does not support destructuring assignments
- if (pattern.children.length > node.children.length) {
- return false;
- }
-
- var keys = _.clone(pattern.children);
- for (var i = 0; i < node.children.length; i++) {
- for (var j = 0; j < keys.length;) {
- if (astIsEqual(node.children[i], keys[j], variables)) {
- // If the pattern has an initializer, it must be equal to the one
- // in the source.
- if (keys[j].initializer &&
- (!node.children[i].initializer ||
- !astIsEqual(node.children[i].initializer,
- keys[j].initializer, variables))) {
- return false;
- }
- // If in strict mode and the pattern has no initializer, neither can
- // the source.
- if (!keys[j].initializer && config.strictMatches &&
- node.children[i].initializer) {
- return false;
- }
- keys.splice(j, 1);
- break;
- } else {
- j++;
- }
- }
-
- if (keys.length == 0) {
- break;
- }
- }
-
- // No keys left over -> match.
- return keys.length == 0;
- break;
-
- case tokens.SEMICOLON:
- if (!node.expression && !pattern.expression) {
- return true;
- } else if (node.expression && pattern.expression) {
- return astIsEqual(node.expression, pattern.expression, variables);
- }
- return false;
- break;
-
- //case tokens.DO:
- //forEachNode(node.body, callback);
- //forEachNode(node.condition, callback);
- break;
-
- //case tokens.WHILE:
- //forEachNode(node.condition, callback);
- //forEachNode(node.body, callback);
- break;
-
- //case tokens.FUNCTION:
- //forEachNode(node.body, callback);
- break;
-
- //case tokens.RETURN:
- if (node.value) {
- //forEachNode(node.value, callback);
- }
- break;
-
- //case tokens.SWITCH:
- //forEachNode(node.discriminant, callback);
- _.each(node.cases, function(child) {
- //forEachNode(child, callback);
- });
- break;
-
- //case tokens.DEFAULT:
- //forEachNode(node.statements, callback);
- break;
-
- //case tokens.CASE:
- //forEachNode(node.caseLabel, callback);
- //forEachNode(node.statements, callback);
- break;
-
- //case tokens.LABEL:
- //forEachNode(node.statement, callback);
- break;
-
- //case tokens.FOR_IN:
- //forEachNode(node.iterator, callback);
- //forEachNode(node.object, callback);
- //forEachNode(node.body, callback);
- break;
-
- //case tokens.FOR:
- //forEachNode(node.setup, callback);
- //forEachNode(node.condition, callback);
- //forEachNode(node.update, callback);
- //forEachNode(node.body, callback);
- break;
-
- //case tokens.IF:
- //forEachNode(node.condition, callback);
- //forEachNode(node.thenPart, callback);
- if (node.elsePart) {
- //forEachNode(node.elsePart, callback);
- }
- break;
-
- //case tokens.TRY:
- //forEachNode(node.tryBlock, callback);
- _.each(node.catchClauses, function(child) {
- //forEachNode(child, callback);
- });
- if (node.finallyBlock) {
- //forEachNode(node.finallyBlock, callback);
- }
- break;
-
- //case tokens.CATCH:
- if (node.guard) {
- //forEachNode(node.guard, callback);
- }
- //forEachNode(node.block);
- break;
-
- case tokens.THROW:
- return astIsEqual(node.exception, pattern.exception, variables);
- break;
-
- default:
- console.error("jsgrep: Pattern type is not yet supported: " +
- tokenString(node.type));
- process.exit(1);
- break;
- }
-}
-
-function astMatchEllipsis(nodes, patterns, variables) {
- // XXX(rpatterson): this needs testing!
- const tokens = Narcissus.definitions.tokenIds;
- var permitVar = true, clonedVars = null;
- function go(i, j) {
- if (i == nodes.length && j == patterns.length) {
- return true;
- }
-
- if (j == patterns.length) {
- return false;
- }
-
- if (patterns[j].type == tokens.ELLIPSIS && j == patterns.length - 1) {
- return true;
- }
-
- if(i == nodes.length) {
- return false;
- }
-
- if (patterns[j].type == tokens.ELLIPSIS) {
- permitVar = false;
- clonedVars = _.clone(variables);
- return go(i, j + 1) || go(i + 1, j);
- }
-
- return astIsEqual(nodes[i], patterns[j],
- permitVar ? variables : clonedVars) &&
- go(i + 1, j + 1);
- }
- var result = go(0, 0);
- if (!permitVar && _.keys(clonedVars).length != _.keys(variables).length) {
- // ..., A, ... is ambiguous, and if A was matched later in the pattern, we
- // would have to backtrack to the ellipsis to try a different match. That's
- // annoying.
- //
- // XXX(rpatterson): This incorrectly bails for (..., A)
- console.error("jsgrep: Matching metavariables inside partially-matched " +
- "lists is unsupported. Sorry!");
- process.exit(1);
- }
- return result;
-}
-
-function forEachNode(node, callback) {
- const tokens = Narcissus.definitions.tokenIds;
-
- callback(node);
-
- switch(node.type) {
- // Core values
- case tokens.FALSE:
- case tokens.NULL:
- case tokens.NUMBER:
- case tokens.REGEXP:
- case tokens.STRING:
- case tokens.THIS:
- case tokens.TRUE:
- // 0-child statements
- case tokens.BREAK:
- case tokens.CONTINUE:
- break;
-
- // Unary expressions
- case tokens.BITWISE_NOT:
- case tokens.DECREMENT:
- case tokens.INCREMENT:
- case tokens.NEW:
- case tokens.NEW_WITH_ARGS:
- case tokens.NOT:
- case tokens.TYPEOF:
- case tokens.UNARY_MINUS:
- case tokens.UNARY_PLUS:
- case tokens.VOID:
- // Binary expressions
- case tokens.AND:
- case tokens.BITWISE_AND:
- case tokens.BITWISE_OR:
- case tokens.BITWISE_XOR:
- case tokens.DIV:
- case tokens.EQ:
- case tokens.GE:
- case tokens.GT:
- case tokens.IN:
- case tokens.INSTANCEOF:
- case tokens.LE:
- case tokens.LSH:
- case tokens.LT:
- case tokens.MINUS:
- case tokens.MOD:
- case tokens.MUL:
- case tokens.NE:
- case tokens.OR:
- case tokens.PLUS:
- case tokens.RSH:
- case tokens.STRICT_EQ:
- case tokens.STRICT_NE:
- case tokens.ULSH:
- case tokens.URSH:
- // Other
- case tokens.ASSIGN:
- case tokens.BLOCK:
- case tokens.CALL:
- case tokens.COMMA:
- case tokens.CONST:
- case tokens.DELETE:
- case tokens.DOT:
- case tokens.HOOK:
- case tokens.INDEX:
- case tokens.LET:
- case tokens.VAR:
- // Special
- case tokens.ARRAY_INIT:
- case tokens.LIST:
- case tokens.OBJECT_INIT:
- case tokens.PROPERTY_INIT:
- case tokens.SCRIPT:
- _.each(node.children, function(child) {
- forEachNode(child, callback);
- });
- break;
-
- case tokens.IDENTIFIER:
- if (node.initializer) {
- forEachNode(node.initializer, callback);
- }
- break;
-
- case tokens.SEMICOLON:
- if (node.expression) {
- forEachNode(node.expression, callback);
- }
- break;
-
- case tokens.DO:
- forEachNode(node.body, callback);
- forEachNode(node.condition, callback);
- break;
-
- case tokens.WHILE:
- forEachNode(node.condition, callback);
- forEachNode(node.body, callback);
- break;
-
- case tokens.FUNCTION:
- forEachNode(node.body, callback);
- break;
-
- case tokens.RETURN:
- if (node.value) {
- forEachNode(node.value, callback);
- }
- break;
-
- case tokens.SWITCH:
- forEachNode(node.discriminant, callback);
- _.each(node.cases, function(child) {
- forEachNode(child, callback);
- });
- break;
-
- case tokens.DEFAULT:
- forEachNode(node.statements, callback);
- break;
-
- case tokens.CASE:
- forEachNode(node.caseLabel, callback);
- forEachNode(node.statements, callback);
- break;
-
- case tokens.LABEL:
- forEachNode(node.statement, callback);
- break;
-
- case tokens.FOR_IN:
- forEachNode(node.iterator, callback);
- forEachNode(node.object, callback);
- forEachNode(node.body, callback);
- break;
-
- case tokens.FOR:
- if (node.setup) {
- forEachNode(node.setup, callback);
- }
- if (node.condition) {
- forEachNode(node.condition, callback);
- }
- if (node.update) {
- forEachNode(node.update, callback);
- }
- forEachNode(node.body, callback);
- break;
-
- case tokens.IF:
- forEachNode(node.condition, callback);
- forEachNode(node.thenPart, callback);
- if (node.elsePart) {
- forEachNode(node.elsePart, callback);
- }
- break;
-
- case tokens.TRY:
- forEachNode(node.tryBlock, callback);
- _.each(node.catchClauses, function(child) {
- forEachNode(child, callback);
- });
- if (node.finallyBlock) {
- forEachNode(node.finallyBlock, callback);
- }
- break;
-
- case tokens.CATCH:
- if (node.guard) {
- forEachNode(node.guard, callback);
- }
- forEachNode(node.block, callback);
- break;
-
- case tokens.THROW:
- forEachNode(node.exception, callback);
- break;
-
- default:
- console.error(node.toString());
- console.error("jsgrep: forEachNode: " +
- node.tokenizer.filename + ":" + node.lineno +
- ": Unimplemented node type");
- process.exit(1);
- break;
- }
-}
-
// vim: ft=javascript
View
548 lib/jsgrep.js
@@ -0,0 +1,548 @@
+var _ = require('underscore');
+var Narcissus = require('narcissus/main');
+
+function tokenString(tt) {
+ var t = Narcissus.definitions.tokens[tt];
+ return /^\W/.test(t) ? Narcissus.definitions.opTypeNames[t] : t.toUpperCase();
+}
+
+/**
+ * Match a set of jsgrep patterns against an AST. Options are:
+ *
+ * - source: AST or String. Source to match against.
+ * - filename: String. Used for errors, and to read from is source isn't given.
+ * - patterns: Array of AST or String. List of patterns to match with.
+ * - pattern: AST or String. Single pattern to match with.
+ * - strictMatches: Boolean. Controls strict matching.
+ * - callback: Function. Called with (node, metavariables) when any pattern
+ * matches an AST node.
+ */
+var jsgrep = exports.jsgrep = function(options) {
+ var ast = options.source;
+ if (!ast) {
+ ast = require('fs').readFileSync(options.filename).toString();
+ }
+ if (ast instanceof String) {
+ ast = Narcissus.parser.parse(ast, options.filename, 1);
+ }
+
+ var patterns = options.patterns || [ options.pattern ];
+ for (var i = 0; i < patterns.length; i++) {
+ if (patterns[i] instanceof String) {
+ patterns[i] = Narcissus.parser.parse(patterns[i], 'pattern', 0);
+ }
+ }
+
+ forEachNode(ast, function(node) {
+ _.each(patterns, function(pattern, patternNum) {
+ options.variables = {};
+ if (astIsEqual(node, pattern, options)) {
+ options.callback.call(null, node, options.variables);
+ }
+ });
+ });
+}
+
+function astMatchEllipsis(nodes, patterns, config) {
+ // XXX(rpatterson): this needs testing!
+ const tokens = Narcissus.definitions.tokenIds;
+ var permitVar = true, clonedConfig = null;
+ function go(i, j) {
+ if (i == nodes.length && j == patterns.length) {
+ return true;
+ }
+
+ if (j == patterns.length) {
+ return false;
+ }
+
+ if (patterns[j].type == tokens.ELLIPSIS && j == patterns.length - 1) {
+ return true;
+ }
+
+ if(i == nodes.length) {
+ return false;
+ }
+
+ if (patterns[j].type == tokens.ELLIPSIS) {
+ permitVar = false;
+ clonedConfig = _.clone(config);
+ return go(i, j + 1) || go(i + 1, j);
+ }
+
+ return astIsEqual(nodes[i], patterns[j],
+ permitVar ? config : clonedConfig) &&
+ go(i + 1, j + 1);
+ }
+ var result = go(0, 0);
+ if (!permitVar &&
+ _.keys(clonedConfig).length != _.keys(config.variables).length) {
+ // ..., A, ... is ambiguous, and if A was matched later in the pattern, we
+ // would have to backtrack to the ellipsis to try a different match. That's
+ // annoying.
+ //
+ // XXX(rpatterson): This incorrectly bails for (..., A)
+ throw {
+ message: "Matching metavariables inside partially-matched lists is " +
+ "unsupported. Sorry!"
+ };
+ }
+ return result;
+}
+
+var astIsEqual = exports.astIsEqual = function(node, pattern, config) {
+ const tokens = Narcissus.definitions.tokenIds;
+
+ if (pattern.type == tokens.IDENTIFIER &&
+ /^[A-Z](_.*)?$/.test(pattern.value)) {
+ if (pattern.value in config.variables) {
+ // Variable already matched, compare this node to that value
+ return astIsEqual(node, config.variables[pattern.value]);
+ } else {
+ // Bind variable to this value
+ config.variables[pattern.value] = node;
+ return true;
+ }
+ }
+
+ if (node.type != pattern.type) {
+ return false;
+ }
+
+ if (node.type == tokens.OBJECT_INIT && !config.strictMatches) {
+ // Strict matching will be handled normally (below).
+ if (pattern.children.length > node.children.length) {
+ return false;
+ }
+
+ var keys = _.clone(pattern.children);
+ for (var i = 0; i < node.children.length; i++) {
+ for (var j = 0; j < keys.length;) {
+ if (astIsEqual(node.children[i], keys[j], config)) {
+ keys.splice(j, 1);
+ break;
+ } else {
+ j++;
+ }
+ }
+
+ if (keys.length == 0) {
+ break;
+ }
+ }
+
+ // No keys left over -> match.
+ return keys.length == 0;
+ }
+
+ switch(node.type) {
+ // Core values
+ case tokens.FALSE:
+ case tokens.IDENTIFIER:
+ case tokens.NULL:
+ case tokens.NUMBER:
+ case tokens.REGEXP:
+ case tokens.STRING:
+ case tokens.THIS:
+ case tokens.TRUE:
+ return node.value == pattern.value;
+ break;
+
+ // 0-child statements
+ case tokens.BREAK:
+ case tokens.CONTINUE:
+ return true;
+ break;
+
+ // Unary expressions
+ case tokens.BITWISE_NOT:
+ case tokens.DECREMENT:
+ case tokens.INCREMENT:
+ case tokens.NEW:
+ case tokens.NEW_WITH_ARGS:
+ case tokens.NOT:
+ case tokens.TYPEOF:
+ case tokens.UNARY_MINUS:
+ case tokens.UNARY_PLUS:
+ case tokens.VOID:
+ // Binary expressions
+ case tokens.AND:
+ case tokens.BITWISE_AND:
+ case tokens.BITWISE_OR:
+ case tokens.BITWISE_XOR:
+ case tokens.DIV:
+ case tokens.EQ:
+ case tokens.GE:
+ case tokens.GT:
+ case tokens.IN:
+ case tokens.INSTANCEOF:
+ case tokens.LE:
+ case tokens.LSH:
+ case tokens.LT:
+ case tokens.MINUS:
+ case tokens.MOD:
+ case tokens.MUL:
+ case tokens.NE:
+ case tokens.OR:
+ case tokens.PLUS:
+ case tokens.RSH:
+ case tokens.STRICT_EQ:
+ case tokens.STRICT_NE:
+ case tokens.ULSH:
+ case tokens.URSH:
+ // Other
+ case tokens.ASSIGN:
+ //case tokens.BLOCK:
+ case tokens.CALL:
+ case tokens.COMMA:
+ case tokens.DELETE:
+ case tokens.DOT:
+ case tokens.HOOK:
+ case tokens.INDEX:
+ // Special
+ case tokens.OBJECT_INIT:
+ case tokens.PROPERTY_INIT:
+ //case tokens.SCRIPT:
+ if (node.children.length == pattern.children.length) {
+ for (var i = 0; i < node.children.length; i++) {
+ if (!astIsEqual(node.children[i], pattern.children[i], config)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ break;
+
+ case tokens.ARRAY_INIT:
+ case tokens.LIST:
+ return astMatchEllipsis(node.children, pattern.children, config);
+ break;
+
+ case tokens.LET:
+ case tokens.VAR:
+ // All of var's children are IDENTIFIERs with name/initializer values
+ // TODO: this does not support destructuring assignments
+ if (pattern.children.length > node.children.length) {
+ return false;
+ }
+
+ var keys = _.clone(pattern.children);
+ for (var i = 0; i < node.children.length; i++) {
+ for (var j = 0; j < keys.length;) {
+ if (astIsEqual(node.children[i], keys[j], config)) {
+ // If the pattern has an initializer, it must be equal to the one
+ // in the source.
+ if (keys[j].initializer &&
+ (!node.children[i].initializer ||
+ !astIsEqual(node.children[i].initializer,
+ keys[j].initializer, config))) {
+ return false;
+ }
+ // If in strict mode and the pattern has no initializer, neither can
+ // the source.
+ if (!keys[j].initializer && config.strictMatches &&
+ node.children[i].initializer) {
+ return false;
+ }
+ keys.splice(j, 1);
+ break;
+ } else {
+ j++;
+ }
+ }
+
+ if (keys.length == 0) {
+ break;
+ }
+ }
+
+ // No keys left over -> match.
+ return keys.length == 0;
+ break;
+
+ case tokens.SEMICOLON:
+ if (!node.expression && !pattern.expression) {
+ return true;
+ } else if (node.expression && pattern.expression) {
+ return astIsEqual(node.expression, pattern.expression, config);
+ }
+ return false;
+ break;
+
+ //case tokens.DO:
+ //forEachNode(node.body, callback);
+ //forEachNode(node.condition, callback);
+ break;
+
+ //case tokens.WHILE:
+ //forEachNode(node.condition, callback);
+ //forEachNode(node.body, callback);
+ break;
+
+ //case tokens.FUNCTION:
+ //forEachNode(node.body, callback);
+ break;
+
+ //case tokens.RETURN:
+ if (node.value) {
+ //forEachNode(node.value, callback);
+ }
+ break;
+
+ //case tokens.SWITCH:
+ //forEachNode(node.discriminant, callback);
+ _.each(node.cases, function(child) {
+ //forEachNode(child, callback);
+ });
+ break;
+
+ //case tokens.DEFAULT:
+ //forEachNode(node.statements, callback);
+ break;
+
+ //case tokens.CASE:
+ //forEachNode(node.caseLabel, callback);
+ //forEachNode(node.statements, callback);
+ break;
+
+ //case tokens.LABEL:
+ //forEachNode(node.statement, callback);
+ break;
+
+ //case tokens.FOR_IN:
+ //forEachNode(node.iterator, callback);
+ //forEachNode(node.object, callback);
+ //forEachNode(node.body, callback);
+ break;
+
+ //case tokens.FOR:
+ //forEachNode(node.setup, callback);
+ //forEachNode(node.condition, callback);
+ //forEachNode(node.update, callback);
+ //forEachNode(node.body, callback);
+ break;
+
+ //case tokens.IF:
+ //forEachNode(node.condition, callback);
+ //forEachNode(node.thenPart, callback);
+ if (node.elsePart) {
+ //forEachNode(node.elsePart, callback);
+ }
+ break;
+
+ //case tokens.TRY:
+ //forEachNode(node.tryBlock, callback);
+ _.each(node.catchClauses, function(child) {
+ //forEachNode(child, callback);
+ });
+ if (node.finallyBlock) {
+ //forEachNode(node.finallyBlock, callback);
+ }
+ break;
+
+ //case tokens.CATCH:
+ if (node.guard) {
+ //forEachNode(node.guard, callback);
+ }
+ //forEachNode(node.block);
+ break;
+
+ case tokens.THROW:
+ return astIsEqual(node.exception, pattern.exception, config);
+ break;
+
+ default:
+ throw {
+ message: "Pattern type is not yet supported: " + tokenString(node.type)
+ };
+ break;
+ }
+}
+
+var forEachNode = exports.forEachNode = function(node, callback) {
+ const tokens = Narcissus.definitions.tokenIds;
+
+ callback(node);
+
+ switch(node.type) {
+ // Core values
+ case tokens.FALSE:
+ case tokens.NULL:
+ case tokens.NUMBER:
+ case tokens.REGEXP:
+ case tokens.STRING:
+ case tokens.THIS:
+ case tokens.TRUE:
+ // 0-child statements
+ case tokens.BREAK:
+ case tokens.CONTINUE:
+ break;
+
+ // Unary expressions
+ case tokens.BITWISE_NOT:
+ case tokens.DECREMENT:
+ case tokens.INCREMENT:
+ case tokens.NEW:
+ case tokens.NEW_WITH_ARGS:
+ case tokens.NOT:
+ case tokens.TYPEOF:
+ case tokens.UNARY_MINUS:
+ case tokens.UNARY_PLUS:
+ case tokens.VOID:
+ // Binary expressions
+ case tokens.AND:
+ case tokens.BITWISE_AND:
+ case tokens.BITWISE_OR:
+ case tokens.BITWISE_XOR:
+ case tokens.DIV:
+ case tokens.EQ:
+ case tokens.GE:
+ case tokens.GT:
+ case tokens.IN:
+ case tokens.INSTANCEOF:
+ case tokens.LE:
+ case tokens.LSH:
+ case tokens.LT:
+ case tokens.MINUS:
+ case tokens.MOD:
+ case tokens.MUL:
+ case tokens.NE:
+ case tokens.OR:
+ case tokens.PLUS:
+ case tokens.RSH:
+ case tokens.STRICT_EQ:
+ case tokens.STRICT_NE:
+ case tokens.ULSH:
+ case tokens.URSH:
+ // Other
+ case tokens.ASSIGN:
+ case tokens.BLOCK:
+ case tokens.CALL:
+ case tokens.COMMA:
+ case tokens.CONST:
+ case tokens.DELETE:
+ case tokens.DOT:
+ case tokens.HOOK:
+ case tokens.INDEX:
+ case tokens.LET:
+ case tokens.VAR:
+ // Special
+ case tokens.ARRAY_INIT:
+ case tokens.LIST:
+ case tokens.OBJECT_INIT:
+ case tokens.PROPERTY_INIT:
+ case tokens.SCRIPT:
+ _.each(node.children, function(child) {
+ forEachNode(child, callback);
+ });
+ break;
+
+ case tokens.IDENTIFIER:
+ if (node.initializer) {
+ forEachNode(node.initializer, callback);
+ }
+ break;
+
+ case tokens.SEMICOLON:
+ if (node.expression) {
+ forEachNode(node.expression, callback);
+ }
+ break;
+
+ case tokens.DO:
+ forEachNode(node.body, callback);
+ forEachNode(node.condition, callback);
+ break;
+
+ case tokens.WHILE:
+ forEachNode(node.condition, callback);
+ forEachNode(node.body, callback);
+ break;
+
+ case tokens.FUNCTION:
+ forEachNode(node.body, callback);
+ break;
+
+ case tokens.RETURN:
+ if (node.value) {
+ forEachNode(node.value, callback);
+ }
+ break;
+
+ case tokens.SWITCH:
+ forEachNode(node.discriminant, callback);
+ _.each(node.cases, function(child) {
+ forEachNode(child, callback);
+ });
+ break;
+
+ case tokens.DEFAULT:
+ forEachNode(node.statements, callback);
+ break;
+
+ case tokens.CASE:
+ forEachNode(node.caseLabel, callback);
+ forEachNode(node.statements, callback);
+ break;
+
+ case tokens.LABEL:
+ forEachNode(node.statement, callback);
+ break;
+
+ case tokens.FOR_IN:
+ forEachNode(node.iterator, callback);
+ forEachNode(node.object, callback);
+ forEachNode(node.body, callback);
+ break;
+
+ case tokens.FOR:
+ if (node.setup) {
+ forEachNode(node.setup, callback);
+ }
+ if (node.condition) {
+ forEachNode(node.condition, callback);
+ }
+ if (node.update) {
+ forEachNode(node.update, callback);
+ }
+ forEachNode(node.body, callback);
+ break;
+
+ case tokens.IF:
+ forEachNode(node.condition, callback);
+ forEachNode(node.thenPart, callback);
+ if (node.elsePart) {
+ forEachNode(node.elsePart, callback);
+ }
+ break;
+
+ case tokens.TRY:
+ forEachNode(node.tryBlock, callback);
+ _.each(node.catchClauses, function(child) {
+ forEachNode(child, callback);
+ });
+ if (node.finallyBlock) {
+ forEachNode(node.finallyBlock, callback);
+ }
+ break;
+
+ case tokens.CATCH:
+ if (node.guard) {
+ forEachNode(node.guard, callback);
+ }
+ forEachNode(node.block, callback);
+ break;
+
+ case tokens.THROW:
+ forEachNode(node.exception, callback);
+ break;
+
+ default:
+ console.error(node.toString());
+ throw {
+ message: "forEachNode: " + node.tokenizer.filename + ":" + node.lineno +
+ ": Unimplemented node type " + tokenString(node.type)
+ };
+ break;
+ }
+}

0 comments on commit 3b611fd

Please sign in to comment.