angular.js/src/ng/parse.js

906 lines
24 KiB
JavaScript
Raw Normal View History

'use strict';
var OPERATORS = {
'null':function(){return null;},
'true':function(){return true;},
'false':function(){return false;},
undefined:noop,
'+':function(self, locals, a,b){
a=a(self, locals); b=b(self, locals);
if (isDefined(a)) {
if (isDefined(b)) {
return a + b;
}
return a;
}
return isDefined(b)?b:undefined;},
'-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
'*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
'/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
'=':noop,
'===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
'!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
'!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
'<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
'>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
'<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
'>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
'&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
'||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
'&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
// '|':function(self, locals, a,b){return a|b;},
'|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
'!':function(self, locals, a){return !a(self, locals);}
2010-01-06 00:36:58 +00:00
};
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
2010-01-06 00:36:58 +00:00
function lex(text, csp){
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();
2010-12-11 18:07:10 +00:00
// 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('(){}[].,;:')) {
2010-12-11 18:07:10 +00:00
tokens.push({
index:index,
text:ch,
2010-12-11 18:07:10 +00:00
json:(was(':[,') && is('{[')) || is('}]:,')
});
if (is('{[')) json.unshift(ch);
if (is('}]')) json.shift();
index++;
} else if (isWhitespace(ch)) {
index++;
continue;
2010-01-12 01:32:33 +00:00
} else {
var ch2 = ch + peek(),
ch3 = ch2 + peek(2),
fn = OPERATORS[ch],
fn2 = OPERATORS[ch2],
fn3 = OPERATORS[ch3];
if (fn3) {
tokens.push({index:index, text:ch3, fn:fn3});
index += 3;
} else if (fn2) {
tokens.push({index:index, text:ch2, fn:fn2});
index += 2;
} else if (fn) {
2010-10-20 14:22:15 +00:00
tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
index += 1;
2010-01-12 01:32:33 +00:00
} else {
throwError("Unexpected next character ", index, index+1);
2010-01-06 00:36:58 +00:00
}
2010-01-12 01:32:33 +00:00
}
lastCh = ch;
}
return tokens;
2010-03-24 17:35:01 +00:00
function is(chars) {
return chars.indexOf(ch) != -1;
}
function was(chars) {
return chars.indexOf(lastCh) != -1;
}
function peek(i) {
var num = i || 1;
return index + num < text.length ? text.charAt(index + num) : false;
}
function isNumber(ch) {
2010-01-12 01:32:33 +00:00
return '0' <= ch && ch <= '9';
}
function isWhitespace(ch) {
2010-01-12 01:32:33 +00:00
return ch == ' ' || ch == '\r' || ch == '\t' ||
2010-10-21 06:17:59 +00:00
ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
}
function isIdent(ch) {
2010-01-12 01:32:33 +00:00
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 = "",
start = index,
lastDot, peekIndex, methodName;
while (index < text.length) {
var ch = text.charAt(index);
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
if (ch == '.') lastDot = index;
ident += ch;
} else {
break;
2010-01-12 01:32:33 +00:00
}
index++;
}
//check if this is not a method invocation and if it is back out to last dot
if (lastDot) {
peekIndex = index;
while(peekIndex < text.length) {
var ch = text.charAt(peekIndex);
if (ch == '(') {
methodName = ident.substr(lastDot - start + 1);
ident = ident.substr(0, lastDot - start);
index = peekIndex;
break;
}
if(isWhitespace(ch)) {
peekIndex++;
} else {
break;
}
}
}
var token = {
index:start,
text:ident
};
if (OPERATORS.hasOwnProperty(ident)) {
token.fn = token.json = OPERATORS[ident];
} else {
var getter = getterFn(ident, csp);
token.fn = extend(function(self, locals) {
return (getter(self, locals));
}, {
assign: function(self, value) {
return setter(self, ident, value);
}
});
}
tokens.push(token);
if (methodName) {
tokens.push({
index:lastDot,
text: '.',
json: false
});
tokens.push({
index: lastDot + 1,
text: methodName,
json: false
});
}
}
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);
}
}
2010-01-06 00:36:58 +00:00
2010-01-12 01:32:33 +00:00
/////////////////////////////////////////
2010-01-06 00:36:58 +00:00
function parser(text, json, $filter, csp){
var ZERO = valueFn(0),
value,
tokens = lex(text, csp),
assignment = _assignment,
functionCall = _functionCall,
fieldAccess = _fieldAccess,
objectIndex = _objectIndex,
filterChain = _filterChain;
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 =
filterChain =
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]);
}
value.literal = !!value.literal;
value.constant = !!value.constant;
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) + "].");
}
2010-03-24 17:35:01 +00:00
function peekToken() {
if (tokens.length === 0)
throw Error("Unexpected end of expression: " + text);
return tokens[0];
}
2010-03-24 17:35:01 +00:00
function peek(e1, e2, e3, e4) {
2010-01-12 01:32:33 +00:00
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;
}
2010-03-24 17:35:01 +00:00
function expect(e1, e2, e3, e4){
var token = peek(e1, e2, e3, e4);
2010-01-12 01:32:33 +00:00
if (token) {
if (json && !token.json) {
throwError("is not valid json", token);
}
tokens.shift();
2010-01-06 00:36:58 +00:00
return token;
}
2010-01-12 01:32:33 +00:00
return false;
}
2010-03-24 17:35:01 +00:00
function consume(e1){
if (!expect(e1)) {
throwError("is unexpected, expecting [" + e1 + "]", peek());
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function unaryFn(fn, right) {
return extend(function(self, locals) {
return fn(self, locals, right);
}, {
constant:right.constant
});
}
2010-03-24 17:35:01 +00:00
function binaryFn(left, fn, right) {
return extend(function(self, locals) {
return fn(self, locals, left, right);
}, {
constant:left.constant && right.constant
});
}
2010-03-24 17:35:01 +00:00
function statements() {
2010-01-12 01:32:33 +00:00
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, locals){
var value;
for ( var i = 0; i < statements.length; i++) {
var statement = statements[i];
if (statement)
value = statement(self, locals);
}
return value;
};
2010-01-12 01:32:33 +00:00
}
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function _filterChain() {
var left = expression();
2010-01-12 01:32:33 +00:00
var token;
while(true) {
if ((token = expect('|'))) {
left = binaryFn(left, token.fn, filter());
2010-01-12 01:32:33 +00:00
} else {
return left;
}
}
}
2010-03-24 17:35:01 +00:00
function filter() {
var token = expect();
var fn = $filter(token.text);
2010-01-12 01:32:33 +00:00
var argsFn = [];
while(true) {
if ((token = expect(':'))) {
argsFn.push(expression());
2010-01-12 01:32:33 +00:00
} else {
var fnInvoke = function(self, locals, input){
2010-01-12 01:32:33 +00:00
var args = [input];
for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self, locals));
2010-01-12 01:32:33 +00:00
}
return fn.apply(self, args);
2010-01-12 01:32:33 +00:00
};
return function() {
2010-01-12 01:32:33 +00:00
return fnInvoke;
};
}
}
}
2010-03-24 17:35:01 +00:00
function expression() {
return assignment();
}
2010-03-24 17:35:01 +00:00
function _assignment() {
var left = logicalOR();
var right;
2010-01-12 01:32:33 +00:00
var token;
if ((token = expect('='))) {
if (!left.assign) {
throwError("implies assignment but [" +
text.substring(0, token.index) + "] can not be assigned to", token);
2010-01-12 01:32:33 +00:00
}
right = logicalOR();
return function(self, locals){
return left.assign(self, right(self, locals), locals);
};
2010-01-06 00:36:58 +00:00
} else {
return left;
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function logicalOR() {
var left = logicalAND();
2010-01-12 01:32:33 +00:00
var token;
while(true) {
if ((token = expect('||'))) {
left = binaryFn(left, token.fn, logicalAND());
2010-01-12 01:32:33 +00:00
} else {
return left;
}
}
}
2010-03-24 17:35:01 +00:00
function logicalAND() {
var left = equality();
2010-01-12 01:32:33 +00:00
var token;
if ((token = expect('&&'))) {
left = binaryFn(left, token.fn, logicalAND());
2010-01-06 00:36:58 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function equality() {
var left = relational();
2010-01-12 01:32:33 +00:00
var token;
if ((token = expect('==','!=','===','!=='))) {
left = binaryFn(left, token.fn, equality());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function relational() {
var left = additive();
2010-01-12 01:32:33 +00:00
var token;
if ((token = expect('<', '>', '<=', '>='))) {
left = binaryFn(left, token.fn, relational());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function additive() {
var left = multiplicative();
2010-01-12 01:32:33 +00:00
var token;
while ((token = expect('+','-'))) {
left = binaryFn(left, token.fn, multiplicative());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function multiplicative() {
var left = unary();
2010-01-12 01:32:33 +00:00
var token;
while ((token = expect('*','/','%'))) {
left = binaryFn(left, token.fn, unary());
2010-01-12 01:32:33 +00:00
}
return left;
}
2010-03-24 17:35:01 +00:00
function unary() {
2010-01-12 01:32:33 +00:00
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());
2010-01-06 00:36:58 +00:00
} else {
return primary();
2010-01-06 00:36:58 +00:00
}
}
2010-03-24 17:35:01 +00:00
function primary() {
2010-01-12 01:32:33 +00:00
var primary;
if (expect('(')) {
primary = filterChain();
consume(')');
} else if (expect('[')) {
primary = arrayDeclaration();
} else if (expect('{')) {
primary = object();
2010-01-06 00:36:58 +00:00
} else {
var token = expect();
2010-01-12 01:32:33 +00:00
primary = token.fn;
if (!primary) {
throwError("not a primary expression", token);
2010-01-12 01:32:33 +00:00
}
if (token.json) {
primary.constant = primary.literal = true;
}
2010-01-06 00:36:58 +00:00
}
var next, context;
while ((next = expect('(', '[', '.'))) {
2010-01-12 01:32:33 +00:00
if (next.text === '(') {
primary = functionCall(primary, context);
context = null;
2010-01-12 01:32:33 +00:00
} else if (next.text === '[') {
context = primary;
primary = objectIndex(primary);
2010-01-12 01:32:33 +00:00
} else if (next.text === '.') {
context = primary;
primary = fieldAccess(primary);
2010-01-12 01:32:33 +00:00
} else {
throwError("IMPOSSIBLE");
2010-01-12 01:32:33 +00:00
}
}
return primary;
}
2010-03-24 17:35:01 +00:00
function _fieldAccess(object) {
var field = expect().text;
var getter = getterFn(field, csp);
return extend(
function(self, locals) {
return getter(object(self, locals), locals);
},
{
assign:function(self, value, locals) {
return setter(object(self, locals), field, value);
}
}
);
}
2010-03-24 17:35:01 +00:00
function _objectIndex(obj) {
var indexFn = expression();
consume(']');
return extend(
function(self, locals){
var o = obj(self, locals),
i = indexFn(self, locals),
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, locals){
return obj(self, locals)[indexFn(self, locals)] = value;
}
});
}
2010-03-24 17:35:01 +00:00
function _functionCall(fn, contextGetter) {
2010-01-12 01:32:33 +00:00
var argsFn = [];
if (peekToken().text != ')') {
2010-01-12 01:32:33 +00:00
do {
argsFn.push(expression());
} while (expect(','));
2010-01-12 01:32:33 +00:00
}
consume(')');
return function(self, locals){
var args = [],
context = contextGetter ? contextGetter(self, locals) : self;
2010-01-12 01:32:33 +00:00
for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self, locals));
2010-01-12 01:32:33 +00:00
}
var fnPtr = fn(self, locals) || noop;
// IE stupidity!
return fnPtr.apply
? fnPtr.apply(context, args)
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
2010-01-06 00:36:58 +00:00
};
}
2010-03-24 17:35:01 +00:00
2010-01-12 01:32:33 +00:00
// This is used with json array declaration
function arrayDeclaration () {
2010-01-12 01:32:33 +00:00
var elementFns = [];
var allConstant = true;
if (peekToken().text != ']') {
2010-01-12 01:32:33 +00:00
do {
var elementFn = expression();
elementFns.push(elementFn);
if (!elementFn.constant) {
allConstant = false;
}
} while (expect(','));
2010-01-12 01:32:33 +00:00
}
consume(']');
return extend(function(self, locals){
2010-01-12 01:32:33 +00:00
var array = [];
for ( var i = 0; i < elementFns.length; i++) {
array.push(elementFns[i](self, locals));
2010-01-12 01:32:33 +00:00
}
return array;
}, {
literal:true,
constant:allConstant
});
}
2010-03-24 17:35:01 +00:00
function object () {
2010-01-12 01:32:33 +00:00
var keyValues = [];
var allConstant = true;
if (peekToken().text != '}') {
2010-01-12 01:32:33 +00:00
do {
var token = expect(),
key = token.string || token.text;
consume(":");
var value = expression();
2010-01-12 01:32:33 +00:00
keyValues.push({key:key, value:value});
if (!value.constant) {
allConstant = false;
}
} while (expect(','));
2010-01-12 01:32:33 +00:00
}
consume('}');
return extend(function(self, locals){
2010-01-12 01:32:33 +00:00
var object = {};
for ( var i = 0; i < keyValues.length; i++) {
var keyValue = keyValues[i];
var value = keyValue.value(self, locals);
2010-01-12 01:32:33 +00:00
object[keyValue.key] = value;
}
return object;
}, {
literal:true,
constant:allConstant
});
}
}
//////////////////////////////////////////////////
// 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;
}
2012-01-24 10:42:20 +00:00
var getterFnCache = {};
/**
* Implementation of the "Black Hole" variant from:
* - http://jsperf.com/angularjs-parse-getter/4
* - http://jsperf.com/path-evaluation-simplified/7
*/
function cspSafeGetterFn(key0, key1, key2, key3, key4) {
return function(scope, locals) {
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
promise;
if (pathVal === null || pathVal === undefined) return pathVal;
pathVal = pathVal[key0];
if (pathVal && pathVal.then) {
if (!("$$v" in pathVal)) {
promise = pathVal;
promise.$$v = undefined;
promise.then(function(val) { promise.$$v = val; });
}
pathVal = pathVal.$$v;
}
if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
pathVal = pathVal[key1];
if (pathVal && pathVal.then) {
if (!("$$v" in pathVal)) {
promise = pathVal;
promise.$$v = undefined;
promise.then(function(val) { promise.$$v = val; });
}
pathVal = pathVal.$$v;
}
if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
pathVal = pathVal[key2];
if (pathVal && pathVal.then) {
if (!("$$v" in pathVal)) {
promise = pathVal;
promise.$$v = undefined;
promise.then(function(val) { promise.$$v = val; });
}
pathVal = pathVal.$$v;
}
if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
pathVal = pathVal[key3];
if (pathVal && pathVal.then) {
if (!("$$v" in pathVal)) {
promise = pathVal;
promise.$$v = undefined;
promise.then(function(val) { promise.$$v = val; });
}
pathVal = pathVal.$$v;
}
if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
pathVal = pathVal[key4];
if (pathVal && pathVal.then) {
if (!("$$v" in pathVal)) {
promise = pathVal;
promise.$$v = undefined;
promise.then(function(val) { promise.$$v = val; });
}
pathVal = pathVal.$$v;
}
return pathVal;
};
};
function getterFn(path, csp) {
if (getterFnCache.hasOwnProperty(path)) {
return getterFnCache[path];
}
var pathKeys = path.split('.'),
pathKeysLength = pathKeys.length,
fn;
if (csp) {
fn = (pathKeysLength < 6)
? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
: function(scope, locals) {
var i = 0, val
do {
val = cspSafeGetterFn(
pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
)(scope, locals);
locals = undefined; // clear after first iteration
scope = val;
} while (i < pathKeysLength);
return val;
}
} else {
var code = 'var l, fn, p;\n';
forEach(pathKeys, function(key, index) {
code += 'if(s === null || s === undefined) return s;\n' +
'l=s;\n' +
's='+ (index
// we simply dereference 's' on any .dot notation
? 's'
// but if we are first then we check locals first, and if so read it first
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
'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', 'k', code); // s=scope, k=locals
fn.toString = function() { return code; };
}
return getterFnCache[path] = fn;
}
///////////////////////////////////
2012-02-28 22:29:58 +00:00
/**
* @ngdoc function
* @name ng.$parse
2012-02-28 22:29:58 +00:00
* @function
*
* @description
*
* Converts Angular {@link guide/expression expression} into a function.
2012-02-28 22:29:58 +00:00
*
* <pre>
* 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');
* </pre>
*
*
* @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 (tipically a scope object).
* * `locals` `{object=}` local variables context object, useful for overriding values in
* `context`.
2012-02-28 22:29:58 +00:00
*
* 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.
2012-02-28 22:29:58 +00:00
*
*/
function $ParseProvider() {
var cache = {};
this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
return function(exp) {
switch(typeof exp) {
case 'string':
return cache.hasOwnProperty(exp)
? cache[exp]
: cache[exp] = parser(exp, false, $filter, $sniffer.csp);
case 'function':
return exp;
default:
return noop;
}
};
}];
}