// see Ruby Language.doc/Runtime/Control Flow Implementation/Break internal override MSA.Expression /*!*/ Transform(AstGenerator /*!*/ gen) { MSA.Expression transformedReturnValue = TransformReturnValue(gen); // eval: if (gen.CompilerOptions.IsEval) { return(Methods.EvalBreak.OpCall(gen.CurrentScopeVariable, AstUtils.Box(transformedReturnValue))); } // loop: if (gen.CurrentLoop != null) { return(Ast.Block( Ast.Assign( gen.CurrentLoop.ResultVariable, Ast.Convert(transformedReturnValue, gen.CurrentLoop.ResultVariable.Type) ), Ast.Break(gen.CurrentLoop.BreakLabel), AstUtils.Empty() )); } // block: if (gen.CurrentBlock != null) { return(gen.Return(Methods.BlockBreak.OpCall(gen.CurrentBlock.BfcVariable, AstUtils.Box(transformedReturnValue)))); } // primary frame: return(Methods.MethodBreak.OpCall(AstUtils.Box(transformedReturnValue))); }
// when [<expr>, ...] *<array> // // generates this code: // // IEnumerator<object>/*!*/ enumVar = RubyOps.Unsplat(<array>).GetEnumerator(); // bool result = false; // while (enumVar.MoveNext()) { // if (<MakeTest>(enumVar.Current)) { // result = true; // break; // } // } private static MSA.Expression /*!*/ MakeArrayTest(AstGenerator /*!*/ gen, MSA.Expression /*!*/ array, MSA.Expression value) { MSA.Expression enumVariable = gen.CurrentScope.DefineHiddenVariable("#case-enumerator", typeof(IEnumerator <object>)); MSA.Expression resultVariable = gen.CurrentScope.DefineHiddenVariable("#case-compare-result", typeof(bool)); MSA.LabelTarget label = Ast.Label(); return(AstFactory.Block( Ast.Assign(enumVariable, Ast.Call( Methods.Unsplat.OpCall(AstFactory.Box(array)), Methods.IEnumerable_Of_Object_GetEnumerator )), Ast.Assign(resultVariable, Ast.Constant(false)), AstUtils.While( Ast.Call(enumVariable, Methods.IEnumerator_MoveNext), AstUtils.If( MakeTest(gen, Ast.Call(enumVariable, Methods.IEnumerator_get_Current), value), Ast.Block( Ast.Assign(resultVariable, Ast.Constant(true)), Ast.Break(label), Ast.Empty() ) ), null, label, null ), resultVariable )); }
internal static MSA.Expression /*!*/ MakeCallWithBlockRetryable(AstGenerator /*!*/ gen, MSA.Expression /*!*/ invoke, MSA.Expression blockArgVariable, MSA.Expression transformedBlock, bool isBlockDefinition) { Assert.NotNull(invoke); Debug.Assert((blockArgVariable == null) == (transformedBlock == null)); // see Ruby Language.doc/Control Flow Implementation/Method Call With a Block MSA.Expression resultVariable = gen.CurrentScope.DefineHiddenVariable("#method-result", typeof(object)); MSA.ParameterExpression evalUnwinder = gen.CurrentScope.DefineHiddenVariable("#unwinder", typeof(EvalUnwinder)); MSA.LabelTarget label = Ast.Label(); return(AstFactory.Block( Ast.Assign(blockArgVariable, Ast.Convert(transformedBlock, blockArgVariable.Type)), AstFactory.Infinite(label, null, (!isBlockDefinition) ? (MSA.Expression)Ast.Empty() : (MSA.Expression)Methods.InitializeBlock.OpCall(blockArgVariable), AstUtils.Try( Ast.Assign(resultVariable, invoke) ).Catch(evalUnwinder, Ast.Assign( resultVariable, Ast.Field(evalUnwinder, EvalUnwinder.ReturnValueField) ) ), // if result != RetrySingleton then break end AstUtils.Unless(Methods.IsRetrySingleton.OpCall(AstFactory.Box(resultVariable)), Ast.Break(label)), // if blockParam == #block then retry end (gen.CurrentMethod.IsTopLevelCode) ? Ast.Empty() : AstUtils.IfThen(Ast.Equal(gen.MakeMethodBlockParameterRead(), blockArgVariable), RetryStatement.TransformRetry(gen)) ), resultVariable )); }
private MSA.Expression /*!*/ TransformExceptionHandling(AstGenerator /*!*/ gen, ResultOperation resultOperation) { Assert.NotNull(gen); MSA.Expression exceptionThrownVariable = gen.CurrentScope.DefineHiddenVariable("#exception-thrown", typeof(bool)); MSA.ParameterExpression exceptionVariable = gen.CurrentScope.DefineHiddenVariable("#exception", typeof(Exception)); MSA.Expression exceptionRethrowVariable = gen.CurrentScope.DefineHiddenVariable("#exception-rethrow", typeof(bool)); MSA.Expression retryingVariable = gen.CurrentScope.DefineHiddenVariable("#retrying", typeof(bool)); MSA.ParameterExpression evalUnwinder = gen.CurrentScope.DefineHiddenVariable("#unwinder", typeof(EvalUnwinder)); MSA.Expression oldExceptionVariable = gen.CurrentScope.DefineHiddenVariable("#old-exception", typeof(Exception)); 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, Ast.Constant(null)) ), Ast.Throw(oldExceptionVariable) ), Ast.Empty() ); } else { // rethrow: transformedEnsure = AstUtils.IfThen( Ast.AndAlso( exceptionRethrowVariable, Ast.NotEqual( Ast.Assign(oldExceptionVariable, Methods.GetCurrentException.OpCall(gen.CurrentScopeVariable)), Ast.Constant(null, typeof(Exception))) ), Ast.Throw(oldExceptionVariable) ); } if (_elseStatements != null) { transformedElse = gen.TransformStatements(_elseStatements, resultOperation); } else { transformedElse = Ast.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 setInRescueFlag = null, clearInRescueFlag = null; var breakLabel = Ast.Label(); var continueLabel = Ast.Label(); // make rescue clause: MSA.Expression transformedRescue; if (_rescueClauses != null) { // outer-most EH blocks sets and clears runtime flag RuntimeFlowControl.InTryRescue: if (gen.CurrentRescue == null) { setInRescueFlag = Ast.Assign(Ast.Field(gen.CurrentRfcVariable, RuntimeFlowControl.InRescueField), Ast.Constant(true)); clearInRescueFlag = Ast.Assign(Ast.Field(gen.CurrentRfcVariable, RuntimeFlowControl.InRescueField), Ast.Constant(false)); } else { setInRescueFlag = clearInRescueFlag = Ast.Empty(); } gen.EnterRescueClause(retryingVariable, breakLabel, continueLabel); var handlers = new IfStatementTest[_rescueClauses.Count]; for (int i = 0; i < handlers.Length; i++) { handlers[i] = _rescueClauses[i].Transform(gen, resultOperation); } transformedRescue = Ast.Block( setInRescueFlag, AstUtils.Try( AstUtils.If(handlers, Ast.Assign(exceptionRethrowVariable, Ast.Constant(true))) ).Filter(evalUnwinder, Ast.Equal(Ast.Field(evalUnwinder, EvalUnwinder.ReasonField), Ast.Constant(BlockReturnReason.Retry)), Ast.Block( Ast.Assign(retryingVariable, Ast.Constant(true)), Ast.Continue(continueLabel), Ast.Empty() ) ) ); gen.LeaveRescueClause(); } else { transformedRescue = Ast.Assign(exceptionRethrowVariable, Ast.Constant(true)); } if (_elseStatements != null) { transformedElse = AstUtils.Unless(exceptionThrownVariable, transformedElse); } var result = AstFactory.Infinite(breakLabel, continueLabel, Ast.Assign(exceptionThrownVariable, Ast.Constant(false)), Ast.Assign(exceptionRethrowVariable, Ast.Constant(false)), Ast.Assign(retryingVariable, Ast.Constant(false)), AstUtils.Try( // save exception (old_$! is not used unless there is a rescue clause): Ast.Block( (_rescueClauses == null) ? (MSA.Expression)Ast.Empty() : Ast.Assign(oldExceptionVariable, Methods.GetCurrentException.OpCall(gen.CurrentScopeVariable)), AstUtils.Try( transformedBody ).Filter(exceptionVariable, Methods.CanRescue.OpCall(gen.CurrentRfcVariable, exceptionVariable), Ast.Assign(exceptionThrownVariable, Ast.Constant(true)), Methods.SetCurrentExceptionAndStackTrace.OpCall(gen.CurrentScopeVariable, exceptionVariable), transformedRescue ).FinallyIf((_rescueClauses != null), // restore previous exception if the current one has been handled: AstUtils.Unless(exceptionRethrowVariable, Methods.SetCurrentException.OpCall(gen.CurrentScopeVariable, oldExceptionVariable) ), clearInRescueFlag ), // unless (exception_thrown) do <else-statements> end transformedElse, Ast.Empty() ) ).FilterIf((_rescueClauses != null || _elseStatements != null), exceptionVariable, Methods.CanRescue.OpCall(gen.CurrentRfcVariable, exceptionVariable), Ast.Block( Methods.SetCurrentExceptionAndStackTrace.OpCall(gen.CurrentScopeVariable, exceptionVariable), Ast.Assign(exceptionRethrowVariable, Ast.Constant(true)), Ast.Empty() ) ).Finally( AstUtils.Unless(retryingVariable, transformedEnsure) ), Ast.Break(breakLabel) ); return(result); }
// see Ruby Language.doc/Runtime/Control Flow Implementation/While-Until internal override MSA.Expression /*!*/ TransformRead(AstGenerator /*!*/ gen) { MSA.Expression resultVariable = gen.CurrentScope.DefineHiddenVariable("#loop-result", typeof(object)); MSA.Expression redoVariable = gen.CurrentScope.DefineHiddenVariable("#skip-condition", typeof(bool)); MSA.ParameterExpression unwinder; bool isInnerLoop = gen.CurrentLoop != null; MSA.LabelTarget breakLabel = Ast.Label(); MSA.LabelTarget continueLabel = Ast.Label(); gen.EnterLoop(redoVariable, resultVariable, breakLabel, continueLabel); MSA.Expression transformedBody = gen.TransformStatements(_statements, ResultOperation.Ignore); MSA.Expression transformedCondition = _condition.TransformCondition(gen, true); gen.LeaveLoop(); MSA.Expression conditionPositiveStmt, conditionNegativeStmt; if (_isWhileLoop) { conditionPositiveStmt = AstUtils.Empty(); conditionNegativeStmt = Ast.Break(breakLabel); } else { conditionPositiveStmt = Ast.Break(breakLabel); conditionNegativeStmt = AstUtils.Empty(); } // make the loop first: MSA.Expression loop = new AstBlock { gen.ClearDebugInfo(), Ast.Assign(redoVariable, AstUtils.Constant(_isPostTest)), AstFactory.Infinite(breakLabel, continueLabel, AstUtils.Try( AstUtils.If(redoVariable, Ast.Assign(redoVariable, AstUtils.Constant(false)) ).ElseIf(transformedCondition, conditionPositiveStmt ).Else( conditionNegativeStmt ), transformedBody, AstUtils.Empty() ).Catch(unwinder = Ast.Parameter(typeof(BlockUnwinder), "#u"), // redo = u.IsRedo Ast.Assign(redoVariable, Ast.Field(unwinder, BlockUnwinder.IsRedoField)), AstUtils.Empty() ).Filter(unwinder = Ast.Parameter(typeof(EvalUnwinder), "#u"), Ast.Equal(Ast.Field(unwinder, EvalUnwinder.ReasonField), AstFactory.BlockReturnReasonBreak), // result = unwinder.ReturnValue Ast.Assign(resultVariable, Ast.Field(unwinder, EvalUnwinder.ReturnValueField)), Ast.Break(breakLabel) ) ), gen.ClearDebugInfo(), AstUtils.Empty(), }; // wrap it to try finally that updates RFC state: if (!isInnerLoop) { loop = AstUtils.Try( Methods.EnterLoop.OpCall(gen.CurrentScopeVariable), loop ).Finally( Methods.LeaveLoop.OpCall(gen.CurrentScopeVariable) ); } return(Ast.Block(loop, resultVariable)); }