Transpiler refactoring finished.

This commit is contained in:
JesseBrault0709 2024-05-27 09:02:53 +02:00
parent 2bac0f55dc
commit e112d81ea8
31 changed files with 287 additions and 861 deletions

View File

@ -361,7 +361,7 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
public @Nullable Node visitPlainScriptlet(WebViewComponentsParser.PlainScriptletContext ctx) { public @Nullable Node visitPlainScriptlet(WebViewComponentsParser.PlainScriptletContext ctx) {
final TerminalNode groovyCode = ctx.GroovyCode(); final TerminalNode groovyCode = ctx.GroovyCode();
if (groovyCode != null) { if (groovyCode != null) {
return this.nodeFactory.plainScriptletNode(this.getTokenRange(ctx), groovyCode.getSymbol().getTokenIndex()); return this.nodeFactory.plainScriptletNode(this.getTokenRange(ctx), groovyCode.getSymbol().getText());
} else { } else {
return null; return null;
} }
@ -381,16 +381,15 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
} }
@Override @Override
public @Nullable Node visitDollarReference(WebViewComponentsParser.DollarReferenceContext ctx) { public Node visitDollarReference(WebViewComponentsParser.DollarReferenceContext ctx) {
final TerminalNode groovyCode = ctx.GroovyCode(); final String groovyCode = ctx.GroovyCode().getText();
if (groovyCode != null) { final List<String> parts = new ArrayList<>();
return this.nodeFactory.dollarReferenceNode( if (groovyCode.contains(".")) {
this.getTokenRange(ctx), parts.addAll(List.of(groovyCode.split("\\.")));
groovyCode.getSymbol().getTokenIndex()
);
} else { } else {
return null; parts.add(groovyCode);
} }
return this.nodeFactory.dollarReferenceNode(this.getTokenRange(ctx), parts);
} }
@Override @Override

View File

@ -225,8 +225,8 @@ public class DefaultNodeFactory implements NodeFactory {
} }
@Override @Override
public PlainScriptletNode plainScriptletNode(TokenRange tokenRange, int groovyIndex) { public PlainScriptletNode plainScriptletNode(TokenRange tokenRange, String groovyCode) {
return this.objectFactory.get(PlainScriptletNode.class, tokenRange, groovyIndex); return this.objectFactory.get(PlainScriptletNode.class, tokenRange, groovyCode);
} }
@Override @Override
@ -235,8 +235,8 @@ public class DefaultNodeFactory implements NodeFactory {
} }
@Override @Override
public DollarReferenceNode dollarReferenceNode(TokenRange tokenRange, int groovyIndex) { public DollarReferenceNode dollarReferenceNode(TokenRange tokenRange, List<String> parts) {
return this.objectFactory.get(DollarReferenceNode.class, tokenRange, groovyIndex); return this.objectFactory.get(DollarReferenceNode.class, tokenRange, parts);
} }
} }

View File

@ -76,10 +76,10 @@ public interface NodeFactory {
EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, String groovyCode); EqualsScriptletNode equalsScriptletNode(TokenRange tokenRange, String groovyCode);
PlainScriptletNode plainScriptletNode(TokenRange tokenRange, int groovyIndex); PlainScriptletNode plainScriptletNode(TokenRange tokenRange, String groovyCode);
DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, String groovyCode); DollarScriptletNode dollarScriptletNode(TokenRange tokenRange, String groovyCode);
DollarReferenceNode dollarReferenceNode(TokenRange tokenRange, int groovyIndex); DollarReferenceNode dollarReferenceNode(TokenRange tokenRange, List<String> parts);
} }

View File

@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@Deprecated
public class GroovyCodeNodeExtension implements NodeExtension { public class GroovyCodeNodeExtension implements NodeExtension {
private final Node self; private final Node self;

View File

@ -38,7 +38,7 @@ public class ClosureValueNode extends AbstractLeafNode implements ValueNode {
} }
protected String toValidGroovyCode(List<Token> groovyTokens) { protected String toValidGroovyCode(List<Token> groovyTokens) {
return "def c = { " + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + "\n}"; return "def cl = {" + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + "}";
} }
public GroovyCodeNodeExtension getGroovyCode() { public GroovyCodeNodeExtension getGroovyCode() {

View File

@ -1,35 +0,0 @@
package groowt.view.component.web.ast.node;
import groowt.util.di.annotation.Given;
import groowt.view.component.web.ast.extension.GStringNodeExtension;
import groowt.view.component.web.ast.extension.NodeExtensionContainer;
import groowt.view.component.web.util.TokenRange;
import jakarta.inject.Inject;
import java.util.List;
@Deprecated
public class GStringBodyTextNode extends AbstractTreeNode implements BodyChildNode {
protected static List<? extends Node> checkChildren(List<? extends Node> children) {
for (final var child : children) {
if (!(child instanceof JStringBodyTextNode || child.hasExtension(GStringNodeExtension.class))) {
throw new IllegalArgumentException(
"Children of GStringBodyTextNode must be either a JStringBodyTextNode, " +
"or have a GStringNodeExtension."
);
}
}
return children;
}
@Inject
public GStringBodyTextNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given List<? extends Node> children
) {
super(tokenRange, extensionContainer, checkChildren(children));
}
}

View File

@ -1,27 +0,0 @@
package groowt.view.component.web.ast.node;
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;
@Deprecated
public class JStringBodyTextNode extends AbstractLeafNode implements BodyChildNode {
private final String content;
@Inject
public JStringBodyTextNode(
NodeExtensionContainer extensionContainer,
@Given TokenRange tokenRange,
@Given String content
) {
super(tokenRange, extensionContainer);
this.content = content;
}
public String getContent() {
return this.content;
}
}

View File

@ -1,22 +0,0 @@
package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.BodyChildNode;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.stmt.Statement;
import java.util.function.Function;
@Deprecated
public interface AppendOrAddStatementFactory {
enum Action {
ADD, APPEND
}
Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Function<Action, Expression> getRightSide);
default Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Expression rightSide) {
return this.addOrAppend(sourceNode, state, ignored -> rightSide);
}
}

View File

@ -1,22 +1,8 @@
package groowt.view.component.web.transpile; package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.BodyChildNode;
import groowt.view.component.web.ast.node.BodyNode; import groowt.view.component.web.ast.node.BodyNode;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.Statement;
public interface BodyTranspiler { public interface BodyTranspiler {
BlockStatement transpileBody(BodyNode bodyNode, TranspilerState state);
@FunctionalInterface
interface AddOrAppendCallback {
Statement createStatement(BodyChildNode source, Expression expression);
}
BlockStatement transpileBody(
BodyNode bodyNode,
AddOrAppendCallback addOrAppendCallback,
TranspilerState state
);
} }

View File

@ -1,79 +0,0 @@
package groowt.view.component.web.transpile;
import groovy.lang.Tuple2;
import groowt.view.component.web.ast.node.BodyChildNode;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import java.util.function.Function;
public class DefaultAppendOrAddStatementFactory implements AppendOrAddStatementFactory {
private void addLineAndColumn(
BodyChildNode bodyChildNode,
TupleExpression args
) {
final Tuple2<ConstantExpression, ConstantExpression> lineAndColumn = TranspilerUtil.lineAndColumn(
bodyChildNode.asNode().getTokenRange().getStartPosition()
);
args.addExpression(lineAndColumn.getV1());
args.addExpression(lineAndColumn.getV2());
}
private Statement doCreate(
BodyChildNode bodyChildNode,
Expression rightSide,
VariableExpression target,
String methodName // ,
// boolean addLineAndColumn
) {
final ArgumentListExpression args;
if (rightSide instanceof ArgumentListExpression argumentListExpression) {
args = argumentListExpression;
} else {
args = new ArgumentListExpression();
args.addExpression(rightSide);
}
// if (addLineAndColumn &&
// NodeUtil.isAnyOfType(bodyChildNode.asNode(), GStringBodyTextNode.class, ComponentNode.class)) {
// this.addLineAndColumn(bodyChildNode, args);
// }
final MethodCallExpression outExpression = new MethodCallExpression(target, methodName, args);
return new ExpressionStatement(outExpression);
}
protected Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
return this.doCreate(
bodyChildNode,
rightSide,
state.getCurrentChildList(),
TranspilerUtil.ADD //,
// false
);
}
protected Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
return this.doCreate(
bodyChildNode,
rightSide,
state.getWriter(),
TranspilerUtil.APPEND //,
// false
);
}
@Override
public Statement addOrAppend(
BodyChildNode bodyChildNode,
TranspilerState state,
Function<Action, Expression> getRightSide
) {
if (state.hasCurrentChildList()) {
return this.addOnly(bodyChildNode, state, getRightSide.apply(Action.ADD));
} else {
return this.appendOnly(bodyChildNode, state, getRightSide.apply(Action.APPEND));
}
}
}

