aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2012-02-06 21:56:05 -0800
committerMisko Hevery2012-02-21 22:45:59 -0800
commit761b2ed85ad9685c35f85513e17363abf17ce6b3 (patch)
treed24d2f1ede08134b5e7891224642222f8285cf7a
parentc8ee631c1999fc87c0002bb27bd411fb953a726f (diff)
downloadangular.js-761b2ed85ad9685c35f85513e17363abf17ce6b3.tar.bz2
feat(parse): add support for local vars in expressions
-rw-r--r--src/service/parse.js139
-rw-r--r--test/service/parseSpec.js14
2 files changed, 88 insertions, 65 deletions
diff --git a/src/service/parse.js b/src/service/parse.js
index 4958884f..f7324ea6 100644
--- a/src/service/parse.js
+++ b/src/service/parse.js
@@ -1,29 +1,29 @@
'use strict';
var OPERATORS = {
- 'null':function(self){return null;},
- 'true':function(self){return true;},
- 'false':function(self){return false;},
+ 'null':function(){return null;},
+ 'true':function(){return true;},
+ 'false':function(){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);},
+ '+':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){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, 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);}
+ '==':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);}
};
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
@@ -146,7 +146,7 @@ function lex(text){
function readIdent() {
var ident = "",
start = index,
- fn, lastDot, peekIndex, methodName;
+ fn, lastDot, peekIndex, methodName, getter;
while (index < text.length) {
var ch = text.charAt(index);
@@ -179,15 +179,21 @@ function lex(text){
}
fn = OPERATORS[ident];
+ getter = getterFn(ident);
tokens.push({
index:start,
text:ident,
json: fn,
- fn:fn||extend(getterFn(ident), {
- assign:function(self, value){
- return setter(self, ident, value);
- }
- })
+ fn:fn||extend(
+ function(self, locals) {
+ return (getter(self, locals));
+ },
+ {
+ assign:function(self, value){
+ return setter(self, ident, value);
+ }
+ }
+ )
});
if (methodName) {
@@ -257,12 +263,10 @@ function parser(text, json, $filter){
value,
tokens = lex(text),
assignment = _assignment,
- assignable = logicalOR,
functionCall = _functionCall,
fieldAccess = _fieldAccess,
objectIndex = _objectIndex,
- filterChain = _filterChain,
- functionIdent = _functionIdent;
+ 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.
@@ -270,9 +274,7 @@ function parser(text, json, $filter){
functionCall =
fieldAccess =
objectIndex =
- assignable =
filterChain =
- functionIdent =
function() { throwError("is not valid json", {text:text, index:0}); };
value = primary();
} else {
@@ -328,14 +330,14 @@ function parser(text, json, $filter){
}
function unaryFn(fn, right) {
- return function(self) {
- return fn(self, right);
+ return function(self, locals) {
+ return fn(self, locals, right);
};
}
function binaryFn(left, fn, right) {
- return function(self) {
- return fn(self, left, right);
+ return function(self, locals) {
+ return fn(self, locals, left, right);
};
}
@@ -353,12 +355,12 @@ function parser(text, json, $filter){
// TODO(size): maybe we should not support multiple statements?
return statements.length == 1
? statements[0]
- : function(self){
+ : function(self, locals){
var value;
for ( var i = 0; i < statements.length; i++) {
var statement = statements[i];
if (statement)
- value = statement(self);
+ value = statement(self, locals);
}
return value;
};
@@ -386,10 +388,10 @@ function parser(text, json, $filter){
if ((token = expect(':'))) {
argsFn.push(expression());
} else {
- var fnInvoke = function(self, input){
+ var fnInvoke = function(self, locals, input){
var args = [input];
for ( var i = 0; i < argsFn.length; i++) {
- args.push(argsFn[i](self));
+ args.push(argsFn[i](self, locals));
}
return fn.apply(self, args);
};
@@ -414,8 +416,8 @@ function parser(text, json, $filter){
text.substring(0, token.index) + "] can not be assigned to", token);
}
right = logicalOR();
- return function(self){
- return left.assign(self, right(self));
+ return function(self, locals){
+ return left.assign(self, right(self, locals), locals);
};
} else {
return left;
@@ -546,22 +548,25 @@ function parser(text, json, $filter){
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);
- }
- });
+ return extend(
+ function(self, locals) {
+ return getter(object(self, locals), locals);
+ },
+ {
+ assign:function(self, value, locals) {
+ return setter(object(self, locals), field, value);
+ }
+ }
+ );
}
function _objectIndex(obj) {
var indexFn = expression();
consume(']');
return extend(
- function(self){
- var o = obj(self),
- i = indexFn(self),
+ function(self, locals){
+ var o = obj(self, locals),
+ i = indexFn(self, locals),
v, p;
if (!o) return undefined;
@@ -576,8 +581,8 @@ function parser(text, json, $filter){
}
return v;
}, {
- assign:function(self, value){
- return obj(self)[indexFn(self)] = value;
+ assign:function(self, value, locals){
+ return obj(self, locals)[indexFn(self, locals)] = value;
}
});
}
@@ -590,14 +595,14 @@ function parser(text, json, $filter){
} while (expect(','));
}
consume(')');
- return function(self){
+ return function(self, locals){
var args = [],
- context = contextGetter ? contextGetter(self) : self;
+ context = contextGetter ? contextGetter(self, locals) : self;
for ( var i = 0; i < argsFn.length; i++) {
- args.push(argsFn[i](self));
+ args.push(argsFn[i](self, locals));
}
- var fnPtr = fn(self) || noop;
+ var fnPtr = fn(self, locals) || noop;
// IE stupidity!
return fnPtr.apply
? fnPtr.apply(context, args)
@@ -614,10 +619,10 @@ function parser(text, json, $filter){
} while (expect(','));
}
consume(']');
- return function(self){
+ return function(self, locals){
var array = [];
for ( var i = 0; i < elementFns.length; i++) {
- array.push(elementFns[i](self));
+ array.push(elementFns[i](self, locals));
}
return array;
};
@@ -635,11 +640,11 @@ function parser(text, json, $filter){
} while (expect(','));
}
consume('}');
- return function(self){
+ return function(self, locals){
var object = {};
for ( var i = 0; i < keyValues.length; i++) {
var keyValue = keyValues[i];
- var value = keyValue.value(self);
+ var value = keyValue.value(self, locals);
object[keyValue.key] = value;
}
return object;
@@ -700,10 +705,14 @@ function getterFn(path) {
if (fn) return fn;
var code = 'var l, fn, p;\n';
- forEach(path.split('.'), function(key) {
+ forEach(path.split('.'), function(key, index) {
code += 'if(!s) return s;\n' +
'l=s;\n' +
- 's=s' + '["' + key + '"]' + ';\n' +
+ 's='+ (index
+ // we simply direference 's' on any .dot notation
+ ? 's'
+ // but if we are first then we check locals firs, 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' +
@@ -714,7 +723,7 @@ function getterFn(path) {
'}\n';
});
code += 'return s;';
- fn = Function('s', code);
+ fn = Function('s', 'k', code);
fn.toString = function() { return code; };
return getterFnCache[path] = fn;
diff --git a/test/service/parseSpec.js b/test/service/parseSpec.js
index d83cc050..cf3f2aa5 100644
--- a/test/service/parseSpec.js
+++ b/test/service/parseSpec.js
@@ -603,4 +603,18 @@ describe('parser', function() {
expect(scope).toEqual({a:123});
}));
});
+
+
+ describe('locals', function() {
+ it('should expose local variables', inject(function($parse) {
+ expect($parse('a')({a: 0}, {a: 1})).toEqual(1);
+ expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3);
+ }));
+
+ it('should expose traverse locals', inject(function($parse) {
+ expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1);
+ expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1);
+ expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
+ }));
+ });
});