mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-28 15:28:15 +00:00
feat(parse): add support for local vars in expressions
This commit is contained in:
parent
c8ee631c19
commit
761b2ed85a
2 changed files with 88 additions and 65 deletions
|
|
@ -1,29 +1,29 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var OPERATORS = {
|
var OPERATORS = {
|
||||||
'null':function(self){return null;},
|
'null':function(){return null;},
|
||||||
'true':function(self){return true;},
|
'true':function(){return true;},
|
||||||
'false':function(self){return false;},
|
'false':function(){return false;},
|
||||||
undefined:noop,
|
undefined:noop,
|
||||||
'+':function(self, a,b){a=a(self); b=b(self); 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, a,b){a=a(self); b=b(self); 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, a,b){return a(self)*b(self);},
|
'*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
|
||||||
'/':function(self, a,b){return a(self)/b(self);},
|
'/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
|
||||||
'%':function(self, a,b){return a(self)%b(self);},
|
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
|
||||||
'^':function(self, a,b){return a(self)^b(self);},
|
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
|
||||||
'=':noop,
|
'=':noop,
|
||||||
'==':function(self, a,b){return a(self)==b(self);},
|
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
|
||||||
'!=':function(self, a,b){return a(self)!=b(self);},
|
'!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
|
||||||
'<':function(self, a,b){return a(self)<b(self);},
|
'<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
|
||||||
'>':function(self, a,b){return a(self)>b(self);},
|
'>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
|
||||||
'<=':function(self, a,b){return a(self)<=b(self);},
|
'<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
|
||||||
'>=':function(self, a,b){return a(self)>=b(self);},
|
'>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
|
||||||
'&&':function(self, a,b){return a(self)&&b(self);},
|
'&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
|
||||||
'||':function(self, a,b){return a(self)||b(self);},
|
'||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
|
||||||
'&':function(self, a,b){return a(self)&b(self);},
|
'&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
|
||||||
// '|':function(self, a,b){return a|b;},
|
// '|':function(self, locals, a,b){return a|b;},
|
||||||
'|':function(self, a,b){return b(self)(self, a(self));},
|
'|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
|
||||||
'!':function(self, a){return !a(self);}
|
'!':function(self, locals, a){return !a(self, locals);}
|
||||||
};
|
};
|
||||||
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
||||||
|
|
||||||
|
|
@ -146,7 +146,7 @@ function lex(text){
|
||||||
function readIdent() {
|
function readIdent() {
|
||||||
var ident = "",
|
var ident = "",
|
||||||
start = index,
|
start = index,
|
||||||
fn, lastDot, peekIndex, methodName;
|
fn, lastDot, peekIndex, methodName, getter;
|
||||||
|
|
||||||
while (index < text.length) {
|
while (index < text.length) {
|
||||||
var ch = text.charAt(index);
|
var ch = text.charAt(index);
|
||||||
|
|
@ -179,15 +179,21 @@ function lex(text){
|
||||||
}
|
}
|
||||||
|
|
||||||
fn = OPERATORS[ident];
|
fn = OPERATORS[ident];
|
||||||
|
getter = getterFn(ident);
|
||||||
tokens.push({
|
tokens.push({
|
||||||
index:start,
|
index:start,
|
||||||
text:ident,
|
text:ident,
|
||||||
json: fn,
|
json: fn,
|
||||||
fn:fn||extend(getterFn(ident), {
|
fn:fn||extend(
|
||||||
assign:function(self, value){
|
function(self, locals) {
|
||||||
return setter(self, ident, value);
|
return (getter(self, locals));
|
||||||
}
|
},
|
||||||
})
|
{
|
||||||
|
assign:function(self, value){
|
||||||
|
return setter(self, ident, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (methodName) {
|
if (methodName) {
|
||||||
|
|
@ -257,12 +263,10 @@ function parser(text, json, $filter){
|
||||||
value,
|
value,
|
||||||
tokens = lex(text),
|
tokens = lex(text),
|
||||||
assignment = _assignment,
|
assignment = _assignment,
|
||||||
assignable = logicalOR,
|
|
||||||
functionCall = _functionCall,
|
functionCall = _functionCall,
|
||||||
fieldAccess = _fieldAccess,
|
fieldAccess = _fieldAccess,
|
||||||
objectIndex = _objectIndex,
|
objectIndex = _objectIndex,
|
||||||
filterChain = _filterChain,
|
filterChain = _filterChain
|
||||||
functionIdent = _functionIdent;
|
|
||||||
if(json){
|
if(json){
|
||||||
// The extra level of aliasing is here, just in case the lexer misses something, so that
|
// The extra level of aliasing is here, just in case the lexer misses something, so that
|
||||||
// we prevent any accidental execution in JSON.
|
// we prevent any accidental execution in JSON.
|
||||||
|
|
@ -270,9 +274,7 @@ function parser(text, json, $filter){
|
||||||
functionCall =
|
functionCall =
|
||||||
fieldAccess =
|
fieldAccess =
|
||||||
objectIndex =
|
objectIndex =
|
||||||
assignable =
|
|
||||||
filterChain =
|
filterChain =
|
||||||
functionIdent =
|
|
||||||
function() { throwError("is not valid json", {text:text, index:0}); };
|
function() { throwError("is not valid json", {text:text, index:0}); };
|
||||||
value = primary();
|
value = primary();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -328,14 +330,14 @@ function parser(text, json, $filter){
|
||||||
}
|
}
|
||||||
|
|
||||||
function unaryFn(fn, right) {
|
function unaryFn(fn, right) {
|
||||||
return function(self) {
|
return function(self, locals) {
|
||||||
return fn(self, right);
|
return fn(self, locals, right);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function binaryFn(left, fn, right) {
|
function binaryFn(left, fn, right) {
|
||||||
return function(self) {
|
return function(self, locals) {
|
||||||
return fn(self, left, right);
|
return fn(self, locals, left, right);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,12 +355,12 @@ function parser(text, json, $filter){
|
||||||
// TODO(size): maybe we should not support multiple statements?
|
// TODO(size): maybe we should not support multiple statements?
|
||||||
return statements.length == 1
|
return statements.length == 1
|
||||||
? statements[0]
|
? statements[0]
|
||||||
: function(self){
|
: function(self, locals){
|
||||||
var value;
|
var value;
|
||||||
for ( var i = 0; i < statements.length; i++) {
|
for ( var i = 0; i < statements.length; i++) {
|
||||||
var statement = statements[i];
|
var statement = statements[i];
|
||||||
if (statement)
|
if (statement)
|
||||||
value = statement(self);
|
value = statement(self, locals);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
@ -386,10 +388,10 @@ function parser(text, json, $filter){
|
||||||
if ((token = expect(':'))) {
|
if ((token = expect(':'))) {
|
||||||
argsFn.push(expression());
|
argsFn.push(expression());
|
||||||
} else {
|
} else {
|
||||||
var fnInvoke = function(self, input){
|
var fnInvoke = function(self, locals, input){
|
||||||
var args = [input];
|
var args = [input];
|
||||||
for ( var i = 0; i < argsFn.length; i++) {
|
for ( var i = 0; i < argsFn.length; i++) {
|
||||||
args.push(argsFn[i](self));
|
args.push(argsFn[i](self, locals));
|
||||||
}
|
}
|
||||||
return fn.apply(self, args);
|
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);
|
text.substring(0, token.index) + "] can not be assigned to", token);
|
||||||
}
|
}
|
||||||
right = logicalOR();
|
right = logicalOR();
|
||||||
return function(self){
|
return function(self, locals){
|
||||||
return left.assign(self, right(self));
|
return left.assign(self, right(self, locals), locals);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return left;
|
return left;
|
||||||
|
|
@ -546,22 +548,25 @@ function parser(text, json, $filter){
|
||||||
function _fieldAccess(object) {
|
function _fieldAccess(object) {
|
||||||
var field = expect().text;
|
var field = expect().text;
|
||||||
var getter = getterFn(field);
|
var getter = getterFn(field);
|
||||||
return extend(function(self){
|
return extend(
|
||||||
return getter(object(self));
|
function(self, locals) {
|
||||||
}, {
|
return getter(object(self, locals), locals);
|
||||||
assign:function(self, value){
|
},
|
||||||
return setter(object(self), field, value);
|
{
|
||||||
}
|
assign:function(self, value, locals) {
|
||||||
});
|
return setter(object(self, locals), field, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _objectIndex(obj) {
|
function _objectIndex(obj) {
|
||||||
var indexFn = expression();
|
var indexFn = expression();
|
||||||
consume(']');
|
consume(']');
|
||||||
return extend(
|
return extend(
|
||||||
function(self){
|
function(self, locals){
|
||||||
var o = obj(self),
|
var o = obj(self, locals),
|
||||||
i = indexFn(self),
|
i = indexFn(self, locals),
|
||||||
v, p;
|
v, p;
|
||||||
|
|
||||||
if (!o) return undefined;
|
if (!o) return undefined;
|
||||||
|
|
@ -576,8 +581,8 @@ function parser(text, json, $filter){
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}, {
|
}, {
|
||||||
assign:function(self, value){
|
assign:function(self, value, locals){
|
||||||
return obj(self)[indexFn(self)] = value;
|
return obj(self, locals)[indexFn(self, locals)] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -590,14 +595,14 @@ function parser(text, json, $filter){
|
||||||
} while (expect(','));
|
} while (expect(','));
|
||||||
}
|
}
|
||||||
consume(')');
|
consume(')');
|
||||||
return function(self){
|
return function(self, locals){
|
||||||
var args = [],
|
var args = [],
|
||||||
context = contextGetter ? contextGetter(self) : self;
|
context = contextGetter ? contextGetter(self, locals) : self;
|
||||||
|
|
||||||
for ( var i = 0; i < argsFn.length; i++) {
|
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!
|
// IE stupidity!
|
||||||
return fnPtr.apply
|
return fnPtr.apply
|
||||||
? fnPtr.apply(context, args)
|
? fnPtr.apply(context, args)
|
||||||
|
|
@ -614,10 +619,10 @@ function parser(text, json, $filter){
|
||||||
} while (expect(','));
|
} while (expect(','));
|
||||||
}
|
}
|
||||||
consume(']');
|
consume(']');
|
||||||
return function(self){
|
return function(self, locals){
|
||||||
var array = [];
|
var array = [];
|
||||||
for ( var i = 0; i < elementFns.length; i++) {
|
for ( var i = 0; i < elementFns.length; i++) {
|
||||||
array.push(elementFns[i](self));
|
array.push(elementFns[i](self, locals));
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
};
|
};
|
||||||
|
|
@ -635,11 +640,11 @@ function parser(text, json, $filter){
|
||||||
} while (expect(','));
|
} while (expect(','));
|
||||||
}
|
}
|
||||||
consume('}');
|
consume('}');
|
||||||
return function(self){
|
return function(self, locals){
|
||||||
var object = {};
|
var object = {};
|
||||||
for ( var i = 0; i < keyValues.length; i++) {
|
for ( var i = 0; i < keyValues.length; i++) {
|
||||||
var keyValue = keyValues[i];
|
var keyValue = keyValues[i];
|
||||||
var value = keyValue.value(self);
|
var value = keyValue.value(self, locals);
|
||||||
object[keyValue.key] = value;
|
object[keyValue.key] = value;
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
|
|
@ -700,10 +705,14 @@ function getterFn(path) {
|
||||||
if (fn) return fn;
|
if (fn) return fn;
|
||||||
|
|
||||||
var code = 'var l, fn, p;\n';
|
var code = 'var l, fn, p;\n';
|
||||||
forEach(path.split('.'), function(key) {
|
forEach(path.split('.'), function(key, index) {
|
||||||
code += 'if(!s) return s;\n' +
|
code += 'if(!s) return s;\n' +
|
||||||
'l=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 (s && s.then) {\n' +
|
||||||
' if (!("$$v" in s)) {\n' +
|
' if (!("$$v" in s)) {\n' +
|
||||||
' p=s;\n' +
|
' p=s;\n' +
|
||||||
|
|
@ -714,7 +723,7 @@ function getterFn(path) {
|
||||||
'}\n';
|
'}\n';
|
||||||
});
|
});
|
||||||
code += 'return s;';
|
code += 'return s;';
|
||||||
fn = Function('s', code);
|
fn = Function('s', 'k', code);
|
||||||
fn.toString = function() { return code; };
|
fn.toString = function() { return code; };
|
||||||
|
|
||||||
return getterFnCache[path] = fn;
|
return getterFnCache[path] = fn;
|
||||||
|
|
|
||||||
|
|
@ -603,4 +603,18 @@ describe('parser', function() {
|
||||||
expect(scope).toEqual({a:123});
|
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);
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue