xaviervia/object-pattern

View on GitHub
object-pattern.js

Summary

Maintainability
D
2 days
Test Coverage
/* jshint asi: true, node: true, supernew: true */
/* global define, window */
// object-pattern
// ==============
//
// [![Codeship status](https://codeship.com/projects/168ec210-8ee6-0132-51af-0a0cf4fe8e66/status?branch=master)](https://codeship.com/projects/61105) [![Code Climate](https://codeclimate.com/github/xaviervia/object-pattern/badges/gpa.svg)](https://codeclimate.com/github/xaviervia/object-pattern) [![Test Coverage](https://codeclimate.com/github/xaviervia/object-pattern/badges/coverage.svg)](https://codeclimate.com/github/xaviervia/object-pattern/coverage)
//
// Object Pattern structures for JavaScript.
//
// > You can try them out on the [Object Pattern playground](http://xaviervia.github.io/object-pattern/)
//
// `object-pattern` provides a fast and complete analog of Regular Expressions
// but aimed to describe generic object structures instead of strings.
//
// What was in my mind while creating this? Simple: creating EventEmitters
// that support listening to object structures as the event descriptions.
//
// A prime example of when this would be useful are REST interfaces.
// `object-pattern` could be used as a drop-in replacement of regular
// endpoints in a [Express.js](http://expressjs.com/) or
// [Sinatra](http://www.sinatrarb.com/) like routing framework.
//
// Installation
// ------------
//
// From NPM:
//
// ```
// npm install --save object-pattern
// ```
//
// From Bower:
//
// ```
// bower install --save object-pattern
// ```
//
// `object-pattern` supports CommonJS, AMD and globals, so feel free to require
// it as best fits you. There is also a minified version in `object-pattern.min.js`.
//
// Usage
// -----
//
// The most straightforward way of using `object-pattern` is with the notation
// language (Object Pattern Notation):
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse("property:'value'");
//
// pattern.match({ property: "value" }); // => true!
// ```
//
// Exactly like Regular Expressions, Object Patterns match if the target object
// _contains_ at the very least the structure described. If you do:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse("property:'value'");
//
// pattern.match({
//   property: "value",
//   something: ["extra", { and: "complex" } ]
// }); // => true!
// ```
//
// ...it will still return `true` since the property `property` with value
// `"value"` exists in the object.
//
// You can also use wildcards for the value:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse("property:*");
//
// pattern.match({ property: 50 }); // => true
// pattern.match({ property: "else" }); // => true
// pattern.match({ property: false }); // => true
// pattern.match({ property: { structure: true } }); // => still true!
// ```
//
// ...and also wildcards for the property name:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse("*:value");
//
// pattern.match({ anything: "value" }); // => true
// ```
//
// You can make sure that a property with some value doesn't exist:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('!property:value');
//
// pattern.match({ property: "value" }); // => false
// ```
//
// You can also make sure that the value is of a
// [certain JSON type](https://github.com/xaviervia/object-pattern/issues/2#issuecomment-87404454):
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('property:<number>');
//
// pattern.match({ property: 20 }); // => true
// pattern.match({ property: -7.20 }); // => true
// pattern.match({ property: "-7.20" }); // => false
// ```
//
// There are also array patterns, both standalone:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('/user/<number>');
//
// pattern.match(["user", 50]); // => true
// ```
//
// ...and within an object structure:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('method:GET,path:/articles/*');
//
// pattern.match({
//   method: "GET",
//   path: ["articles", "rest-is-awesome"]
// }); // =>  true
// ```
//
// There are also ellipsis, to match any amount of items in an array.
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('/**/saturn');
//
// pattern.match(["planets", "saturn"]); // => true
// pattern.match(["planets", 6, "many-moons", "saturn"]); // => true
// pattern.match(["saturn"]); // => true
// ```
//
// Ellipsis matchers are non-greedy.
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('/**/saturn/**/moons');
//
// pattern.match(["saturn", 62, "moons"]); // => true
// pattern.match(["saturn", 62, "moons", "saturn", 62, "moons"]); // => false
// ```
//
// Object and array structures can be nested:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('userAgent:(os:"linux",browser:"firefox")');
//
// pattern.match({
//   userAgent: {
//     os: "linux",
//     browser: "firefox",
//     device: "tablet"
//   }
// }); // => true
// ```
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('/[/users/*]/[/articles/*]');
//
// pattern.match([
//   ["users", 60],
//   ["articles", 70]
// ]); // => true
// ```
//
// Once you have the pattern structure, you can stringify it again into OPN:
//
// ```javascript
// var OP = require("object-pattern");
//
// var pattern = OP.parse('some:structure,withTypes:<number>');
//
// pattern.toString(); // => "some:'structure',withTypes:<number>"
// ```
//
// Keep reading for a complete reference of the `object-pattern` types (the
// structures into which the OPN string compiles while parsed) and how to
// interact with them low-level.
//
// ### Advanced usage note
//
// Most of the `object-pattern` structure looks for descendants of `Matchable`
// or `ArrayMatchable` to delegate matching operations. You can create your
// own object matching structures by inheriting from those functions and
// assembling the pattern structure yourself. The only drawback is that it's
// not going to be available to be parsed from the OPN, but that could be OK
// in many cases (and you can always pull request if you think your
// extension should be standard).
//
// Pattern Structures
// ------------------
//
// All of this structures are accessible from the main object. To create an
// `ObjectPattern` structure from scratch, you coud do, for example:
//
// ```javascript
// var OP = require("object-pattern");
//
// new OP.ObjectPattern(
//   new OP.ExactProperty("property", "value")
// );
// ```
//
"use strict";

