diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultAstBuilderVisitor.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultAstBuilderVisitor.java index f60dddd..c406e98 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultAstBuilderVisitor.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultAstBuilderVisitor.java @@ -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; diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultNodeFactory.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultNodeFactory.java index 41880c3..b7f8d9e 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultNodeFactory.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/DefaultNodeFactory.java @@ -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 children) { - return this.objectFactory.get(QuestionNode.class, tokenRange, children); + public QuestionNode questionTagNode( + TokenRange tokenRange, + Token openToken, + Token closeToken, + List children + ) { + return this.objectFactory.get(QuestionNode.class, tokenRange, openToken, closeToken, children); } @Override - public HtmlCommentNode htmlCommentNode(TokenRange tokenRange, List children) { - return this.objectFactory.get(HtmlCommentNode.class, tokenRange, children); + public HtmlCommentNode htmlCommentNode( + TokenRange tokenRange, + Token openToken, + Token closeToken, + List 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 diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/NodeFactory.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/NodeFactory.java index 5a80a35..7e1942b 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/NodeFactory.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/NodeFactory.java @@ -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 children); - QuestionNode questionTagNode(TokenRange tokenRange, List children); + QuestionNode questionTagNode( + TokenRange tokenRange, + Token openToken, + Token closeToken, + List children + ); - HtmlCommentNode htmlCommentNode(TokenRange tokenRange, List children); + HtmlCommentNode htmlCommentNode( + TokenRange tokenRange, + Token openToken, + Token closeToken, + List 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); diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringNodeExtension.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringNodeExtension.java index 704c9ed..f81d7ff 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringNodeExtension.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringNodeExtension.java @@ -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 { diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringPathExtension.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringPathExtension.java index b5602c3..5057b51 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringPathExtension.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringPathExtension.java @@ -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 diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringScriptletExtension.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringScriptletExtension.java index 04a1653..4841fc6 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringScriptletExtension.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/extension/GStringScriptletExtension.java @@ -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 diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/BodyTextNode.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/BodyTextNode.java index 208537f..4e61bcf 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/BodyTextNode.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/BodyTextNode.java @@ -29,4 +29,8 @@ public class BodyTextNode extends AbstractTreeNode implements BodyChildNode { super(tokenRange, extensionContainer, childrenAsNodes(checkChildren(children))); } + public List getChildrenAsBodyTextChildren() { + return this.getChildren().stream().map(BodyTextChild.class::cast).toList(); + } + } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarReferenceNode.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarReferenceNode.java index e922fae..6677156 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarReferenceNode.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarReferenceNode.java @@ -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 parts; @Inject public DollarReferenceNode( - TokenList tokenList, NodeExtensionContainer extensionContainer, @Given TokenRange tokenRange, - @Given int groovyTokenIndex + List 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 getParts() { + return this.parts; } } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarScriptletNode.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarScriptletNode.java index 827f851..c30e36d 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarScriptletNode.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/DollarScriptletNode.java @@ -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; } } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/EqualsScriptletNode.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/EqualsScriptletNode.java index 3da76e0..01f4376 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/EqualsScriptletNode.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/EqualsScriptletNode.java @@ -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, String>) this::toValidGroovyCode - ); - } - - protected String toValidGroovyCode(List 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; } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/HtmlCommentNode.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/HtmlCommentNode.java index fe5b4d1..af05a24 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/HtmlCommentNode.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/HtmlCommentNode.java @@ -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 children ) { super(tokenRange, extensionContainer, children.stream().map(HtmlCommentChild::asNode).toList()); + this.openToken = openToken; + this.closeToken = closeToken; + } + + public List getChildrenAsHtmlCommentChildren() { + return this.getChildren().stream().map(HtmlCommentChild.class::cast).toList(); + } + + public Token getOpenToken() { + return this.openToken; + } + + public Token getCloseToken() { + return this.closeToken; } } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/PlainScriptletNode.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/PlainScriptletNode.java index c194696..3717c80 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/PlainScriptletNode.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/PlainScriptletNode.java @@ -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, String>) this::toValidGroovyCode - ); - } - - protected String toValidGroovyCode(List 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; } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/QuestionNode.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/QuestionNode.java index c73de51..0504db5 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/QuestionNode.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/ast/node/QuestionNode.java @@ -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 children ) { super(tokenRange, extensionContainer, children.stream().map(QuestionTagChild::asNode).toList()); + this.openToken = openToken; + this.closeToken = closeToken; + } + + public List getChildrenAsQuestionTagChildren() { + return this.getChildren().stream().map(QuestionTagChild.class::cast).toList(); + } + + public Token getOpenToken() { + return this.openToken; + } + + public Token getCloseToken() { + return this.closeToken; } } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/AppendOrAddStatementFactory.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/AppendOrAddStatementFactory.java index eaa7123..65ae3d1 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/AppendOrAddStatementFactory.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/AppendOrAddStatementFactory.java @@ -6,6 +6,7 @@ import org.codehaus.groovy.ast.stmt.Statement; import java.util.function.Function; +@Deprecated public interface AppendOrAddStatementFactory { enum Action { diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/BodyTextTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/BodyTextTranspiler.java new file mode 100644 index 0000000..63bb432 --- /dev/null +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/BodyTextTranspiler.java @@ -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 createBodyTextStatements(BodyTextNode bodyTextNode, TranspilerState state); +} diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ComponentTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ComponentTranspiler.java index a8b785c..accc774 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ComponentTranspiler.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ComponentTranspiler.java @@ -6,8 +6,5 @@ import org.codehaus.groovy.ast.stmt.Statement; import java.util.List; public interface ComponentTranspiler { - List createComponentStatements( - ComponentNode componentNode, - TranspilerState state - ); + List createComponentStatements(ComponentNode componentNode, TranspilerState state); } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultBodyTextTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultBodyTextTranspiler.java new file mode 100644 index 0000000..970c5a7 --- /dev/null +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultBodyTextTranspiler.java @@ -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 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 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 handleBodyTextChild(BodyTextChild child, TranspilerState state) { + final List 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 createBodyTextStatements(BodyTextNode bodyTextNode, TranspilerState state) { + final List result = new ArrayList<>(); + for (final BodyTextChild child : bodyTextNode.getChildrenAsBodyTextChildren()) { + result.addAll(this.handleBodyTextChild(child, state)); + } + return result; + } + +} diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultBodyTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultBodyTranspiler.java index 695e3aa..1d35006 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultBodyTranspiler.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultBodyTranspiler.java @@ -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." )); diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultGroovyBodyNodeTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultGroovyBodyNodeTranspiler.java new file mode 100644 index 0000000..18f8843 --- /dev/null +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultGroovyBodyNodeTranspiler.java @@ -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." + )); + }; + } + +} diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultJStringTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultJStringTranspiler.java index 604b577..f33a718 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultJStringTranspiler.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultJStringTranspiler.java @@ -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; diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultTranspilerConfiguration.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultTranspilerConfiguration.java index 2494c11..a72ace7 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultTranspilerConfiguration.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/DefaultTranspilerConfiguration.java @@ -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 diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/GroovyBodyNodeTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/GroovyBodyNodeTranspiler.java new file mode 100644 index 0000000..1d98a9c --- /dev/null +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/GroovyBodyNodeTranspiler.java @@ -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); +} diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/JStringTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/JStringTranspiler.java index fba1d01..41de8d7 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/JStringTranspiler.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/JStringTranspiler.java @@ -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); diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/LeftShiftFactory.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/LeftShiftFactory.java new file mode 100644 index 0000000..3b75e57 --- /dev/null +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/LeftShiftFactory.java @@ -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); +} diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/PositionSetter.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/PositionSetter.java index 1d719dc..69ea82d 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/PositionSetter.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/PositionSetter.java @@ -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); + } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/SimplePositionSetter.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/SimplePositionSetter.java index 6ff5d82..d3a1706 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/SimplePositionSetter.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/SimplePositionSetter.java @@ -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); + } + } diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/TranspilerUtil.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/TranspilerUtil.java index 59cb038..d2b6463 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/TranspilerUtil.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/TranspilerUtil.java @@ -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() { diff --git a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ValueNodeTranspiler.java b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ValueNodeTranspiler.java index 2e8eedc..196c071 100644 --- a/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ValueNodeTranspiler.java +++ b/web-view-components-compiler/src/main/java/groowt/view/component/web/transpile/ValueNodeTranspiler.java @@ -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); } diff --git a/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/ast/NodeFactoryTests.java b/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/ast/NodeFactoryTests.java index cd4e2c0..ac0c1d4 100644 --- a/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/ast/NodeFactoryTests.java +++ b/web-view-components-compiler/src/testFixtures/java/groowt/view/component/web/ast/NodeFactoryTests.java @@ -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