Refactoring transpiler.

This commit is contained in:
JesseBrault0709 2024-05-26 12:51:02 +02:00
parent dd92d99afd
commit d116b2c555
29 changed files with 463 additions and 164 deletions

View File

@ -135,7 +135,12 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
children.add((QuestionTagChild) childResult);
}
}
return this.nodeFactory.questionTagNode(this.getTokenRange(ctx), children);
return this.nodeFactory.questionTagNode(
this.getTokenRange(ctx),
ctx.QuestionTagOpen().getSymbol(),
ctx.QuestionTagClose().getSymbol(),
children
);
}
@Override
@ -147,7 +152,12 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
children.add((HtmlCommentChild) childResult);
}
}
return this.nodeFactory.htmlCommentNode(this.getTokenRange(ctx), children);
return this.nodeFactory.htmlCommentNode(
this.getTokenRange(ctx),
ctx.HtmlCommentOpen().getSymbol(),
ctx.HtmlCommentClose().getSymbol(),
children
);
}
@Override
@ -340,7 +350,7 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
if (groovyCode != null) {
return this.nodeFactory.equalsScriptletNode(
this.getTokenRange(ctx),
ctx.GroovyCode().getSymbol().getTokenIndex()
ctx.GroovyCode().getSymbol().getText()
);
} else {
return null;
@ -363,7 +373,7 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
if (groovyCode != null) {
return this.nodeFactory.dollarScriptletNode(
this.getTokenRange(ctx),
groovyCode.getSymbol().getTokenIndex()
groovyCode.getSymbol().getText()
);
} else {
return null;

View File

@ -7,6 +7,7 @@ import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.extension.*;
import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -109,13 +110,23 @@ public class DefaultNodeFactory implements NodeFactory {
}
@Override
public QuestionNode questionTagNode(TokenRange tokenRange, List<? extends QuestionTagChild> children) {
return this.objectFactory.get(QuestionNode.class, tokenRange, children);
public QuestionNode questionTagNode(
TokenRange tokenRange,
Token openToken,
Token closeToken,
List<? extends QuestionTagChild> children
) {
return this.objectFactory.get(QuestionNode.class, tokenRange, openToken, closeToken, children);
}
@Override
public HtmlCommentNode htmlCommentNode(TokenRange tokenRange, List<? extends HtmlCommentChild> children) {
return this.objectFactory.get(HtmlCommentNode.class, tokenRange, children);
public HtmlCommentNode htmlCommentNode(
TokenRange tokenRange,
Token openToken,
Token closeToken,
List<? extends HtmlCommentChild> children
) {
return this.objectFactory.get(HtmlCommentNode.class, tokenRange, openToken, closeToken, children);
}
@Override
@ -209,8 +220,8 @@ public class DefaultNodeFactory implements NodeFactory {
}
@Override
public EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, int groovyIndex) {
return this.objectFactory.get(EqualsScriptletNode.class, tokenRange, groovyIndex);
public EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, String groovyCode) {
return this.objectFactory.get(EqualsScriptletNode.class, tokenRange, groovyCode);
}
@Override
@ -219,8 +230,8 @@ public class DefaultNodeFactory implements NodeFactory {
}
@Override
public DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, int groovyIndex) {
return this.objectFactory.get(DollarScriptletNode.class, tokenRange, groovyIndex);
public DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, String groovyCode) {
return this.objectFactory.get(DollarScriptletNode.class, tokenRange, groovyCode);
}
@Override

View File

@ -2,6 +2,7 @@ package groowt.view.component.web.ast;
import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@ -20,9 +21,19 @@ public interface NodeFactory {
BodyTextNode bodyTextNode(TokenRange tokenRange, List<? extends BodyTextChild> children);
QuestionNode questionTagNode(TokenRange tokenRange, List<? extends QuestionTagChild> children);
QuestionNode questionTagNode(
TokenRange tokenRange,
Token openToken,
Token closeToken,
List<? extends QuestionTagChild> children
);
HtmlCommentNode htmlCommentNode(TokenRange tokenRange, List<? extends HtmlCommentChild> children);
HtmlCommentNode htmlCommentNode(
TokenRange tokenRange,
Token openToken,
Token closeToken,
List<? extends HtmlCommentChild> children
);
TextNode textNode(TokenRange tokenRange, String content);
@ -63,11 +74,11 @@ public interface NodeFactory {
ComponentValueNode componentValueNode(TokenRange tokenRange, ComponentNode componentNode);
EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, int groovyIndex);
EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, String groovyCode);
PlainScriptletNode plainScriptletNode(TokenRange tokenRange, int groovyIndex);
DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, int groovyIndex);
DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, String groovyCode);
DollarReferenceNode dollarReferenceNode(TokenRange tokenRange, int groovyIndex);

