forked from griddynamics/OpenGenesis
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
261 additions
and
7 deletions.
There are no files selected for viewing
14 changes: 14 additions & 0 deletions
14
...c/main/java/com/griddynamics/genesis/template/dsl/groovy/transformations/MacroExpand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.griddynamics.genesis.template.dsl.groovy.transformations; | ||
|
||
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, ElementType.METHOD}) | ||
@GroovyASTTransformationClass({"com.griddynamics.genesis.template.dsl.groovy.transformations.MacroASTTransformation"}) | ||
public @interface MacroExpand { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
...com/griddynamics/genesis/template/dsl/groovy/transformations/MacroASTTransformation.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package com.griddynamics.genesis.template.dsl.groovy.transformations | ||
|
||
import org.codehaus.groovy.transform.{ASTTransformation, GroovyASTTransformation} | ||
import org.codehaus.groovy.control.{SourceUnit, CompilePhase} | ||
import com.griddynamics.genesis.util.Logging | ||
import org.codehaus.groovy.ast.{CodeVisitorSupport, ASTNode} | ||
import org.codehaus.groovy.ast.expr._ | ||
import scala.collection.mutable | ||
import org.codehaus.groovy.ast.stmt.{ExpressionStatement, Statement, BlockStatement} | ||
import java.util | ||
import scala.collection.JavaConversions._ | ||
|
||
@GroovyASTTransformation(phase = CompilePhase.CONVERSION) | ||
class MacroASTTransformation extends ASTTransformation with Logging { | ||
def visit(nodes: Array[ASTNode], source: SourceUnit) { | ||
log.trace(s"Inside a macros transformer: ${nodes.length}") | ||
val methods = source.getAST.getStatementBlock.getStatements | ||
val it = methods.iterator().next() | ||
val collector = new MacroCollector | ||
it.visit(new NameMatchArgumentTransformer("template", collector)) | ||
if (collector.macros.size > 0) { | ||
methods.iterator().next().visit(new MacroExpandVisitor(collector.macros.toMap)) | ||
} | ||
} | ||
} | ||
|
||
class MacroExpandVisitor(val macrodefs: Map[String, ClosureExpression]) extends CodeVisitorSupport with Logging { | ||
|
||
val nameExtract: PartialFunction[Expression, String] = { | ||
case call if call.isInstanceOf[MethodCallExpression] => call.asInstanceOf[MethodCallExpression].getMethodAsString | ||
} | ||
|
||
def replaceStatement(statement: BlockStatement, expression: ExpressionStatement): Statement = { | ||
val initialStatements: util.List[Statement] = statement.getStatements | ||
val position = initialStatements.indexOf(expression) | ||
expression.getExpression match { | ||
case mce: MethodCallExpression => | ||
val key: String = nameExtract(expression.getExpression) | ||
log.trace(s"Expanding call of $key at position $position") | ||
macrodefs.get(key).map(e => { | ||
initialStatements.set(position, e.getCode) | ||
}) orElse(throw new IllegalArgumentException(s"Macro $key not found anywhere in template")) | ||
case other => | ||
throw new IllegalArgumentException("Macro call must have a form of function call: " + expression.getExpression) | ||
} | ||
val cleared = initialStatements.filterNot(_.getStatementLabel == "macro") | ||
new BlockStatement(cleared, statement.getVariableScope) | ||
} | ||
|
||
override def visitClosureExpression(call: ClosureExpression) { | ||
call.getCode match { | ||
case bs: BlockStatement => { | ||
bs.getStatements.foreach( statement => | ||
statement match { | ||
case es: ExpressionStatement => { | ||
if (es.getStatementLabel == "macro") { | ||
call.setCode(replaceStatement(bs, es)) | ||
} else { | ||
es.getExpression match { | ||
case call: MethodCallExpression => { | ||
this.visitMethodCallExpression(call) | ||
} | ||
case other => | ||
} | ||
} | ||
} | ||
case other => | ||
} | ||
) | ||
} | ||
case other => | ||
} | ||
} | ||
|
||
|
||
|
||
override def visitMethodCallExpression(call: MethodCallExpression) { | ||
call.getArguments match { | ||
case ale: ArgumentListExpression => { | ||
ale.visit(this) | ||
} | ||
case _ => | ||
} | ||
} | ||
} | ||
|
||
class MacroCollector extends ExpressionTransformer with Logging { | ||
var macros: mutable.Map[String, ClosureExpression] = new mutable.HashMap[String, ClosureExpression]() | ||
def transform(expression: Expression) : Expression = { | ||
expression match { | ||
case a: ClosureExpression => { | ||
a.getCode match { | ||
case block: BlockStatement => { | ||
val statements: util.List[Statement] = block.getStatements | ||
val seq: Seq[Statement] = statements.toSeq | ||
val macroDefs: Seq[Statement] = seq.filter(evalStatement) | ||
if (macroDefs.size > 0) { | ||
val rest: Seq[Statement] = seq.diff(macroDefs) | ||
val content = new BlockStatement(rest.toArray, block.getVariableScope) | ||
a.setCode(content) | ||
} | ||
macroDefs.foreach(m => { | ||
m match { | ||
case statement: ExpressionStatement => { | ||
statement.getExpression match { | ||
case mce: MethodCallExpression => saveExpr(mce) | ||
case other => | ||
} | ||
} | ||
case other => | ||
} | ||
}) | ||
a | ||
} | ||
case x => a | ||
} | ||
|
||
} | ||
case x => x | ||
} | ||
} | ||
|
||
def saveExpr(expression: MethodCallExpression) { | ||
def parseArgurmentList(mapExpr: NamedArgumentListExpression): (String, ClosureExpression) = { | ||
val top = mapExpr.getMapEntryExpressions.iterator().next() | ||
val key: String = top.getKeyExpression.asInstanceOf[ConstantExpression].getValue.toString | ||
val closure = top.getValueExpression.asInstanceOf[ClosureExpression] | ||
(key, closure) | ||
} | ||
val (name, body) = expression.getArguments match { | ||
case mapExpr: NamedArgumentListExpression => | ||
parseArgurmentList(mapExpr) | ||
case tuple: TupleExpression => | ||
parseArgurmentList(tuple.getExpression(0).asInstanceOf[NamedArgumentListExpression]) | ||
} | ||
macros(name) = body | ||
} | ||
|
||
def evalStatement(statement: Statement): Boolean = { | ||
statement match { | ||
case es: ExpressionStatement => { | ||
es.getExpression match { | ||
case mce: MethodCallExpression => { | ||
mce.getMethodAsString == "defmacro" | ||
} | ||
case other => false | ||
} | ||
} | ||
case other => { | ||
false | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package groovy | ||
template { | ||
name("Macros") | ||
version("0.1") | ||
createWorkflow("create") | ||
destroyWorkflow("destroy") | ||
|
||
|
||
defmacro "create_steps": { | ||
teststep { | ||
text = "test from macro" | ||
} | ||
} | ||
|
||
workflow("create") { | ||
steps { | ||
teststep { | ||
text = "test input" | ||
} | ||
teststep { | ||
text = "another input" | ||
} | ||
} | ||
} | ||
|
||
workflow("macros") { | ||
steps { | ||
teststep { | ||
text = "It was here" | ||
} | ||
macro:create_steps() | ||
} | ||
} | ||
|
||
workflow("destroy") { | ||
steps { | ||
teststep { | ||
phase = "undeploy" | ||
text = "destroy" | ||
} | ||
} | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
backend/src/test/scala/com/griddynamics/genesis/service/impl/MacrosTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.griddynamics.genesis.service.impl | ||
|
||
import org.scalatest.junit.AssertionsForJUnit | ||
import org.scalatest.mock.MockitoSugar | ||
import org.springframework.core.convert.support.DefaultConversionService | ||
import com.griddynamics.genesis.template.ListVarDSFactory | ||
import com.griddynamics.genesis.util.{Logging, IoUtil} | ||
import org.mockito.{Matchers, Mockito} | ||
import org.mockito.Mockito._ | ||
import scala.Some | ||
import org.junit.Test | ||
import com.griddynamics.genesis.template.support.DatabagDataSourceFactory | ||
import com.griddynamics.genesis.cache.NullCacheManager | ||
import com.griddynamics.genesis.template.VersionedTemplate | ||
import com.griddynamics.genesis.api | ||
import com.griddynamics.genesis.plugin.StepBuilder | ||
|
||
|
||
class MacrosTest extends AssertionsForJUnit with MockitoSugar with DSLTestUniverse with Logging { | ||
val templateService = new GroovyTemplateService(templateRepoService, | ||
List(new DoNothingStepBuilderFactory), new DefaultConversionService, | ||
Seq(new ListVarDSFactory, new DependentListVarDSFactory, | ||
new DatabagDataSourceFactory(databagRepository)), databagRepository, configService, NullCacheManager) | ||
|
||
val body = IoUtil.streamAsString(classOf[GroovyTemplateServiceTest].getResourceAsStream("/groovy/Macros.genesis")) | ||
|
||
Mockito.when(templateRepository.listSources()).thenReturn(Map(VersionedTemplate("1") -> body)) | ||
when(configService.get(Matchers.any(), Matchers.any())).thenReturn(Some(new api.Configuration(Some(0), "", 0, None, Map()))) | ||
|
||
@Test | ||
def testPhaseApplied() { | ||
val createWorkflow = templateService.findTemplate(0, "Macros", "0.1", 0).flatMap(_.getWorkflow("macros")).get | ||
val steps = createWorkflow.embody(Map()) | ||
expectResult(2)(steps.regular.size) | ||
val initialPhase: Option[StepBuilder] = steps.regular.find(_.phase == "auto_0") | ||
assert(initialPhase.isDefined) | ||
assert(initialPhase.get.getPrecedingPhases.isEmpty) | ||
} | ||
} | ||
|