BOO-999
[boo.git] / src / Boo.Lang.Compiler / Steps / GeneratorMethodProcessor.cs
blob41aad2ae4bfdfbfb32c514f411628df05fd48241
1 #region license
2 // Copyright (c) 2003, 2004, 2005 Rodrigo B. de Oliveira (rbo@acm.org)
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without modification,
6 // are permitted provided that the following conditions are met:
7 //
8 // * Redistributions of source code must retain the above copyright notice,
9 // this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright notice,
11 // this list of conditions and the following disclaimer in the documentation
12 // and/or other materials provided with the distribution.
13 // * Neither the name of Rodrigo B. de Oliveira nor the names of its
14 // contributors may be used to endorse or promote products derived from this
15 // software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #endregion
30 namespace Boo.Lang.Compiler.Steps
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Diagnostics;
35 using Boo.Lang.Compiler;
36 using Boo.Lang.Compiler.Ast;
37 using Boo.Lang.Compiler.TypeSystem;
39 internal class GeneratorMethodProcessor : AbstractTransformerCompilerStep
41 InternalMethod _generator;
43 InternalMethod _moveNext;
45 BooClassBuilder _enumerable;
47 BooClassBuilder _enumerator;
49 BooMethodBuilder _enumeratorConstructor;
51 BooMethodBuilder _enumerableConstructor;
53 IField _state;
55 IMethod _yield;
57 Field _externalEnumeratorSelf;
59 List _labels;
60 List<TryStatementInfo> _tryStatementInfoForLabels = new List<TryStatementInfo>();
62 Hashtable _mapping;
64 IType _generatorItemType;
66 /// <summary>
67 /// used for expressionless yield statements when the generator type
68 /// is a value type (and thus 'null' is not an appropriate value)
69 /// </summary>
70 Field _nullValueField;
72 public GeneratorMethodProcessor(CompilerContext context, InternalMethod method)
74 _labels = new List();
75 _mapping = new Hashtable();
76 _generator = method;
77 _generatorItemType = (IType)_generator.Method["GeneratorItemType"];
78 _enumerable = (BooClassBuilder)_generator.Method["GeneratorClassBuilder"];
79 Debug.Assert(null != _generatorItemType);
80 Debug.Assert(null != _enumerable);
81 Initialize(context);
84 public LexicalInfo LexicalInfo
86 get { return _generator.Method.LexicalInfo; }
89 public InternalMethod MoveNextMethod
91 get { return _moveNext; }
94 override public void Run()
96 CreateEnumerableConstructor();
97 CreateEnumerator();
98 MethodInvocationExpression enumerableConstructorInvocation = CodeBuilder.CreateConstructorInvocation(_enumerable.ClassDefinition);
99 MethodInvocationExpression enumeratorConstructorInvocation = CodeBuilder.CreateConstructorInvocation(_enumerator.ClassDefinition);
100 PropagateReferences(enumerableConstructorInvocation, enumeratorConstructorInvocation);
101 CreateGetEnumerator(enumeratorConstructorInvocation);
102 FixGeneratorMethodBody(enumerableConstructorInvocation);
105 void FixGeneratorMethodBody(MethodInvocationExpression enumerableConstructorInvocation)
107 Block body = _generator.Method.Body;
108 body.Clear();
110 body.Add(
111 new ReturnStatement(
112 _generator.Method.LexicalInfo,
113 GeneratorReturnsIEnumerator()
114 ? CreateGetEnumeratorInvocation(enumerableConstructorInvocation)
115 : enumerableConstructorInvocation));
118 void PropagateReferences(MethodInvocationExpression enumerableConstructorInvocation,
119 MethodInvocationExpression enumeratorConstructorInvocation)
121 // propagate the necessary parameters from
122 // the enumerable to the enumerator
123 foreach (ParameterDeclaration parameter in _generator.Method.Parameters)
125 InternalParameter entity = (InternalParameter)parameter.Entity;
126 if (entity.IsUsed)
128 enumerableConstructorInvocation.Arguments.Add(
129 CodeBuilder.CreateReference(parameter));
131 PropagateFromEnumerableToEnumerator(enumeratorConstructorInvocation,
132 entity.Name,
133 entity.Type);
137 // propagate the external self reference if necessary
138 if (null != _externalEnumeratorSelf)
140 IType type = (IType)_externalEnumeratorSelf.Type.Entity;
141 enumerableConstructorInvocation.Arguments.Add(CodeBuilder.CreateSelfReference(type));
143 PropagateFromEnumerableToEnumerator(enumeratorConstructorInvocation,
144 "self_",
145 type);
150 private MethodInvocationExpression CreateGetEnumeratorInvocation(MethodInvocationExpression enumerableConstructorInvocation)
152 return CodeBuilder.CreateMethodInvocation(
153 enumerableConstructorInvocation,
154 GetGetEnumeratorEntity());
157 private InternalMethod GetGetEnumeratorEntity()
159 return GetGetEnumeratorBuilder().Entity;
162 private bool GeneratorReturnsIEnumerator()
164 bool returnsEnumerator = _generator.ReturnType == TypeSystemServices.IEnumeratorType;
165 returnsEnumerator |=
166 _generator.ReturnType.ConstructedInfo != null &&
167 _generator.ReturnType.ConstructedInfo.GenericDefinition == TypeSystemServices.IEnumeratorGenericType;
169 return returnsEnumerator;
172 void CreateGetEnumerator(Expression enumeratorExpression)
174 BooMethodBuilder method = GetGetEnumeratorBuilder();
175 method.Body.Add(new ReturnStatement(enumeratorExpression));
178 private BooMethodBuilder GetGetEnumeratorBuilder()
180 return (BooMethodBuilder)_generator.Method["GetEnumeratorBuilder"];
183 void CreateEnumerableConstructor()
185 _enumerableConstructor = CreateConstructor(_enumerable);
188 void CreateEnumeratorConstructor()
190 _enumeratorConstructor = CreateConstructor(_enumerator);
193 void CreateEnumerator()
195 IType abstractEnumeratorType =
196 TypeSystemServices.Map(typeof(Boo.Lang.GenericGeneratorEnumerator<>)).
197 GenericInfo.ConstructType(new IType[] {_generatorItemType});
199 _state = NameResolutionService.ResolveField(abstractEnumeratorType, "_state");
200 _yield = NameResolutionService.ResolveMethod(abstractEnumeratorType, "Yield");
202 _enumerator = CodeBuilder.CreateClass("$");
203 _enumerator.AddAttribute(CodeBuilder.CreateAttribute(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute)));
204 _enumerator.Modifiers |= TypeMemberModifiers.Final;
205 _enumerator.LexicalInfo = this.LexicalInfo;
206 _enumerator.AddBaseType(abstractEnumeratorType);
207 _enumerator.AddBaseType(TypeSystemServices.IEnumeratorType);
209 CreateEnumeratorConstructor();
210 CreateMoveNext();
212 _enumerable.ClassDefinition.Members.Add(_enumerator.ClassDefinition);
213 //new Boo.Lang.Compiler.Ast.Visitors.BooPrinterVisitor(System.Console.Out).Visit(_enumerator.ClassDefinition);
216 void CreateMoveNext()
218 Method generator = _generator.Method;
220 BooMethodBuilder methodBuilder = _enumerator.AddVirtualMethod("MoveNext", TypeSystemServices.BoolType);
221 methodBuilder.Method.LexicalInfo = generator.LexicalInfo;
222 _moveNext = methodBuilder.Entity;
224 foreach (Local local in generator.Locals)
226 InternalLocal entity = (InternalLocal)local.Entity;
228 Field field = _enumerator.AddInternalField("$" + entity.Name + "$" + _context.AllocIndex(), entity.Type);
229 _mapping[entity] = field.Entity;
231 generator.Locals.Clear();
233 foreach (ParameterDeclaration parameter in generator.Parameters)
235 InternalParameter entity = (InternalParameter)parameter.Entity;
236 if (entity.IsUsed)
238 Field field = DeclareFieldInitializedFromConstructorParameter(_enumerator, _enumeratorConstructor,
239 entity.Name,
240 entity.Type);
241 _mapping[entity] = field.Entity;
245 methodBuilder.Body.Add(CreateLabel(generator));
246 // Visit() needs to know the number of the finished state
247 _finishedStateNumber = _labels.Count;
248 LabelStatement finishedLabel = CreateLabel(generator);
249 methodBuilder.Body.Add(generator.Body);
250 generator.Body.Clear();
252 Visit(methodBuilder.Body);
254 methodBuilder.Body.Add(CreateYieldInvocation(null, _finishedStateNumber));
255 methodBuilder.Body.Add(finishedLabel);
257 methodBuilder.Body.Insert(0,
258 CodeBuilder.CreateSwitch(
259 this.LexicalInfo,
260 CodeBuilder.CreateMemberReference(_state),
261 _labels));
263 // if the method contains converted try statements, put it in a try/failure block
264 if (_convertedTryStatements.Count > 0)
266 IMethod dispose = CreateDisposeMethod();
268 TryStatement tryFailure = new TryStatement();
269 tryFailure.ProtectedBlock.Add(methodBuilder.Body);
270 tryFailure.FailureBlock = new Block();
271 tryFailure.FailureBlock.Add(CallMethodOnSelf(dispose));
272 methodBuilder.Body.Clear();
273 methodBuilder.Body.Add(tryFailure);
277 MethodInvocationExpression CallMethodOnSelf(IMethod method)
279 return CodeBuilder.CreateMethodInvocation(
280 CodeBuilder.CreateSelfReference(_enumerator.Entity),
281 method);
284 IMethod CreateDisposeMethod()
286 BooMethodBuilder mn = _enumerator.AddVirtualMethod("Dispose", TypeSystemServices.VoidType);
287 mn.Method.LexicalInfo = this.LexicalInfo;
289 LabelStatement noEnsure = CodeBuilder.CreateLabel(_generator.Method, "noEnsure").LabelStatement;
290 mn.Body.Add(noEnsure);
291 mn.Body.Add(SetStateTo(_finishedStateNumber));
292 mn.Body.Add(new ReturnStatement());
294 // Create a section calling all ensure methods for each converted try block
295 LabelStatement[] disposeLabels = new LabelStatement[_labels.Count];
296 for (int i = 0; i < _convertedTryStatements.Count; i++) {
297 TryStatementInfo info = _convertedTryStatements[i];
298 disposeLabels[info._stateNumber] = CodeBuilder.CreateLabel(_generator.Method, "$ensure_" + info._stateNumber).LabelStatement;
299 mn.Body.Add(disposeLabels[info._stateNumber]);
300 mn.Body.Add(SetStateTo(_finishedStateNumber));
301 Block block = mn.Body;
302 while (info._parent != null) {
303 TryStatement ts = new TryStatement();
304 block.Add(ts);
305 ts.ProtectedBlock.Add(CallMethodOnSelf(info._ensureMethod));
306 block = ts.EnsureBlock = new Block();
307 info = info._parent;
309 block.Add(CallMethodOnSelf(info._ensureMethod));
310 mn.Body.Add(new ReturnStatement());
313 // now map the labels of the suspended states to the labels we just created
314 for (int i = 0; i < _labels.Count; i++) {
315 if (_tryStatementInfoForLabels[i] == null)
316 disposeLabels[i] = noEnsure;
317 else
318 disposeLabels[i] = disposeLabels[_tryStatementInfoForLabels[i]._stateNumber];
321 mn.Body.Insert(0, CodeBuilder.CreateSwitch(
322 this.LexicalInfo,
323 CodeBuilder.CreateMemberReference(_state),
324 disposeLabels));
325 return mn.Entity;
328 void PropagateFromEnumerableToEnumerator(MethodInvocationExpression enumeratorConstructorInvocation,
329 string parameterName,
330 IType parameterType)
332 Field field = DeclareFieldInitializedFromConstructorParameter(_enumerable, _enumerableConstructor, parameterName, parameterType);
333 enumeratorConstructorInvocation.Arguments.Add(
334 CodeBuilder.CreateReference(field));
337 Field DeclareFieldInitializedFromConstructorParameter(BooClassBuilder type,
338 BooMethodBuilder constructor,
339 string parameterName,
340 IType parameterType)
342 Field field = type.AddInternalField("$" + parameterName + _context.AllocIndex(), parameterType);
343 InitializeFieldFromConstructorParameter(constructor, field, parameterName, parameterType);
344 return field;
347 void InitializeFieldFromConstructorParameter(BooMethodBuilder constructor,
348 Field field,
349 string parameterName,
350 IType parameterType)
352 ParameterDeclaration parameter = constructor.AddParameter(parameterName, parameterType);
353 constructor.Body.Add(
354 CodeBuilder.CreateAssignment(
355 CodeBuilder.CreateReference(field),
356 CodeBuilder.CreateReference(parameter)));
359 override public void OnReferenceExpression(ReferenceExpression node)
361 InternalField mapped = (InternalField)_mapping[node.Entity];
362 if (null != mapped)
364 ReplaceCurrentNode(
365 CodeBuilder.CreateMemberReference(
366 node.LexicalInfo,
367 CodeBuilder.CreateSelfReference(_enumerator.Entity),
368 mapped));
372 override public void OnSelfLiteralExpression(SelfLiteralExpression node)
374 if (null == _externalEnumeratorSelf)
376 IType type = node.ExpressionType;
377 _externalEnumeratorSelf = DeclareFieldInitializedFromConstructorParameter(
378 _enumerator,
379 _enumeratorConstructor,
380 "self_",
381 type);
384 ReplaceCurrentNode(CodeBuilder.CreateReference(node.LexicalInfo, _externalEnumeratorSelf));
387 class TryStatementInfo
389 internal TryStatement _statement;
390 internal TryStatementInfo _parent;
392 internal bool _containsYield;
393 internal int _stateNumber = -1;
394 internal Block _replacement;
396 internal IMethod _ensureMethod;
399 List<TryStatementInfo> _convertedTryStatements = new List<TryStatementInfo>();
400 Stack<TryStatementInfo> _tryStatementStack = new Stack<TryStatementInfo>();
401 int _finishedStateNumber;
403 public override bool EnterTryStatement(TryStatement node)
405 TryStatementInfo info = new TryStatementInfo();
406 info._statement = node;
407 if (_tryStatementStack.Count > 0)
408 info._parent = _tryStatementStack.Peek();
409 _tryStatementStack.Push(info);
410 return true;
413 BinaryExpression SetStateTo(int num)
415 return CodeBuilder.CreateAssignment(CodeBuilder.CreateMemberReference(_state),
416 CodeBuilder.CreateIntegerLiteral(num));
419 public override void LeaveTryStatement(TryStatement node)
421 TryStatementInfo info = _tryStatementStack.Pop();
422 if (info._containsYield) {
423 ReplaceCurrentNode(info._replacement);
424 TryStatementInfo currentTry = (_tryStatementStack.Count > 0) ? _tryStatementStack.Peek() : null;
425 info._replacement.Add(node.ProtectedBlock);
426 if (currentTry != null) {
427 ConvertTryStatement(currentTry);
428 info._replacement.Add(SetStateTo(currentTry._stateNumber));
429 } else {
430 // leave try block, reset state to prevent ensure block from being called again
431 info._replacement.Add(SetStateTo(_finishedStateNumber));
433 BooMethodBuilder ensureMethod = _enumerator.AddMethod("$ensure" + info._stateNumber, TypeSystemServices.VoidType, TypeMemberModifiers.Private);
434 ensureMethod.Body.Add(info._statement.EnsureBlock);
435 info._ensureMethod = ensureMethod.Entity;
436 info._replacement.Add(CallMethodOnSelf(ensureMethod.Entity));
437 _convertedTryStatements.Add(info);
441 void ConvertTryStatement(TryStatementInfo currentTry)
443 if (currentTry._containsYield)
444 return;
445 currentTry._containsYield = true;
446 currentTry._stateNumber = _labels.Count;
447 Block tryReplacement = new Block();
448 //tryReplacement.Add(CreateLabel(tryReplacement));
449 // when the MoveNext() is called while the enumerator is still in running state, don't jump to the
450 // try block, but handle it like MoveNext() calls when the enumerator is in the finished state.
451 _labels.Add(_labels[_finishedStateNumber]);
452 _tryStatementInfoForLabels.Add(currentTry);
453 tryReplacement.Add(SetStateTo(currentTry._stateNumber));
454 currentTry._replacement = tryReplacement;
457 override public void LeaveYieldStatement(YieldStatement node)
459 TryStatementInfo currentTry = (_tryStatementStack.Count > 0) ? _tryStatementStack.Peek() : null;
460 if (currentTry != null) {
461 ConvertTryStatement(currentTry);
463 Block block = new Block();
464 block.Add(
465 new ReturnStatement(
466 node.LexicalInfo,
467 CreateYieldInvocation(node.Expression, _labels.Count),
468 null));
469 block.Add(CreateLabel(node));
470 // setting the state back to the "running" state not required, as that state has the same ensure blocks
471 // as the state we are currently in.
472 // if (currentTry != null) {
473 // block.Add(SetStateTo(currentTry._stateNumber));
474 // }
475 ReplaceCurrentNode(block);
478 MethodInvocationExpression CreateYieldInvocation(Expression value, int newState)
480 MethodInvocationExpression invocation = CodeBuilder.CreateMethodInvocation(
481 CodeBuilder.CreateSelfReference(_enumerator.Entity),
482 _yield,
483 CodeBuilder.CreateIntegerLiteral(newState),
484 value == null ? GetDefaultYieldValue() : value);
485 if (null != value) invocation.LexicalInfo = value.LexicalInfo;
486 return invocation;
489 private Expression GetDefaultYieldValue()
491 if (_generatorItemType.IsValueType)
493 if (null == _nullValueField)
495 _nullValueField = _enumerator.AddField("$empty", _generatorItemType);
497 return CodeBuilder.CreateReference(_nullValueField);
499 return new NullLiteralExpression();
502 LabelStatement CreateLabel(Node sourceNode)
504 InternalLabel label = CodeBuilder.CreateLabel(sourceNode, "$state$" + _labels.Count);
505 _labels.Add(label.LabelStatement);
506 _tryStatementInfoForLabels.Add(_tryStatementStack.Count > 0 ? _tryStatementStack.Peek() : null);
507 return label.LabelStatement;
510 BooMethodBuilder CreateConstructor(BooClassBuilder builder)
512 BooMethodBuilder constructor = builder.AddConstructor();
513 constructor.Body.Add(CodeBuilder.CreateSuperConstructorInvocation(builder.Entity.BaseType));
514 return constructor;