View File

@ -33,16 +33,16 @@ public class DefaultBodyTextTranspiler implements BodyTextTranspiler {
this.includeComments = includeComments; this.includeComments = includeComments;
} }
protected Statement handleStringLiteral(Token source) { protected Statement handleStringLiteral(TranspilerState state, Token source) {
final ConstantExpression literal = getStringLiteral(source.getText()); final ConstantExpression literal = getStringLiteral(source.getText());
this.positionSetter.setPosition(literal, source); this.positionSetter.setPosition(literal, source);
return this.leftShiftFactory.create(literal); return this.leftShiftFactory.create(state, literal);
} }
protected Statement handleStringLiteral(Node source, String content) { protected Statement handleStringLiteral(TranspilerState state, Node source, String content) {
final ConstantExpression literal = getStringLiteral(content); final ConstantExpression literal = getStringLiteral(content);
this.positionSetter.setPosition(literal, source); this.positionSetter.setPosition(literal, source);
return this.leftShiftFactory.create(literal); return this.leftShiftFactory.create(state, literal);
} }
protected List<Statement> handleHtmlCommentChild(HtmlCommentChild child, TranspilerState state) { protected List<Statement> handleHtmlCommentChild(HtmlCommentChild child, TranspilerState state) {
@ -67,23 +67,23 @@ public class DefaultBodyTextTranspiler implements BodyTextTranspiler {
final List<Statement> result = new ArrayList<>(); final List<Statement> result = new ArrayList<>();
switch (child) { switch (child) {
case QuestionNode questionNode -> { case QuestionNode questionNode -> {
result.add(this.handleStringLiteral(questionNode.getOpenToken())); result.add(this.handleStringLiteral(state, questionNode.getOpenToken()));
questionNode.getChildrenAsQuestionTagChildren().stream() questionNode.getChildrenAsQuestionTagChildren().stream()
.map(questionChild -> this.handleQuestionTagChild(questionChild, state)) .map(questionChild -> this.handleQuestionTagChild(questionChild, state))
.forEach(result::addAll); .forEach(result::addAll);
result.add(this.handleStringLiteral(questionNode.getCloseToken())); result.add(this.handleStringLiteral(state, questionNode.getCloseToken()));
} }
case HtmlCommentNode commentNode -> { case HtmlCommentNode commentNode -> {
if (this.includeComments) { if (this.includeComments) {
result.add(this.handleStringLiteral(commentNode.getOpenToken())); result.add(this.handleStringLiteral(state, commentNode.getOpenToken()));
commentNode.getChildrenAsHtmlCommentChildren().stream() commentNode.getChildrenAsHtmlCommentChildren().stream()
.map(commentChild -> this.handleHtmlCommentChild(commentChild, state)) .map(commentChild -> this.handleHtmlCommentChild(commentChild, state))
.forEach(result::addAll); .forEach(result::addAll);
result.add(this.handleStringLiteral(commentNode.getCloseToken())); result.add(this.handleStringLiteral(state, commentNode.getCloseToken()));
} }
} }
case TextNode textNode -> { case TextNode textNode -> {
result.add(this.handleStringLiteral(textNode, textNode.getContent())); result.add(this.handleStringLiteral(state, textNode, textNode.getContent()));
} }
case GroovyBodyNode groovyBodyNode -> { case GroovyBodyNode groovyBodyNode -> {
result.add(this.groovyBodyNodeTranspiler.createGroovyBodyNodeStatements(groovyBodyNode, state)); result.add(this.groovyBodyNodeTranspiler.createGroovyBodyNodeStatements(groovyBodyNode, state));

View File

@ -18,11 +18,7 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
} }
@Override @Override
public BlockStatement transpileBody( public BlockStatement transpileBody(BodyNode bodyNode, TranspilerState state) {
BodyNode bodyNode,
AddOrAppendCallback addOrAppendCallback,
TranspilerState state
) {
final BlockStatement block = new BlockStatement(); final BlockStatement block = new BlockStatement();
block.setVariableScope(state.pushScope()); block.setVariableScope(state.pushScope());
for (final Node child : bodyNode.getChildren()) { for (final Node child : bodyNode.getChildren()) {

View File

@ -1,6 +1,5 @@
package groowt.view.component.web.transpile; package groowt.view.component.web.transpile;
import groowt.util.fp.provider.Provider;
import groowt.view.component.context.ComponentResolveException; import groowt.view.component.context.ComponentResolveException;
import groowt.view.component.runtime.ComponentCreateException; import groowt.view.component.runtime.ComponentCreateException;
import groowt.view.component.web.WebViewComponentBugError; import groowt.view.component.web.WebViewComponentBugError;
@ -29,37 +28,25 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
private static final Pattern isFqn = Pattern.compile("^(\\p{Ll}.+\\.)+\\p{Lu}.+$"); private static final Pattern isFqn = Pattern.compile("^(\\p{Ll}.+\\.)+\\p{Lu}.+$");
private static final Pattern isWithPackage = Pattern.compile("^\\p{Ll}.+\\."); private static final Pattern isWithPackage = Pattern.compile("^\\p{Ll}.+\\.");
private final Provider<AppendOrAddStatementFactory> appendOrAddStatementFactoryProvider; private LeftShiftFactory leftShiftFactory;
private final Provider<ComponentClassNodeResolver> componentClassNodeResolverProvider; private ValueNodeTranspiler valueNodeTranspiler;
private final Provider<ValueNodeTranspiler> valueNodeTranspilerProvider; private BodyTranspiler bodyTranspiler;
private final Provider<BodyTranspiler> bodyTranspilerProvider; private ComponentClassNodeResolver componentClassNodeResolver;
public DefaultComponentTranspiler( public void setLeftShiftFactory(LeftShiftFactory leftShiftFactory) {
Provider<AppendOrAddStatementFactory> appendOrAddStatementFactoryProvider, this.leftShiftFactory = leftShiftFactory;
Provider<ComponentClassNodeResolver> componentClassNodeResolverProvider,
Provider<ValueNodeTranspiler> valueNodeTranspilerProvider,
Provider<BodyTranspiler> bodyTranspilerProvider
) {
this.appendOrAddStatementFactoryProvider = appendOrAddStatementFactoryProvider;
this.componentClassNodeResolverProvider = componentClassNodeResolverProvider;
this.valueNodeTranspilerProvider = valueNodeTranspilerProvider;
this.bodyTranspilerProvider = bodyTranspilerProvider;
} }
protected ValueNodeTranspiler getValueNodeTranspiler() { public void setValueNodeTranspiler(ValueNodeTranspiler valueNodeTranspiler) {
return this.valueNodeTranspilerProvider.get(); this.valueNodeTranspiler = valueNodeTranspiler;
} }
protected BodyTranspiler getBodyTranspiler() { public void setBodyTranspiler(BodyTranspiler bodyTranspiler) {
return this.bodyTranspilerProvider.get(); this.bodyTranspiler = bodyTranspiler;
} }
protected AppendOrAddStatementFactory getAppendOrAddStatementFactory() { public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
return this.appendOrAddStatementFactoryProvider.get(); this.componentClassNodeResolver = componentClassNodeResolver;
}
protected ComponentClassNodeResolver getComponentClassNodeResolver() {
return this.componentClassNodeResolverProvider.get();
} }
/* UTIL */ /* UTIL */
@ -104,7 +91,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
// we need to resolve it // we need to resolve it
final var isWithPackageMatcher = isWithPackage.matcher(identifier); final var isWithPackageMatcher = isWithPackage.matcher(identifier);
if (isWithPackageMatcher.matches()) { if (isWithPackageMatcher.matches()) {
final var resolveResult = this.getComponentClassNodeResolver().getClassForFqn(identifier); final var resolveResult = this.componentClassNodeResolver.getClassForFqn(identifier);
if (resolveResult.isLeft()) { if (resolveResult.isLeft()) {
final var error = resolveResult.getLeft(); final var error = resolveResult.getLeft();
error.setNode(componentNode.getArgs().getType()); error.setNode(componentNode.getArgs().getType());
@ -117,7 +104,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
} }
} else { } else {
final var resolveResult = final var resolveResult =
this.getComponentClassNodeResolver().getClassForNameWithoutPackage(identifier); this.componentClassNodeResolver.getClassForNameWithoutPackage(identifier);
if (resolveResult.isLeft()) { if (resolveResult.isLeft()) {
final var error = resolveResult.getLeft(); final var error = resolveResult.getLeft();
error.setNode(componentNode.getArgs().getType()); error.setNode(componentNode.getArgs().getType());
@ -260,7 +247,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
final Expression valueExpr = switch (attrNode) { final Expression valueExpr = switch (attrNode) {
case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE; case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE;
case KeyValueAttrNode keyValueAttrNode -> case KeyValueAttrNode keyValueAttrNode ->
this.getValueNodeTranspiler().createExpression(keyValueAttrNode.getValueNode(), state); this.valueNodeTranspiler.createExpression(keyValueAttrNode.getValueNode(), state);
}; };
return new MapEntryExpression(keyExpr, valueExpr); return new MapEntryExpression(keyExpr, valueExpr);
} }
@ -328,11 +315,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
scope.putDeclaredVariable(childListParam); scope.putDeclaredVariable(childListParam);
state.pushChildList(childListParam); state.pushChildList(childListParam);
final BlockStatement bodyStatements = this.getBodyTranspiler().transpileBody( final BlockStatement bodyStatements = this.bodyTranspiler.transpileBody(bodyNode, state);
bodyNode,
(sourceNode, expr) -> this.getChildListAdd(childListParam, expr),
state
);
// clean up // clean up
state.popChildList(); state.popChildList();
@ -491,11 +474,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
// Create // Create
final List<Statement> createStatements = this.getTypedCreateStatements(typedComponentNode, state); final List<Statement> createStatements = this.getTypedCreateStatements(typedComponentNode, state);
// Append/Add // Append/Add
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend( final Statement leftShift = this.leftShiftFactory.create(state, state.getCurrentComponent());
componentNode,
state,
(VariableExpression) state.getCurrentComponent()
);
// cleanup // cleanup
state.popResolved(); state.popResolved();
@ -504,17 +483,16 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
final List<Statement> allStatements = new ArrayList<>(); final List<Statement> allStatements = new ArrayList<>();
allStatements.addAll(resolveStatements); allStatements.addAll(resolveStatements);
allStatements.addAll(createStatements); allStatements.addAll(createStatements);
allStatements.add(addOrAppend); allStatements.add(leftShift);
return allStatements; return allStatements;
} else if (componentNode instanceof FragmentComponentNode fragmentComponentNode) { } else if (componentNode instanceof FragmentComponentNode fragmentComponentNode) {
// Create and add all at once // Create and add all at once
final Statement addOrAppend = this.getAppendOrAddStatementFactory().addOrAppend( final Statement leftShift = this.leftShiftFactory.create(
componentNode,
state, state,
this.getFragmentCreateExpression(fragmentComponentNode, state) this.getFragmentCreateExpression(fragmentComponentNode, state)
); );
return List.of(addOrAppend); return List.of(leftShift);
} else { } else {
throw new WebViewComponentBugError(new IllegalArgumentException( throw new WebViewComponentBugError(new IllegalArgumentException(
"Cannot handle a ComponentNode not of type TypedComponentNode or FragmentComponentNode." "Cannot handle a ComponentNode not of type TypedComponentNode or FragmentComponentNode."

View File

@ -1,204 +0,0 @@
package groowt.view.component.web.transpile;
import groowt.util.fp.option.Option;
import groowt.view.component.web.antlr.MergedGroovyCodeToken;
import groowt.view.component.web.antlr.WebViewComponentsLexer;
import groowt.view.component.web.ast.extension.GStringNodeExtension;
import groowt.view.component.web.ast.extension.GStringPathExtension;
import groowt.view.component.web.ast.extension.GStringScriptletExtension;
import groowt.view.component.web.ast.node.GStringBodyTextNode;
import groowt.view.component.web.ast.node.JStringBodyTextNode;
import groowt.view.component.web.ast.node.Node;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.util.FilteringIterable;
import groowt.view.component.web.util.TokenRange;
import org.antlr.v4.runtime.Token;
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 java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.stream.Collectors;
@Deprecated
public class DefaultGStringTranspiler implements GStringTranspiler {
private final PositionSetter positionSetter;
private final JStringTranspiler jStringTranspiler;
public DefaultGStringTranspiler(PositionSetter positionSetter, JStringTranspiler jStringTranspiler) {
this.positionSetter = positionSetter;
this.jStringTranspiler = jStringTranspiler;
}
protected Option<ConstantExpression> checkPrevBeforeDollar(@Nullable Node prev, Node current) {
if (!(prev instanceof JStringBodyTextNode)) {
return Option.liftLazy(() -> {
final ConstantExpression expression = this.jStringTranspiler.createEmptyStringLiteral();
this.positionSetter.setToStartOf(expression, current);
return expression;
});
} else {
return Option.empty();
}
}
protected Option<ConstantExpression> checkNextAfterDollar(@Nullable Node next) {
if (next != null && next.hasExtension(GStringNodeExtension.class)) {
return Option.liftLazy(() -> {
final ConstantExpression expression = this.jStringTranspiler.createEmptyStringLiteral();
this.positionSetter.setToStartOf(expression, next);
return expression;
});
} else {
return Option.empty();
}
}
protected ConstantExpression handleText(JStringBodyTextNode jStringBodyTextNode, @Nullable Node prev) {
if (prev instanceof JStringBodyTextNode) {
throw new IllegalStateException("Cannot have two texts in a row");
}
return this.jStringTranspiler.createStringLiteral(jStringBodyTextNode);
}
protected record PathResult(
Expression result,
Option<ConstantExpression> before,
Option<ConstantExpression> after
) {}
protected PathResult handlePath(Node current, GStringPathExtension path, @Nullable Node prev, @Nullable Node next) {
final List<Token> groowtTokens = path.getRawTokens();
VariableExpression begin = null;
PropertyExpression propertyExpression = null;
for (final Token groowtToken : groowtTokens) {
if (groowtToken instanceof MergedGroovyCodeToken groovyCodeToken) {
final Iterable<Token> identifierTokenIterable = FilteringIterable.continuingUntilSuccess(
groovyCodeToken.getOriginals(),
token -> token.getType() == WebViewComponentsLexer.GStringIdentifier
);
for (final Token identifierToken : identifierTokenIterable) {
final String identifier = identifierToken.getText();
final TokenRange identifierTokenRange = TokenRange.of(identifierToken);
if (begin == null) {
begin = new VariableExpression(identifier);
this.positionSetter.setPosition(begin, identifierTokenRange);
} else if (propertyExpression == null) {
propertyExpression = new PropertyExpression(begin, identifier);
this.positionSetter.setPosition(propertyExpression, identifierTokenRange);
} else {
propertyExpression = new PropertyExpression(propertyExpression, identifier);
this.positionSetter.setPosition(propertyExpression, identifierTokenRange);
}
}
} else {
throw new IllegalStateException("Received a non-MergedGroovyToken from a GStringExtension");
}
}
if (begin == null) {
throw new IllegalStateException("begin is null!");
}
if (propertyExpression != null) {
return new PathResult(
propertyExpression,
this.checkPrevBeforeDollar(prev, current),
this.checkNextAfterDollar(next)
);
} else {
return new PathResult(
begin,
this.checkPrevBeforeDollar(prev, current),
this.checkNextAfterDollar(next)
);
}
}
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
public GStringExpression createGStringExpression(GStringBodyTextNode gStringBodyTextNode) {
final var children = gStringBodyTextNode.getChildren();
if (children.isEmpty()) {
throw new IllegalArgumentException("Cannot make a gStringOutStatement from zero GStringParts");
}
final String verbatimText = children.stream().map(node -> {
if (node instanceof JStringBodyTextNode jStringBodyTextNode) {
return jStringBodyTextNode.getContent();
} else if (node.hasExtension(GStringNodeExtension.class)) {
final var gString = node.getExtension(GStringNodeExtension.class);
return switch (gString) {
case GStringPathExtension ignored -> gString.getAsValidEmbeddableCode();
case GStringScriptletExtension ignored -> "${" + gString.getAsValidEmbeddableCode() + "}";
};
} else {
throw new IllegalArgumentException(
"Cannot get verbatim text when one of the given parts has "
+ "neither a JStringNodeExtension nor a GStringNodeExtension"
);
}
}).collect(Collectors.joining());
final List<ConstantExpression> texts = new ArrayList<>();
final List<Expression> values = new ArrayList<>();
final ListIterator<Node> iter = children.listIterator();
while (iter.hasNext()) {
final var prev = iter.previousIndex() > -1 ? children.get(iter.previousIndex()) : null;
final var current = iter.next();
final var next = iter.nextIndex() < children.size() ? children.get(iter.nextIndex()) : null;
if (current instanceof JStringBodyTextNode jStringBodyTextNode) {
texts.add(this.handleText(jStringBodyTextNode, prev));
} else {
switch (current.getExtension(GStringNodeExtension.class)) {
case GStringPathExtension path -> {
final var pathResult = this.handlePath(current, path, prev, next);
pathResult.before().ifPresent(texts::add);
values.add(pathResult.result());
pathResult.after().ifPresent(texts::add);
}
case GStringScriptletExtension scriptlet -> {
checkPrevBeforeDollar(prev, current).ifPresent(texts::add);
values.add(this.handleScriptlet(scriptlet));
checkNextAfterDollar(next).ifPresent(texts::add);
}
}
}
}
if (!(texts.size() == values.size() || texts.size() == values.size() + 1)) {
throw new IllegalStateException(
"incorrect amount of texts vs. values: " + texts.size() + " " + values.size()
);
}
final var gString = new GStringExpression(verbatimText, texts, values);
this.positionSetter.setPosition(gString, gStringBodyTextNode);
return gString;
}
}

View File

@ -3,7 +3,6 @@ package groowt.view.component.web.transpile;
import groowt.view.component.web.WebViewComponentBugError; import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*; import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.transpile.groovy.GroovyUtil; import groowt.view.component.web.transpile.groovy.GroovyUtil;
import jakarta.inject.Inject;
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.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement;
@ -16,7 +15,6 @@ public class DefaultGroovyBodyNodeTranspiler implements GroovyBodyNodeTranspiler
private final PositionSetter positionSetter; private final PositionSetter positionSetter;
private final LeftShiftFactory leftShiftFactory; private final LeftShiftFactory leftShiftFactory;
@Inject
public DefaultGroovyBodyNodeTranspiler(PositionSetter positionSetter, LeftShiftFactory leftShiftFactory) { public DefaultGroovyBodyNodeTranspiler(PositionSetter positionSetter, LeftShiftFactory leftShiftFactory) {
this.positionSetter = positionSetter; this.positionSetter = positionSetter;
this.leftShiftFactory = leftShiftFactory; this.leftShiftFactory = leftShiftFactory;
@ -58,7 +56,7 @@ public class DefaultGroovyBodyNodeTranspiler implements GroovyBodyNodeTranspiler
); );
callExpr = new MethodCallExpression(cl, "call", argsList); callExpr = new MethodCallExpression(cl, "call", argsList);
} }
return this.leftShiftFactory.create(callExpr); return this.leftShiftFactory.create(state, callExpr);
} }
protected Statement handlePlainScriptlet(PlainScriptletNode plainScriptletNode, TranspilerState state) { protected Statement handlePlainScriptlet(PlainScriptletNode plainScriptletNode, TranspilerState state) {
@ -76,7 +74,7 @@ public class DefaultGroovyBodyNodeTranspiler implements GroovyBodyNodeTranspiler
return new ExpressionStatement(callExpr); return new ExpressionStatement(callExpr);
} }
protected Statement handleDollarScriptlet(DollarScriptletNode dollarScriptletNode) { protected Statement handleDollarScriptlet(DollarScriptletNode dollarScriptletNode, TranspilerState state) {
final ClosureExpression cl = this.convertToClosure(dollarScriptletNode, dollarScriptletNode.getGroovyCode()); final ClosureExpression cl = this.convertToClosure(dollarScriptletNode, dollarScriptletNode.getGroovyCode());
final Expression toLeftShift; final Expression toLeftShift;
if (cl.getParameters() == null) { if (cl.getParameters() == null) {
@ -89,10 +87,10 @@ public class DefaultGroovyBodyNodeTranspiler implements GroovyBodyNodeTranspiler
toLeftShift = cl; toLeftShift = cl;
} }
} }
return this.leftShiftFactory.create(toLeftShift); return this.leftShiftFactory.create(state, toLeftShift);
} }
protected Statement handleDollarReference(DollarReferenceNode dollarReferenceNode) { protected Statement handleDollarReference(DollarReferenceNode dollarReferenceNode, TranspilerState state) {
VariableExpression root = null; VariableExpression root = null;
PropertyExpression propertyExpr = null; PropertyExpression propertyExpr = null;
for (final String part : dollarReferenceNode.getParts()) { for (final String part : dollarReferenceNode.getParts()) {
@ -107,10 +105,10 @@ public class DefaultGroovyBodyNodeTranspiler implements GroovyBodyNodeTranspiler
final var positionVisitor = new PositionVisitor(this.positionSetter, dollarReferenceNode); final var positionVisitor = new PositionVisitor(this.positionSetter, dollarReferenceNode);
if (propertyExpr != null) { if (propertyExpr != null) {
propertyExpr.visit(positionVisitor); propertyExpr.visit(positionVisitor);
return this.leftShiftFactory.create(propertyExpr); return this.leftShiftFactory.create(state, propertyExpr);
} else if (root != null) { } else if (root != null) {
root.visit(positionVisitor); root.visit(positionVisitor);
return this.leftShiftFactory.create(root); return this.leftShiftFactory.create(state, root);
} else { } else {
throw new WebViewComponentBugError("Did not expect root to be null."); throw new WebViewComponentBugError("Did not expect root to be null.");
} }
@ -121,8 +119,8 @@ public class DefaultGroovyBodyNodeTranspiler implements GroovyBodyNodeTranspiler
return switch (groovyBodyNode) { return switch (groovyBodyNode) {
case EqualsScriptletNode equalsScriptletNode -> this.handleEqualsScriptlet(equalsScriptletNode, state); case EqualsScriptletNode equalsScriptletNode -> this.handleEqualsScriptlet(equalsScriptletNode, state);
case PlainScriptletNode plainScriptletNode -> this.handlePlainScriptlet(plainScriptletNode, state); case PlainScriptletNode plainScriptletNode -> this.handlePlainScriptlet(plainScriptletNode, state);
case DollarScriptletNode dollarScriptletNode -> this.handleDollarScriptlet(dollarScriptletNode); case DollarScriptletNode dollarScriptletNode -> this.handleDollarScriptlet(dollarScriptletNode, state);
case DollarReferenceNode dollarReferenceNode -> this.handleDollarReference(dollarReferenceNode); case DollarReferenceNode dollarReferenceNode -> this.handleDollarReference(dollarReferenceNode, state);
default -> throw new WebViewComponentBugError(new UnsupportedOperationException( default -> throw new WebViewComponentBugError(new UnsupportedOperationException(
"GroovyBodyNode of type " + groovyBodyNode.getClass().getName() + " is not supported." "GroovyBodyNode of type " + groovyBodyNode.getClass().getName() + " is not supported."
)); ));

View File

@ -4,14 +4,12 @@ import groovy.transform.Field;
import groowt.view.component.compiler.ComponentTemplateCompileException; import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.ComponentTemplateCompileUnit; import groowt.view.component.compiler.ComponentTemplateCompileUnit;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration; import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.BodyNode; import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.ast.node.CompilationUnitNode; import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.ast.node.PreambleNode; import groowt.view.component.web.ast.node.PreambleNode;
import groowt.view.component.web.compiler.MultipleWebViewComponentCompileErrorsException; import groowt.view.component.web.compiler.MultipleWebViewComponentCompileErrorsException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException; import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit; import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.BodyTranspiler.AddOrAppendCallback;
import groowt.view.component.web.transpile.groovy.GroovyUtil; import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.resolve.ClassLoaderComponentClassNodeResolver; import groowt.view.component.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.*;
@ -37,10 +35,9 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class); private static final ClassNode FIELD_ANNOTATION = ClassHelper.make(Field.class);
protected TranspilerConfiguration getConfiguration( protected TranspilerConfiguration getConfiguration(
WebViewComponentTemplateCompileUnit compileUnit, ClassLoaderComponentClassNodeResolver classLoaderComponentClassNodeResolver
ClassLoader classLoader
) { ) {
return new DefaultTranspilerConfiguration(new ClassLoaderComponentClassNodeResolver(compileUnit, classLoader)); return SimpleTranspilerConfiguration.withDefaults(classLoaderComponentClassNodeResolver);
} }
protected void addAutomaticImports(WebViewComponentModuleNode moduleNode, TranspilerConfiguration configuration) { protected void addAutomaticImports(WebViewComponentModuleNode moduleNode, TranspilerConfiguration configuration) {
@ -235,20 +232,7 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
TranspilerConfiguration transpilerConfiguration, TranspilerConfiguration transpilerConfiguration,
TranspilerState state TranspilerState state
) { ) {
final var appendOrAddStatementFactory = transpilerConfiguration.getAppendOrAddStatementFactory(); return transpilerConfiguration.getBodyTranspiler().transpileBody(bodyNode, state);
final AddOrAppendCallback callback = (source, expr) -> appendOrAddStatementFactory.addOrAppend(
source,
state,
action -> {
if (action == AppendOrAddStatementFactory.Action.ADD) {
throw new WebViewComponentBugError(new IllegalStateException(
"Should not be adding from document root, only appending!"
));
}
return expr;
}
);
return transpilerConfiguration.getBodyTranspiler().transpileBody(bodyNode, callback, state);
} }
@Override @Override
@ -258,10 +242,12 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
CompilationUnitNode compilationUnitNode, CompilationUnitNode compilationUnitNode,
String templateClassSimpleName String templateClassSimpleName
) throws ComponentTemplateCompileException { ) throws ComponentTemplateCompileException {
// transpilerConfiguration and positionSetter // resolver, transpilerConfiguration, and positionSetter
final var transpilerConfiguration = this.getConfiguration( final ClassLoaderComponentClassNodeResolver resolver = new ClassLoaderComponentClassNodeResolver(
compileUnit, compileUnit.getGroovyCompilationUnit().getClassLoader() compileUnit,
compileUnit.getGroovyCompilationUnit().getClassLoader()
); );
final var transpilerConfiguration = this.getConfiguration(resolver);
final PositionSetter positionSetter = transpilerConfiguration.getPositionSetter(); final PositionSetter positionSetter = transpilerConfiguration.getPositionSetter();
// prepare sourceUnit // prepare sourceUnit
@ -280,6 +266,9 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
compileUnit, sourceUnit, transpilerConfiguration compileUnit, sourceUnit, transpilerConfiguration
); );
// set resolver's moduleNode
resolver.setModuleNode(moduleNode);
// prepare mainClassNode // prepare mainClassNode
final ClassNode mainClassNode = this.initMainClassNode(compileUnit, templateClassSimpleName, moduleNode); final ClassNode mainClassNode = this.initMainClassNode(compileUnit, templateClassSimpleName, moduleNode);

View File

@ -1,47 +0,0 @@
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 org.codehaus.groovy.ast.expr.ConstantExpression;
import static org.apache.groovy.parser.antlr4.util.StringUtils.*;
@Deprecated
public class DefaultJStringTranspiler implements JStringTranspiler {
private final PositionSetter positionSetter;
@Inject
public DefaultJStringTranspiler(PositionSetter positionSetter) {
this.positionSetter = positionSetter;
}
@Override
public ConstantExpression createStringLiteral(JStringBodyTextNode jStringBodyTextNode) {
final var withoutCR = removeCR(jStringBodyTextNode.getContent());
final var escaped = replaceEscapes(withoutCR, NONE_SLASHY);
final var expression = new ConstantExpression(escaped);
expression.setNodeMetaData("_IS_STRING", true);
this.positionSetter.setPosition(expression, jStringBodyTextNode);
return expression;
}
@Override
public ConstantExpression createStringLiteral(JStringValueNode jStringValueNode) {
final var content = jStringValueNode.getContent();
final var escaped = replaceEscapes(content, NONE_SLASHY);
final var expression = new ConstantExpression(escaped);
expression.setNodeMetaData("_IS_STRING", true);
this.positionSetter.setPosition(expression, jStringValueNode);
return expression;
}
@Override
public ConstantExpression createEmptyStringLiteral() {
final var expression = new ConstantExpression("");
expression.setNodeMetaData("_IS_STRING", true);
return expression;
}
}

View File

@ -0,0 +1,28 @@
package groowt.view.component.web.transpile;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
public class DefaultLeftShiftFactory implements LeftShiftFactory {
@Override
public Statement create(TranspilerState state, Expression rightSide) {
final Expression left;
if (state.hasCurrentChildList()) {
left = state.getCurrentChildList();
} else {
left = state.getWriter();
}
final BinaryExpression leftShift = new BinaryExpression(
left,
new Token(Types.LEFT_SHIFT, "<<", -1, -1),
rightSide
);
return new ExpressionStatement(leftShift);
}
}

View File

@ -1,85 +0,0 @@
package groowt.view.component.web.transpile;
import groovy.lang.Tuple3;
import groowt.util.fp.provider.DefaultProvider;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import org.codehaus.groovy.ast.ClassNode;
import java.util.Map;
import java.util.Set;
import static groowt.view.component.web.transpile.TranspilerUtil.*;
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
private final PositionSetter positionSetter;
private final AppendOrAddStatementFactory appendOrAddStatementFactory = new DefaultAppendOrAddStatementFactory();
private final BodyTranspiler bodyTranspiler;
private final ValueNodeTranspiler valueNodeTranspiler;
public DefaultTranspilerConfiguration(ComponentClassNodeResolver classNodeResolver) {
this.positionSetter = new SimplePositionSetter();
final var jStringTranspiler = new DefaultJStringTranspiler(this.positionSetter);
final var gStringTranspiler = new DefaultGStringTranspiler(this.positionSetter, jStringTranspiler);
final var componentTranspiler = new DefaultComponentTranspiler(
DefaultProvider.of(this.appendOrAddStatementFactory),
DefaultProvider.of(classNodeResolver),
DefaultProvider.ofLazy(ValueNodeTranspiler.class, this::getValueNodeTranspiler),
DefaultProvider.ofLazy(BodyTranspiler.class, this::getBodyTranspiler)
);
this.valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
this.bodyTranspiler = new DefaultBodyTranspiler(componentTranspiler, null); // TODO
}
@Override
public PositionSetter getPositionSetter() {
return this.positionSetter;
}
@Override
public BodyTranspiler getBodyTranspiler() {
return this.bodyTranspiler;
}
@Override
public AppendOrAddStatementFactory getAppendOrAddStatementFactory() {
return this.appendOrAddStatementFactory;
}
protected ValueNodeTranspiler getValueNodeTranspiler() {
return this.valueNodeTranspiler;
}
@Override
public Map<String, ClassNode> getImports() {
return Map.of(
COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE,
COMPONENT_CONTEXT_TYPE.getNameWithoutPackage(), COMPONENT_CONTEXT_TYPE
);
}
@Override
public Set<String> getStarImports() {
return Set.of(
GROOWT_VIEW_COMPONENT_WEB + ".lib",
"groowt.view.component.runtime",
GROOWT_VIEW_COMPONENT_WEB + ".runtime"
);
}
@Override
public Set<Tuple3<ClassNode, String, String>> getStaticImports() {
return Set.of();
}
@Override
public Map<String, ClassNode> getStaticStarImports() {
return Map.of();
}
@Override
public ClassNode getRenderContextImplementation() {
return DEFAULT_RENDER_CONTEXT_IMPLEMENTATION;
}
}

View File

@ -1,6 +1,5 @@
package groowt.view.component.web.transpile; package groowt.view.component.web.transpile;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*; import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.transpile.groovy.GroovyUtil; import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.groovy.GroovyUtil.ConvertResult; import groowt.view.component.web.transpile.groovy.GroovyUtil.ConvertResult;
@ -12,33 +11,30 @@ import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.stmt.Statement;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List;
import static groowt.view.component.web.transpile.TranspilerUtil.getStringLiteral; import static groowt.view.component.web.transpile.TranspilerUtil.getStringLiteral;
// TODO: set positions
public class DefaultValueNodeTranspiler implements ValueNodeTranspiler { public class DefaultValueNodeTranspiler implements ValueNodeTranspiler {
private final ComponentTranspiler componentTranspiler; private final ComponentTranspiler componentTranspiler;
private final PositionSetter positionSetter;
public DefaultValueNodeTranspiler(ComponentTranspiler componentTranspiler) { public DefaultValueNodeTranspiler(ComponentTranspiler componentTranspiler, PositionSetter positionSetter) {
this.componentTranspiler = componentTranspiler; this.componentTranspiler = componentTranspiler;
this.positionSetter = positionSetter;
} }
// TODO: positions
protected Expression handleClosureNode(ClosureValueNode closureValueNode) { protected Expression handleClosureNode(ClosureValueNode closureValueNode) {
final var rawCode = closureValueNode.getGroovyCode().getAsValidGroovyCode(); final var rawCode = closureValueNode.getGroovyCode().getAsValidGroovyCode();
final ClosureExpression convertedClosure = GroovyUtil.getClosure(rawCode); final ClosureExpression convertedClosure = GroovyUtil.getClosure(rawCode);
final PositionVisitor positionVisitor = new PositionVisitor(
this.positionSetter.withOffset(0, -10),
closureValueNode
);
convertedClosure.visit(positionVisitor);
final Statement closureCode = convertedClosure.getCode(); final Statement closureCode = convertedClosure.getCode();
if (closureCode instanceof BlockStatement blockStatement) { if (closureCode instanceof ExpressionStatement expressionStatement) {
final List<Statement> statements = blockStatement.getStatements();
if (statements.isEmpty()) {
throw new WebViewComponentBugError(new IllegalArgumentException(
"Did not expect ClosureValueNode to produce no statements."
));
} else if (statements.size() == 1) {
final Statement statement = statements.getFirst();
if (statement instanceof ExpressionStatement expressionStatement) {
final Expression expression = expressionStatement.getExpression(); final Expression expression = expressionStatement.getExpression();
return switch (expression) { return switch (expression) {
case ConstantExpression ignored -> expression; case ConstantExpression ignored -> expression;
@ -46,16 +42,9 @@ public class DefaultValueNodeTranspiler implements ValueNodeTranspiler {
case PropertyExpression ignored -> expression; case PropertyExpression ignored -> expression;
default -> convertedClosure; default -> convertedClosure;
}; };
} else {
throw new IllegalArgumentException("A component closure value must produce a value.");
} }
} else {
return convertedClosure; return convertedClosure;
} }
} else {
return convertedClosure;
}
}
private Expression gStringValue(GStringValueNode gStringValueNode) { private Expression gStringValue(GStringValueNode gStringValueNode) {
final var rawCode = gStringValueNode.getGroovyCode().getAsValidGroovyCode(); final var rawCode = gStringValueNode.getGroovyCode().getAsValidGroovyCode();
@ -65,16 +54,27 @@ public class DefaultValueNodeTranspiler implements ValueNodeTranspiler {
throw new IllegalStateException("block statement is null or empty"); throw new IllegalStateException("block statement is null or empty");
} }
final ExpressionStatement exprStmt = (ExpressionStatement) blockStatement.getStatements().getFirst(); final ExpressionStatement exprStmt = (ExpressionStatement) blockStatement.getStatements().getFirst();
// TODO: set pos
final PositionVisitor positionVisitor = new PositionVisitor(
this.positionSetter.withOffset(0, -1),
gStringValueNode
);
exprStmt.visit(positionVisitor);
return exprStmt.getExpression(); return exprStmt.getExpression();
} }
private ConstantExpression jStringValue(JStringValueNode jStringValueNode) { private ConstantExpression jStringValue(JStringValueNode jStringValueNode) {
return getStringLiteral(jStringValueNode.getContent()); // TODO: set pos final ConstantExpression literal = getStringLiteral(jStringValueNode.getContent());
this.positionSetter.setPosition(literal, jStringValueNode);
return literal;
} }
private ClosureExpression emptyClosureValue(EmptyClosureValueNode emptyClosureValueNode) { private ClosureExpression emptyClosureValue(EmptyClosureValueNode emptyClosureValueNode) {
return new ClosureExpression(Parameter.EMPTY_ARRAY, EmptyStatement.INSTANCE); // TODO: set pos final ClosureExpression cl = new ClosureExpression(Parameter.EMPTY_ARRAY, EmptyStatement.INSTANCE);
final PositionVisitor positionVisitor = new PositionVisitor(this.positionSetter, emptyClosureValueNode);
cl.visit(positionVisitor);
return cl;
} }
private ClosureExpression componentValue(ComponentValueNode componentValueNode, TranspilerState state) { private ClosureExpression componentValue(ComponentValueNode componentValueNode, TranspilerState state) {
@ -84,7 +84,7 @@ public class DefaultValueNodeTranspiler implements ValueNodeTranspiler {
componentValueNode.getComponentNode(), componentValueNode.getComponentNode(),
state state
), state.getCurrentScope()) ), state.getCurrentScope())
); // TODO: set pos );
} }
@Override @Override

View File

@ -1,9 +0,0 @@
package groowt.view.component.web.transpile;
import groowt.view.component.web.ast.node.GStringBodyTextNode;
import org.codehaus.groovy.ast.expr.GStringExpression;
@Deprecated
public interface GStringTranspiler {
GStringExpression createGStringExpression(GStringBodyTextNode parent);
}

View File

@ -1,12 +0,0 @@
package groowt.view.component.web.transpile;
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);
ConstantExpression createEmptyStringLiteral();
}

View File

@ -4,5 +4,5 @@ import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.stmt.Statement;
public interface LeftShiftFactory { public interface LeftShiftFactory {
Statement create(Expression rightSide); Statement create(TranspilerState state, Expression rightSide);
} }

View File

@ -0,0 +1,150 @@
package groowt.view.component.web.transpile;
import groovy.lang.Tuple3;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import org.codehaus.groovy.ast.ClassNode;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static groowt.view.component.web.transpile.TranspilerUtil.*;
public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
public static TranspilerConfiguration withDefaults(ComponentClassNodeResolver componentClassNodeResolver) {
final var c = new SimpleTranspilerConfiguration();
c.setComponentClassNodeResolver(componentClassNodeResolver);
final var ct = new DefaultComponentTranspiler();
final PositionSetter ps = new SimplePositionSetter();
final LeftShiftFactory lsf = new DefaultLeftShiftFactory();
final var gbnt = new DefaultGroovyBodyNodeTranspiler(ps, lsf);
final var btt = new DefaultBodyTextTranspiler(gbnt, ps, lsf, true);
final var bt = new DefaultBodyTranspiler(ct, btt);
final var vnt = new DefaultValueNodeTranspiler(ct, ps);
ct.setLeftShiftFactory(lsf);
ct.setBodyTranspiler(bt);
ct.setValueNodeTranspiler(vnt);
ct.setComponentClassNodeResolver(componentClassNodeResolver);
c.setComponentTranspiler(ct);
c.setPositionSetter(ps);
c.setLeftShiftFactory(lsf);
c.setGroovyBodyNodeTranspiler(gbnt);
c.setBodyTextTranspiler(btt);
c.setBodyTranspiler(bt);
c.setValueNodeTranspiler(vnt);
return c;
}
private ComponentClassNodeResolver componentClassNodeResolver;
private ComponentTranspiler componentTranspiler;
private PositionSetter positionSetter;
private LeftShiftFactory leftShiftFactory;
private GroovyBodyNodeTranspiler groovyBodyNodeTranspiler;
private BodyTextTranspiler bodyTextTranspiler;
private BodyTranspiler bodyTranspiler;
private ValueNodeTranspiler valueNodeTranspiler;
public ComponentClassNodeResolver getComponentClassNodeResolver() {
return Objects.requireNonNull(this.componentClassNodeResolver);
}
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
this.componentClassNodeResolver = componentClassNodeResolver;
}
public ComponentTranspiler getComponentTranspiler() {
return Objects.requireNonNull(this.componentTranspiler);
}
public void setComponentTranspiler(ComponentTranspiler componentTranspiler) {
this.componentTranspiler = componentTranspiler;
}
@Override
public PositionSetter getPositionSetter() {
return Objects.requireNonNull(this.positionSetter);
}
public void setPositionSetter(PositionSetter positionSetter) {
this.positionSetter = positionSetter;
}
public LeftShiftFactory getLeftShiftFactory() {
return this.leftShiftFactory;
}
public void setLeftShiftFactory(LeftShiftFactory leftShiftFactory) {
this.leftShiftFactory = leftShiftFactory;
}
public GroovyBodyNodeTranspiler getGroovyBodyNodeTranspiler() {
return this.groovyBodyNodeTranspiler;
}
public void setGroovyBodyNodeTranspiler(GroovyBodyNodeTranspiler groovyBodyNodeTranspiler) {
this.groovyBodyNodeTranspiler = groovyBodyNodeTranspiler;
}
public BodyTextTranspiler getBodyTextTranspiler() {
return this.bodyTextTranspiler;
}
public void setBodyTextTranspiler(BodyTextTranspiler bodyTextTranspiler) {
this.bodyTextTranspiler = bodyTextTranspiler;
}
@Override
public BodyTranspiler getBodyTranspiler() {
return Objects.requireNonNull(this.bodyTranspiler);
}
public void setBodyTranspiler(BodyTranspiler bodyTranspiler) {
this.bodyTranspiler = bodyTranspiler;
}
public ValueNodeTranspiler getValueNodeTranspiler() {
return Objects.requireNonNull(this.valueNodeTranspiler);
}
public void setValueNodeTranspiler(ValueNodeTranspiler valueNodeTranspiler) {
this.valueNodeTranspiler = valueNodeTranspiler;
}
@Override
public Map<String, ClassNode> getImports() {
return Map.of(
COMPONENT_TEMPLATE.getNameWithoutPackage(), COMPONENT_TEMPLATE,
COMPONENT_CONTEXT_TYPE.getNameWithoutPackage(), COMPONENT_CONTEXT_TYPE
);
}
@Override
public Set<String> getStarImports() {
return Set.of(
GROOWT_VIEW_COMPONENT_WEB + ".lib",
"groowt.view.component.runtime",
GROOWT_VIEW_COMPONENT_WEB + ".runtime"
);
}
@Override
public Set<Tuple3<ClassNode, String, String>> getStaticImports() {
return Set.of();
}
@Override
public Map<String, ClassNode> getStaticStarImports() {
return Map.of();
}
@Override
public ClassNode getRenderContextImplementation() {
return DEFAULT_RENDER_CONTEXT_IMPLEMENTATION;
}
}

View File

@ -9,7 +9,6 @@ import java.util.Set;
public interface TranspilerConfiguration { public interface TranspilerConfiguration {
PositionSetter getPositionSetter(); PositionSetter getPositionSetter();
BodyTranspiler getBodyTranspiler(); BodyTranspiler getBodyTranspiler();
AppendOrAddStatementFactory getAppendOrAddStatementFactory();
Map<String, ClassNode> getImports(); Map<String, ClassNode> getImports();
Set<String> getStarImports(); Set<String> getStarImports();
Set<Tuple3<ClassNode, String, String>> getStaticImports(); Set<Tuple3<ClassNode, String, String>> getStaticImports();

View File

@ -6,13 +6,12 @@ import groowt.view.component.web.antlr.TokenList
import groowt.view.component.web.antlr.WebViewComponentsLexer import groowt.view.component.web.antlr.WebViewComponentsLexer
import groowt.view.component.web.antlr.WebViewComponentsParser import groowt.view.component.web.antlr.WebViewComponentsParser
import groowt.view.component.web.antlr.WebViewComponentsTokenStream import groowt.view.component.web.antlr.WebViewComponentsTokenStream
import groowt.view.component.web.ast.node.* import groowt.view.component.web.ast.node.Node
import org.antlr.v4.runtime.CharStreams import org.antlr.v4.runtime.CharStreams
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import static groowt.view.component.web.antlr.WebViewComponentsParser.CompilationUnitContext import static groowt.view.component.web.antlr.WebViewComponentsParser.CompilationUnitContext
import static org.junit.jupiter.api.Assertions.* import static org.junit.jupiter.api.Assertions.assertInstanceOf
import static org.junit.jupiter.api.Assertions.assertNotNull
class DefaultAstBuilderVisitorTests { class DefaultAstBuilderVisitorTests {
@ -52,30 +51,4 @@ class DefaultAstBuilderVisitorTests {
return new Tuple2<>(cu.accept(visitor), tokenList) return new Tuple2<>(cu.accept(visitor), tokenList)
} }
@Test
@Disabled('Move to file tests.')
void helloTarget() {
def (node, tokenList) = this.doBuild('Hello, $target!')
assertNodeWith(CompilationUnitNode, node) {
assertNull(preambleNode)
bodyNode.with {
assertEquals(1, childrenSize)
assertNodeWith(GStringBodyTextNode, children.first) {
assertEquals(3, childrenSize)
assertNodeWith(JStringBodyTextNode, it[0]) {
assertEquals('Hello, ', it.getText(tokenList))
assertEquals('Hello, ', it.content)
}
assertNodeWith(DollarReferenceNode, it[1]) {
assertEquals('$target', it.getText(tokenList))
assertEquals('$target', it.GStringPath.asValidEmbeddableCode)
}
assertNodeWith(JStringBodyTextNode, it[2]) {
assertEquals('!', it.content)
}
}
}
}
}
} }

View File

@ -1,7 +1,7 @@
package groowt.view.component.web.transpiler; package groowt.view.component.web.transpiler;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit; import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.DefaultTranspilerConfiguration; import groowt.view.component.web.transpile.SimpleTranspilerConfiguration;
import groowt.view.component.web.transpile.TranspilerConfiguration; import groowt.view.component.web.transpile.TranspilerConfiguration;
import groowt.view.component.web.transpile.resolve.CachingComponentClassNodeResolver; import groowt.view.component.web.transpile.resolve.CachingComponentClassNodeResolver;
import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.ModuleNode;
@ -13,9 +13,7 @@ public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
WebViewComponentTemplateCompileUnit compileUnit, WebViewComponentTemplateCompileUnit compileUnit,
ModuleNode moduleNode ModuleNode moduleNode
) { ) {
return new DefaultTranspilerConfiguration( return SimpleTranspilerConfiguration.withDefaults(new CachingComponentClassNodeResolver(compileUnit));
new CachingComponentClassNodeResolver(compileUnit)
);
} }
} }

View File

@ -1,16 +0,0 @@
package groowt.view.component.web.transpiler;
import groowt.view.component.web.transpile.DefaultGStringTranspiler;
import groowt.view.component.web.transpile.DefaultJStringTranspiler;
import groowt.view.component.web.transpile.GStringTranspiler;
import groowt.view.component.web.transpile.SimplePositionSetter;
public class DefaultGStringTranspilerTests extends GStringTranspilerTests {
@Override
protected GStringTranspiler getGStringTranspiler() {
final var positionSetter = new SimplePositionSetter();
return new DefaultGStringTranspiler(positionSetter, new DefaultJStringTranspiler(positionSetter));
}
}

View File

@ -223,7 +223,7 @@ public abstract class NodeFactoryTests {
@Test @Test
public void plainScriptletNode() { public void plainScriptletNode() {
assertNotNull(this.nodeFactory.plainScriptletNode(this.getTokenRange(), 0)); assertNotNull(this.nodeFactory.plainScriptletNode(this.getTokenRange(), ""));
} }
@Test @Test
@ -233,7 +233,7 @@ public abstract class NodeFactoryTests {
@Test @Test
public void dollarReferenceNode() { public void dollarReferenceNode() {
assertNotNull(this.nodeFactory.dollarReferenceNode(this.getTokenRange(), 0)); assertNotNull(this.nodeFactory.dollarReferenceNode(this.getTokenRange(), List.of("test")));
} }
} }

View File

@ -8,13 +8,7 @@ import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit; import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.BodyTranspiler; import groowt.view.component.web.transpile.BodyTranspiler;
import groowt.view.component.web.transpile.TranspilerConfiguration; import groowt.view.component.web.transpile.TranspilerConfiguration;
import groowt.view.component.web.transpile.TranspilerState;
import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock; import org.mockito.Mock;
@ -30,9 +24,7 @@ public abstract class BodyTranspilerTests {
ModuleNode moduleNode ModuleNode moduleNode
); );
protected record BuildResult(BodyNode bodyNode, TokenList tokenList) { protected record BuildResult(BodyNode bodyNode, TokenList tokenList) {}
}
protected BuildResult build(String source) { protected BuildResult build(String source) {
final var parseResult = ParserUtil.parseCompilationUnit(source); final var parseResult = ParserUtil.parseCompilationUnit(source);
@ -58,47 +50,4 @@ public abstract class BodyTranspilerTests {
}); });
} }
@Test
public void simpleGStringOutStatement(
@Mock WebViewComponentTemplateCompileUnit compileUnit,
@Mock ModuleNode moduleNode
) {
final var source = "Hello, $target!";
final var buildResult = this.build(source);
final var configuration = this.getConfiguration(compileUnit, moduleNode);
final var transpiler = this.getBodyTranspiler(configuration);
final var state = TranspilerState.withDefaultRootScope();
final var addOrAppend = configuration.getAppendOrAddStatementFactory();
final BlockStatement blockStatement = transpiler.transpileBody(
buildResult.bodyNode(),
(node, expression) -> addOrAppend.addOrAppend(node, state, ignored -> expression),
TranspilerState.withDefaultRootScope()
);
assertEquals(1, blockStatement.getStatements().size());
}
@Test
public void simpleJStringOutStatement(
@Mock WebViewComponentTemplateCompileUnit compileUnit,
@Mock ModuleNode moduleNode
) {
final var source = "Hello, World!";
final var buildResult = this.build(source);
final var configuration = this.getConfiguration(compileUnit, moduleNode);
final var transpiler = this.getBodyTranspiler(configuration);
final var state = TranspilerState.withDefaultRootScope();
final var addOrAppend = configuration.getAppendOrAddStatementFactory();
final BlockStatement blockStatement = transpiler.transpileBody(
buildResult.bodyNode(),
(node, expression) -> addOrAppend.addOrAppend(node, state, ignored -> expression),
TranspilerState.withDefaultRootScope()
);
assertEquals(1, blockStatement.getStatements().size());
final var s0 = (ExpressionStatement) blockStatement.getStatements().getFirst();
final var binaryExpression = (MethodCallExpression) s0.getExpression();
final var args = (TupleExpression) binaryExpression.getArguments();
final var first = (ConstantExpression) args.getExpression(0);
assertEquals("Hello, World!", first.getValue());
}
} }

