internal override MSAst.Expression TransformSet(AstGenerator ag, SourceSpan span, MSAst.Expression right, PythonOperationKind op) { // if we just have a simple named multi-assignment (e.g. a, b = 1,2) // then go ahead and step over the entire statement at once. If we have a // more complex statement (e.g. a.b, c.d = 1, 2) then we'll step over the // sets individually as they could be property sets the user wants to step // into. TODO: Enable stepping of the right hand side? bool emitIndividualSets = false; foreach (Expression e in _items) { if (IsComplexAssignment(e)) { emitIndividualSets = true; break; } } SourceSpan rightSpan = SourceSpan.None; SourceSpan leftSpan = (Span.Start.IsValid && span.IsValid) ? new SourceSpan(Span.Start, span.End) : SourceSpan.None; SourceSpan totalSpan = SourceSpan.None; if (emitIndividualSets) { rightSpan = span; leftSpan = SourceSpan.None; totalSpan = (Span.Start.IsValid && span.IsValid) ? new SourceSpan(Span.Start, span.End) : SourceSpan.None; } // 1. Evaluate the expression and assign the value to the temp. MSAst.ParameterExpression right_temp = ag.GetTemporary("unpacking"); // 2. Add the assignment "right_temp = right" into the suite/block MSAst.Expression assignStmt1 = AstGenerator.MakeAssignment(right_temp, right); // 3. Call GetEnumeratorValues on the right side (stored in temp) MSAst.Expression enumeratorValues = Ast.Call( AstGenerator.GetHelperMethod("GetEnumeratorValues"), // method // arguments ag.LocalContext, right_temp, AstUtils.Constant(_items.Length) ); // 4. Create temporary variable for the array MSAst.ParameterExpression array_temp = ag.GetTemporary("array", typeof(object[])); // 5. Assign the value of the method call (mce) into the array temp // And add the assignment "array_temp = Ops.GetEnumeratorValues(...)" into the block MSAst.Expression assignStmt2 = ag.MakeAssignment( array_temp, enumeratorValues, rightSpan ); ReadOnlyCollectionBuilder<MSAst.Expression> sets = new ReadOnlyCollectionBuilder<MSAst.Expression>(_items.Length + 1); for (int i = 0; i < _items.Length; i ++) { // target = array_temp[i] Expression target = _items[i]; if (target == null) { continue; } // 6. array_temp[i] MSAst.Expression element = Ast.ArrayAccess( array_temp, // array expression AstUtils.Constant(i) // index ); // 7. target = array_temp[i], and add the transformed assignment into the list of sets MSAst.Expression set = target.TransformSet( ag, emitIndividualSets ? // span target.Span : SourceSpan.None, element, PythonOperationKind.None ); sets.Add(set); } // 9. add the sets as their own block so they can be marked as a single span, if necessary. sets.Add(AstUtils.Empty()); MSAst.Expression itemSet = ag.AddDebugInfo(Ast.Block(sets.ToReadOnlyCollection()), leftSpan); // 10. Free the temps ag.FreeTemp(array_temp); ag.FreeTemp(right_temp); // 11. Return the suite statement (block) return ag.AddDebugInfo(Ast.Block(assignStmt1, assignStmt2, itemSet, AstUtils.Empty()), totalSpan); }
/// <summary> /// Transform multiple python except handlers for a try block into a single catch body. /// </summary> /// <param name="ag"></param> /// <param name="variable">The variable for the exception in the catch block.</param> /// <returns>Null if there are no except handlers. Else the statement to go inside the catch handler</returns> private MSAst.Expression TransformHandlers(AstGenerator ag, out MSAst.ParameterExpression variable) { if (_handlers == null || _handlers.Length == 0) { variable = null; return null; } MSAst.ParameterExpression exception = ag.GetTemporary("exception", typeof(Exception)); MSAst.ParameterExpression extracted = ag.GetTemporary("extracted", typeof(object)); // The variable where the runtime will store the exception. variable = exception; var tests = new List<Microsoft.Scripting.Ast.IfStatementTest>(_handlers.Length); MSAst.ParameterExpression converted = null; MSAst.Expression catchAll = null; for (int index = 0; index < _handlers.Length; index++) { TryStatementHandler tsh = _handlers[index]; if (tsh.Test != null) { Microsoft.Scripting.Ast.IfStatementTest ist; // translating: // except Test ... // // generate following AST for the Test (common part): // CheckException(exception, Test) MSAst.Expression test = Ast.Call( AstGenerator.GetHelperMethod("CheckException"), extracted, ag.TransformAsObject(tsh.Test) ); if (tsh.Target != null) { // translating: // except Test, Target: // <body> // into: // if ((converted = CheckException(exception, Test)) != null) { // Target = converted; // traceback-header // <body> // } if (converted == null) { converted = ag.GetTemporary("converted"); } ist = AstUtils.IfCondition( Ast.NotEqual( Ast.Assign(converted, test), Ast.Constant(null) ), Ast.Block( tsh.Target.TransformSet(ag, SourceSpan.None, converted, Operators.None), GetTracebackHeader( new SourceSpan(tsh.Start, tsh.Header), ag, exception, ag.Transform(tsh.Body) ), Ast.Empty() ) ); } else { // translating: // except Test: // <body> // into: // if (CheckException(exception, Test) != null) { // traceback-header // <body> // } ist = AstUtils.IfCondition( Ast.NotEqual( test, Ast.Constant(null) ), GetTracebackHeader( new SourceSpan(tsh.Start, tsh.Header), ag, exception, ag.Transform(tsh.Body) ) ); } // Add the test to the if statement test cascade tests.Add(ist); } else { Debug.Assert(index == _handlers.Length - 1); Debug.Assert(catchAll == null); // translating: // except: // <body> // into: // { // traceback-header // <body> // } catchAll = GetTracebackHeader(new SourceSpan(tsh.Start, tsh.Header), ag, exception, ag.Transform(tsh.Body)); } } MSAst.Expression body = null; if (tests.Count > 0) { // rethrow the exception if we have no catch-all block if (catchAll == null) { catchAll = Ast.Throw(exception); } body = AstUtils.If( tests.ToArray(), catchAll ); } else { Debug.Assert(catchAll != null); body = catchAll; } if (converted != null) { ag.FreeTemp(converted); } ag.FreeTemp(exception); ag.FreeTemp(extracted); // Codegen becomes: // extracted = PythonOps.SetCurrentException(exception) // < dynamic exception analysis > return Ast.Block( Ast.Assign( extracted, Ast.Call( AstGenerator.GetHelperMethod("SetCurrentException"), AstUtils.CodeContext(), exception ) ), body, Ast.Empty() ); }
internal override MSAst.Expression Transform(AstGenerator ag) { // allocated all variables here so they won't be shared w/ other // locals allocated during the body or except blocks. MSAst.ParameterExpression noNestedException = null; if (_finally != null) { noNestedException = ag.GetTemporary("$noException", typeof(bool)); } MSAst.ParameterExpression lineUpdated = null; MSAst.ParameterExpression runElse = null; if (_else != null || (_handlers != null && _handlers.Length > 0)) { lineUpdated = ag.GetTemporary("$lineUpdated", typeof(bool)); if (_else != null) { runElse = ag.GetTemporary("run_else", typeof(bool)); } } // don't allocate locals below here... MSAst.Expression body = ag.Transform(_body); MSAst.Expression @else = ag.Transform(_else); if (body == null) { return null; } MSAst.ParameterExpression exception; MSAst.Expression @catch = TransformHandlers(ag, out exception); MSAst.Expression result; // We have else clause, must generate guard around it if (@else != null) { Debug.Assert(@catch != null); // run_else = true; // try { // try_body // } catch ( ... ) { // run_else = false; // catch_body // } // if (run_else) { // else_body // } result = Ast.Block( Ast.Assign(runElse, Ast.Constant(true)), // save existing line updated, we could choose to do this only for nested exception handlers. ag.PushLineUpdated(false, lineUpdated), AstUtils.Try( ag.AddDebugInfo(Ast.Empty(), new SourceSpan(Span.Start, _header)), body ).Catch(exception, Ast.Assign(runElse, Ast.Constant(false)), @catch, // restore existing line updated after exception handler completes ag.PopLineUpdated(lineUpdated), Ast.Default(body.Type) ), AstUtils.IfThen(runElse, @else ), Ast.Empty() ); } else if (@catch != null) { // no "else" clause // try { // <try body> // } catch (Exception e) { // ... catch handling ... // } // result = AstUtils.Try( ag.AddDebugInfo(Ast.Empty(), new SourceSpan(Span.Start, _header)), // save existing line updated ag.PushLineUpdated(false, lineUpdated), body ).Catch(exception, @catch, // restore existing line updated after exception handler completes ag.PopLineUpdated(lineUpdated), Ast.Default(body.Type) ); } else { result = body; } try { return AddFinally(ag, result, noNestedException); } finally { // free all locals here after the children nodes have been generated if (lineUpdated != null) { ag.FreeTemp(lineUpdated); } if (runElse != null) { ag.FreeTemp(@runElse); } } }
private MSAst.Expression AddFinally(AstGenerator/*!*/ ag, MSAst.Expression/*!*/ body, MSAst.ParameterExpression noNestedException) { if (_finally != null) { Debug.Assert(noNestedException != null); MSAst.ParameterExpression nestedFrames = ag.GetTemporary("$nestedFrames", typeof(List<DynamicStackFrame>)); bool inFinally = ag.InFinally; ag.InFinally = true; MSAst.Expression @finally = ag.Transform(_finally); ag.InFinally = inFinally; if (@finally == null) { // error reported during compilation return null; } if (ag.TrackLines) { // lots is going on here. We need to consider: // 1. Exceptions propagating out of try/except/finally. Here we need to save the line # // from the exception block and not save the # from the finally block later. // 2. Exceptions propagating out of the finally block. Here we need to report the line number // from the finally block and leave the existing stack traces cleared. // 3. Returning from the try block: Here we need to run the finally block and not update the // line numbers. body = AstUtils.Try(// we use a filter to know when we have an exception and when control leaves normally (via // either a return or the body completing successfully). AstUtils.Try( ag.AddDebugInfo(Ast.Empty(), new SourceSpan(Span.Start, _header)), Ast.Assign(noNestedException, Ast.Constant(true)), body ).Filter( typeof(Exception), // condition is never true, just note the exception and let it propagate Ast.Equal( Ast.Assign(noNestedException, Ast.Constant(false)), Ast.Constant(true) ), Ast.Default(body.Type) ) ).Finally( // if we had an exception save the line # that was last executing during the try AstUtils.If( Ast.Not(noNestedException), ag.GetLineNumberUpdateExpression(false) ), // clear the frames incase thae finally throws, and allow line number // updates to proceed ag.UpdateLineUpdated(false), Ast.Assign( nestedFrames, Ast.Call(AstGenerator.GetHelperMethod("GetAndClearDynamicStackFrames")) ), // run the finally code @finally, // if the finally exits normally restore any previous exception info Ast.Call( AstGenerator.GetHelperMethod("SetDynamicStackFrames"), nestedFrames ), ag.UpdateLineUpdated(true) ); ag.FreeTemp(nestedFrames); ag.FreeTemp(noNestedException); } else { body = AstUtils.Try(body).Finally( Ast.Assign( nestedFrames, Ast.Call(AstGenerator.GetHelperMethod("GetAndClearDynamicStackFrames")) ), // run the finally code @finally, // if the finally exits normally restore any previous exception info Ast.Call( AstGenerator.GetHelperMethod("SetDynamicStackFrames"), nestedFrames ) ); } } return body; }
private MSAst.Expression AddFinally(AstGenerator/*!*/ ag, MSAst.Expression/*!*/ body, MSAst.ParameterExpression nestedException) { if (_finally != null) { bool isEmitting = ag._isEmittingFinally; ag._isEmittingFinally = true; int loopId = ++ag._loopOrFinallyId; ag.LoopOrFinallyIds.Add(loopId, true); try { Debug.Assert(nestedException != null); MSAst.ParameterExpression nestedFrames = ag.GetTemporary("$nestedFrames", typeof(List<DynamicStackFrame>)); bool inFinally = ag.InFinally; ag.InFinally = true; MSAst.Expression @finally = ag.Transform(_finally); ag.InFinally = inFinally; if (@finally == null) { // error reported during compilation return null; } // lots is going on here. We need to consider: // 1. Exceptions propagating out of try/except/finally. Here we need to save the line # // from the exception block and not save the # from the finally block later. // 2. Exceptions propagating out of the finally block. Here we need to report the line number // from the finally block and leave the existing stack traces cleared. // 3. Returning from the try block: Here we need to run the finally block and not update the // line numbers. body = AstUtils.Try( // we use a fault to know when we have an exception and when control leaves normally (via // either a return or the body completing successfully). AstUtils.Try( ag.AddDebugInfo(AstUtils.Empty(), new SourceSpan(Span.Start, _header)), Ast.Assign(nestedException, AstUtils.Constant(false)), body ).Fault( // fault Ast.Assign(nestedException, AstUtils.Constant(true)) ) ).FinallyWithJumps( // if we had an exception save the line # that was last executing during the try AstUtils.If( nestedException, ag.GetSaveLineNumberExpression(false) ), // clear the frames incase thae finally throws, and allow line number // updates to proceed ag.UpdateLineUpdated(false), Ast.Assign( nestedFrames, Ast.Call(AstGenerator.GetHelperMethod("GetAndClearDynamicStackFrames")) ), // run the finally code @finally, // if the finally exits normally restore any previous exception info Ast.Call( AstGenerator.GetHelperMethod("SetDynamicStackFrames"), nestedFrames ), // if we took an exception in the try block we have saved the line number. Otherwise // we have no line number saved and will need to continue saving them if // other exceptions are thrown. AstUtils.If( nestedException, ag.UpdateLineUpdated(true) ) ); ag.FreeTemp(nestedFrames); ag.FreeTemp(nestedException); } finally { ag._isEmittingFinally = isEmitting; ag.LoopOrFinallyIds.Remove(loopId); } } return body; }
// This is a compound comparison operator like: a < b < c. // That's represented as binary operators, but it's not the same as (a<b) < c, so we do special transformations. // We need to: // - return true iff (a<b) && (b<c), but ensure that b is only evaluated once. // - ensure evaluation order is correct (a,b,c) // - don't evaluate c if a<b is false. private MSAst.Expression FinishCompare(MSAst.Expression left, AstGenerator ag) { Debug.Assert(_right is BinaryExpression); BinaryExpression bright = (BinaryExpression)_right; // Transform the left child of my right child (the next node in sequence) MSAst.Expression rleft = ag.Transform(bright.Left); // Store it in the temp MSAst.ParameterExpression temp = ag.GetTemporary("chained_comparison"); // Create binary operation: left <_op> (temp = rleft) MSAst.Expression comparison = MakeBinaryOperation( ag, _op, left, Ast.Assign(temp, AstUtils.Convert(rleft, temp.Type)), typeof(object), Span ); MSAst.Expression rright; // Transform rright, comparing to temp if (IsComparison(bright._right)) { rright = bright.FinishCompare(temp, ag); } else { MSAst.Expression transformedRight = ag.Transform(bright.Right); rright = MakeBinaryOperation( ag, bright.Operator, temp, transformedRight, typeof(object), bright.Span ); } ag.FreeTemp(temp); // return (left (op) (temp = rleft)) and (rright) return AstUtils.CoalesceTrue( ag.Block, comparison, rright, AstGenerator.GetHelperMethod("IsTrue") ); }
internal override MSAst.Expression TransformSet(AstGenerator ag, SourceSpan span, MSAst.Expression right, Operators op) { if (op != Operators.None) { ag.AddError("augmented assign to sequence prohibited", Span); return null; } // if we just have a simple named multi-assignment (e.g. a, b = 1,2) // then go ahead and step over the entire statement at once. If we have a // more complex statement (e.g. a.b, c.d = 1, 2) then we'll step over the // sets individually as they could be property sets the user wants to step // into. TODO: Enable stepping of the right hand side? bool emitIndividualSets = false; foreach (Expression e in _items) { if (IsComplexAssignment(e)) { emitIndividualSets = true; break; } } SourceSpan rightSpan = SourceSpan.None; SourceSpan leftSpan = (Span.Start.IsValid && span.IsValid) ? new SourceSpan(Span.Start, span.End) : SourceSpan.None; SourceSpan totalSpan = SourceSpan.None; if (emitIndividualSets) { rightSpan = span; leftSpan = SourceSpan.None; totalSpan = (Span.Start.IsValid && span.IsValid) ? new SourceSpan(Span.Start, span.End) : SourceSpan.None; } MSAst.Expression[] statements = new MSAst.Expression[4]; // 1. Evaluate the expression and assign the value to the temp. MSAst.ParameterExpression right_temp = ag.GetTemporary("unpacking"); // 2. Add the assignment "right_temp = right" into the suite/block statements[0] = ag.MakeAssignment(right_temp, right); // 3. Call GetEnumeratorValues on the right side (stored in temp) MSAst.Expression enumeratorValues = Ast.Call( AstGenerator.GetHelperMethod("GetEnumeratorValues"), // method // arguments AstUtils.CodeContext(), right_temp, Ast.Constant(_items.Length) ); // 4. Create temporary variable for the array MSAst.ParameterExpression array_temp = ag.GetTemporary("array", typeof(object[])); // 5. Assign the value of the method call (mce) into the array temp // And add the assignment "array_temp = Ops.GetEnumeratorValues(...)" into the block statements[1] = ag.MakeAssignment( array_temp, enumeratorValues, rightSpan ); MSAst.Expression[] sets = new MSAst.Expression[_items.Length + 1]; for (int i = 0; i < _items.Length; i ++) { // target = array_temp[i] Expression target = _items[i]; if (target == null) { continue; } // 6. array_temp[i] MSAst.Expression element = Ast.ArrayAccess( array_temp, // array expression Ast.Constant(i) // index ); // 7. target = array_temp[i], and add the transformed assignment into the list of sets MSAst.Expression set = target.TransformSet( ag, emitIndividualSets ? // span target.Span : SourceSpan.None, element, Operators.None ); if (set == null) { throw PythonOps.SyntaxError(string.Format("can't assign to {0}", target.NodeName), ag.Context.SourceUnit, target.Span, -1); } sets[i] = set; } // 9. add the sets as their own block so they can be marked as a single span, if necessary. sets[_items.Length] = Ast.Empty(); statements[2] = ag.AddDebugInfo(Ast.Block(sets), leftSpan); // 10. Free the temps ag.FreeTemp(array_temp); ag.FreeTemp(right_temp); // 11. Return the suite statement (block) statements[3] = Ast.Empty(); return ag.AddDebugInfo(Ast.Block(statements), totalSpan); }
private MSAst.Expression AssignOne(AstGenerator ag) { Debug.Assert(_left.Length == 1); SequenceExpression seLeft = _left[0] as SequenceExpression; SequenceExpression seRight = _right as SequenceExpression; if (seLeft != null && seRight != null && seLeft.Items.Length == seRight.Items.Length) { int cnt = seLeft.Items.Length; // a, b = 1, 2, or [a,b] = 1,2 - not something like a, b = range(2) // we can do a fast parallel assignment MSAst.ParameterExpression[] tmps = new MSAst.ParameterExpression[cnt]; MSAst.Expression[] body = new MSAst.Expression[cnt * 2 + 1]; // generate the body, the 1st n are the temporary assigns, the // last n are the assignments to the left hand side // 0: tmp0 = right[0] // ... // n-1: tmpn-1 = right[n-1] // n: right[0] = tmp0 // ... // n+n-1: right[n-1] = tmpn-1 // allocate the temps first before transforming so we don't pick up a bad temp... for (int i = 0; i < cnt; i++) { MSAst.Expression tmpVal = ag.Transform(seRight.Items[i]); tmps[i] = ag.GetTemporary("parallelAssign", tmpVal.Type); body[i] = Ast.Assign(tmps[i], tmpVal); } // then transform which can allocate more temps for (int i = 0; i < cnt; i++) { body[i + cnt] = seLeft.Items[i].TransformSet(ag, SourceSpan.None, tmps[i], PythonOperationKind.None); } // finally free the temps. foreach (MSAst.ParameterExpression variable in tmps) { ag.FreeTemp(variable); } // 4. Create and return the resulting suite body[cnt * 2] = AstUtils.Empty(); return ag.AddDebugInfo(Ast.Block(body), Span); } return _left[0].TransformSet(ag, Span, ag.Transform(_right), PythonOperationKind.None); }