Fragments and child rendering are working.
This commit is contained in:
parent
f573f1cec4
commit
ea4c29f1d7
@ -9,50 +9,38 @@ import java.util.*;
|
|||||||
public abstract class AbstractComponentFactory<T extends ViewComponent> extends GroovyObjectSupport
|
public abstract class AbstractComponentFactory<T extends ViewComponent> extends GroovyObjectSupport
|
||||||
implements ComponentFactory<T> {
|
implements ComponentFactory<T> {
|
||||||
|
|
||||||
private static final String DO_CREATE = "doCreate";
|
protected final Map<Class<?>[], MetaMethod> cache = new HashMap<>();
|
||||||
private static final Class<?>[] EMPTY_CLASSES = new Class[0];
|
|
||||||
|
|
||||||
private static Object[] flatten(Object... args) {
|
protected MetaMethod findDoCreateMethod(Object[] allArgs) {
|
||||||
if (args.length == 0) {
|
return this.cache.computeIfAbsent(ComponentFactoryUtil.asTypes(allArgs), types ->
|
||||||
return args;
|
ComponentFactoryUtil.findDoCreateMethod(this.getMetaClass(), types)
|
||||||
} else {
|
|
||||||
final List<Object> result = new ArrayList<>(args.length);
|
|
||||||
for (final var arg : args) {
|
|
||||||
if (arg instanceof Object[] arr) {
|
|
||||||
result.addAll(Arrays.asList(arr));
|
|
||||||
} else {
|
|
||||||
result.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toArray(Object[]::new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?>[] asTypes(Object[] args) {
|
|
||||||
if (args.length == 0) {
|
|
||||||
return EMPTY_CLASSES;
|
|
||||||
}
|
|
||||||
final Class<?>[] result = new Class[args.length];
|
|
||||||
for (int i = 0; i < args.length; i++) {
|
|
||||||
result[i] = args[i].getClass();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<Class<?>[], MetaMethod> cache = new HashMap<>();
|
|
||||||
|
|
||||||
private MetaMethod findDoCreateMethod(Object[] allArgs) {
|
|
||||||
return this.cache.computeIfAbsent(asTypes(allArgs), types ->
|
|
||||||
this.getMetaClass().getMetaMethod(DO_CREATE, types)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private T findAndDoCreate(ComponentContext componentContext, Object[] args) {
|
protected T findAndDoCreate(Object type, ComponentContext componentContext, Object[] args) {
|
||||||
final Object[] contextsAndArgs = flatten(componentContext, args);
|
final Object[] typeContextAndArgs = ComponentFactoryUtil.flatten(type, componentContext, args);
|
||||||
final MetaMethod contextsAndArgsMethod = this.findDoCreateMethod(contextsAndArgs);
|
final MetaMethod typeContextAndArgsMethod = this.findDoCreateMethod(typeContextAndArgs);
|
||||||
if (contextsAndArgsMethod != null) {
|
if (typeContextAndArgsMethod != null) {
|
||||||
return (T) contextsAndArgsMethod.invoke(this, contextsAndArgs);
|
return (T) typeContextAndArgsMethod.invoke(this, typeContextAndArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object[] typeAndContext = new Object[] { type, componentContext };
|
||||||
|
final MetaMethod typeAndContextMethod = this.findDoCreateMethod(typeAndContext);
|
||||||
|
if (typeAndContextMethod != null) {
|
||||||
|
return (T) typeAndContextMethod.invoke(this, typeAndContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object[] typeAndArgs = ComponentFactoryUtil.flatten(type, args);
|
||||||
|
final MetaMethod typeAndArgsMethod = this.findDoCreateMethod(typeAndArgs);
|
||||||
|
if (typeAndArgsMethod != null) {
|
||||||
|
return (T) typeAndArgsMethod.invoke(this, typeAndArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object[] typeOnly = new Object[] { type };
|
||||||
|
final MetaMethod typeOnlyMethod = this.findDoCreateMethod(typeOnly);
|
||||||
|
if (typeOnlyMethod != null) {
|
||||||
|
return (T) typeOnlyMethod.invoke(this, typeOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Object[] contextOnly = new Object[] { componentContext };
|
final Object[] contextOnly = new Object[] { componentContext };
|
||||||
@ -67,15 +55,15 @@ public abstract class AbstractComponentFactory<T extends ViewComponent> extends
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new MissingMethodException(
|
throw new MissingMethodException(
|
||||||
DO_CREATE,
|
ComponentFactoryUtil.DO_CREATE,
|
||||||
this.getClass(),
|
this.getClass(),
|
||||||
args
|
args
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T create(ComponentContext componentContext, Object... args) {
|
public T create(Object type, ComponentContext componentContext, Object... args) {
|
||||||
return this.findAndDoCreate(componentContext, args);
|
return this.findAndDoCreate(type, componentContext, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,12 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
|||||||
this.template = Objects.requireNonNull(template);
|
this.template = Objects.requireNonNull(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void beforeRender() {}
|
protected void beforeRender() {
|
||||||
|
this.getContext().beforeComponentRender(this);
|
||||||
|
}
|
||||||
|
|
||||||
protected void afterRender() {
|
protected void afterRender() {
|
||||||
this.getContext().afterComponent(this);
|
this.getContext().afterComponentRender(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package groowt.view.component;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
|
||||||
|
final class ClosureComponentFactory<T extends ViewComponent> extends AbstractComponentFactory<T> {
|
||||||
|
|
||||||
|
private final Closure<T> closure;
|
||||||
|
|
||||||
|
public ClosureComponentFactory(Closure<T> closure) {
|
||||||
|
this.closure = closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T create(Object type, ComponentContext componentContext, Object... args) {
|
||||||
|
final Object[] flattened = ComponentFactoryUtil.flatten(type, componentContext, args);
|
||||||
|
return this.closure.call(flattened);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -11,13 +11,21 @@ import java.util.function.Predicate;
|
|||||||
public interface ComponentContext {
|
public interface ComponentContext {
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
ComponentFactory<?> resolve(String component);
|
interface Resolved {
|
||||||
|
String getTypeName();
|
||||||
|
ComponentFactory<?> getComponentFactory();
|
||||||
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
ViewComponent create(ComponentFactory<?> factory, Object... args);
|
Resolved resolve(String component);
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
void afterComponent(ViewComponent component);
|
ViewComponent create(Resolved resolved, Object... args);
|
||||||
|
|
||||||
|
void beforeComponentRender(ViewComponent component);
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
void afterComponentRender(ViewComponent component);
|
||||||
|
|
||||||
Deque<ComponentScope> getScopeStack();
|
Deque<ComponentScope> getScopeStack();
|
||||||
|
|
||||||
|
@ -7,14 +7,14 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
public interface ComponentFactory<T extends ViewComponent> extends GroovyObject {
|
public interface ComponentFactory<T extends ViewComponent> extends GroovyObject {
|
||||||
|
|
||||||
static <T extends ViewComponent> ComponentFactory<T> of(Closure<T> closure) {
|
static <T extends ViewComponent> ComponentFactory<T> ofClosure(Closure<T> closure) {
|
||||||
return new DelegatingComponentFactory<>((context, args) -> closure.call(context, args));
|
return new ClosureComponentFactory<>(closure);
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T extends ViewComponent> ComponentFactory<T> of(Supplier<T> supplier) {
|
static <T extends ViewComponent> ComponentFactory<T> ofSupplier(Supplier<T> supplier) {
|
||||||
return new DelegatingComponentFactory<>((ignored0, ignored1) -> supplier.get());
|
return new SupplierComponentFactory<>(supplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
T create(ComponentContext componentContext, Object... args);
|
T create(Object type, ComponentContext componentContext, Object... args);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
package groowt.view.component;
|
||||||
|
|
||||||
|
import groovy.lang.MetaClass;
|
||||||
|
import groovy.lang.MetaMethod;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ComponentFactoryUtil {
|
||||||
|
|
||||||
|
public static final String DO_CREATE = "doCreate";
|
||||||
|
public static final Class<?>[] EMPTY_CLASSES = new Class[0];
|
||||||
|
|
||||||
|
public static Object[] flatten(Object... args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
return args;
|
||||||
|
} else {
|
||||||
|
final List<Object> result = new ArrayList<>(args.length);
|
||||||
|
for (final var arg : args) {
|
||||||
|
if (arg instanceof Object[] arr) {
|
||||||
|
result.addAll(Arrays.asList(arr));
|
||||||
|
} else {
|
||||||
|
result.add(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toArray(Object[]::new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Class<?>[] asTypes(Object[] args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
return EMPTY_CLASSES;
|
||||||
|
}
|
||||||
|
final Class<?>[] result = new Class[args.length];
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
final Object arg = args[i];
|
||||||
|
if (arg instanceof Class<?> argAsClass) {
|
||||||
|
result[i] = argAsClass;
|
||||||
|
} else {
|
||||||
|
result[i] = arg.getClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MetaMethod findDoCreateMethod(MetaClass metaClass, Class<?>[] types) {
|
||||||
|
return metaClass.getMetaMethod(DO_CREATE, types);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ComponentFactoryUtil() {}
|
||||||
|
|
||||||
|
}
|
@ -10,11 +10,33 @@ import java.util.function.Predicate;
|
|||||||
|
|
||||||
public class DefaultComponentContext implements ComponentContext {
|
public class DefaultComponentContext implements ComponentContext {
|
||||||
|
|
||||||
|
protected static class DefaultResolved implements ComponentContext.Resolved {
|
||||||
|
|
||||||
|
private final String typeName;
|
||||||
|
private final ComponentFactory<?> factory;
|
||||||
|
|
||||||
|
public DefaultResolved(String typeName, ComponentFactory<?> factory) {
|
||||||
|
this.typeName = typeName;
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeName() {
|
||||||
|
return this.typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentFactory<?> getComponentFactory() {
|
||||||
|
return this.factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private final Deque<ComponentScope> scopeStack = new LinkedList<>();
|
private final Deque<ComponentScope> scopeStack = new LinkedList<>();
|
||||||
private final Deque<ViewComponent> componentStack = new LinkedList<>();
|
private final Deque<ViewComponent> componentStack = new LinkedList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComponentFactory<?> resolve(String component) {
|
public Resolved resolve(String component) {
|
||||||
if (scopeStack.isEmpty()) {
|
if (scopeStack.isEmpty()) {
|
||||||
throw new IllegalStateException("There are no scopes on the scopeStack.");
|
throw new IllegalStateException("There are no scopes on the scopeStack.");
|
||||||
}
|
}
|
||||||
@ -23,7 +45,7 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
while (!getStack.isEmpty()) {
|
while (!getStack.isEmpty()) {
|
||||||
final ComponentScope scope = getStack.pop();
|
final ComponentScope scope = getStack.pop();
|
||||||
if (scope.contains(component)) {
|
if (scope.contains(component)) {
|
||||||
return scope.get(component);
|
return new DefaultResolved(component, scope.get(component));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +54,7 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
while (!missingStack.isEmpty()) {
|
while (!missingStack.isEmpty()) {
|
||||||
final ComponentScope scope = getStack.pop();
|
final ComponentScope scope = getStack.pop();
|
||||||
try {
|
try {
|
||||||
return scope.factoryMissing(component);
|
return new DefaultResolved(component, scope.factoryMissing(component));
|
||||||
} catch (NoFactoryMissingException e) {
|
} catch (NoFactoryMissingException e) {
|
||||||
if (first == null) {
|
if (first == null) {
|
||||||
first = e;
|
first = e;
|
||||||
@ -48,14 +70,19 @@ public class DefaultComponentContext implements ComponentContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ViewComponent create(ComponentFactory<?> factory, Object... args) {
|
public ViewComponent create(Resolved resolved, Object... args) {
|
||||||
final ViewComponent component = factory.create(this, args);
|
return resolved.getComponentFactory().create(
|
||||||
this.componentStack.push(component);
|
resolved.getTypeName(), this, args
|
||||||
return component;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterComponent(ViewComponent component) {
|
public void beforeComponentRender(ViewComponent component) {
|
||||||
|
this.componentStack.push(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterComponentRender(ViewComponent component) {
|
||||||
final var popped = this.componentStack.pop();
|
final var popped = this.componentStack.pop();
|
||||||
if (!popped.equals(component)) {
|
if (!popped.equals(component)) {
|
||||||
throw new IllegalStateException("Popped component does not equal arg to afterComponent()");
|
throw new IllegalStateException("Popped component does not equal arg to afterComponent()");
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package groowt.view.component;
|
|
||||||
|
|
||||||
final class DelegatingComponentFactory<T extends ViewComponent> extends AbstractComponentFactory<T> {
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
interface ComponentFactoryDelegate<T extends ViewComponent> {
|
|
||||||
T doCreate(ComponentContext context, Object... args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ComponentFactoryDelegate<T> function;
|
|
||||||
|
|
||||||
public DelegatingComponentFactory(ComponentFactoryDelegate<T> function) {
|
|
||||||
this.function = function;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T doCreate(ComponentContext componentContext, Object... args) {
|
|
||||||
return this.function.doCreate(componentContext, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,17 @@
|
|||||||
|
package groowt.view.component;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
final class SupplierComponentFactory<T extends ViewComponent> extends AbstractComponentFactory<T> {
|
||||||
|
|
||||||
|
private final Supplier<T> tSupplier;
|
||||||
|
|
||||||
|
public SupplierComponentFactory(Supplier<T> tSupplier) {
|
||||||
|
this.tSupplier = tSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T doCreate(Object type, ComponentContext componentContext, Object... args) {
|
||||||
|
return this.tSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package groowt.view.component;
|
package groowt.view.component;
|
||||||
|
|
||||||
import groowt.view.View;
|
import groowt.view.View;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
public interface ViewComponent extends View {
|
public interface ViewComponent extends View {
|
||||||
|
|
||||||
@ -8,7 +9,9 @@ public interface ViewComponent extends View {
|
|||||||
return this.getClass().getName();
|
return this.getClass().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
void setContext(ComponentContext context);
|
void setContext(ComponentContext context);
|
||||||
|
|
||||||
ComponentContext getContext();
|
ComponentContext getContext();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
3
web-views/sketching/twoGreeters.wvc
Normal file
3
web-views/sketching/twoGreeters.wvc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<>
|
||||||
|
<Greeter greeting='Hello, one!' /> <Greeter greeting='Hello, two!' />
|
||||||
|
</>
|
@ -56,7 +56,7 @@ closingComponent
|
|||||||
|
|
||||||
fragmentComponent
|
fragmentComponent
|
||||||
: ComponentOpen ComponentClose
|
: ComponentOpen ComponentClose
|
||||||
body?
|
body
|
||||||
ClosingComponentOpen ComponentClose
|
ClosingComponentOpen ComponentClose
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -2,13 +2,11 @@ package groowt.view.web;
|
|||||||
|
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groovy.lang.GroovyClassLoader;
|
import groovy.lang.GroovyClassLoader;
|
||||||
import groowt.util.di.DefaultRegistryObjectFactory;
|
|
||||||
import groowt.view.component.AbstractViewComponent;
|
import groowt.view.component.AbstractViewComponent;
|
||||||
import groowt.view.component.ComponentContext;
|
import groowt.view.component.ComponentContext;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import groowt.view.web.WebViewTemplateComponentSource.*;
|
import groowt.view.web.WebViewTemplateComponentSource.*;
|
||||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
import groowt.view.web.runtime.WebViewComponentWriter;
|
||||||
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -77,25 +75,36 @@ public class DefaultWebViewComponent extends AbstractViewComponent implements We
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WebViewChildRenderer> getChildren() {
|
public List<WebViewChildRenderer> getChildRenderers() {
|
||||||
return Objects.requireNonNullElseGet(this.children, ArrayList::new);
|
if (this.children == null) {
|
||||||
|
this.children = new ArrayList<>();
|
||||||
|
}
|
||||||
|
return this.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setChildren(List<WebViewChildRenderer> children) {
|
public boolean hasChildren() {
|
||||||
|
return !this.getChildRenderers().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChildRenderers(List<WebViewChildRenderer> children) {
|
||||||
this.children = children;
|
this.children = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void renderChildren() {
|
public void renderChildren() {
|
||||||
for (final var childRenderer : this.getChildren()) {
|
for (final var childRenderer : this.getChildRenderers()) {
|
||||||
try {
|
try {
|
||||||
|
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
||||||
|
this.getContext().beforeComponentRender(childComponentRenderer.getComponent());
|
||||||
|
}
|
||||||
childRenderer.render(this);
|
childRenderer.render(this);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ChildRenderException(e);
|
throw new ChildRenderException(e);
|
||||||
} finally {
|
} finally {
|
||||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
||||||
this.getContext().afterComponent(childComponentRenderer.getComponent());
|
this.getContext().afterComponentRender(childComponentRenderer.getComponent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package groowt.view.web
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentScope
|
||||||
|
import groowt.view.component.DefaultComponentContext
|
||||||
|
import groowt.view.component.ViewComponent
|
||||||
|
import groowt.view.web.lib.Fragment
|
||||||
|
import groowt.view.web.runtime.WebViewComponentChildCollector
|
||||||
|
import org.jetbrains.annotations.ApiStatus
|
||||||
|
|
||||||
|
class DefaultWebViewComponentContext extends DefaultComponentContext {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ComponentScope getNewDefaultScope() {
|
||||||
|
new WebViewScope()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
ViewComponent createFragment(Closure<?> childCollector) {
|
||||||
|
def collector = new WebViewComponentChildCollector()
|
||||||
|
childCollector.call(collector)
|
||||||
|
def fragment = new Fragment()
|
||||||
|
fragment.childRenderers = collector.children
|
||||||
|
fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package groowt.view.web;
|
||||||
|
|
||||||
|
import groowt.view.component.ViewComponent;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface WebViewComponent extends ViewComponent {
|
||||||
|
|
||||||
|
List<WebViewChildRenderer> getChildRenderers();
|
||||||
|
boolean hasChildren();
|
||||||
|
void setChildRenderers(List<WebViewChildRenderer> children);
|
||||||
|
void renderChildren();
|
||||||
|
|
||||||
|
default List<Object> getChildren() {
|
||||||
|
return this.getChildRenderers().stream()
|
||||||
|
.map(childRenderer -> switch (childRenderer) {
|
||||||
|
case WebViewChildComponentRenderer componentRenderer -> componentRenderer.getComponent();
|
||||||
|
case WebViewChildGStringRenderer gStringRenderer -> gStringRenderer.getGString();
|
||||||
|
case WebViewChildJStringRenderer jStringRenderer -> jStringRenderer.getContent();
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package groowt.view.web
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentFactory
|
||||||
|
import groowt.view.component.DefaultComponentScope
|
||||||
|
import groowt.view.web.lib.Echo.EchoFactory
|
||||||
|
|
||||||
|
class WebViewScope extends DefaultComponentScope {
|
||||||
|
|
||||||
|
private final EchoFactory echoFactory = new EchoFactory()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ComponentFactory factoryMissing(String typeName) {
|
||||||
|
echoFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package groowt.view.web.lib;
|
||||||
|
|
||||||
|
import groowt.view.View;
|
||||||
|
import groowt.view.web.DefaultWebViewComponent;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class DelegatingWebViewComponent extends DefaultWebViewComponent {
|
||||||
|
|
||||||
|
private final Map<String, Object> attr;
|
||||||
|
|
||||||
|
public DelegatingWebViewComponent(Map<String, Object> attr) {
|
||||||
|
this.attr = attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, Object> getAttr() {
|
||||||
|
return this.attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract View getDelegate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void renderTo(Writer out) throws IOException {
|
||||||
|
this.getDelegate().renderTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
82
web-views/src/main/groovy/groowt/view/web/lib/Echo.groovy
Normal file
82
web-views/src/main/groovy/groowt/view/web/lib/Echo.groovy
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
import groowt.view.StandardGStringTemplateView
|
||||||
|
import groowt.view.View
|
||||||
|
import groowt.view.component.AbstractComponentFactory
|
||||||
|
import groowt.view.component.ComponentContext
|
||||||
|
import groowt.view.component.ComponentFactory
|
||||||
|
import groowt.view.web.WebViewChildComponentRenderer
|
||||||
|
|
||||||
|
class Echo extends DelegatingWebViewComponent {
|
||||||
|
|
||||||
|
static final class EchoFactory implements ComponentFactory<Echo> {
|
||||||
|
|
||||||
|
Echo doCreate(String typeName, ComponentContext context, Map<String, Object> attr) {
|
||||||
|
doCreate(typeName, context, attr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
Echo doCreate(String typeName, ComponentContext context, Map<String, Object> attr, boolean selfClose) {
|
||||||
|
def echo = new Echo(attr, typeName, selfClose)
|
||||||
|
echo.context = context
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
Echo doCreate(
|
||||||
|
String typeName,
|
||||||
|
ComponentContext context,
|
||||||
|
Map<String, Object> attr,
|
||||||
|
List<WebViewChildComponentRenderer> children
|
||||||
|
) {
|
||||||
|
def echo = new Echo(attr, typeName, false)
|
||||||
|
echo.context = context
|
||||||
|
echo.childRenderers = children
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Echo create(Object type, ComponentContext componentContext, Object... args) {
|
||||||
|
if (!type instanceof String) {
|
||||||
|
throw new IllegalArgumentException('<Echo> can only be used with String types.')
|
||||||
|
}
|
||||||
|
if (args == null || args.length < 1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
'<Echo> must have at least one attribute. ' +
|
||||||
|
'If you are just echoing children, use a fragment (<>...</>) instead. '
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.invokeMethod('doCreate', type as String, componentContext, *args) as Echo
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
String typeName
|
||||||
|
boolean selfClose
|
||||||
|
|
||||||
|
Echo(Map<String, Object> attr, String typeName, boolean selfClose) {
|
||||||
|
super(attr)
|
||||||
|
this.typeName = typeName
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View getDelegate() {
|
||||||
|
return new StandardGStringTemplateView(
|
||||||
|
src: Echo.getResource('EchoTemplate.gst'),
|
||||||
|
parent: this
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderAttr(Writer out) {
|
||||||
|
def iter = this.attr.iterator()
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
def entry = iter.next()
|
||||||
|
out << entry.key
|
||||||
|
out << '="'
|
||||||
|
out << entry.value
|
||||||
|
out << '"'
|
||||||
|
if (iter.hasNext()) {
|
||||||
|
out << ' '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
import groowt.view.web.DefaultWebViewComponent
|
||||||
|
|
||||||
|
class Fragment extends DefaultWebViewComponent {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void renderTo(Writer out) throws IOException {
|
||||||
|
this.beforeRender()
|
||||||
|
this.renderChildren()
|
||||||
|
this.afterRender()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package groowt.view.web;
|
package groowt.view.web;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
import groovy.lang.GroovyClassLoader;
|
||||||
import groowt.util.di.RegistryObjectFactory;
|
|
||||||
import groowt.view.component.CachingComponentTemplateCompiler;
|
import groowt.view.component.CachingComponentTemplateCompiler;
|
||||||
import groowt.view.component.ComponentTemplate;
|
import groowt.view.component.ComponentTemplate;
|
||||||
import groowt.view.component.ComponentTemplateCreateException;
|
import groowt.view.component.ComponentTemplateCreateException;
|
||||||
@ -14,9 +13,7 @@ import groowt.view.web.ast.DefaultNodeFactory;
|
|||||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||||
import groowt.view.web.transpile.DefaultGroovyTranspiler;
|
import groowt.view.web.transpile.DefaultGroovyTranspiler;
|
||||||
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
||||||
import groowt.view.web.transpile.TranspilerConfiguration;
|
|
||||||
import org.codehaus.groovy.control.CompilationUnit;
|
import org.codehaus.groovy.control.CompilationUnit;
|
||||||
import org.codehaus.groovy.control.CompilePhase;
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
import org.codehaus.groovy.control.Phases;
|
import org.codehaus.groovy.control.Phases;
|
||||||
import org.codehaus.groovy.control.io.AbstractReaderSource;
|
import org.codehaus.groovy.control.io.AbstractReaderSource;
|
||||||
@ -31,7 +28,6 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplateCompiler {
|
public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplateCompiler {
|
||||||
|
|
||||||
@ -74,11 +70,11 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
|||||||
this.groovyClassLoader = null;
|
this.groovyClassLoader = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ComponentTemplate doCompile(Class<? extends ViewComponent> forClass, Reader reader) {
|
protected ComponentTemplate doCompile(@Nullable Class<? extends ViewComponent> forClass, Reader reader) {
|
||||||
return this.doCompile(forClass, reader, null);
|
return this.doCompile(forClass, reader, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ComponentTemplate doCompile(Class<? extends ViewComponent> forClass, Reader reader, @Nullable URI uri) {
|
protected ComponentTemplate doCompile(@Nullable Class<? extends ViewComponent> forClass, Reader reader, @Nullable URI uri) {
|
||||||
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
final CompilationUnitParseResult parseResult = ParserUtil.parseCompilationUnit(reader);
|
||||||
|
|
||||||
// TODO: analysis
|
// TODO: analysis
|
||||||
@ -94,7 +90,7 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
|||||||
DefaultTranspilerConfiguration::new
|
DefaultTranspilerConfiguration::new
|
||||||
);
|
);
|
||||||
|
|
||||||
final var ownerComponentName = forClass.getSimpleName();
|
final var ownerComponentName = forClass != null ? forClass.getSimpleName() : "AnonymousComponent";
|
||||||
final var templateClassName = ownerComponentName + "Template";
|
final var templateClassName = ownerComponentName + "Template";
|
||||||
final var fqn = this.defaultPackageName + "." + templateClassName;
|
final var fqn = this.defaultPackageName + "." + templateClassName;
|
||||||
|
|
||||||
@ -205,4 +201,8 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
|||||||
return this.getFromCacheOrElse(forClass, () -> this.doCompile(forClass, reader));
|
return this.getFromCacheOrElse(forClass, () -> this.doCompile(forClass, reader));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentTemplate compileAnonymous(Reader reader) {
|
||||||
|
return this.doCompile(null, reader);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package groowt.view.web;
|
|
||||||
|
|
||||||
import groowt.view.component.ViewComponent;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface WebViewComponent extends ViewComponent {
|
|
||||||
List<WebViewChildRenderer> getChildren();
|
|
||||||
void setChildren(List<WebViewChildRenderer> children);
|
|
||||||
void renderChildren();
|
|
||||||
}
|
|
@ -189,7 +189,7 @@ public class DefaultAstBuilderVisitor extends WebViewComponentsParserBaseVisitor
|
|||||||
public Node visitFragmentComponent(WebViewComponentsParser.FragmentComponentContext ctx) {
|
public Node visitFragmentComponent(WebViewComponentsParser.FragmentComponentContext ctx) {
|
||||||
return this.nodeFactory.fragmentComponentNode(
|
return this.nodeFactory.fragmentComponentNode(
|
||||||
this.getTokenRange(ctx),
|
this.getTokenRange(ctx),
|
||||||
this.getSingleAs(ctx.body(), BodyNode.class)
|
this.getSingleAsNonNull(ctx.body(), BodyNode.class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ public class DefaultNodeFactory implements NodeFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, @Nullable BodyNode bodyNode) {
|
public FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, BodyNode bodyNode) {
|
||||||
return this.objectFactory.get(FragmentComponentNode.class, tokenRange, bodyNode);
|
return this.objectFactory.get(FragmentComponentNode.class, tokenRange, bodyNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ public interface NodeFactory {
|
|||||||
@Nullable BodyNode body
|
@Nullable BodyNode body
|
||||||
);
|
);
|
||||||
|
|
||||||
FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, @Nullable BodyNode bodyNode);
|
FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, BodyNode bodyNode);
|
||||||
|
|
||||||
ComponentArgsNode componentArgsNode(
|
ComponentArgsNode componentArgsNode(
|
||||||
TokenRange tokenRange,
|
TokenRange tokenRange,
|
||||||
|
@ -4,7 +4,9 @@ import groowt.util.di.annotation.Given;
|
|||||||
import groowt.view.web.ast.extension.NodeExtensionContainer;
|
import groowt.view.web.ast.extension.NodeExtensionContainer;
|
||||||
import groowt.view.web.util.TokenRange;
|
import groowt.view.web.util.TokenRange;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public non-sealed class FragmentComponentNode extends ComponentNode {
|
public non-sealed class FragmentComponentNode extends ComponentNode {
|
||||||
|
|
||||||
@ -12,9 +14,14 @@ public non-sealed class FragmentComponentNode extends ComponentNode {
|
|||||||
public FragmentComponentNode(
|
public FragmentComponentNode(
|
||||||
NodeExtensionContainer extensionContainer,
|
NodeExtensionContainer extensionContainer,
|
||||||
@Given TokenRange tokenRange,
|
@Given TokenRange tokenRange,
|
||||||
@Given @Nullable BodyNode body
|
@Given BodyNode body
|
||||||
) {
|
) {
|
||||||
super(tokenRange, extensionContainer, filterNulls(body), body);
|
super(tokenRange, extensionContainer, List.of(Objects.requireNonNull(body)), body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BodyNode getBody() {
|
||||||
|
return Objects.requireNonNull(super.getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package groowt.view.web.lib;
|
|
||||||
|
|
||||||
import groowt.view.web.DefaultWebViewComponent;
|
|
||||||
|
|
||||||
// TODO: anything special?
|
|
||||||
public class FragmentComponent extends DefaultWebViewComponent {
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,20 @@
|
|||||||
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
|
import groowt.view.web.ast.node.BodyChildNode;
|
||||||
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
|
import org.codehaus.groovy.ast.expr.Expression;
|
||||||
|
import org.codehaus.groovy.ast.stmt.Statement;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public interface AppendOrAddStatementFactory {
|
||||||
|
|
||||||
|
enum Action {
|
||||||
|
ADD, APPEND
|
||||||
|
}
|
||||||
|
|
||||||
|
Statement addOnly(BodyChildNode sourceNode, TranspilerState state, Expression rightSide);
|
||||||
|
Statement appendOnly(BodyChildNode sourceNode, TranspilerState state, Expression rightSide);
|
||||||
|
Statement addOrAppend(BodyChildNode sourceNode, TranspilerState state, Function<Action, Expression> getRightSide);
|
||||||
|
|
||||||
|
}
|
@ -1,25 +1,22 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
|
import groowt.view.web.ast.node.BodyChildNode;
|
||||||
import groowt.view.web.ast.node.BodyNode;
|
import groowt.view.web.ast.node.BodyNode;
|
||||||
import groowt.view.web.ast.node.Node;
|
|
||||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
import org.codehaus.groovy.ast.VariableScope;
|
|
||||||
import org.codehaus.groovy.ast.expr.Expression;
|
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;
|
import org.codehaus.groovy.ast.stmt.Statement;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface BodyTranspiler {
|
public interface BodyTranspiler {
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface ExpressionStatementConverter {
|
interface AddOrAppendCallback {
|
||||||
Statement createStatement(Node source, Expression expression);
|
Statement createStatement(BodyChildNode source, Expression expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockStatement transpileBody(
|
BlockStatement transpileBody(
|
||||||
BodyNode bodyNode,
|
BodyNode bodyNode,
|
||||||
ExpressionStatementConverter converter,
|
AddOrAppendCallback addOrAppendCallback,
|
||||||
TranspilerState state
|
TranspilerState state
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,10 +2,7 @@ package groowt.view.web.transpile;
|
|||||||
|
|
||||||
import groowt.view.web.ast.node.ComponentNode;
|
import groowt.view.web.ast.node.ComponentNode;
|
||||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
import org.codehaus.groovy.ast.Variable;
|
|
||||||
import org.codehaus.groovy.ast.expr.VariableExpression;
|
|
||||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||||
import org.codehaus.groovy.ast.stmt.Statement;
|
|
||||||
|
|
||||||
public interface ComponentTranspiler {
|
public interface ComponentTranspiler {
|
||||||
BlockStatement createComponentStatements(
|
BlockStatement createComponentStatements(
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
|
import groovy.lang.Tuple2;
|
||||||
|
import groowt.view.web.ast.NodeUtil;
|
||||||
|
import groowt.view.web.ast.node.BodyChildNode;
|
||||||
|
import groowt.view.web.ast.node.ComponentNode;
|
||||||
|
import groowt.view.web.ast.node.GStringBodyTextNode;
|
||||||
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement addOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
|
||||||
|
return this.doCreate(
|
||||||
|
bodyChildNode,
|
||||||
|
rightSide,
|
||||||
|
new VariableExpression(state.getCurrentChildCollector()),
|
||||||
|
TranspilerUtil.ADD,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement appendOnly(BodyChildNode bodyChildNode, TranspilerState state, Expression rightSide) {
|
||||||
|
return this.doCreate(
|
||||||
|
bodyChildNode,
|
||||||
|
rightSide,
|
||||||
|
new VariableExpression(state.out()),
|
||||||
|
TranspilerUtil.APPEND,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement addOrAppend(
|
||||||
|
BodyChildNode bodyChildNode,
|
||||||
|
TranspilerState state,
|
||||||
|
Function<Action, Expression> getRightSide
|
||||||
|
) {
|
||||||
|
if (state.hasCurrentChildCollector()) {
|
||||||
|
return this.addOnly(bodyChildNode, state, getRightSide.apply(Action.ADD));
|
||||||
|
} else {
|
||||||
|
return this.appendOnly(bodyChildNode, state, getRightSide.apply(Action.APPEND));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,15 +3,9 @@ package groowt.view.web.transpile;
|
|||||||
import groowt.view.web.ast.node.*;
|
import groowt.view.web.ast.node.*;
|
||||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
import org.codehaus.groovy.ast.expr.GStringExpression;
|
import org.codehaus.groovy.ast.expr.GStringExpression;
|
||||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||||
import org.codehaus.groovy.ast.stmt.Statement;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class DefaultBodyTranspiler implements BodyTranspiler {
|
public class DefaultBodyTranspiler implements BodyTranspiler {
|
||||||
|
|
||||||
private final GStringTranspiler gStringTranspiler;
|
private final GStringTranspiler gStringTranspiler;
|
||||||
@ -32,7 +26,7 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
|||||||
@Override
|
@Override
|
||||||
public BlockStatement transpileBody(
|
public BlockStatement transpileBody(
|
||||||
BodyNode bodyNode,
|
BodyNode bodyNode,
|
||||||
ExpressionStatementConverter converter,
|
AddOrAppendCallback addOrAppendCallback,
|
||||||
TranspilerState state
|
TranspilerState state
|
||||||
) {
|
) {
|
||||||
final BlockStatement block = new BlockStatement();
|
final BlockStatement block = new BlockStatement();
|
||||||
@ -43,22 +37,19 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
|||||||
final GStringExpression gString = this.gStringTranspiler.createGStringExpression(
|
final GStringExpression gString = this.gStringTranspiler.createGStringExpression(
|
||||||
gStringBodyTextNode
|
gStringBodyTextNode
|
||||||
);
|
);
|
||||||
block.addStatement(converter.createStatement(gStringBodyTextNode, gString));
|
block.addStatement(addOrAppendCallback.createStatement(gStringBodyTextNode, gString));
|
||||||
}
|
}
|
||||||
case JStringBodyTextNode jStringBodyTextNode -> {
|
case JStringBodyTextNode jStringBodyTextNode -> {
|
||||||
block.addStatement(
|
block.addStatement(
|
||||||
converter.createStatement(
|
addOrAppendCallback.createStatement(
|
||||||
jStringBodyTextNode,
|
jStringBodyTextNode,
|
||||||
this.jStringTranspiler.createStringLiteral(jStringBodyTextNode)
|
this.jStringTranspiler.createStringLiteral(jStringBodyTextNode)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case ComponentNode componentNode -> {
|
case ComponentNode componentNode -> {
|
||||||
final BlockStatement componentBlock = this.componentTranspiler.createComponentStatements(
|
// DO NOT add/append this, because the component transpiler does it already
|
||||||
componentNode,
|
block.addStatement(this.componentTranspiler.createComponentStatements(componentNode, state));
|
||||||
state
|
|
||||||
);
|
|
||||||
block.addStatement(componentBlock);
|
|
||||||
}
|
}
|
||||||
case PlainScriptletNode plainScriptletNode -> {
|
case PlainScriptletNode plainScriptletNode -> {
|
||||||
throw new UnsupportedOperationException("TODO");
|
throw new UnsupportedOperationException("TODO");
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package groowt.view.web.transpile;
|
package groowt.view.web.transpile;
|
||||||
|
|
||||||
|
import groovy.lang.Tuple2;
|
||||||
import groowt.view.component.*;
|
import groowt.view.component.*;
|
||||||
import groowt.view.web.ast.node.*;
|
import groowt.view.web.ast.node.*;
|
||||||
import groowt.view.web.lib.FragmentComponent;
|
|
||||||
import groowt.view.web.runtime.WebViewComponentChildCollector;
|
import groowt.view.web.runtime.WebViewComponentChildCollector;
|
||||||
import groowt.view.web.transpile.TranspilerUtil.TranspilerState;
|
|
||||||
import groowt.view.web.transpile.util.GroovyUtil;
|
import groowt.view.web.transpile.util.GroovyUtil;
|
||||||
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
import groowt.view.web.transpile.util.GroovyUtil.ConvertResult;
|
||||||
import org.codehaus.groovy.ast.*;
|
import org.codehaus.groovy.ast.*;
|
||||||
@ -16,9 +15,9 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static groowt.view.web.transpile.TranspilerUtil.lineAndColumn;
|
import static groowt.view.web.transpile.TranspilerUtil.*;
|
||||||
import static groowt.view.web.transpile.TranspilerUtil.makeStringLiteral;
|
|
||||||
|
|
||||||
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||||
|
|
||||||
@ -33,16 +32,19 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
private static final ClassNode MISSING_COMPONENT_EXCEPTION = ClassHelper.make(MissingComponentException.class);
|
private static final ClassNode MISSING_COMPONENT_EXCEPTION = ClassHelper.make(MissingComponentException.class);
|
||||||
private static final ClassNode MISSING_CLASS_TYPE_EXCEPTION = ClassHelper.make(MissingClassTypeException.class);
|
private static final ClassNode MISSING_CLASS_TYPE_EXCEPTION = ClassHelper.make(MissingClassTypeException.class);
|
||||||
private static final ClassNode MISSING_STRING_TYPE_EXCEPTION = ClassHelper.make(MissingStringTypeException.class);
|
private static final ClassNode MISSING_STRING_TYPE_EXCEPTION = ClassHelper.make(MissingStringTypeException.class);
|
||||||
private static final ClassNode MISSING_FRAGMENT_TYPE_EXCEPTION = ClassHelper.make(MissingFragmentTypeException.class);
|
private static final ClassNode MISSING_FRAGMENT_TYPE_EXCEPTION =
|
||||||
|
ClassHelper.make(MissingFragmentTypeException.class);
|
||||||
|
|
||||||
private static final String CREATE = "create";
|
private static final String CREATE = "create";
|
||||||
|
private static final String CREATE_FRAGMENT = "createFragment";
|
||||||
private static final String RESOLVE = "resolve";
|
private static final String RESOLVE = "resolve";
|
||||||
private static final String ADD = "add";
|
private static final String ADD = "add";
|
||||||
private static final String APPEND = "append";
|
private static final String APPEND = "append";
|
||||||
private static final String FRAGMENT_FQN = FragmentComponent.class.getCanonicalName();
|
private static final String FRAGMENT_FQN = GROOWT_VIEW_WEB + ".lib.Fragment";
|
||||||
|
|
||||||
private ValueNodeTranspiler valueNodeTranspiler;
|
private ValueNodeTranspiler valueNodeTranspiler;
|
||||||
private BodyTranspiler bodyTranspiler;
|
private BodyTranspiler bodyTranspiler;
|
||||||
|
private AppendOrAddStatementFactory appendOrAddStatementFactory;
|
||||||
|
|
||||||
public void setValueNodeTranspiler(ValueNodeTranspiler valueNodeTranspiler) {
|
public void setValueNodeTranspiler(ValueNodeTranspiler valueNodeTranspiler) {
|
||||||
this.valueNodeTranspiler = valueNodeTranspiler;
|
this.valueNodeTranspiler = valueNodeTranspiler;
|
||||||
@ -52,6 +54,10 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
this.bodyTranspiler = bodyTranspiler;
|
this.bodyTranspiler = bodyTranspiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAppendOrAddStatementFactory(AppendOrAddStatementFactory appendOrAddStatementFactory) {
|
||||||
|
this.appendOrAddStatementFactory = Objects.requireNonNull(appendOrAddStatementFactory);
|
||||||
|
}
|
||||||
|
|
||||||
// ViewComponent c0
|
// ViewComponent c0
|
||||||
protected ExpressionStatement getComponentDeclaration(Variable component) {
|
protected ExpressionStatement getComponentDeclaration(Variable component) {
|
||||||
final var componentDeclaration = new DeclarationExpression(
|
final var componentDeclaration = new DeclarationExpression(
|
||||||
@ -82,9 +88,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// key: value
|
// key: value
|
||||||
protected MapEntryExpression getAttrExpression(
|
protected MapEntryExpression getAttrExpression(AttrNode attrNode, TranspilerState state) {
|
||||||
AttrNode attrNode, TranspilerState state
|
|
||||||
) {
|
|
||||||
final var keyExpr = makeStringLiteral(attrNode.getKeyNode().getKey());
|
final var keyExpr = makeStringLiteral(attrNode.getKeyNode().getKey());
|
||||||
final Expression valueExpr = switch (attrNode) {
|
final Expression valueExpr = switch (attrNode) {
|
||||||
case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE;
|
case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE;
|
||||||
@ -129,13 +133,13 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
args.addExpression(lineAndColumn.getV2());
|
args.addExpression(lineAndColumn.getV2());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MethodCallExpression getOutCall(Node sourceNode, TranspilerState state, Expression toOutput) {
|
protected MethodCallExpression getOutCall(BodyChildNode sourceNode, TranspilerState state, Expression toOutput) {
|
||||||
final VariableExpression outVariableExpr = new VariableExpression(state.out());
|
final VariableExpression outVariableExpr = new VariableExpression(state.out());
|
||||||
final ArgumentListExpression args = new ArgumentListExpression();
|
final ArgumentListExpression args = new ArgumentListExpression();
|
||||||
args.addExpression(toOutput);
|
args.addExpression(toOutput);
|
||||||
switch (sourceNode) {
|
switch (sourceNode) {
|
||||||
case GStringBodyTextNode ignored -> this.addLineAndColumn(sourceNode, args);
|
case GStringBodyTextNode ignored -> this.addLineAndColumn(sourceNode.asNode(), args);
|
||||||
case ComponentNode ignored -> this.addLineAndColumn(sourceNode, args);
|
case ComponentNode ignored -> this.addLineAndColumn(sourceNode.asNode(), args);
|
||||||
default -> {
|
default -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,7 +147,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// { out << jString | gString | component }
|
// { out << jString | gString | component }
|
||||||
protected ClosureExpression getOutClosure(Node sourceNode, TranspilerState state, Expression toRender) {
|
protected ClosureExpression getOutClosure(BodyChildNode sourceNode, TranspilerState state, Expression toRender) {
|
||||||
if (toRender instanceof VariableExpression variableExpression) {
|
if (toRender instanceof VariableExpression variableExpression) {
|
||||||
variableExpression.setClosureSharedVariable(true);
|
variableExpression.setClosureSharedVariable(true);
|
||||||
}
|
}
|
||||||
@ -153,7 +157,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
|
|
||||||
// c0_childCollector.add (jString | gString | component) { out << ... }
|
// c0_childCollector.add (jString | gString | component) { out << ... }
|
||||||
protected Statement getChildCollectorAdd(
|
protected Statement getChildCollectorAdd(
|
||||||
Node sourceNode,
|
BodyChildNode sourceNode,
|
||||||
TranspilerState state,
|
TranspilerState state,
|
||||||
Variable childCollector,
|
Variable childCollector,
|
||||||
Expression toAdd
|
Expression toAdd
|
||||||
@ -168,8 +172,15 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
return new ExpressionStatement(methodCall);
|
return new ExpressionStatement(methodCall);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Tuple containing 1. body ClosureExpression, and 2. childCollector Variable
|
||||||
|
*/
|
||||||
// { WebViewComponentChildCollector c0_childCollector -> ... }
|
// { WebViewComponentChildCollector c0_childCollector -> ... }
|
||||||
protected ClosureExpression getBodyClosure(BodyNode bodyNode, TranspilerState state, String componentVariableName) {
|
protected Tuple2<ClosureExpression, Variable> getBodyClosure(
|
||||||
|
BodyNode bodyNode,
|
||||||
|
TranspilerState state,
|
||||||
|
String componentVariableName
|
||||||
|
) {
|
||||||
final Parameter childCollectorParam = new Parameter(
|
final Parameter childCollectorParam = new Parameter(
|
||||||
CHILD_COLLECTOR,
|
CHILD_COLLECTOR,
|
||||||
componentVariableName + "_childCollector"
|
componentVariableName + "_childCollector"
|
||||||
@ -177,26 +188,40 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
|
|
||||||
final var scope = state.pushScope();
|
final var scope = state.pushScope();
|
||||||
scope.putDeclaredVariable(childCollectorParam);
|
scope.putDeclaredVariable(childCollectorParam);
|
||||||
|
state.pushChildCollector(childCollectorParam);
|
||||||
final BlockStatement bodyStatements = this.bodyTranspiler.transpileBody(
|
final BlockStatement bodyStatements = this.bodyTranspiler.transpileBody(
|
||||||
bodyNode,
|
bodyNode,
|
||||||
(sourceNode, expr) -> this.getChildCollectorAdd(sourceNode, state, childCollectorParam, expr),
|
(sourceNode, expr) -> this.getChildCollectorAdd(sourceNode, state, childCollectorParam, expr),
|
||||||
state
|
state
|
||||||
);
|
);
|
||||||
|
state.popChildCollector();
|
||||||
state.popScope();
|
state.popScope();
|
||||||
|
|
||||||
return new ClosureExpression(new Parameter[]{childCollectorParam}, bodyStatements);
|
final ClosureExpression bodyClosure = new ClosureExpression(
|
||||||
|
new Parameter[] { childCollectorParam },
|
||||||
|
bodyStatements
|
||||||
|
);
|
||||||
|
return new Tuple2<>(bodyClosure, childCollectorParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Tuple containing 1. create Expression,
|
||||||
|
* and 2. childCollector Variable, possibly {@code null}.
|
||||||
|
*/
|
||||||
// context.create(...) {...}
|
// context.create(...) {...}
|
||||||
protected MethodCallExpression getCreateExpression(
|
protected Tuple2<MethodCallExpression, @Nullable Variable> getCreateExpression(
|
||||||
ComponentNode componentNode, TranspilerState state, String componentVariableName
|
ComponentNode componentNode,
|
||||||
|
TranspilerState state,
|
||||||
|
String componentVariableName
|
||||||
) {
|
) {
|
||||||
final var createArgs = new ArgumentListExpression();
|
final var createArgs = new ArgumentListExpression();
|
||||||
|
final String createName;
|
||||||
final var contextResolve = this.getContextResolveExpr(componentNode, state.context());
|
Variable childCollector = null;
|
||||||
createArgs.addExpression(contextResolve);
|
|
||||||
|
|
||||||
if (componentNode instanceof TypedComponentNode typedComponentNode) {
|
if (componentNode instanceof TypedComponentNode typedComponentNode) {
|
||||||
|
createName = CREATE;
|
||||||
|
final var contextResolve = this.getContextResolveExpr(componentNode, state.context());
|
||||||
|
createArgs.addExpression(contextResolve);
|
||||||
|
|
||||||
final List<AttrNode> attributeNodes = typedComponentNode.getArgs().getAttributes();
|
final List<AttrNode> attributeNodes = typedComponentNode.getArgs().getAttributes();
|
||||||
if (!attributeNodes.isEmpty()) {
|
if (!attributeNodes.isEmpty()) {
|
||||||
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
||||||
@ -205,35 +230,62 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
if (constructorNode != null) {
|
if (constructorNode != null) {
|
||||||
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
|
this.getConstructorArgs(constructorNode).forEach(createArgs::addExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
||||||
|
if (bodyNode != null) {
|
||||||
|
final var bodyResult = this.getBodyClosure(bodyNode, state, componentVariableName);
|
||||||
|
childCollector = bodyResult.getV2();
|
||||||
|
createArgs.addExpression(bodyResult.getV1());
|
||||||
|
}
|
||||||
|
} else if (componentNode instanceof FragmentComponentNode fragmentComponentNode) {
|
||||||
|
createName = CREATE_FRAGMENT;
|
||||||
|
final BodyNode bodyNode = Objects.requireNonNull(
|
||||||
|
fragmentComponentNode.getBody(),
|
||||||
|
"FragmentComponentNode cannot have a null body."
|
||||||
|
);
|
||||||
|
final var bodyResult = this.getBodyClosure(bodyNode, state, componentVariableName);
|
||||||
|
childCollector = bodyResult.getV2();
|
||||||
|
createArgs.addExpression(bodyResult.getV1());
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported ComponentNode type: " + componentNode.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
final @Nullable BodyNode bodyNode = componentNode.getBody();
|
final var createCall = new MethodCallExpression(
|
||||||
if (bodyNode != null) {
|
new VariableExpression(state.context()),
|
||||||
createArgs.addExpression(this.getBodyClosure(bodyNode, state, componentVariableName));
|
createName,
|
||||||
}
|
createArgs
|
||||||
|
);
|
||||||
|
|
||||||
return new MethodCallExpression(new VariableExpression(state.context()), CREATE, createArgs);
|
return new Tuple2<>(createCall, childCollector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Tuple containing 1. assignment ExpressionStatement,
|
||||||
|
* and 2. childCollector Variable, possibly {@code null}.
|
||||||
|
*/
|
||||||
// c0 = context.create(context.resolve(''), [:], ...) {...}
|
// c0 = context.create(context.resolve(''), [:], ...) {...}
|
||||||
protected ExpressionStatement getCreateAssignStatement(
|
protected Tuple2<ExpressionStatement, @Nullable Variable> getCreateAssignStatement(
|
||||||
ComponentNode componentNode, TranspilerState state, String componentVariableName
|
ComponentNode componentNode,
|
||||||
|
TranspilerState state,
|
||||||
|
String componentVariableName,
|
||||||
|
Variable component
|
||||||
) {
|
) {
|
||||||
final var componentAssignLeft = new VariableExpression(state.getDeclaredVariable(componentVariableName));
|
final var componentAssignLeft = new VariableExpression(component);
|
||||||
final var createExpr = this.getCreateExpression(componentNode, state, componentVariableName);
|
final var createExprResult = this.getCreateExpression(componentNode, state, componentVariableName);
|
||||||
final var componentAssignExpr = new BinaryExpression(
|
final var componentAssignExpr = new BinaryExpression(
|
||||||
componentAssignLeft,
|
componentAssignLeft,
|
||||||
new Token(Types.ASSIGN, "=", -1, -1),
|
new Token(Types.ASSIGN, "=", -1, -1),
|
||||||
createExpr
|
createExprResult.getV1()
|
||||||
);
|
);
|
||||||
return new ExpressionStatement(componentAssignExpr);
|
return new Tuple2<>(new ExpressionStatement(componentAssignExpr), createExprResult.getV2());
|
||||||
}
|
}
|
||||||
|
|
||||||
// catch (NoFactoryMissingException c0nfme) {
|
// catch (NoFactoryMissingException c0nfme) {
|
||||||
// throw new MissingClassComponentException(this, 'ComponentType', c0nfme)
|
// throw new MissingClassComponentException(this, 'ComponentType', c0nfme)
|
||||||
// }
|
// }
|
||||||
protected CatchStatement getNoMissingFactoryExceptionCatch(
|
protected CatchStatement getNoMissingFactoryExceptionCatch(
|
||||||
ComponentNode componentNode, String componentVariableName
|
ComponentNode componentNode,
|
||||||
|
String componentVariableName
|
||||||
) {
|
) {
|
||||||
final String exceptionName = componentVariableName + "nfme";
|
final String exceptionName = componentVariableName + "nfme";
|
||||||
final Parameter fmeParam = new Parameter(NO_FACTORY_MISSING_EXCEPTION, exceptionName);
|
final Parameter fmeParam = new Parameter(NO_FACTORY_MISSING_EXCEPTION, exceptionName);
|
||||||
@ -281,7 +333,10 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// catch (Exception c0ce) { throw new ComponentCreateException(c0ce) }
|
// catch (Exception c0ce) { throw new ComponentCreateException(c0ce) }
|
||||||
protected CatchStatement getGeneralCreateExceptionCatch(ComponentNode componentNode, String componentVariableName) {
|
protected CatchStatement getGeneralCreateExceptionCatch(
|
||||||
|
ComponentNode componentNode,
|
||||||
|
String componentVariableName
|
||||||
|
) {
|
||||||
final String exceptionName = componentVariableName + "ce";
|
final String exceptionName = componentVariableName + "ce";
|
||||||
final Parameter exceptionParam = new Parameter(EXCEPTION, exceptionName);
|
final Parameter exceptionParam = new Parameter(EXCEPTION, exceptionName);
|
||||||
final VariableExpression exceptionVar = new VariableExpression(exceptionName);
|
final VariableExpression exceptionVar = new VariableExpression(exceptionName);
|
||||||
@ -317,53 +372,68 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
|||||||
return new ExpressionStatement(setContext);
|
return new ExpressionStatement(setContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Statement createComponentOutCall(
|
|
||||||
ComponentNode componentNode,
|
|
||||||
TranspilerState state,
|
|
||||||
Variable component
|
|
||||||
) {
|
|
||||||
// out << c0
|
|
||||||
final VariableExpression toOutput = new VariableExpression(component);
|
|
||||||
final Expression outCallExpr = this.getOutCall(componentNode, state, toOutput);
|
|
||||||
return new ExpressionStatement(outCallExpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String getComponentVariableName(int componentNumber) {
|
protected String getComponentVariableName(int componentNumber) {
|
||||||
return "c" + componentNumber;
|
return "c" + componentNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BlockStatement createComponentStatements(
|
public BlockStatement createComponentStatements(ComponentNode componentNode, TranspilerState state) {
|
||||||
ComponentNode componentNode,
|
|
||||||
TranspilerState state
|
|
||||||
) {
|
|
||||||
final var componentVariableName = this.getComponentVariableName(state.newComponentNumber());
|
final var componentVariableName = this.getComponentVariableName(state.newComponentNumber());
|
||||||
final Variable component = new VariableExpression(componentVariableName, VIEW_COMPONENT);
|
final VariableExpression component = new VariableExpression(componentVariableName, VIEW_COMPONENT);
|
||||||
|
|
||||||
final BlockStatement result = new BlockStatement();
|
final BlockStatement result = new BlockStatement();
|
||||||
final VariableScope scope = state.pushScope();
|
final VariableScope scope = state.currentScope();
|
||||||
result.setVariableScope(scope);
|
result.setVariableScope(scope);
|
||||||
scope.putDeclaredVariable(component);
|
scope.putDeclaredVariable(component);
|
||||||
|
|
||||||
// ViewComponent c0;
|
// ViewComponent c0;
|
||||||
result.addStatement(this.getComponentDeclaration(component));
|
result.addStatement(this.getComponentDeclaration(component));
|
||||||
|
|
||||||
// try { context.create(...) } catch { ... }
|
// c0 = context.create(...) { ... }
|
||||||
final var tryCreateStatement = new TryCatchStatement(this.getCreateAssignStatement(
|
final var createAssignStatementResult = this.getCreateAssignStatement(
|
||||||
componentNode,
|
componentNode,
|
||||||
state,
|
state,
|
||||||
componentVariableName
|
componentVariableName,
|
||||||
), EmptyStatement.INSTANCE);
|
component
|
||||||
|
);
|
||||||
|
|
||||||
|
// try { ... } catch { ... }
|
||||||
|
final var tryCreateStatement = new TryCatchStatement(
|
||||||
|
createAssignStatementResult.getV1(),
|
||||||
|
EmptyStatement.INSTANCE
|
||||||
|
);
|
||||||
this.getCreateCatches(componentNode, componentVariableName).forEach(tryCreateStatement::addCatch);
|
this.getCreateCatches(componentNode, componentVariableName).forEach(tryCreateStatement::addCatch);
|
||||||
result.addStatement(tryCreateStatement);
|
result.addStatement(tryCreateStatement);
|
||||||
|
|
||||||
// component.setContext(context)
|
// component.setContext(context)
|
||||||
result.addStatement(this.createSetContext(state, component));
|
result.addStatement(this.createSetContext(state, component));
|
||||||
|
|
||||||
// out << component
|
// out or collect
|
||||||
result.addStatement(this.createComponentOutCall(componentNode, state, component));
|
final var addOrAppend = this.appendOrAddStatementFactory.addOrAppend(
|
||||||
|
componentNode,
|
||||||
state.popScope();
|
state,
|
||||||
|
action -> switch (action) {
|
||||||
|
case ADD -> {
|
||||||
|
final var args = new ArgumentListExpression();
|
||||||
|
args.addExpression(component);
|
||||||
|
final var outComponent = new VariableExpression(component);
|
||||||
|
outComponent.setClosureSharedVariable(true);
|
||||||
|
final Statement renderStatement = this.appendOrAddStatementFactory.appendOnly(
|
||||||
|
componentNode,
|
||||||
|
state,
|
||||||
|
outComponent
|
||||||
|
);
|
||||||
|
final ClosureExpression renderArg = new ClosureExpression(
|
||||||
|
Parameter.EMPTY_ARRAY,
|
||||||
|
renderStatement
|
||||||
|
);
|
||||||
|
args.addExpression(renderArg);
|
||||||
|
yield args;
|
||||||
|
}
|
||||||
|
case APPEND -> new VariableExpression(component);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
result.addStatement(addOrAppend);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,6 @@ import groowt.view.web.transpile.util.GroovyUtil;
|
|||||||
import groowt.view.web.util.FilteringIterable;
|
import groowt.view.web.util.FilteringIterable;
|
||||||
import groowt.view.web.util.Option;
|
import groowt.view.web.util.Option;
|
||||||
import groowt.view.web.util.TokenRange;
|
import groowt.view.web.util.TokenRange;
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
import org.antlr.v4.runtime.Token;
|
import org.antlr.v4.runtime.Token;
|
||||||
import org.codehaus.groovy.ast.expr.*;
|
import org.codehaus.groovy.ast.expr.*;
|
||||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||||
@ -26,13 +24,11 @@ import java.util.List;
|
|||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class DefaultGStringTranspiler implements GStringTranspiler {
|
public class DefaultGStringTranspiler implements GStringTranspiler {
|
||||||
|
|
||||||
private final PositionSetter positionSetter;
|
private final PositionSetter positionSetter;
|
||||||
private final JStringTranspiler jStringTranspiler;
|
private final JStringTranspiler jStringTranspiler;
|
||||||
|
|
||||||
@Inject
|
|
||||||
public DefaultGStringTranspiler(PositionSetter positionSetter, JStringTranspiler jStringTranspiler) {
|
public DefaultGStringTranspiler(PositionSetter positionSetter, JStringTranspiler jStringTranspiler) {
|
||||||
this.positionSetter = positionSetter;
|
this.positionSetter = positionSetter;
|
||||||
this.jStringTranspiler = jStringTranspiler;
|
this.jStringTranspiler = jStringTranspiler;
|
||||||
|
@ -210,12 +210,17 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
|
|
||||||
final BodyNode bodyNode = compilationUnitNode.getBodyNode();
|
final BodyNode bodyNode = compilationUnitNode.getBodyNode();
|
||||||
if (bodyNode != null) {
|
if (bodyNode != null) {
|
||||||
final var outStatementFactory = configuration.getOutStatementFactory();
|
final var appendOrAddStatementFactory = configuration.getAppendOrAddStatementFactory();
|
||||||
renderBlock.addStatement(
|
renderBlock.addStatement(
|
||||||
configuration.getBodyTranspiler()
|
configuration.getBodyTranspiler()
|
||||||
.transpileBody(
|
.transpileBody(
|
||||||
compilationUnitNode.getBodyNode(),
|
compilationUnitNode.getBodyNode(),
|
||||||
(ignored, expr) -> outStatementFactory.create(expr),
|
(source, expr) -> appendOrAddStatementFactory.addOrAppend(source, state, action -> {
|
||||||
|
if (action == AppendOrAddStatementFactory.Action.ADD) {
|
||||||
|
throw new IllegalStateException("Should not be adding here!");
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}),
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -223,8 +228,8 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
|||||||
|
|
||||||
final ClosureExpression renderer = new ClosureExpression(
|
final ClosureExpression renderer = new ClosureExpression(
|
||||||
new Parameter[] {
|
new Parameter[] {
|
||||||
(Parameter) state.getDeclaredVariable(CONTEXT),
|
(Parameter) state.context(),
|
||||||
(Parameter) state.getDeclaredVariable(OUT)
|
(Parameter) state.out()
|
||||||
},
|
},
|
||||||
renderBlock
|
renderBlock
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import jakarta.inject.Inject;
|
|||||||
|
|
||||||
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||||
|
|
||||||
private final OutStatementFactory outStatementFactory = new SimpleOutStatementFactory();
|
private final AppendOrAddStatementFactory appendOrAddStatementFactory = new DefaultAppendOrAddStatementFactory();
|
||||||
private final BodyTranspiler bodyTranspiler;
|
private final BodyTranspiler bodyTranspiler;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ -13,10 +13,13 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
|||||||
final var jStringTranspiler = new DefaultJStringTranspiler(positionSetter);
|
final var jStringTranspiler = new DefaultJStringTranspiler(positionSetter);
|
||||||
final var gStringTranspiler = new DefaultGStringTranspiler(positionSetter, jStringTranspiler);
|
final var gStringTranspiler = new DefaultGStringTranspiler(positionSetter, jStringTranspiler);
|
||||||
final var componentTranspiler = new DefaultComponentTranspiler();
|
final var componentTranspiler = new DefaultComponentTranspiler();
|
||||||
this.bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
|
|
||||||
componentTranspiler.setBodyTranspiler(this.bodyTranspiler);
|
|
||||||
final var valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
|
final var valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
|
||||||
|
|
||||||
|
this.bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
|
||||||
|
|
||||||
|
componentTranspiler.setBodyTranspiler(this.bodyTranspiler);
|
||||||
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
||||||
|
componentTranspiler.setAppendOrAddStatementFactory(this.appendOrAddStatementFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -25,8 +28,8 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OutStatementFactory getOutStatementFactory() {
|
public AppendOrAddStatementFactory getAppendOrAddStatementFactory() {
|
||||||
return this.outStatementFactory;
|
return this.appendOrAddStatementFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package groowt.view.web.transpile;
|
|
||||||
|
|
||||||
import org.codehaus.groovy.ast.expr.Expression;
|
|
||||||
import org.codehaus.groovy.ast.stmt.Statement;
|
|
||||||
|
|
||||||
public interface OutStatementFactory {
|
|
||||||
Statement create(Expression rightSide);
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package groowt.view.web.transpile;
|
|
||||||
|
|
||||||
import groowt.view.web.transpile.util.GroovyUtil;
|
|
||||||
import org.codehaus.groovy.ast.expr.BinaryExpression;
|
|
||||||
import org.codehaus.groovy.ast.expr.Expression;
|
|
||||||
import org.codehaus.groovy.ast.expr.MethodCallExpression;
|
|
||||||
import org.codehaus.groovy.ast.expr.VariableExpression;
|
|
||||||
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
|
|
||||||
import org.codehaus.groovy.ast.stmt.Statement;
|
|
||||||
import org.codehaus.groovy.syntax.Types;
|
|
||||||
|
|
||||||
import static org.codehaus.groovy.syntax.Token.newSymbol;
|
|
||||||
|
|
||||||
public class SimpleOutStatementFactory implements OutStatementFactory {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Statement create(Expression rightSide) {
|
|
||||||
final VariableExpression out = new VariableExpression("out");
|
|
||||||
final MethodCallExpression methodCallExpression = new MethodCallExpression(
|
|
||||||
out,
|
|
||||||
"append",
|
|
||||||
rightSide
|
|
||||||
);
|
|
||||||
return new ExpressionStatement(methodCallExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -2,5 +2,5 @@ package groowt.view.web.transpile;
|
|||||||
|
|
||||||
public interface TranspilerConfiguration {
|
public interface TranspilerConfiguration {
|
||||||
BodyTranspiler getBodyTranspiler();
|
BodyTranspiler getBodyTranspiler();
|
||||||
OutStatementFactory getOutStatementFactory();
|
AppendOrAddStatementFactory getAppendOrAddStatementFactory();
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ public final class TranspilerUtil {
|
|||||||
public static final String OUT = "out";
|
public static final String OUT = "out";
|
||||||
public static final String CONTEXT = "context";
|
public static final String CONTEXT = "context";
|
||||||
public static final String GET_RENDERER = "getRenderer";
|
public static final String GET_RENDERER = "getRenderer";
|
||||||
|
public static final String APPEND = "append";
|
||||||
|
public static final String ADD = "add";
|
||||||
|
|
||||||
public static Tuple2<ConstantExpression, ConstantExpression> lineAndColumn(SourcePosition sourcePosition) {
|
public static Tuple2<ConstantExpression, ConstantExpression> lineAndColumn(SourcePosition sourcePosition) {
|
||||||
return new Tuple2<>(
|
return new Tuple2<>(
|
||||||
@ -58,6 +60,7 @@ public final class TranspilerUtil {
|
|||||||
|
|
||||||
private final AtomicInteger componentCounter = new AtomicInteger();
|
private final AtomicInteger componentCounter = new AtomicInteger();
|
||||||
private final Deque<VariableScope> scopeStack = new LinkedList<>();
|
private final Deque<VariableScope> scopeStack = new LinkedList<>();
|
||||||
|
private final Deque<Variable> childCollectorStack = new LinkedList<>();
|
||||||
|
|
||||||
private TranspilerState(VariableScope rootScope) {
|
private TranspilerState(VariableScope rootScope) {
|
||||||
this.scopeStack.push(rootScope);
|
this.scopeStack.push(rootScope);
|
||||||
@ -103,6 +106,22 @@ public final class TranspilerUtil {
|
|||||||
throw new NullPointerException("Cannot find variable: " + name);
|
throw new NullPointerException("Cannot find variable: " + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void popChildCollector() {
|
||||||
|
this.childCollectorStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushChildCollector(Variable childCollector) {
|
||||||
|
this.childCollectorStack.push(childCollector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Variable getCurrentChildCollector() {
|
||||||
|
return Objects.requireNonNull(this.childCollectorStack.peek());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasCurrentChildCollector() {
|
||||||
|
return this.childCollectorStack.peek() != null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TranspilerUtil() {}
|
private TranspilerUtil() {}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<$typeName ${renderAttr()}${selfClose ? ' /' : ''}>${renderChildren()}${!selfClose ? "</$typeName>" : ''}
|
@ -0,0 +1,47 @@
|
|||||||
|
package groowt.view.web.lib
|
||||||
|
|
||||||
|
import groowt.view.component.ComponentContext
|
||||||
|
import groowt.view.component.ComponentFactory
|
||||||
|
import groowt.view.web.DefaultWebViewComponent
|
||||||
|
import groowt.view.web.DefaultWebViewComponentContext
|
||||||
|
import groowt.view.web.WebViewTemplateComponentSource
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class FragmentTests extends AbstractComponentTests {
|
||||||
|
|
||||||
|
static class Greeter extends DefaultWebViewComponent {
|
||||||
|
|
||||||
|
String greeting
|
||||||
|
|
||||||
|
Greeter(Map<String, Object> attr) {
|
||||||
|
super(WebViewTemplateComponentSource.of('$greeting'))
|
||||||
|
greeting = attr.greeting
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ComponentContext greeterContext = new DefaultWebViewComponentContext().tap {
|
||||||
|
pushDefaultScope()
|
||||||
|
def greeterFactory = ComponentFactory.ofClosure { type, componentContext, attr ->
|
||||||
|
new Greeter(attr)
|
||||||
|
}
|
||||||
|
currentScope.add('Greeter', greeterFactory)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void simple() {
|
||||||
|
this.doTest('<><Greeter greeting="Hello, World!" /></>', 'Hello, World!', this.greeterContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void multipleChildren() {
|
||||||
|
this.doTest(
|
||||||
|
'''
|
||||||
|
<>
|
||||||
|
<Greeter greeting='Hello, one!' /> <Greeter greeting='Hello, two!' />
|
||||||
|
</>
|
||||||
|
'''.stripIndent(), 'Hello, one! Hello, two!', this.greeterContext
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,21 +5,8 @@ import groowt.view.web.transpile.*;
|
|||||||
public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
|
public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BodyTranspiler getBodyTranspiler() {
|
protected TranspilerConfiguration getConfiguration() {
|
||||||
final var positionSetter = new SimplePositionSetter();
|
return new DefaultTranspilerConfiguration();
|
||||||
final var jStringTranspiler = new DefaultJStringTranspiler(positionSetter);
|
|
||||||
final var gStringTranspiler = new DefaultGStringTranspiler(positionSetter, jStringTranspiler);
|
|
||||||
final var componentTranspiler = new DefaultComponentTranspiler();
|
|
||||||
final var valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
|
|
||||||
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
|
||||||
final var bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
|
|
||||||
componentTranspiler.setBodyTranspiler(bodyTranspiler);
|
|
||||||
return bodyTranspiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected OutStatementFactory getOutStatementFactory() {
|
|
||||||
return new SimpleOutStatementFactory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -100,11 +100,6 @@ public abstract class NodeFactoryTests {
|
|||||||
assertNotNull(this.nodeFactory.fragmentComponentNode(this.getTokenRange(), bodyNode));
|
assertNotNull(this.nodeFactory.fragmentComponentNode(this.getTokenRange(), bodyNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fragmentComponentNodeBodyNull() {
|
|
||||||
assertNotNull(this.nodeFactory.fragmentComponentNode(this.getTokenRange(), null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void componentArgsNodeWithClassComponentType(
|
public void componentArgsNodeWithClassComponentType(
|
||||||
@Mock ClassComponentTypeNode componentTypeNode,
|
@Mock ClassComponentTypeNode componentTypeNode,
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package groowt.view.web.lib;
|
||||||
|
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groowt.view.component.ComponentContext;
|
||||||
|
import groowt.view.component.ComponentTemplate;
|
||||||
|
import groowt.view.web.DefaultWebComponentTemplateCompiler;
|
||||||
|
import groowt.view.web.DefaultWebViewComponentContext;
|
||||||
|
import groowt.view.web.runtime.WebViewComponentWriter;
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public abstract class AbstractComponentTests {
|
||||||
|
|
||||||
|
protected void doTest(Reader source, String expected, ComponentContext context) {
|
||||||
|
final var compiler = new DefaultWebComponentTemplateCompiler(
|
||||||
|
CompilerConfiguration.DEFAULT, this.getClass().getPackageName()
|
||||||
|
);
|
||||||
|
final ComponentTemplate template = compiler.compileAnonymous(source);
|
||||||
|
final Closure<?> renderer = template.getRenderer();
|
||||||
|
final StringWriter sw = new StringWriter();
|
||||||
|
final WebViewComponentWriter out = new WebViewComponentWriter(sw);
|
||||||
|
renderer.call(context, out);
|
||||||
|
assertEquals(expected, sw.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doTest(String source, String expected, ComponentContext context) {
|
||||||
|
this.doTest(new StringReader(source), expected, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doTest(String source, String expected) {
|
||||||
|
final var context = new DefaultWebViewComponentContext();
|
||||||
|
context.pushDefaultScope();
|
||||||
|
this.doTest(source, expected, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -7,7 +7,7 @@ import groowt.view.web.ast.DefaultNodeFactory;
|
|||||||
import groowt.view.web.ast.node.BodyNode;
|
import groowt.view.web.ast.node.BodyNode;
|
||||||
import groowt.view.web.ast.node.CompilationUnitNode;
|
import groowt.view.web.ast.node.CompilationUnitNode;
|
||||||
import groowt.view.web.transpile.BodyTranspiler;
|
import groowt.view.web.transpile.BodyTranspiler;
|
||||||
import groowt.view.web.transpile.OutStatementFactory;
|
import groowt.view.web.transpile.TranspilerConfiguration;
|
||||||
import groowt.view.web.transpile.TranspilerUtil;
|
import groowt.view.web.transpile.TranspilerUtil;
|
||||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||||
import org.codehaus.groovy.ast.expr.MethodCallExpression;
|
import org.codehaus.groovy.ast.expr.MethodCallExpression;
|
||||||
@ -20,8 +20,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
|
|
||||||
public abstract class BodyTranspilerTests {
|
public abstract class BodyTranspilerTests {
|
||||||
|
|
||||||
protected abstract BodyTranspiler getBodyTranspiler();
|
protected abstract TranspilerConfiguration getConfiguration();
|
||||||
protected abstract OutStatementFactory getOutStatementFactory();
|
|
||||||
|
|
||||||
protected record BuildResult(BodyNode bodyNode, TokenList tokenList) {}
|
protected record BuildResult(BodyNode bodyNode, TokenList tokenList) {}
|
||||||
|
|
||||||
@ -37,10 +36,14 @@ public abstract class BodyTranspilerTests {
|
|||||||
return new BuildResult(bodyNode, tokenList);
|
return new BuildResult(bodyNode, tokenList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected BodyTranspiler getBodyTranspiler() {
|
||||||
|
return this.getConfiguration().getBodyTranspiler();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void smokeScreen() {
|
public void smokeScreen() {
|
||||||
assertDoesNotThrow(() -> {
|
assertDoesNotThrow(() -> {
|
||||||
getBodyTranspiler();
|
this.getBodyTranspiler();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,10 +52,11 @@ public abstract class BodyTranspilerTests {
|
|||||||
final var source = "Hello, $target!";
|
final var source = "Hello, $target!";
|
||||||
final var buildResult = this.build(source);
|
final var buildResult = this.build(source);
|
||||||
final var transpiler = this.getBodyTranspiler();
|
final var transpiler = this.getBodyTranspiler();
|
||||||
final var outStatementFactory = this.getOutStatementFactory();
|
final var state = TranspilerUtil.TranspilerState.withDefaultRootScope();
|
||||||
|
final var addOrAppend = this.getConfiguration().getAppendOrAddStatementFactory();
|
||||||
final BlockStatement blockStatement = transpiler.transpileBody(
|
final BlockStatement blockStatement = transpiler.transpileBody(
|
||||||
buildResult.bodyNode(),
|
buildResult.bodyNode(),
|
||||||
(ignored, expression) -> outStatementFactory.create(expression),
|
(node, expression) -> addOrAppend.addOrAppend(node, state, ignored -> expression),
|
||||||
TranspilerUtil.TranspilerState.withDefaultRootScope()
|
TranspilerUtil.TranspilerState.withDefaultRootScope()
|
||||||
);
|
);
|
||||||
assertEquals(1, blockStatement.getStatements().size());
|
assertEquals(1, blockStatement.getStatements().size());
|
||||||
@ -63,10 +67,11 @@ public abstract class BodyTranspilerTests {
|
|||||||
final var source = "Hello, World!";
|
final var source = "Hello, World!";
|
||||||
final var buildResult = this.build(source);
|
final var buildResult = this.build(source);
|
||||||
final var transpiler = this.getBodyTranspiler();
|
final var transpiler = this.getBodyTranspiler();
|
||||||
final var outStatementFactory = this.getOutStatementFactory();
|
final var state = TranspilerUtil.TranspilerState.withDefaultRootScope();
|
||||||
|
final var addOrAppend = this.getConfiguration().getAppendOrAddStatementFactory();
|
||||||
final BlockStatement blockStatement = transpiler.transpileBody(
|
final BlockStatement blockStatement = transpiler.transpileBody(
|
||||||
buildResult.bodyNode(),
|
buildResult.bodyNode(),
|
||||||
(ignored, expression) -> outStatementFactory.create(expression),
|
(node, expression) -> addOrAppend.addOrAppend(node, state, ignored -> expression),
|
||||||
TranspilerUtil.TranspilerState.withDefaultRootScope()
|
TranspilerUtil.TranspilerState.withDefaultRootScope()
|
||||||
);
|
);
|
||||||
assertEquals(1, blockStatement.getStatements().size());
|
assertEquals(1, blockStatement.getStatements().size());
|
||||||
|
Loading…
Reference in New Issue
Block a user