Всем привет.
Недавно потребовалось заиметь простой и легко расширяемый способ сопоставления по образцу.
Было решено сделать конструирование сопоставляющей функции в функциональном стиле, иными словами нечто вроде такого:
// сопоставляющая функция. Её можно инициализировать, чтобы в случае отстутствия шаблона выкидывалось исключение
// к примеру так: 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)
// промежуточные результаты matcher можно так же сохранять и использовать в сопоставлении
matcher = combine(PATTERN3, CALLBACK3(OBJ, .. OPTIONAL_ARGS){...}, matcher)
...
// сопоставление
matcher(OBJ, ... OPTIONAL_ARGS)
Пример использования:
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")
Реализация (простая как 3 рубля. Расширение сопоставителя — простое добавление):
// 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[i]
var source = scheme[i]
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[i](matcher, pattern[i])
}
// 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")
}
Буду рад любым комментариям