1
2
3 . 1
3 . 1
3 . 1
…​ …​
3 . 1
3 . 2
4 . 1
4 . 1
4 . 1
…​
4 . 1
4 . 2
5
5
5
5
6 . 1
6 . 2
6 . 3
6 . 3
6 . 3
…​
6 . 4
6 . 5
6 . 6
6 . 7
⇒
1	==	1
6 . 8
⇒
1	==	1
6 . 8
⇒
1	==	1
6 . 8
⇒
1	==	1
6 . 8
⇒ ⇒
ref.myMethod(3)
6 . 9
⇒ ⇒
ref.myMethod(3)
6 . 9
⇒ ⇒
ref.myMethod(3)
6 . 9
⇒ ⇒
ref.myMethod(3)
6 . 9
6 . 10
if(booleanExpression)	{
	println	"hello"	//	statement
}
6 . 11
if(booleanExpression)	{
	println	"hello"	//	statement
}
6 . 11
if(booleanExpression)	{
	println	"hello"	//	statement
}
6 . 11
public	void	main(String[]	args)	{	//	block	starts
		//	this	is	inside	a	block	statement
}	//	block	ends
6 . 12
public	void	main(String[]	args)	{	//	block	starts
		//	this	is	inside	a	block	statement
}	//	block	ends
6 . 12
public	void	main(String[]	args)	{	//	block	starts
		//	this	is	inside	a	block	statement
}	//	block	ends
6 . 12
public	String	greetings()	{
				return	"Hello	Greach"
}
6 . 13
6 . 14
6 . 15
6 . 15
6 . 15
6 . 15
6 . 15
…​
6 . 15
…​
…​
class	A	{	//	ClassNode
			String	greetings	//	FieldNode
			String	hello()	{	//	MethodNode
			}
}
6 . 16
class	A	{	//	ClassNode
			String	hello()	//	MethodNode
			{	//	blockStatement	{
							return	"Hello"	//	returnStatement(constantExpression)
				}	//	}
}
6 . 17
7 . 1
7 . 2
7 . 3
7 . 3
7 . 3
7 . 4
7 . 5
…​
7 . 5
…​
7 . 5
7 . 6
8 . 1
8 . 2
8 . 3
8 . 4
8 . 5
8 . 6
9
10 . 1
10 . 2
10 . 3
10 . 4
10 . 5
10 . 5
10 . 5
10 . 5
11 . 1
package	greach.local
class	A	{
				@WithLogging
				void	doSomething()	{
								//	println	"Starting	doSomething"
								println	"mystuff"
								//	println	"Ending	doSomething"
				}
}
11 . 2
11 . 3
…​
11 . 3
…​
11 . 3
11 . 4
11 . 4
11 . 4
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
11 . 6
import	org.codehaus.groovy.ast.expr.*
import	org.codehaus.groovy.ast.stmt.*
import	org.codehaus.groovy.ast.*
import	org.codehaus.groovy.transform.*
11 . 7
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class	WithLoggingExplainedTransformation	implements	ASTTransformation	{
11 . 8
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class	WithLoggingExplainedTransformation	implements	ASTTransformation	{
11 . 8
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class	WithLoggingExplainedTransformation	implements	ASTTransformation	{
11 . 8
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class	WithLoggingExplainedTransformation	implements	ASTTransformation	{
11 . 8
@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
11 . 10
11 . 10
11 . 10
private	static	Statement	createPrintlnAst(String	message)	{
								new	ExpressionStatement(
												new	MethodCallExpression(
																new	VariableExpression("this"),
																new	ConstantExpression("println"),
																new	ArgumentListExpression(
																				new	ConstantExpression(message)
																)
												)
								)
				}
11 . 11
11 . 12
11 . 12
11 . 12
12 . 1
12 . 2
12 . 3
import	static	org.codehaus.groovy.ast.tools.GeneralUtils.*
12 . 4
12 . 5
12 . 5
12 . 5
private	static	Statement	createPrintlnAst(String	message)	{
								new	ExpressionStatement(
												new	MethodCallExpression(
																new	VariableExpression("this"),
																new	ConstantExpression("println"),
																new	ArgumentListExpression(
																				new	ConstantExpression(message)
																)
												)
								)
				}
12 . 6
private	static	Statement	createPrintlnAst(String	message)	{
								return	stmt(callThisX("println",	args(constX(message))))
				}
12 . 7
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
…​
12 . 9
12 . 10
BlockStatement	getBlockStmt()	{
								ASTNode[]	stmts	=	new	AstBuilder().buildFromCode	{
												return	number	%	2	==	0
								}
								return	stmts.first()	as	BlockStatement
				}
12 . 11
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
12 . 13
13 . 1
@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
…​
…​
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
@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
@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
13 . 6
13 . 7
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
@GlobalTransformation(A.PHASE_GLOBAL.SEMANTIC)
class	AddTransformation	extends	GlobalTransformationImpl	{
				List<Class<Transformer>>	getTransformers()	{
								return	[AddPropertyTransformer,	AddMethodTransformer]
				}
}
13 . 9
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
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
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
14 . 1
14 . 2
…​
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
package	greach.builder
import	asteroid.local.Local
@Local(EvenCheckerImpl)
@interface	EvenChecker	{	}
14 . 4
package	greach.builder
import	asteroid.local.Local
@Local(EvenCheckerImpl)
@interface	EvenChecker	{	}
14 . 4
package	greach.builder
import	asteroid.local.Local
@Local(EvenCheckerImpl)
@interface	EvenChecker	{	}
14 . 4
14 . 5
…​
14 . 6
package	greach.meta
import	groovy.transform.ToString
import	groovy.transform.AnnotationCollector
@ToJson
@ToString
@AnnotationCollector
@interface	ToEverything	{	}
14 . 7
package	greach.meta
@ToEverything
class	A	{
				String	name
}
A	aInstance	=	new	A()
assert	aInstance.toJson()
assert	aInstance.toString()
14 . 8
package	greach.meta
@ToEverything
class	A	{
				String	name
}
A	aInstance	=	new	A()
assert	aInstance.toJson()
assert	aInstance.toString()
14 . 8
package	greach.meta
@ToEverything
class	A	{
				String	name
}
A	aInstance	=	new	A()
assert	aInstance.toJson()
assert	aInstance.toString()
14 . 8
15 . 1
​
@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
​ ​
15 . 3
15 . 4
@ASTTest({
				assert	node
								.properties
								.every	{	it.type	==	ClassHelper.make(Integer)	}
})
@EvenChecker
class	A	{
				Integer	max
				Integer	min
				String	toString()	{
								return	"A"
				}
}
15 . 5
16 . 1
16 . 2
package	greach.builder
class	ToMD5Test	extends	GroovyTestCase	{
16 . 3
package	greach.builder
class	ToMD5Test	extends	GroovyTestCase	{
16 . 3
package	greach.builder
class	ToMD5Test	extends	GroovyTestCase	{
16 . 3
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
void	testFailsToUseAnInteger()	{
				shouldFail	'''
				package	greach.builder
				class	Order	{
								@ToMD5	Integer	month
				}
				'''
}
16 . 5
16 . 6
17 . 1
17 . 2
​
17 . 3
17 . 4
BlockStatement	result	=	macro(true)	{
		println	"foo"
}
17 . 5
BlockStatement	result	=	macro(true)	{
		println	"foo"
}
//	VS
def	expected	=	block(stmt(callThisX("println",	args(constX("foo")))))
//	CHECKED	BY
AstAssert.assertSyntaxTree([expected],	[result]);
17 . 6
BlockStatement	result	=	macro(true)	{
		println	"foo"
}
//	VS
def	expected	=	block(stmt(callThisX("println",	args(constX("foo")))))
//	CHECKED	BY
AstAssert.assertSyntaxTree([expected],	[result]);
17 . 6
BlockStatement	result	=	macro(true)	{
		println	"foo"
}
//	VS
def	expected	=	block(stmt(callThisX("println",	args(constX("foo")))))
//	CHECKED	BY
AstAssert.assertSyntaxTree([expected],	[result]);
17 . 6
BlockStatement	result	=	macro(true)	{
		println	"foo"
}
//	VS
def	expected	=	block(stmt(callThisX("println",	args(constX("foo")))))
//	CHECKED	BY
AstAssert.assertSyntaxTree([expected],	[result]);
17 . 6
…​
17 . 7
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
17 . 9
17 . 10
17 . 11
18 . 1
18 . 2
18 . 3
18 . 4
18 . 5
18 . 6

Creating ASTTs The painful truth