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
|
||||
implements ComponentFactory<T> {
|
||||
|
||||
private static final String DO_CREATE = "doCreate";
|
||||
private static final Class<?>[] EMPTY_CLASSES = new Class[0];
|
||||
protected final Map<Class<?>[], MetaMethod> cache = new HashMap<>();
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
protected MetaMethod findDoCreateMethod(Object[] allArgs) {
|
||||
return this.cache.computeIfAbsent(ComponentFactoryUtil.asTypes(allArgs), types ->
|
||||
ComponentFactoryUtil.findDoCreateMethod(this.getMetaClass(), types)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T findAndDoCreate(ComponentContext componentContext, Object[] args) {
|
||||
final Object[] contextsAndArgs = flatten(componentContext, args);
|
||||
final MetaMethod contextsAndArgsMethod = this.findDoCreateMethod(contextsAndArgs);
|
||||
if (contextsAndArgsMethod != null) {
|
||||
return (T) contextsAndArgsMethod.invoke(this, contextsAndArgs);
|
||||
protected T findAndDoCreate(Object type, ComponentContext componentContext, Object[] args) {
|
||||
final Object[] typeContextAndArgs = ComponentFactoryUtil.flatten(type, componentContext, args);
|
||||
final MetaMethod typeContextAndArgsMethod = this.findDoCreateMethod(typeContextAndArgs);
|
||||
if (typeContextAndArgsMethod != null) {
|
||||
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 };
|
||||
@ -67,15 +55,15 @@ public abstract class AbstractComponentFactory<T extends ViewComponent> extends
|
||||
}
|
||||
|
||||
throw new MissingMethodException(
|
||||
DO_CREATE,
|
||||
ComponentFactoryUtil.DO_CREATE,
|
||||
this.getClass(),
|
||||
args
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T create(ComponentContext componentContext, Object... args) {
|
||||
return this.findAndDoCreate(componentContext, args);
|
||||
public T create(Object type, ComponentContext componentContext, Object... args) {
|
||||
return this.findAndDoCreate(type, componentContext, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,10 +32,12 @@ public abstract class AbstractViewComponent implements ViewComponent {
|
||||
this.template = Objects.requireNonNull(template);
|
||||
}
|
||||
|
||||
protected void beforeRender() {}
|
||||
protected void beforeRender() {
|
||||
this.getContext().beforeComponentRender(this);
|
||||
}
|
||||
|
||||
protected void afterRender() {
|
||||
this.getContext().afterComponent(this);
|
||||
this.getContext().afterComponentRender(this);
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
@ApiStatus.Internal
|
||||
ComponentFactory<?> resolve(String component);
|
||||
interface Resolved {
|
||||
String getTypeName();
|
||||
ComponentFactory<?> getComponentFactory();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
ViewComponent create(ComponentFactory<?> factory, Object... args);
|
||||
Resolved resolve(String component);
|
||||
|
||||
@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();
|
||||
|
||||
|
@ -7,14 +7,14 @@ import java.util.function.Supplier;
|
||||
|
||||
public interface ComponentFactory<T extends ViewComponent> extends GroovyObject {
|
||||
|
||||
static <T extends ViewComponent> ComponentFactory<T> of(Closure<T> closure) {
|
||||
return new DelegatingComponentFactory<>((context, args) -> closure.call(context, args));
|
||||
static <T extends ViewComponent> ComponentFactory<T> ofClosure(Closure<T> closure) {
|
||||
return new ClosureComponentFactory<>(closure);
|
||||
}
|
||||
|
||||
static <T extends ViewComponent> ComponentFactory<T> of(Supplier<T> supplier) {
|
||||
return new DelegatingComponentFactory<>((ignored0, ignored1) -> supplier.get());
|
||||
static <T extends ViewComponent> ComponentFactory<T> ofSupplier(Supplier<T> supplier) {
|
||||
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 {
|
||||
|
||||
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<ViewComponent> componentStack = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public ComponentFactory<?> resolve(String component) {
|
||||
public Resolved resolve(String component) {
|
||||
if (scopeStack.isEmpty()) {
|
||||
throw new IllegalStateException("There are no scopes on the scopeStack.");
|
||||
}
|
||||
@ -23,7 +45,7 @@ public class DefaultComponentContext implements ComponentContext {
|
||||
while (!getStack.isEmpty()) {
|
||||
final ComponentScope scope = getStack.pop();
|
||||
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()) {
|
||||
final ComponentScope scope = getStack.pop();
|
||||
try {
|
||||
return scope.factoryMissing(component);
|
||||
return new DefaultResolved(component, scope.factoryMissing(component));
|
||||
} catch (NoFactoryMissingException e) {
|
||||
if (first == null) {
|
||||
first = e;
|
||||
@ -48,14 +70,19 @@ public class DefaultComponentContext implements ComponentContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewComponent create(ComponentFactory<?> factory, Object... args) {
|
||||
final ViewComponent component = factory.create(this, args);
|
||||
this.componentStack.push(component);
|
||||
return component;
|
||||
public ViewComponent create(Resolved resolved, Object... args) {
|
||||
return resolved.getComponentFactory().create(
|
||||
resolved.getTypeName(), this, args
|
||||
);
|
||||
}
|
||||
|
||||
@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();
|
||||
if (!popped.equals(component)) {
|
||||
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;
|
||||
|
||||
import groowt.view.View;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
public interface ViewComponent extends View {
|
||||
|
||||
@ -8,7 +9,9 @@ public interface ViewComponent extends View {
|
||||
return this.getClass().getName();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
void setContext(ComponentContext context);
|
||||
|
||||
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
|
||||
: ComponentOpen ComponentClose
|
||||
body?
|
||||
body
|
||||
ClosingComponentOpen ComponentClose
|
||||
;
|
||||
|
||||
|
@ -2,13 +2,11 @@ package groowt.view.web;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.util.di.DefaultRegistryObjectFactory;
|
||||
import groowt.view.component.AbstractViewComponent;
|
||||
import groowt.view.component.ComponentContext;
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
import groowt.view.web.WebViewTemplateComponentSource.*;
|
||||
import groowt.view.web.runtime.WebViewComponentWriter;
|
||||
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -77,25 +75,36 @@ public class DefaultWebViewComponent extends AbstractViewComponent implements We
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<WebViewChildRenderer> getChildren() {
|
||||
return Objects.requireNonNullElseGet(this.children, ArrayList::new);
|
||||
public List<WebViewChildRenderer> getChildRenderers() {
|
||||
if (this.children == null) {
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
return this.children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChildren(List<WebViewChildRenderer> children) {
|
||||
public boolean hasChildren() {
|
||||
return !this.getChildRenderers().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChildRenderers(List<WebViewChildRenderer> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderChildren() {
|
||||
for (final var childRenderer : this.getChildren()) {
|
||||
for (final var childRenderer : this.getChildRenderers()) {
|
||||
try {
|
||||
if (childRenderer instanceof WebViewChildComponentRenderer childComponentRenderer) {
|
||||
this.getContext().beforeComponentRender(childComponentRenderer.getComponent());
|
||||
}
|
||||
childRenderer.render(this);
|
||||
} catch (Exception e) {
|
||||
throw new ChildRenderException(e);
|
||||
} finally {
|
||||
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;
|
||||
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.util.di.RegistryObjectFactory;
|
||||
import groowt.view.component.CachingComponentTemplateCompiler;
|
||||
import groowt.view.component.ComponentTemplate;
|
||||
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.transpile.DefaultGroovyTranspiler;
|
||||
import groowt.view.web.transpile.DefaultTranspilerConfiguration;
|
||||
import groowt.view.web.transpile.TranspilerConfiguration;
|
||||
import org.codehaus.groovy.control.CompilationUnit;
|
||||
import org.codehaus.groovy.control.CompilePhase;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
import org.codehaus.groovy.control.Phases;
|
||||
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.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplateCompiler {
|
||||
|
||||
@ -74,11 +70,11 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO: analysis
|
||||
@ -94,7 +90,7 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
||||
DefaultTranspilerConfiguration::new
|
||||
);
|
||||
|
||||
final var ownerComponentName = forClass.getSimpleName();
|
||||
final var ownerComponentName = forClass != null ? forClass.getSimpleName() : "AnonymousComponent";
|
||||
final var templateClassName = ownerComponentName + "Template";
|
||||
final var fqn = this.defaultPackageName + "." + templateClassName;
|
||||
|
||||
@ -205,4 +201,8 @@ public class DefaultWebComponentTemplateCompiler extends CachingComponentTemplat
|
||||
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) {
|
||||
return this.nodeFactory.fragmentComponentNode(
|
||||
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
|
||||
public FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, @Nullable BodyNode bodyNode) {
|
||||
public FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, BodyNode bodyNode) {
|
||||
return this.objectFactory.get(FragmentComponentNode.class, tokenRange, bodyNode);
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ public interface NodeFactory {
|
||||
@Nullable BodyNode body
|
||||
);
|
||||
|
||||
FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, @Nullable BodyNode bodyNode);
|
||||
FragmentComponentNode fragmentComponentNode(TokenRange tokenRange, BodyNode bodyNode);
|
||||
|
||||
ComponentArgsNode componentArgsNode(
|
||||
TokenRange tokenRange,
|
||||
|
@ -4,7 +4,9 @@ import groowt.util.di.annotation.Given;
|
||||
import groowt.view.web.ast.extension.NodeExtensionContainer;
|
||||
import groowt.view.web.util.TokenRange;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public non-sealed class FragmentComponentNode extends ComponentNode {
|
||||
|
||||
@ -12,9 +14,14 @@ public non-sealed class FragmentComponentNode extends ComponentNode {
|
||||
public FragmentComponentNode(
|
||||
NodeExtensionContainer extensionContainer,
|
||||
@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;
|
||||
|
||||
import groowt.view.web.ast.node.BodyChildNode;
|
||||
import groowt.view.web.ast.node.BodyNode;
|
||||
import groowt.view.web.ast.node.Node;
|
||||
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.stmt.BlockStatement;
|
||||
import org.codehaus.groovy.ast.stmt.Statement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BodyTranspiler {
|
||||
|
||||
@FunctionalInterface
|
||||
interface ExpressionStatementConverter {
|
||||
Statement createStatement(Node source, Expression expression);
|
||||
interface AddOrAppendCallback {
|
||||
Statement createStatement(BodyChildNode source, Expression expression);
|
||||
}
|
||||
|
||||
BlockStatement transpileBody(
|
||||
BodyNode bodyNode,
|
||||
ExpressionStatementConverter converter,
|
||||
AddOrAppendCallback addOrAppendCallback,
|
||||
TranspilerState state
|
||||
);
|
||||
|
||||
|
@ -2,10 +2,7 @@ package groowt.view.web.transpile;
|
||||
|
||||
import groowt.view.web.ast.node.ComponentNode;
|
||||
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.Statement;
|
||||
|
||||
public interface ComponentTranspiler {
|
||||
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.transpile.TranspilerUtil.TranspilerState;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import org.codehaus.groovy.ast.expr.GStringExpression;
|
||||
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 {
|
||||
|
||||
private final GStringTranspiler gStringTranspiler;
|
||||
@ -32,7 +26,7 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
||||
@Override
|
||||
public BlockStatement transpileBody(
|
||||
BodyNode bodyNode,
|
||||
ExpressionStatementConverter converter,
|
||||
AddOrAppendCallback addOrAppendCallback,
|
||||
TranspilerState state
|
||||
) {
|
||||
final BlockStatement block = new BlockStatement();
|
||||
@ -43,22 +37,19 @@ public class DefaultBodyTranspiler implements BodyTranspiler {
|
||||
final GStringExpression gString = this.gStringTranspiler.createGStringExpression(
|
||||
gStringBodyTextNode
|
||||
);
|
||||
block.addStatement(converter.createStatement(gStringBodyTextNode, gString));
|
||||
block.addStatement(addOrAppendCallback.createStatement(gStringBodyTextNode, gString));
|
||||
}
|
||||
case JStringBodyTextNode jStringBodyTextNode -> {
|
||||
block.addStatement(
|
||||
converter.createStatement(
|
||||
addOrAppendCallback.createStatement(
|
||||
jStringBodyTextNode,
|
||||
this.jStringTranspiler.createStringLiteral(jStringBodyTextNode)
|
||||
)
|
||||
);
|
||||
}
|
||||
case ComponentNode componentNode -> {
|
||||
final BlockStatement componentBlock = this.componentTranspiler.createComponentStatements(
|
||||
componentNode,
|
||||
state
|
||||
);
|
||||
block.addStatement(componentBlock);
|
||||
// DO NOT add/append this, because the component transpiler does it already
|
||||
block.addStatement(this.componentTranspiler.createComponentStatements(componentNode, state));
|
||||
}
|
||||
case PlainScriptletNode plainScriptletNode -> {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
|
@ -1,10 +1,9 @@
|
||||
package groowt.view.web.transpile;
|
||||
|
||||
import groovy.lang.Tuple2;
|
||||
import groowt.view.component.*;
|
||||
import groowt.view.web.ast.node.*;
|
||||
import groowt.view.web.lib.FragmentComponent;
|
||||
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.ConvertResult;
|
||||
import org.codehaus.groovy.ast.*;
|
||||
@ -16,9 +15,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static groowt.view.web.transpile.TranspilerUtil.lineAndColumn;
|
||||
import static groowt.view.web.transpile.TranspilerUtil.makeStringLiteral;
|
||||
import static groowt.view.web.transpile.TranspilerUtil.*;
|
||||
|
||||
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_CLASS_TYPE_EXCEPTION = ClassHelper.make(MissingClassTypeException.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_FRAGMENT = "createFragment";
|
||||
private static final String RESOLVE = "resolve";
|
||||
private static final String ADD = "add";
|
||||
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 BodyTranspiler bodyTranspiler;
|
||||
private AppendOrAddStatementFactory appendOrAddStatementFactory;
|
||||
|
||||
public void setValueNodeTranspiler(ValueNodeTranspiler valueNodeTranspiler) {
|
||||
this.valueNodeTranspiler = valueNodeTranspiler;
|
||||
@ -52,6 +54,10 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
this.bodyTranspiler = bodyTranspiler;
|
||||
}
|
||||
|
||||
public void setAppendOrAddStatementFactory(AppendOrAddStatementFactory appendOrAddStatementFactory) {
|
||||
this.appendOrAddStatementFactory = Objects.requireNonNull(appendOrAddStatementFactory);
|
||||
}
|
||||
|
||||
// ViewComponent c0
|
||||
protected ExpressionStatement getComponentDeclaration(Variable component) {
|
||||
final var componentDeclaration = new DeclarationExpression(
|
||||
@ -82,9 +88,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
}
|
||||
|
||||
// key: value
|
||||
protected MapEntryExpression getAttrExpression(
|
||||
AttrNode attrNode, TranspilerState state
|
||||
) {
|
||||
protected MapEntryExpression getAttrExpression(AttrNode attrNode, TranspilerState state) {
|
||||
final var keyExpr = makeStringLiteral(attrNode.getKeyNode().getKey());
|
||||
final Expression valueExpr = switch (attrNode) {
|
||||
case BooleanValueAttrNode ignored -> ConstantExpression.PRIM_TRUE;
|
||||
@ -129,13 +133,13 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
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 ArgumentListExpression args = new ArgumentListExpression();
|
||||
args.addExpression(toOutput);
|
||||
switch (sourceNode) {
|
||||
case GStringBodyTextNode ignored -> this.addLineAndColumn(sourceNode, args);
|
||||
case ComponentNode ignored -> this.addLineAndColumn(sourceNode, args);
|
||||
case GStringBodyTextNode ignored -> this.addLineAndColumn(sourceNode.asNode(), args);
|
||||
case ComponentNode ignored -> this.addLineAndColumn(sourceNode.asNode(), args);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
@ -143,7 +147,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
}
|
||||
|
||||
// { 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) {
|
||||
variableExpression.setClosureSharedVariable(true);
|
||||
}
|
||||
@ -153,7 +157,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
|
||||
// c0_childCollector.add (jString | gString | component) { out << ... }
|
||||
protected Statement getChildCollectorAdd(
|
||||
Node sourceNode,
|
||||
BodyChildNode sourceNode,
|
||||
TranspilerState state,
|
||||
Variable childCollector,
|
||||
Expression toAdd
|
||||
@ -168,8 +172,15 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
return new ExpressionStatement(methodCall);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tuple containing 1. body ClosureExpression, and 2. childCollector Variable
|
||||
*/
|
||||
// { 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(
|
||||
CHILD_COLLECTOR,
|
||||
componentVariableName + "_childCollector"
|
||||
@ -177,26 +188,40 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
|
||||
final var scope = state.pushScope();
|
||||
scope.putDeclaredVariable(childCollectorParam);
|
||||
state.pushChildCollector(childCollectorParam);
|
||||
final BlockStatement bodyStatements = this.bodyTranspiler.transpileBody(
|
||||
bodyNode,
|
||||
(sourceNode, expr) -> this.getChildCollectorAdd(sourceNode, state, childCollectorParam, expr),
|
||||
state
|
||||
);
|
||||
state.popChildCollector();
|
||||
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(...) {...}
|
||||
protected MethodCallExpression getCreateExpression(
|
||||
ComponentNode componentNode, TranspilerState state, String componentVariableName
|
||||
protected Tuple2<MethodCallExpression, @Nullable Variable> getCreateExpression(
|
||||
ComponentNode componentNode,
|
||||
TranspilerState state,
|
||||
String componentVariableName
|
||||
) {
|
||||
final var createArgs = new ArgumentListExpression();
|
||||
|
||||
final var contextResolve = this.getContextResolveExpr(componentNode, state.context());
|
||||
createArgs.addExpression(contextResolve);
|
||||
|
||||
final String createName;
|
||||
Variable childCollector = null;
|
||||
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();
|
||||
if (!attributeNodes.isEmpty()) {
|
||||
createArgs.addExpression(this.getAttrMap(attributeNodes, state));
|
||||
@ -205,35 +230,62 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
if (constructorNode != null) {
|
||||
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();
|
||||
if (bodyNode != null) {
|
||||
createArgs.addExpression(this.getBodyClosure(bodyNode, state, componentVariableName));
|
||||
}
|
||||
final var createCall = new MethodCallExpression(
|
||||
new VariableExpression(state.context()),
|
||||
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(''), [:], ...) {...}
|
||||
protected ExpressionStatement getCreateAssignStatement(
|
||||
ComponentNode componentNode, TranspilerState state, String componentVariableName
|
||||
protected Tuple2<ExpressionStatement, @Nullable Variable> getCreateAssignStatement(
|
||||
ComponentNode componentNode,
|
||||
TranspilerState state,
|
||||
String componentVariableName,
|
||||
Variable component
|
||||
) {
|
||||
final var componentAssignLeft = new VariableExpression(state.getDeclaredVariable(componentVariableName));
|
||||
final var createExpr = this.getCreateExpression(componentNode, state, componentVariableName);
|
||||
final var componentAssignLeft = new VariableExpression(component);
|
||||
final var createExprResult = this.getCreateExpression(componentNode, state, componentVariableName);
|
||||
final var componentAssignExpr = new BinaryExpression(
|
||||
componentAssignLeft,
|
||||
new Token(Types.ASSIGN, "=", -1, -1),
|
||||
createExpr
|
||||
createExprResult.getV1()
|
||||
);
|
||||
return new ExpressionStatement(componentAssignExpr);
|
||||
return new Tuple2<>(new ExpressionStatement(componentAssignExpr), createExprResult.getV2());
|
||||
}
|
||||
|
||||
// catch (NoFactoryMissingException c0nfme) {
|
||||
// throw new MissingClassComponentException(this, 'ComponentType', c0nfme)
|
||||
// }
|
||||
protected CatchStatement getNoMissingFactoryExceptionCatch(
|
||||
ComponentNode componentNode, String componentVariableName
|
||||
ComponentNode componentNode,
|
||||
String componentVariableName
|
||||
) {
|
||||
final String exceptionName = componentVariableName + "nfme";
|
||||
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) }
|
||||
protected CatchStatement getGeneralCreateExceptionCatch(ComponentNode componentNode, String componentVariableName) {
|
||||
protected CatchStatement getGeneralCreateExceptionCatch(
|
||||
ComponentNode componentNode,
|
||||
String componentVariableName
|
||||
) {
|
||||
final String exceptionName = componentVariableName + "ce";
|
||||
final Parameter exceptionParam = new Parameter(EXCEPTION, exceptionName);
|
||||
final VariableExpression exceptionVar = new VariableExpression(exceptionName);
|
||||
@ -317,53 +372,68 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
|
||||
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) {
|
||||
return "c" + componentNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockStatement createComponentStatements(
|
||||
ComponentNode componentNode,
|
||||
TranspilerState state
|
||||
) {
|
||||
public BlockStatement createComponentStatements(ComponentNode componentNode, TranspilerState state) {
|
||||
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 VariableScope scope = state.pushScope();
|
||||
final VariableScope scope = state.currentScope();
|
||||
result.setVariableScope(scope);
|
||||
scope.putDeclaredVariable(component);
|
||||
|
||||
// ViewComponent c0;
|
||||
result.addStatement(this.getComponentDeclaration(component));
|
||||
|
||||
// try { context.create(...) } catch { ... }
|
||||
final var tryCreateStatement = new TryCatchStatement(this.getCreateAssignStatement(
|
||||
// c0 = context.create(...) { ... }
|
||||
final var createAssignStatementResult = this.getCreateAssignStatement(
|
||||
componentNode,
|
||||
state,
|
||||
componentVariableName
|
||||
), EmptyStatement.INSTANCE);
|
||||
componentVariableName,
|
||||
component
|
||||
);
|
||||
|
||||
// try { ... } catch { ... }
|
||||
final var tryCreateStatement = new TryCatchStatement(
|
||||
createAssignStatementResult.getV1(),
|
||||
EmptyStatement.INSTANCE
|
||||
);
|
||||
this.getCreateCatches(componentNode, componentVariableName).forEach(tryCreateStatement::addCatch);
|
||||
result.addStatement(tryCreateStatement);
|
||||
|
||||
// component.setContext(context)
|
||||
result.addStatement(this.createSetContext(state, component));
|
||||
|
||||
// out << component
|
||||
result.addStatement(this.createComponentOutCall(componentNode, state, component));
|
||||
|
||||
state.popScope();
|
||||
// out or collect
|
||||
final var addOrAppend = this.appendOrAddStatementFactory.addOrAppend(
|
||||
componentNode,
|
||||
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;
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ import groowt.view.web.transpile.util.GroovyUtil;
|
||||
import groowt.view.web.util.FilteringIterable;
|
||||
import groowt.view.web.util.Option;
|
||||
import groowt.view.web.util.TokenRange;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.codehaus.groovy.ast.expr.*;
|
||||
import org.codehaus.groovy.ast.stmt.BlockStatement;
|
||||
@ -26,13 +24,11 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class DefaultGStringTranspiler implements GStringTranspiler {
|
||||
|
||||
private final PositionSetter positionSetter;
|
||||
private final JStringTranspiler jStringTranspiler;
|
||||
|
||||
@Inject
|
||||
public DefaultGStringTranspiler(PositionSetter positionSetter, JStringTranspiler jStringTranspiler) {
|
||||
this.positionSetter = positionSetter;
|
||||
this.jStringTranspiler = jStringTranspiler;
|
||||
|
@ -210,12 +210,17 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
|
||||
final BodyNode bodyNode = compilationUnitNode.getBodyNode();
|
||||
if (bodyNode != null) {
|
||||
final var outStatementFactory = configuration.getOutStatementFactory();
|
||||
final var appendOrAddStatementFactory = configuration.getAppendOrAddStatementFactory();
|
||||
renderBlock.addStatement(
|
||||
configuration.getBodyTranspiler()
|
||||
.transpileBody(
|
||||
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
|
||||
)
|
||||
);
|
||||
@ -223,8 +228,8 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
|
||||
|
||||
final ClosureExpression renderer = new ClosureExpression(
|
||||
new Parameter[] {
|
||||
(Parameter) state.getDeclaredVariable(CONTEXT),
|
||||
(Parameter) state.getDeclaredVariable(OUT)
|
||||
(Parameter) state.context(),
|
||||
(Parameter) state.out()
|
||||
},
|
||||
renderBlock
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import jakarta.inject.Inject;
|
||||
|
||||
public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||
|
||||
private final OutStatementFactory outStatementFactory = new SimpleOutStatementFactory();
|
||||
private final AppendOrAddStatementFactory appendOrAddStatementFactory = new DefaultAppendOrAddStatementFactory();
|
||||
private final BodyTranspiler bodyTranspiler;
|
||||
|
||||
@Inject
|
||||
@ -13,10 +13,13 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||
final var jStringTranspiler = new DefaultJStringTranspiler(positionSetter);
|
||||
final var gStringTranspiler = new DefaultGStringTranspiler(positionSetter, jStringTranspiler);
|
||||
final var componentTranspiler = new DefaultComponentTranspiler();
|
||||
this.bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
|
||||
componentTranspiler.setBodyTranspiler(this.bodyTranspiler);
|
||||
final var valueNodeTranspiler = new DefaultValueNodeTranspiler(componentTranspiler);
|
||||
|
||||
this.bodyTranspiler = new DefaultBodyTranspiler(gStringTranspiler, jStringTranspiler, componentTranspiler);
|
||||
|
||||
componentTranspiler.setBodyTranspiler(this.bodyTranspiler);
|
||||
componentTranspiler.setValueNodeTranspiler(valueNodeTranspiler);
|
||||
componentTranspiler.setAppendOrAddStatementFactory(this.appendOrAddStatementFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -25,8 +28,8 @@ public class DefaultTranspilerConfiguration implements TranspilerConfiguration {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutStatementFactory getOutStatementFactory() {
|
||||
return this.outStatementFactory;
|
||||
public AppendOrAddStatementFactory getAppendOrAddStatementFactory() {
|
||||
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 {
|
||||
BodyTranspiler getBodyTranspiler();
|
||||
OutStatementFactory getOutStatementFactory();
|
||||
AppendOrAddStatementFactory getAppendOrAddStatementFactory();
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ public final class TranspilerUtil {
|
||||
public static final String OUT = "out";
|
||||
public static final String CONTEXT = "context";
|
||||
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) {
|
||||
return new Tuple2<>(
|
||||
@ -58,6 +60,7 @@ public final class TranspilerUtil {
|
||||
|
||||
private final AtomicInteger componentCounter = new AtomicInteger();
|
||||
private final Deque<VariableScope> scopeStack = new LinkedList<>();
|
||||
private final Deque<Variable> childCollectorStack = new LinkedList<>();
|
||||
|
||||
private TranspilerState(VariableScope rootScope) {
|
||||
this.scopeStack.push(rootScope);
|
||||
@ -103,6 +106,22 @@ public final class TranspilerUtil {
|
||||
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() {}
|
||||
|
@ -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 {
|
||||
|
||||
@Override
|
||||
protected BodyTranspiler getBodyTranspiler() {
|
||||
final var positionSetter = new SimplePositionSetter();
|
||||
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();
|
||||
protected TranspilerConfiguration getConfiguration() {
|
||||
return new DefaultTranspilerConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -100,11 +100,6 @@ public abstract class NodeFactoryTests {
|
||||
assertNotNull(this.nodeFactory.fragmentComponentNode(this.getTokenRange(), bodyNode));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fragmentComponentNodeBodyNull() {
|
||||
assertNotNull(this.nodeFactory.fragmentComponentNode(this.getTokenRange(), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void componentArgsNodeWithClassComponentType(
|
||||
@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.CompilationUnitNode;
|
||||
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 org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.ast.expr.MethodCallExpression;
|
||||
@ -20,8 +20,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public abstract class BodyTranspilerTests {
|
||||
|
||||
protected abstract BodyTranspiler getBodyTranspiler();
|
||||
protected abstract OutStatementFactory getOutStatementFactory();
|
||||
protected abstract TranspilerConfiguration getConfiguration();
|
||||
|
||||
protected record BuildResult(BodyNode bodyNode, TokenList tokenList) {}
|
||||
|
||||
@ -37,10 +36,14 @@ public abstract class BodyTranspilerTests {
|
||||
return new BuildResult(bodyNode, tokenList);
|
||||
}
|
||||
|
||||
protected BodyTranspiler getBodyTranspiler() {
|
||||
return this.getConfiguration().getBodyTranspiler();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void smokeScreen() {
|
||||
assertDoesNotThrow(() -> {
|
||||
getBodyTranspiler();
|
||||
this.getBodyTranspiler();
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,10 +52,11 @@ public abstract class BodyTranspilerTests {
|
||||
final var source = "Hello, $target!";
|
||||
final var buildResult = this.build(source);
|
||||
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(
|
||||
buildResult.bodyNode(),
|
||||
(ignored, expression) -> outStatementFactory.create(expression),
|
||||
(node, expression) -> addOrAppend.addOrAppend(node, state, ignored -> expression),
|
||||
TranspilerUtil.TranspilerState.withDefaultRootScope()
|
||||
);
|
||||
assertEquals(1, blockStatement.getStatements().size());
|
||||
@ -63,10 +67,11 @@ public abstract class BodyTranspilerTests {
|
||||
final var source = "Hello, World!";
|
||||
final var buildResult = this.build(source);
|
||||
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(
|
||||
buildResult.bodyNode(),
|
||||
(ignored, expression) -> outStatementFactory.create(expression),
|
||||
(node, expression) -> addOrAppend.addOrAppend(node, state, ignored -> expression),
|
||||
TranspilerUtil.TranspilerState.withDefaultRootScope()
|
||||
);
|
||||
assertEquals(1, blockStatement.getStatements().size());
|
||||
|
Loading…
Reference in New Issue
Block a user