Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Creating ASTTs The painful truth

952 vues

Publié le

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

Publié dans : Logiciels
  • Soyez le premier à commenter

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

×