text: 'use strict';
  
  var OPERATORS = {
      'null':function(self){return null;},
      'true':function(self){return true;},
      'false':function(self){return false;},
      $undefined:noop,
      '+':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
      '-':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
      '*':function(self, a,b){return a(self)*b(self);},
      '/':function(self, a,b){return a(self)/b(self);},
      '%':function(self, a,b){return a(self)%b(self);},
      '^':function(self, a,b){return a(self)^b(self);},
      '=':noop,
      '==':function(self, a,b){return a(self)==b(self);},
      '!=':function(self, a,b){return a(self)!=b(self);},
      '<':function(self, a,b){return a(self)<b(self);},
      '>':function(self, a,b){return a(self)>b(self);},
      '<=':function(self, a,b){return a(self)<=b(self);},
      '>=':function(self, a,b){return a(self)>=b(self);},
      '&&':function(self, a,b){return a(self)&&b(self);},
      '||':function(self, a,b){return a(self)||b(self);},
      '&':function(self, a,b){return a(self)&b(self);},
  //    '|':function(self, a,b){return a|b;},
      '|':function(self, a,b){return b(self)(self, a(self));},
      '!':function(self, a){return !a(self);}
  };
  var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
  
  function lex(text){
    var tokens = [],
        token,
        index = 0,
        json = [],
        ch,
        lastCh = ':'; // can start regexp
  
    while (index < text.length) {
      ch = text.charAt(index);
      if (is('"\'')) {
        readString(ch);
      } else if (isNumber(ch) || is('.') && isNumber(peek())) {
        readNumber();
      } else if (isIdent(ch)) {
        readIdent();
        // identifiers can only be if the preceding char was a { or ,
        if (was('{,') && json[0]=='{' &&
           (token=tokens[tokens.length-1])) {
          token.json = token.text.indexOf('.') == -1;
        }
      } else if (is('(){}[].,;:')) {
        tokens.push({
          index:index,
          text:ch,
          json:(was(':[,') && is('{[')) || is('}]:,')
        });
        if (is('{[')) json.unshift(ch);
        if (is('}]')) json.shift();
        index++;
      } else if (isWhitespace(ch)) {
        index++;
        continue;
      } else {
        var ch2 = ch + peek(),
            fn = OPERATORS[ch],
            fn2 = OPERATORS[ch2];
        if (fn2) {
          tokens.push({index:index, text:ch2, fn:fn2});
          index += 2;
        } else if (fn) {
          tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
          index += 1;
        } else {
          throwError("Unexpected next character ", index, index+1);
        }
      }
      lastCh = ch;
    }
    return tokens;
  
    function is(chars) {
      return chars.indexOf(ch) != -1;
    }
  
    function was(chars) {
      return chars.indexOf(lastCh) != -1;
    }
  
    function peek() {
      return index + 1 < text.length ? text.charAt(index + 1) : false;
    }
    function isNumber(ch) {
      return '0' <= ch && ch <= '9';
    }
    function isWhitespace(ch) {
      return ch == ' ' || ch == '\r' || ch == '\t' ||
             ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
    }
    function isIdent(ch) {
      return 'a' <= ch && ch <= 'z' ||
             'A' <= ch && ch <= 'Z' ||
             '_' == ch || ch == '$';
    }
    function isExpOperator(ch) {
      return ch == '-' || ch == '+' || isNumber(ch);
    }
  
    function throwError(error, start, end) {
      end = end || index;
      throw Error("Lexer Error: " + error + " at column" +
          (isDefined(start)
              ? "s " + start +  "-" + index + " [" + text.substring(start, end) + "]"
              : " " + end) +
          " in expression [" + text + "].");
    }
  
    function readNumber() {
      var number = "";
      var start = index;
      while (index < text.length) {
        var ch = lowercase(text.charAt(index));
        if (ch == '.' || isNumber(ch)) {
          number += ch;
        } else {
          var peekCh = peek();
          if (ch == 'e' && isExpOperator(peekCh)) {
            number += ch;
          } else if (isExpOperator(ch) &&
              peekCh && isNumber(peekCh) &&
              number.charAt(number.length - 1) == 'e') {
            number += ch;
          } else if (isExpOperator(ch) &&
              (!peekCh || !isNumber(peekCh)) &&
              number.charAt(number.length - 1) == 'e') {
            throwError('Invalid exponent');
          } else {
            break;
          }
        }
        index++;
      }
      number = 1 * number;
      tokens.push({index:start, text:number, json:true,
        fn:function() {return number;}});
    }
    function readIdent() {
      var ident = "";
      var start = index;
      var fn;
      while (index < text.length) {
        var ch = text.charAt(index);
        if (ch == '.' || isIdent(ch) || isNumber(ch)) {
          ident += ch;
        } else {
          break;
        }
        index++;
      }
      fn = OPERATORS[ident];
      tokens.push({
        index:start,
        text:ident,
        json: fn,
        fn:fn||extend(getterFn(ident), {
          assign:function(self, value){
            return setter(self, ident, value);
          }
        })
      });
    }
  
    function readString(quote) {
      var start = index;
      index++;
      var string = "";
      var rawString = quote;
      var escape = false;
      while (index < text.length) {
        var ch = text.charAt(index);
        rawString += ch;
        if (escape) {
          if (ch == 'u') {
            var hex = text.substring(index + 1, index + 5);
            if (!hex.match(/[\da-f]{4}/i))
              throwError( "Invalid unicode escape [\\u" + hex + "]");
            index += 4;
            string += String.fromCharCode(parseInt(hex, 16));
          } else {
            var rep = ESCAPE[ch];
            if (rep) {
              string += rep;
            } else {
              string += ch;
            }
          }
          escape = false;
        } else if (ch == '\\') {
          escape = true;
        } else if (ch == quote) {
          index++;
          tokens.push({
            index:start,
            text:rawString,
            string:string,
            json:true,
            fn:function() { return string; }
          });
          return;
        } else {
          string += ch;
        }
        index++;
      }
      throwError("Unterminated quote", start);
    }
  }
  
  /////////////////////////////////////////
  
  function parser(text, json, $filter){
    var ZERO = valueFn(0),
        value,
        tokens = lex(text),
        assignment = _assignment,
        assignable = logicalOR,
        functionCall = _functionCall,
        fieldAccess = _fieldAccess,
        objectIndex = _objectIndex,
        filterChain = _filterChain,
        functionIdent = _functionIdent;
    if(json){
      // The extra level of aliasing is here, just in case the lexer misses something, so that
      // we prevent any accidental execution in JSON.
      assignment = logicalOR;
      functionCall =
        fieldAccess =
        objectIndex =
        assignable =
        filterChain =
        functionIdent =
          function() { throwError("is not valid json", {text:text, index:0}); };
      value = primary();
    } else {
      value = statements();
    }
    if (tokens.length !== 0) {
      throwError("is an unexpected token", tokens[0]);
    }
    return value;
  
    ///////////////////////////////////
    function throwError(msg, token) {
      throw Error("Syntax Error: Token '" + token.text +
        "' " + msg + " at column " +
        (token.index + 1) + " of the expression [" +
        text + "] starting at [" + text.substring(token.index) + "].");
    }
  
    function peekToken() {
      if (tokens.length === 0)
        throw Error("Unexpected end of expression: " + text);
      return tokens[0];
    }
  
    function peek(e1, e2, e3, e4) {
      if (tokens.length > 0) {
        var token = tokens[0];
        var t = token.text;
        if (t==e1 || t==e2 || t==e3 || t==e4 ||
            (!e1 && !e2 && !e3 && !e4)) {
          return token;
        }
      }
      return false;
    }
  
    function expect(e1, e2, e3, e4){
      var token = peek(e1, e2, e3, e4);
      if (token) {
        if (json && !token.json) {
          throwError("is not valid json", token);
        }
        tokens.shift();
        return token;
      }
      return false;
    }
  
    function consume(e1){
      if (!expect(e1)) {
        throwError("is unexpected, expecting [" + e1 + "]", peek());
      }
    }
  
    function unaryFn(fn, right) {
      return function(self) {
        return fn(self, right);
      };
    }
  
    function binaryFn(left, fn, right) {
      return function(self) {
        return fn(self, left, right);
      };
    }
  
    function hasTokens () {
      return tokens.length > 0;
    }
  
    function statements() {
      var statements = [];
      while(true) {
        if (tokens.length > 0 && !peek('}', ')', ';', ']'))
          statements.push(filterChain());
        if (!expect(';')) {
          // optimize for the common case where there is only one statement.
          // TODO(size): maybe we should not support multiple statements?
          return statements.length == 1
            ? statements[0]
            : function(self){
              var value;
              for ( var i = 0; i < statements.length; i++) {
                var statement = statements[i];
                if (statement)
                  value = statement(self);
              }
              return value;
            };
        }
      }
    }
  
    function _filterChain() {
      var left = expression();
      var token;
      while(true) {
        if ((token = expect('|'))) {
          left = binaryFn(left, token.fn, filter());
        } else {
          return left;
        }
      }
    }
  
    function filter() {
      var token = expect();
      var fn = $filter(token.text);
      var argsFn = [];
      while(true) {
        if ((token = expect(':'))) {
          argsFn.push(expression());
        } else {
          var fnInvoke = function(self, input){
            var args = [input];
            for ( var i = 0; i < argsFn.length; i++) {
              args.push(argsFn[i](self));
            }
            return fn.apply(self, args);
          };
          return function() {
            return fnInvoke;
          };
        }
      }
    }
  
    function expression() {
      return assignment();
    }
  
    function _assignment() {
      var left = logicalOR();
      var right;
      var token;
      if ((token = expect('='))) {
        if (!left.assign) {
          throwError("implies assignment but [" +
            text.substring(0, token.index) + "] can not be assigned to", token);
        }
        right = logicalOR();
        return function(self){
          return left.assign(self, right(self));
        };
      } else {
        return left;
      }
    }
  
    function logicalOR() {
      var left = logicalAND();
      var token;
      while(true) {
        if ((token = expect('||'))) {
          left = binaryFn(left, token.fn, logicalAND());
        } else {
          return left;
        }
      }
    }
  
    function logicalAND() {
      var left = equality();
      var token;
      if ((token = expect('&&'))) {
        left = binaryFn(left, token.fn, logicalAND());
      }
      return left;
    }
  
    function equality() {
      var left = relational();
      var token;
      if ((token = expect('==','!='))) {
        left = binaryFn(left, token.fn, equality());
      }
      return left;
    }
  
    function relational() {
      var left = additive();
      var token;
      if ((token = expect('<', '>', '<=', '>='))) {
        left = binaryFn(left, token.fn, relational());
      }
      return left;
    }
  
    function additive() {
      var left = multiplicative();
      var token;
      while ((token = expect('+','-'))) {
        left = binaryFn(left, token.fn, multiplicative());
      }
      return left;
    }
  
    function multiplicative() {
      var left = unary();
      var token;
      while ((token = expect('*','/','%'))) {
        left = binaryFn(left, token.fn, unary());
      }
      return left;
    }
  
    function unary() {
      var token;
      if (expect('+')) {
        return primary();
      } else if ((token = expect('-'))) {
        return binaryFn(ZERO, token.fn, unary());
      } else if ((token = expect('!'))) {
        return unaryFn(token.fn, unary());
      } else {
        return primary();
      }
    }
  
    function _functionIdent(fnScope) {
      var token = expect();
      var element = token.text.split('.');
      var instance = fnScope;
      var key;
      for ( var i = 0; i < element.length; i++) {
        key = element[i];
        if (instance)
          instance = instance[key];
      }
      if (!isFunction(instance)) {
        throwError("should be a function", token);
      }
      return instance;
    }
  
    function primary() {
      var primary;
      if (expect('(')) {
        var expression = filterChain();
        consume(')');
        primary = expression;
      } else if (expect('[')) {
        primary = arrayDeclaration();
      } else if (expect('{')) {
        primary = object();
      } else {
        var token = expect();
        primary = token.fn;
        if (!primary) {
          throwError("not a primary expression", token);
        }
      }
      var next;
      while ((next = expect('(', '[', '.'))) {
        if (next.text === '(') {
          primary = functionCall(primary);
        } else if (next.text === '[') {
          primary = objectIndex(primary);
        } else if (next.text === '.') {
          primary = fieldAccess(primary);
        } else {
          throwError("IMPOSSIBLE");
        }
      }
      return primary;
    }
  
    function _fieldAccess(object) {
      var field = expect().text;
      var getter = getterFn(field);
      return extend(function(self){
        return getter(object(self));
      }, {
        assign:function(self, value){
          return setter(object(self), field, value);
        }
      });
    }
  
    function _objectIndex(obj) {
      var indexFn = expression();
      consume(']');
      return extend(
        function(self){
          var o = obj(self),
              i = indexFn(self),
              v, p;
  
          if (!o) return undefined;
          v = o[i];
          if (v && v.then) {
            p = v;
            if (!('$$v' in v)) {
              p.$$v = undefined;
              p.then(function(val) { p.$$v = val; });
            }
            v = v.$$v;
          }
          return v;
        }, {
          assign:function(self, value){
            return obj(self)[indexFn(self)] = value;
          }
        });
    }
  
    function _functionCall(fn) {
      var argsFn = [];
      if (peekToken().text != ')') {
        do {
          argsFn.push(expression());
        } while (expect(','));
      }
      consume(')');
      return function(self){
        var args = [];
        for ( var i = 0; i < argsFn.length; i++) {
          args.push(argsFn[i](self));
        }
        var fnPtr = fn(self) || noop;
        // IE stupidity!
        return fnPtr.apply
            ? fnPtr.apply(self, args)
            : fnPtr(args[0], args[1], args[2], args[3], args[4]);
      };
    }
  
    // This is used with json array declaration
    function arrayDeclaration () {
      var elementFns = [];
      if (peekToken().text != ']') {
        do {
          elementFns.push(expression());
        } while (expect(','));
      }
      consume(']');
      return function(self){
        var array = [];
        for ( var i = 0; i < elementFns.length; i++) {
          array.push(elementFns[i](self));
        }
        return array;
      };
    }
  
    function object () {
      var keyValues = [];
      if (peekToken().text != '}') {
        do {
          var token = expect(),
          key = token.string || token.text;
          consume(":");
          var value = expression();
          keyValues.push({key:key, value:value});
        } while (expect(','));
      }
      consume('}');
      return function(self){
        var object = {};
        for ( var i = 0; i < keyValues.length; i++) {
          var keyValue = keyValues[i];
          var value = keyValue.value(self);
          object[keyValue.key] = value;
        }
        return object;
      };
    }
  
    function watchDecl () {
      var anchorName = expect().text;
      consume(":");
      var expressionFn;
      if (peekToken().text == '{') {
        consume("{");
        expressionFn = statements();
        consume("}");
      } else {
        expressionFn = expression();
      }
      return function(self) {
        return {name:anchorName, fn:expressionFn};
      };
    }
  }
  
  //////////////////////////////////////////////////
  // Parser helper functions
  //////////////////////////////////////////////////
  
  function setter(obj, path, setValue) {
    var element = path.split('.');
    for (var i = 0; element.length > 1; i++) {
      var key = element.shift();
      var propertyObj = obj[key];
      if (!propertyObj) {
        propertyObj = {};
        obj[key] = propertyObj;
      }
      obj = propertyObj;
    }
    obj[element.shift()] = setValue;
    return setValue;
  }
  
  /**
   * Return the value accesible from the object by path. Any undefined traversals are ignored
   * @param {Object} obj starting object
   * @param {string} path path to traverse
   * @param {boolean=true} bindFnToScope
   * @returns value as accesbile by path
   */
  //TODO(misko): this function needs to be removed
  function getter(obj, path, bindFnToScope) {
    if (!path) return obj;
    var keys = path.split('.');
    var key;
    var lastInstance = obj;
    var len = keys.length;
  
    for (var i = 0; i < len; i++) {
      key = keys[i];
      if (obj) {
        obj = (lastInstance = obj)[key];
      }
    }
    if (!bindFnToScope && isFunction(obj)) {
      return bind(lastInstance, obj);
    }
    return obj;
  }
  
  var getterFnCache = {},
      JS_KEYWORDS = {};
  
  forEach(
      ("abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default," +
      "delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto," +
      "if,implements,import,ininstanceof,intinterface,long,native,new,null,package,private," +
      "protected,public,return,short,static,super,switch,synchronized,this,throw,throws," +
      "transient,true,try,typeof,var,volatile,void,undefined,while,with").split(/,/),
    function(key){ JS_KEYWORDS[key] = true;}
  );
  
  function getterFn(path) {
    var fn = getterFnCache[path];
    if (fn) return fn;
  
    var code = 'var l, fn, p;\n';
    forEach(path.split('.'), function(key) {
      key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
      code += 'if(!s) return s;\n' +
              'l=s;\n' +
              's=s' + key + ';\n' +
              'if(typeof s=="function" && !(s instanceof RegExp)) {\n' +
                ' fn=function(){ return l' + key + '.apply(l, arguments); };\n' +
                ' fn.$unboundFn=s;\n' +
                ' s=fn;\n' +
              '} else if (s && s.then) {\n' +
                ' if (!("$$v" in s)) {\n' +
                  ' p=s;\n' +
                  ' p.$$v = undefined;\n' +
                  ' p.then(function(v) {p.$$v=v;});\n' +
                  '}\n' +
                ' s=s.$$v\n' +
              '}\n';
    });
    code += 'return s;';
    fn = Function('s', code);
    fn.toString = function() { return code; };
  
    return getterFnCache[path] = fn;
  }
  
  ///////////////////////////////////
  
  function $ParseProvider() {
    var cache = {};
    this.$get = ['$filter', function($filter) {
      return function(exp) {
        switch(typeof exp) {
          case 'string':
            return cache.hasOwnProperty(exp)
              ? cache[exp]
              : cache[exp] =  parser(exp, false, $filter);
          case 'function':
            return exp;
          default:
            return noop;
        }
      };
    }];
  }
  
  
  // This is a special access for JSON parser which bypasses the injector
  var parseJson = function(json) {
    return parser(json, true);
  };
  