GString transpilation: values in correct order, verbatim fixed.

This commit is contained in:
JesseBrault0709 2024-05-03 18:13:41 +02:00
parent cd63bd60a7
commit 5c3d973c4c
20 changed files with 304 additions and 158 deletions

View File

@ -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 ->

View File

@ -1,2 +1,3 @@
*.groovy *.groovy
classes classes
.txt

View File

@ -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.

View File

@ -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() + "}";
}
} }

View File

@ -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;
} }
} }

View File

@ -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);
}
}
} }

View File

@ -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);
} }
} }

View File

@ -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,25 +183,11 @@ 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 PreambleResult preambleResult = configuration.getPreambleTranspiler().getPreambleResult(
compilationUnitNode.getPreambleNode(),
templateName,
tokens
);
if (preambleResult.moduleNode() != null) {
WebViewComponentModuleNode.copyTo(preambleResult.moduleNode(), moduleNode);
}
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 packageName = this.getPackageName(moduleNode);
final String templateClassName = packageName + "." + templateName; moduleNode.setPackageName(packageName);
mainClassNode = new ClassNode( final ClassNode mainClassNode = new ClassNode(
templateClassName, packageName + "." + templateName,
ACC_PUBLIC, ACC_PUBLIC,
ClassHelper.OBJECT_TYPE ClassHelper.OBJECT_TYPE
); );
@ -111,8 +195,14 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
mainClassNode.addInterface(TranspilerUtil.COMPONENT_TEMPLATE); mainClassNode.addInterface(TranspilerUtil.COMPONENT_TEMPLATE);
moduleNode.addClass(mainClassNode); 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();

View File

@ -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()
);
}
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
} }

View File

@ -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";

View File

@ -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<>();

View File

@ -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());
}
}

View File

@ -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));
});
} }
} }

View File

@ -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() {}
}

View File

@ -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 "\$@"

View File

@ -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

View File

@ -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))
}
}