diff --git a/lib/cfg/constructor.js b/lib/cfg/constructor.js index ac78055..00ae781 100644 --- a/lib/cfg/constructor.js +++ b/lib/cfg/constructor.js @@ -83,6 +83,8 @@ Constructor.prototype.visit = function visit(ast) { return this.visitUpdate(ast); else if (ast.type === 'SequenceExpression') return this.visitSequence(ast); + else if (ast.type === 'IfStatement' || ast.type === 'ConditionalExpression') + return this.visitConditional(ast); else assert(false, `Unknown AST node type: "${ast.type}"`); }; @@ -238,6 +240,39 @@ Constructor.prototype.visitSequence = function visitSequence(ast) { return res; }; +Constructor.prototype.visitConditional = function visitConditional(ast) { + const test = this.visit(ast.test); + + let start = test.block; + + if (start.predecessors.length > 1) { + this.pipeline.addControl('jump'); + start = this.pipeline.jumpFrom(start); + } + + const branch = this.pipeline.addControl('if', [ test ]); + + const left = this.pipeline.jumpFrom(start); + const consequent = this.visit(ast.consequent); + this.pipeline.addControl('jump'); + + if (ast.alternate) { + const right = this.pipeline.jumpFrom(start); + const alternate = this.visit(ast.alternate); + this.pipeline.addControl('jump'); + + this.pipeline.merge(left, right); + + if (ast.type === 'ConditionalExpression') { + return this.pipeline.addControl('phi', [ consequent, alternate ]); + } + } else { + this.pipeline.merge(left, start); + } + + return null; +}; + // // Routines // diff --git a/test/constructor-test.js b/test/constructor-test.js index 06a1d9d..806a7f1 100644 --- a/test/constructor-test.js +++ b/test/constructor-test.js @@ -439,4 +439,127 @@ describe('CFG.js/Constructor', () => { }`); }); }); + + describe('conditional', () => { + it('statement should be supported', () => { + test(() => { + if (true) { + 'x'; + } else { + 'y'; + } + }, `pipeline { + b0 { + i0 = literal true + i1 = if ^b0, i0 + } + b0 -> b1, b2 + b1 { + i2 = literal "x" + i3 = jump ^b1 + } + b1 -> b3 + b2 { + i4 = literal "y" + i5 = jump ^b2 + } + b2 -> b3 + b3 { + } + }`); + }); + + it('expression should evaluate', () => { + test(() => { + true ? 'x' : 'y'; + }, `pipeline { + b0 { + i0 = literal true + i1 = if ^b0, i0 + } + b0 -> b1, b2 + b1 { + i2 = literal "x" + i3 = jump ^b1 + } + b1 -> b3 + b2 { + i4 = literal "y" + i5 = jump ^b2 + } + b2 -> b3 + b3 { + i6 = phi ^b3, i2, i4 + } + }`); + }); + + it('statement without else should be supported', () => { + test(() => { + 'a'; + if (true) 'b'; + 'c'; + }, `pipeline { + b0 { + i0 = literal "a" + i1 = literal true + i2 = if ^b0, i1 + } + b0 -> b1, b2 + b1 { + i3 = literal "b" + i4 = jump ^b1 + } + b1 -> b2 + b2 { + i5 = literal "c" + } + }`) + }); + + test(() => { + if (true ? 1 : 2) { + 'x'; + } else { + 'y'; + } + }, `pipeline { + b0 { + i0 = literal true + i1 = if ^b0, i0 + } + b0 -> b1, b2 + b1 { + i2 = literal 1 + i3 = jump ^b1 + } + b1 -> b3 + b2 { + i4 = literal 2 + i5 = jump ^b2 + } + b2 -> b3 + b3 { + i6 = phi ^b3, i2, i4 + i7 = jump ^i6 + } + b3 -> b4 + b4 { + i8 = if ^b4, i6 + } + b4 -> b5, b6 + b5 { + i9 = literal "x" + i10 = jump ^b5 + } + b5 -> b7 + b6 { + i11 = literal "y" + i12 = jump ^b6 + } + b6 -> b7 + b7 { + } + }`); + }); });