Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Parser $unboundFn removal #732

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,6 @@ function equals(o1, o2) {
return true;
}
}
if (t1 == 'function' && o1.$unboundFn) return o1.$unboundFn === o2.$unboundFn;
}
return false;
}
Expand Down
103 changes: 56 additions & 47 deletions src/service/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var OPERATORS = {
'null':function(self){return null;},
'true':function(self){return true;},
'false':function(self){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, 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);},
Expand Down Expand Up @@ -33,7 +33,8 @@ function lex(text){
index = 0,
json = [],
ch,
lastCh = ':'; // can start regexp
lastCh = ':', // can start regexp
methodName, lastDot;

while (index < text.length) {
ch = text.charAt(index);
Expand All @@ -48,7 +49,39 @@ function lex(text){
(token=tokens[tokens.length-1])) {
token.json = token.text.indexOf('.') == -1;
}
} else if (is('(){}[].,;:')) {
} else if (is('(')) {
token=tokens[tokens.length-1];
if (token && !OPERATORS[token.text]) {
lastDot = token.text.lastIndexOf('.');
if (lastDot !== -1) {
// method invocation
methodName = token.text.substr(lastDot + 1);
token.text = token.text.substr(0, lastDot);
token.fn = extend(getterFn(token.text), {
assign:function(self, value){
return setter(self, token.text, value);
}
});
tokens.push({
index: token.index + token.text.length,
text: '.',
json: false
});
tokens.push({
index: token.index + token.text.length + 1,
text: methodName,
json: false
});
}
}
tokens.push({
index: index,
text: ch,
json: false
});
json.unshift(ch);
index++;
} else if (is('){}[].,;:')) {
tokens.push({
index:index,
text:ch,
Expand Down Expand Up @@ -144,9 +177,10 @@ function lex(text){
fn:function() {return number;}});
}
function readIdent() {
var ident = "";
var start = index;
var fn;
var ident = "",
start = index,
fn;

while (index < text.length) {
var ch = text.charAt(index);
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
Expand All @@ -156,6 +190,7 @@ function lex(text){
}
index++;
}

fn = OPERATORS[ident];
tokens.push({
index:start,
Expand Down Expand Up @@ -476,9 +511,8 @@ function parser(text, json, $filter){
function primary() {
var primary;
if (expect('(')) {
var expression = filterChain();
primary = filterChain();
consume(')');
primary = expression;
} else if (expect('[')) {
primary = arrayDeclaration();
} else if (expect('{')) {
Expand All @@ -490,13 +524,17 @@ function parser(text, json, $filter){
throwError("not a primary expression", token);
}
}
var next;

var next, context;
while ((next = expect('(', '[', '.'))) {
if (next.text === '(') {
primary = functionCall(primary);
primary = functionCall(primary, context);
context = null;
} else if (next.text === '[') {
context = primary;
primary = objectIndex(primary);
} else if (next.text === '.') {
context = primary;
primary = fieldAccess(primary);
} else {
throwError("IMPOSSIBLE");
Expand Down Expand Up @@ -544,7 +582,7 @@ function parser(text, json, $filter){
});
}

function _functionCall(fn) {
function _functionCall(fn, contextGetter) {
var argsFn = [];
if (peekToken().text != ')') {
do {
Expand All @@ -553,14 +591,16 @@ function parser(text, json, $filter){
}
consume(')');
return function(self){
var args = [];
var args = [],
context = contextGetter ? contextGetter(self) : self;

for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self));
}
var fnPtr = fn(self) || noop;
// IE stupidity!
return fnPtr.apply
? fnPtr.apply(self, args)
? fnPtr.apply(context, args)
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
};
}
Expand Down Expand Up @@ -605,22 +645,6 @@ function parser(text, json, $filter){
return object;
};
}

function watchDecl () {
var anchorName = expect().text;
consume(":");
var expressionFn;
if (peekToken().text == '{') {
consume("{");
expressionFn = statements();
consume("}");
} else {
expressionFn = expression();
}
return function(self) {
return {name:anchorName, fn:expressionFn};
};
}
}

//////////////////////////////////////////////////
Expand Down Expand Up @@ -669,33 +693,18 @@ function getter(obj, path, bindFnToScope) {
return obj;
}

var getterFnCache = {},
JS_KEYWORDS = {};

forEach(
("abstract,boolean,break,byte,case,catch,char,class,const,continue,debugger,default," +
"delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,goto," +
"if,implements,import,in,instanceof,int,interface,long,native,new,null,package,private," +
"protected,public,return,short,static,super,switch,synchronized,this,throw,throws," +
"transient,true,try,typeof,var,volatile,void,undefined,while,with").split(/,/),
function(key){ JS_KEYWORDS[key] = true;}
);
var getterFnCache = {};

function getterFn(path) {
var fn = getterFnCache[path];
if (fn) return fn;

var code = 'var l, fn, p;\n';
forEach(path.split('.'), function(key) {
key = (JS_KEYWORDS[key]) ? '["' + key + '"]' : '.' + key;
code += 'if(!s) return s;\n' +
'l=s;\n' +
's=s' + key + ';\n' +
'if(typeof s=="function" && !(s instanceof RegExp)) {\n' +
' fn=function(){ return l' + key + '.apply(l, arguments); };\n' +
' fn.$unboundFn=s;\n' +
' s=fn;\n' +
'} else if (s && s.then) {\n' +
's=s' + '["' + key + '"]' + ';\n' +
'if (s && s.then) {\n' +
' if (!("$$v" in s)) {\n' +
' p=s;\n' +
' p.$$v = undefined;\n' +
Expand Down
2 changes: 1 addition & 1 deletion src/widget/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ var inputDirective = ['$defer', '$formFactory', function($defer, $formFactory) {

type = lowercase(type);
TypeController = (loadFromScope
? (assertArgFn(modelScope.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn
? assertArgFn(modelScope.$eval(loadFromScope[1]), loadFromScope[1])
: angularInputType(type)) || noop;

if (!HTML5_INPUTS_TYPES[type]) {
Expand Down
21 changes: 20 additions & 1 deletion test/service/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ describe('parser', function() {
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() {
var tokens = lex("0.5");
expect(tokens[0].text).toEqual(0.5);
Expand Down Expand Up @@ -244,6 +255,12 @@ describe('parser', function() {
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() {
scope.taxRate = 8;
scope.subTotal = 100;
Expand Down Expand Up @@ -281,7 +298,7 @@ describe('parser', function() {
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(";;1;;")).toEqual(1);
});
Expand All @@ -296,6 +313,7 @@ describe('parser', function() {

scope.obj = new C();
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() {
Expand All @@ -311,6 +329,7 @@ describe('parser', function() {

scope.obj = new C();
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() {
Expand Down
2 changes: 1 addition & 1 deletion test/service/scopeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ describe('Scope', function() {
module(provideLog);
inject(function($rootScope, log) {
$rootScope.fn = function() {return 'a'};
$rootScope.$watch('fn', function(scope, fn) {
$rootScope.$watch('fn', function(fn) {
log(fn());
});
$rootScope.$digest();
Expand Down