diff options
Diffstat (limited to 'js/src/tests/non262/reflect-parse/Match.js')
-rw-r--r-- | js/src/tests/non262/reflect-parse/Match.js | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/js/src/tests/non262/reflect-parse/Match.js b/js/src/tests/non262/reflect-parse/Match.js new file mode 100644 index 0000000000..2a14d8a315 --- /dev/null +++ b/js/src/tests/non262/reflect-parse/Match.js @@ -0,0 +1,242 @@ +// |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)) + throw e; + return false; + } + }, + + assert: function(act, message) { + try { + return this.match(act); + } + catch (e) { + if (!(e instanceof MatchError)) + throw e; + 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)); + } + } + + class ObjectWithExactly extends Pattern { + constructor(template) { + super(template); + } + + match(actual) { + return matchObjectWithExactly(actual, this.template) + } + } + + Pattern.OBJECT_WITH_EXACTLY = function (template) { + return new ObjectWithExactly(template); + } + + var quote = JSON.stringify; + + class MatchError extends Error { + toString() { + return "match error: " + this.message; + } + }; + + Pattern.MatchError = MatchError; + + function isAtom(x) { + return (typeof x === "number") || + (typeof x === "string") || + (typeof x === "boolean") || + (x === null) || + (x === undefined) || + (typeof x === "object" && x instanceof RegExp) || + (typeof x === "bigint"); + } + + 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": + case "undefined": + if (act !== exp) + throw new MatchError("expected " + quote(exp) + ", got " + quote(act)); + return true; + case "boolean": + case "number": + case "bigint": + if (exp !== act) + throw new MatchError("expected " + exp + ", got " + quote(act)); + return true; + } + + throw new Error("bad pattern: " + JSON.stringify(exp)); + } + + // Match an object having at least the expected properties. + function matchObjectWithAtLeast(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)); + try { + match(act[key], exp[key]); + } catch (inner) { + if (!(inner instanceof MatchError)) { + throw inner; + } + inner.message = `matching property "${String(key)}":\n${inner.message}`; + throw inner; + } + } + + return true; + } + + // Match an object having all the expected properties and no more. + function matchObjectWithExactly(act, exp) { + matchObjectWithAtLeast(act, exp); + + for (var key in act) { + if (!(key in exp)) { + throw new MatchError("unexpected property " + quote(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)); + try { + match(act[i], exp[i]); + } catch (inner) { + if (!(inner instanceof MatchError)) { + throw inner; + } + inner.message = `matching array element [${i}]:\n${inner.message}`; + throw inner; + } + } + } + + 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); + + if (isObject(exp)) + return matchObjectWithAtLeast(act, exp); + + throw new Error("bad pattern: " + JSON.stringify(exp)); + } + + return { Pattern: Pattern, + MatchError: MatchError }; + +})(); + |