Comparing version 0.0.10 to 0.0.11
@@ -13,7 +13,6 @@ /** | ||
var contents = // ... | ||
var syntax = esprima.parse(syntax); | ||
var syntax = esprima.parse(contents); | ||
jsstana.traverse(syntax, function (node) { | ||
jsstana.traverse(syntax, function (node) { | ||
var m = jsstana.match("(call alert ?argument)", node); | ||
var m = jsstana.match("(call alert ?argument)", node); | ||
if (m) { | ||
@@ -113,2 +112,3 @@ console.log("alert called with argument", m.argument); | ||
// http://ecma-international.org/ecma-262/5.1/#sec-7.7 | ||
var validBinaryOperators = [ | ||
@@ -131,3 +131,70 @@ "+", "-", "*", "/", "%", | ||
var validAssignmentOperators = [ | ||
"=", | ||
"+=", "-=", "*=", "/=", "%=", | ||
"<<=", ">>=", ">>>=", | ||
"&=", "|=", "^=", | ||
]; | ||
var builtInMatchers = { | ||
"not": notMatcher, | ||
"or": orMatcher, | ||
"and": andMatcher, | ||
"var": varMatcher, | ||
"ident": identMatcher, | ||
"return": returnMatcher, | ||
"call": _.partial(callMatcher, true), | ||
"new": _.partial(callMatcher, false), | ||
"expr": expressionMatcher, | ||
"binary": binaryMatcher, | ||
"unary": unaryMatcher, | ||
"update": _.partial(updateMatcher, undefined), | ||
"prefix": _.partial(updateMatcher, true), | ||
"postfix": _.partial(updateMatcher, false), | ||
"assign": assignMatcher, | ||
"member": _.partial(memberMatcher, undefined), | ||
"property": _.partial(memberMatcher, false), | ||
"subscript": _.partial(memberMatcher, true), | ||
"lookup": lookupMatcher, | ||
"throw": throwMatcher, | ||
"ternary": ternaryMatcher, | ||
"literal": _.partial(literalMatcher, "any"), | ||
"string": _.partial(literalMatcher, "string"), | ||
"number": _.partial(literalMatcher, "number"), | ||
"bool": _.partial(literalMatcher, "bool"), | ||
"regexp": _.partial(literalMatcher, "regexp"), | ||
"null": _.partial(literalBuiltinMatcher, "null"), | ||
"true": _.partial(literalBuiltinMatcher, "true"), | ||
"false": _.partial(literalBuiltinMatcher, "false"), | ||
"infinity": _.partial(literalBuiltinMatcher, "infinity"), | ||
"nan": _.partial(literalBuiltinMatcher, "nan"), | ||
"undefined": _.partial(literalBuiltinMatcher, "undefined"), | ||
"null-node": nullNodeMatcher, | ||
}; | ||
var unaryOperators = _.difference(validUnaryOperators, validBinaryOperators); | ||
// "TODO: , == SequenceExpression" | ||
_.each(validBinaryOperators, function (binop) { | ||
builtInMatchers[binop] = _.partial(binaryMatcher, binop); | ||
}); | ||
_.each(unaryOperators, function (unop) { | ||
builtInMatchers[unop] = _.partial(unaryMatcher, unop); | ||
}); | ||
_.each(validAssignmentOperators, function (assignOp) { | ||
builtInMatchers[assignOp] = _.partial(assignMatcher, assignOp); | ||
}); | ||
function matcher(sexpr) { | ||
/* jshint validthis:true */ | ||
var that = this instanceof JsstanaContext ? this : new JsstanaContext(); | ||
var args = _.toArray(arguments).slice(1); | ||
if (args.length !== 0) { | ||
that = new JsstanaContext(that); | ||
that.positionalMatchers = args; | ||
} | ||
if (_.isString(sexpr)) { | ||
@@ -138,3 +205,3 @@ if (sexpr.indexOf(".") !== -1) { | ||
}); | ||
return matcher(sexpr); | ||
return that.matcher(sexpr); | ||
} else if (sexpr === "?") { | ||
@@ -151,2 +218,7 @@ return function () { | ||
}; | ||
} else if (sexpr.match(/^\$\d+$/)) { | ||
sexpr = parseInt(sexpr.substr(1), 10); | ||
assert(sexpr < that.positionalMatchers.length, | ||
"there is only " + that.positionalMatchers.length + " positional matchers, required " + sexpr); | ||
return that.positionalMatchers[sexpr]; | ||
} else if (sexpr === "true") { | ||
@@ -182,52 +254,6 @@ return function (node) { | ||
var matchers = { | ||
"not": notMatcher, | ||
"or": orMatcher, | ||
"and": andMatcher, | ||
"var": varMatcher, | ||
"ident": identMatcher, | ||
"return": returnMatcher, | ||
"call": callMatcher.bind(undefined, true), | ||
"new": callMatcher.bind(undefined, false), | ||
"expr": expressionMatcher, | ||
"binary": binaryMatcher, | ||
"unary": unaryMatcher, | ||
"update": updateMatcher.bind(undefined, undefined), | ||
"prefix": updateMatcher.bind(undefined, true), | ||
"postfix": updateMatcher.bind(undefined, false), | ||
"member": memberMatcher.bind(undefined, undefined), | ||
"property": memberMatcher.bind(undefined, false), | ||
"subscript": memberMatcher.bind(undefined, true), | ||
"lookup": lookupMatcher, | ||
"throw": throwMatcher, | ||
"ternary": ternaryMatcher, | ||
"literal": literalMatcher.bind(undefined, "any"), | ||
"string": literalMatcher.bind(undefined, "string"), | ||
"number": literalMatcher.bind(undefined, "number"), | ||
"bool": literalMatcher.bind(undefined, "bool"), | ||
"regexp": literalMatcher.bind(undefined, "regexp"), | ||
"null": literalBuiltinMatcher.bind(undefined, "null"), | ||
"true": literalBuiltinMatcher.bind(undefined, "true"), | ||
"false": literalBuiltinMatcher.bind(undefined, "false"), | ||
"infinity": literalBuiltinMatcher.bind(undefined, "infinity"), | ||
"nan": literalBuiltinMatcher.bind(undefined, "nan"), | ||
"undefined": literalBuiltinMatcher.bind(undefined, "undefined"), | ||
"null-node": nullNodeMatcher, | ||
}; | ||
var binaryOperators = validBinaryOperators; | ||
var unaryOperators = _.difference(validUnaryOperators, validBinaryOperators); | ||
// "TODO: , == SequenceExpression" | ||
_.each(binaryOperators, function (binop) { | ||
matchers[binop] = binaryMatcher.bind(undefined, binop); | ||
}); | ||
_.each(unaryOperators, function (unop) { | ||
matchers[unop] = unaryMatcher.bind(undefined, unop); | ||
}); | ||
if (_.has(matchers, rator)) { | ||
return matchers[rator].apply(undefined, rands); | ||
if (_.has(that.matchers, rator)) { | ||
return that.matchers[rator].apply(that, rands); | ||
} else if (_.has(builtInMatchers, rator)) { | ||
return builtInMatchers[rator].apply(that, rands); | ||
} else { | ||
@@ -259,6 +285,7 @@ throw new Error("unknown node type: " + rator); | ||
function notMatcher(pattern) { | ||
assert("not", 1, arguments); | ||
/* jshint validthis:true */ | ||
assertArguments("not", 1, arguments); | ||
pattern = pattern || "?"; | ||
var patternMatcher = matcher(pattern); | ||
var patternMatcher = this.matcher(pattern); | ||
@@ -276,4 +303,5 @@ return function (node) { | ||
function orMatcher() { | ||
/* jshint validthis:true */ | ||
var args = _.toArray(arguments); | ||
var argsMatchers = args.map(matcher); | ||
var argsMatchers = args.map(matcher, this); | ||
@@ -298,4 +326,5 @@ return function (node) { | ||
function andMatcher() { | ||
/* jshint validthis:true */ | ||
var args = _.toArray(arguments); | ||
var argsMatchers = args.map(matcher); | ||
var argsMatchers = args.map(matcher, this); | ||
@@ -336,6 +365,7 @@ return function (node) { | ||
function returnMatcher(value) { | ||
/* jshint validthis:true */ | ||
assertArguments("return", 1, arguments); | ||
value = value || "?"; | ||
var valueMatcher = matcher(value); | ||
var valueMatcher = this.matcher(value); | ||
@@ -476,2 +506,3 @@ return function (node) { | ||
function varMatcher(id, init) { | ||
/* jshint validthis:true */ | ||
assertArguments("var", 2, arguments); | ||
@@ -482,3 +513,3 @@ id = id || "?"; | ||
var idMatcher = identifierMatcher(id); | ||
var initMatcher = matcher(init); | ||
var initMatcher = this.matcher(init); | ||
@@ -523,5 +554,6 @@ return function (node) { | ||
function callMatcher(callnew, callee) { | ||
/* jshint validthis:true */ | ||
callee = callee || "?"; | ||
var calleeMatcher = matcher(callee); | ||
var calleeMatcher = this.matcher(callee); | ||
var args = _.toArray(arguments).slice(2); | ||
@@ -551,3 +583,3 @@ var dotted = false; | ||
var argumentMatchers = args.map(matcher); | ||
var argumentMatchers = args.map(matcher, this); | ||
@@ -582,6 +614,7 @@ return function (node) { | ||
function expressionMatcher(expr) { | ||
/* jshint validthis:true */ | ||
assertArguments("expr", 1, arguments); | ||
expr = expr || "?"; | ||
var exprMatcher = matcher(expr); | ||
var exprMatcher = this.matcher(expr); | ||
@@ -603,2 +636,3 @@ return function (node) { | ||
function binaryMatcher(operator, lhs, rhs) { | ||
/* jshint validthis:true */ | ||
assertArguments("binary", 3, arguments); | ||
@@ -613,4 +647,4 @@ | ||
var opMatcher = operatorMatcher(operator, validBinaryOperators); | ||
var lhsMatcher = matcher(lhs); | ||
var rhsMatcher = matcher(rhs); | ||
var lhsMatcher = this.matcher(lhs); | ||
var rhsMatcher = this.matcher(rhs); | ||
@@ -636,2 +670,3 @@ return function (node) { | ||
function unaryMatcher(operator, value) { | ||
/* jshint validthis:true */ | ||
assertArguments("unary", 2, arguments, 1); | ||
@@ -642,6 +677,6 @@ | ||
assert(_.isString(operator), "binary operator should be string expr"); | ||
assert(_.isString(operator), "unary operator should be string expr"); | ||
var opMatcher = operatorMatcher(operator, validUnaryOperators); | ||
var valueMatcher = matcher(value); | ||
var valueMatcher = this.matcher(value); | ||
@@ -666,2 +701,3 @@ return function (node) { | ||
function updateMatcher(prefix, operator, value) { | ||
/* jshint validthis:true */ | ||
assertArguments("update/postfix/prefix", 2, arguments, 1); | ||
@@ -672,6 +708,6 @@ | ||
assert(_.isString(operator), "binary operator should be string expr"); | ||
assert(_.isString(operator), "update operator should be string expr"); | ||
var opMatcher = operatorMatcher(operator, validUpdateOperators); | ||
var valueMatcher = matcher(value); | ||
var valueMatcher = this.matcher(value); | ||
@@ -690,2 +726,31 @@ return function (node) { | ||
/** | ||
#### (assign op var value) | ||
Matches `AssignmentExpression`. | ||
*/ | ||
function assignMatcher(operator, variable, value) { | ||
/* jshint validthis:true */ | ||
assertArguments("assign", 3, arguments); | ||
operator = operator || "?"; | ||
variable = variable || "?"; | ||
value = value || "?"; | ||
assert(_.isString(operator), "assignment operator should be string expr"); | ||
var opMatcher = operatorMatcher(operator, validAssignmentOperators); | ||
var variableMatcher = this.matcher(variable); | ||
var valueMatcher = this.matcher(value); | ||
return function (node) { | ||
if (node.type !== "AssignmentExpression") { return undefined; } | ||
var opM = opMatcher(node.operator); | ||
var variableM = variableMatcher(node.left); | ||
var valueM = valueMatcher(node.right); | ||
return combineMatches(opM, variableM, valueM); | ||
}; | ||
} | ||
/** | ||
#### (member object property) | ||
@@ -699,2 +764,3 @@ | ||
function memberMatcher(computed, object, property) { | ||
/* jshint validthis:true */ | ||
assertArguments("member/property/subscript", 2, arguments, 1); | ||
@@ -705,3 +771,3 @@ object = object || "?"; | ||
var objectMatcher = matcher(object); | ||
var propertyMatcher = matcher(property); | ||
var propertyMatcher = this.matcher(property); | ||
@@ -745,6 +811,7 @@ return function (node) { | ||
function throwMatcher(ex) { | ||
/* jshint validthis:true */ | ||
assertArguments("throw", 1, arguments); | ||
ex = ex || "?"; | ||
var exMatcher = matcher(ex); | ||
var exMatcher = this.matcher(ex); | ||
@@ -764,2 +831,3 @@ return function (node) { | ||
function ternaryMatcher(test, con, alt) { | ||
/* jshint validthis:true */ | ||
assertArguments("ternary", 3, arguments); | ||
@@ -770,5 +838,5 @@ test = test || "?"; | ||
var testMatcher = matcher(test); | ||
var consequentMatcher = matcher(con); | ||
var alternateMatcher = matcher(alt); | ||
var testMatcher = this.matcher(test); | ||
var consequentMatcher = this.matcher(con); | ||
var alternateMatcher = this.matcher(alt); | ||
@@ -801,6 +869,8 @@ return function (node) { | ||
function match(pattern, node) { | ||
/* jshint validthis:true */ | ||
assert(arguments.length === 1 || arguments.length === 2, "match takes one or two arguments"); | ||
var that = this instanceof JsstanaContext ? this : new JsstanaContext(); | ||
if (!_.has(matchPatterns, pattern)) { | ||
matchPatterns[pattern] = matcher(sexpr.parse(pattern)); | ||
matchPatterns[pattern] = that.matcher(sexpr.parse(pattern)); | ||
} | ||
@@ -817,7 +887,65 @@ | ||
/** | ||
### createMatcher(pattern, [posMatcher]) | ||
Create matcher. With one argument, `matcher(pattern) === match(pattern)`. | ||
With additional arguments, you can add `$0`, `$1`... additional anonymous matchers. | ||
```js | ||
var matcher = jsstana.createMatcher("(expr (= a $0))", function (node) { | ||
return node.type === "ObjectExpression" && node.properties.length === 0 ? {} : undefined; | ||
}); | ||
``` | ||
*/ | ||
function createMatcher() { | ||
/* jshint validthis:true */ | ||
var args = _.toArray(arguments); | ||
args[0] = sexpr.parse(args[0]); | ||
return matcher.apply(this, args); | ||
} | ||
/** | ||
### new jsstana() | ||
Create new jsstana context. You can add new operations to this one. | ||
```js | ||
var ctx = new jsstana(); | ||
ctx.addMatchers("empty-object", function () { | ||
this.assertArguments("empty-object", 0, arguments); | ||
return function (node) { | ||
return node.type === "ObjectExpression" && node.properties.length === 0 ? {} : undefined; | ||
}; | ||
}); | ||
ctx.match("(empty-object", node); | ||
``` | ||
You may compile submatchers with `this.matcher(sexpr)` and combine their results with `this.combineMatches`. | ||
`this.assertArguments` checks argument (rator) count, to help validate pattern grammar. | ||
*/ | ||
function JsstanaContext(context) { | ||
this.matchers = context instanceof JsstanaContext ? context.matchers : {}; | ||
this.positionalMatchers = []; | ||
} | ||
// matcher utilities | ||
JsstanaContext.prototype.combineMatches = combineMatches; | ||
JsstanaContext.prototype.assertArguments = assertArguments; | ||
JsstanaContext.prototype.matcher = matcher; | ||
// public api | ||
JsstanaContext.prototype.match = match; | ||
JsstanaContext.prototype.createMatcher = createMatcher; | ||
JsstanaContext.prototype.addMatcher = function (name, f) { | ||
assert(!_.has(this.matchers, name), "matcher names should be unique: " + name); | ||
this.matchers[name] = f; | ||
}; | ||
// Exports | ||
exports.traverse = traverse; | ||
exports.parseSExpr = sexpr.parse; | ||
exports.match = match; | ||
JsstanaContext.traverse = traverse; | ||
JsstanaContext.match = match; | ||
JsstanaContext.createMatcher = createMatcher; | ||
module.exports = JsstanaContext; | ||
/** | ||
@@ -836,2 +964,6 @@ | ||
- 0.0.11 User-provided patterns | ||
- fixed installing on Windows | ||
- assignment pattern | ||
- anonymous matchers | ||
- 0.0.10 ident pattern | ||
@@ -838,0 +970,0 @@ - 0.0.9 Boolean patterns |
@@ -12,5 +12,19 @@ "use string"; | ||
function unquote(m) { | ||
return m[1].replace(/\\(.)/g, function (unused, c) { | ||
return c; | ||
}); | ||
} | ||
function quote(str) { | ||
return str.replace(/(['\\])/g, function (unused, c) { | ||
return "\\" + c; | ||
}); | ||
} | ||
var sexprP = p.alt( | ||
p.seq(lexemeP("("), p.repeat(function () { return sexprP; }), lexemeP(")")).onMatch(function (arr) { return arr[1]; }), | ||
lexemeP(p.regex(/[a-zA-Z\?\.\-\/*+<>=!%,~][a-zA-Z0-9_\?\.\-\/*+<>+!%,~]*/)).onMatch(_.first), | ||
lexemeP(p.regex(/[a-zA-Z\?\.\-\/*+<>=!%,~\$][a-zA-Z0-9_\?\.\-\/*+<>=+!%,~\$]*/)).onMatch(_.first), | ||
lexemeP(p.regex(/"((?:[^"]|\\.)*)"/)).onMatch(unquote), | ||
lexemeP(p.regex(/'((?:[^']|\\.)*)'/)).onMatch(unquote), | ||
lexemeP(p.regex(/[0-9]+/)).onMatch(function (m) { return parseInt(_.first(m), 10); } ) | ||
@@ -30,4 +44,27 @@ ); | ||
function stringifyString(sexpr) { | ||
if (sexpr === "") { | ||
return "''"; | ||
} else if (sexpr.match(/[ '"]/) || sexpr.match(/^\d*$/)) { | ||
return "'" + quote(sexpr) + "'"; | ||
} else { | ||
return sexpr; | ||
} | ||
} | ||
function stringify(sexpr) { | ||
if (_.isString(sexpr)) { | ||
return stringifyString(sexpr); | ||
} else if (_.isNumber(sexpr)) { | ||
return ""+sexpr; | ||
} else if (_.isArray(sexpr)) { | ||
return "(" + sexpr.map(stringify).join(" ") + ")"; | ||
} | ||
throw new Error("sexpr should be an array, a number or a string"); | ||
} | ||
module.exports = { | ||
parse: parse, | ||
}; | ||
stringify: stringify, | ||
}; |
{ | ||
"name": "jsstana", | ||
"description": "s-expression match patterns for Mozilla Parser AST", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"homepage": "https://github.com/phadej/jsstana", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -12,7 +12,6 @@ # jsstana [![Build Status](https://secure.travis-ci.org/phadej/jsstana.png?branch=master)](http://travis-ci.org/phadej/jsstana) | ||
var contents = // ... | ||
var syntax = esprima.parse(syntax); | ||
var syntax = esprima.parse(contents); | ||
jsstana.traverse(syntax, function (node) { | ||
jsstana.traverse(syntax, function (node) { | ||
var m = jsstana.match("(call alert ?argument)", node); | ||
var m = jsstana.match("(call alert ?argument)", node); | ||
if (m) { | ||
@@ -114,2 +113,6 @@ console.log("alert called with argument", m.argument); | ||
#### (assign op var value) | ||
Matches `AssignmentExpression`. | ||
#### (member object property) | ||
@@ -147,2 +150,31 @@ | ||
### createMatcher(pattern, [posMatcher]) | ||
Create matcher. With one argument, `matcher(pattern) === match(pattern)`. | ||
With additional arguments, you can add `$0`, `$1`... additional anonymous matchers. | ||
```js | ||
var matcher = jsstana.createMatcher("(expr (= a $0))", function (node) { | ||
return node.type === "ObjectExpression" && node.properties.length === 0 ? {} : undefined; | ||
}); | ||
``` | ||
### new jsstana() | ||
Create new jsstana context. You can add new operations to this one. | ||
```js | ||
var ctx = new jsstana(); | ||
ctx.addMatchers("empty-object", function () { | ||
this.assertArguments("empty-object", 0, arguments); | ||
return function (node) { | ||
return node.type === "ObjectExpression" && node.properties.length === 0 ? {} : undefined; | ||
}; | ||
}); | ||
ctx.match("(empty-object", node); | ||
``` | ||
You may compile submatchers with `this.matcher(sexpr)` and combine their results with `this.combineMatches`. | ||
`this.assertArguments` checks argument (rator) count, to help validate pattern grammar. | ||
## Contributing | ||
@@ -159,2 +191,6 @@ | ||
- 0.0.11 User-provided patterns | ||
- fixed installing on Windows | ||
- assignment pattern | ||
- anonymous matchers | ||
- 0.0.10 ident pattern | ||
@@ -184,2 +220,1 @@ - 0.0.9 Boolean patterns | ||
Licensed under the BSD3 license. | ||
@@ -61,3 +61,3 @@ /* global describe:true, it:true */ | ||
it("takes operands as second and third parameters, non-match", function () { | ||
it("takes operands as second and third parameters", function () { | ||
var syntax = esprima.parse("1 + 2;"); | ||
@@ -64,0 +64,0 @@ var node = syntax.body[0]; |
@@ -21,10 +21,1 @@ /* global describe:true, it:true */ | ||
}); | ||
describe("parseSExpr()", function () { | ||
it("works", function () { | ||
var actual = jsstana.parseSExpr("(foo bar 1 2 (1 2) 1)"); | ||
var expected = ["foo", "bar", 1, 2, [1, 2], 1]; | ||
assert.deepEqual(actual, expected); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35
2501
216
97899