lib/angular/js/ng/parse.js
function $ParseProvider() {
  var cache = {};

  var $parseOptions = {
    csp: false
  };


  this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
    $parseOptions.csp = $sniffer.csp;

    return function(exp, interceptorFn) {
      var parsedExpression, oneTime,
          cacheKey = (exp = trim(exp));

      switch (typeof exp) {
        case 'string':
          if (cache.hasOwnProperty(cacheKey)) {
            parsedExpression = cache[cacheKey];
          } else {
            if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
              oneTime = true;
              exp = exp.substring(2);
            }

            var lexer = new Lexer($parseOptions);
            var parser = new Parser(lexer, $filter, $parseOptions);
            parsedExpression = parser.parse(exp);

            if (parsedExpression.constant) parsedExpression.$$watchDelegate = constantWatch;
            else if (oneTime) parsedExpression.$$watchDelegate = oneTimeWatch;

            if (cacheKey !== 'hasOwnProperty') {
              // Only cache the value if it's not going to mess up the cache object
              // This is more performant that using Object.prototype.hasOwnProperty.call
              cache[cacheKey] = parsedExpression;
            }
          }
          return addInterceptor(parsedExpression, interceptorFn);

        case 'function':
          return addInterceptor(exp, interceptorFn);

        default:
          return addInterceptor(noop, interceptorFn);
      }
    };

    function oneTimeWatch(scope, listener, objectEquality, deregisterNotifier, parsedExpression) {
      var unwatch, lastValue;
      return unwatch = scope.$watch(function oneTimeWatch(scope) {
        return parsedExpression(scope);
      }, function oneTimeListener(value, old, scope) {
        lastValue = value;
        if (isFunction(listener)) {
          listener.apply(this, arguments);
        }
        if (isDefined(value)) {
          scope.$$postDigest(function () {
            if (isDefined(lastValue)) {
              unwatch();
            }
          });
        }
      }, objectEquality, deregisterNotifier);
    }

    function constantWatch(scope, listener, objectEquality, deregisterNotifier, parsedExpression) {
      var unwatch;
      return unwatch = scope.$watch(function constantWatch(scope) {
        return parsedExpression(scope);
      }, function constantListener(value, old, scope) {
        if (isFunction(listener)) {
          listener.apply(this, arguments);
        }
        unwatch();
      }, objectEquality, deregisterNotifier);
    }

    function addInterceptor(parsedExpression, interceptorFn) {
      if (isFunction(interceptorFn)) {
        var fn = function interceptedExpression(scope, locals) {
          var value = parsedExpression(scope, locals);
          var result = interceptorFn(value, scope, locals);
          // we only return the interceptor's result if the
          // initial value is defined (for bind-once)
          return isDefined(value) ? result : value;
        };
        fn.$$watchDelegate = parsedExpression.$$watchDelegate;
        return fn;
      } else {
        return parsedExpression;
      }
    }
  }];
}
function $ParseProvider() {
  var cache = {};

  var $parseOptions = {
    csp: false
  };


  this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
    $parseOptions.csp = $sniffer.csp;

    return function(exp, interceptorFn) {
      var parsedExpression, oneTime,
          cacheKey = (exp = trim(exp));

      switch (typeof exp) {
        case 'string':
          if (cache.hasOwnProperty(cacheKey)) {
            parsedExpression = cache[cacheKey];
          } else {
            if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
              oneTime = true;
              exp = exp.substring(2);
            }

            var lexer = new Lexer($parseOptions);
            var parser = new Parser(lexer, $filter, $parseOptions);
            parsedExpression = parser.parse(exp);

            if (parsedExpression.constant) parsedExpression.$$watchDelegate = constantWatch;
            else if (oneTime) parsedExpression.$$watchDelegate = oneTimeWatch;

            if (cacheKey !== 'hasOwnProperty') {
              // Only cache the value if it's not going to mess up the cache object
              // This is more performant that using Object.prototype.hasOwnProperty.call
              cache[cacheKey] = parsedExpression;
            }
          }
          return addInterceptor(parsedExpression, interceptorFn);

        case 'function':
          return addInterceptor(exp, interceptorFn);

        default:
          return addInterceptor(noop, interceptorFn);
      }
    };

    function oneTimeWatch(scope, listener, objectEquality, deregisterNotifier, parsedExpression) {
      var unwatch, lastValue;
      return unwatch = scope.$watch(function oneTimeWatch(scope) {
        return parsedExpression(scope);
      }, function oneTimeListener(value, old, scope) {
        lastValue = value;
        if (isFunction(listener)) {
          listener.apply(this, arguments);
        }
        if (isDefined(value)) {
          scope.$$postDigest(function () {
            if (isDefined(lastValue)) {
              unwatch();
            }
          });
        }
      }, objectEquality, deregisterNotifier);
    }

    function constantWatch(scope, listener, objectEquality, deregisterNotifier, parsedExpression) {
      var unwatch;
      return unwatch = scope.$watch(function constantWatch(scope) {
        return parsedExpression(scope);
      }, function constantListener(value, old, scope) {
        if (isFunction(listener)) {
          listener.apply(this, arguments);
        }
        unwatch();
      }, objectEquality, deregisterNotifier);
    }

    function addInterceptor(parsedExpression, interceptorFn) {
      if (isFunction(interceptorFn)) {
        var fn = function interceptedExpression(scope, locals) {
          var value = parsedExpression(scope, locals);
          var result = interceptorFn(value, scope, locals);
          // we only return the interceptor's result if the
          // initial value is defined (for bind-once)
          return isDefined(value) ? result : value;
        };
        fn.$$watchDelegate = parsedExpression.$$watchDelegate;
        return fn;
      } else {
        return parsedExpression;
      }
    }
  }];
}
@ngdoc service
@name $parse
@kind function

@description

Converts Angular {@link guide/expression expression} into a function.

```js
var getter = $parse('user.name');
var setter = getter.assign;
var context = {user:{name:'angular'}};
var locals = {user:{name:'local'}};

expect(getter(context)).toEqual('angular');
setter(context, 'newValue');
expect(context.user.name).toEqual('newValue');
expect(getter(context, locals)).toEqual('local');
```


@param {string} expression String expression to compile.
@returns {function(context, locals)} a function which represents the compiled expression:

* `context` – `{object}` – an object against which any expressions embedded in the strings
are evaluated against (typically a scope object).
* `locals` – `{object=}` – local variables context object, useful for overriding values in
`context`.

The returned function also has the following properties:
* `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
literal.
* `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
constant literals.
* `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
set to a function to change its value on the given context.

 
@ngdoc provider
@name $parseProvider
@kind function

@description
`$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse}
service.