| // |reftest| skip |
| |
| // A little pattern-matching library. |
| var Match = |
| |
| (function() { |
| |
| function Pattern(template) { |
| // act like a constructor even as a function |
| if (!(this instanceof Pattern)) |
| return new Pattern(template); |
| |
| this.template = template; |
| } |
| |
| Pattern.prototype = { |
| match: function(act) { |
| return match(act, this.template); |
| }, |
| |
| matches: function(act) { |
| try { |
| return this.match(act); |
| } |
| catch (e if e instanceof MatchError) { |
| return false; |
| } |
| }, |
| |
| assert: function(act, message) { |
| try { |
| return this.match(act); |
| } |
| catch (e if e instanceof MatchError) { |
| throw new Error((message || "failed match") + ": " + e.message); |
| } |
| }, |
| |
| toString: () => "[object Pattern]" |
| }; |
| |
| Pattern.ANY = new Pattern; |
| Pattern.ANY.template = Pattern.ANY; |
| |
| Pattern.NUMBER = new Pattern; |
| Pattern.NUMBER.match = function (act) { |
| if (typeof act !== 'number') { |
| throw new MatchError("Expected number, got: " + quote(act)); |
| } |
| } |
| |
| Pattern.NATURAL = new Pattern |
| Pattern.NATURAL.match = function (act) { |
| if (typeof act !== 'number' || act !== Math.floor(act) || act < 0) { |
| throw new MatchError("Expected natural number, got: " + quote(act)); |
| } |
| } |
| |
| var quote = uneval; |
| |
| function MatchError(msg) { |
| this.message = msg; |
| } |
| |
| MatchError.prototype = { |
| toString: function() { |
| return "match error: " + this.message; |
| } |
| }; |
| |
| function isAtom(x) { |
| return (typeof x === "number") || |
| (typeof x === "string") || |
| (typeof x === "boolean") || |
| (x === null) || |
| (typeof x === "object" && x instanceof RegExp); |
| } |
| |
| function isObject(x) { |
| return (x !== null) && (typeof x === "object"); |
| } |
| |
| function isFunction(x) { |
| return typeof x === "function"; |
| } |
| |
| function isArrayLike(x) { |
| return isObject(x) && ("length" in x); |
| } |
| |
| function matchAtom(act, exp) { |
| if ((typeof exp) === "number" && isNaN(exp)) { |
| if ((typeof act) !== "number" || !isNaN(act)) |
| throw new MatchError("expected NaN, got: " + quote(act)); |
| return true; |
| } |
| |
| if (exp === null) { |
| if (act !== null) |
| throw new MatchError("expected null, got: " + quote(act)); |
| return true; |
| } |
| |
| if (exp instanceof RegExp) { |
| if (!(act instanceof RegExp) || exp.source !== act.source) |
| throw new MatchError("expected " + quote(exp) + ", got: " + quote(act)); |
| return true; |
| } |
| |
| switch (typeof exp) { |
| case "string": |
| if (act !== exp) |
| throw new MatchError("expected " + quote(exp) + ", got " + quote(act)); |
| return true; |
| case "boolean": |
| case "number": |
| if (exp !== act) |
| throw new MatchError("expected " + exp + ", got " + quote(act)); |
| return true; |
| } |
| |
| throw new Error("bad pattern: " + exp.toSource()); |
| } |
| |
| function matchObject(act, exp) { |
| if (!isObject(act)) |
| throw new MatchError("expected object, got " + quote(act)); |
| |
| for (var key in exp) { |
| if (!(key in act)) |
| throw new MatchError("expected property " + quote(key) + " not found in " + quote(act)); |
| match(act[key], exp[key]); |
| } |
| |
| return true; |
| } |
| |
| function matchFunction(act, exp) { |
| if (!isFunction(act)) |
| throw new MatchError("expected function, got " + quote(act)); |
| |
| if (act !== exp) |
| throw new MatchError("expected function: " + exp + |
| "\nbut got different function: " + act); |
| } |
| |
| function matchArray(act, exp) { |
| if (!isObject(act) || !("length" in act)) |
| throw new MatchError("expected array-like object, got " + quote(act)); |
| |
| var length = exp.length; |
| if (act.length !== exp.length) |
| throw new MatchError("expected array-like object of length " + length + ", got " + quote(act)); |
| |
| for (var i = 0; i < length; i++) { |
| if (i in exp) { |
| if (!(i in act)) |
| throw new MatchError("expected array property " + i + " not found in " + quote(act)); |
| match(act[i], exp[i]); |
| } |
| } |
| |
| return true; |
| } |
| |
| function match(act, exp) { |
| if (exp === Pattern.ANY) |
| return true; |
| |
| if (exp instanceof Pattern) |
| return exp.match(act); |
| |
| if (isAtom(exp)) |
| return matchAtom(act, exp); |
| |
| if (isArrayLike(exp)) |
| return matchArray(act, exp); |
| |
| if (isFunction(exp)) |
| return matchFunction(act, exp); |
| |
| return matchObject(act, exp); |
| } |
| |
| return { Pattern: Pattern, |
| MatchError: MatchError }; |
| |
| })(); |
| |