text: /* @flow */
  
  import type VueRouter from './index'
  import { resolvePath } from './util/path'
  import { assert, warn } from './util/warn'
  import { createRoute } from './util/route'
  import { fillParams } from './util/params'
  import { createRouteMap } from './create-route-map'
  import { normalizeLocation } from './util/location'
  import { decode } from './util/query'
  
  export type Matcher = {
    match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
    addRoutes: (routes: Array<RouteConfig>) => void;
    addRoute: (parentNameOrRoute: string | RouteConfig, route?: RouteConfig) => void;
  };
  
  export function createMatcher (
    routes: Array<RouteConfig>,
    router: VueRouter
  ): Matcher {
    const { pathList, pathMap, nameMap } = createRouteMap(routes)
  
    function addRoutes (routes) {
      createRouteMap(routes, pathList, pathMap, nameMap)
    }
  
    function addRoute (parentOrRoute, route) {
      const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined
      // $flow-disable-line
      createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)
    }
  
    function match (
      raw: RawLocation,
      currentRoute?: Route,
      redirectedFrom?: Location
    ): Route {
      const location = normalizeLocation(raw, currentRoute, false, router)
      const { name } = location
  
      if (name) {
        const record = nameMap[name]
        if (process.env.NODE_ENV !== 'production') {
          warn(record, `Route with name '${name}' does not exist`)
        }
        if (!record) return _createRoute(null, location)
        const paramNames = record.regex.keys
          .filter(key => !key.optional)
          .map(key => key.name)
  
        if (typeof location.params !== 'object') {
          location.params = {}
        }
  
        if (currentRoute && typeof currentRoute.params === 'object') {
          for (const key in currentRoute.params) {
            if (!(key in location.params) && paramNames.indexOf(key) > -1) {
              location.params[key] = currentRoute.params[key]
            }
          }
        }
  
        location.path = fillParams(record.path, location.params, `named route "${name}"`)
        return _createRoute(record, location, redirectedFrom)
      } else if (location.path) {
        location.params = {}
        for (let i = 0; i < pathList.length; i++) {
          const path = pathList[i]
          const record = pathMap[path]
          if (matchRoute(record.regex, location.path, location.params)) {
            return _createRoute(record, location, redirectedFrom)
          }
        }
      }
      // no match
      return _createRoute(null, location)
    }
  
    function redirect (
      record: RouteRecord,
      location: Location
    ): Route {
      const originalRedirect = record.redirect
      let redirect = typeof originalRedirect === 'function'
        ? originalRedirect(createRoute(record, location, null, router))
        : originalRedirect
  
      if (typeof redirect === 'string') {
        redirect = { path: redirect }
      }
  
      if (!redirect || typeof redirect !== 'object') {
        if (process.env.NODE_ENV !== 'production') {
          warn(
            false, `invalid redirect option: ${JSON.stringify(redirect)}`
          )
        }
        return _createRoute(null, location)
      }
  
      const re: Object = redirect
      const { name, path } = re
      let { query, hash, params } = location
      query = re.hasOwnProperty('query') ? re.query : query
      hash = re.hasOwnProperty('hash') ? re.hash : hash
      params = re.hasOwnProperty('params') ? re.params : params
  
      if (name) {
        // resolved named direct
        const targetRecord = nameMap[name]
        if (process.env.NODE_ENV !== 'production') {
          assert(targetRecord, `redirect failed: named route "${name}" not found.`)
        }
        return match({
          _normalized: true,
          name,
          query,
          hash,
          params
        }, undefined, location)
      } else if (path) {
        // 1. resolve relative redirect
        const rawPath = resolveRecordPath(path, record)
        // 2. resolve params
        const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
        // 3. rematch with existing query and hash
        return match({
          _normalized: true,
          path: resolvedPath,
          query,
          hash
        }, undefined, location)
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
        }
        return _createRoute(null, location)
      }
    }
  
    function alias (
      record: RouteRecord,
      location: Location,
      matchAs: string
    ): Route {
      const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
      const aliasedMatch = match({
        _normalized: true,
        path: aliasedPath
      })
      if (aliasedMatch) {
        const matched = aliasedMatch.matched
        const aliasedRecord = matched[matched.length - 1]
        location.params = aliasedMatch.params
        return _createRoute(aliasedRecord, location)
      }
      return _createRoute(null, location)
    }
  
    function _createRoute (
      record: ?RouteRecord,
      location: Location,
      redirectedFrom?: Location
    ): Route {
      if (record && record.redirect) {
        return redirect(record, redirectedFrom || location)
      }
      if (record && record.matchAs) {
        return alias(record, location, record.matchAs)
      }
      return createRoute(record, location, redirectedFrom, router)
    }
  
    return {
      match,
      addRoute,
      addRoutes
    }
  }
  
  function matchRoute (
    regex: RouteRegExp,
    path: string,
    params: Object
  ): boolean {
    const m = path.match(regex)
  
    if (!m) {
      return false
    } else if (!params) {
      return true
    }
  
    for (let i = 1, len = m.length; i < len; ++i) {
      const key = regex.keys[i - 1]
      if (key) {
        // Fix #1994: using * with props: true generates a param named 0
        params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
      }
    }
  
    return true
  }
  
  function resolveRecordPath (path: string, record: RouteRecord): string {
    return resolvePath(path, record.parent ? record.parent.path : '/', true)
  }
  