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

Commit

Permalink
feat($parse): add support for ternary operators to parser
Browse files Browse the repository at this point in the history
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 May 16, 2013
1 parent cefbcd4 commit 6798fec
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 3 deletions.
32 changes: 29 additions & 3 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
};
Expand All @@ -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;
Expand Down
54 changes: 54 additions & 0 deletions test/ng/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;");
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit 6798fec

Please sign in to comment.