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

Commit 6798fec

Browse files
zachsnowpetebacondarwin
authored andcommitted
feat($parse): add support for ternary operators to parser
Add '?' token to lexer, add ternary rule to parser at (hopefully) proper precedence and associativity (based on https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Operator_Precedence). Since (exp1 && exp2 || exp3) is supported by the parser, and (exp1 ? exp2 : exp3) works the same way, it seems reasonable to add this minor form of control to templates (see #719).
1 parent cefbcd4 commit 6798fec

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

src/ng/parse.js

+29-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function lex(text, csp){
5858
(token=tokens[tokens.length-1])) {
5959
token.json = token.text.indexOf('.') == -1;
6060
}
61-
} else if (is('(){}[].,;:')) {
61+
} else if (is('(){}[].,;:?')) {
6262
tokens.push({
6363
index:index,
6464
text:ch,
@@ -359,6 +359,14 @@ function parser(text, json, $filter, csp){
359359
});
360360
}
361361

362+
function ternaryFn(left, middle, right){
363+
return extend(function(self, locals){
364+
return left(self, locals) ? middle(self, locals) : right(self, locals);
365+
}, {
366+
constant: left.constant && middle.constant && right.constant
367+
});
368+
}
369+
362370
function binaryFn(left, fn, right) {
363371
return extend(function(self, locals) {
364372
return fn(self, locals, left, right);
@@ -429,15 +437,15 @@ function parser(text, json, $filter, csp){
429437
}
430438

431439
function _assignment() {
432-
var left = logicalOR();
440+
var left = ternary();
433441
var right;
434442
var token;
435443
if ((token = expect('='))) {
436444
if (!left.assign) {
437445
throwError("implies assignment but [" +
438446
text.substring(0, token.index) + "] can not be assigned to", token);
439447
}
440-
right = logicalOR();
448+
right = ternary();
441449
return function(scope, locals){
442450
return left.assign(scope, right(scope, locals), locals);
443451
};
@@ -446,6 +454,24 @@ function parser(text, json, $filter, csp){
446454
}
447455
}
448456

457+
function ternary() {
458+
var left = logicalOR();
459+
var middle;
460+
var token;
461+
if((token = expect('?'))){
462+
middle = ternary();
463+
if((token = expect(':'))){
464+
return ternaryFn(left, middle, ternary());
465+
}
466+
else {
467+
throwError('expected :', token);
468+
}
469+
}
470+
else {
471+
return left;
472+
}
473+
}
474+
449475
function logicalOR() {
450476
var left = logicalAND();
451477
var token;

test/ng/parseSpec.js

+54
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ describe('parser', function() {
103103
expect(tokens[7].text).toEqual('===');
104104
expect(tokens[8].text).toEqual('!==');
105105
});
106+
107+
it('should tokenize logical and ternary', function() {
108+
var tokens = lex("&& || ? :");
109+
expect(tokens[0].text).toEqual('&&');
110+
expect(tokens[1].text).toEqual('||');
111+
expect(tokens[2].text).toEqual('?');
112+
expect(tokens[3].text).toEqual(':');
113+
});
106114

107115
it('should tokenize statements', function() {
108116
var tokens = lex("a;b;");
@@ -220,6 +228,52 @@ describe('parser', function() {
220228
expect(scope.$eval("0||2")).toEqual(0||2);
221229
expect(scope.$eval("0||1&&2")).toEqual(0||1&&2);
222230
});
231+
232+
it('should parse ternary', function(){
233+
var f = scope.f = function(){ return true; };
234+
var g = scope.g = function(){ return false; };
235+
var h = scope.h = function(){ return 'asd'; };
236+
var i = scope.i = function(){ return 123; };
237+
var id = scope.id = function(x){ return x; };
238+
239+
// Simple.
240+
expect(scope.$eval('0?0:2')).toEqual(0?0:2);
241+
expect(scope.$eval('1?0:2')).toEqual(1?0:2);
242+
243+
// Nested on the left.
244+
expect(scope.$eval('0?0?0:0:2')).toEqual(0?0?0:0:2);
245+
expect(scope.$eval('1?0?0:0:2')).toEqual(1?0?0:0:2);
246+
expect(scope.$eval('0?1?0:0:2')).toEqual(0?1?0:0:2);
247+
expect(scope.$eval('0?0?1:0:2')).toEqual(0?0?1:0:2);
248+
expect(scope.$eval('0?0?0:2:3')).toEqual(0?0?0:2:3);
249+
expect(scope.$eval('1?1?0:0:2')).toEqual(1?1?0:0:2);
250+
expect(scope.$eval('1?1?1:0:2')).toEqual(1?1?1:0:2);
251+
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
252+
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
253+
254+
// Nested on the right.
255+
expect(scope.$eval('0?0:0?0:2')).toEqual(0?0:0?0:2);
256+
expect(scope.$eval('1?0:0?0:2')).toEqual(1?0:0?0:2);
257+
expect(scope.$eval('0?1:0?0:2')).toEqual(0?1:0?0:2);
258+
expect(scope.$eval('0?0:1?0:2')).toEqual(0?0:1?0:2);
259+
expect(scope.$eval('0?0:0?2:3')).toEqual(0?0:0?2:3);
260+
expect(scope.$eval('1?1:0?0:2')).toEqual(1?1:0?0:2);
261+
expect(scope.$eval('1?1:1?0:2')).toEqual(1?1:1?0:2);
262+
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
263+
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
264+
265+
// Precedence with respect to logical operators.
266+
expect(scope.$eval('0&&1?0:1')).toEqual(0&&1?0:1);
267+
expect(scope.$eval('0&&1?0:1')).toEqual((0&&1)?0:1);
268+
expect(scope.$eval('1||0?0:0')).toEqual(1||0?0:0);
269+
expect(scope.$eval('1||0?0:0')).toEqual((1||0)?0:0);
270+
271+
// Function calls.
272+
expect(scope.$eval('f() ? h() : i()')).toEqual(f() ? h() : i());
273+
expect(scope.$eval('g() ? h() : i()')).toEqual(g() ? h() : i());
274+
expect(scope.$eval('f() ? h() : i()')).toEqual(f() ? h() : i());
275+
expect(scope.$eval('id(g() ? h() : i())')).toEqual(id(g() ? h() : i()));
276+
});
223277

224278
it('should parse string', function() {
225279
expect(scope.$eval("'a' + 'b c'")).toEqual("ab c");

0 commit comments

Comments
 (0)