Some gString/closure transpilation fixing, moving groovy util, and star imports.
This commit is contained in:
parent
a2c6b787f7
commit
494da75fe9
@ -1,16 +1,19 @@
|
||||
package groowt.view.web.ast;
|
||||
|
||||
import groowt.view.web.antlr.TokenList;
|
||||
import groowt.view.web.ast.extension.NodeExtension;
|
||||
import groowt.view.web.ast.node.LeafNode;
|
||||
import groowt.view.web.ast.node.Node;
|
||||
import groowt.view.web.ast.node.TreeNode;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class NodeUtil {
|
||||
|
||||
public static boolean isAnyOfType(Node subject, Class<?>... nodeTypes) {
|
||||
Objects.requireNonNull(subject);
|
||||
for (final var type : nodeTypes) {
|
||||
if (type.isAssignableFrom(subject.getClass())) {
|
||||
return true;
|
||||
@ -19,7 +22,19 @@ public final class NodeUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean hasExtensionOfType(Node subject, Class<?>... extensionTypes) {
|
||||
Objects.requireNonNull(subject);
|
||||
for (final var extensionType : extensionTypes) {
|
||||
if (subject.hasExtension((Class<? extends NodeExtension>) extensionType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAnyOfType(Node subject, List<Class<? extends Node>> nodeTypes) {
|
||||
Objects.requireNonNull(subject);
|
||||
for (final var type : nodeTypes) {
|
||||
if (type.isAssignableFrom(subject.getClass())) {
|
||||
return true;
|
||||
|
@ -38,7 +38,7 @@ public class ClosureValueNode extends AbstractLeafNode implements ValueNode {
|
||||
}
|
||||
|
||||
protected String toValidGroovyCode(List<Token> groovyTokens) {
|
||||
return "{ " + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + "\n}";
|
||||
return "def c = { " + groovyTokens.stream().map(Token::getText).collect(Collectors.joining()) + "\n}";
|
||||
}
|
||||
|
||||
public GroovyCodeNodeExtension getGroovyCode() {
|
||||
|
@ -4,9 +4,9 @@ import groowt.view.component.context.ComponentResolveException;
|
||||
import groowt.view.component.runtime.ComponentCreateException;
|
||||
import groowt.view.web.WebViewComponentBugError;
|
||||
import groowt.view.web.ast.node.*;
|
||||
import groowt.view.web.transpile.groovy.GroovyUtil;
|
||||
import groowt.view.web.transpile.groovy.GroovyUtil.ConvertResult;
|
||||
import groowt.view.web.transpile.resolve.ComponentClassNodeResolver;
|
||||
import groowt.view.web.transpile.util.GroovyUtil;
|
||||
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
||||
import groowt.view.web.util.Provider;
|
||||
import groowt.view.web.util.SourcePosition;
|
||||
import org.codehaus.groovy.ast.*;
|
||||
@ -352,15 +352,9 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
TranspilerState state
|
||||
) {
|
||||
final var createArgs = new ArgumentListExpression();
|
||||
|
||||
final VariableExpression resolvedVariableExpression;
|
||||
final Variable currentResolved = state.getCurrentResolved();
|
||||
if (currentResolved instanceof VariableExpression) {
|
||||
resolvedVariableExpression = (VariableExpression) currentResolved;
|
||||
} else {
|
||||
resolvedVariableExpression = new VariableExpression(currentResolved);
|
||||
}
|
||||
createArgs.addExpression(resolvedVariableExpression);
|
||||
|
||||
final VariableExpression currentResolved = state.getCurrentResolved();
|
||||
createArgs.addExpression(currentResolved);
|
||||
|
||||
final List<AttrNode> attrNodes = componentNode.getArgs().getAttributes();
|
||||
if (attrNodes.isEmpty()) {
|
||||
|
@ -8,7 +8,7 @@ import groowt.view.web.ast.extension.GStringScriptletExtension;
|
||||
import groowt.view.web.ast.node.GStringBodyTextNode;
|
||||
import groowt.view.web.ast.node.JStringBodyTextNode;
|
||||
import groowt.view.web.ast.node.Node;
|
||||
import groowt.view.web.transpile.util.GroovyUtil;
|
||||
import groowt.view.web.transpile.groovy.GroovyUtil;
|
||||
import groowt.view.web.util.FilteringIterable;
|
||||
import groowt.view.web.util.Option;
|
||||
import groowt.view.web.util.TokenRange;
|
||||
@ -46,15 +46,11 @@ public class DefaultGStringTranspiler implements GStringTranspiler {
|
||||
}
|
||||
}
|
||||
|
||||
protected Option<ConstantExpression> checkNextAfterDollar(Node current, @Nullable Node next) {
|
||||
if (!(next instanceof JStringBodyTextNode)) {
|
||||
protected Option<ConstantExpression> checkNextAfterDollar(@Nullable Node next) {
|
||||
if (next != null && next.hasExtension(GStringNodeExtension.class)) {
|
||||
return Option.liftLazy(() -> {
|
||||
final ConstantExpression expression = this.jStringTranspiler.createEmptyStringLiteral();
|
||||
if (next != null) {
|
||||
this.positionSetter.setToStartOf(expression, next);
|
||||
} else {
|
||||
this.positionSetter.setToStartOf(expression, current);
|
||||
}
|
||||
this.positionSetter.setToStartOf(expression, next);
|
||||
return expression;
|
||||
});
|
||||
} else {
|
||||
@ -114,13 +110,13 @@ public class DefaultGStringTranspiler implements GStringTranspiler {
|
||||
return new PathResult(
|
||||
propertyExpression,
|
||||
this.checkPrevBeforeDollar(prev, current),
|
||||
this.checkNextAfterDollar(current, next)
|
||||
this.checkNextAfterDollar(next)
|
||||
);
|
||||
} else {
|
||||
return new PathResult(
|
||||
begin,
|
||||
this.checkPrevBeforeDollar(prev, current),
|
||||
this.checkNextAfterDollar(current, next)
|
||||
this.checkNextAfterDollar(next)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -187,13 +183,13 @@ public class DefaultGStringTranspiler implements GStringTranspiler {
|
||||
case GStringScriptletExtension scriptlet -> {
|
||||
checkPrevBeforeDollar(prev, current).ifPresent(texts::add);
|
||||
values.add(this.handleScriptlet(scriptlet));
|
||||
checkNextAfterDollar(current, next).ifPresent(texts::add);
|
||||
checkNextAfterDollar(next).ifPresent(texts::add);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (texts.size() != values.size() + 1) {
|
||||
if (!(texts.size() == values.size() || texts.size() == values.size() + 1)) {
|
||||
throw new IllegalStateException(
|
||||
"incorrect amount of texts vs. values: " + texts.size() + " " + values.size()
|
||||
);
|
||||
|
@ -11,8 +11,8 @@ import groowt.view.web.compiler.MultipleWebViewComponentCompileErrorsException;
|
||||
import groowt.view.web.compiler.WebViewComponentTemplateCompileException;
|
||||
import groowt.view.web.compiler.WebViewComponentTemplateCompileUnit;
|
||||
import groowt.view.web.runtime.DefaultWebViewRenderContext;
|
||||
import groowt.view.web.transpile.groovy.GroovyUtil;
|
||||
import groowt.view.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
|
||||
import groowt.view.web.transpile.util.GroovyUtil;
|
||||
import org.codehaus.groovy.ast.*;
|
||||
import org.codehaus.groovy.ast.expr.*;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
|
@ -1,18 +1,20 @@
|
||||
package groowt.view.web.transpile;
|
||||
|
||||
import groowt.view.web.WebViewComponentBugError;
|
||||
import groowt.view.web.ast.node.*;
|
||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||
import groowt.view.web.transpile.util.GroovyUtil;
|
||||
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
||||
import groowt.view.web.transpile.groovy.GroovyUtil;
|
||||
import groowt.view.web.transpile.groovy.GroovyUtil.ConvertResult;
|
||||
import org.codehaus.groovy.ast.Parameter;
|
||||
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.Expression;
|
||||
import org.codehaus.groovy.ast.expr.*;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
import org.codehaus.groovy.ast.stmt.EmptyStatement;
|
||||
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||
import org.codehaus.groovy.ast.stmt.Statement;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static groowt.view.web.transpile.TranspilerUtil.getStringLiteral;
|
||||
|
||||
// TODO: set positions
|
||||
@ -24,16 +26,36 @@ public class DefaultValueNodeTranspiler implements ValueNodeTranspiler {
|
||||
this.componentTranspiler = componentTranspiler;
|
||||
}
|
||||
|
||||
protected ClosureExpression closureValue(ClosureValueNode closureValueNode) {
|
||||
// TODO: positions
|
||||
protected Expression handleClosureNode(ClosureValueNode closureValueNode) {
|
||||
final var rawCode = closureValueNode.getGroovyCode().getAsValidGroovyCode();
|
||||
final ConvertResult convertResult = GroovyUtil.convert(rawCode);
|
||||
final @Nullable BlockStatement blockStatement = convertResult.blockStatement();
|
||||
if (blockStatement == null || blockStatement.isEmpty()) {
|
||||
throw new IllegalStateException("block statement is null or empty");
|
||||
final ClosureExpression convertedClosure = GroovyUtil.getClosure(rawCode);
|
||||
final Statement closureCode = convertedClosure.getCode();
|
||||
if (closureCode instanceof BlockStatement blockStatement) {
|
||||
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();
|
||||
return switch (expression) {
|
||||
case ConstantExpression ignored -> expression;
|
||||
case VariableExpression ignored -> expression;
|
||||
case PropertyExpression ignored -> expression;
|
||||
default -> convertedClosure;
|
||||
};
|
||||
} else {
|
||||
throw new IllegalArgumentException("A component closure value must produce a value.");
|
||||
}
|
||||
} else {
|
||||
return convertedClosure;
|
||||
}
|
||||
} else {
|
||||
return convertedClosure;
|
||||
}
|
||||
final ExpressionStatement exprStmt = (ExpressionStatement) blockStatement.getStatements().getFirst();
|
||||
// TODO: set pos
|
||||
return (ClosureExpression) exprStmt.getExpression();
|
||||
}
|
||||
|
||||
private Expression gStringValue(GStringValueNode gStringValueNode) {
|
||||
@ -69,7 +91,7 @@ public class DefaultValueNodeTranspiler implements ValueNodeTranspiler {
|
||||
@Override
|
||||
public Expression createExpression(ValueNode valueNode, TranspilerState state) {
|
||||
return switch (valueNode) {
|
||||
case ClosureValueNode closureValueNode -> this.closureValue(closureValueNode);
|
||||
case ClosureValueNode closureValueNode -> this.handleClosureNode(closureValueNode);
|
||||
case GStringValueNode gStringValueNode -> this.gStringValue(gStringValueNode);
|
||||
case JStringValueNode jStringValueNode -> this.jStringValue(jStringValueNode);
|
||||
case EmptyClosureValueNode emptyClosureValueNode -> this.emptyClosureValue(emptyClosureValueNode);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package groowt.view.web.transpile;
|
||||
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ImportNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
@ -55,9 +56,16 @@ public class WebViewComponentModuleNode extends ModuleNode {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param alias the name of interest
|
||||
* @return a standard (non-static, non-star) import, or {@code null} if there is none
|
||||
*/
|
||||
@Override
|
||||
public @Nullable ImportNode getImport(String alias) {
|
||||
return this.allImports.get(alias);
|
||||
return this.imports.stream()
|
||||
.filter(importNode -> importNode.getAlias().equals(alias))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected void putToAll(String alias, ImportNode importNode) {
|
||||
@ -88,4 +96,52 @@ public class WebViewComponentModuleNode extends ModuleNode {
|
||||
this.putToAll(alias, importNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addImport(String alias, ClassNode type) {
|
||||
this.addImport(new ImportNode(type, alias));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addImport(String alias, ClassNode type, List<AnnotationNode> annotations) {
|
||||
final var importNode = new ImportNode(type, alias);
|
||||
importNode.addAnnotations(annotations);
|
||||
this.addImport(importNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStarImport(String packageName) {
|
||||
this.addStarImport(new ImportNode(packageName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStarImport(String packageName, List<AnnotationNode> annotations) {
|
||||
final var importNode = new ImportNode(packageName);
|
||||
importNode.addAnnotations(annotations);
|
||||
this.addStarImport(importNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStaticImport(ClassNode type, String fieldName, String alias) {
|
||||
this.addStaticImport(alias, new ImportNode(type, fieldName, alias));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStaticImport(ClassNode type, String fieldName, String alias, List<AnnotationNode> annotations) {
|
||||
final var importNode = new ImportNode(type, fieldName, alias);
|
||||
importNode.addAnnotations(annotations);
|
||||
this.addStaticImport(alias, importNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStaticStarImport(String name, ClassNode type) {
|
||||
this.addStaticStarImport(name, new ImportNode(type, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStaticStarImport(String name, ClassNode type, List<AnnotationNode> annotations) {
|
||||
final var importNode = new ImportNode(type, name);
|
||||
importNode.addAnnotations(annotations);
|
||||
this.addStaticStarImport(name, importNode);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package groowt.view.web.transpile.util;
|
||||
package groowt.view.web.transpile.groovy;
|
||||
|
||||
import org.codehaus.groovy.ast.*;
|
||||
import org.codehaus.groovy.ast.expr.*;
|
@ -1,11 +1,15 @@
|
||||
package groowt.view.web.transpile.util;
|
||||
package groowt.view.web.transpile.groovy;
|
||||
|
||||
import groovy.lang.GroovyCodeSource;
|
||||
import groowt.view.web.WebViewComponentBugError;
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.ast.builder.AstStringCompiler;
|
||||
import org.codehaus.groovy.ast.expr.BinaryExpression;
|
||||
import org.codehaus.groovy.ast.expr.ClosureExpression;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
||||
import org.codehaus.groovy.control.CompilationUnit;
|
||||
import org.codehaus.groovy.control.CompilePhase;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
@ -97,6 +101,28 @@ public final class GroovyUtil {
|
||||
return new ConvertResult(moduleNode, blockStatement, scriptClassNode, classNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source must be of form {@code def c = { ... }} and the closure must not be empty.
|
||||
* @return the block inside the closure
|
||||
*/
|
||||
public static ClosureExpression getClosure(String source) {
|
||||
final ConvertResult convertResult = convert(source);
|
||||
final BlockStatement blockStatement = convertResult.blockStatement();
|
||||
if (blockStatement == null) {
|
||||
throw new WebViewComponentBugError(new IllegalArgumentException(
|
||||
"Did not expect the source to produce no BlockStatement."
|
||||
));
|
||||
}
|
||||
if (blockStatement.isEmpty()) {
|
||||
throw new WebViewComponentBugError(new IllegalArgumentException(
|
||||
"Did not expect the BlockStatement to be empty."
|
||||
));
|
||||
}
|
||||
final ExpressionStatement exprStmt = (ExpressionStatement) blockStatement.getStatements().getFirst();
|
||||
final BinaryExpression binaryExpression = (BinaryExpression) exprStmt.getExpression();
|
||||
return (ClosureExpression) binaryExpression.getRightExpression();
|
||||
}
|
||||
|
||||
private GroovyUtil() {}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package groowt.view.web.transpile.util
|
||||
package groowt.view.web.transpile.groovy
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode
|
||||
|
@ -20,18 +20,34 @@ public class ModuleNodeComponentClassNodeResolver extends CachingComponentClassN
|
||||
@Override
|
||||
public Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
|
||||
return super.getClassForNameWithoutPackage(nameWithoutPackage).flatMapLeft(ignored -> {
|
||||
// try imports first
|
||||
// try regular imports first
|
||||
final var importedClassNode = this.moduleNode.getImportType(nameWithoutPackage);
|
||||
if (importedClassNode != null) {
|
||||
this.addClassNode(importedClassNode);
|
||||
return Either.right(importedClassNode);
|
||||
}
|
||||
|
||||
// try star imports
|
||||
final var starImports = this.moduleNode.getStarImports();
|
||||
for (final var starImport : starImports) {
|
||||
final var packageName = starImport.getPackageName();
|
||||
final String fqn;
|
||||
if (packageName.endsWith(".")) {
|
||||
fqn = packageName + nameWithoutPackage;
|
||||
} else {
|
||||
fqn = packageName + "." + nameWithoutPackage;
|
||||
}
|
||||
final var withPackage = this.getClassForFqn(fqn);
|
||||
if (withPackage.isRight()) {
|
||||
return withPackage;
|
||||
}
|
||||
}
|
||||
|
||||
// try pre-pending package and asking for fqn
|
||||
final var packageName = this.moduleNode.getPackageName();
|
||||
final String fqn;
|
||||
if (packageName.endsWith(".")) {
|
||||
fqn = this.moduleNode + nameWithoutPackage;
|
||||
fqn = this.moduleNode.getPackageName() + nameWithoutPackage;
|
||||
} else {
|
||||
fqn = this.moduleNode.getPackageName() + "." + nameWithoutPackage;
|
||||
}
|
||||
|
@ -7,17 +7,13 @@ class BaseWebViewComponentTests extends AbstractWebViewComponentTests {
|
||||
|
||||
static final class Greeter extends BaseWebViewComponent {
|
||||
|
||||
private final String target
|
||||
final String target
|
||||
|
||||
Greeter(Map<String, Object> attr) {
|
||||
super('Hello, $target!')
|
||||
this.target = Objects.requireNonNull(attr.get("target"))
|
||||
}
|
||||
|
||||
String getTarget() {
|
||||
return this.target
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class UsingGreeter extends BaseWebViewComponent {
|
||||
|
@ -0,0 +1,51 @@
|
||||
package groowt.view.web
|
||||
|
||||
import groowt.view.web.lib.AbstractWebViewComponentTests
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class SimpleWebViewComponentTests extends AbstractWebViewComponentTests {
|
||||
|
||||
@Test
|
||||
void closureValueWithConstantExpressionEvaluatesToValue() {
|
||||
this.doTest('<Echo greeting={"Hello, World!"}>$greeting</Echo>', 'Hello, World!')
|
||||
}
|
||||
|
||||
@Test
|
||||
void closureValueWithVariableExpressionEvaluatesToValue() {
|
||||
this.doTest(
|
||||
'<Echo greeting="Hello, World!"><Echo subGreeting={greeting}>$subGreeting</Echo></Echo>',
|
||||
'Hello, World!'
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void closureWithPropertyExpressionEvaluatesToValue() {
|
||||
this.doTest(
|
||||
'''
|
||||
---
|
||||
import groovy.transform.Field
|
||||
|
||||
@Field
|
||||
Map greetings = [hello: 'Hello!']
|
||||
---
|
||||
<Echo greeting={greetings.hello}>$greeting</Echo>
|
||||
'''.stripIndent().trim(),
|
||||
'Hello!'
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void closureWithMethodCallIsClosure() {
|
||||
this.doTest(
|
||||
'''
|
||||
---
|
||||
def helper(String input) {
|
||||
input.capitalize()
|
||||
}
|
||||
---
|
||||
<Echo subHelper={ helper('lowercase') }>${ -> subHelper.call() }</Echo>
|
||||
'''.stripIndent().trim(), 'Lowercase'
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package groowt.view.web.tools
|
||||
|
||||
import groowt.view.web.transpile.util.GroovyUtil
|
||||
import groowt.view.web.transpile.groovy.GroovyUtil
|
||||
import org.codehaus.groovy.ast.ImportNode
|
||||
|
||||
import static groowt.view.web.transpile.util.GroovyUtil.formatGroovy
|
||||
import static groowt.view.web.transpile.groovy.GroovyUtil.formatGroovy
|
||||
|
||||
def src = '''
|
||||
import some.Thing
|
||||
|
Loading…
Reference in New Issue
Block a user