2011-07-17 08:05:43 +00:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
2010-10-08 23:25:12 +00:00
|
|
|
|
var OPERATORS = {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
'null':function(){return null;},
|
|
|
|
|
|
'true':function(){return true;},
|
|
|
|
|
|
'false':function(){return false;},
|
2012-01-24 10:40:25 +00:00
|
|
|
|
undefined:noop,
|
2012-05-25 20:58:34 +00:00
|
|
|
|
'+':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;},
|
2012-02-07 05:56:05 +00:00
|
|
|
|
'-':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);},
|
2010-12-07 19:39:59 +00:00
|
|
|
|
'=':noop,
|
2012-11-27 16:00:46 +00:00
|
|
|
|
'===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
|
|
|
|
|
|
'!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
|
2012-02-07 05:56:05 +00:00
|
|
|
|
'==':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
|
|
|
|
};
|
2010-10-08 23:25:12 +00:00
|
|
|
|
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
2010-01-06 00:36:58 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
function lex(text, csp){
|
2011-11-03 17:28:28 +00:00
|
|
|
|
var tokens = [],
|
2010-10-15 22:28:58 +00:00
|
|
|
|
token,
|
2010-08-19 00:50:21 +00:00
|
|
|
|
index = 0,
|
2010-10-15 22:28:58 +00:00
|
|
|
|
json = [],
|
|
|
|
|
|
ch,
|
2010-12-22 01:38:04 +00:00
|
|
|
|
lastCh = ':'; // can start regexp
|
2010-08-19 00:50:21 +00:00
|
|
|
|
|
|
|
|
|
|
while (index < text.length) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
ch = text.charAt(index);
|
|
|
|
|
|
if (is('"\'')) {
|
2010-08-19 00:50:21 +00:00
|
|
|
|
readString(ch);
|
2010-10-15 22:28:58 +00:00
|
|
|
|
} else if (isNumber(ch) || is('.') && isNumber(peek())) {
|
2010-10-09 00:11:45 +00:00
|
|
|
|
readNumber();
|
2010-08-19 00:50:21 +00:00
|
|
|
|
} else if (isIdent(ch)) {
|
|
|
|
|
|
readIdent();
|
2010-12-11 18:07:10 +00:00
|
|
|
|
// identifiers can only be if the preceding char was a { or ,
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (was('{,') && json[0]=='{' &&
|
|
|
|
|
|
(token=tokens[tokens.length-1])) {
|
|
|
|
|
|
token.json = token.text.indexOf('.') == -1;
|
|
|
|
|
|
}
|
2013-04-23 00:42:34 +00:00
|
|
|
|
} else if (is('(){}[].,;:?')) {
|
2010-12-11 18:07:10 +00:00
|
|
|
|
tokens.push({
|
2011-01-19 23:42:11 +00:00
|
|
|
|
index:index,
|
|
|
|
|
|
text:ch,
|
2010-12-11 18:07:10 +00:00
|
|
|
|
json:(was(':[,') && is('{[')) || is('}]:,')
|
|
|
|
|
|
});
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (is('{[')) json.unshift(ch);
|
|
|
|
|
|
if (is('}]')) json.shift();
|
|
|
|
|
|
index++;
|
2010-08-19 00:50:21 +00:00
|
|
|
|
} else if (isWhitespace(ch)) {
|
|
|
|
|
|
index++;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
continue;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
2010-08-19 00:50:21 +00:00
|
|
|
|
var ch2 = ch + peek(),
|
2012-11-27 16:00:46 +00:00
|
|
|
|
ch3 = ch2 + peek(2),
|
2010-08-19 00:50:21 +00:00
|
|
|
|
fn = OPERATORS[ch],
|
2012-11-27 16:00:46 +00:00
|
|
|
|
fn2 = OPERATORS[ch2],
|
|
|
|
|
|
fn3 = OPERATORS[ch3];
|
|
|
|
|
|
if (fn3) {
|
|
|
|
|
|
tokens.push({index:index, text:ch3, fn:fn3});
|
|
|
|
|
|
index += 3;
|
|
|
|
|
|
} else if (fn2) {
|
2010-08-19 00:50:21 +00:00
|
|
|
|
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('+-')});
|
2010-08-19 00:50:21 +00:00
|
|
|
|
index += 1;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
2010-11-16 21:57:41 +00:00
|
|
|
|
throwError("Unexpected next character ", index, index+1);
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
lastCh = ch;
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
return tokens;
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function is(chars) {
|
|
|
|
|
|
return chars.indexOf(ch) != -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function was(chars) {
|
|
|
|
|
|
return chars.indexOf(lastCh) != -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-11-27 16:00:46 +00:00
|
|
|
|
function peek(i) {
|
|
|
|
|
|
var num = i || 1;
|
|
|
|
|
|
return index + num < text.length ? text.charAt(index + num) : false;
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
function isNumber(ch) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
return '0' <= ch && ch <= '9';
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
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
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
function isIdent(ch) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
return 'a' <= ch && ch <= 'z' ||
|
|
|
|
|
|
'A' <= ch && ch <= 'Z' ||
|
|
|
|
|
|
'_' == ch || ch == '$';
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
2010-12-22 01:38:04 +00:00
|
|
|
|
function isExpOperator(ch) {
|
|
|
|
|
|
return ch == '-' || ch == '+' || isNumber(ch);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2010-11-16 21:57:41 +00:00
|
|
|
|
function throwError(error, start, end) {
|
|
|
|
|
|
end = end || index;
|
2013-05-24 18:00:14 +00:00
|
|
|
|
var colStr = (isDefined(start) ?
|
|
|
|
|
|
"s " + start + "-" + index + " [" + text.substring(start, end) + "]"
|
|
|
|
|
|
: " " + end);
|
|
|
|
|
|
throw ngError(23, "Lexer Error: {0} at column{1} in expression [{2}].",
|
|
|
|
|
|
error, colStr, text);
|
2010-11-16 21:57:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2010-08-19 00:50:21 +00:00
|
|
|
|
function readNumber() {
|
2010-12-22 01:38:04 +00:00
|
|
|
|
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,
|
2011-10-07 18:27:49 +00:00
|
|
|
|
fn:function() {return number;}});
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
function readIdent() {
|
2012-01-24 10:33:35 +00:00
|
|
|
|
var ident = "",
|
|
|
|
|
|
start = index,
|
2013-05-07 04:56:51 +00:00
|
|
|
|
lastDot, peekIndex, methodName, ch;
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
2010-12-22 01:38:04 +00:00
|
|
|
|
while (index < text.length) {
|
2013-05-07 04:56:51 +00:00
|
|
|
|
ch = text.charAt(index);
|
2010-12-22 01:38:04 +00:00
|
|
|
|
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
|
2012-01-24 10:33:35 +00:00
|
|
|
|
if (ch == '.') lastDot = index;
|
2010-12-22 01:38:04 +00:00
|
|
|
|
ident += ch;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
break;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-12-22 01:38:04 +00:00
|
|
|
|
index++;
|
|
|
|
|
|
}
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
|
|
|
|
|
//check if this is not a method invocation and if it is back out to last dot
|
|
|
|
|
|
if (lastDot) {
|
2012-04-10 21:29:49 +00:00
|
|
|
|
peekIndex = index;
|
2012-01-24 10:33:35 +00:00
|
|
|
|
while(peekIndex < text.length) {
|
2013-05-07 04:56:51 +00:00
|
|
|
|
ch = text.charAt(peekIndex);
|
2012-01-24 10:33:35 +00:00
|
|
|
|
if (ch == '(') {
|
|
|
|
|
|
methodName = ident.substr(lastDot - start + 1);
|
|
|
|
|
|
ident = ident.substr(0, lastDot - start);
|
|
|
|
|
|
index = peekIndex;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(isWhitespace(ch)) {
|
|
|
|
|
|
peekIndex++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-03-08 01:07:25 +00:00
|
|
|
|
|
|
|
|
|
|
var token = {
|
2011-01-19 23:42:11 +00:00
|
|
|
|
index:start,
|
2012-03-08 01:07:25 +00:00
|
|
|
|
text:ident
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (OPERATORS.hasOwnProperty(ident)) {
|
|
|
|
|
|
token.fn = token.json = OPERATORS[ident];
|
|
|
|
|
|
} else {
|
2012-04-27 22:20:54 +00:00
|
|
|
|
var getter = getterFn(ident, csp);
|
2012-03-08 01:07:25 +00:00
|
|
|
|
token.fn = extend(function(self, locals) {
|
|
|
|
|
|
return (getter(self, locals));
|
|
|
|
|
|
}, {
|
|
|
|
|
|
assign: function(self, value) {
|
|
|
|
|
|
return setter(self, ident, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tokens.push(token);
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
|
|
|
|
|
if (methodName) {
|
|
|
|
|
|
tokens.push({
|
|
|
|
|
|
index:lastDot,
|
|
|
|
|
|
text: '.',
|
|
|
|
|
|
json: false
|
|
|
|
|
|
});
|
|
|
|
|
|
tokens.push({
|
|
|
|
|
|
index: lastDot + 1,
|
|
|
|
|
|
text: methodName,
|
|
|
|
|
|
json: false
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
2011-01-19 23:42:11 +00:00
|
|
|
|
|
2010-08-19 00:50:21 +00:00
|
|
|
|
function readString(quote) {
|
2010-12-22 01:38:04 +00:00
|
|
|
|
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++;
|
2011-11-03 17:28:28 +00:00
|
|
|
|
tokens.push({
|
|
|
|
|
|
index:start,
|
|
|
|
|
|
text:rawString,
|
|
|
|
|
|
string:string,
|
|
|
|
|
|
json:true,
|
|
|
|
|
|
fn:function() { return string; }
|
|
|
|
|
|
});
|
2010-12-22 01:38:04 +00:00
|
|
|
|
return;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
string += ch;
|
|
|
|
|
|
}
|
|
|
|
|
|
index++;
|
|
|
|
|
|
}
|
|
|
|
|
|
throwError("Unterminated quote", start);
|
2010-08-19 00:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-01-06 00:36:58 +00:00
|
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
|
/////////////////////////////////////////
|
2010-01-06 00:36:58 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
function parser(text, json, $filter, csp){
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var ZERO = valueFn(0),
|
2011-11-03 20:53:37 +00:00
|
|
|
|
value,
|
2012-04-27 22:20:54 +00:00
|
|
|
|
tokens = lex(text, csp),
|
2011-01-19 23:42:11 +00:00
|
|
|
|
assignment = _assignment,
|
|
|
|
|
|
functionCall = _functionCall,
|
|
|
|
|
|
fieldAccess = _fieldAccess,
|
|
|
|
|
|
objectIndex = _objectIndex,
|
2012-04-10 21:29:49 +00:00
|
|
|
|
filterChain = _filterChain;
|
|
|
|
|
|
|
2011-01-04 21:23:39 +00:00
|
|
|
|
if(json){
|
2011-01-19 23:42:11 +00:00
|
|
|
|
// The extra level of aliasing is here, just in case the lexer misses something, so that
|
2011-01-04 21:23:39 +00:00
|
|
|
|
// we prevent any accidental execution in JSON.
|
|
|
|
|
|
assignment = logicalOR;
|
2011-01-19 23:42:11 +00:00
|
|
|
|
functionCall =
|
|
|
|
|
|
fieldAccess =
|
|
|
|
|
|
objectIndex =
|
|
|
|
|
|
filterChain =
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function() { throwError("is not valid json", {text:text, index:0}); };
|
2011-11-03 20:53:37 +00:00
|
|
|
|
value = primary();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
value = statements();
|
2011-01-04 21:23:39 +00:00
|
|
|
|
}
|
2011-11-03 20:53:37 +00:00
|
|
|
|
if (tokens.length !== 0) {
|
|
|
|
|
|
throwError("is an unexpected token", tokens[0]);
|
2011-04-05 18:00:26 +00:00
|
|
|
|
}
|
2012-11-04 13:05:58 +00:00
|
|
|
|
value.literal = !!value.literal;
|
|
|
|
|
|
value.constant = !!value.constant;
|
2011-11-03 20:53:37 +00:00
|
|
|
|
return value;
|
2011-04-05 18:00:26 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
///////////////////////////////////
|
2010-11-16 21:57:41 +00:00
|
|
|
|
function throwError(msg, token) {
|
2013-05-24 18:00:14 +00:00
|
|
|
|
throw ngError(24,
|
|
|
|
|
|
"Syntax Error: Token '{0}' {1} at column {2} of the expression [{3}] starting at [{4}].",
|
|
|
|
|
|
token.text, msg, (token.index + 1), text, text.substring(token.index));
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function peekToken() {
|
|
|
|
|
|
if (tokens.length === 0)
|
2013-05-24 18:00:14 +00:00
|
|
|
|
throw ngError(25, "Unexpected end of expression: {0}", text);
|
2010-10-15 22:28:58 +00:00
|
|
|
|
return tokens[0];
|
|
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +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-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +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) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (json && !token.json) {
|
2010-11-16 21:57:41 +00:00
|
|
|
|
throwError("is not valid json", token);
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
tokens.shift();
|
2010-01-06 00:36:58 +00:00
|
|
|
|
return token;
|
|
|
|
|
|
}
|
2010-01-12 01:32:33 +00:00
|
|
|
|
return false;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function consume(e1){
|
|
|
|
|
|
if (!expect(e1)) {
|
2010-11-16 21:57:41 +00:00
|
|
|
|
throwError("is unexpected, expecting [" + e1 + "]", peek());
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function unaryFn(fn, right) {
|
2012-11-04 13:05:58 +00:00
|
|
|
|
return extend(function(self, locals) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
return fn(self, locals, right);
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
|
|
|
|
|
constant:right.constant
|
|
|
|
|
|
});
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-04-23 00:42:34 +00:00
|
|
|
|
function ternaryFn(left, middle, right){
|
|
|
|
|
|
return extend(function(self, locals){
|
|
|
|
|
|
return left(self, locals) ? middle(self, locals) : right(self, locals);
|
|
|
|
|
|
}, {
|
|
|
|
|
|
constant: left.constant && middle.constant && right.constant
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2013-05-24 18:00:14 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function binaryFn(left, fn, right) {
|
2012-11-04 13:05:58 +00:00
|
|
|
|
return extend(function(self, locals) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
return fn(self, locals, left, right);
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
|
|
|
|
|
constant:left.constant && right.constant
|
|
|
|
|
|
});
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function statements() {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var statements = [];
|
|
|
|
|
|
while(true) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (tokens.length > 0 && !peek('}', ')', ';', ']'))
|
|
|
|
|
|
statements.push(filterChain());
|
|
|
|
|
|
if (!expect(';')) {
|
2011-03-23 16:30:08 +00:00
|
|
|
|
// 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]
|
2012-02-07 05:56:05 +00:00
|
|
|
|
: function(self, locals){
|
2011-03-23 16:30:08 +00:00
|
|
|
|
var value;
|
|
|
|
|
|
for ( var i = 0; i < statements.length; i++) {
|
|
|
|
|
|
var statement = statements[i];
|
|
|
|
|
|
if (statement)
|
2012-02-07 05:56:05 +00:00
|
|
|
|
value = statement(self, locals);
|
2011-03-23 16:30:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
return value;
|
|
|
|
|
|
};
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function _filterChain() {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var left = expression();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
|
|
|
|
|
while(true) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if ((token = expect('|'))) {
|
|
|
|
|
|
left = binaryFn(left, token.fn, filter());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return left;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function filter() {
|
2011-11-03 22:59:18 +00:00
|
|
|
|
var token = expect();
|
|
|
|
|
|
var fn = $filter(token.text);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var argsFn = [];
|
|
|
|
|
|
while(true) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if ((token = expect(':'))) {
|
|
|
|
|
|
argsFn.push(expression());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
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++) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
args.push(argsFn[i](self, locals));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-05-30 23:11:00 +00:00
|
|
|
|
return fn.apply(self, args);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
};
|
2011-10-07 18:27:49 +00:00
|
|
|
|
return function() {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
return fnInvoke;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function expression() {
|
2010-11-16 21:57:41 +00:00
|
|
|
|
return assignment();
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function _assignment() {
|
2013-04-23 00:42:34 +00:00
|
|
|
|
var left = ternary();
|
2010-12-07 19:39:59 +00:00
|
|
|
|
var right;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2011-08-14 08:26:56 +00:00
|
|
|
|
if ((token = expect('='))) {
|
2010-12-07 19:39:59 +00:00
|
|
|
|
if (!left.assign) {
|
2010-11-16 21:57:41 +00:00
|
|
|
|
throwError("implies assignment but [" +
|
|
|
|
|
|
text.substring(0, token.index) + "] can not be assigned to", token);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-04-23 00:42:34 +00:00
|
|
|
|
right = ternary();
|
2013-04-25 15:54:28 +00:00
|
|
|
|
return function(scope, locals){
|
|
|
|
|
|
return left.assign(scope, right(scope, locals), locals);
|
2010-12-07 19:39:59 +00:00
|
|
|
|
};
|
2010-01-06 00:36:58 +00:00
|
|
|
|
} else {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
return left;
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2013-04-23 00:42:34 +00:00
|
|
|
|
function ternary() {
|
|
|
|
|
|
var left = logicalOR();
|
|
|
|
|
|
var middle;
|
|
|
|
|
|
var token;
|
|
|
|
|
|
if((token = expect('?'))){
|
|
|
|
|
|
middle = ternary();
|
|
|
|
|
|
if((token = expect(':'))){
|
|
|
|
|
|
return ternaryFn(left, middle, ternary());
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
throwError('expected :', token);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
return left;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2013-05-24 18:00:14 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function logicalOR() {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var left = logicalAND();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
|
|
|
|
|
while(true) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if ((token = expect('||'))) {
|
|
|
|
|
|
left = binaryFn(left, token.fn, logicalAND());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
return left;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function logicalAND() {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var left = equality();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if ((token = expect('&&'))) {
|
|
|
|
|
|
left = binaryFn(left, token.fn, logicalAND());
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2010-02-12 22:16:33 +00:00
|
|
|
|
return left;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function equality() {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var left = relational();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2012-11-27 16:00:46 +00:00
|
|
|
|
if ((token = expect('==','!=','===','!=='))) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
left = binaryFn(left, token.fn, equality());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-02-12 22:16:33 +00:00
|
|
|
|
return left;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function relational() {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var left = additive();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2011-08-14 08:26:56 +00:00
|
|
|
|
if ((token = expect('<', '>', '<=', '>='))) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
left = binaryFn(left, token.fn, relational());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-02-12 22:16:33 +00:00
|
|
|
|
return left;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function additive() {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var left = multiplicative();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2011-08-14 08:26:56 +00:00
|
|
|
|
while ((token = expect('+','-'))) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
left = binaryFn(left, token.fn, multiplicative());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return left;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function multiplicative() {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var left = unary();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2011-08-14 08:26:56 +00:00
|
|
|
|
while ((token = expect('*','/','%'))) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
left = binaryFn(left, token.fn, unary());
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return left;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-10-07 18:27:49 +00:00
|
|
|
|
function unary() {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var token;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (expect('+')) {
|
|
|
|
|
|
return primary();
|
2011-08-14 08:26:56 +00:00
|
|
|
|
} else if ((token = expect('-'))) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
return binaryFn(ZERO, token.fn, unary());
|
2011-08-14 08:26:56 +00:00
|
|
|
|
} else if ((token = expect('!'))) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
return unaryFn(token.fn, unary());
|
2010-01-06 00:36:58 +00:00
|
|
|
|
} else {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
return primary();
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function primary() {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var primary;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (expect('(')) {
|
2012-01-24 10:40:25 +00:00
|
|
|
|
primary = filterChain();
|
2010-10-15 22:28:58 +00:00
|
|
|
|
consume(')');
|
|
|
|
|
|
} else if (expect('[')) {
|
|
|
|
|
|
primary = arrayDeclaration();
|
|
|
|
|
|
} else if (expect('{')) {
|
|
|
|
|
|
primary = object();
|
2010-01-06 00:36:58 +00:00
|
|
|
|
} else {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var token = expect();
|
2010-01-12 01:32:33 +00:00
|
|
|
|
primary = token.fn;
|
|
|
|
|
|
if (!primary) {
|
2010-11-16 21:57:41 +00:00
|
|
|
|
throwError("not a primary expression", token);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2012-11-04 13:05:58 +00:00
|
|
|
|
if (token.json) {
|
|
|
|
|
|
primary.constant = primary.literal = true;
|
|
|
|
|
|
}
|
2010-01-06 00:36:58 +00:00
|
|
|
|
}
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
|
|
|
|
|
var next, context;
|
2011-08-14 08:26:56 +00:00
|
|
|
|
while ((next = expect('(', '[', '.'))) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
if (next.text === '(') {
|
2012-01-24 10:33:35 +00:00
|
|
|
|
primary = functionCall(primary, context);
|
|
|
|
|
|
context = null;
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else if (next.text === '[') {
|
2012-01-24 10:33:35 +00:00
|
|
|
|
context = primary;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
primary = objectIndex(primary);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else if (next.text === '.') {
|
2012-01-24 10:33:35 +00:00
|
|
|
|
context = primary;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
primary = fieldAccess(primary);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
} else {
|
2010-11-16 21:57:41 +00:00
|
|
|
|
throwError("IMPOSSIBLE");
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return primary;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-01-04 21:23:39 +00:00
|
|
|
|
function _fieldAccess(object) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var field = expect().text;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
var getter = getterFn(field, csp);
|
2012-02-07 05:56:05 +00:00
|
|
|
|
return extend(
|
2013-04-25 15:54:28 +00:00
|
|
|
|
function(scope, locals, self) {
|
|
|
|
|
|
return getter(self || object(scope, locals), locals);
|
2012-02-07 05:56:05 +00:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2013-04-25 15:54:28 +00:00
|
|
|
|
assign:function(scope, value, locals) {
|
|
|
|
|
|
return setter(object(scope, locals), field, value);
|
2012-02-07 05:56:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2011-01-04 21:23:39 +00:00
|
|
|
|
function _objectIndex(obj) {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
var indexFn = expression();
|
|
|
|
|
|
consume(']');
|
2010-12-07 19:39:59 +00:00
|
|
|
|
return extend(
|
2012-02-07 05:56:05 +00:00
|
|
|
|
function(self, locals){
|
|
|
|
|
|
var o = obj(self, locals),
|
|
|
|
|
|
i = indexFn(self, locals),
|
2011-11-23 23:53:06 +00:00
|
|
|
|
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;
|
2010-12-07 19:39:59 +00:00
|
|
|
|
}, {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
assign:function(self, value, locals){
|
|
|
|
|
|
return obj(self, locals)[indexFn(self, locals)] = value;
|
2010-12-07 19:39:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2012-01-24 10:33:35 +00:00
|
|
|
|
function _functionCall(fn, contextGetter) {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var argsFn = [];
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (peekToken().text != ')') {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
do {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
argsFn.push(expression());
|
|
|
|
|
|
} while (expect(','));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
consume(')');
|
2013-04-25 15:54:28 +00:00
|
|
|
|
return function(scope, locals){
|
2012-01-24 10:33:35 +00:00
|
|
|
|
var args = [],
|
2013-04-25 15:54:28 +00:00
|
|
|
|
context = contextGetter ? contextGetter(scope, locals) : scope;
|
2012-01-24 10:33:35 +00:00
|
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
|
for ( var i = 0; i < argsFn.length; i++) {
|
2013-04-25 15:54:28 +00:00
|
|
|
|
args.push(argsFn[i](scope, locals));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2013-04-25 15:54:28 +00:00
|
|
|
|
var fnPtr = fn(scope, locals, context) || noop;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
// IE stupidity!
|
2011-02-12 16:58:11 +00:00
|
|
|
|
return fnPtr.apply
|
2012-01-24 10:33:35 +00:00
|
|
|
|
? fnPtr.apply(context, args)
|
2011-02-12 16:58:11 +00:00
|
|
|
|
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
|
2010-01-06 00:36:58 +00:00
|
|
|
|
};
|
2010-10-15 22:28: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
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function arrayDeclaration () {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var elementFns = [];
|
2012-11-04 13:05:58 +00:00
|
|
|
|
var allConstant = true;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (peekToken().text != ']') {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
do {
|
2012-11-04 13:05:58 +00:00
|
|
|
|
var elementFn = expression();
|
|
|
|
|
|
elementFns.push(elementFn);
|
|
|
|
|
|
if (!elementFn.constant) {
|
|
|
|
|
|
allConstant = false;
|
|
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
} while (expect(','));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
consume(']');
|
2012-11-04 13:05:58 +00:00
|
|
|
|
return extend(function(self, locals){
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var array = [];
|
|
|
|
|
|
for ( var i = 0; i < elementFns.length; i++) {
|
2012-02-07 05:56:05 +00:00
|
|
|
|
array.push(elementFns[i](self, locals));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return array;
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
|
|
|
|
|
literal:true,
|
|
|
|
|
|
constant:allConstant
|
|
|
|
|
|
});
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
2010-03-24 17:35:01 +00:00
|
|
|
|
|
2010-10-15 22:28:58 +00:00
|
|
|
|
function object () {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
var keyValues = [];
|
2012-11-04 13:05:58 +00:00
|
|
|
|
var allConstant = true;
|
2010-10-15 22:28:58 +00:00
|
|
|
|
if (peekToken().text != '}') {
|
2010-01-12 01:32:33 +00:00
|
|
|
|
do {
|
2010-10-15 22:28:58 +00:00
|
|
|
|
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});
|
2012-11-04 13:05:58 +00:00
|
|
|
|
if (!value.constant) {
|
|
|
|
|
|
allConstant = false;
|
|
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
} while (expect(','));
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
consume('}');
|
2012-11-04 13:05:58 +00:00
|
|
|
|
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];
|
2013-05-07 04:56:51 +00:00
|
|
|
|
object[keyValue.key] = keyValue.value(self, locals);
|
2010-01-12 01:32:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
return object;
|
2012-11-04 13:05:58 +00:00
|
|
|
|
}, {
|
|
|
|
|
|
literal:true,
|
|
|
|
|
|
constant:allConstant
|
|
|
|
|
|
});
|
2010-10-15 22:28:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-03-23 16:33:29 +00:00
|
|
|
|
//////////////////////////////////////////////////
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2013-03-21 19:09:47 +00:00
|
|
|
|
* Return the value accessible from the object by path. Any undefined traversals are ignored
|
2011-03-23 16:33:29 +00:00
|
|
|
|
* @param {Object} obj starting object
|
|
|
|
|
|
* @param {string} path path to traverse
|
|
|
|
|
|
* @param {boolean=true} bindFnToScope
|
2013-03-21 19:09:47 +00:00
|
|
|
|
* @returns value as accessible by path
|
2011-03-23 16:33:29 +00:00
|
|
|
|
*/
|
2011-11-04 04:14:04 +00:00
|
|
|
|
//TODO(misko): this function needs to be removed
|
2011-03-23 16:33:29 +00:00
|
|
|
|
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 = {};
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 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;
|
|
|
|
|
|
|
2012-04-14 17:39:24 +00:00
|
|
|
|
if (pathVal === null || pathVal === undefined) return pathVal;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2012-04-14 17:39:24 +00:00
|
|
|
|
if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2012-04-14 17:39:24 +00:00
|
|
|
|
if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2012-04-14 17:39:24 +00:00
|
|
|
|
if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2012-04-14 17:39:24 +00:00
|
|
|
|
if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
};
|
2013-05-07 04:56:51 +00:00
|
|
|
|
}
|
2012-04-27 22:20:54 +00:00
|
|
|
|
|
|
|
|
|
|
function getterFn(path, csp) {
|
2012-03-08 01:07:25 +00:00
|
|
|
|
if (getterFnCache.hasOwnProperty(path)) {
|
|
|
|
|
|
return getterFnCache[path];
|
|
|
|
|
|
}
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
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) {
|
2013-05-07 04:56:51 +00:00
|
|
|
|
var i = 0, val;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
do {
|
|
|
|
|
|
val = cspSafeGetterFn(
|
|
|
|
|
|
pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
|
|
|
|
|
|
)(scope, locals);
|
2012-04-14 17:39:24 +00:00
|
|
|
|
|
2012-04-27 22:20:54 +00:00
|
|
|
|
locals = undefined; // clear after first iteration
|
2012-04-14 17:39:24 +00:00
|
|
|
|
scope = val;
|
2012-04-27 22:20:54 +00:00
|
|
|
|
} while (i < pathKeysLength);
|
2012-04-14 17:39:24 +00:00
|
|
|
|
return val;
|
|
|
|
|
|
}
|
2012-04-27 22:20:54 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
var code = 'var l, fn, p;\n';
|
|
|
|
|
|
forEach(pathKeys, function(key, index) {
|
2012-04-14 17:39:24 +00:00
|
|
|
|
code += 'if(s === null || s === undefined) return s;\n' +
|
2012-04-27 22:20:54 +00:00
|
|
|
|
'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; };
|
|
|
|
|
|
}
|
2011-03-23 16:33:29 +00:00
|
|
|
|
|
|
|
|
|
|
return getterFnCache[path] = fn;
|
|
|
|
|
|
}
|
2010-10-15 22:28:58 +00:00
|
|
|
|
|
2011-03-23 16:33:29 +00:00
|
|
|
|
///////////////////////////////////
|
2010-10-15 22:28:58 +00:00
|
|
|
|
|
2012-02-28 22:29:58 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @ngdoc function
|
2012-06-12 06:49:24 +00:00
|
|
|
|
* @name ng.$parse
|
2012-02-28 22:29:58 +00:00
|
|
|
|
* @function
|
|
|
|
|
|
*
|
|
|
|
|
|
* @description
|
|
|
|
|
|
*
|
2012-03-01 19:28:50 +00:00
|
|
|
|
* 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:
|
|
|
|
|
|
*
|
2012-11-04 22:36:35 +00:00
|
|
|
|
* * `context` – `{object}` – an object against which any expressions embedded in the strings
|
2013-03-21 19:09:47 +00:00
|
|
|
|
* are evaluated against (typically a scope object).
|
2012-11-04 22:36:35 +00:00
|
|
|
|
* * `locals` – `{object=}` – local variables context object, useful for overriding values in
|
|
|
|
|
|
* `context`.
|
2012-02-28 22:29:58 +00:00
|
|
|
|
*
|
2012-11-04 13:05: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
|
|
|
|
*
|
|
|
|
|
|
*/
|
2011-11-03 20:53:37 +00:00
|
|
|
|
function $ParseProvider() {
|
|
|
|
|
|
var cache = {};
|
2012-04-27 22:20:54 +00:00
|
|
|
|
this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
|
2011-11-03 20:53:37 +00:00
|
|
|
|
return function(exp) {
|
|
|
|
|
|
switch(typeof exp) {
|
|
|
|
|
|
case 'string':
|
|
|
|
|
|
return cache.hasOwnProperty(exp)
|
|
|
|
|
|
? cache[exp]
|
2012-04-27 22:20:54 +00:00
|
|
|
|
: cache[exp] = parser(exp, false, $filter, $sniffer.csp);
|
2011-11-03 20:53:37 +00:00
|
|
|
|
case 'function':
|
|
|
|
|
|
return exp;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return noop;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}];
|
2011-03-23 16:33:29 +00:00
|
|
|
|
}
|