View File

@ -1,82 +0,0 @@
package groowt.view.component.web.transpiler;
import groowt.view.component.web.antlr.ParserUtil;
import groowt.view.component.web.antlr.TokenList;
import groowt.view.component.web.ast.DefaultAstBuilder;
import groowt.view.component.web.ast.DefaultNodeFactory;
import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.ast.node.GStringBodyTextNode;
import groowt.view.component.web.transpile.GStringTranspiler;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.GStringExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.junit.jupiter.api.Test;
import java.util.Objects;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.*;
public abstract class GStringTranspilerTests {
protected abstract GStringTranspiler getGStringTranspiler();
@Test
public void smokeScreen() {
assertDoesNotThrow(() -> {
getGStringTranspiler();
});
}
protected BodyNode getBodyNode(String source) {
final var parseResult = ParserUtil.parseCompilationUnit(source);
final var tokenList = new TokenList(parseResult.getTokenStream());
final var nodeFactory = new DefaultNodeFactory(tokenList);
final var astBuilder = new DefaultAstBuilder(nodeFactory);
final var cuNode = astBuilder.buildCompilationUnit(parseResult.getCompilationUnitContext());
return Objects.requireNonNull(cuNode.getBodyNode());
}
protected void doTest(String source, Consumer<GStringExpression> further) {
final var gStringBodyTextNode = this.getBodyNode(source).getAt(0, GStringBodyTextNode.class);
final var transpiler = this.getGStringTranspiler();
final GStringExpression gStringExpression = transpiler.createGStringExpression(gStringBodyTextNode);
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));
});
}
}