Skip to content
Permalink
Browse files

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).
  • Loading branch information...
zachsnow authored and petebacondarwin committed Apr 23, 2013
1 parent cefbcd4 commit 6798fec4390a72b7943a49505f8a245b6016c84b
Showing with 83 additions and 3 deletions.
  1. +29 −3 src/ng/parse.js
  2. +54 −0 test/ng/parseSpec.js
@@ -58,7 +58,7 @@ function lex(text, csp){
(token=tokens[tokens.length-1])) {
token.json = token.text.indexOf('.') == -1;
}
} else if (is('(){}[].,;:')) {
} else if (is('(){}[].,;:?')) {
tokens.push({
index:index,
text:ch,
@@ -359,6 +359,14 @@ function parser(text, json, $filter, csp){
});
}

function ternaryFn(left, middle, right){
return extend(function(self, locals){
return left(self, locals) ? middle(self, locals) : right(self, locals);
}, {
constant: left.constant && middle.constant && right.constant
});
}

function binaryFn(left, fn, right) {
return extend(function(self, locals) {
return fn(self, locals, left, right);
@@ -429,15 +437,15 @@ function parser(text, json, $filter, csp){
}

function _assignment() {
var left = logicalOR();
var left = ternary();
var right;
var token;
if ((token = expect('='))) {
if (!left.assign) {
throwError("implies assignment but [" +
text.substring(0, token.index) + "] can not be assigned to", token);
}
right = logicalOR();
right = ternary();
return function(scope, locals){
return left.assign(scope, right(scope, locals), locals);
};
@@ -446,6 +454,24 @@ function parser(text, json, $filter, csp){
}
}

function ternary() {
var left = logicalOR();
var middle;
var token;
if((token = expect('?'))){
middle = ternary();
if((token = expect(':'))){
return ternaryFn(left, middle, ternary());
}
else {
throwError('expected :', token);
}
}
else {
return left;
}
}

function logicalOR() {
var left = logicalAND();
var token;
@@ -103,6 +103,14 @@ describe('parser', function() {
expect(tokens[7].text).toEqual('===');
expect(tokens[8].text).toEqual('!==');
});

it('should tokenize logical and ternary', function() {
var tokens = lex("&& || ? :");
expect(tokens[0].text).toEqual('&&');
expect(tokens[1].text).toEqual('||');
expect(tokens[2].text).toEqual('?');
expect(tokens[3].text).toEqual(':');
});

it('should tokenize statements', function() {
var tokens = lex("a;b;");
@@ -220,6 +228,52 @@ describe('parser', function() {
expect(scope.$eval("0||2")).toEqual(0||2);
expect(scope.$eval("0||1&&2")).toEqual(0||1&&2);
});

it('should parse ternary', function(){
var f = scope.f = function(){ return true; };
var g = scope.g = function(){ return false; };
var h = scope.h = function(){ return 'asd'; };
var i = scope.i = function(){ return 123; };
var id = scope.id = function(x){ return x; };

// Simple.
expect(scope.$eval('0?0:2')).toEqual(0?0:2);
expect(scope.$eval('1?0:2')).toEqual(1?0:2);

// Nested on the left.
expect(scope.$eval('0?0?0:0:2')).toEqual(0?0?0:0:2);
expect(scope.$eval('1?0?0:0:2')).toEqual(1?0?0:0:2);
expect(scope.$eval('0?1?0:0:2')).toEqual(0?1?0:0:2);
expect(scope.$eval('0?0?1:0:2')).toEqual(0?0?1:0:2);
expect(scope.$eval('0?0?0:2:3')).toEqual(0?0?0:2:3);
expect(scope.$eval('1?1?0:0:2')).toEqual(1?1?0:0:2);
expect(scope.$eval('1?1?1:0:2')).toEqual(1?1?1:0:2);
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);
expect(scope.$eval('1?1?1:2:3')).toEqual(1?1?1:2:3);

// Nested on the right.
expect(scope.$eval('0?0:0?0:2')).toEqual(0?0:0?0:2);
expect(scope.$eval('1?0:0?0:2')).toEqual(1?0:0?0:2);
expect(scope.$eval('0?1:0?0:2')).toEqual(0?1:0?0:2);
expect(scope.$eval('0?0:1?0:2')).toEqual(0?0:1?0:2);
expect(scope.$eval('0?0:0?2:3')).toEqual(0?0:0?2:3);
expect(scope.$eval('1?1:0?0:2')).toEqual(1?1:0?0:2);
expect(scope.$eval('1?1:1?0:2')).toEqual(1?1:1?0:2);
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);
expect(scope.$eval('1?1:1?2:3')).toEqual(1?1:1?2:3);

// Precedence with respect to logical operators.
expect(scope.$eval('0&&1?0:1')).toEqual(0&&1?0:1);
expect(scope.$eval('0&&1?0:1')).toEqual((0&&1)?0:1);
expect(scope.$eval('1||0?0:0')).toEqual(1||0?0:0);
expect(scope.$eval('1||0?0:0')).toEqual((1||0)?0:0);

// Function calls.
expect(scope.$eval('f() ? h() : i()')).toEqual(f() ? h() : i());
expect(scope.$eval('g() ? h() : i()')).toEqual(g() ? h() : i());
expect(scope.$eval('f() ? h() : i()')).toEqual(f() ? h() : i());
expect(scope.$eval('id(g() ? h() : i())')).toEqual(id(g() ? h() : i()));
});

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

0 comments on commit 6798fec

Please sign in to comment.
You can’t perform that action at this time.