1+ nested components in component attr values working; Each tests.

This commit is contained in:
JesseBrault0709 2024-05-31 16:50:30 +02:00
parent 93cf3905ae
commit 131fbaf155
9 changed files with 186 additions and 31 deletions

View File

@ -110,7 +110,8 @@ channels {
this.pushMode(GROOVY_CODE); this.pushMode(GROOVY_CODE);
} }
private boolean inAttrComponent() { @Override
protected boolean inAttrComponent() {
return this.peekMode(1) == COMPONENT_ATTR_VALUE; return this.peekMode(1) == COMPONENT_ATTR_VALUE;
} }
@ -269,7 +270,7 @@ mode IN_TAG;
ComponentClose ComponentClose
: GT : GT
{ {
if (this.inAttrComponent() && this.attrComponentFinished()) { if (this.inAttrComponent() && this.isAttrComponentFinished()) {
this.popMode(); this.popMode();
this.popMode(); this.popMode();
} else { } else {
@ -283,7 +284,7 @@ ComponentSelfClose
{ {
if (this.inAttrComponent()) { if (this.inAttrComponent()) {
this.exitAttrComponent(); this.exitAttrComponent();
if (this.attrComponentFinished()) { if (this.isAttrComponentFinished()) {
this.popMode(); this.popMode();
this.popMode(); this.popMode();
} }
@ -338,7 +339,7 @@ ClosureAttrValueStart
ComponentAttrValueStart ComponentAttrValueStart
: LEFT_CURLY InTagNlws? { this.isNext('<') }? : LEFT_CURLY InTagNlws? { this.isNext('<') }?
{ {
this.enterAttrComponent(); this.pushAttrComponent();
this.pushMode(COMPONENT_ATTR_VALUE); this.pushMode(COMPONENT_ATTR_VALUE);
} }
; ;
@ -359,7 +360,10 @@ TagError
mode COMPONENT_ATTR_VALUE; mode COMPONENT_ATTR_VALUE;
AttrComponentOpen AttrComponentOpen
: LT { !isAnyOf(this.getNextChar(), '/', '>') }? -> type(ComponentOpen), pushMode(TAG_START) : LT { !isAnyOf(this.getNextChar(), '/', '>') }?
{
this.enterAttrComponent();
} -> type(ComponentOpen), pushMode(TAG_START)
; ;
AttrClosingComponentOpen AttrClosingComponentOpen
@ -370,7 +374,10 @@ AttrClosingComponentOpen
; ;
AttrFragmentOpen AttrFragmentOpen
: LT GT -> type(FragmentOpen) : LT GT
{
this.enterAttrComponent();
} -> type(FragmentOpen)
; ;
AttrFragmentClose AttrFragmentClose

View File

@ -14,7 +14,8 @@ import org.slf4j.LoggerFactory;
import java.util.Arrays; import java.util.Arrays;
import java.util.Deque; import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -59,7 +60,7 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
private boolean canPreamble = true; private boolean canPreamble = true;
private boolean inPreamble; private boolean inPreamble;
private boolean inConstructor; private boolean inConstructor;
private Deque<AtomicBoolean> attrComponentFinishedStack = new LinkedList<>(); private Deque<AtomicInteger> attrComponentCountStack = new LinkedList<>();
public AbstractWebViewComponentsLexer(CharStream input) { public AbstractWebViewComponentsLexer(CharStream input) {
@ -119,12 +120,12 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.inConstructor = inConstructor; this.inConstructor = inConstructor;
} }
public Deque<AtomicBoolean> getAttrComponentFinishedStack() { public Deque<AtomicInteger> getAttrComponentCountStack() {
return this.attrComponentFinishedStack; return this.attrComponentCountStack;
} }
public void setAttrComponentFinishedStack(Deque<AtomicBoolean> attrComponentFinishedStack) { public void setAttrComponentCountStack(Deque<AtomicInteger> attrComponentCountStack) {
this.attrComponentFinishedStack = attrComponentFinishedStack; this.attrComponentCountStack = attrComponentCountStack;
} }
@Override @Override
@ -134,7 +135,7 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.canPreamble = true; this.canPreamble = true;
this.inPreamble = false; this.inPreamble = false;
this.inConstructor = false; this.inConstructor = false;
this.attrComponentFinishedStack = new LinkedList<>(); this.attrComponentCountStack = new LinkedList<>();
super.reset(); super.reset();
} }
@ -217,28 +218,42 @@ public abstract class AbstractWebViewComponentsLexer extends Lexer {
this.inConstructor = false; this.inConstructor = false;
} }
protected void enterAttrComponent() { protected abstract boolean inAttrComponent();
this.attrComponentFinishedStack.push(new AtomicBoolean());
}
protected void exitAttrComponent() { protected void pushAttrComponent() {
final AtomicBoolean attrComponentFinished = this.attrComponentFinishedStack.peek(); this.attrComponentCountStack.push(new AtomicInteger());
if (attrComponentFinished == null) {
throw new WebViewComponentBugError(new IllegalStateException());
}
attrComponentFinished.set(true);
}
protected boolean attrComponentFinished() {
final AtomicBoolean attrComponentFinished = this.attrComponentFinishedStack.peek();
if (attrComponentFinished == null) {
throw new WebViewComponentBugError(new IllegalStateException());
}
return attrComponentFinished.get();
} }
protected void popAttrComponent() { protected void popAttrComponent() {
this.attrComponentFinishedStack.pop(); try {
this.attrComponentCountStack.pop();
} catch (NoSuchElementException e) {
throw new WebViewComponentBugError("attrComponentCountStack was empty.", e);
}
}
protected boolean isAttrComponentFinished() {
final AtomicInteger attrComponentFinished = this.attrComponentCountStack.peek();
if (attrComponentFinished == null) {
throw new WebViewComponentBugError(new IllegalStateException());
}
return attrComponentFinished.get() == 0;
}
protected void enterAttrComponent() {
final AtomicInteger attrComponentCount = this.attrComponentCountStack.peek();
if (attrComponentCount == null) {
throw new WebViewComponentBugError(new IllegalStateException("attrComponentCountStack is empty."));
}
attrComponentCount.incrementAndGet();
}
protected void exitAttrComponent() {
final AtomicInteger attrComponentCount = this.attrComponentCountStack.peek();
if (attrComponentCount == null) {
throw new WebViewComponentBugError(new IllegalStateException("attrComponentCountStack is empty."));
}
attrComponentCount.decrementAndGet();
} }
protected String getNextCharsAsString(int numberOfChars) { protected String getNextCharsAsString(int numberOfChars) {

View File

@ -39,6 +39,7 @@ public class DefaultNodeFactory implements NodeFactory {
GStringValueNode.class, GStringValueNode.class,
JStringValueNode.class, JStringValueNode.class,
ClosureValueNode.class, ClosureValueNode.class,
EmptyClosureValueNode.class,
ComponentValueNode.class, ComponentValueNode.class,
PlainScriptletNode.class, PlainScriptletNode.class,
EqualsScriptletNode.class, EqualsScriptletNode.class,

View File

@ -0,0 +1 @@
<Each items={menuItems} transform={<li><a href={it.path}>$it.name</a></li>} />

View File

@ -0,0 +1,36 @@
0: ComponentOpen[1,1](<)
1: TypedIdentifier[1,2](Each)
2: Nlws[1,6]( )
3: AttributeIdentifier[1,7](items)
4: Equals[1,12](=)
5: ClosureAttrValueStart[1,13]({)
6: GroovyCode[1,14](menuItems)
7: ClosureAttrValueEnd[1,23](})
8: Nlws[1,24]( )
9: AttributeIdentifier[1,25](transform)
10: Equals[1,34](=)
11: ComponentAttrValueStart[1,35]({)
12: ComponentOpen[1,36](<)
13: StringIdentifier[1,37](li)
14: ComponentClose[1,39](>)
15: ComponentOpen[1,40](<)
16: StringIdentifier[1,41](a)
17: Nlws[1,42]( )
18: AttributeIdentifier[1,43](href)
19: Equals[1,47](=)
20: ClosureAttrValueStart[1,48]({)
21: GroovyCode[1,49](it.path)
22: ClosureAttrValueEnd[1,56](})
23: ComponentClose[1,57](>)
24: DollarReferenceStart[1,58]($)
25: GroovyCode[1,59](it.name)
26: ClosingComponentOpen[1,66](</)
27: StringIdentifier[1,68](a)
28: ComponentClose[1,69](>)
29: ClosingComponentOpen[1,70](</)
30: StringIdentifier[1,72](li)
31: ComponentClose[1,74](>)
32: ComponentAttrValueEnd[1,75](})
33: Nlws[1,76]( )
34: ComponentSelfClose[1,77](/>)
35: RawText[1,79](\n)

View File

@ -0,0 +1 @@
<Each items={menuItems} transform={<li><a href={it.path}>$it.name</a></li>} />

View File

@ -0,0 +1,72 @@
compilationUnit[1,1..2,1]
body[1,1..1,79]
component[1,1..1,79]
selfClosingComponent[1,1..1,79]
ComponentOpen[1,1](<)
componentArgs[1,2..1,75]
componentType[1,2..1,6]
TypedIdentifier[1,2](Each)
attr[1,7..1,23]
keyValueAttr[1,7..1,23]
AttributeIdentifier[1,7](items)
Equals[1,12](=)
value[1,13..1,23]
closureAttrValue[1,13..1,23]
ClosureAttrValueStart[1,13]({)
GroovyCode[1,14](menuItems)
ClosureAttrValueEnd[1,23](})
attr[1,25..1,75]
keyValueAttr[1,25..1,75]
AttributeIdentifier[1,25](transform)
Equals[1,34](=)
value[1,35..1,75]
componentAttrValue[1,35..1,75]
ComponentAttrValueStart[1,35]({)
component[1,36..1,74]
componentWithChildren[1,36..1,74]
openComponent[1,36..1,39]
ComponentOpen[1,36](<)
componentArgs[1,37..1,39]
componentType[1,37..1,39]
StringIdentifier[1,37](li)
ComponentClose[1,39](>)
body[1,40..1,69]
component[1,40..1,69]
componentWithChildren[1,40..1,69]
openComponent[1,40..1,57]
ComponentOpen[1,40](<)
componentArgs[1,41..1,56]
componentType[1,41..1,41]
StringIdentifier[1,41](a)
attr[1,43..1,56]
keyValueAttr[1,43..1,56]
AttributeIdentifier[1,43](href)
Equals[1,47](=)
value[1,48..1,56]
closureAttrValue[1,48..1,56]
ClosureAttrValueStart[1,48]({)
GroovyCode[1,49](it.path)
ClosureAttrValueEnd[1,56](})
ComponentClose[1,57](>)
body[1,58..1,66]
bodyText[1,58..1,66]
bodyTextGroovyElement[1,58..1,66]
dollarReference[1,58..1,66]
DollarReferenceStart[1,58]($)
GroovyCode[1,59](it.name)
closingComponent[1,66..1,69]
ClosingComponentOpen[1,66](</)
componentType[1,68..1,68]
StringIdentifier[1,68](a)
ComponentClose[1,69](>)
closingComponent[1,70..1,74]
ClosingComponentOpen[1,70](</)
componentType[1,72..1,74]
StringIdentifier[1,72](li)
ComponentClose[1,74](>)
ComponentAttrValueEnd[1,75](})
ComponentSelfClose[1,77](/>)
bodyText[1,79..1,79]
text[1,79..1,79]
RawText[1,79](\n)
EOF[2,1](<EOF>)

View File

@ -1,6 +1,7 @@
package groowt.view.component.web package groowt.view.component.web
import groowt.view.component.context.DefaultComponentScope import groowt.view.component.context.DefaultComponentScope
import groowt.view.component.web.lib.Each
import groowt.view.component.web.lib.Echo import groowt.view.component.web.lib.Echo
import groowt.view.component.web.lib.IntrinsicHtml import groowt.view.component.web.lib.IntrinsicHtml
import org.codehaus.groovy.runtime.InvokerHelper import org.codehaus.groovy.runtime.InvokerHelper
@ -12,6 +13,7 @@ class DefaultWebViewComponentScope extends DefaultComponentScope implements WebV
static DefaultWebViewComponentScope getDefaultRootScope() { static DefaultWebViewComponentScope getDefaultRootScope() {
new DefaultWebViewComponentScope().tap { new DefaultWebViewComponentScope().tap {
addWithAttr(Echo) addWithAttr(Echo)
addWithAttr(Each)
} }
} }

View File

@ -0,0 +1,20 @@
package groowt.view.component.web.lib
import org.junit.jupiter.api.Test
class EachTests extends AbstractWebViewComponentTests {
@Test
void simple() {
this.doTest('<Echo items={[0, 1, 2]}><Each items={items} /></Echo>', '012')
}
@Test
void withTransform() {
this.doTest(
'<Echo items={[0, 1, 2]}><Each items={items} transform={<p>$it</p>} /></Echo>',
'<p>0</p><p>1</p><p>2</p>'
)
}
}