218 lines
4.8 KiB
JavaScript
218 lines
4.8 KiB
JavaScript
// A little pattern-matching library.
|
|
//
|
|
// Ported from js/src/tests/js1_8_5/reflect-parse/Match.js for use with devtools
|
|
// server xpcshell tests.
|
|
|
|
export const 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(act) {
|
|
return match(act, this.template);
|
|
},
|
|
|
|
matches(act) {
|
|
try {
|
|
return this.match(act);
|
|
} catch (e) {
|
|
if (e instanceof MatchError) {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
assert(act, message) {
|
|
try {
|
|
return this.match(act);
|
|
} catch (e) {
|
|
if (e instanceof MatchError) {
|
|
throw new Error((message || "failed match") + ": " + e.message);
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
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));
|
|
}
|
|
};
|
|
|
|
const quote = uneval;
|
|
|
|
function MatchError(msg) {
|
|
this.message = msg;
|
|
}
|
|
|
|
MatchError.prototype = {
|
|
toString() {
|
|
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 (const 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));
|
|
}
|
|
|
|
const length = exp.length;
|
|
if (act.length !== exp.length) {
|
|
throw new MatchError(
|
|
"expected array-like object of length " + length + ", got " + quote(act)
|
|
);
|
|
}
|
|
|
|
for (let 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, MatchError };
|
|
})();
|