public static InterestingConstruct Analyze(JsStatement statement)
        {
            var obj = new FindInterestingConstructsVisitor();

            obj.VisitStatement(statement, null);
            return(obj._result);
        }
 public static JsBlockStatement RewriteNormalMethod(JsBlockStatement block, Func <JsExpression, bool> isExpressionComplexEnoughForATemporaryVariable, Func <string> allocateTempVariable, Func <string> allocateStateVariable, Func <string> allocateLoopLabel)
 {
     if (FindInterestingConstructsVisitor.Analyze(block, InterestingConstruct.Label))
     {
         return(new StateMachineRewriter(isExpressionComplexEnoughForATemporaryVariable, allocateTempVariable, allocateStateVariable, allocateLoopLabel).Process(block));
     }
     else
     {
         return(block);
     }
 }
 public override JsStatement VisitTryStatement(JsTryStatement statement, object data)
 {
     if (statement.Finally != null)
     {
         _result |= FindInterestingConstructsVisitor.Analyze(statement.GuardedStatement, InterestingConstruct.Await);
         return(statement);
     }
     else
     {
         return(base.VisitTryStatement(statement, data));
     }
 }
        private bool HandleTryStatement(JsTryStatement stmt, StackEntry location, ImmutableStack <StackEntry> stack, ImmutableStack <Tuple <string, State> > breakStack, ImmutableStack <Tuple <string, State> > continueStack, State currentState, State returnState, IList <JsStatement> currentBlock, bool isFirstStatement)
        {
            if (_isIteratorBlock && (FindInterestingConstructsVisitor.Analyze(stmt.GuardedStatement, InterestingConstruct.YieldReturn) || (stmt.Finally != null && stmt.Catch == null && !currentState.FinallyStack.IsEmpty)))
            {
                if (stmt.Catch != null)
                {
                    throw new InvalidOperationException("Cannot yield return from try with catch");
                }
                string           handlerName = _allocateFinallyHandler();
                JsBlockStatement handler;
                if (FindInterestingConstructsVisitor.Analyze(stmt.Finally, InterestingConstruct.Label))
                {
                    var inner = ProcessInner(stmt.Finally, breakStack, continueStack, currentState.FinallyStack, currentState.StateValue);
                    handler = JsStatement.Block(new[]  { new JsSetNextStateStatement(inner.Item2) }.Concat(inner.Item1));
                    handler = new FinalizerRewriter(_stateVariableName, _labelStates).Process(handler);
                }
                else
                {
                    handler = stmt.Finally;
                }

                _finallyHandlers.Add(Tuple.Create(handlerName, handler));
                var stateAfter         = GetStateAfterStatement(location, stack, currentState.FinallyStack, returnState);
                var innerState         = CreateNewStateValue(currentState.FinallyStack, handlerName);
                var stateBeforeFinally = CreateNewStateValue(innerState.FinallyStack);
                currentBlock.Add(new JsSetNextStateStatement(innerState.StateValue));
                currentBlock.AddRange(Handle(ImmutableStack <StackEntry> .Empty.Push(new StackEntry(stmt.GuardedStatement, 0)), breakStack, continueStack, new State(currentState.LoopLabelName, currentState.StateValue, innerState.FinallyStack), stateBeforeFinally, false, false));

                Enqueue(ImmutableStack <StackEntry> .Empty.Push(new StackEntry(JsStatement.Block(JsStatement.BlockMerged(new JsStatement[0])), 0)), breakStack, continueStack, stateBeforeFinally, stateAfter.Item1);
                if (!stack.IsEmpty || location.Index < location.Block.Statements.Count - 1)
                {
                    Enqueue(PushFollowing(stack, location), breakStack, continueStack, stateAfter.Item1, returnState);
                }
                return(false);
            }
            else if (_isIteratorBlock && stmt.Finally != null && !currentState.FinallyStack.IsEmpty)
            {
                // This is necessary to special-case in order to ensure that the inner finally block is executed before all outer ones.
                return(HandleTryStatement(JsStatement.Try(JsStatement.Try(stmt.GuardedStatement, stmt.Catch, null), null, stmt.Finally), location, stack, breakStack, continueStack, currentState, returnState, currentBlock, isFirstStatement));
            }
            else
            {
                var rewriter = new NestedStatementFixer(breakStack, continueStack, currentState, _exitState.Value, _makeSetResult);
                JsBlockStatement guarded;
                var guardedConstructs = FindInterestingConstructsVisitor.Analyze(stmt.GuardedStatement);
                if ((guardedConstructs & (InterestingConstruct.Label | InterestingConstruct.Await)) != InterestingConstruct.None)
                {
                    if (!isFirstStatement)
                    {
                        var sv = CreateNewStateValue(currentState.FinallyStack);
                        Enqueue(stack.Push(location), breakStack, continueStack, sv, returnState);
                        currentBlock.Add(new JsGotoStateStatement(sv, currentState));
                        return(false);
                    }

                    var inner = ProcessInner(stmt.GuardedStatement, breakStack, continueStack, currentState.FinallyStack, currentState.StateValue);
                    guarded = JsStatement.Block(inner.Item1);
                    currentBlock.Add(new JsSetNextStateStatement(inner.Item2));
                }
                else
                {
                    guarded = rewriter.Process(stmt.GuardedStatement);
                }

                JsCatchClause @catch;
                if (stmt.Catch != null)
                {
                    if (FindInterestingConstructsVisitor.Analyze(stmt.Catch.Body, InterestingConstruct.Label))
                    {
                        var inner = ProcessInner(stmt.Catch.Body, breakStack, continueStack, currentState.FinallyStack, null);
                        @catch = JsStatement.Catch(stmt.Catch.Identifier, JsStatement.Block(new[] { new JsSetNextStateStatement(inner.Item2) }.Concat(inner.Item1)));
                    }
                    else
                    {
                        var body = rewriter.Process(stmt.Catch.Body);
                        @catch = ReferenceEquals(body, stmt.Catch.Body) ? stmt.Catch : JsStatement.Catch(stmt.Catch.Identifier, body);
                    }
                }
                else
                {
                    @catch = null;
                }

                JsBlockStatement @finally;
                if (stmt.Finally != null)
                {
                    if (FindInterestingConstructsVisitor.Analyze(stmt.Finally, InterestingConstruct.Label))
                    {
                        var inner = ProcessInner(stmt.Finally, breakStack, continueStack, currentState.FinallyStack, null);
                        @finally = JsStatement.Block(new[] { new JsSetNextStateStatement(inner.Item2) }.Concat(inner.Item1));
                    }
                    else
                    {
                        @finally = rewriter.Process(stmt.Finally);
                    }

                    if ((guardedConstructs & InterestingConstruct.Await) != InterestingConstruct.None)
                    {
                        // Wrap the finally block inside an 'if (doFinallyBlocks) {}'
                        @finally = JsStatement.Block(JsStatement.If(JsExpression.Identifier(_doFinallyBlocksVariableName), @finally, null));
                    }
                }
                else
                {
                    @finally = null;
                }

                if (currentBlock.Count > 0 && _childStates.ContainsKey(currentState.StateValue))
                {
                    var newBlock = JsStatement.If(JsExpression.Same(JsExpression.Identifier(_stateVariableName), JsExpression.Number(currentState.StateValue)), JsStatement.Block(currentBlock), null);
                    currentBlock.Clear();
                    currentBlock.Add(newBlock);
                }

                currentBlock.Add(JsStatement.Try(guarded, @catch, @finally));
                return(true);
            }
        }
        private List <JsStatement> Handle(ImmutableStack <StackEntry> stack, ImmutableStack <Tuple <string, State> > breakStack, ImmutableStack <Tuple <string, State> > continueStack, State currentState, State returnState, bool setIntermediateState, bool isRoot)
        {
            var currentBlock = new List <JsStatement>();

            if (setIntermediateState)
            {
                currentBlock.Add(new JsSetNextStateStatement(currentState.FinallyStack.IsEmpty ? -1 : currentState.FinallyStack.Peek().Item1));
            }

            bool isFirstStatement = isRoot;

            while (!stack.IsEmpty)
            {
                bool setIsFirstStatementFalse = true;
                var  tos = stack.Peek();
                stack = stack.Pop();

                var stmt = tos.Block.Statements[tos.Index];
                var lbl  = stmt as JsLabelledStatement;
                if (lbl != null)
                {
                    if (_processedStates.Contains(GetOrCreateStateForLabel(lbl.Label, currentState.FinallyStack)))
                    {
                        // First statement in the new block
                        stmt = lbl.Statement;
                    }
                    else
                    {
                        // A label that terminates the current block.
                        Enqueue(stack.Push(new StackEntry(tos.Block, tos.Index)), breakStack, continueStack, GetOrCreateStateForLabel(lbl.Label, currentState.FinallyStack), returnState);
                        if (currentBlock.Count == 0 || IsNextStatementReachable(currentBlock[currentBlock.Count - 1]))
                        {
                            currentBlock.Add(new JsGotoStateStatement(GetOrCreateStateForLabel(lbl.Label, currentState.FinallyStack), currentState));
                        }
                        return(currentBlock);
                    }
                }

                if (stmt is JsYieldStatement)
                {
                    var ystmt = (JsYieldStatement)stmt;
                    if (ystmt.Value != null)
                    {
                        if (!HandleYieldReturnStatement(ystmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock))
                        {
                            return(currentBlock);
                        }
                    }
                    else
                    {
                        currentBlock.AddRange(new NestedStatementFixer(breakStack, continueStack, currentState, _exitState.Value, _makeSetResult).Process(stmt));
                    }
                    stack = PushFollowing(stack, tos);
                }
                else if (stmt is JsAwaitStatement)
                {
                    if (!HandleAwaitStatement((JsAwaitStatement)stmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock))
                    {
                        return(currentBlock);
                    }
                    stack = PushFollowing(stack, tos);
                }
                else if (stmt is JsTryStatement)
                {
                    if (!HandleTryStatement((JsTryStatement)stmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock, isFirstStatement))
                    {
                        return(currentBlock);
                    }
                    stack = PushFollowing(stack, tos);
                }
                else if (FindInterestingConstructsVisitor.Analyze(stmt, InterestingConstruct.YieldReturn | InterestingConstruct.Label | InterestingConstruct.Await))
                {
                    if (stmt is JsBlockStatement)
                    {
                        stack = PushFollowing(stack, tos).Push(new StackEntry((JsBlockStatement)stmt, 0));
                        setIsFirstStatementFalse = false;
                    }
                    else
                    {
                        if (stmt is JsIfStatement)
                        {
                            if (!HandleIfStatement((JsIfStatement)stmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock))
                            {
                                return(currentBlock);
                            }
                        }
                        else if (stmt is JsDoWhileStatement)
                        {
                            if (!HandleDoWhileStatement((JsDoWhileStatement)stmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock, isFirstStatement))
                            {
                                return(currentBlock);
                            }
                        }
                        else if (stmt is JsWhileStatement)
                        {
                            if (!HandleWhileStatement((JsWhileStatement)stmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock, isFirstStatement))
                            {
                                return(currentBlock);
                            }
                        }
                        else if (stmt is JsForStatement)
                        {
                            if (!HandleForStatement((JsForStatement)stmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock, isFirstStatement))
                            {
                                return(currentBlock);
                            }
                        }
                        else if (stmt is JsSwitchStatement)
                        {
                            if (!HandleSwitchStatement((JsSwitchStatement)stmt, tos, stack, breakStack, continueStack, currentState, returnState, currentBlock))
                            {
                                return(currentBlock);
                            }
                        }
                        else
                        {
                            throw new NotSupportedException("Statement " + stmt + " cannot contain labels.");
                        }

                        stack = PushFollowing(stack, tos);
                    }
                }
                else
                {
                    currentBlock.AddRange(new NestedStatementFixer(breakStack, continueStack, currentState, _exitState.Value, _makeSetResult).Process(stmt));
                    stack = PushFollowing(stack, tos);
                }
                if (setIsFirstStatementFalse)
                {
                    isFirstStatement = false;
                }
            }
            if (currentBlock.Count == 0 || IsNextStatementReachable(currentBlock[currentBlock.Count - 1]))
            {
                currentBlock.Add(new JsGotoStateStatement(returnState, currentState));
            }

            return(currentBlock);
        }