private MSA.Expression/*!*/ TransformExceptionHandling(AstGenerator/*!*/ gen, ResultOperation resultOperation) {
Assert.NotNull(gen);
MSA.Expression exceptionThrownVariable = gen.CurrentScope.DefineHiddenVariable("#exception-thrown", typeof(bool));
MSA.Expression exceptionRethrowVariable = gen.CurrentScope.DefineHiddenVariable("#exception-rethrow", typeof(bool));
MSA.Expression retryingVariable = gen.CurrentScope.DefineHiddenVariable("#retrying", typeof(bool));
MSA.Expression oldExceptionVariable = gen.CurrentScope.DefineHiddenVariable("#old-exception", typeof(Exception));
MSA.ParameterExpression unwinder, exceptionVariable;
MSA.Expression transformedBody;
MSA.Expression transformedEnsure;
MSA.Expression transformedElse;
if (_ensureStatements != null) {
transformedEnsure = Ast.Block(
// ensure:
Ast.Assign(oldExceptionVariable, Methods.GetCurrentException.OpCall(gen.CurrentScopeVariable)),
gen.TransformStatements(_ensureStatements, ResultOperation.Ignore),
Methods.SetCurrentException.OpCall(gen.CurrentScopeVariable, oldExceptionVariable),
// rethrow:
AstUtils.IfThen(
Ast.AndAlso(
exceptionRethrowVariable,
Ast.NotEqual(oldExceptionVariable, AstUtils.Constant(null))
),
Ast.Throw(oldExceptionVariable)
)
);
} else {
// rethrow:
transformedEnsure = AstUtils.IfThen(
Ast.AndAlso(
exceptionRethrowVariable,
Ast.NotEqual(
Ast.Assign(oldExceptionVariable, Methods.GetCurrentException.OpCall(gen.CurrentScopeVariable)),
AstUtils.Constant(null, typeof(Exception)))
),
Ast.Throw(oldExceptionVariable)
);
}
if (_elseStatements != null) {
transformedElse = gen.TransformStatements(_elseStatements, resultOperation);
} else {
transformedElse = AstUtils.Empty();
}
// body should do return, but else-clause is present => we cannot do return from the guarded statements:
// (the value of the last expression in the body cannot be the last executed expression statement => we can ignore it):
transformedBody = gen.TransformStatements(_statements, (_elseStatements != null) ? ResultOperation.Ignore : resultOperation);
MSA.Expression enterRescue = null, leaveRescue = null;
var retryLabel = Ast.Label("retry");
// make rescue clause:
MSA.Expression transformedRescue;
if (_rescueClauses != null) {
// outer-most EH blocks sets and clears runtime flag RuntimeFlowControl.InTryRescue:
if (gen.CurrentRescue == null) {
enterRescue = Methods.EnterRescue.OpCall(gen.CurrentScopeVariable);
leaveRescue = Methods.LeaveRescue.OpCall(gen.CurrentScopeVariable);
} else {
enterRescue = leaveRescue = AstUtils.Empty();
}
gen.EnterRescueClause(retryingVariable, retryLabel);
var handlers = new IfStatementTest[_rescueClauses.Count];
for (int i = 0; i < handlers.Length; i++) {
handlers[i] = _rescueClauses[i].Transform(gen, resultOperation);
}
transformedRescue =
AstUtils.Try(
enterRescue,
AstUtils.If(handlers, Ast.Assign(exceptionRethrowVariable, AstUtils.Constant(true)))
).Filter(unwinder = Ast.Parameter(typeof(EvalUnwinder), "#u"),
Ast.Equal(Ast.Field(unwinder, EvalUnwinder.ReasonField), AstUtils.Constant(BlockReturnReason.Retry)),
Ast.Block(
Ast.Assign(retryingVariable, AstUtils.Constant(true)),
Ast.Continue(retryLabel),
AstUtils.Empty()
)
);
gen.LeaveRescueClause();
} else {
transformedRescue = Ast.Assign(exceptionRethrowVariable, AstUtils.Constant(true));
}
if (_elseStatements != null) {
transformedElse = AstUtils.Unless(exceptionThrownVariable, transformedElse);
}
var result = Ast.Block(
Ast.Label(retryLabel),
AstUtils.Try(
Ast.Assign(exceptionThrownVariable, AstUtils.Constant(false)),
Ast.Assign(exceptionRethrowVariable, AstUtils.Constant(false)),
Ast.Assign(retryingVariable, AstUtils.Constant(false)),
// save exception (old_$! is not used unless there is a rescue clause):
(_rescueClauses == null) ? (MSA.Expression)AstUtils.Empty() :
Ast.Assign(oldExceptionVariable, Methods.GetCurrentException.OpCall(gen.CurrentScopeVariable)),
AstUtils.Try(
Ast.Block(transformedBody, AstUtils.Empty())
).Filter(exceptionVariable = Ast.Parameter(typeof(Exception), "#e"),
Methods.CanRescue.OpCall(gen.CurrentScopeVariable, exceptionVariable),
Ast.Assign(exceptionThrownVariable, AstUtils.Constant(true)),
transformedRescue,
AstUtils.Empty()
).FinallyIf((_rescueClauses != null),
// restore previous exception if the current one has been handled:
AstUtils.Unless(exceptionRethrowVariable,
Methods.SetCurrentException.OpCall(gen.CurrentScopeVariable, oldExceptionVariable)
),
leaveRescue
),
// unless (exception_thrown) do <else-statements> end
transformedElse,
AstUtils.Empty()
).FilterIf((_rescueClauses != null || _elseStatements != null),
exceptionVariable = Ast.Parameter(typeof(Exception), "#e"),
Methods.CanRescue.OpCall(gen.CurrentScopeVariable, exceptionVariable),
Ast.Assign(exceptionRethrowVariable, AstUtils.Constant(true)),
AstUtils.Empty()
).FinallyWithJumps(
AstUtils.Unless(retryingVariable, transformedEnsure)
)
);
return result;
}