X Tutup
Skip to content

Commit 7d32879

Browse files
committed
feat(Parser): support if statements in actions
fixes #2022
1 parent d64cc8d commit 7d32879

File tree

7 files changed

+146
-9
lines changed

7 files changed

+146
-9
lines changed

modules/angular2/src/change_detection/parser/ast.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ export class Conditional extends AST {
6060
visit(visitor: AstVisitor) { return visitor.visitConditional(this); }
6161
}
6262

63+
export class If extends AST {
64+
constructor(public condition: AST, public trueExp: AST, public falseExp?: AST) { super(); }
65+
66+
eval(context, locals) {
67+
if (this.condition.eval(context, locals)) {
68+
this.trueExp.eval(context, locals);
69+
} else if (isPresent(this.falseExp)) {
70+
this.falseExp.eval(context, locals);
71+
}
72+
}
73+
74+
visit(visitor: AstVisitor) { return visitor.visitIf(this); }
75+
}
76+
6377
export class AccessMember extends AST {
6478
constructor(public receiver: AST, public name: string, public getter: Function,
6579
public setter: Function) {
@@ -321,6 +335,7 @@ export interface AstVisitor {
321335
visitBinary(ast: Binary): any;
322336
visitChain(ast: Chain): any;
323337
visitConditional(ast: Conditional): any;
338+
visitIf(ast: If): any;
324339
visitPipe(ast: Pipe): any;
325340
visitFunctionCall(ast: FunctionCall): any;
326341
visitImplicitReceiver(ast: ImplicitReceiver): any;
@@ -398,6 +413,8 @@ export class AstTransformer implements AstVisitor {
398413
visitChain(ast: Chain) { throw new BaseException('Not implemented'); }
399414

400415
visitAssignment(ast: Assignment) { throw new BaseException('Not implemented'); }
416+
417+
visitIf(ast: If) { throw new BaseException('Not implemented'); }
401418
}
402419

403420
var _evalListCache = [

modules/angular2/src/change_detection/parser/lexer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ export class Token {
5959

6060
isKeywordTrue(): boolean { return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "true"); }
6161

62+
isKeywordIf(): boolean { return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "if"); }
63+
64+
isKeywordElse(): boolean { return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "else"); }
65+
6266
isKeywordFalse(): boolean {
6367
return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "false");
6468
}
@@ -463,4 +467,5 @@ var OPERATORS = SetWrapper.createFromList([
463467
]);
464468

465469

466-
var KEYWORDS = SetWrapper.createFromList(['var', 'null', 'undefined', 'true', 'false']);
470+
var KEYWORDS =
471+
SetWrapper.createFromList(['var', 'null', 'undefined', 'true', 'false', 'if', 'else']);

modules/angular2/src/change_detection/parser/parser.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
Binary,
3434
PrefixNot,
3535
Conditional,
36+
If,
3637
Pipe,
3738
Assignment,
3839
Chain,
@@ -412,6 +413,19 @@ class _ParseAST {
412413
this.advance();
413414
return new LiteralPrimitive(false);
414415

416+
} else if (this.parseAction && this.next.isKeywordIf()) {
417+
this.advance();
418+
this.expectCharacter($LPAREN);
419+
let condition = this.parseExpression();
420+
this.expectCharacter($RPAREN);
421+
let ifExp = this.parseExpressionOrBlock();
422+
let elseExp;
423+
if (this.next.isKeywordElse()) {
424+
this.advance();
425+
elseExp = this.parseExpressionOrBlock();
426+
}
427+
return new If(condition, ifExp, elseExp)
428+
415429
} else if (this.optionalCharacter($LBRACKET)) {
416430
var elements = this.parseExpressionList($RBRACKET);
417431
this.expectCharacter($RBRACKET);
@@ -494,6 +508,37 @@ class _ParseAST {
494508
return positionals;
495509
}
496510

511+
parseExpressionOrBlock(): AST {
512+
if (this.optionalCharacter($LBRACE)) {
513+
let block = this.parseBlockContent();
514+
this.expectCharacter($RBRACE);
515+
return block;
516+
}
517+
518+
return this.parseExpression();
519+
}
520+
521+
parseBlockContent(): AST {
522+
if (!this.parseAction) {
523+
this.error("Binding expression cannot contain chained expression");
524+
}
525+
var exprs = [];
526+
while (this.index < this.tokens.length && !this.next.isCharacter($RBRACE)) {
527+
var expr = this.parseExpression();
528+
ListWrapper.push(exprs, expr);
529+
530+
if (this.optionalCharacter($SEMICOLON)) {
531+
while (this.optionalCharacter($SEMICOLON)) {
532+
} // read all semicolons
533+
}
534+
}
535+
if (exprs.length == 0) return new EmptyExpr();
536+
if (exprs.length == 1) return exprs[0];
537+
538+
return new Chain(exprs);
539+
}
540+
541+
497542
/**
498543
* An identifier, a keyword, a string with an optional `-` inbetween.
499544
*/

modules/angular2/src/change_detection/proto_change_detector.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Binary,
1111
Chain,
1212
Conditional,
13+
If,
1314
Pipe,
1415
FunctionCall,
1516
ImplicitReceiver,
@@ -201,6 +202,8 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
201202

202203
visitChain(ast: Chain) { throw new BaseException('Not supported'); }
203204

205+
visitIf(ast: If) { throw new BaseException('Not supported'); }
206+
204207
_visitAll(asts: List<any>) {
205208
var res = ListWrapper.createFixedSize(asts.length);
206209
for (var i = 0; i < asts.length; ++i) {

modules/angular2/test/change_detection/parser/parser_spec.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ export function main() {
4343

4444
function emptyLocals() { return new Locals(null, MapWrapper.create()); }
4545

46-
function expectEval(text, passedInContext = null, passedInLocals = null) {
46+
function evalAction(text, passedInContext = null, passedInLocals = null) {
4747
var c = isBlank(passedInContext) ? td() : passedInContext;
4848
var l = isBlank(passedInLocals) ? emptyLocals() : passedInLocals;
49-
return expect(parseAction(text).eval(c, l));
49+
return parseAction(text).eval(c, l);
50+
}
51+
52+
function expectEval(text, passedInContext = null, passedInLocals = null) {
53+
return expect(evalAction(text, passedInContext, passedInLocals));
5054
}
5155

5256
function expectEvalError(text, passedInContext = null, passedInLocals = null) {
@@ -280,6 +284,28 @@ export function main() {
280284
});
281285
});
282286

287+
describe("if", () => {
288+
it('should parse if statements', () => {
289+
290+
var fixtures = [
291+
['if (true) a = 0', 0, null],
292+
['if (false) a = 0', null, null],
293+
['if (a == null) b = 0', null, 0],
294+
['if (true) { a = 0; b = 0 }', 0, 0],
295+
['if (true) { a = 0; b = 0 } else { a = 1; b = 1; }', 0, 0],
296+
['if (false) { a = 0; b = 0 } else { a = 1; b = 1; }', 1, 1],
297+
['if (false) { } else { a = 1; b = 1; }', 1, 1],
298+
];
299+
300+
fixtures.forEach(fix => {
301+
var testData = td(null, null);
302+
evalAction(fix[0], testData);
303+
expect(testData.a).toEqual(fix[1]);
304+
expect(testData.b).toEqual(fix[2]);
305+
});
306+
});
307+
});
308+
283309
describe("assignment", () => {
284310
it("should support field assignments", () => {
285311
var context = td();

modules/angular2/test/change_detection/parser/unparser.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
Binary,
77
Chain,
88
Conditional,
9+
EmptyExpr,
10+
If,
911
Pipe,
1012
FunctionCall,
1113
ImplicitReceiver,
@@ -21,7 +23,7 @@ import {
2123
} from 'angular2/src/change_detection/parser/ast';
2224

2325

24-
import {StringWrapper, RegExpWrapper} from 'angular2/src/facade/lang';
26+
import {StringWrapper, RegExpWrapper, isPresent} from 'angular2/src/facade/lang';
2527

2628
var quoteRegExp = RegExpWrapper.create('"');
2729

@@ -53,10 +55,11 @@ export class Unparser implements AstVisitor {
5355
}
5456

5557
visitChain(ast: Chain) {
56-
ast.expressions.forEach(expression => {
57-
this._visit(expression);
58-
this._expression += ';'
59-
});
58+
var len = ast.expressions.length;
59+
for (let i = 0; i < len; i++) {
60+
this._visit(ast.expressions[i]);
61+
this._expression += i == len - 1 ? ';' : '; ';
62+
}
6063
}
6164

6265
visitConditional(ast: Conditional) {
@@ -67,6 +70,17 @@ export class Unparser implements AstVisitor {
6770
this._visit(ast.falseExp);
6871
}
6972

73+
visitIf(ast: If) {
74+
this._expression += 'if (';
75+
this._visit(ast.condition);
76+
this._expression += ') ';
77+
this._visitExpOrBlock(ast.trueExp);
78+
if (isPresent(ast.falseExp)) {
79+
this._expression += ' else ';
80+
this._visitExpOrBlock(ast.falseExp);
81+
}
82+
}
83+
7084
visitPipe(ast: Pipe) {
7185
this._expression += '(';
7286
this._visit(ast.exp);
@@ -179,4 +193,11 @@ export class Unparser implements AstVisitor {
179193
}
180194

181195
private _visit(ast: AST) { ast.visit(this); }
196+
197+
private _visitExpOrBlock(ast: AST) {
198+
var isBlock = ast instanceof Chain || ast instanceof EmptyExpr;
199+
if (isBlock) this._expression += '{ ';
200+
this._visit(ast);
201+
if (isBlock) this._expression += ' }';
202+
}
182203
}

modules/angular2/test/change_detection/parser/unparser_spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
Binary,
99
Chain,
1010
Conditional,
11+
EmptyExpr,
12+
If,
1113
Pipe,
1214
ImplicitReceiver,
1315
Interpolation,
@@ -59,7 +61,7 @@ export function main() {
5961

6062
it('should support Binary', () => { check('a && b', Binary); });
6163

62-
it('should support Chain', () => { check('a;b;', Chain); });
64+
it('should support Chain', () => { check('a; b;', Chain); });
6365

6466
it('should support Conditional', () => { check('a ? b : c', Conditional); });
6567

@@ -93,6 +95,17 @@ export function main() {
9395

9496
it('should support SafeMethodCall', () => { check('a?.b(c, d)', SafeMethodCall); });
9597

98+
it('should support if statements', () => {
99+
var ifs = [
100+
'if (true) a()',
101+
'if (true) a() else b()',
102+
'if (a()) { b = 1; c = 2; }',
103+
'if (a()) b = 1 else { c = 2; d = e(); }'
104+
];
105+
106+
ifs.forEach(ifStmt => check(ifStmt, If));
107+
});
108+
96109
it('should support complex expression', () => {
97110
var originalExp = 'a + 3 * fn([(c + d | e).f], {a: 3})[g].h && i';
98111
var ast = parseBinding(originalExp).ast;
@@ -108,5 +121,12 @@ export function main() {
108121
expect(ast).toBeAnInstanceOf(Interpolation);
109122
expect(unparser.unparse(ast)).toEqual('a {{ b }} c');
110123
});
124+
125+
it('should support EmptyExpr', () => {
126+
var ast = parser.parseAction('if (true) { }', null).ast;
127+
expect(ast).toBeAnInstanceOf(If);
128+
expect((<If>ast).trueExp).toBeAnInstanceOf(EmptyExpr);
129+
expect(unparser.unparse(ast)).toEqual('if (true) { }');
130+
});
111131
});
112132
}

0 commit comments

Comments
 (0)
X Tutup