View File

@ -6,6 +6,7 @@ import org.antlr.v4.runtime.Token;
import java.util.List;
import java.util.stream.Collectors;
@Deprecated
public abstract sealed class GStringNodeExtension implements NodeExtension
permits GStringPathExtension, GStringScriptletExtension {

View File

@ -6,6 +6,7 @@ import groowt.view.component.web.ast.node.Node;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
@Deprecated
public non-sealed class GStringPathExtension extends GStringNodeExtension {
@Inject

View File

@ -6,6 +6,7 @@ import groowt.view.component.web.ast.node.Node;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
@Deprecated
public non-sealed class GStringScriptletExtension extends GStringNodeExtension {
@Inject

View File

@ -29,4 +29,8 @@ public class BodyTextNode extends AbstractTreeNode implements BodyChildNode {
super(tokenRange, extensionContainer, childrenAsNodes(checkChildren(children)));
}
public List<BodyTextChild> getChildrenAsBodyTextChildren() {
return this.getChildren().stream().map(BodyTextChild.class::cast).toList();
}
}

View File

@ -1,34 +1,28 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.extension.GStringPathExtension;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
import java.util.List;
public class DollarReferenceNode extends AbstractLeafNode implements GroovyBodyNode {
private final int groovyTokenIndex;
private final List<String> parts;
@Inject
public DollarReferenceNode(
TokenList tokenList,
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given int groovyTokenIndex
List<String> parts
) {
super(tokenRange, extensionContainer);
this.groovyTokenIndex = groovyTokenIndex;
this.createGStringPath(tokenList);
this.parts = parts;
}
protected void createGStringPath(TokenList tokenList) {
this.createExtension(GStringPathExtension.class, TokenRange.fromIndex(tokenList, this.groovyTokenIndex));
}
public GStringPathExtension getGStringPath() {
return this.getExtension(GStringPathExtension.class);
public List<String> getParts() {
return this.parts;
}
}

View File

@ -1,33 +1,26 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.extension.GStringScriptletExtension;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
public class DollarScriptletNode extends AbstractLeafNode implements GroovyBodyNode {
private final GStringScriptletExtension gStringScriptlet;
private final String groovyCode;
@Inject
public DollarScriptletNode(
TokenList tokenList,
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given int groovyTokenIndex
@Given String groovyCode
) {
super(tokenRange, extensionContainer);
this.gStringScriptlet = this.createGStringScriptlet(tokenList, groovyTokenIndex);
this.groovyCode = groovyCode;
}
protected GStringScriptletExtension createGStringScriptlet(TokenList tokenList, int groovyTokenIndex) {
return this.createExtension(GStringScriptletExtension.class, TokenRange.fromIndex(tokenList, groovyTokenIndex));
}
public GStringScriptletExtension getGStringScriptlet() {
return this.gStringScriptlet;
public String getGroovyCode() {
return this.groovyCode;
}
}

View File

@ -1,49 +1,23 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.extension.GroovyCodeNodeExtension;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class EqualsScriptletNode extends AbstractLeafNode implements GroovyBodyNode {
private final int groovyIndex;
private final GroovyCodeNodeExtension groovyCode;
private final String groovyCode;
public EqualsScriptletNode(
TokenList tokenList,
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given int groovyIndex
@Given String groovyCode
) {
super(tokenRange, extensionContainer);
this.groovyIndex = groovyIndex;
this.groovyCode = this.createGroovyCode(tokenList);
this.groovyCode = groovyCode;
}
protected GroovyCodeNodeExtension createGroovyCode(TokenList tokenList) {
return this.createExtension(
GroovyCodeNodeExtension.class,
TokenRange.fromIndex(tokenList, this.groovyIndex),
(Function<? super List<Token>, String>) this::toValidGroovyCode
);
}
protected String toValidGroovyCode(List<Token> groovyTokens) {
return "{ -> " + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + " }";
}
public int getGroovyIndex() {
return this.groovyIndex;
}
public GroovyCodeNodeExtension getGroovyCode() {
public String getGroovyCode() {
return this.groovyCode;
}

View File

@ -4,18 +4,38 @@ import groowt.util.di.annotation.Given;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
import org.antlr.v4.runtime.Token;
import java.util.List;
public class HtmlCommentNode extends AbstractTreeNode implements BodyTextChild {
private final Token openToken;
private final Token closeToken;
@Inject
public HtmlCommentNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given Token openToken,
@Given Token closeToken,
@Given List<? extends HtmlCommentChild> children
) {
super(tokenRange, extensionContainer, children.stream().map(HtmlCommentChild::asNode).toList());
this.openToken = openToken;
this.closeToken = closeToken;
}
public List<HtmlCommentChild> getChildrenAsHtmlCommentChildren() {
return this.getChildren().stream().map(HtmlCommentChild.class::cast).toList();
}
public Token getOpenToken() {
return this.openToken;
}
public Token getCloseToken() {
return this.closeToken;
}
}

View File

@ -2,50 +2,26 @@ package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.extension.GroovyCodeNodeExtension;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
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 PlainScriptletNode extends AbstractLeafNode implements GroovyBodyNode {
private final int groovyIndex;
private final GroovyCodeNodeExtension groovyCode;
private final String groovyCode;
@Inject
public PlainScriptletNode(
TokenList tokenList,
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given int groovyIndex
@Given String groovyCode
) {
super(tokenRange, extensionContainer);
this.groovyIndex = groovyIndex;
this.groovyCode = this.createGroovyCode(tokenList);
this.groovyCode = groovyCode;
}
protected GroovyCodeNodeExtension createGroovyCode(TokenList tokenList) {
return this.createExtension(
GroovyCodeNodeExtension.class,
TokenRange.fromIndex(tokenList, this.groovyIndex),
(Function<? super List<Token>, String>) this::toValidGroovyCode
);
}
protected String toValidGroovyCode(List<Token> groovyTokens) {
return "{ Writer out -> " + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + " }";
}
public int getGroovyIndex() {
return this.groovyIndex;
}
public GroovyCodeNodeExtension getGroovyCode() {
public String getGroovyCode() {
return this.groovyCode;
}

View File

@ -4,18 +4,38 @@ import groowt.util.di.annotation.Given;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
import org.antlr.v4.runtime.Token;
import java.util.List;
public class QuestionNode extends AbstractTreeNode implements BodyTextChild {
private final Token openToken;
private final Token closeToken;
@Inject
public QuestionNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given Token openToken,
@Given Token closeToken,
@Given List<? extends QuestionTagChild> children
) {
super(tokenRange, extensionContainer, children.stream().map(QuestionTagChild::asNode).toList());
this.openToken = openToken;
this.closeToken = closeToken;
}
public List<QuestionTagChild> getChildrenAsQuestionTagChildren() {
return this.getChildren().stream().map(QuestionTagChild.class::cast).toList();
}
public Token getOpenToken() {
return this.openToken;
}
public Token getCloseToken() {
return this.closeToken;
}
}

View File

@ -6,6 +6,7 @@ import org.codehaus.groovy.ast.stmt.Statement;
import java.util.function.Function;
@Deprecated
public interface AppendOrAddStatementFactory {
enum Action {

View File

@ -0,0 +1,10 @@
package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.BodyTextNode;
import org.codehaus.groovy.ast.stmt.Statement;
import java.util.List;
public interface BodyTextTranspiler {
List<Statement> createBodyTextStatements(BodyTextNode bodyTextNode, TranspilerState state);
}

View File

@ -6,8 +6,5 @@ import org.codehaus.groovy.ast.stmt.Statement;
import java.util.List;
public interface ComponentTranspiler {
List<Statement> createComponentStatements(
ComponentNode componentNode,
TranspilerState state
);
List<Statement> createComponentStatements(ComponentNode componentNode, TranspilerState state);
}

View File

@ -0,0 +1,107 @@
package groowt.view.component.web.transpile;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*;
import jakarta.inject.Inject;
import org.antlr.v4.runtime.Token;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.stmt.Statement;
import java.util.ArrayList;
import java.util.List;
import static groowt.view.component.web.transpile.TranspilerUtil.getStringLiteral;
public class DefaultBodyTextTranspiler implements BodyTextTranspiler {
private final GroovyBodyNodeTranspiler groovyBodyNodeTranspiler;
private final PositionSetter positionSetter;
private final LeftShiftFactory leftShiftFactory;
private final boolean includeComments;
@Inject
public DefaultBodyTextTranspiler(
GroovyBodyNodeTranspiler groovyBodyNodeTranspiler,
PositionSetter positionSetter,
LeftShiftFactory leftShiftFactory,
@Given boolean includeComments
) {
this.groovyBodyNodeTranspiler = groovyBodyNodeTranspiler;
this.positionSetter = positionSetter;
this.leftShiftFactory = leftShiftFactory;
this.includeComments = includeComments;
}
protected Statement handleStringLiteral(Token source) {
final ConstantExpression literal = getStringLiteral(source.getText());
this.positionSetter.setPosition(literal, source);
return this.leftShiftFactory.create(literal);
}
protected Statement handleStringLiteral(Node source, String content) {
final ConstantExpression literal = getStringLiteral(content);
this.positionSetter.setPosition(literal, source);
return this.leftShiftFactory.create(literal);
}
protected List<Statement> handleHtmlCommentChild(HtmlCommentChild child, TranspilerState state) {
return switch (child) {
case BodyTextChild bodyTextChild -> this.handleBodyTextChild(bodyTextChild, state);
default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
"Unsupported HtmlCommentChild type " + child.getClass().getName()
));
};
}
protected List<Statement> handleQuestionTagChild(QuestionTagChild child, TranspilerState state) {
return switch (child) {
case BodyTextChild bodyTextChild -> this.handleBodyTextChild(bodyTextChild, state);
default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
"Unsupported QuestionTagChild type " + child.getClass().getName()
));
};
}
protected List<Statement> handleBodyTextChild(BodyTextChild child, TranspilerState state) {
final List<Statement> result = new ArrayList<>();
switch (child) {
case QuestionNode questionNode -> {
result.add(this.handleStringLiteral(questionNode.getOpenToken()));
questionNode.getChildrenAsQuestionTagChildren().stream()
.map(questionChild -> this.handleQuestionTagChild(questionChild, state))
.forEach(result::addAll);
result.add(this.handleStringLiteral(questionNode.getCloseToken()));
}
case HtmlCommentNode commentNode -> {
if (this.includeComments) {
result.add(this.handleStringLiteral(commentNode.getOpenToken()));
commentNode.getChildrenAsHtmlCommentChildren().stream()
.map(commentChild -> this.handleHtmlCommentChild(commentChild, state))
.forEach(result::addAll);
result.add(this.handleStringLiteral(commentNode.getCloseToken()));
}
}
case TextNode textNode -> {
result.add(this.handleStringLiteral(textNode, textNode.getContent()));
}
case GroovyBodyNode groovyBodyNode -> {
result.add(this.groovyBodyNodeTranspiler.createGroovyBodyNodeStatements(groovyBodyNode, state));
}
default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
"BodyTextChild of type " + child.getClass().getName() + " is not supported."
));
}
return result;
}
@Override
public List<Statement> createBodyTextStatements(BodyTextNode bodyTextNode, TranspilerState state) {
final List<Statement> result = new ArrayList<>();
for (final BodyTextChild child : bodyTextNode.getChildrenAsBodyTextChildren()) {
result.addAll(this.handleBodyTextChild(child, state));
}
return result;
}
}

View File

@ -1,26 +1,20 @@
package groowt.view.component.web.transpile;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*;
import jakarta.inject.Inject;
import org.codehaus.groovy.ast.expr.GStringExpression;
import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.ast.node.BodyTextNode;
import groowt.view.component.web.ast.node.ComponentNode;
import groowt.view.component.web.ast.node.Node;
import org.codehaus.groovy.ast.stmt.BlockStatement;
public class DefaultBodyTranspiler implements BodyTranspiler {
private final GStringTranspiler gStringTranspiler;
private final JStringTranspiler jStringTranspiler;
private final ComponentTranspiler componentTranspiler;
private final BodyTextTranspiler bodyTextTranspiler;
@Inject
public DefaultBodyTranspiler(
GStringTranspiler gStringTranspiler,
JStringTranspiler jStringTranspiler,
ComponentTranspiler componentTranspiler
) {
this.gStringTranspiler = gStringTranspiler;
this.jStringTranspiler = jStringTranspiler;
public DefaultBodyTranspiler(ComponentTranspiler componentTranspiler, BodyTextTranspiler bodyTextTranspiler) {
this.componentTranspiler = componentTranspiler;
this.bodyTextTranspiler = bodyTextTranspiler;
}
@Override
@ -33,27 +27,10 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
block.setVariableScope(state.pushScope());
for (final Node child : bodyNode.getChildren()) {
switch (child) {
case GStringBodyTextNode gStringBodyTextNode -> {
final GStringExpression gString = this.gStringTranspiler.createGStringExpression(
gStringBodyTextNode
);
block.addStatement(addOrAppendCallback.createStatement(gStringBodyTextNode, gString));
}
case JStringBodyTextNode jStringBodyTextNode -> {
block.addStatement(
addOrAppendCallback.createStatement(
jStringBodyTextNode,
this.jStringTranspiler.createStringLiteral(jStringBodyTextNode)
)
);
}
case ComponentNode componentNode -> {
// DO NOT add/append this, because the component transpiler does it already
block.addStatements(this.componentTranspiler.createComponentStatements(componentNode, state));
}
case PlainScriptletNode plainScriptletNode -> {
throw new UnsupportedOperationException("TODO");
}
case ComponentNode componentNode ->
block.addStatements(this.componentTranspiler.createComponentStatements(componentNode, state));
case BodyTextNode bodyTextNode ->
block.addStatements(this.bodyTextTranspiler.createBodyTextStatements(bodyTextNode, state));
default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
"BodyNode child of type " + child.getClass().getSimpleName() + " is not supported."
));

View File

@ -0,0 +1,132 @@
package groowt.view.component.web.transpile;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import jakarta.inject.Inject;
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 java.util.List;
public class DefaultGroovyBodyNodeTranspiler implements GroovyBodyNodeTranspiler {
private final PositionSetter positionSetter;
private final LeftShiftFactory leftShiftFactory;
@Inject
public DefaultGroovyBodyNodeTranspiler(PositionSetter positionSetter, LeftShiftFactory leftShiftFactory) {
this.positionSetter = positionSetter;
this.leftShiftFactory = leftShiftFactory;
}
protected ClosureExpression convertToClosure(Node node, String source) {
final GroovyUtil.ConvertResult convertResult = GroovyUtil.convert(
"def cl = {" + source + "}"
);
final BlockStatement convertBlock = convertResult.blockStatement();
if (convertBlock == null) {
throw new WebViewComponentBugError("Did not expect convertBlock to be null.");
}
if (convertBlock.isEmpty()) {
throw new WebViewComponentBugError("Did not expect convertBlock to be empty.");
}
final ExpressionStatement clStmt = (ExpressionStatement) convertBlock.getStatements().getFirst();
final BinaryExpression clAssign = (BinaryExpression) clStmt.getExpression();
final ClosureExpression cl = (ClosureExpression) clAssign.getRightExpression();
final PositionVisitor positionVisitor = new PositionVisitor(
this.positionSetter.withOffset(0, -10), node
);
cl.visit(positionVisitor);
return cl;
}
protected Statement handleEqualsScriptlet(EqualsScriptletNode equalsScriptletNode, TranspilerState state) {
final ClosureExpression cl = this.convertToClosure(equalsScriptletNode, equalsScriptletNode.getGroovyCode());
final MethodCallExpression callExpr;
if (cl.getParameters() == null) {
callExpr = new MethodCallExpression(cl, "call", EmptyExpression.INSTANCE);
} else {
final ArgumentListExpression argsList = new ArgumentListExpression(
List.of(state.hasCurrentChildList() ? state.getCurrentChildList() : state.getWriter())
);
callExpr = new MethodCallExpression(cl, "call", argsList);
}
return this.leftShiftFactory.create(callExpr);
}
protected Statement handlePlainScriptlet(PlainScriptletNode plainScriptletNode, TranspilerState state) {
final ClosureExpression cl = this.convertToClosure(plainScriptletNode, plainScriptletNode.getGroovyCode());
final MethodCallExpression callExpr;
if (cl.getParameters() == null) {
callExpr = new MethodCallExpression(cl, "call", EmptyExpression.INSTANCE);
} else {
final ArgumentListExpression argsList = new ArgumentListExpression(
List.of(state.hasCurrentChildList() ? state.getCurrentChildList() : state.getWriter())
);
callExpr = new MethodCallExpression(cl, "call", argsList);
}
return new ExpressionStatement(callExpr);
}
protected Statement handleDollarScriptlet(DollarScriptletNode dollarScriptletNode) {
final ClosureExpression cl = this.convertToClosure(dollarScriptletNode, dollarScriptletNode.getGroovyCode());
final Expression toLeftShift;
if (cl.getParameters() == null) {
toLeftShift = cl;
} else {
final Statement stmt = cl.getCode();
if (stmt instanceof ExpressionStatement exprStmt) {
toLeftShift = exprStmt.getExpression();
} else {
toLeftShift = cl;
}
}
return this.leftShiftFactory.create(toLeftShift);
}
protected Statement handleDollarReference(DollarReferenceNode dollarReferenceNode) {
VariableExpression root = null;
PropertyExpression propertyExpr = null;
for (final String part : dollarReferenceNode.getParts()) {
if (root == null) {
root = new VariableExpression(part);
} else if (propertyExpr == null) {
propertyExpr = new PropertyExpression(root, part);
} else {
propertyExpr = new PropertyExpression(propertyExpr, part);
}
}
final var positionVisitor = new PositionVisitor(this.positionSetter, dollarReferenceNode);
if (propertyExpr != null) {
propertyExpr.visit(positionVisitor);
return this.leftShiftFactory.create(propertyExpr);
} else if (root != null) {
root.visit(positionVisitor);
return this.leftShiftFactory.create(root);
} else {
throw new WebViewComponentBugError("Did not expect root to be null.");
}
}
@Override
public Statement createGroovyBodyNodeStatements(GroovyBodyNode groovyBodyNode, TranspilerState state) {
return switch (groovyBodyNode) {
case EqualsScriptletNode equalsScriptletNode -> this.handleEqualsScriptlet(equalsScriptletNode, state);
case PlainScriptletNode plainScriptletNode -> this.handlePlainScriptlet(plainScriptletNode, state);
case DollarScriptletNode dollarScriptletNode -> this.handleDollarScriptlet(dollarScriptletNode);
case DollarReferenceNode dollarReferenceNode -> this.handleDollarReference(dollarReferenceNode);
default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
"GroovyBodyNode of type " + groovyBodyNode.getClass().getName() + " is not supported."
));
};
}
}

View File

@ -3,12 +3,11 @@ package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.JStringBodyTextNode;
import groowt.view.component.web.ast.node.JStringValueNode;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import static org.apache.groovy.parser.antlr4.util.StringUtils.*;
@Singleton
@Deprecated
public class DefaultJStringTranspiler implements JStringTranspiler {
private final PositionSetter positionSetter;

View File

@ -28,7 +28,7 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
DefaultProvider.ofLazy(BodyTranspiler.class, this::getBodyTranspiler)
);
this.valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
this.bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
this.bodyTranspiler = new DefaultBodyTranspiler(componentTranspiler, null); // TODO
}
@Override

View File

@ -0,0 +1,8 @@
package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.GroovyBodyNode;
import org.codehaus.groovy.ast.stmt.Statement;
public interface GroovyBodyNodeTranspiler {
Statement createGroovyBodyNodeStatements(GroovyBodyNode groovyBodyNode, TranspilerState state);
}

View File

@ -4,6 +4,7 @@ import groowt.view.component.web.ast.node.JStringBodyTextNode;
import groowt.view.component.web.ast.node.JStringValueNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
@Deprecated
public interface JStringTranspiler {
ConstantExpression createStringLiteral(JStringBodyTextNode bodyTextNode);
ConstantExpression createStringLiteral(JStringValueNode jStringValueNode);

View File

@ -0,0 +1,8 @@
package groowt.view.component.web.transpile;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.stmt.Statement;
public interface LeftShiftFactory {
Statement create(Expression rightSide);
}

View File

@ -2,6 +2,7 @@ package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.Node;
import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token;
import org.codehaus.groovy.ast.ASTNode;
public interface PositionSetter {
@ -24,8 +25,17 @@ public interface PositionSetter {
*/
void setPositionOffsetInContainer(ASTNode target, Node container);
void setPosition(ASTNode target, Token source);
void setPosition(ASTNode target, TokenRange tokenRange);
void setPosition(ASTNode target, Node source);
@Deprecated
void setPosition(ASTNode target, Node start, Node end);
@Deprecated
void setToStartOf(ASTNode target, Node source);
PositionSetter withOffset(int lineOffset, int columnOffset);
}

View File

@ -3,18 +3,35 @@ package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.Node;
import groowt.view.component.web.util.SourcePosition;
import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token;
import org.codehaus.groovy.ast.ASTNode;
import static groowt.view.component.web.util.SourcePosition.fromEndOfToken;
import static groowt.view.component.web.util.SourcePosition.fromStartOfToken;
public class SimplePositionSetter implements PositionSetter {
protected void set(ASTNode target, int startLine, int startColumn, int endLine, int endColumn) {
target.setLineNumber(startLine);
target.setColumnNumber(startColumn);
target.setLastLineNumber(endLine);
target.setLastColumnNumber(endColumn);
private final int lineOffset;
private final int columnOffset;
public SimplePositionSetter(int lineOffset, int columnOffset) {
this.lineOffset = lineOffset;
this.columnOffset = columnOffset;
}
protected void set(ASTNode target, SourcePosition start, SourcePosition end) {
public SimplePositionSetter() {
this.lineOffset = 0;
this.columnOffset = 0;
}
protected void set(ASTNode target, int startLine, int startColumn, int endLine, int endColumn) {
target.setLineNumber(startLine + this.lineOffset);
target.setColumnNumber(startColumn + this.columnOffset);
target.setLastLineNumber(endLine + this.lineOffset);
target.setLastColumnNumber(endColumn + this.columnOffset);
}
protected final void set(ASTNode target, SourcePosition start, SourcePosition end) {
this.set(target, start.line(), start.column(), end.line(), end.column());
}
@ -36,6 +53,11 @@ public class SimplePositionSetter implements PositionSetter {
this.set(target, startPosition, endPosition);
}
@Override
public void setPosition(ASTNode target, Token source) {
this.set(target, fromStartOfToken(source), fromEndOfToken(source));
}
@Override
public void setPosition(ASTNode target, TokenRange tokenRange) {
this.set(target, tokenRange.getStartPosition(), tokenRange.getEndPosition());
@ -49,11 +71,8 @@ public class SimplePositionSetter implements PositionSetter {
@Override
public void setPosition(ASTNode target, Node start, Node end) {
final var startPosition = start.getTokenRange().getStartPosition();
target.setLineNumber(startPosition.line());
target.setColumnNumber(startPosition.column());
final var endPosition = end.getTokenRange().getEndPosition();
target.setLastLineNumber(endPosition.line());
target.setLastColumnNumber(endPosition.column());
this.set(target, startPosition, endPosition);
}
@Override
@ -62,4 +81,9 @@ public class SimplePositionSetter implements PositionSetter {
this.set(target, tokenRange.getStartPosition(), tokenRange.getStartPosition());
}
@Override
public PositionSetter withOffset(int lineOffset, int columnOffset) {
return new SimplePositionSetter(lineOffset, columnOffset);
}
}

View File

@ -13,6 +13,8 @@ import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import static org.apache.groovy.parser.antlr4.util.StringUtils.*;
public final class TranspilerUtil {
public static final ClassNode COMPONENT_TEMPLATE = ClassHelper.make(ComponentTemplate.class);
@ -39,9 +41,11 @@ public final class TranspilerUtil {
}
public static ConstantExpression getStringLiteral(String content) {
final var e = new ConstantExpression(content);
e.setNodeMetaData("_IS_STRING", true);
return e;
final var withoutCR = removeCR(content);
final var escaped = replaceEscapes(withoutCR, NONE_SLASHY);
final var expr = new ConstantExpression(escaped);
expr.setNodeMetaData("_IS_STRING", true);
return expr;
}
public static Token getAssignToken() {

View File

@ -4,10 +4,5 @@ import groowt.view.component.web.ast.node.ValueNode;
import org.codehaus.groovy.ast.expr.Expression;
public interface ValueNodeTranspiler {
Expression createExpression(
ValueNode valueNode,
TranspilerState state
);
Expression createExpression(ValueNode valueNode, TranspilerState state);
}

View File

@ -80,15 +80,25 @@ public abstract class NodeFactoryTests {
}
@Test
public void questionTagNode(@Mock QuestionTagChild child, @Mock TreeNode childAsNode) {
public void questionTagNode(
@Mock Token open,
@Mock Token close,
@Mock QuestionTagChild child,
@Mock TreeNode childAsNode
) {
when(child.asNode()).thenReturn(childAsNode);
assertNotNull(this.nodeFactory.questionTagNode(this.getTokenRange(), List.of(child)));
assertNotNull(this.nodeFactory.questionTagNode(this.getTokenRange(), open, close, List.of(child)));
}
@Test
public void htmlCommentNode(@Mock HtmlCommentChild child, @Mock TreeNode childAsNode) {
public void htmlCommentNode(
@Mock Token open,
@Mock Token close,
@Mock HtmlCommentChild child,
@Mock TreeNode childAsNode
) {
when(child.asNode()).thenReturn(childAsNode);
assertNotNull(this.nodeFactory.htmlCommentNode(this.getTokenRange(), List.of(child)));
assertNotNull(this.nodeFactory.htmlCommentNode(this.getTokenRange(), open, close, List.of(child)));
}
@Test
@ -218,7 +228,7 @@ public abstract class NodeFactoryTests {
@Test
public void dollarScriptletNode() {
assertNotNull(this.nodeFactory.dollarScriptletNode(this.getTokenRange(), 0));
assertNotNull(this.nodeFactory.dollarScriptletNode(this.getTokenRange(), ""));
}
@Test