text: var Watcher = require('../watcher')
  var Path = require('../parsers/path')
  var textParser = require('../parsers/text')
  var dirParser = require('../parsers/directive')
  var expParser = require('../parsers/expression')
  var filterRE = /[^|]\|[^|]/
  
  /**
   * Get the value from an expression on this vm.
   *
   * @param {String} exp
   * @return {*}
   */
  
  exports.$get = function (exp) {
    var res = expParser.parse(exp)
    if (res) {
      try {
        return res.get.call(this, this)
      } catch (e) {}
    }
  }
  
  /**
   * Set the value from an expression on this vm.
   * The expression must be a valid left-hand
   * expression in an assignment.
   *
   * @param {String} exp
   * @param {*} val
   */
  
  exports.$set = function (exp, val) {
    var res = expParser.parse(exp, true)
    if (res && res.set) {
      res.set.call(this, this, val)
    }
  }
  
  /**
   * Add a property on the VM
   *
   * @param {String} key
   * @param {*} val
   */
  
  exports.$add = function (key, val) {
    this._data.$add(key, val)
  }
  
  /**
   * Delete a property on the VM
   *
   * @param {String} key
   */
  
  exports.$delete = function (key) {
    this._data.$delete(key)
  }
  
  /**
   * Watch an expression, trigger callback when its
   * value changes.
   *
   * @param {String} exp
   * @param {Function} cb
   * @param {Object} [options]
   *                 - {Boolean} deep
   *                 - {Boolean} immediate
   *                 - {Boolean} user
   * @return {Function} - unwatchFn
   */
  
  exports.$watch = function (exp, cb, options) {
    var vm = this
    var wrappedCb = function (val, oldVal) {
      cb.call(vm, val, oldVal)
    }
    var watcher = new Watcher(vm, exp, wrappedCb, {
      deep: options && options.deep,
      user: !options || options.user !== false
    })
    if (options && options.immediate) {
      wrappedCb(watcher.value)
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
  
  /**
   * Evaluate a text directive, including filters.
   *
   * @param {String} text
   * @return {String}
   */
  
  exports.$eval = function (text) {
    // check for filters.
    if (filterRE.test(text)) {
      var dir = dirParser.parse(text)[0]
      // the filter regex check might give false positive
      // for pipes inside strings, so it's possible that
      // we don't get any filters here
      var val = this.$get(dir.expression)
      return dir.filters
        ? this._applyFilters(val, null, dir.filters)
        : val
    } else {
      // no filter
      return this.$get(text)
    }
  }
  
  /**
   * Interpolate a piece of template text.
   *
   * @param {String} text
   * @return {String}
   */
  
  exports.$interpolate = function (text) {
    var tokens = textParser.parse(text)
    var vm = this
    if (tokens) {
      return tokens.length === 1
        ? vm.$eval(tokens[0].value)
        : tokens.map(function (token) {
            return token.tag
              ? vm.$eval(token.value)
              : token.value
          }).join('')
    } else {
      return text
    }
  }
  
  /**
   * Log instance data as a plain JS object
   * so that it is easier to inspect in console.
   * This method assumes console is available.
   *
   * @param {String} [path]
   */
  
  exports.$log = function (path) {
    var data = path
      ? Path.get(this._data, path)
      : this._data
    if (data) {
      data = JSON.parse(JSON.stringify(data))
    }
    console.log(data)
  }
  