/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

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