(function (name, definition) {

  //! AMD
  if (typeof define === 'function')
    define(definition)

  //! CommonJS
  else if (typeof module !== 'undefined' && module.exports)
    module.exports = definition()

  //! Global
  else {
    var theModule = definition(), global = window, old = global[name];

    theModule.noConflict = function () {
      global[name] = old
      return theModule
    }

    global[name] = theModule
  }

})("OP", function () {

  // ### Matchable
  //
  // A common parent for all matchables. The interface that they are supposed to
  // implement (although `Matchable` itself does not) is to expose a `match`
  // method that returns either `true` or `false`.
  //
  var Matchable = function () {}

  var toString = function (value) {
    if (value instanceof ObjectPattern)
      return "(" + value.toString() + ")"

    if (value instanceof Matchable) return value.toString()

    if (value instanceof ArrayMatchable) return value.toString()

    if ( ! isNaN(value)) return value.toString()

    return "'" + value + "'"
  }

  var toJSON = function (value) {
    if (value instanceof ObjectPattern ||
        value instanceof ArrayPattern)
      return value.toJSON()

    if (value instanceof WildcardValue)
      return '*'

    if (value instanceof TypedValue)
      return '<' + value.type + '>'

    return value
  }

  // ### WildcardProperty
  //
  // Returns `true` if the value of any property is `===` to the assigned value.
  // `false` otherwise. If initialized with an inheritor of `Matchable` it will
  // forward the `match` to the matchable instead.
  //
  // Usage:
  //
  // ```javascript
  // // Static value property
  // var wildcardProperty = new WildcardProperty("public"):
  // wildcardProperty.match({"project": "public"}); // => true
  //
  // // Matchable
  // var matchable    = new Matchable();
  // matchable.match  = function () { return true } ;
  // wildcardProperty = new WildcardProperty(matchable);
  // wildcardProperty.match({"property": "value"}); // => true
  // ```
  //
  var WildcardProperty = function (value) {
    this.value = value
  }


  WildcardProperty.prototype = new Matchable


  WildcardProperty.prototype.match = function (object) {
    var key;

    if (this.value instanceof Matchable) {
      for (key in object) if (this.value.match(object[key])) return true }

    else {
      for (key in object) if (object[key] === this.value) return true }

    return false
  }

  WildcardProperty.prototype.toString = function () {
    return "*:" + toString(this.value)
  }



  // ### ExactProperty
  //
  // Returns `true` if there is a property with the given name which value is
  // `===` to the assigned value. `false` otherwise.
  //
  // If initialized with an inheritor of `Matchable` it will
  // forward the `match` to the matchable, if the property exists.
  //
  // Usage:
  //
  // ```javascript
  // // Static value property
  // var exactProperty = new ExactProperty("project", "public"):
  // exactProperty.match({"project": "public"}); // => true
  //
  // // Matchable
  // var matchable    = new Matchable();
  // matchable.match  = function () { return true } ;
  // exactProperty = new ExactProperty("property", matchable);
  // exactProperty.match({"property": "value"}); // => true
  //
  // // Matchable but property missing
  // var matchable    = new Matchable();
  // matchable.match  = function () { return true };
  // exactProperty = new ExactProperty("project", matchable);
  // exactProperty.match({"property": "value"}); // => false
  // ```
  //
  var ExactProperty = function (name, value) {
    this.name   = name
    this.value  = value
  }


  ExactProperty.prototype = new Matchable


  ExactProperty.prototype.match = function (object) {
    if (this.value instanceof Matchable)
      return object[this.name] && this.value.match(object[this.name])

    return object[this.name] && object[this.name] === this.value
  }


  ExactProperty.prototype.toString = function () {
    return this.name + ":" + toString(this.value)
  }



  // ### Negator
  //
  // Delegates the matching to the sent matchable and negates the result.
  //
  // Usage:
  // ```javascript
  // var matchable = new Matchable();
  // matchable.match = function () {
  //   return true;
  // }
  //
  // var negator = new Negator(matchable);
  // negator.match({"here": "ignored"}); // => false
  // ```
  //
  var Negator = function (matchable) {
    this.matchable = matchable
  }

  Negator.prototype = new Matchable

  Negator.prototype.match = function (object) {
    return !this.matchable.match(object)
  }

  Negator.prototype.toString = function () {
    return "!" + toString(this.matchable)
  }



  // ### ObjectPattern
  //
  // Returns the `&&` result of calling the `match` method in each `properties`,
  // forwarding the argument.
  //
  // Usage:
  // ```javascript
  // var property = new ObjectPattern(
  //   new ExactProperty("public", true),
  //   new WildcardProperty("value"),
  //   new ExactProperty("timestamp", 123456789)
  // )
  //
  // property.match({
  //   "public": true,
  //   "anyProp": "value",
  //   "timestamp": 123456789
  // }) // => true
  // ```
  //
  var ObjectPattern = function () {
    this.properties = []
    for (var i = 0, j = arguments.length; i < j; i ++)
      this.properties.push(arguments[i])
  }

  ObjectPattern.prototype = new Matchable


  ObjectPattern.prototype.match = function (object) {
    for (var i = 0, j = this.properties.length; i < j; i ++)
      if (!this.properties[i].match(object)) return false

    return true
  }


  ObjectPattern.prototype.toString = function () {
    return this.properties.map(function (property) {
      return property.toString()
    }).join(",")
  }


  ObjectPattern.prototype.toJSON = function () {
    var json = {}

    this.properties.forEach(function (property) {
      if (property instanceof ExactProperty)
        return (json[property.name] = toJSON(property.value))

      if (property instanceof WildcardProperty)
        return (json['*'] = toJSON(property.value))

      if (property instanceof Negator)
        if (property.matchable instanceof ExactProperty)
          return (json['!' + property.matchable.name] =
            toJSON(property.matchable.value))

        else if (property.matchable instanceof WildcardProperty)
          return (json['!*'] =
            toJSON(property.matchable.value))
    })

    return json
  }



  // ### WildcardValue
  //
  // Returns always `true` except if the argument is `undefined`.
  //
  // Usage:
  // ```javascript
  // var wildcardValue = new WildcardValue();
  // wildcardValue.match("something"); // => true
  // ```
  //
  var WildcardValue = function () {}

  WildcardValue.prototype = new Matchable

  WildcardValue.prototype.match = function (object) {
    return object !== undefined
  }

  WildcardValue.prototype.toString = function () {
    return "*"
  }



  // ### TypedValue
  //
  // If initialized with a `Function`, returns `true` only if the argument if
  // `instanceof` the `Function`.
  //
  // If initialized with the following `String` arguments, it returns `true`:
  //
  // - **number**: any value that serialized to JSON would be casted into a
  //   `number` literal.
  // - **string**: any value that serialized to JSON would be casted into a
  //   `string` literal.
  // - **array**: any value that serialized to JSON would be casted into an
  //   `array` literal.
  // - **object**: any value that serialized to JSON would be casted into an
  //   `object` literal.
  // - **boolean**: any value that serialized to JSON would be casted into
  //   either `true` or `false`
  //
  // Usage:
  //
  // ```javascript
  // var Type = function () {};
  // var typedValue = new TypedValue(Type);
  //
  // typedValue.match(new Type()) // => true
  // ```
  //
  var TypedValue = function (type) {
    this.type = type
  }


  TypedValue.prototype = new Matchable


  TypedValue.prototype.match = function (object) {
    switch (this.type) {
      case 'array':
        return JSON.stringify(object).substring(0, 1) === '['

      case 'boolean':
        return object === true || object === false

      case 'number':
        return JSON.stringify(object) === '' + object

      case 'object':
        return (JSON.stringify(object) || '').substring(0, 1) === '{'

      case 'string':
        return JSON.stringify(object) === '"' + object + '"'

      default:
        return object instanceof this.type
    }
  }


  TypedValue.prototype.toString = function () {
    return "<" + this.type + ">"
  }



  // ### ArrayPattern
  //
  // Handles `ArrayMatchable`s, combining their results to return a final
  // `Boolean` value representing whether the `Array` was or not a match.
  //
  // Usage:
  //
  // ```javascript
  // var arrayMatcher = new ArrayPattern(
  //   new TypedValue( 'number' ),
  //   'user',
  //   new WildcardValue(),
  //   new ArrayEllipsis( 9 )
  // );
  //
  // arrayMatcher.match([6, 'user', 9]); // => false
  // arrayMatcher.match([-56.2, 'user', 'extra', 9]); // => true
  // ```
  //
  var ArrayPattern = function () {
    this.matchables = []

    for (var i = 0; i < arguments.length; i ++)
      this.matchables.push(arguments[i])
  }

  ArrayPattern.prototype = new Matchable

  ArrayPattern.prototype.match = function (array) {
    if (!(array instanceof Array))
      return false

    if (this.matchables.length === 0 && array.length > 0)
      return false

    else if (this.matchables.length === 0 && array.length === 0)
      return true

    var filteredArray = array
    var result = {}
    var i = 0

    for (; i < this.matchables.length; i ++) {
      if (this.matchables[i] instanceof ArrayMatchable) {
        result = this.matchables[i].match(filteredArray)

        if (result.matched === false)
          return false

        filteredArray = result.unmatched
      }

      else {
        if (filteredArray.length === 0)
          return false

        if (this.matchables[i] instanceof Matchable) {
          if (! this.matchables[i].match(filteredArray[0]) )
            return false
        }

        else if (filteredArray[0] !== this.matchables[i])
          return false

        result.matched = true
        filteredArray  = filteredArray.slice(1)
      }
    }

    return result.matched && filteredArray.length === 0
  }


  ArrayPattern.prototype.toString = function () {
    return "/" + this.matchables.map(function (matchable) {
      return toString(matchable)
    }).join("/")
  }


  ArrayPattern.prototype.toJSON = function () {
    var json = []

    this.matchables.forEach(function (matchable) {
      if (matchable instanceof ArrayEllipsis) {
        json.push(toJSON('**'))
        json.push(toJSON(matchable.termination))
      }

      else json.push(toJSON(matchable))
    })

    return json
  }



  // ### ArrayMatchable
  //
  // A common parent for all descriptors of `Array` components. `ArrayMatchable`s
  // have a slightly different interface than regular `Matchable`s because they
  // need to send back the chunk of the Array that wasn't consumed by the current
  // pattern so that the `ArrayPattern` can forward it to the next
  // `ArrayMatchable`.
  //
  var ArrayMatchable = function () {}



  // ### ArrayEllipsis
  //
  // The `ArrayEllipsis` represents a variable length pattern, and it's behavior
  // depends on how it is configured.
  //
  // 1. Passing no arguments to the `ArrayEllipsis` will create a _catch all_
  //    pattern that will match anything, even no elements at all.
  // 2. Passing any `Matchable` to the `ArrayEllipsis` will cause it to
  //    sequentially probe each element for a match with the `Matchable`. That
  //    `Matchable` is called the _termination_ of the ellipsis pattern. If a
  //    match happens, the `ArrayEllipsis` will stop, return `true` in `matched`
  //    and the remainings of the `Array` in `unmatched`.
  //
  // Usage:
  //
  // ```javascript
  // var arrayEllipsis = new ArrayEllipsis();
  //
  // var result = arrayEllipsis.match(['element', 2, {}]);
  // result.matched; // => true
  // result.unmatched; // => []
  // ```
  //
  // With termination:
  //
  // ```javascript
  // var arrayEllipsis = new ArrayEllipsis(new TypedValue('string'));
  //
  // var result = arrayEllipsis.match([2, 4, 'text', 'extra']);
  // result.matched; // => true
  // result.unmatched; // => ['extra']
  // ```
  //
  var ArrayEllipsis = function (termination) {
    this.termination = termination
  }


  ArrayEllipsis.prototype = new ArrayMatchable


  ArrayEllipsis.prototype.match = function (array) {
    if ( ! this.termination)
      return {
        matched: true,
        unmatched: [] }

    for (var index = 0; index < array.length; index ++) {
      if (this.termination instanceof Matchable) {
        if (this.termination.match(array[index]))
          return {
            matched: true,
            unmatched: array.slice(index + 1) }
      }

      else {
        if (this.termination === array[index])
          return {
            matched: true,
            unmatched: array.slice(index + 1)}
      }
    }

    return {
      matched: false,
      unmatched: []
    }
  }


  ArrayEllipsis.prototype.toString = function () {
    return "**/" + toString(this.termination)
  }


  // ### parse
  //
  // Parses an [OPN (Object Pattern Notation)](https://github.com/xaviervia/sydney/wiki/Object-Pattern-Notation)
  // string and returns the corresponding pattern structure.
  //
  // Usage:
  //
  // ```javascript
  // var parse = require("object-pattern").parse
  //
  // var pattern = parse("name:*,age:<number>")
  // pattern.match({
  //   name: "Alex",
  //   age: 24
  // }) // => true
  // ```
  //
  // For more examples please refer to the [OPN examples](OPN.js)
  var parse = function (source) {
    if (source instanceof Object) return parseObject(source)

    if (source === "") return undefined

    if (source.substring(0, 1) === "/")
      return parse.array(source)

    if (source === "*")
      return new WildcardValue

    if (source.substring(0, 1) === "[" &&
        source.substring(source.length - 1, source.length) === "]")
      return parse.array(source.substring(1, source.length - 1))

    if (source.substring(0, 1) === "<" &&
        source.substring(source.length - 1, source.length) === ">")
      return new TypedValue(source.substring(1, source.length - 1))

    if (source.substring(0, 1) === "(" &&
        source.substring(source.length - 1, source.length) === ")")
      return parse.object(source.substring(1, source.length - 1))

    if (( source.substring(0, 1) === '"' &&
          source.substring(source.length - 1, source.length) === '"') ||
        ( source.substring(0, 1) === "'" &&
          source.substring(source.length - 1, source.length) === "'"))
      return source
        .substring(1, source.length - 1)
        .split("\\\\")
        .map(function (chunk) { return chunk.replace("\\", "") })
        .join("\\")

    if (source === "true") return true

    if (source === "false") return false

    if (!isNaN(source)) return parseFloat(source)

    if (source.indexOf(":") > -1) return parse.object(source)

    return source
  }


  parse.array = function (source) {
    var pattern = new ArrayPattern
    var nested = parse.nestedChecker(["/"])
    var buffer = ""
    var list = []

    source.split("").forEach(function (character, index) {
      if (nested(character) && buffer !== "**") {
        list.push(buffer)
        buffer = ""
      }

      else
        buffer += character
    })

    list.push(buffer)

    pattern.matchables = list
      .filter(function (source) {
        return source !== ""
      })
      .map(function (source, index, list) {
        if (source.substring(0, 3) === "**/")
          return new ArrayEllipsis(parse(source.substring(3)))

        else if (source === "**")
          return new ArrayEllipsis

        return parse(source)
      })

    return pattern
  }


  parse.nestedChecker = function (separators) {
    var stack = []
    var brackets = [["(", ")"], ["[", "]"]]
    var quotes = ["'", '"']
    separators = separators || []

    return function (character) {
      brackets.forEach(function (pair) {
        if (character === pair[0])
          stack.push(pair[0])

        if (character === pair[1] &&
            stack[stack.length - 1] === pair[0])
          stack.pop()
      })

      quotes.forEach(function (quote) {
        if (character === quote) {
          if (stack[stack.length - 1] === quote)
            stack.pop()

          else
            stack.push(quote)
        }
      })

      if (stack.length > 0) return false

      return separators
        .filter(function (separator) { return character === separator })
        .length > 0
    }
  }


  parse.object = function (source) {
    var buffer = ""
    var nested = parse.nestedChecker([","])
    var pattern = new ObjectPattern
    var escaped = false

    source.split("").forEach(function (character, index) {
      if (nested(character) && !escaped) {
        pattern.properties.push(parse.property(buffer))
        buffer = ""
      }

      else
        buffer += character

      escaped = false

      if (character === "\\") escaped = true
    })

    pattern.properties.push(parse.property(buffer))

    return pattern
  }


  parse.property = function (source) {
    var propertyName  = source.substring(0, source.indexOf(":"))
    var propertyValue = parse(source.substring(source.indexOf(":") + 1))

    if (propertyName === "*")
      return new WildcardProperty(propertyValue)

    else if (propertyName.substring(0, 1) === "!")
      return new Negator( propertyName.substring(1) === "*" ?
        new WildcardProperty(propertyValue) :
        new ExactProperty(
          propertyName.substring(1),
          propertyValue ) )

    else
      return new ExactProperty(propertyName, propertyValue)
  }



  // ### parseObject
  //
  // Parses an endpoint constructed as a JavaScript object
  // and returns the corresponding pattern structure.
  //
  // Usage:
  //
  // ```javascript
  // var parseObject = require("object-pattern").parseObject
  //
  // var pattern = parse({ name: "*", age: "<number>"})
  // pattern.match({
  //   name: "Alex",
  //   age: 24
  // }) // => true
  // ```
  //
  var parseObject = function (object) {
    var pattern;

    if (object instanceof Array) return parseObject.value(object)

    pattern = new ObjectPattern

    pattern.properties = Object
      .keys(object)
      .map(function (key) {
        var value = parseObject.value( object[key] )

        if (key === "*")
          return new WildcardProperty(value)

        if (key.substring(0, 1) === "!")
          if (key.substring(1) === "*")
            return new Negator(
              new WildcardProperty( value ) )
          else
            return new Negator(
              new ExactProperty( key.substring(1), value ) )

        return new ExactProperty(key, value)
      })

    return pattern
  }


  parseObject.value = function (object) {
    if (object instanceof Array)
      return parseObject.array(object)

    if (object instanceof Object)
      return parseObject(object)

    if (object === "*")
      return new WildcardValue

    if (object.substring &&
        object.substring(0, 1) === "<" &&
        object.substring(object.length - 1) === ">")
      return new TypedValue(object.substring(1, object.length - 1))

    return object
  }


  parseObject.array = function (array) {
    var pattern = new ArrayPattern
    pattern.matchables = array
      .map(function (matchable, index, list) {
        if (matchable === '**')
          if (list[index + 1])
            return new ArrayEllipsis(parseObject.value(list[index + 1]))
          else
            return new ArrayEllipsis()

        return parseObject.value(matchable)
      })

      .filter(function (matchable, index, list) {
        if (list[index - 1] && list[index - 1] instanceof ArrayEllipsis)
          return false

        return true
      })

    return pattern
  }


  return {
    Matchable: Matchable,
    WildcardProperty: WildcardProperty,
    ExactProperty: ExactProperty,
    Negator: Negator,
    ObjectPattern: ObjectPattern,
    WildcardValue: WildcardValue,
    TypedValue: TypedValue,
    ArrayPattern: ArrayPattern,
    ArrayMatchable: ArrayMatchable,
    ArrayEllipsis: ArrayEllipsis,
    parse: parse
  }
})

//
// License
// -------
//
// Copyright 2014 Xavier Via
//
// ISC license.
//
// See [LICENSE](LICENSE) attached.