tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Match.js (6794B)


      1 // |reftest| skip
      2 
      3 // A little pattern-matching library.
      4 var Match =
      5 
      6 (function() {
      7 
      8    function Pattern(template) {
      9        // act like a constructor even as a function
     10        if (!(this instanceof Pattern))
     11            return new Pattern(template);
     12 
     13        this.template = template;
     14    }
     15 
     16    Pattern.prototype = {
     17        match: function(act) {
     18            return match(act, this.template);
     19        },
     20 
     21        matches: function(act) {
     22            try {
     23                return this.match(act);
     24            }
     25            catch (e) {
     26                if (!(e instanceof MatchError))
     27                    throw e;
     28                return false;
     29            }
     30        },
     31 
     32        assert: function(act, message) {
     33            try {
     34                return this.match(act);
     35            }
     36            catch (e) {
     37                if (!(e instanceof MatchError))
     38                    throw e;
     39                throw new Error((message || "failed match") + ": " + e.message);
     40            }
     41        },
     42 
     43        toString: () => "[object Pattern]"
     44    };
     45 
     46    Pattern.ANY = new Pattern;
     47    Pattern.ANY.template = Pattern.ANY;
     48 
     49    Pattern.NUMBER = new Pattern;
     50    Pattern.NUMBER.match = function (act) {
     51      if (typeof act !== 'number') {
     52        throw new MatchError("Expected number, got: " + quote(act));
     53      }
     54    }
     55 
     56    Pattern.NATURAL = new Pattern
     57    Pattern.NATURAL.match = function (act) {
     58      if (typeof act !== 'number' || act !== Math.floor(act) || act < 0) {
     59        throw new MatchError("Expected natural number, got: " + quote(act));
     60      }
     61    }
     62 
     63    class ObjectWithExactly extends Pattern {
     64      constructor(template) {
     65        super(template);
     66      }
     67 
     68      match(actual) {
     69        return matchObjectWithExactly(actual, this.template)
     70      }
     71    }
     72 
     73    Pattern.OBJECT_WITH_EXACTLY = function (template) {
     74      return new ObjectWithExactly(template);
     75    }
     76 
     77    var quote = JSON.stringify;
     78 
     79    class MatchError extends Error {
     80        toString() {
     81            return "match error: " + this.message;
     82        }
     83    };
     84 
     85    Pattern.MatchError = MatchError;
     86 
     87    function isAtom(x) {
     88        return (typeof x === "number") ||
     89            (typeof x === "string") ||
     90            (typeof x === "boolean") ||
     91            (x === null) ||
     92            (x === undefined) ||
     93            (typeof x === "object" && x instanceof RegExp) ||
     94            (typeof x === "bigint");
     95    }
     96 
     97    function isObject(x) {
     98        return (x !== null) && (typeof x === "object");
     99    }
    100 
    101    function isFunction(x) {
    102        return typeof x === "function";
    103    }
    104 
    105    function isArrayLike(x) {
    106        return isObject(x) && ("length" in x);
    107    }
    108 
    109    function matchAtom(act, exp) {
    110        if ((typeof exp) === "number" && isNaN(exp)) {
    111            if ((typeof act) !== "number" || !isNaN(act))
    112                throw new MatchError("expected NaN, got: " + quote(act));
    113            return true;
    114        }
    115 
    116        if (exp === null) {
    117            if (act !== null)
    118                throw new MatchError("expected null, got: " + quote(act));
    119            return true;
    120        }
    121 
    122        if (exp instanceof RegExp) {
    123            if (!(act instanceof RegExp) || exp.source !== act.source)
    124                throw new MatchError("expected " + quote(exp) + ", got: " + quote(act));
    125            return true;
    126        }
    127 
    128        switch (typeof exp) {
    129        case "string":
    130        case "undefined":
    131            if (act !== exp)
    132                throw new MatchError("expected " + quote(exp) + ", got " + quote(act));
    133            return true;
    134        case "boolean":
    135        case "number":
    136        case "bigint":
    137            if (exp !== act)
    138                throw new MatchError("expected " + exp + ", got " + quote(act));
    139            return true;
    140        }
    141 
    142        throw new Error("bad pattern: " + JSON.stringify(exp));
    143    }
    144 
    145    // Match an object having at least the expected properties.
    146    function matchObjectWithAtLeast(act, exp) {
    147        if (!isObject(act))
    148            throw new MatchError("expected object, got " + quote(act));
    149 
    150        for (var key in exp) {
    151            if (!(key in act))
    152                throw new MatchError("expected property " + quote(key) + " not found in " + quote(act));
    153            try {
    154                match(act[key], exp[key]);
    155            } catch (inner) {
    156                if (!(inner instanceof MatchError)) {
    157                    throw inner;
    158                }
    159                inner.message = `matching property "${String(key)}":\n${inner.message}`;
    160                throw inner;
    161            }
    162        }
    163 
    164        return true;
    165    }
    166 
    167    // Match an object having all the expected properties and no more.
    168    function matchObjectWithExactly(act, exp) {
    169        matchObjectWithAtLeast(act, exp);
    170 
    171        for (var key in act) {
    172            if (!(key in exp)) {
    173                throw new MatchError("unexpected property " + quote(key));
    174            }
    175        }
    176 
    177        return true;
    178    }
    179 
    180    function matchFunction(act, exp) {
    181        if (!isFunction(act))
    182            throw new MatchError("expected function, got " + quote(act));
    183 
    184        if (act !== exp)
    185            throw new MatchError("expected function: " + exp +
    186                                 "\nbut got different function: " + act);
    187    }
    188 
    189    function matchArray(act, exp) {
    190        if (!isObject(act) || !("length" in act))
    191            throw new MatchError("expected array-like object, got " + quote(act));
    192 
    193        var length = exp.length;
    194        if (act.length !== exp.length)
    195            throw new MatchError("expected array-like object of length " + length + ", got " + quote(act));
    196 
    197        for (var i = 0; i < length; i++) {
    198            if (i in exp) {
    199                if (!(i in act))
    200                    throw new MatchError("expected array property " + i + " not found in " + quote(act));
    201                try {
    202                    match(act[i], exp[i]);
    203                } catch (inner) {
    204                    if (!(inner instanceof MatchError)) {
    205                        throw inner;
    206                    }
    207                    inner.message = `matching array element [${i}]:\n${inner.message}`;
    208                    throw inner;
    209                }
    210            }
    211        }
    212 
    213        return true;
    214    }
    215 
    216    function match(act, exp) {
    217        if (exp === Pattern.ANY)
    218            return true;
    219 
    220        if (exp instanceof Pattern)
    221            return exp.match(act);
    222 
    223        if (isAtom(exp))
    224            return matchAtom(act, exp);
    225 
    226        if (isArrayLike(exp))
    227            return matchArray(act, exp);
    228 
    229        if (isFunction(exp))
    230            return matchFunction(act, exp);
    231 
    232        if (isObject(exp))
    233            return matchObjectWithAtLeast(act, exp);
    234 
    235        throw new Error("bad pattern: " + JSON.stringify(exp));
    236    }
    237 
    238    return { Pattern: Pattern,
    239             MatchError: MatchError };
    240 
    241 })();