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); }