Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Creating ASTTs The painful truth

953 Aufrufe

Veröffentlicht am

Talk about creating Groovy AST transformations. Pitfalls, wins, things to come...

Veröffentlicht in: Software
  • Als Erste(r) kommentieren

Creating ASTTs The painful truth

  1. 1. 1
  2. 2. 2
  3. 3. 3 . 1
  4. 4. 3 . 1
  5. 5. 3 . 1
  6. 6. …​ …​ 3 . 1
  7. 7. 3 . 2
  8. 8. 4 . 1
  9. 9. 4 . 1
  10. 10. 4 . 1
  11. 11. …​ 4 . 1
  12. 12. 4 . 2
  13. 13. 5
  14. 14. 5
  15. 15. 5
  16. 16. 5
  17. 17. 6 . 1
  18. 18. 6 . 2
  19. 19. 6 . 3
  20. 20. 6 . 3
  21. 21. 6 . 3
  22. 22. …​ 6 . 4
  23. 23. 6 . 5
  24. 24. 6 . 6
  25. 25. 6 . 7
  26. 26. ⇒ 1 == 1 6 . 8
  27. 27. ⇒ 1 == 1 6 . 8
  28. 28. ⇒ 1 == 1 6 . 8
  29. 29. ⇒ 1 == 1 6 . 8
  30. 30. ⇒ ⇒ ref.myMethod(3) 6 . 9
  31. 31. ⇒ ⇒ ref.myMethod(3) 6 . 9
  32. 32. ⇒ ⇒ ref.myMethod(3) 6 . 9
  33. 33. ⇒ ⇒ ref.myMethod(3) 6 . 9
  34. 34. 6 . 10
  35. 35. if(booleanExpression) { println "hello" // statement } 6 . 11
  36. 36. if(booleanExpression) { println "hello" // statement } 6 . 11
  37. 37. if(booleanExpression) { println "hello" // statement } 6 . 11
  38. 38. public void main(String[] args) { // block starts // this is inside a block statement } // block ends 6 . 12
  39. 39. public void main(String[] args) { // block starts // this is inside a block statement } // block ends 6 . 12
  40. 40. public void main(String[] args) { // block starts // this is inside a block statement } // block ends 6 . 12
  41. 41. public String greetings() { return "Hello Greach" } 6 . 13
  42. 42. 6 . 14
  43. 43. 6 . 15
  44. 44. 6 . 15
  45. 45. 6 . 15
  46. 46. 6 . 15
  47. 47. 6 . 15
  48. 48. …​ 6 . 15
  49. 49. …​ …​ class A { // ClassNode String greetings // FieldNode String hello() { // MethodNode } } 6 . 16
  50. 50. class A { // ClassNode String hello() // MethodNode { // blockStatement { return "Hello" // returnStatement(constantExpression) } // } } 6 . 17
  51. 51. 7 . 1
  52. 52. 7 . 2
  53. 53. 7 . 3
  54. 54. 7 . 3
  55. 55. 7 . 3
  56. 56. 7 . 4
  57. 57. 7 . 5
  58. 58. …​ 7 . 5
  59. 59. …​ 7 . 5
  60. 60. 7 . 6
  61. 61. 8 . 1
  62. 62. 8 . 2
  63. 63. 8 . 3
  64. 64. 8 . 4
  65. 65. 8 . 5
  66. 66. 8 . 6
  67. 67. 9
  68. 68. 10 . 1
  69. 69. 10 . 2
  70. 70. 10 . 3
  71. 71. 10 . 4
  72. 72. 10 . 5
  73. 73. 10 . 5
  74. 74. 10 . 5
  75. 75. 10 . 5
  76. 76. 11 . 1
  77. 77. package greach.local class A { @WithLogging void doSomething() { // println "Starting doSomething" println "mystuff" // println "Ending doSomething" } } 11 . 2
  78. 78. 11 . 3
  79. 79. …​ 11 . 3
  80. 80. …​ 11 . 3
  81. 81. 11 . 4
  82. 82. 11 . 4
  83. 83. 11 . 4
  84. 84. package greach.local import org.codehaus.groovy.transform.GroovyASTTransformationClass import java.lang.annotation.* (1) @Retention(RetentionPolicy.SOURCE) @Target([ElementType.METHOD]) (2) @GroovyASTTransformationClass( ["greach.local.WithLoggingExplainedTransformation"]) @interface WithLoggingExplained { } 11 . 5
  85. 85. 11 . 6
  86. 86. import org.codehaus.groovy.ast.expr.* import org.codehaus.groovy.ast.stmt.* import org.codehaus.groovy.ast.* import org.codehaus.groovy.transform.* 11 . 7
  87. 87. @GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS) class WithLoggingExplainedTransformation implements ASTTransformation { 11 . 8
  88. 88. @GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS) class WithLoggingExplainedTransformation implements ASTTransformation { 11 . 8
  89. 89. @GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS) class WithLoggingExplainedTransformation implements ASTTransformation { 11 . 8
  90. 90. @GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS) class WithLoggingExplainedTransformation implements ASTTransformation { 11 . 8
  91. 91. @Override void visit(ASTNode[] nodes, SourceUnit sourceUnit) { MethodNode method = (MethodNode) nodes[1] (1) def startMessage = createPrintlnAst("Starting $method.name") def endMessage = createPrintlnAst("Ending $method.name") def existingStatements = ((BlockStatement)method.code).statements (2) existingStatements.add(0, startMessage) existingStatements.add(endMessage) } 11 . 9
  92. 92. 11 . 10
  93. 93. 11 . 10
  94. 94. 11 . 10
  95. 95. private static Statement createPrintlnAst(String message) { new ExpressionStatement( new MethodCallExpression( new VariableExpression("this"), new ConstantExpression("println"), new ArgumentListExpression( new ConstantExpression(message) ) ) ) } 11 . 11
  96. 96. 11 . 12
  97. 97. 11 . 12
  98. 98. 11 . 12
  99. 99. 12 . 1
  100. 100. 12 . 2
  101. 101. 12 . 3
  102. 102. import static org.codehaus.groovy.ast.tools.GeneralUtils.* 12 . 4
  103. 103. 12 . 5
  104. 104. 12 . 5
  105. 105. 12 . 5
  106. 106. private static Statement createPrintlnAst(String message) { new ExpressionStatement( new MethodCallExpression( new VariableExpression("this"), new ConstantExpression("println"), new ArgumentListExpression( new ConstantExpression(message) ) ) ) } 12 . 6
  107. 107. private static Statement createPrintlnAst(String message) { return stmt(callThisX("println", args(constX(message)))) } 12 . 7
  108. 108. private static Statement createPrintlnAst(String message) { new ExpressionStatement( new MethodCallExpression( new VariableExpression("this"), new ConstantExpression("println"), new ArgumentListExpression( new ConstantExpression(message) ) ) ) } private static Statement createPrintlnAst(String message) { return stmt(callThisX("println", args(constX(message)))) } 12 . 8
  109. 109. …​ 12 . 9
  110. 110. 12 . 10
  111. 111. BlockStatement getBlockStmt() { ASTNode[] stmts = new AstBuilder().buildFromCode { return number % 2 == 0 } return stmts.first() as BlockStatement } 12 . 11
  112. 112. BlockStatement getMD5Code(final String propertyName) { def blockStatement = new AstBuilder().buildFromString """ java.security.MessageDigest.getInstance('MD5') .digest(${propertyName}.getBytes('UTF-8')) .encodeHex() .toString() """ return blockStatement.first() as BlockStatement } 12 . 12
  113. 113. 12 . 13
  114. 114. 13 . 1
  115. 115. @GroovyASTTransformation(phase=CompilePhase.INSTRUCTION_SELECTION) class PlayAst extends ExceptionFriendlyAst{ static final PLAY_METHOD_NAME = "play" static final PLAY_METHOD_PARAM_NAME = "params" /* We need to inject a DataFlows instance in a variable called "flow" */ void processNodes(ASTNode[] astNodes,SourceUnit sourceUnit){ /* Checking constraints */ if (!astNodes) return if (!astNodes[0] || !astNodes[1]) return if (!(astNodes[0] instanceof AnnotationNode)) return if (astNodes[0].classNode?.name != Play.class.name) return if (!(astNodes[1] instanceof MethodNode)) return 13 . 2
  116. 116. …​ …​ package greach.builder class Order { @ToMD5 String name @ToMD5 String description } Order order = new Order(name: "john", description: "desc") assert order.nameToMD5() == "527bd5b5d689e2c32ae974c6229ff785" assert order.descriptionToMD5() == "1dee80c7d5ab2c1c90aa8d2f7dd47256" 13 . 3
  117. 117. @CompileStatic @LocalTransformation(A.PHASE_LOCAL.INSTRUCTION_SELECTION) class ToMD5Impl extends LocalTransformationImpl<ToMD5,FieldNode> { @Override void doVisit(AnnotationNode annotation, FieldNode fieldNode, SourceUnit sourceUnit) { MethodNode md5Method = getMD5Method(fieldNode.name) fieldNode.declaringClass.addMethod(md5Method) } 13 . 4
  118. 118. @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) class MacroExpandAst extends AbstractASTTransformation { void visit(ASTNode[] nodes, SourceUnit sourceUnit) { sourceUnit.AST.classes.each { ClassNode classNode -> new MacroExpandTransformer().visitClass(classNode) } } } 13 . 5
  119. 119. 13 . 6
  120. 120. 13 . 7
  121. 121. class MacroExpandTransformer extends ClassCodeExpressionTransformer { private SourceUnit sourceUnit MacroExpandTransformer(SourceUnit sourceUnit) { // PASSING SOURCE UNIT this.sourceUnit = sourceUnit } public Expression transform(Expression expression) { // CHECKING AGAIN if (expression instanceof MethodCallExpression && expression.methodAsString == 'let') // CASTINGS MethodCallExpression methodCallExpression = (MethodCallExpression) expression 13 . 8
  122. 122. @GlobalTransformation(A.PHASE_GLOBAL.SEMANTIC) class AddTransformation extends GlobalTransformationImpl { List<Class<Transformer>> getTransformers() { return [AddPropertyTransformer, AddMethodTransformer] } } 13 . 9
  123. 123. class ChangeTripleXToPlusOne extends ExpressionTransformer<MethodCallExpression> { ChangeTripleXToPlusOne(final SourceUnit sourceUnit) { super(sourceUnit, methodCallByNameEq('xxx')) } Expression transformExpression(final MethodCallExpression target) { return A.EXPR.constX(1) } } 13 . 10
  124. 124. class ChangeTripleXToPlusOne extends ExpressionTransformer<MethodCallExpression> { ChangeTripleXToPlusOne(final SourceUnit sourceUnit) { super(sourceUnit, methodCallByNameEq('xxx')) } Expression transformExpression(final MethodCallExpression target) { return A.EXPR.constX(1) } } 13 . 10
  125. 125. class ChangeTripleXToPlusOne extends ExpressionTransformer<MethodCallExpression> { ChangeTripleXToPlusOne(final SourceUnit sourceUnit) { super(sourceUnit, methodCallByNameEq('xxx')) } Expression transformExpression(final MethodCallExpression target) { return A.EXPR.constX(1) } } 13 . 10
  126. 126. 14 . 1
  127. 127. 14 . 2
  128. 128. …​ package greach.builder import org.codehaus.groovy.transform.GroovyASTTransformationClass import java.lang.annotation.ElementType import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy import java.lang.annotation.Target @Retention(RetentionPolicy.SOURCE) @Target([ElementType.TYPE]) @GroovyASTTransformationClass(["greach.builder.EvenCheckerImpl"]) @interface EvenCheckerJava { } 14 . 3
  129. 129. package greach.builder import asteroid.local.Local @Local(EvenCheckerImpl) @interface EvenChecker { } 14 . 4
  130. 130. package greach.builder import asteroid.local.Local @Local(EvenCheckerImpl) @interface EvenChecker { } 14 . 4
  131. 131. package greach.builder import asteroid.local.Local @Local(EvenCheckerImpl) @interface EvenChecker { } 14 . 4
  132. 132. 14 . 5
  133. 133. …​ 14 . 6
  134. 134. package greach.meta import groovy.transform.ToString import groovy.transform.AnnotationCollector @ToJson @ToString @AnnotationCollector @interface ToEverything { } 14 . 7
  135. 135. package greach.meta @ToEverything class A { String name } A aInstance = new A() assert aInstance.toJson() assert aInstance.toString() 14 . 8
  136. 136. package greach.meta @ToEverything class A { String name } A aInstance = new A() assert aInstance.toJson() assert aInstance.toString() 14 . 8
  137. 137. package greach.meta @ToEverything class A { String name } A aInstance = new A() assert aInstance.toJson() assert aInstance.toString() 14 . 8
  138. 138. 15 . 1
  139. 139. ​ @CompileStatic @LocalTransformation(A.PHASE_LOCAL.INSTRUCTION_SELECTION) class SerializableImpl extends LocalTransformationImpl<Serializable, ClassNode> { @Override void doVisit(AnnotationNode annotation, ClassNode classNode, SourceUnit source) { check: 'package starts with asteroid' classNode.packageName.startsWith('asteroid') check: 'there are at most 2 methods' classNode.methods.size() < 3 then: 'make it implements Serializable and Cloneable' addInterfaces(classNode, java.io.Serializable, Cloneable) } } 15 . 2
  140. 140. ​ ​ 15 . 3
  141. 141. 15 . 4
  142. 142. @ASTTest({ assert node .properties .every { it.type == ClassHelper.make(Integer) } }) @EvenChecker class A { Integer max Integer min String toString() { return "A" } } 15 . 5
  143. 143. 16 . 1
  144. 144. 16 . 2
  145. 145. package greach.builder class ToMD5Test extends GroovyTestCase { 16 . 3
  146. 146. package greach.builder class ToMD5Test extends GroovyTestCase { 16 . 3
  147. 147. package greach.builder class ToMD5Test extends GroovyTestCase { 16 . 3
  148. 148. void testAddingToMD5() { assertScript ''' package greach.builder class Order { @ToMD5 String name @ToMD5 String description } Order order = new Order(name: "john", description: "desc") assert order.nameToMD5() == "527bd5b5d689e2c32ae974c6229ff785" assert order.descriptionToMD5() == "1dee80c7d5ab2c1c90aa8d2f7dd47256" ''' } 16 . 4
  149. 149. void testFailsToUseAnInteger() { shouldFail ''' package greach.builder class Order { @ToMD5 Integer month } ''' } 16 . 5
  150. 150. 16 . 6
  151. 151. 17 . 1
  152. 152. 17 . 2
  153. 153. ​ 17 . 3
  154. 154. 17 . 4
  155. 155. BlockStatement result = macro(true) { println "foo" } 17 . 5
  156. 156. BlockStatement result = macro(true) { println "foo" } // VS def expected = block(stmt(callThisX("println", args(constX("foo"))))) // CHECKED BY AstAssert.assertSyntaxTree([expected], [result]); 17 . 6
  157. 157. BlockStatement result = macro(true) { println "foo" } // VS def expected = block(stmt(callThisX("println", args(constX("foo"))))) // CHECKED BY AstAssert.assertSyntaxTree([expected], [result]); 17 . 6
  158. 158. BlockStatement result = macro(true) { println "foo" } // VS def expected = block(stmt(callThisX("println", args(constX("foo"))))) // CHECKED BY AstAssert.assertSyntaxTree([expected], [result]); 17 . 6
  159. 159. BlockStatement result = macro(true) { println "foo" } // VS def expected = block(stmt(callThisX("println", args(constX("foo"))))) // CHECKED BY AstAssert.assertSyntaxTree([expected], [result]); 17 . 6
  160. 160. …​ 17 . 7
  161. 161. void testClosureExpression() { def ast1 = macro { {-> a } } def ast2 = macro { {-> a } } def ast3 = macro { {-> b } } def ast4 = macro { { a -> a } } def ast5 = macro { { a -> a } } def ast6 = macro { { a,b -> a } } def ast7 = macro { { int a -> a } } assert ASTMatcher.matches(ast1, ast1) assert ASTMatcher.matches(ast1, ast2) assert ASTMatcher.matches(ast2, ast1) assert !ASTMatcher.matches(ast1, ast3) assert !ASTMatcher.matches(ast1, ast4) assert ASTMatcher.matches(ast4, ast5) assert !ASTMatcher.matches(ast5, ast6) assert !ASTMatcher.matches(ast5, ast7) } 17 . 8
  162. 162. 17 . 9
  163. 163. 17 . 10
  164. 164. 17 . 11
  165. 165. 18 . 1
  166. 166. 18 . 2
  167. 167. 18 . 3
  168. 168. 18 . 4
  169. 169. 18 . 5
  170. 170. 18 . 6

×