Match.sys.mjs (4748B)
1 // A little pattern-matching library. 2 // 3 // Ported from js/src/tests/js1_8_5/reflect-parse/Match.js for use with devtools 4 // server xpcshell tests. 5 6 export const Match = (function () { 7 class Pattern { 8 constructor(template) { 9 this.template = template; 10 } 11 12 match(act) { 13 return match(act, this.template); 14 } 15 16 matches(act) { 17 try { 18 return this.match(act); 19 } catch (e) { 20 if (e instanceof MatchError) { 21 return false; 22 } 23 } 24 return false; 25 } 26 27 assert(act, message) { 28 try { 29 return this.match(act); 30 } catch (e) { 31 if (e instanceof MatchError) { 32 throw new Error((message || "failed match") + ": " + e.message); 33 } 34 } 35 return false; 36 } 37 38 toString() { 39 return "[object Pattern]"; 40 } 41 } 42 43 Pattern.ANY = new Pattern(); 44 Pattern.ANY.template = Pattern.ANY; 45 46 Pattern.NUMBER = new Pattern(); 47 Pattern.NUMBER.match = function (act) { 48 if (typeof act !== "number") { 49 throw new MatchError("Expected number, got: " + quote(act)); 50 } 51 }; 52 53 Pattern.NATURAL = new Pattern(); 54 Pattern.NATURAL.match = function (act) { 55 if (typeof act !== "number" || act !== Math.floor(act) || act < 0) { 56 throw new MatchError("Expected natural number, got: " + quote(act)); 57 } 58 }; 59 60 const quote = uneval; 61 62 class MatchError { 63 constructor(msg) { 64 this.message = msg; 65 } 66 toString() { 67 return "match error: " + this.message; 68 } 69 } 70 71 function isAtom(x) { 72 return ( 73 typeof x === "number" || 74 typeof x === "string" || 75 typeof x === "boolean" || 76 x === null || 77 (typeof x === "object" && x instanceof RegExp) 78 ); 79 } 80 81 function isObject(x) { 82 return x !== null && typeof x === "object"; 83 } 84 85 function isFunction(x) { 86 return typeof x === "function"; 87 } 88 89 function isArrayLike(x) { 90 return isObject(x) && "length" in x; 91 } 92 93 function matchAtom(act, exp) { 94 if (typeof exp === "number" && isNaN(exp)) { 95 if (typeof act !== "number" || !isNaN(act)) { 96 throw new MatchError("expected NaN, got: " + quote(act)); 97 } 98 return true; 99 } 100 101 if (exp === null) { 102 if (act !== null) { 103 throw new MatchError("expected null, got: " + quote(act)); 104 } 105 return true; 106 } 107 108 if (exp instanceof RegExp) { 109 if (!(act instanceof RegExp) || exp.source !== act.source) { 110 throw new MatchError("expected " + quote(exp) + ", got: " + quote(act)); 111 } 112 return true; 113 } 114 115 switch (typeof exp) { 116 case "string": 117 if (act !== exp) { 118 throw new MatchError( 119 "expected " + quote(exp) + ", got " + quote(act) 120 ); 121 } 122 return true; 123 case "boolean": 124 case "number": 125 if (exp !== act) { 126 throw new MatchError("expected " + exp + ", got " + quote(act)); 127 } 128 return true; 129 } 130 131 throw new Error("bad pattern: " + exp.toSource()); 132 } 133 134 function matchObject(act, exp) { 135 if (!isObject(act)) { 136 throw new MatchError("expected object, got " + quote(act)); 137 } 138 139 for (const key in exp) { 140 if (!(key in act)) { 141 throw new MatchError( 142 "expected property " + quote(key) + " not found in " + quote(act) 143 ); 144 } 145 match(act[key], exp[key]); 146 } 147 148 return true; 149 } 150 151 function matchFunction(act, exp) { 152 if (!isFunction(act)) { 153 throw new MatchError("expected function, got " + quote(act)); 154 } 155 156 if (act !== exp) { 157 throw new MatchError( 158 "expected function: " + exp + "\nbut got different function: " + act 159 ); 160 } 161 } 162 163 function matchArray(act, exp) { 164 if (!isObject(act) || !("length" in act)) { 165 throw new MatchError("expected array-like object, got " + quote(act)); 166 } 167 168 const length = exp.length; 169 if (act.length !== exp.length) { 170 throw new MatchError( 171 "expected array-like object of length " + length + ", got " + quote(act) 172 ); 173 } 174 175 for (let i = 0; i < length; i++) { 176 if (i in exp) { 177 if (!(i in act)) { 178 throw new MatchError( 179 "expected array property " + i + " not found in " + quote(act) 180 ); 181 } 182 match(act[i], exp[i]); 183 } 184 } 185 186 return true; 187 } 188 189 function match(act, exp) { 190 if (exp === Pattern.ANY) { 191 return true; 192 } 193 194 if (exp instanceof Pattern) { 195 return exp.match(act); 196 } 197 198 if (isAtom(exp)) { 199 return matchAtom(act, exp); 200 } 201 202 if (isArrayLike(exp)) { 203 return matchArray(act, exp); 204 } 205 206 if (isFunction(exp)) { 207 return matchFunction(act, exp); 208 } 209 210 return matchObject(act, exp); 211 } 212 213 return { Pattern, MatchError }; 214 })();