GString transpilation: values in correct order, verbatim fixed.
This commit is contained in:
parent
cd63bd60a7
commit
5c3d973c4c
@ -144,11 +144,12 @@ def toolSpec = { String name, String mainClass ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
final List<ToolSpec> toolSpecs = [
|
final List<ToolSpec> toolSpecs = [
|
||||||
toolSpec('parseTreeFileMaker', 'ParseTreeFileMakerCli'),
|
|
||||||
toolSpec('astFileMaker', 'AstFileMakerCli'),
|
toolSpec('astFileMaker', 'AstFileMakerCli'),
|
||||||
toolSpec('convertToGroovy', 'ConvertToGroovy'),
|
toolSpec('convertToGroovy', 'ConvertToGroovy'),
|
||||||
toolSpec('lexer', 'LexerTool'),
|
toolSpec('lexer', 'LexerTool'),
|
||||||
toolSpec('parser', 'ParserTool')
|
toolSpec('parser', 'ParserTool'),
|
||||||
|
toolSpec('parseTreeFileMaker', 'ParseTreeFileMakerCli'),
|
||||||
|
toolSpec('runTemplate', 'RunTemplate')
|
||||||
]
|
]
|
||||||
|
|
||||||
toolSpecs.each { spec ->
|
toolSpecs.each { spec ->
|
||||||
|
1
web-views/sketching/.gitignore
vendored
1
web-views/sketching/.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
*.groovy
|
*.groovy
|
||||||
classes
|
classes
|
||||||
|
.txt
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
import groowt.view.component.ComponentContext
|
void consume(out) {
|
||||||
|
out << 'World'
|
||||||
class Helper {
|
|
||||||
String greeting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@groovy.transform.Field
|
||||||
|
String greeting = 'Hello'
|
||||||
---
|
---
|
||||||
Hello, $target!
|
$greeting, ${consume(out)}!
|
||||||
What a nice day.
|
What a nice day.
|
||||||
|
@ -13,9 +13,4 @@ public non-sealed class GStringScriptletExtension extends GStringNodeExtension {
|
|||||||
super(self, allTokens.getRange(rawTokenRange));
|
super(self, allTokens.getRange(rawTokenRange));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
}
|
||||||
public String getAsValidEmbeddableCode() {
|
|
||||||
return "${" + super.getAsValidEmbeddableCode() + "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,26 +1,46 @@
|
|||||||
package groowt.view.web.ast.node;
|
package groowt.view.web.ast.node;
|
||||||
|
|
||||||
import groowt.util.di.annotation.Given;
|
import groowt.util.di.annotation.Given;
|
||||||
|
import groowt.view.web.antlr.TokenList;
|
||||||
|
import groowt.view.web.ast.extension.GroovyCodeNodeExtension;
|
||||||
import groowt.view.web.ast.extension.NodeExtensionContainer;
|
import groowt.view.web.ast.extension.NodeExtensionContainer;
|
||||||
import groowt.view.web.util.TokenRange;
|
import groowt.view.web.util.TokenRange;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import org.antlr.v4.runtime.Token;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class PreambleNode extends AbstractLeafNode {
|
public class PreambleNode extends AbstractLeafNode {
|
||||||
|
|
||||||
private final int groovyIndex;
|
private final GroovyCodeNodeExtension groovyCode;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PreambleNode(
|
public PreambleNode(
|
||||||
|
TokenList tokenList,
|
||||||
NodeExtensionContainer extensionContainer,
|
NodeExtensionContainer extensionContainer,
|
||||||
@Given TokenRange tokenRange,
|
@Given TokenRange tokenRange,
|
||||||
@Given int groovyCodeIndex
|
@Given int groovyCodeIndex
|
||||||
) {
|
) {
|
||||||
super(tokenRange, extensionContainer);
|
super(tokenRange, extensionContainer);
|
||||||
this.groovyIndex = groovyCodeIndex;
|
this.groovyCode = this.createGroovyCode(tokenList, groovyCodeIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getGroovyCodeIndex() {
|
protected GroovyCodeNodeExtension createGroovyCode(TokenList tokenList, int groovyCodeIndex) {
|
||||||
return this.groovyIndex;
|
return this.createExtension(
|
||||||
|
GroovyCodeNodeExtension.class,
|
||||||
|
TokenRange.fromIndex(tokenList, groovyCodeIndex),
|
||||||
|
(Function<? super List<Token>, String>) this::toValidGroovyCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String toValidGroovyCode(List<Token> tokenList) {
|
||||||
|
return tokenList.stream().map(Token::getText).collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroovyCodeNodeExtension getGroovyCode() {
|
||||||
|
return this.groovyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,16 +24,12 @@ public class WebViewComponentWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void append(GString gString) {
|
public void append(GString gString) {
|
||||||
final String content;
|
|
||||||
try {
|
try {
|
||||||
content = gString.toString();
|
gString.writeTo(this.delegate);
|
||||||
} catch (Exception exception) {
|
|
||||||
throw new ComponentRenderException(exception);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.delegate.append(content);
|
|
||||||
} catch (IOException ioException) {
|
} catch (IOException ioException) {
|
||||||
throw new RuntimeException(ioException);
|
throw new RuntimeException(ioException);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
throw new ComponentRenderException(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,4 +75,13 @@ public class WebViewComponentWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void leftShift(Object object) {
|
||||||
|
switch (object) {
|
||||||
|
case String s -> this.append(s);
|
||||||
|
case GString gs -> this.append(gs);
|
||||||
|
case ViewComponent viewComponent -> this.append(viewComponent);
|
||||||
|
default -> this.append(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import groowt.view.web.ast.extension.GStringScriptletExtension;
|
|||||||
import groowt.view.web.ast.node.GStringBodyTextNode;
|
import groowt.view.web.ast.node.GStringBodyTextNode;
|
||||||
import groowt.view.web.ast.node.JStringBodyTextNode;
|
import groowt.view.web.ast.node.JStringBodyTextNode;
|
||||||
import groowt.view.web.ast.node.Node;
|
import groowt.view.web.ast.node.Node;
|
||||||
|
import groowt.view.web.transpile.util.GroovyUtil;
|
||||||
import groowt.view.web.util.FilteringIterable;
|
import groowt.view.web.util.FilteringIterable;
|
||||||
import groowt.view.web.util.Option;
|
import groowt.view.web.util.Option;
|
||||||
import groowt.view.web.util.TokenRange;
|
import groowt.view.web.util.TokenRange;
|
||||||
@ -15,6 +16,9 @@ import jakarta.inject.Inject;
|
|||||||
import jakarta.inject.Singleton;
|
import jakarta.inject.Singleton;
|
||||||
import org.antlr.v4.runtime.Token;
|
import org.antlr.v4.runtime.Token;
|
||||||
import org.codehaus.groovy.ast.expr.*;
|
import org.codehaus.groovy.ast.expr.*;
|
||||||
|
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||||
|
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||||
|
import org.codehaus.groovy.ast.stmt.Statement;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -70,7 +74,8 @@ public class DefaultGStringTranspiler implements GStringTranspiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected record PathResult(
|
protected record PathResult(
|
||||||
Expression result, Option<ConstantExpression> before,
|
Expression result,
|
||||||
|
Option<ConstantExpression> before,
|
||||||
Option<ConstantExpression> after
|
Option<ConstantExpression> after
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -124,6 +129,23 @@ public class DefaultGStringTranspiler implements GStringTranspiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ClosureExpression handleScriptlet(GStringScriptletExtension gStringScriptletExtension) {
|
||||||
|
final GroovyUtil.ConvertResult convertResult = GroovyUtil.convert(
|
||||||
|
"def cl = {" + gStringScriptletExtension.getAsValidEmbeddableCode() + "}"
|
||||||
|
);
|
||||||
|
final BlockStatement convertBlock = convertResult.blockStatement();
|
||||||
|
if (convertBlock == null) {
|
||||||
|
throw new NullPointerException("Did not except convertBlock to be null");
|
||||||
|
}
|
||||||
|
final List<Statement> convertStatements = convertBlock.getStatements();
|
||||||
|
if (convertStatements.size() != 1) {
|
||||||
|
throw new IllegalStateException("Did not expect convertStatements.size() to not equal 1");
|
||||||
|
}
|
||||||
|
final ExpressionStatement convertExpressionStatement = (ExpressionStatement) convertStatements.getFirst();
|
||||||
|
final BinaryExpression assignment = (BinaryExpression) convertExpressionStatement.getExpression();
|
||||||
|
return (ClosureExpression) assignment.getRightExpression();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GStringExpression createGStringExpression(GStringBodyTextNode gStringBodyTextNode) {
|
public GStringExpression createGStringExpression(GStringBodyTextNode gStringBodyTextNode) {
|
||||||
final var children = gStringBodyTextNode.getChildren();
|
final var children = gStringBodyTextNode.getChildren();
|
||||||
@ -136,7 +158,10 @@ public class DefaultGStringTranspiler implements GStringTranspiler {
|
|||||||
return jStringBodyTextNode.getContent();
|
return jStringBodyTextNode.getContent();
|
||||||
} else if (node.hasExtension(GStringNodeExtension.class)) {
|
} else if (node.hasExtension(GStringNodeExtension.class)) {
|
||||||
final var gString = node.getExtension(GStringNodeExtension.class);
|
final var gString = node.getExtension(GStringNodeExtension.class);
|
||||||
return gString.getAsValidEmbeddableCode();
|
return switch (gString) {
|
||||||
|
case GStringPathExtension ignored -> gString.getAsValidEmbeddableCode();
|
||||||
|
case GStringScriptletExtension ignored -> "${" + gString.getAsValidEmbeddableCode() + "}";
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Cannot get verbatim text when one of the given parts has "
|
"Cannot get verbatim text when one of the given parts has "
|
||||||
@ -165,7 +190,7 @@ public class DefaultGStringTranspiler implements GStringTranspiler {
|
|||||||
}
|
}
|
||||||
case GStringScriptletExtension scriptlet -> {
|
case GStringScriptletExtension scriptlet -> {
|
||||||
checkPrevBeforeDollar(prev, current).ifPresent(texts::add);
|
checkPrevBeforeDollar(prev, current).ifPresent(texts::add);
|
||||||
// TODO
|
values.add(this.handleScriptlet(scriptlet));
|
||||||
checkNextAfterDollar(current, next).ifPresent(texts::add);
|
checkNextAfterDollar(current, next).ifPresent(texts::add);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ package groowt.view.web.transpile;
|
|||||||
import groowt.view.web.antlr.TokenList;
|
import groowt.view.web.antlr.TokenList;
|
||||||
import groowt.view.web.ast.node.BodyNode;
|
import groowt.view.web.ast.node.BodyNode;
|
||||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||||
import groowt.view.web.transpile.PreambleTranspiler.PreambleResult;
|
import groowt.view.web.ast.node.PreambleNode;
|
||||||
|
import groowt.view.web.transpile.util.GroovyUtil;
|
||||||
import org.codehaus.groovy.ast.*;
|
import org.codehaus.groovy.ast.*;
|
||||||
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
||||||
|
import org.codehaus.groovy.ast.expr.DeclarationExpression;
|
||||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||||
|
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||||
import org.codehaus.groovy.ast.stmt.ReturnStatement;
|
import org.codehaus.groovy.ast.stmt.ReturnStatement;
|
||||||
import org.codehaus.groovy.ast.stmt.Statement;
|
import org.codehaus.groovy.ast.stmt.Statement;
|
||||||
import org.codehaus.groovy.control.CompilationUnit;
|
import org.codehaus.groovy.control.CompilationUnit;
|
||||||
@ -14,8 +17,11 @@ import org.codehaus.groovy.control.SourceUnit;
|
|||||||
import org.codehaus.groovy.control.io.ReaderSource;
|
import org.codehaus.groovy.control.io.ReaderSource;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static groowt.view.web.transpile.TranspilerUtil.*;
|
import static groowt.view.web.transpile.TranspilerUtil.*;
|
||||||
@ -30,6 +36,8 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
|||||||
*/
|
*/
|
||||||
public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DefaultGroovyTranspiler.class);
|
||||||
|
|
||||||
private final CompilationUnit groovyCompilationUnit;
|
private final CompilationUnit groovyCompilationUnit;
|
||||||
private final String defaultPackageName;
|
private final String defaultPackageName;
|
||||||
private final Supplier<? extends TranspilerConfiguration> configurationSupplier;
|
private final Supplier<? extends TranspilerConfiguration> configurationSupplier;
|
||||||
@ -60,6 +68,96 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkPreambleClasses(String templateName, List<ClassNode> classNodes) {
|
||||||
|
final ClassNode offending = classNodes.stream()
|
||||||
|
.filter(classNode -> classNode.getName().equals(templateName))
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
if (offending != null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
templateName + " cannot define itself in the template. " +
|
||||||
|
"Remove the class with that name."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<InnerClassNode> convertPreambleClassesToInnerClasses(ClassNode mainClassNode, List<ClassNode> classNodes) {
|
||||||
|
final List<InnerClassNode> result = new ArrayList<>();
|
||||||
|
for (final var classNode : classNodes) {
|
||||||
|
if (classNode instanceof InnerClassNode innerClassNode) {
|
||||||
|
result.add(innerClassNode);
|
||||||
|
} else {
|
||||||
|
final InnerClassNode icn = new InnerClassNode(
|
||||||
|
mainClassNode,
|
||||||
|
mainClassNode.getName() + "." + classNode.getNameWithoutPackage(),
|
||||||
|
classNode.getModifiers(),
|
||||||
|
classNode.getSuperClass(),
|
||||||
|
classNode.getInterfaces(),
|
||||||
|
classNode.getMixins()
|
||||||
|
);
|
||||||
|
icn.setDeclaringClass(mainClassNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handlePreamble(
|
||||||
|
String templateName,
|
||||||
|
String packageName,
|
||||||
|
PreambleNode preambleNode,
|
||||||
|
ClassNode mainClassNode,
|
||||||
|
WebViewComponentModuleNode moduleNode
|
||||||
|
) {
|
||||||
|
final GroovyUtil.ConvertResult preambleConvert = GroovyUtil.convert(
|
||||||
|
preambleNode.getGroovyCode().getAsValidGroovyCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
WebViewComponentModuleNode.copyTo(preambleConvert.moduleNode(), moduleNode);
|
||||||
|
|
||||||
|
final BlockStatement preambleBlock = preambleConvert.blockStatement();
|
||||||
|
if (preambleBlock != null) {
|
||||||
|
// Fields
|
||||||
|
final List<Statement> preambleStatements = preambleBlock.getStatements();
|
||||||
|
final List<DeclarationExpression> declarationsWithField = preambleStatements.stream()
|
||||||
|
.filter(statement -> statement instanceof ExpressionStatement)
|
||||||
|
.map(ExpressionStatement.class::cast)
|
||||||
|
.map(ExpressionStatement::getExpression)
|
||||||
|
.filter(expression -> expression instanceof DeclarationExpression)
|
||||||
|
.map(DeclarationExpression.class::cast)
|
||||||
|
.filter(declarationExpression ->
|
||||||
|
!declarationExpression.getAnnotations(FIELD_ANNOTATION).isEmpty()
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
if (declarationsWithField.size() != preambleStatements.size()) {
|
||||||
|
logger.warn(
|
||||||
|
"{} contains script statements which are not supported. " +
|
||||||
|
"Currently, only classes, methods, and field declarations (marked with @Field) " +
|
||||||
|
"are supported. The rest will be ignored.",
|
||||||
|
templateName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
declarationsWithField.forEach(declaration -> {
|
||||||
|
declaration.setDeclaringClass(mainClassNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// move methods from script class
|
||||||
|
final ClassNode scriptClass = preambleConvert.scriptClass();
|
||||||
|
if (scriptClass != null) {
|
||||||
|
scriptClass.getMethods().forEach(mainClassNode::addMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle classes
|
||||||
|
final List<ClassNode> classNodes = preambleConvert.classNodes();
|
||||||
|
this.checkPreambleClasses(templateName, classNodes);
|
||||||
|
final List<ClassNode> toInner = classNodes.stream()
|
||||||
|
.filter(classNode -> classNode != preambleConvert.scriptClass())
|
||||||
|
.filter(classNode -> !classNode.isScript())
|
||||||
|
.toList();
|
||||||
|
final List<InnerClassNode> innerClassNodes = this.convertPreambleClassesToInnerClasses(mainClassNode, toInner);
|
||||||
|
innerClassNodes.forEach(moduleNode::addClass);
|
||||||
|
}
|
||||||
|
|
||||||
// Cases:
|
// Cases:
|
||||||
// - no preamble -> create our own class
|
// - no preamble -> create our own class
|
||||||
// - some preamble, but no script -> create our own class but use imports/packageName from preamble
|
// - some preamble, but no script -> create our own class but use imports/packageName from preamble
|
||||||
@ -85,34 +183,26 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
final var moduleNode = new WebViewComponentModuleNode(sourceUnit);
|
final var moduleNode = new WebViewComponentModuleNode(sourceUnit);
|
||||||
sourceUnit.setModuleNode(moduleNode);
|
sourceUnit.setModuleNode(moduleNode);
|
||||||
|
|
||||||
ClassNode mainClassNode;
|
final String packageName = this.getPackageName(moduleNode);
|
||||||
|
moduleNode.setPackageName(packageName);
|
||||||
|
|
||||||
final PreambleResult preambleResult = configuration.getPreambleTranspiler().getPreambleResult(
|
final ClassNode mainClassNode = new ClassNode(
|
||||||
compilationUnitNode.getPreambleNode(),
|
packageName + "." + templateName,
|
||||||
templateName,
|
ACC_PUBLIC,
|
||||||
tokens
|
ClassHelper.OBJECT_TYPE
|
||||||
);
|
);
|
||||||
if (preambleResult.moduleNode() != null) {
|
mainClassNode.setScript(true);
|
||||||
WebViewComponentModuleNode.copyTo(preambleResult.moduleNode(), moduleNode);
|
mainClassNode.addInterface(TranspilerUtil.COMPONENT_TEMPLATE);
|
||||||
}
|
|
||||||
if (preambleResult.scriptClass() != null) {
|
|
||||||
mainClassNode = preambleResult.scriptClass();
|
|
||||||
// do not add it to moduleNode because it's already there
|
|
||||||
} else {
|
|
||||||
final String packageName = this.getPackageName(moduleNode);
|
|
||||||
final String templateClassName = packageName + "." + templateName;
|
|
||||||
|
|
||||||
mainClassNode = new ClassNode(
|
moduleNode.addClass(mainClassNode);
|
||||||
templateClassName,
|
|
||||||
ACC_PUBLIC,
|
|
||||||
ClassHelper.OBJECT_TYPE
|
|
||||||
);
|
|
||||||
mainClassNode.setScript(true);
|
|
||||||
mainClassNode.addInterface(TranspilerUtil.COMPONENT_TEMPLATE);
|
|
||||||
|
|
||||||
moduleNode.addClass(mainClassNode);
|
// preamble
|
||||||
|
final PreambleNode preambleNode = compilationUnitNode.getPreambleNode();
|
||||||
|
if (preambleNode != null) {
|
||||||
|
this.handlePreamble(templateName, packageName, preambleNode, mainClassNode, moduleNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renderer
|
||||||
final var renderBlock = new BlockStatement();
|
final var renderBlock = new BlockStatement();
|
||||||
|
|
||||||
final TranspilerState state = TranspilerState.withDefaultRootScope();
|
final TranspilerState state = TranspilerState.withDefaultRootScope();
|
||||||
@ -138,6 +228,8 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
},
|
},
|
||||||
renderBlock
|
renderBlock
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// getRenderer()
|
||||||
final Statement returnRendererStmt = new ReturnStatement(renderer);
|
final Statement returnRendererStmt = new ReturnStatement(renderer);
|
||||||
|
|
||||||
final var voidClosure = ClassHelper.CLOSURE_TYPE.getPlainNodeReference();
|
final var voidClosure = ClassHelper.CLOSURE_TYPE.getPlainNodeReference();
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
package groowt.view.web.transpile;
|
|
||||||
|
|
||||||
import groowt.view.web.antlr.TokenList;
|
|
||||||
import groowt.view.web.ast.node.PreambleNode;
|
|
||||||
import groowt.view.web.transpile.util.GroovyUtil;
|
|
||||||
import org.antlr.v4.runtime.Token;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DefaultPreambleTranspiler implements PreambleTranspiler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PreambleResult getPreambleResult(
|
|
||||||
@Nullable PreambleNode preambleNode,
|
|
||||||
String templateName,
|
|
||||||
TokenList tokens
|
|
||||||
) {
|
|
||||||
if (preambleNode == null) {
|
|
||||||
return new PreambleResult(null, null, List.of());
|
|
||||||
} else {
|
|
||||||
final Token groovyToken = tokens.getGroovyToken(preambleNode.getGroovyCodeIndex());
|
|
||||||
final GroovyUtil.ConvertResult convertResult = GroovyUtil.convert(groovyToken.getText(), templateName);
|
|
||||||
return new PreambleResult(
|
|
||||||
convertResult.moduleNode(),
|
|
||||||
convertResult.scriptClass(),
|
|
||||||
convertResult.classNodes()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ import jakarta.inject.Inject;
|
|||||||
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||||
|
|
||||||
private final OutStatementFactory outStatementFactory = new SimpleOutStatementFactory();
|
private final OutStatementFactory outStatementFactory = new SimpleOutStatementFactory();
|
||||||
private final PreambleTranspiler preambleTranspiler = new DefaultPreambleTranspiler();
|
|
||||||
private final BodyTranspiler bodyTranspiler;
|
private final BodyTranspiler bodyTranspiler;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ -20,11 +19,6 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
|||||||
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PreambleTranspiler getPreambleTranspiler() {
|
|
||||||
return this.preambleTranspiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BodyTranspiler getBodyTranspiler() {
|
public BodyTranspiler getBodyTranspiler() {
|
||||||
return this.bodyTranspiler;
|
return this.bodyTranspiler;
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package groowt.view.web.transpile;
|
|
||||||
|
|
||||||
import groowt.view.web.antlr.TokenList;
|
|
||||||
import groowt.view.web.ast.node.PreambleNode;
|
|
||||||
import org.codehaus.groovy.ast.ClassNode;
|
|
||||||
import org.codehaus.groovy.ast.ModuleNode;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface PreambleTranspiler {
|
|
||||||
|
|
||||||
record PreambleResult(
|
|
||||||
@Nullable ModuleNode moduleNode, @Nullable ClassNode scriptClass,
|
|
||||||
List<ClassNode> allClasses
|
|
||||||
) {}
|
|
||||||
|
|
||||||
PreambleResult getPreambleResult(@Nullable PreambleNode preambleNode, String templateName, TokenList tokens);
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
public interface TranspilerConfiguration {
|
public interface TranspilerConfiguration {
|
||||||
PreambleTranspiler getPreambleTranspiler();
|
|
||||||
BodyTranspiler getBodyTranspiler();
|
BodyTranspiler getBodyTranspiler();
|
||||||
OutStatementFactory getOutStatementFactory();
|
OutStatementFactory getOutStatementFactory();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
import groovy.lang.Tuple2;
|
import groovy.lang.Tuple2;
|
||||||
|
import groovy.transform.Field;
|
||||||
import groowt.view.component.ComponentContext;
|
import groowt.view.component.ComponentContext;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
import groowt.view.web.runtime.WebViewComponentWriter;
|
||||||
@ -18,6 +19,7 @@ public final class TranspilerUtil {
|
|||||||
public static final ClassNode COMPONENT_TEMPLATE = ClassHelper.make(ComponentTemplate.class);
|
public static final ClassNode COMPONENT_TEMPLATE = ClassHelper.make(ComponentTemplate.class);
|
||||||
public static final ClassNode OUT_TYPE = ClassHelper.make(WebViewComponentWriter.class);
|
public static final ClassNode OUT_TYPE = ClassHelper.make(WebViewComponentWriter.class);
|
||||||
public static final ClassNode CONTEXT_CLASSNODE = ClassHelper.make(ComponentContext.class);
|
public static final ClassNode CONTEXT_CLASSNODE = ClassHelper.make(ComponentContext.class);
|
||||||
|
public static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class);
|
||||||
|
|
||||||
public static final String GROOWT_VIEW_WEB = "groowt.view.web";
|
public static final String GROOWT_VIEW_WEB = "groowt.view.web";
|
||||||
public static final String OUT = "out";
|
public static final String OUT = "out";
|
||||||
|
@ -14,16 +14,10 @@ import java.util.stream.Collectors;
|
|||||||
public class WebViewComponentModuleNode extends ModuleNode {
|
public class WebViewComponentModuleNode extends ModuleNode {
|
||||||
|
|
||||||
public static void copyTo(ModuleNode from, WebViewComponentModuleNode to) {
|
public static void copyTo(ModuleNode from, WebViewComponentModuleNode to) {
|
||||||
to.setDescription(from.getDescription());
|
|
||||||
to.setPackage(from.getPackage());
|
|
||||||
to.setPackageName(from.getPackageName());
|
|
||||||
to.setImportsResolved(from.hasImportsResolved());
|
|
||||||
to.setMetaDataMap(from.getMetaDataMap());
|
|
||||||
from.getImports().forEach(to::addImport);
|
from.getImports().forEach(to::addImport);
|
||||||
from.getStarImports().forEach(to::addStarImport);
|
from.getStarImports().forEach(to::addStarImport);
|
||||||
from.getStaticImports().forEach(to::addStaticImport);
|
from.getStaticImports().forEach(to::addStaticImport);
|
||||||
from.getStaticStarImports().forEach(to::addStaticStarImport);
|
from.getStaticStarImports().forEach(to::addStaticStarImport);
|
||||||
from.getClasses().forEach(to::addClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final List<ImportNode> imports = new ArrayList<>();
|
protected final List<ImportNode> imports = new ArrayList<>();
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package groowt.view.web.transpiler;
|
|
||||||
|
|
||||||
import groowt.view.web.transpile.DefaultPreambleTranspiler;
|
|
||||||
|
|
||||||
public class DefaultPreambleTranspilerTests extends PreambleTranspilerTests {
|
|
||||||
|
|
||||||
protected static DefaultPreambleTranspiler getDefaultPreambleTranspiler() {
|
|
||||||
return new DefaultPreambleTranspiler();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultPreambleTranspilerTests() {
|
|
||||||
super(getDefaultPreambleTranspiler());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -4,12 +4,18 @@ import groowt.view.web.antlr.ParserUtil;
|
|||||||
import groowt.view.web.antlr.TokenList;
|
import groowt.view.web.antlr.TokenList;
|
||||||
import groowt.view.web.ast.DefaultAstBuilder;
|
import groowt.view.web.ast.DefaultAstBuilder;
|
||||||
import groowt.view.web.ast.DefaultNodeFactory;
|
import groowt.view.web.ast.DefaultNodeFactory;
|
||||||
|
import groowt.view.web.ast.node.BodyNode;
|
||||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||||
import groowt.view.web.ast.node.GStringBodyTextNode;
|
import groowt.view.web.ast.node.GStringBodyTextNode;
|
||||||
import groowt.view.web.transpile.GStringTranspiler;
|
import groowt.view.web.transpile.GStringTranspiler;
|
||||||
|
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
||||||
import org.codehaus.groovy.ast.expr.GStringExpression;
|
import org.codehaus.groovy.ast.expr.GStringExpression;
|
||||||
|
import org.codehaus.groovy.ast.expr.VariableExpression;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public abstract class GStringTranspilerTests {
|
public abstract class GStringTranspilerTests {
|
||||||
@ -23,20 +29,55 @@ public abstract class GStringTranspilerTests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
protected BodyNode getBodyNode(String source) {
|
||||||
public void gStringExpressionWithDollarReference() {
|
|
||||||
final var source = "Hello, $target!";
|
|
||||||
final var parseResult = ParserUtil.parseCompilationUnit(source);
|
final var parseResult = ParserUtil.parseCompilationUnit(source);
|
||||||
final var tokenList = new TokenList(parseResult.getTokenStream());
|
final var tokenList = new TokenList(parseResult.getTokenStream());
|
||||||
final var nodeFactory = new DefaultNodeFactory(tokenList);
|
final var nodeFactory = new DefaultNodeFactory(tokenList);
|
||||||
final var astBuilder = new DefaultAstBuilder(nodeFactory);
|
final var astBuilder = new DefaultAstBuilder(nodeFactory);
|
||||||
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
|
final var cuNode = (CompilationUnitNode) astBuilder.build(parseResult.getCompilationUnitContext());
|
||||||
final var bodyNode = cuNode.getBodyNode();
|
return Objects.requireNonNull(cuNode.getBodyNode());
|
||||||
assertNotNull(bodyNode);
|
}
|
||||||
final var gStringBodyTextNode = bodyNode.getAt(0, GStringBodyTextNode.class);
|
|
||||||
|
protected void doTest(String source, Consumer<GStringExpression> further) {
|
||||||
|
final var gStringBodyTextNode = this.getBodyNode(source).getAt(0, GStringBodyTextNode.class);
|
||||||
final var transpiler = this.getGStringTranspiler();
|
final var transpiler = this.getGStringTranspiler();
|
||||||
final GStringExpression gStringExpression = transpiler.createGStringExpression(gStringBodyTextNode);
|
final GStringExpression gStringExpression = transpiler.createGStringExpression(gStringBodyTextNode);
|
||||||
assertEquals("Hello, $target!", gStringExpression.getText());
|
assertEquals(source, gStringExpression.getText());
|
||||||
|
further.accept(gStringExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void gStringExpressionWithDollarReference() {
|
||||||
|
this.doTest("Hello, $target!", gStringExpression -> {
|
||||||
|
assertEquals(2, gStringExpression.getStrings().size());
|
||||||
|
assertEquals(1, gStringExpression.getValues().size());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void multiplePathValues() {
|
||||||
|
this.doTest("$greeting, $target!", gStringExpression -> {
|
||||||
|
assertEquals(3, gStringExpression.getStrings().size());
|
||||||
|
assertEquals(2, gStringExpression.getValues().size());
|
||||||
|
final var firstValue = gStringExpression.getValue(0);
|
||||||
|
assertInstanceOf(VariableExpression.class, firstValue);
|
||||||
|
assertEquals("greeting", firstValue.getText());
|
||||||
|
final var secondValue = gStringExpression.getValue(1);
|
||||||
|
assertInstanceOf(VariableExpression.class, secondValue);
|
||||||
|
assertEquals("target", secondValue.getText());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pathAndClosure() {
|
||||||
|
this.doTest("$greeting, ${consume(out)}!", gStringExpression -> {
|
||||||
|
assertEquals(3, gStringExpression.getStrings().size());
|
||||||
|
assertEquals(2, gStringExpression.getValues().size());
|
||||||
|
final var firstValue = gStringExpression.getValue(0);
|
||||||
|
assertInstanceOf(VariableExpression.class, firstValue);
|
||||||
|
assertEquals("greeting", firstValue.getText());
|
||||||
|
assertInstanceOf(ClosureExpression.class, gStringExpression.getValue(1));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package groowt.view.web.transpiler;
|
|
||||||
|
|
||||||
import groowt.view.web.transpile.PreambleTranspiler;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
public abstract class PreambleTranspilerTests {
|
|
||||||
|
|
||||||
protected final PreambleTranspiler preambleTranspiler;
|
|
||||||
|
|
||||||
public PreambleTranspilerTests(PreambleTranspiler preambleTranspiler) {
|
|
||||||
this.preambleTranspiler = preambleTranspiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void smokeScreen() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +1,3 @@
|
|||||||
#/usr/bin/env bash
|
#/usr/bin/env bash
|
||||||
|
|
||||||
java -cp build/libs/web-tools.jar:build/libs/web.jar $mainClassName "\$@"
|
../gradlew toolsJar && java -cp build/libs/web-tools.jar:build/libs/web.jar $mainClassName "\$@"
|
||||||
|
@ -36,6 +36,9 @@ class ConvertToGroovy implements Callable<Integer> {
|
|||||||
@CommandLine.Option(names = ['-o', '--out'], description = 'Write source files to disk instead of printing them.')
|
@CommandLine.Option(names = ['-o', '--out'], description = 'Write source files to disk instead of printing them.')
|
||||||
boolean writeOut
|
boolean writeOut
|
||||||
|
|
||||||
|
@CommandLine.Option(names = ['-q', '--quiet'], description = 'Do not print the class source to the console.')
|
||||||
|
boolean quiet
|
||||||
|
|
||||||
// Default is Phases.CLASS_GENERATION (7)
|
// Default is Phases.CLASS_GENERATION (7)
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ['-t', '--compilePhase'],
|
names = ['-t', '--compilePhase'],
|
||||||
@ -44,6 +47,12 @@ class ConvertToGroovy implements Callable<Integer> {
|
|||||||
)
|
)
|
||||||
int compilePhase
|
int compilePhase
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ['-c', '--classes'],
|
||||||
|
description = 'Whether to output the class files, if the compile phase is late enough. If this is false, any generated classes will be thrown out.'
|
||||||
|
)
|
||||||
|
boolean doClasses
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ['-d', '--classesDir'],
|
names = ['-d', '--classesDir'],
|
||||||
description = 'If the GroovyCompiler outputs classes, where to write them.'
|
description = 'If the GroovyCompiler outputs classes, where to write them.'
|
||||||
@ -60,7 +69,9 @@ class ConvertToGroovy implements Callable<Integer> {
|
|||||||
def astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList))
|
def astBuilder = new DefaultAstBuilder(new DefaultNodeFactory(tokenList))
|
||||||
def cuNode = astBuilder.build(parseResult.compilationUnitContext) as CompilationUnitNode
|
def cuNode = astBuilder.build(parseResult.compilationUnitContext) as CompilationUnitNode
|
||||||
def config = new CompilerConfiguration().tap {
|
def config = new CompilerConfiguration().tap {
|
||||||
it.targetDirectory = this.classesDir ?: new File(target.parentFile, 'classes')
|
it.targetDirectory = this.doClasses
|
||||||
|
? this.classesDir ?: new File(target.parentFile, 'classes')
|
||||||
|
: File.createTempDir()
|
||||||
}
|
}
|
||||||
def gcu = new CompilationUnit(config)
|
def gcu = new CompilationUnit(config)
|
||||||
|
|
||||||
@ -84,7 +95,8 @@ class ConvertToGroovy implements Callable<Integer> {
|
|||||||
if (this.writeOut) {
|
if (this.writeOut) {
|
||||||
def outFile = new File(target.parentFile, name + '.groovy')
|
def outFile = new File(target.parentFile, name + '.groovy')
|
||||||
outFile.write(w.toString())
|
outFile.write(w.toString())
|
||||||
} else {
|
}
|
||||||
|
if (!this.quiet) {
|
||||||
println w.toString()
|
println w.toString()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package groowt.view.web.tools
|
||||||
|
|
||||||
|
import groowt.view.component.DefaultComponentContext
|
||||||
|
import groowt.view.web.DefaultWebComponentTemplateCompiler
|
||||||
|
import groowt.view.web.DefaultWebViewComponent
|
||||||
|
import groowt.view.web.WebViewTemplateComponentSource
|
||||||
|
import groowt.view.web.runtime.WebViewComponentWriter
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
|
import picocli.CommandLine
|
||||||
|
import picocli.CommandLine.Command
|
||||||
|
import picocli.CommandLine.Option
|
||||||
|
import picocli.CommandLine.Parameters
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable
|
||||||
|
|
||||||
|
@Command(
|
||||||
|
name = 'runTemplate',
|
||||||
|
description = 'render a wvc template with the given params'
|
||||||
|
)
|
||||||
|
class RunTemplate implements Callable<Integer> {
|
||||||
|
|
||||||
|
@Parameters(arity = '1', description = 'The template file.')
|
||||||
|
File template
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
names = ['-A', '--attr', '--attribute'],
|
||||||
|
description = 'Attribute(s) to pass to the template.'
|
||||||
|
)
|
||||||
|
Map<String, String> properties
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Integer call() throws Exception {
|
||||||
|
def component = new DefaultWebViewComponent(WebViewTemplateComponentSource.of(this.template))
|
||||||
|
|
||||||
|
def context = new DefaultComponentContext()
|
||||||
|
context.pushDefaultScope()
|
||||||
|
component.context = context
|
||||||
|
|
||||||
|
println component.render()
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static void main(String[] args) {
|
||||||
|
System.exit(new CommandLine(new RunTemplate()).execute(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user