Skip to content

Commit 19290cb

Browse files
committed
refactor($parse): separate tokenizing vs parsing more
Fixes `a.b` and `a .b` generating different getterFns Fixes angular#9131
1 parent 7cb01a8 commit 19290cb

File tree

2 files changed

+126
-147
lines changed

2 files changed

+126
-147
lines changed

src/ng/parse.js

+82-123
Original file line numberDiff line numberDiff line change
@@ -154,44 +154,33 @@ Lexer.prototype = {
154154
lex: function (text) {
155155
this.text = text;
156156
this.index = 0;
157-
this.ch = undefined;
158157
this.tokens = [];
159158

160-
while (this.index < this.text.length) {
161-
this.ch = this.text.charAt(this.index);
162-
if (this.is('"\'')) {
163-
this.readString(this.ch);
164-
} else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) {
159+
var length = this.text.length;
160+
161+
while (this.index < length) {
162+
var ch = this.text.charAt(this.index);
163+
if (ch === '"' || ch === "'") {
164+
this.readString(ch);
165+
} else if (this.isNumber(ch) || (this.index < length-1) && ch === '.' && this.isNumber(this.peek())) {
165166
this.readNumber();
166-
} else if (this.isIdent(this.ch)) {
167+
} else if (this.isIdent(ch)) {
167168
this.readIdent();
168-
} else if (this.is('(){}[].,;:?')) {
169-
this.tokens.push({
170-
index: this.index,
171-
text: this.ch
172-
});
169+
} else if ('(){}[].,;:?'.indexOf(ch) !== -1) {
170+
this.tokens.push({index: this.index, text: ch});
173171
this.index++;
174-
} else if (this.isWhitespace(this.ch)) {
172+
} else if (this.isWhitespace(ch)) {
175173
this.index++;
176174
} else {
177-
var ch2 = this.ch + this.peek();
175+
var ch2 = ch + this.peek();
178176
var ch3 = ch2 + this.peek(2);
179-
var fn = OPERATORS[this.ch];
180-
var fn2 = OPERATORS[ch2];
181-
var fn3 = OPERATORS[ch3];
182-
if (fn3) {
183-
this.tokens.push({index: this.index, text: ch3, fn: fn3});
184-
this.index += 3;
185-
} else if (fn2) {
186-
this.tokens.push({index: this.index, text: ch2, fn: fn2});
187-
this.index += 2;
188-
} else if (fn) {
189-
this.tokens.push({
190-
index: this.index,
191-
text: this.ch,
192-
fn: fn
193-
});
194-
this.index += 1;
177+
var op1 = OPERATORS[ch];
178+
var op2 = OPERATORS[ch2];
179+
var op3 = OPERATORS[ch3];
180+
if (op1 || op2 || op3) {
181+
var token = op3 ? ch3 : (op2 ? ch2 : ch);
182+
this.tokens.push({index: this.index, text: token, operator: true});
183+
this.index += token.length;
195184
} else {
196185
this.throwError('Unexpected next character ', this.index, this.index + 1);
197186
}
@@ -200,10 +189,6 @@ Lexer.prototype = {
200189
return this.tokens;
201190
},
202191

203-
is: function(chars) {
204-
return chars.indexOf(this.ch) !== -1;
205-
},
206-
207192
peek: function(i) {
208193
var num = i || 1;
209194
return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
@@ -263,79 +248,28 @@ Lexer.prototype = {
263248
}
264249
this.index++;
265250
}
266-
number = 1 * number;
267251
this.tokens.push({
268252
index: start,
269253
text: number,
270254
constant: true,
271-
fn: function() { return number; }
255+
value: Number(number)
272256
});
273257
},
274258

275259
readIdent: function() {
276-
var expression = this.text;
277-
278-
var ident = '';
279260
var start = this.index;
280-
281-
var lastDot, peekIndex, methodName, ch;
282-
283261
while (this.index < this.text.length) {
284-
ch = this.text.charAt(this.index);
285-
if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) {
286-
if (ch === '.') lastDot = this.index;
287-
ident += ch;
288-
} else {
262+
var ch = this.text.charAt(this.index);
263+
if (!(this.isIdent(ch) || this.isNumber(ch))) {
289264
break;
290265
}
291266
this.index++;
292267
}
293-
294-
//check if the identifier ends with . and if so move back one char
295-
if (lastDot && ident[ident.length - 1] === '.') {
296-
this.index--;
297-
ident = ident.slice(0, -1);
298-
lastDot = ident.lastIndexOf('.');
299-
if (lastDot === -1) {
300-
lastDot = undefined;
301-
}
302-
}
303-
304-
//check if this is not a method invocation and if it is back out to last dot
305-
if (lastDot) {
306-
peekIndex = this.index;
307-
while (peekIndex < this.text.length) {
308-
ch = this.text.charAt(peekIndex);
309-
if (ch === '(') {
310-
methodName = ident.substr(lastDot - start + 1);
311-
ident = ident.substr(0, lastDot - start);
312-
this.index = peekIndex;
313-
break;
314-
}
315-
if (this.isWhitespace(ch)) {
316-
peekIndex++;
317-
} else {
318-
break;
319-
}
320-
}
321-
}
322-
323268
this.tokens.push({
324269
index: start,
325-
text: ident,
326-
fn: CONSTANTS[ident] || getterFn(ident, this.options, expression)
270+
text: this.text.slice(start, this.index),
271+
identifier: true
327272
});
328-
329-
if (methodName) {
330-
this.tokens.push({
331-
index: lastDot,
332-
text: '.'
333-
});
334-
this.tokens.push({
335-
index: lastDot + 1,
336-
text: methodName
337-
});
338-
}
339273
},
340274

341275
readString: function(quote) {
@@ -366,9 +300,8 @@ Lexer.prototype = {
366300
this.tokens.push({
367301
index: start,
368302
text: rawString,
369-
string: string,
370303
constant: true,
371-
fn: function() { return string; }
304+
value: string
372305
});
373306
return;
374307
} else {
@@ -429,16 +362,12 @@ Parser.prototype = {
429362
primary = this.arrayDeclaration();
430363
} else if (this.expect('{')) {
431364
primary = this.object();
365+
} else if (this.peek().identifier) {
366+
primary = this.identifier();
367+
} else if (this.peek().constant) {
368+
primary = this.constant();
432369
} else {
433-
var token = this.expect();
434-
primary = token.fn;
435-
if (!primary) {
436-
this.throwError('not a primary expression', token);
437-
}
438-
if (token.constant) {
439-
primary.constant = true;
440-
primary.literal = true;
441-
}
370+
this.throwError('not a primary expression', this.peek());
442371
}
443372

444373
var next, context;
@@ -472,8 +401,11 @@ Parser.prototype = {
472401
},
473402

474403
peek: function(e1, e2, e3, e4) {
475-
if (this.tokens.length > 0) {
476-
var token = this.tokens[0];
404+
return this.peekAhead(0, e1, e2, e3, e4);
405+
},
406+
peekAhead: function(i, e1, e2, e3, e4) {
407+
if (this.tokens.length > i) {
408+
var token = this.tokens[i];
477409
var t = token.text;
478410
if (t === e1 || t === e2 || t === e3 || t === e4 ||
479411
(!e1 && !e2 && !e3 && !e4)) {
@@ -493,12 +425,19 @@ Parser.prototype = {
493425
},
494426

495427
consume: function(e1){
496-
if (!this.expect(e1)) {
428+
if (this.tokens.length === 0) {
429+
throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text);
430+
}
431+
432+
var token = this.expect(e1);
433+
if (!token) {
497434
this.throwError('is unexpected, expecting [' + e1 + ']', this.peek());
498435
}
436+
return token;
499437
},
500438

501-
unaryFn: function(fn, right) {
439+
unaryFn: function(op, right) {
440+
var fn = OPERATORS[op];
502441
return extend(function $parseUnaryFn(self, locals) {
503442
return fn(self, locals, right);
504443
}, {
@@ -507,7 +446,8 @@ Parser.prototype = {
507446
});
508447
},
509448

510-
binaryFn: function(left, fn, right, isBranching) {
449+
binaryFn: function(left, op, right, isBranching) {
450+
var fn = OPERATORS[op];
511451
return extend(function $parseBinaryFn(self, locals) {
512452
return fn(self, locals, left, right);
513453
}, {
@@ -516,6 +456,28 @@ Parser.prototype = {
516456
});
517457
},
518458

459+
identifier: function() {
460+
var id = this.consume().text;
461+
462+
//Continue reading each `.identifier` unless it is a method invocation
463+
while (this.peekAhead(1).identifier && !this.peekAhead(2, '(') && this.expect('.')) {
464+
id += '.' + this.consume().text;
465+
}
466+
467+
return CONSTANTS[id] || getterFn(id, this.options, this.text);
468+
},
469+
470+
constant: function() {
471+
var value = this.consume().value;
472+
473+
return extend(function $parseConstant() {
474+
return value;
475+
}, {
476+
constant: true,
477+
literal: true
478+
});
479+
},
480+
519481
statements: function() {
520482
var statements = [];
521483
while (true) {
@@ -547,8 +509,7 @@ Parser.prototype = {
547509
},
548510

549511
filter: function(inputFn) {
550-
var token = this.expect();
551-
var fn = this.$filter(token.text);
512+
var fn = this.$filter(this.consume().text);
552513
var argsFn;
553514
var args;
554515

@@ -632,7 +593,7 @@ Parser.prototype = {
632593
var left = this.logicalAND();
633594
var token;
634595
while ((token = this.expect('||'))) {
635-
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
596+
left = this.binaryFn(left, token.text, this.logicalAND(), true);
636597
}
637598
return left;
638599
},
@@ -641,7 +602,7 @@ Parser.prototype = {
641602
var left = this.equality();
642603
var token;
643604
if ((token = this.expect('&&'))) {
644-
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
605+
left = this.binaryFn(left, token.text, this.logicalAND(), true);
645606
}
646607
return left;
647608
},
@@ -650,7 +611,7 @@ Parser.prototype = {
650611
var left = this.relational();
651612
var token;
652613
if ((token = this.expect('==','!=','===','!=='))) {
653-
left = this.binaryFn(left, token.fn, this.equality());
614+
left = this.binaryFn(left, token.text, this.equality());
654615
}
655616
return left;
656617
},
@@ -659,7 +620,7 @@ Parser.prototype = {
659620
var left = this.additive();
660621
var token;
661622
if ((token = this.expect('<', '>', '<=', '>='))) {
662-
left = this.binaryFn(left, token.fn, this.relational());
623+
left = this.binaryFn(left, token.text, this.relational());
663624
}
664625
return left;
665626
},
@@ -668,7 +629,7 @@ Parser.prototype = {
668629
var left = this.multiplicative();
669630
var token;
670631
while ((token = this.expect('+','-'))) {
671-
left = this.binaryFn(left, token.fn, this.multiplicative());
632+
left = this.binaryFn(left, token.text, this.multiplicative());
672633
}
673634
return left;
674635
},
@@ -677,7 +638,7 @@ Parser.prototype = {
677638
var left = this.unary();
678639
var token;
679640
while ((token = this.expect('*','/','%'))) {
680-
left = this.binaryFn(left, token.fn, this.unary());
641+
left = this.binaryFn(left, token.text, this.unary());
681642
}
682643
return left;
683644
},
@@ -687,17 +648,17 @@ Parser.prototype = {
687648
if (this.expect('+')) {
688649
return this.primary();
689650
} else if ((token = this.expect('-'))) {
690-
return this.binaryFn(Parser.ZERO, token.fn, this.unary());
651+
return this.binaryFn(Parser.ZERO, token.text, this.unary());
691652
} else if ((token = this.expect('!'))) {
692-
return this.unaryFn(token.fn, this.unary());
653+
return this.unaryFn(token.text, this.unary());
693654
} else {
694655
return this.primary();
695656
}
696657
},
697658

698659
fieldAccess: function(object) {
699660
var expression = this.text;
700-
var field = this.expect().text;
661+
var field = this.consume().text;
701662
var getter = getterFn(field, this.options, expression);
702663

703664
return extend(function $parseFieldAccess(scope, locals, self) {
@@ -782,8 +743,7 @@ Parser.prototype = {
782743
// Support trailing commas per ES5.1.
783744
break;
784745
}
785-
var elementFn = this.expression();
786-
elementFns.push(elementFn);
746+
elementFns.push(this.expression());
787747
} while (this.expect(','));
788748
}
789749
this.consume(']');
@@ -809,11 +769,10 @@ Parser.prototype = {
809769
// Support trailing commas per ES5.1.
810770
break;
811771
}
812-
var token = this.expect();
813-
keys.push(token.string || token.text);
772+
var token = this.consume();
773+
keys.push(('value' in token) ? token.value : token.text);
814774
this.consume(':');
815-
var value = this.expression();
816-
valueFns.push(value);
775+
valueFns.push(this.expression());
817776
} while (this.expect(','));
818777
}
819778
this.consume('}');

0 commit comments

Comments
 (0)