public static JsBlockStatement RewriteAsyncMethod(JsBlockStatement block, Func<JsExpression, bool> isExpressionComplexEnoughForATemporaryVariable, Func<string> allocateTempVariable, Func<string> allocateStateVariable, Func<string> allocateLoopLabel, string stateMachineVariableName, string doFinallyBlocksVariableName, JsVariableDeclaration taskCompletionSource, Func<JsExpression, JsExpression> makeSetResult, Func<JsExpression, JsExpression> makeSetException, Func<JsExpression> getTask, Func<JsExpression, JsExpression, JsExpression> bindToContext) { var obj = new StateMachineRewriter(isExpressionComplexEnoughForATemporaryVariable, allocateTempVariable, allocateStateVariable, allocateLoopLabel); return obj.ProcessAsyncMethod(block, stateMachineVariableName, doFinallyBlocksVariableName, taskCompletionSource, makeSetResult, makeSetException, getTask, bindToContext); }
private JsBlockStatement ProcessAsyncMethod(JsBlockStatement statement, string stateMachineMethodName, string doFinallyBlocksVariableName, JsVariableDeclaration taskCompletionSource, Func<JsExpression, JsExpression> makeSetResult, Func<JsExpression, JsExpression> makeSetException, Func<JsExpression> getTask, Func<JsExpression, JsExpression, JsExpression> bindToContext) { _stateMachineMethodName = stateMachineMethodName; _doFinallyBlocksVariableName = doFinallyBlocksVariableName; _makeSetResult = taskCompletionSource != null ? makeSetResult : null; _needDoFinallyBlocksVariable = new HasAwaitInsideTryWithFinallyVisitor().Analyze(statement); var result = Process(statement, isIteratorBlock: false, isAsync: true); var hoistResult = VariableHoistingVisitor.Process(result); string catchVariable = _allocateTempVariable(); JsStatement body; if (taskCompletionSource != null && (statement.Statements.Count == 0 || IsNextStatementReachable(statement.Statements[statement.Statements.Count - 1]))) { // If we return the task, and if we risk falling out of the original method, we need to add a setResult call. body = JsStatement.Block(hoistResult.Item1.Statements.Concat(new JsStatement[] { makeSetResult(null) })); } else { body = hoistResult.Item1; } if (taskCompletionSource != null) body = JsStatement.Try(body, JsStatement.Catch(catchVariable, JsStatement.Block(makeSetException(JsExpression.Identifier(catchVariable)))), null); IEnumerable<JsVariableDeclaration> declarations = new[] { JsStatement.Declaration(_stateVariableName, JsExpression.Number(0)) }; if (taskCompletionSource != null) declarations = declarations.Concat(new[] { taskCompletionSource }); declarations = declarations.Concat(hoistResult.Item2.Select(v => JsStatement.Declaration(v, null))); if (_needDoFinallyBlocksVariable) body = JsStatement.Block(new[] { JsStatement.Var(_doFinallyBlocksVariableName, JsExpression.True) }.Concat(body is JsBlockStatement ? ((JsBlockStatement)body).Statements : (IEnumerable<JsStatement>)new[] { body })); var stateMachine = JsExpression.FunctionDefinition(new string[0], body); var boundStateMachine = UsesThisVisitor.Analyze(stateMachine.Body) ? bindToContext(stateMachine, JsExpression.This) : stateMachine; IEnumerable<JsStatement> stmts = new JsStatement[] { JsStatement.Var(declarations), JsStatement.Var(stateMachineMethodName, boundStateMachine), JsExpression.Invocation(JsExpression.Identifier(stateMachineMethodName)) }; if (taskCompletionSource != null) stmts = stmts.Concat(new[] { JsStatement.Return(getTask()) }); return JsStatement.Block(stmts); }