PageRenderTime 84ms CodeModel.GetById 20ms app.highlight 34ms RepoModel.GetById 13ms app.codeStats 1ms

/Rhino.Etl.Dsl/Macros/AbstractClassGeneratorMacro.cs

http://github.com/ayende/rhino-etl
C# | 290 lines | 198 code | 29 blank | 63 comment | 34 complexity | aac15a1d64b3b080ff810295c544720f MD5 | raw file
  1namespace Rhino.Etl.Dsl.Macros
  2{
  3    using System;
  4    using System.Collections.Generic;
  5    using System.Reflection;
  6    using Boo.Lang.Compiler;
  7    using Boo.Lang.Compiler.Ast;
  8    using Module = Boo.Lang.Compiler.Ast.Module;
  9
 10    /// <summary>
 11    /// Allow to easily generate a class from the DSL file
 12    /// </summary>
 13    /// <typeparam name="T">Base class</typeparam>
 14    public abstract class AbstractClassGeneratorMacro<T> : AbstractAstMacro
 15    {
 16        private readonly string blockMethodName;
 17
 18        /// <summary>
 19        /// Gets the start index of the arguments collection.
 20        /// </summary>
 21        protected int argumentStartIndex = 0;
 22
 23        private ClassDefinition classDefinition;
 24
 25        private bool isAnonymous = false;
 26
 27        /// <summary>
 28        /// Initializes a new instance of the <see cref="AbstractClassGeneratorMacro&lt;T&gt;"/> class.
 29        /// </summary>
 30        /// <param name="blockMethodName">Name of the method to move the block to, null if this is not permitted</param>
 31        protected AbstractClassGeneratorMacro(string blockMethodName)
 32        {
 33            this.blockMethodName = blockMethodName;
 34        }
 35
 36        /// <summary>
 37        /// Gets the name of the macro.
 38        /// </summary>
 39        /// <value>The name of the macro.</value>
 40        private string MacroName
 41        {
 42            get { return GetType().Name; }
 43        }
 44
 45        private IList<ParameterDeclaration> BuildParameters(MethodInfo method)
 46        {
 47            List<ParameterDeclaration> list = new List<ParameterDeclaration>();
 48            foreach (ParameterInfo info in method.GetParameters())
 49            {
 50                ParameterDeclaration declaration =
 51                    new ParameterDeclaration(info.Name, CodeBuilder.CreateTypeReference(info.ParameterType));
 52                list.Add(declaration);
 53            }
 54            return list;
 55        }
 56
 57        /// <summary>
 58        /// Expands the specified macro
 59        /// </summary>
 60        /// <param name="macro">The macro.</param>
 61        /// <returns></returns>
 62        public override Statement Expand(MacroStatement macro)
 63        {
 64            classDefinition = CreateClassDefinition(macro);
 65
 66            CreateMethodFromMacroBlock(macro, classDefinition);
 67
 68            Module ancestor = (Module)macro.GetAncestor(NodeType.Module);
 69            ancestor.Members.Add(classDefinition);
 70
 71            AddMemberMethods(macro);
 72
 73            if (isAnonymous == false)
 74                return null;
 75
 76            ReferenceExpression typeName = AstUtil.CreateReferenceExpression(GetClassName(macro));
 77            MethodInvocationExpression createInstance = new MethodInvocationExpression(typeName);
 78            return new ExpressionStatement(createInstance);
 79        }
 80
 81        private void AddMemberMethods(MacroStatement macro)
 82        {
 83            if (Members(macro) != null)
 84            {
 85                foreach (Method method in Members(macro))
 86                {
 87                    classDefinition.Members.Add(method);
 88                }
 89            }
 90        }
 91
 92        private ClassDefinition CreateClassDefinition(MacroStatement macro)
 93        {
 94            ClassDefinition classDefinition = new ClassDefinition(macro.LexicalInfo);
 95            classDefinition.BaseTypes.Add(new SimpleTypeReference(typeof(T).FullName));
 96            classDefinition.Name = GetClassName(macro);
 97            Constructor ctor = new Constructor();
 98            MethodInvocationExpression super = new MethodInvocationExpression(new SuperLiteralExpression());
 99            ctor.Body.Add(super);
100
101            MoveConstructorArguments(ctor, super, macro);
102            classDefinition.Members.Add(ctor);
103            return classDefinition;
104        }
105
106        /// <summary>
107        /// Gets the method to override.
108        /// </summary>
109        /// <param name="macro">The macro.</param>
110        /// <returns></returns>
111        private MethodInfo GetMethodToOverride(Node macro)
112        {
113            MethodInfo overridenMethod = null;
114            if (blockMethodName != null)
115            {
116                try
117                {
118                    overridenMethod =
119                        typeof(T).GetMethod(blockMethodName,
120                                             BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
121                }
122                catch (AmbiguousMatchException)
123                {
124                    string msg = typeof (T).Name + " has more than one overload for method " + blockMethodName;
125                    Errors.Add(CompilerErrorFactory.CustomError(macro.LexicalInfo, msg));
126                
127                }
128                catch (Exception exception)
129                {
130                    string msg =
131                        string.Format("Error occured when trying to get method '{0}' on {1}. {2}",
132                        blockMethodName, typeof(T).Name, exception);
133                    Errors.Add(CompilerErrorFactory.CustomError(macro.LexicalInfo, msg));
134                }
135                if (overridenMethod == null)
136                {
137                    Errors.Add(
138                        CompilerErrorFactory.CustomError(macro.LexicalInfo,
139                                                         "Could not find " + blockMethodName + " on " +
140                                                         typeof(T).FullName));
141                }
142            }
143            return overridenMethod;
144        }
145
146        private void CreateMethodFromMacroBlock(MacroStatement macro, TypeDefinition classDefinition)
147        {
148            MethodInfo methodToOverride = GetMethodToOverride(macro);
149
150            if (macro.Body != null && macro.Body.Statements.Count > 0)
151            {
152                if (methodToOverride == null)
153                {
154                    Errors.Add(
155                        CompilerErrorFactory.CustomError(macro.LexicalInfo, MacroName + " cannot be use with a block"));
156                }
157                Method method = new Method(blockMethodName);
158                method.Modifiers = TypeMemberModifiers.Override;
159                method.Body = macro.Body;
160                classDefinition.Members.Add(method);
161                method.Parameters.Extend(BuildParameters(methodToOverride));
162            }
163        }
164
165        /// <summary>
166        /// Gets the name of the class that we will generate
167        /// </summary>
168        /// <param name="macro">The macro.</param>
169        /// <returns></returns>
170        protected virtual string GetClassName(MacroStatement macro)
171        {
172            if (macro.Arguments.Count == 0 || (macro.Arguments[0] is ReferenceExpression) == false)
173            {
174                return GetAnonymousClassName(macro);
175            }
176            argumentStartIndex = 1;
177
178            ReferenceExpression referenceExpression = macro.Arguments[0] as ReferenceExpression;
179            if (referenceExpression == null)
180            {
181                Errors.Add(
182                    CompilerErrorFactory.CustomError(macro.LexicalInfo,
183                                                     GetType().Name + " first parameter must be a valid name."));
184                return null;
185            }
186            return referenceExpression.Name;
187        }
188
189        /// <summary>
190        /// Gets the name of the anonymous class.
191        /// </summary>
192        /// <param name="macro">The macro.</param>
193        /// <returns></returns>
194        private string GetAnonymousClassName(MacroStatement macro)
195        {
196            if (macro.GetAncestor(NodeType.MacroStatement) == null)
197            {
198                Errors.Add(
199                    CompilerErrorFactory.CustomError(macro.LexicalInfo,
200                                                     GetType().Name + " must have a single parameter, the name of the " +
201                                                     GetType().Name));
202                return null;
203            }
204            isAnonymous = true;
205            string name = typeof(T).Name.Replace("Abstract", "").Replace("Operation", "");
206            if (macro["anonymous_name_index"] == null)
207                macro["anonymous_name_index"] = Context.GetUniqueName();
208            return "Anonymous_" + name + "_" + macro["anonymous_name_index"];
209        }
210
211        /// <summary>
212        /// Moves the constructor arguments from the macro to the superInvocation method invocation
213        /// </summary>
214        /// <param name="constructor">The constructor.</param>
215        /// <param name="superInvocation">The create.</param>
216        /// <param name="macro">The macro.</param>
217        protected void MoveConstructorArguments(
218            Constructor constructor,
219            MethodInvocationExpression superInvocation,
220            MacroStatement macro)
221        {
222            for (int i = argumentStartIndex; i < macro.Arguments.Count; i++)
223            {
224                Expression argument = macro.Arguments[i];
225                BinaryExpression assign;
226                MethodInvocationExpression call;
227                if (TryGetAssignment(argument, out assign))
228                {
229                    constructor.Body.Add(assign);
230                }
231                else if(TryGetCall(argument, out call))
232                {
233                    constructor.Body.Add(call);
234                }
235                else
236                {
237                    superInvocation.Arguments.Add(argument);
238                }
239            }
240        }
241
242
243        /// <summary>
244        /// Tries to get an assignment from the expression
245        /// </summary>
246        /// <param name="expression">The expression.</param>
247        /// <param name="assign">The assign.</param>
248        /// <returns></returns>
249        protected static bool TryGetAssignment(Expression expression, out BinaryExpression assign)
250        {
251            assign = expression as BinaryExpression;
252            return (assign != null && assign.Operator == BinaryOperatorType.Assign);
253        }
254
255        /// <summary>
256        /// Tries to get a method call from the expression
257        /// </summary>
258        /// <param name="expression">The expression.</param>
259        /// <param name="call">The method call.</param>
260        /// <returns></returns>
261        protected static bool TryGetCall(Expression expression, out MethodInvocationExpression call)
262        {
263            call = expression as MethodInvocationExpression;
264            return (call != null && call.Target is ReferenceExpression);
265        }
266
267        /// <summary>
268        /// Add a method definition to the resultant class definition
269        /// </summary>
270        /// <param name="macro"></param>
271        /// <param name="method"></param>
272        protected void AddMethodDefinitionToClassDefinition(MacroStatement macro, Method method)
273        {
274            var members = (IList<Method>)macro["members"];
275            if (members == null)
276                macro["members"] = members = new List<Method>();
277            members.Add(method);
278        }
279
280        /// <summary>
281        /// Get the members collection from this macro
282        /// </summary>
283        /// <param name="macro">The macro.</param>
284        /// <returns></returns>
285        protected static IList<Method> Members(MacroStatement macro)
286        {
287            return (IList<Method>)macro["members"];
288        }
289    }
290}