Skip to content

Commit 04cac66

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 04cac66

File tree

2 files changed

+129
-145
lines changed

2 files changed

+129
-145
lines changed

src/ng/parse.js

+78-121
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: token});
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,9 +425,15 @@ 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

501439
unaryFn: function(fn, right) {
@@ -516,6 +454,28 @@ Parser.prototype = {
516454
});
517455
},
518456

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

549509
filter: function(inputFn) {
550-
var token = this.expect();
551-
var fn = this.$filter(token.text);
510+
var fn = this.$filter(this.consume().text);
552511
var argsFn;
553512
var args;
554513

@@ -632,7 +591,7 @@ Parser.prototype = {
632591
var left = this.logicalAND();
633592
var token;
634593
while ((token = this.expect('||'))) {
635-
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
594+
left = this.binaryFn(left, token.operator, this.logicalAND(), true);
636595
}
637596
return left;
638597
},
@@ -641,7 +600,7 @@ Parser.prototype = {
641600
var left = this.equality();
642601
var token;
643602
if ((token = this.expect('&&'))) {
644-
left = this.binaryFn(left, token.fn, this.logicalAND(), true);
603+
left = this.binaryFn(left, token.operator, this.logicalAND(), true);
645604
}
646605
return left;
647606
},
@@ -650,7 +609,7 @@ Parser.prototype = {
650609
var left = this.relational();
651610
var token;
652611
if ((token = this.expect('==','!=','===','!=='))) {
653-
left = this.binaryFn(left, token.fn, this.equality());
612+
left = this.binaryFn(left, token.operator, this.equality());
654613
}
655614
return left;
656615
},
@@ -659,7 +618,7 @@ Parser.prototype = {
659618
var left = this.additive();
660619
var token;
661620
if ((token = this.expect('<', '>', '<=', '>='))) {
662-
left = this.binaryFn(left, token.fn, this.relational());
621+
left = this.binaryFn(left, token.operator, this.relational());
663622
}
664623
return left;
665624
},
@@ -668,7 +627,7 @@ Parser.prototype = {
668627
var left = this.multiplicative();
669628
var token;
670629
while ((token = this.expect('+','-'))) {
671-
left = this.binaryFn(left, token.fn, this.multiplicative());
630+
left = this.binaryFn(left, token.operator, this.multiplicative());
672631
}
673632
return left;
674633
},
@@ -677,7 +636,7 @@ Parser.prototype = {
677636
var left = this.unary();
678637
var token;
679638
while ((token = this.expect('*','/','%'))) {
680-
left = this.binaryFn(left, token.fn, this.unary());
639+
left = this.binaryFn(left, token.operator, this.unary());
681640
}
682641
return left;
683642
},
@@ -687,17 +646,17 @@ Parser.prototype = {
687646
if (this.expect('+')) {
688647
return this.primary();
689648
} else if ((token = this.expect('-'))) {
690-
return this.binaryFn(Parser.ZERO, token.fn, this.unary());
649+
return this.binaryFn(Parser.ZERO, token.operator, this.unary());
691650
} else if ((token = this.expect('!'))) {
692-
return this.unaryFn(token.fn, this.unary());
651+
return this.unaryFn(token.operator, this.unary());
693652
} else {
694653
return this.primary();
695654
}
696655
},
697656

698657
fieldAccess: function(object) {
699658
var expression = this.text;
700-
var field = this.expect().text;
659+
var field = this.consume().text;
701660
var getter = getterFn(field, this.options, expression);
702661

703662
return extend(function $parseFieldAccess(scope, locals, self) {
@@ -782,8 +741,7 @@ Parser.prototype = {
782741
// Support trailing commas per ES5.1.
783742
break;
784743
}
785-
var elementFn = this.expression();
786-
elementFns.push(elementFn);
744+
elementFns.push(this.expression());
787745
} while (this.expect(','));
788746
}
789747
this.consume(']');
@@ -809,11 +767,10 @@ Parser.prototype = {
809767
// Support trailing commas per ES5.1.
810768
break;
811769
}
812-
var token = this.expect();
813-
keys.push(token.string || token.text);
770+
var token = this.consume();
771+
keys.push(('value' in token) ? token.value : token.text);
814772
this.consume(':');
815-
var value = this.expression();
816-
valueFns.push(value);
773+
valueFns.push(this.expression());
817774
} while (this.expect(','));
818775
}
819776
this.consume('}');

0 commit comments

Comments
 (0)