/// <summary> /// Takes current result and wraps it into try-filter(MethodUnwinder)-finally block that ensures correct "break" behavior for /// Ruby method calls with a block given in arguments. /// /// Sets up a RFC frame similarly to MethodDeclaration. /// </summary> internal static void ApplyBlockFlowHandlingInternal(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args) { // TODO (improvement): // We don't special case null block here, although we could (we would need a test for that then). // We could also statically know (via call-site flag) that the current method is not a proc-converter (passed by ref), // which would make such calls faster. if (metaBuilder.Error || !args.Signature.HasBlock) { return; } Expression rfcVariable = metaBuilder.GetTemporary(typeof(RuntimeFlowControl), "#rfc"); ParameterExpression methodUnwinder = metaBuilder.GetTemporary(typeof(MethodUnwinder), "#unwinder"); Expression resultVariable = metaBuilder.GetTemporary(typeof(object), "#result"); metaBuilder.Result = Ast.Block( // initialize frame (RFC): Ast.Assign(rfcVariable, Methods.CreateRfcForMethod.OpCall(AstUtils.Convert(args.GetBlockExpression(), typeof(Proc)))), AstUtils.Try( Ast.Assign(resultVariable, metaBuilder.Result) ).Filter(methodUnwinder, Ast.Equal(Ast.Field(methodUnwinder, MethodUnwinder.TargetFrameField), rfcVariable), // return unwinder.ReturnValue; Ast.Assign(resultVariable, Ast.Field(methodUnwinder, MethodUnwinder.ReturnValueField)) ).Finally( // we need to mark the RFC dead snce the block might escape and break later: Ast.Assign(Ast.Field(rfcVariable, RuntimeFlowControl.IsActiveMethodField), Ast.Constant(false)) ), resultVariable ); }
private MSAst.Expression AddModulePublishing(MSAst.Expression body) { if (IsModule) { PythonCompilerOptions pco = _compilerContext.Options as PythonCompilerOptions; string moduleName = ModuleName; if ((pco.Module & ModuleOptions.Initialize) != 0) { var tmp = Ast.Variable(typeof(object), "$originalModule"); // TODO: Should be try/fault body = Ast.Block( new[] { tmp }, AstUtils.Try( Ast.Assign(tmp, Ast.Call(AstMethods.PublishModule, LocalContext, Ast.Constant(moduleName))), body ).Catch( typeof(Exception), Ast.Call(AstMethods.RemoveModule, LocalContext, Ast.Constant(moduleName), tmp), Ast.Rethrow(body.Type) ) ); } } return(body); }
/// <summary> /// Takes current result and wraps it into try-filter(MethodUnwinder)-finally block that ensures correct "break" behavior for /// library method calls with block given in bfcVariable (BlockParam). /// </summary> internal static void ApplyBlockFlowHandlingInternal(MetaObjectBuilder /*!*/ metaBuilder, CallArguments /*!*/ args) { if (metaBuilder.Error) { return; } Expression expression = metaBuilder.Result; Expression bfcVariable = metaBuilder.BfcVariable; // Method call with proc can invoke control flow that returns an arbitrary value from the call, so we need to type result to Object. // Otherwise, the result could only be result of targetExpression unless its return type is void. Type resultType = (bfcVariable != null) ? typeof(object) : expression.Type; Expression resultVariable; if (resultType != typeof(void)) { resultVariable = metaBuilder.GetTemporary(resultType, "#result"); } else { resultVariable = Expression.Empty(); } if (expression.Type != typeof(void)) { expression = Ast.Assign(resultVariable, AstUtils.Convert(expression, resultType)); } // a non-null proc is being passed to the callee: if (bfcVariable != null) { ParameterExpression methodUnwinder = metaBuilder.GetTemporary(typeof(MethodUnwinder), "#unwinder"); expression = AstFactory.Block( Ast.Assign(bfcVariable, Methods.CreateBfcForLibraryMethod.OpCall(AstUtils.Convert(args.GetBlockExpression(), typeof(Proc)))), AstUtils.Try( expression ).Filter(methodUnwinder, Methods.IsProcConverterTarget.OpCall(bfcVariable, methodUnwinder), Ast.Assign(resultVariable, Ast.Field(methodUnwinder, MethodUnwinder.ReturnValueField)) ).Finally( Methods.LeaveProcConverter.OpCall(bfcVariable) ), resultVariable ); } metaBuilder.Result = expression; }
/// <summary> /// Helper to wrap explicit conversion call into try/catch incase it throws an exception. If /// it throws the default value is returned. /// </summary> private static Expression WrapForThrowingTry(ConversionResultKind kind, bool isImplicit, Expression ret, Type retType) { if (!isImplicit && kind == ConversionResultKind.ExplicitTry) { Expression convFailed = GetTryConvertReturnValue(retType); ParameterExpression tmp = Expression.Variable(convFailed.Type == typeof(object) ? typeof(object) : ret.Type, "tmp"); ret = Expression.Block( new ParameterExpression[] { tmp }, AstUtils.Try( Expression.Assign(tmp, AstUtils.Convert(ret, tmp.Type)) ).Catch( typeof(Exception), Expression.Assign(tmp, convFailed) ), tmp ); } return(ret); }
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 )); }
/// <summary> /// WithStatement is translated to the DLR AST equivalent to /// the following Python code snippet (from with statement spec): /// /// mgr = (EXPR) /// exit = mgr.__exit__ # Not calling it yet /// value = mgr.__enter__() /// exc = True /// try: /// VAR = value # Only if "as VAR" is present /// BLOCK /// except: /// # The exceptional case is handled here /// exc = False /// if not exit(*sys.exc_info()): /// raise /// # The exception is swallowed if exit() returns true /// finally: /// # The normal and non-local-goto cases are handled here /// if exc: /// exit(None, None, None) /// /// </summary> public override MSAst.Expression Reduce() { // Five statements in the result... ReadOnlyCollectionBuilder <MSAst.Expression> statements = new ReadOnlyCollectionBuilder <MSAst.Expression>(6); ReadOnlyCollectionBuilder <MSAst.ParameterExpression> variables = new ReadOnlyCollectionBuilder <MSAst.ParameterExpression>(6); MSAst.ParameterExpression lineUpdated = Ast.Variable(typeof(bool), "$lineUpdated_with"); variables.Add(lineUpdated); //****************************************************************** // 1. mgr = (EXPR) //****************************************************************** MSAst.ParameterExpression manager = Ast.Variable(typeof(object), "with_manager"); variables.Add(manager); statements.Add( GlobalParent.AddDebugInfo( Ast.Assign( manager, _contextManager ), new SourceSpan(GlobalParent.IndexToLocation(StartIndex), GlobalParent.IndexToLocation(_headerIndex)) ) ); //****************************************************************** // 2. exit = mgr.__exit__ # Not calling it yet //****************************************************************** MSAst.ParameterExpression exit = Ast.Variable(typeof(object), "with_exit"); variables.Add(exit); statements.Add( MakeAssignment( exit, GlobalParent.Get( "__exit__", manager ) ) ); //****************************************************************** // 3. value = mgr.__enter__() //****************************************************************** MSAst.ParameterExpression value = Ast.Variable(typeof(object), "with_value"); variables.Add(value); statements.Add( GlobalParent.AddDebugInfoAndVoid( MakeAssignment( value, Parent.Invoke( new CallSignature(0), Parent.LocalContext, GlobalParent.Get( "__enter__", manager ) ) ), new SourceSpan(GlobalParent.IndexToLocation(StartIndex), GlobalParent.IndexToLocation(_headerIndex)) ) ); //****************************************************************** // 4. exc = True //****************************************************************** MSAst.ParameterExpression exc = Ast.Variable(typeof(bool), "with_exc"); variables.Add(exc); statements.Add( MakeAssignment( exc, AstUtils.Constant(true) ) ); //****************************************************************** // 5. The final try statement: // // try: // VAR = value # Only if "as VAR" is present // BLOCK // except: // # The exceptional case is handled here // exc = False // if not exit(*sys.exc_info()): // raise // # The exception is swallowed if exit() returns true // finally: // # The normal and non-local-goto cases are handled here // if exc: // exit(None, None, None) //****************************************************************** var previousException = Ast.Variable(typeof(Exception), "$previousException"); variables.Add(previousException); MSAst.ParameterExpression exception; statements.Add( // try: AstUtils.Try( AstUtils.Try(// try statement body PushLineUpdated(false, lineUpdated), Ast.Assign(previousException, Ast.Call(AstMethods.SaveCurrentException)), _var != null ? (MSAst.Expression)Ast.Block( // VAR = value _var.TransformSet(SourceSpan.None, value, PythonOperationKind.None), // BLOCK _body, AstUtils.Empty() ) : // BLOCK (MSAst.Expression)_body // except:, // try statement location ).Catch(exception = Ast.Variable(typeof(Exception), "exception"), // Python specific exception handling code TryStatement.GetTracebackHeader( this, exception, GlobalParent.AddDebugInfoAndVoid( Ast.Block( Ast.Call(AstMethods.SetCurrentException, Parent.LocalContext, exception), // exc = False MakeAssignment( exc, AstUtils.Constant(false) ), // if not exit(*sys.exc_info()): // raise AstUtils.IfThen( GlobalParent.Convert( typeof(bool), ConversionResultKind.ExplicitCast, GlobalParent.Operation( typeof(bool), PythonOperationKind.IsFalse, MakeExitCall(exit, exception) ) ), UpdateLineUpdated(true), Ast.Throw( Ast.Call( AstMethods.MakeRethrowExceptionWorker, exception ) ) ) ), _body.Span ) ), PopLineUpdated(lineUpdated), Ast.Empty() ) // finally: ).Finally( Ast.Call(AstMethods.RestoreCurrentException, previousException), // if exc: // exit(None, None, None) AstUtils.IfThen( exc, GlobalParent.AddDebugInfoAndVoid( Ast.Block( MSAst.DynamicExpression.Dynamic( GlobalParent.PyContext.Invoke( new CallSignature(3) // signature doesn't include function ), typeof(object), new MSAst.Expression[] { Parent.LocalContext, exit, AstUtils.Constant(null), AstUtils.Constant(null), AstUtils.Constant(null) } ), Ast.Empty() ), _contextManager.Span ) ) ) ); statements.Add(AstUtils.Empty()); return(Ast.Block(variables.ToReadOnlyCollection(), statements.ToReadOnlyCollection())); }
/// <summary> /// Creates the LambdaExpression which implements the body of the function. /// /// The functions signature is either "object Function(PythonFunction, ...)" /// where there is one object parameter for each user defined parameter or /// object Function(PythonFunction, object[]) for functions which take more /// than PythonCallTargets.MaxArgs arguments. /// </summary> private LightLambdaExpression CreateFunctionLambda() { bool needsWrapperMethod = _parameters.Length > PythonCallTargets.MaxArgs; Type delegateType = GetDelegateType(_parameters, needsWrapperMethod, out _); MSAst.ParameterExpression localContext = null; ReadOnlyCollectionBuilder <MSAst.ParameterExpression> locals = new ReadOnlyCollectionBuilder <MSAst.ParameterExpression>(); if (NeedsLocalContext) { localContext = LocalCodeContextVariable; locals.Add(localContext); } MSAst.ParameterExpression[] parameters = CreateParameters(needsWrapperMethod, locals); List <MSAst.Expression> init = new List <MSAst.Expression>(); foreach (var param in _parameters) { if (GetVariableExpression(param.PythonVariable) is IPythonVariableExpression pyVar) { var varInit = pyVar.Create(); if (varInit != null) { init.Add(varInit); } } } // Transform the parameters. init.Add(Ast.ClearDebugInfo(GlobalParent.Document)); locals.Add(PythonAst._globalContext); init.Add(Ast.Assign(PythonAst._globalContext, new GetGlobalContextExpression(_parentContext))); GlobalParent.PrepareScope(locals, init); // Create variables and references. Since references refer to // parameters, do this after parameters have been created. CreateFunctionVariables(locals, init); // If the __class__ variable is used in a class method then we need to initialize it. // This must be done before parameter initialization (in case one of the parameters is called __class__). ClassDefinition parent = FindParentOfType <ClassDefinition>(); if (parent != null && TryGetVariable("__class__", out PythonVariable pVar)) { init.Add( AssignValue( GetVariableExpression(pVar), Ast.Call(AstMethods.LookupName, parent.Parent.LocalContext, Ast.Constant(parent.Name)) ) ); } // Initialize parameters - unpack tuples. // Since tuples unpack into locals, this must be done after locals have been created. InitializeParameters(init, needsWrapperMethod, parameters); List <MSAst.Expression> statements = new List <MSAst.Expression>(); // add beginning sequence point var start = GlobalParent.IndexToLocation(StartIndex); statements.Add(GlobalParent.AddDebugInfo( AstUtils.Empty(), new SourceSpan(new SourceLocation(0, start.Line, start.Column), new SourceLocation(0, start.Line, int.MaxValue)))); // For generators, we need to do a check before the first statement for Generator.Throw() / Generator.Close(). // The exception traceback needs to come from the generator's method body, and so we must do the check and throw // from inside the generator. if (IsGenerator) { MSAst.Expression s1 = YieldExpression.CreateCheckThrowExpression(SourceSpan.None); statements.Add(s1); } MSAst.ParameterExpression extracted = null; if (!IsGenerator && CanSetSysExcInfo) { // need to allocate the exception here so we don't share w/ exceptions made & freed // during the body. extracted = Ast.Parameter(typeof(Exception), "$ex"); locals.Add(extracted); } if (Body.CanThrow && !(Body is SuiteStatement) && Body.StartIndex != -1) { statements.Add(UpdateLineNumber(GlobalParent.IndexToLocation(Body.StartIndex).Line)); } statements.Add(Body); MSAst.Expression body = Ast.Block(statements); // If this function can modify sys.exc_info() (_canSetSysExcInfo), then it must restore the result on finish. // // Wrap in // $temp = PythonOps.SaveCurrentException() // <body> // PythonOps.RestoreCurrentException($temp) // Skip this if we're a generator. For generators, the try finally is handled by the PythonGenerator class // before it's invoked. This is because the restoration must occur at every place the function returns from // a yield point. That's different than the finally semantics in a generator. if (extracted != null) { MSAst.Expression s = AstUtils.Try( Ast.Assign( extracted, Ast.Call(AstMethods.SaveCurrentException) ), body ).Finally( Ast.Call( AstMethods.RestoreCurrentException, extracted ) ); body = s; } if (Body.CanThrow && GlobalParent.PyContext.PythonOptions.Frames) { body = AddFrame(LocalContext, Ast.Property(_functionParam, typeof(PythonFunction).GetProperty("__code__")), body); locals.Add(FunctionStackVariable); } body = AddProfiling(body); body = WrapScopeStatements(body, Body.CanThrow); body = Ast.Block(body, AstUtils.Empty()); body = AddReturnTarget(body); MSAst.Expression bodyStmt = body; if (localContext != null) { var createLocal = CreateLocalContext(_parentContext); init.Add( Ast.Assign( localContext, createLocal ) ); } init.Add(bodyStmt); bodyStmt = Ast.Block(init); // wrap a scope if needed bodyStmt = Ast.Block(locals.ToReadOnlyCollection(), bodyStmt); return(AstUtils.LightLambda( typeof(object), delegateType, AddDefaultReturn(bodyStmt, typeof(object)), Name + "$" + Interlocked.Increment(ref _lambdaId), parameters )); }
internal override MSA.Expression /*!*/ Transform(AstGenerator /*!*/ gen) { MSA.Expression parentScope = gen.CurrentScopeVariable; ScopeBuilder scope = new ScopeBuilder(); // define hidden parameters and RHS-placeholders (#1..#n will be used as RHS of a parallel assignment): MSA.Expression blockParameter, selfParameter; MSA.ParameterExpression[] parameters = DefineParameters(out selfParameter, out blockParameter); MSA.Expression scopeVariable = scope.DefineHiddenVariable("#scope", typeof(RubyBlockScope)); MSA.LabelTarget redoLabel = Ast.Label(); gen.EnterBlockDefinition( scope, blockParameter, selfParameter, scopeVariable, redoLabel ); if (_definedScope != null) { _definedScope.TransformLocals(scope); } MSA.Expression paramInit = MakeParametersInitialization(gen, parameters); MSA.ParameterExpression blockUnwinder = scope.DefineHiddenVariable("#unwinder", typeof(BlockUnwinder)); MSA.Expression loop = AstFactory.Infinite(null, redoLabel, AstUtils.Try( gen.TransformStatements(_body, ResultOperation.Return) ).Catch(blockUnwinder, // redo: AstUtils.IfThen(Ast.Field(blockUnwinder, BlockUnwinder.IsRedoField), Ast.Continue(redoLabel)), // next: gen.Return(Ast.Field(blockUnwinder, BlockUnwinder.ReturnValueField)) ) ); if (gen.TraceEnabled) { int firstStatementLine = _body.Count > 0 ? _body[0].Location.Start.Line : Location.End.Line; int lastStatementLine = _body.Count > 0 ? _body[_body.Count - 1].Location.End.Line : Location.End.Line; loop = Ast.TryFinally( Ast.Block( Methods.TraceBlockCall.OpCall(scopeVariable, blockParameter, Ast.Convert(Ast.Constant(gen.SourceUnit.Path), typeof(string)), Ast.Constant(firstStatementLine)), loop ), Methods.TraceBlockReturn.OpCall(scopeVariable, blockParameter, Ast.Convert(Ast.Constant(gen.SourceUnit.Path), typeof(string)), Ast.Constant(lastStatementLine)) ); } MSA.Expression body = Ast.Block( Ast.Assign(scopeVariable, Methods.CreateBlockScope.OpCall(scope.VisibleVariables(), parentScope, blockParameter, selfParameter) ), paramInit, loop, Ast.Empty() ); body = gen.AddReturnTarget(scope.CreateScope(body)); gen.LeaveBlockDefinition(); int parameterCount = _parameters.LeftValues.Count; var attributes = _parameters.GetBlockSignatureAttributes(); return(Methods.DefineBlock.OpCall( gen.CurrentScopeVariable, gen.CurrentRfcVariable, gen.CurrentSelfVariable, Ast.Lambda( BlockDispatcher.GetDelegateType(parameterCount, attributes), body, gen.EncodeMethodName(gen.CurrentMethod.MethodName, Location), new ReadOnlyCollection <MSA.ParameterExpression>(parameters) ), Ast.Constant(parameterCount), Ast.Constant(attributes) )); }
// 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 blockUnwinder = gen.CurrentScope.DefineHiddenVariable("#unwinder", typeof(BlockUnwinder)); MSA.ParameterExpression evalUnwinder = gen.CurrentScope.DefineHiddenVariable("#unwinder", typeof(EvalUnwinder)); 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 = AstFactory.IsTrue(_condition.TransformRead(gen)); gen.LeaveLoop(); MSA.Expression conditionPositiveStmt, conditionNegativeStmt; if (_isWhileLoop) { conditionPositiveStmt = Ast.Empty(); conditionNegativeStmt = Ast.Break(breakLabel); } else { conditionPositiveStmt = Ast.Break(breakLabel); conditionNegativeStmt = Ast.Empty(); } // make the loop first: MSA.Expression loop = Ast.Block( Ast.Assign(redoVariable, Ast.Constant(_isPostTest)), AstFactory.Infinite(breakLabel, continueLabel, AstUtils.Try( AstUtils.If(redoVariable, Ast.Assign(redoVariable, Ast.Constant(false)) ).ElseIf(transformedCondition, conditionPositiveStmt ).Else( conditionNegativeStmt ), transformedBody ).Catch(blockUnwinder, // redo = u.IsRedo Ast.Assign(redoVariable, Ast.Field(blockUnwinder, BlockUnwinder.IsRedoField)) ).Filter(evalUnwinder, Ast.Equal(Ast.Field(evalUnwinder, EvalUnwinder.ReasonField), AstFactory.BlockReturnReasonBreak), // result = unwinder.ReturnValue Ast.Assign(resultVariable, Ast.Field(evalUnwinder, EvalUnwinder.ReturnValueField)), Ast.Break(breakLabel) ) ), Ast.Empty() ); // wrap it to try finally that updates RFC state: if (!isInnerLoop) { loop = AstUtils.Try( Ast.Assign(Ast.Field(gen.CurrentRfcVariable, RuntimeFlowControl.InLoopField), Ast.Constant(true)), loop ).Finally( Ast.Assign(Ast.Field(gen.CurrentRfcVariable, RuntimeFlowControl.InLoopField), Ast.Constant(false)) ); } return(AstFactory.Block(loop, resultVariable)); }