A
Alex Shabanov
Several days ago I needed an easy to use and easy to extend facility
that performs pattern matching.
I ended up with the following interface:
// matcher function. To prevent situations when no pattern matches the
object given,
// it might be initialized as follows:
// matcher = function() { throw new Error("no applicable pattern
found") }
var matcher
matcher = combine(PATTERN1, CALLBACK1(OBJ, .. OPTIONAL_ARGS){...},
matcher)
matcher = combine(PATTERN2, CALLBACK2(OBJ, .. OPTIONAL_ARGS){...},
matcher)
// the result of combine method shall be immediately callable to
perform pattern matching
matcher = combine(PATTERN3, CALLBACK3(OBJ, .. OPTIONAL_ARGS){...},
matcher)
....
// perform pattern matching
matcher(OBJ, ... OPTIONAL_ARGS)
Here is a sample usage:
var matcher = function(val, arg) {
print("matcher fallback: val = " + val + ", arg = " + arg)
}
matcher = pm.combine({type: "string"}, function(val, arg) {
print({expr: "matcher(stringVal, arg)", value: "val = " + val
+ ", arg = " + arg})
}, matcher)
matcher = pm.combine({instanceOf: Function}, function(val, arg) {
print({expr: "matcher(functionVal, arg)", value: "val = " +
val + ", arg = " + arg})
}, matcher)
matcher = pm.combine({scheme: {key: "number", value: "any"}},
function(val, arg) {
print({expr: "matcher({key:number, value:any}, arg)", value:
"val = (" + val.key + "," + val.value + "), arg = " + arg})
}, matcher)
matcher(5, "one")
matcher("str", "two")
matcher(new Function("return 1"), "three")
matcher({key: 12, value: 34}, "four")
matcher({key: "some", value: "unk"}, "five")
Here is an implementation:
// namespace
var pm = {}
/**
* Matcher functions constructors are used in pm.combine method.
* Each key in this object corresponds to the certain pattern member.
*/
pm._matcherConstructors = {
instanceOf: function (matcher, instanceTarget) {
return function (obj) {
if (obj instanceof instanceTarget) {
return matcher.apply(this, arguments)
}
return false
}
},
type: function (matcher, typeId) {
return function (obj) {
if (typeof(obj) === typeId) {
return matcher.apply(this, arguments)
}
return false
}
},
scheme: function (matcher, scheme) {
return function (obj) {
if (typeof(obj) !== "object") {
return false
}
for (var i in scheme) {
if (i in obj) {
var target = obj
var source = scheme
var sourceType = typeof(source)
if (sourceType === "string") {
if (source === "any" || source ==
typeof(target)) {
continue
}
return false
}
if (source !== target) {
return false
}
}
else {
return false
}
}
return matcher.apply(this, arguments)
}
}
}
/**
* Creates pattern matching function that accepts the pattern given.
* The latter combined patterns takes priority over the previously
declared ones.
* @param pattern Pattern to match the target object.
* @param callback User-defined callback to accept target object as
well as the accompanying arguments.
* @param prevMatcher Previous matcher function created by combine
method or null or undefined.
* @returns Matcher function to be used as follows:
matcher.call(objectToBeMatched, optionalArguments...).
*/
pm.combine = function(pattern, callback, prevMatcher) {
var matcher = function() {
callback.apply(this, arguments)
return true
}
// join visitor function according to the pattern given
for (var i in pattern) {
if (!(i in pm._matcherConstructors)) {
throw new Error("unexpected pattern tag: " + i)
}
matcher = pm._matcherConstructors(matcher, pattern)
}
// if prev matcher either undefined or null - create new function
if (prevMatcher == null) {
return matcher
}
else {
return function() {
if (matcher.apply(this, arguments)) {
return true
}
return prevMatcher.apply(this, arguments)
}
}
}
/**
* Helper function that initializes matcher for all the types of
objects with
* the callback that throws an error.
*/
pm.unknownObjectMatcher = function() {
throw new Error("unknown object matched")
}
Comments appreciated
that performs pattern matching.
I ended up with the following interface:
// matcher function. To prevent situations when no pattern matches the
object given,
// it might be initialized as follows:
// matcher = function() { throw new Error("no applicable pattern
found") }
var matcher
matcher = combine(PATTERN1, CALLBACK1(OBJ, .. OPTIONAL_ARGS){...},
matcher)
matcher = combine(PATTERN2, CALLBACK2(OBJ, .. OPTIONAL_ARGS){...},
matcher)
// the result of combine method shall be immediately callable to
perform pattern matching
matcher = combine(PATTERN3, CALLBACK3(OBJ, .. OPTIONAL_ARGS){...},
matcher)
....
// perform pattern matching
matcher(OBJ, ... OPTIONAL_ARGS)
Here is a sample usage:
var matcher = function(val, arg) {
print("matcher fallback: val = " + val + ", arg = " + arg)
}
matcher = pm.combine({type: "string"}, function(val, arg) {
print({expr: "matcher(stringVal, arg)", value: "val = " + val
+ ", arg = " + arg})
}, matcher)
matcher = pm.combine({instanceOf: Function}, function(val, arg) {
print({expr: "matcher(functionVal, arg)", value: "val = " +
val + ", arg = " + arg})
}, matcher)
matcher = pm.combine({scheme: {key: "number", value: "any"}},
function(val, arg) {
print({expr: "matcher({key:number, value:any}, arg)", value:
"val = (" + val.key + "," + val.value + "), arg = " + arg})
}, matcher)
matcher(5, "one")
matcher("str", "two")
matcher(new Function("return 1"), "three")
matcher({key: 12, value: 34}, "four")
matcher({key: "some", value: "unk"}, "five")
Here is an implementation:
// namespace
var pm = {}
/**
* Matcher functions constructors are used in pm.combine method.
* Each key in this object corresponds to the certain pattern member.
*/
pm._matcherConstructors = {
instanceOf: function (matcher, instanceTarget) {
return function (obj) {
if (obj instanceof instanceTarget) {
return matcher.apply(this, arguments)
}
return false
}
},
type: function (matcher, typeId) {
return function (obj) {
if (typeof(obj) === typeId) {
return matcher.apply(this, arguments)
}
return false
}
},
scheme: function (matcher, scheme) {
return function (obj) {
if (typeof(obj) !== "object") {
return false
}
for (var i in scheme) {
if (i in obj) {
var target = obj
var source = scheme
var sourceType = typeof(source)
if (sourceType === "string") {
if (source === "any" || source ==
typeof(target)) {
continue
}
return false
}
if (source !== target) {
return false
}
}
else {
return false
}
}
return matcher.apply(this, arguments)
}
}
}
/**
* Creates pattern matching function that accepts the pattern given.
* The latter combined patterns takes priority over the previously
declared ones.
* @param pattern Pattern to match the target object.
* @param callback User-defined callback to accept target object as
well as the accompanying arguments.
* @param prevMatcher Previous matcher function created by combine
method or null or undefined.
* @returns Matcher function to be used as follows:
matcher.call(objectToBeMatched, optionalArguments...).
*/
pm.combine = function(pattern, callback, prevMatcher) {
var matcher = function() {
callback.apply(this, arguments)
return true
}
// join visitor function according to the pattern given
for (var i in pattern) {
if (!(i in pm._matcherConstructors)) {
throw new Error("unexpected pattern tag: " + i)
}
matcher = pm._matcherConstructors(matcher, pattern)
}
// if prev matcher either undefined or null - create new function
if (prevMatcher == null) {
return matcher
}
else {
return function() {
if (matcher.apply(this, arguments)) {
return true
}
return prevMatcher.apply(this, arguments)
}
}
}
/**
* Helper function that initializes matcher for all the types of
objects with
* the callback that throws an error.
*/
pm.unknownObjectMatcher = function() {
throw new Error("unknown object matched")
}
Comments appreciated