fix($parse): get rid of $unboundFn

Closes #731
This commit is contained in:
Igor Minar 2012-01-24 02:33:35 -08:00
parent 1268fc1a44
commit 39b3297fc3
5 changed files with 72 additions and 17 deletions

View file

@ -686,7 +686,6 @@ function equals(o1, o2) {
return true; return true;
} }
} }
if (t1 == 'function' && o1.$unboundFn) return o1.$unboundFn === o2.$unboundFn;
} }
return false; return false;
} }

View file

@ -144,18 +144,40 @@ function lex(text){
fn:function() {return number;}}); fn:function() {return number;}});
} }
function readIdent() { function readIdent() {
var ident = ""; var ident = "",
var start = index; start = index,
var fn; fn, lastDot, peekIndex, methodName;
while (index < text.length) { while (index < text.length) {
var ch = text.charAt(index); var ch = text.charAt(index);
if (ch == '.' || isIdent(ch) || isNumber(ch)) { if (ch == '.' || isIdent(ch) || isNumber(ch)) {
if (ch == '.') lastDot = index;
ident += ch; ident += ch;
} else { } else {
break; break;
} }
index++; 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;
}
}
}
fn = OPERATORS[ident]; fn = OPERATORS[ident];
tokens.push({ tokens.push({
index:start, index:start,
@ -167,6 +189,19 @@ function lex(text){
} }
}) })
}); });
if (methodName) {
tokens.push({
index:lastDot,
text: '.',
json: false
});
tokens.push({
index: lastDot + 1,
text: methodName,
json: false
});
}
} }
function readString(quote) { function readString(quote) {
@ -490,13 +525,17 @@ function parser(text, json, $filter){
throwError("not a primary expression", token); throwError("not a primary expression", token);
} }
} }
var next;
var next, context;
while ((next = expect('(', '[', '.'))) { while ((next = expect('(', '[', '.'))) {
if (next.text === '(') { if (next.text === '(') {
primary = functionCall(primary); primary = functionCall(primary, context);
context = null;
} else if (next.text === '[') { } else if (next.text === '[') {
context = primary;
primary = objectIndex(primary); primary = objectIndex(primary);
} else if (next.text === '.') { } else if (next.text === '.') {
context = primary;
primary = fieldAccess(primary); primary = fieldAccess(primary);
} else { } else {
throwError("IMPOSSIBLE"); throwError("IMPOSSIBLE");
@ -544,7 +583,7 @@ function parser(text, json, $filter){
}); });
} }
function _functionCall(fn) { function _functionCall(fn, contextGetter) {
var argsFn = []; var argsFn = [];
if (peekToken().text != ')') { if (peekToken().text != ')') {
do { do {
@ -553,14 +592,16 @@ function parser(text, json, $filter){
} }
consume(')'); consume(')');
return function(self){ return function(self){
var args = []; var args = [],
context = contextGetter ? contextGetter(self) : 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));
} }
var fnPtr = fn(self) || noop; var fnPtr = fn(self) || noop;
// IE stupidity! // IE stupidity!
return fnPtr.apply return fnPtr.apply
? fnPtr.apply(self, args) ? fnPtr.apply(context, args)
: fnPtr(args[0], args[1], args[2], args[3], args[4]); : fnPtr(args[0], args[1], args[2], args[3], args[4]);
}; };
} }
@ -691,11 +732,7 @@ function getterFn(path) {
code += 'if(!s) return s;\n' + code += 'if(!s) return s;\n' +
'l=s;\n' + 'l=s;\n' +
's=s' + key + ';\n' + 's=s' + key + ';\n' +
'if(typeof s=="function" && !(s instanceof RegExp)) {\n' + 'if (s && s.then) {\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' + ' if (!("$$v" in s)) {\n' +
' p=s;\n' + ' p=s;\n' +
' p.$$v = undefined;\n' + ' p.$$v = undefined;\n' +

View file

@ -741,7 +741,7 @@ var inputDirective = ['$defer', '$formFactory', function($defer, $formFactory) {
type = lowercase(type); type = lowercase(type);
TypeController = (loadFromScope TypeController = (loadFromScope
? (assertArgFn(modelScope.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn ? assertArgFn(modelScope.$eval(loadFromScope[1]), loadFromScope[1])
: angularInputType(type)) || noop; : angularInputType(type)) || noop;
if (!HTML5_INPUTS_TYPES[type]) { if (!HTML5_INPUTS_TYPES[type]) {

View file

@ -110,6 +110,17 @@ describe('parser', function() {
expect(tokens[3].text).toEqual(';'); expect(tokens[3].text).toEqual(';');
}); });
it('should tokenize function invocation', function() {
var tokens = lex("a()")
expect(map(tokens, function(t) { return t.text;})).toEqual(['a', '(', ')']);
});
it('should tokenize method invocation', function() {
var tokens = lex("a.b.c (d) - e.f()");
expect(map(tokens, function(t) { return t.text;})).
toEqual(['a.b', '.', 'c', '(', 'd', ')', '-', 'e', '.', 'f', '(', ')']);
});
it('should tokenize number', function() { it('should tokenize number', function() {
var tokens = lex("0.5"); var tokens = lex("0.5");
expect(tokens[0].text).toEqual(0.5); expect(tokens[0].text).toEqual(0.5);
@ -244,6 +255,12 @@ describe('parser', function() {
expect(scope.$eval("add(1,2)")).toEqual(3); expect(scope.$eval("add(1,2)")).toEqual(3);
}); });
it('should evaluate function call from a return value', function() {
scope.val = 33;
scope.getter = function() { return function() { return this.val; }};
expect(scope.$eval("getter()()")).toBe(33);
});
it('should evaluate multiplication and division', function() { it('should evaluate multiplication and division', function() {
scope.taxRate = 8; scope.taxRate = 8;
scope.subTotal = 100; scope.subTotal = 100;
@ -281,7 +298,7 @@ describe('parser', function() {
expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]'); expect(toJson(scope.$eval("[{a:[]}, {b:1}]"))).toEqual('[{"a":[]},{"b":1}]');
}); });
it('should evaluate multipple statements', function() { it('should evaluate multiple statements', function() {
expect(scope.$eval("a=1;b=3;a+b")).toEqual(4); expect(scope.$eval("a=1;b=3;a+b")).toEqual(4);
expect(scope.$eval(";;1;;")).toEqual(1); expect(scope.$eval(";;1;;")).toEqual(1);
}); });
@ -296,6 +313,7 @@ describe('parser', function() {
scope.obj = new C(); scope.obj = new C();
expect(scope.$eval("obj.getA()")).toEqual(123); expect(scope.$eval("obj.getA()")).toEqual(123);
expect(scope.$eval("obj['getA']()")).toEqual(123);
}); });
it('should evaluate methods in correct context (this) in argument', function() { it('should evaluate methods in correct context (this) in argument', function() {
@ -311,6 +329,7 @@ describe('parser', function() {
scope.obj = new C(); scope.obj = new C();
expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246); expect(scope.$eval("obj.sum(obj.getA())")).toEqual(246);
expect(scope.$eval("obj['sum'](obj.getA())")).toEqual(246);
}); });
it('should evaluate objects on scope context', function() { it('should evaluate objects on scope context', function() {

View file

@ -252,7 +252,7 @@ describe('Scope', function() {
module(provideLog); module(provideLog);
inject(function($rootScope, log) { inject(function($rootScope, log) {
$rootScope.fn = function() {return 'a'}; $rootScope.fn = function() {return 'a'};
$rootScope.$watch('fn', function(scope, fn) { $rootScope.$watch('fn', function(fn) {
log(fn()); log(fn());
}); });
$rootScope.$digest(); $rootScope.$digest();