Skip to content
Snippets Groups Projects
Commit 517c6a91 authored by René Schöne's avatar René Schöne
Browse files

Merge branch '38-feature-send-endpoint-for-attributes' into 'dev'

Resolve "Feature: Send endpoint for attributes"

Closes #38

See merge request !25
parents 81c522ef c16a42ac
No related branches found
No related tags found
3 merge requests!39Version 1.1.0,!35Version 1.0.0,!25Resolve "Feature: Send endpoint for attributes"
Pipeline #12565 passed
Showing
with 296 additions and 65 deletions
...@@ -13,7 +13,7 @@ The kind of the element determines, whether an endpoint for it can be receiving, ...@@ -13,7 +13,7 @@ The kind of the element determines, whether an endpoint for it can be receiving,
To declare a new endpoints, use the following syntax: To declare a new endpoints, use the following syntax:
``` ```
("send"|"receive") ["indexed"] ["with add"] <Non-Terminal>[.<Target>["()"]] ["using" <Mapping-Name> (, <Mapping-Name>)*] ";" ("send"|"receive") ["indexed"] ["with add"] <Non-Terminal>[.<Target>["(<AttributeType>)"]] ["using" <Mapping-Name> (, <Mapping-Name>)*] ";"
``` ```
A breakdown of the parts of that syntax: A breakdown of the parts of that syntax:
...@@ -28,10 +28,12 @@ A breakdown of the parts of that syntax: ...@@ -28,10 +28,12 @@ A breakdown of the parts of that syntax:
- The second optional keyword `with add` can also be used only for receiving endpoints targeting a list children. - The second optional keyword `with add` can also be used only for receiving endpoints targeting a list children.
As described above, it can be combined with `indexed`. As described above, it can be combined with `indexed`.
If used on its own, the incoming data is interpreted as a complete list and its elements will be appended to the current list. If used on its own, the incoming data is interpreted as a complete list and its elements will be appended to the current list.
- The `<Non-Terminal>[.<Target>["()"]]` notation describes the actual affected node. - The `<Non-Terminal>[.<Target>["(<AttributeType>)"]]` notation describes the actual affected node.
- If the target is omitted, all nodes of that non-terminal type can be connected, irrespective of their context. This is a context-free endpoint definition. - If the target is omitted, all nodes of that non-terminal type can be connected, irrespective of their context. This is a context-free endpoint definition.
- The target can be any child on the right-hand side of a production rule, a role of a relation, or an attribute. - The target can be any child on the right-hand side of a production rule, a role of a relation, or an attribute.
The brackets `()` after the target must be used in case of an attribute, and only then. The brackets `(<AttributeType>)` after the target must be used in case of an attribute, and only then.
Here, the return type of the attribute has to be specified, as aspect files are not parsed by RagConnect.
Hence, RagConnect can not and will not verify the existence of the attribute, and the possible non-existence of an attribute will be found by the Java compiler.
- Optionally, an endpoint can use one or more [mappings](#mappings). - Optionally, an endpoint can use one or more [mappings](#mappings).
They will be applied before sending, or after receiving a message. They will be applied before sending, or after receiving a message.
Mappings will always be applied in the order they are listed after `using`. Mappings will always be applied in the order they are listed after `using`.
......
# Inner workings of `RagConnect` # Inner Workings of `RagConnect`
Please see [API documentation](ragdoc/index.html) for more details. Please see [API documentation](ragdoc/index.html) for more details.
...@@ -20,11 +20,11 @@ The other main aspect (which is currently not really used) is `IntermediateToYAM ...@@ -20,11 +20,11 @@ The other main aspect (which is currently not really used) is `IntermediateToYAM
This is used to generate a YAML file containing the data used by mustache. This is used to generate a YAML file containing the data used by mustache.
It can be used by the default mustache implementation together with the templates. It can be used by the default mustache implementation together with the templates.
# Implementation details # Implementation Details
In the following, details for special implementation topics are discussed. In the following, details for special implementation topics are discussed.
## forwarding ## Forwarding
When a nonterminal is used in a send endpoints, it needs an implicit forwarding attribute to work, because only _computed elements_ can be sent. When a nonterminal is used in a send endpoints, it needs an implicit forwarding attribute to work, because only _computed elements_ can be sent.
Since the nonterminal itself should be sent, the generated attribute simply returns this nonterminal. Since the nonterminal itself should be sent, the generated attribute simply returns this nonterminal.
...@@ -33,3 +33,29 @@ However, changing any token within the whole subtree or changing the structure o ...@@ -33,3 +33,29 @@ However, changing any token within the whole subtree or changing the structure o
This way, the dependency tracking registers a dependency between structure and tokens to the attribute. This way, the dependency tracking registers a dependency between structure and tokens to the attribute.
The attribute (as well as any other generated element) is prefixed with `_ragconnect_` to avoid potential name conflicts with user-specified elements. The attribute (as well as any other generated element) is prefixed with `_ragconnect_` to avoid potential name conflicts with user-specified elements.
# Implementation Hints
## Debugging Tests and Finding Bugs
To help with finding errors/bugs when tests fail, there are several things to find the correct spot.
- **Look closely**. Analyze the error message closely, and possible any previous error message(s) that could have caused the test to fail.
- **Focus on single error**
- To only inspect one test, mark them with `@Tag("New")` and use the gradle task "newTests".
- Use `Assumptions.assumeTrue(false);` to abort unneeded test cases early.
- When editing RagConnect itself and force recreating source for the affected test, e.g., `compileForwardingIncremental.outputs.upToDateWhen { false }`
- _Remember to undo all changes, once the bug is fixed._
- **Activate logs**. Add the following to the `ragconnect` specification of the compile-task of the affected test:
```
logReads = true
logWrites = true
logIncremental = true
```
_Remember to remove those lines, once the bug is fixed._
- **Trace incremental events**. Add the following right after create the root node (named `model` here):
```java
model.trace().setReceiver(TestUtils::logEvent);
```
This will output every event fired by the incremental evaluation engine. _Remember to remove this line, once the bug is fixed._
- **Add log statements**. As there will be quite some log output, add some identifying log statement (i.e., using `logger.fatal("---")`) right before the suspicious statement to inspect only the relevant log message after that.
...@@ -2,6 +2,19 @@ aspect Analysis { ...@@ -2,6 +2,19 @@ aspect Analysis {
// --- isAlreadyDefined --- // --- isAlreadyDefined ---
syn boolean EndpointDefinition.isAlreadyDefined() = getEndpointTarget().isAlreadyDefined(); syn boolean EndpointDefinition.isAlreadyDefined() = getEndpointTarget().isAlreadyDefined();
syn boolean EndpointTarget.isAlreadyDefined(); syn boolean EndpointTarget.isAlreadyDefined();
eq AttributeEndpointTarget.isAlreadyDefined() {
// define lookup here, as not used elsewhere
int numberOfSameDefs = 0;
for (EndpointTarget target : ragconnect().givenEndpointTargetList()) {
if (target.isAttributeEndpointTarget()) {
AttributeEndpointTarget other = target.asAttributeEndpointTarget();
if (other.getParentTypeDecl().equals(this.getParentTypeDecl()) && other.getName().equals(this.getName())) {
numberOfSameDefs += 1;
}
}
}
return numberOfSameDefs > 1;
}
eq TokenEndpointTarget.isAlreadyDefined() { eq TokenEndpointTarget.isAlreadyDefined() {
return lookupTokenEndpointDefinitions(getToken()).stream() return lookupTokenEndpointDefinitions(getToken()).stream()
.filter(containingEndpointDefinition()::matchesType) .filter(containingEndpointDefinition()::matchesType)
...@@ -63,13 +76,17 @@ aspect Analysis { ...@@ -63,13 +76,17 @@ aspect Analysis {
} }
} }
syn boolean EndpointTarget.entityIsNormalAttribute(); syn boolean EndpointTarget.hasAttributeResetMethod();
eq TokenEndpointTarget.entityIsNormalAttribute() = !getToken().getNTA(); eq AttributeEndpointTarget.hasAttributeResetMethod() = false;
eq TypeEndpointTarget.entityIsNormalAttribute() = !getType().getNTA(); eq TokenEndpointTarget.hasAttributeResetMethod() = getToken().getNTA();
eq ContextFreeTypeEndpointTarget.entityIsNormalAttribute() = false; eq TypeEndpointTarget.hasAttributeResetMethod() = getType().getNTA();
eq ContextFreeTypeEndpointTarget.hasAttributeResetMethod() = false;
// --- needProxyToken --- // --- needProxyToken ---
syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() || getTokenEndpointTargetList().stream().map(EndpointTarget::containingEndpointDefinition).anyMatch(EndpointDefinition::shouldSendValue); syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() ||
getTokenEndpointTargetList().stream()
.map(EndpointTarget::containingEndpointDefinition)
.anyMatch(EndpointDefinition::shouldNotResetValue);
// --- effectiveUsedAt --- // --- effectiveUsedAt ---
coll Set<EndpointDefinition> MappingDefinition.effectiveUsedAt() coll Set<EndpointDefinition> MappingDefinition.effectiveUsedAt()
......
...@@ -168,6 +168,10 @@ aspect MustacheMappingApplicationAndDefinition { ...@@ -168,6 +168,10 @@ aspect MustacheMappingApplicationAndDefinition {
syn String MEndpointDefinition.preemptiveReturn(); syn String MEndpointDefinition.preemptiveReturn();
syn String MEndpointDefinition.firstInputVarName(); syn String MEndpointDefinition.firstInputVarName();
eq MAttributeSendDefinition.firstInputVarName() = getterMethodCall();
eq MAttributeSendDefinition.preemptiveExpectedValue() = lastValueGetterCall();
eq MAttributeSendDefinition.preemptiveReturn() = "return false;";
eq MTokenReceiveDefinition.firstInputVarName() = "message"; eq MTokenReceiveDefinition.firstInputVarName() = "message";
eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall(); eq MTokenReceiveDefinition.preemptiveExpectedValue() = getterMethodCall();
eq MTokenReceiveDefinition.preemptiveReturn() = "return;"; eq MTokenReceiveDefinition.preemptiveReturn() = "return;";
...@@ -323,7 +327,7 @@ aspect MustacheRagConnect { ...@@ -323,7 +327,7 @@ aspect MustacheRagConnect {
aspect MustacheReceiveAndSendAndHandleUri { aspect MustacheReceiveAndSendAndHandleUri {
// === EndpointDefinition === // === EndpointDefinition ===
syn String EndpointDefinition.connectMethodName() = "connect" + entityName(); syn String EndpointDefinition.connectMethodName() = "connect" + capitalize(entityName());
syn String EndpointDefinition.connectParameterName() = "uriString"; syn String EndpointDefinition.connectParameterName() = "uriString";
...@@ -348,7 +352,7 @@ aspect MustacheReceiveAndSendAndHandleUri { ...@@ -348,7 +352,7 @@ aspect MustacheReceiveAndSendAndHandleUri {
} else { } else {
extra = ""; extra = "";
} }
return "disconnect" + extra + entityName(); return "disconnect" + extra + capitalize(entityName());
} }
syn String EndpointDefinition.entityName() = getEndpointTarget().entityName(); syn String EndpointDefinition.entityName() = getEndpointTarget().entityName();
...@@ -371,6 +375,10 @@ aspect MustacheReceiveAndSendAndHandleUri { ...@@ -371,6 +375,10 @@ aspect MustacheReceiveAndSendAndHandleUri {
syn String EndpointTarget.parentTypeName(); syn String EndpointTarget.parentTypeName();
syn String EndpointTarget.entityName(); syn String EndpointTarget.entityName();
eq AttributeEndpointTarget.getterMethodName() = getName();
eq AttributeEndpointTarget.parentTypeName() = getParentTypeDecl().getName();
eq AttributeEndpointTarget.entityName() = getName();
eq TokenEndpointTarget.getterMethodName() = "get" + getToken().getName(); eq TokenEndpointTarget.getterMethodName() = "get" + getToken().getName();
eq TokenEndpointTarget.parentTypeName() = getToken().containingTypeDecl().getName(); eq TokenEndpointTarget.parentTypeName() = getToken().containingTypeDecl().getName();
eq TokenEndpointTarget.entityName() = getToken().getName(); eq TokenEndpointTarget.entityName() = getToken().getName();
...@@ -429,7 +437,7 @@ aspect MustacheSendDefinition { ...@@ -429,7 +437,7 @@ aspect MustacheSendDefinition {
syn String EndpointDefinition.senderName() = getEndpointTarget().senderName(); syn String EndpointDefinition.senderName() = getEndpointTarget().senderName();
syn boolean EndpointDefinition.shouldSendValue() = getSend() && getEndpointTarget().entityIsNormalAttribute(); syn boolean EndpointDefinition.shouldNotResetValue() = getSend() && !getEndpointTarget().hasAttributeResetMethod();
syn String EndpointDefinition.tokenResetMethodName() = getterMethodName() + "_reset"; syn String EndpointDefinition.tokenResetMethodName() = getterMethodName() + "_reset";
...@@ -456,14 +464,15 @@ aspect MustacheSendDefinition { ...@@ -456,14 +464,15 @@ aspect MustacheSendDefinition {
getTypeDecl().getName() : getTypeDecl().getName() :
ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">"; ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">";
syn String EndpointTarget.senderName(); syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
eq TokenEndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + getToken().getName();
eq TypeEndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + getType().getName();
eq ContextFreeTypeEndpointTarget.senderName() = null; eq ContextFreeTypeEndpointTarget.senderName() = null;
syn String MEndpointDefinition.updateMethodName(); syn String MEndpointDefinition.updateMethodName();
syn String MEndpointDefinition.writeMethodName(); syn String MEndpointDefinition.writeMethodName();
eq MAttributeSendDefinition.updateMethodName() = ragconnect().internalRagConnectPrefix() + "_update_attr_" + getEndpointDefinition().entityName();
eq MAttributeSendDefinition.writeMethodName() = ragconnect().internalRagConnectPrefix() + "_writeLastValue_attr_" + getEndpointDefinition().entityName();
eq MTokenReceiveDefinition.updateMethodName() = null; eq MTokenReceiveDefinition.updateMethodName() = null;
eq MTokenReceiveDefinition.writeMethodName() = null; eq MTokenReceiveDefinition.writeMethodName() = null;
...@@ -497,7 +506,7 @@ aspect MustacheTokenComponent { ...@@ -497,7 +506,7 @@ aspect MustacheTokenComponent {
syn EndpointDefinition TokenComponent.normalTokenSendDef() { syn EndpointDefinition TokenComponent.normalTokenSendDef() {
for (EndpointTarget target : getTokenEndpointTargetList()) { for (EndpointTarget target : getTokenEndpointTargetList()) {
if (target.isTokenEndpointTarget() && target.containingEndpointDefinition().shouldSendValue()) { if (target.isTokenEndpointTarget() && target.containingEndpointDefinition().shouldNotResetValue()) {
return target.containingEndpointDefinition(); return target.containingEndpointDefinition();
} }
} }
...@@ -580,6 +589,12 @@ aspect AttributesForMustache { ...@@ -580,6 +589,12 @@ aspect AttributesForMustache {
return result; return result;
} }
abstract MEndpointDefinition EndpointTarget.createMEndpointDefinition(boolean isSend); abstract MEndpointDefinition EndpointTarget.createMEndpointDefinition(boolean isSend);
MEndpointDefinition AttributeEndpointTarget.createMEndpointDefinition(boolean isSend) {
if (!isSend) {
throw new IllegalArgumentException("AttributeEndpointTarget can only be sent!");
}
return new MAttributeSendDefinition();
}
MEndpointDefinition TokenEndpointTarget.createMEndpointDefinition(boolean isSend) { MEndpointDefinition TokenEndpointTarget.createMEndpointDefinition(boolean isSend) {
return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition(); return isSend ? new MTokenSendDefinition() : new MTokenReceiveDefinition();
} }
......
abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*; abstract MEndpointDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*;
rel MEndpointDefinition.EndpointDefinition -> EndpointDefinition; rel MEndpointDefinition.EndpointDefinition -> EndpointDefinition;
MAttributeSendDefinition : MEndpointDefinition;
abstract MTokenEndpointDefinition : MEndpointDefinition; abstract MTokenEndpointDefinition : MEndpointDefinition;
MTokenReceiveDefinition : MTokenEndpointDefinition; MTokenReceiveDefinition : MTokenEndpointDefinition;
MTokenSendDefinition : MTokenEndpointDefinition; MTokenSendDefinition : MTokenEndpointDefinition;
......
...@@ -103,7 +103,7 @@ aspect IntermediateToYAML { ...@@ -103,7 +103,7 @@ aspect IntermediateToYAML {
result.put("lastValueGetterCall" , lastValueGetterCall()); result.put("lastValueGetterCall" , lastValueGetterCall());
result.put("lastValueSetter" , lastValueSetter()); result.put("lastValueSetter" , lastValueSetter());
result.put("senderName" , senderName()); result.put("senderName" , senderName());
result.put("shouldSendValue" , shouldSendValue()); result.put("shouldNotResetValue" , shouldNotResetValue());
result.put("tokenResetMethodName" , tokenResetMethodName()); result.put("tokenResetMethodName" , tokenResetMethodName());
result.put("updateMethodName" , updateMethodName()); result.put("updateMethodName" , updateMethodName());
result.put("writeMethodName" , writeMethodName()); result.put("writeMethodName" , writeMethodName());
......
...@@ -191,13 +191,6 @@ aspect Mappings { ...@@ -191,13 +191,6 @@ aspect Mappings {
// --- suitableReceiveDefaultMapping --- // --- suitableReceiveDefaultMapping ---
syn DefaultMappingDefinition EndpointDefinition.suitableReceiveDefaultMapping() { syn DefaultMappingDefinition EndpointDefinition.suitableReceiveDefaultMapping() {
if (getEndpointTarget().isTypeEndpointTarget()) {
try {
TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
return typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
} catch (Exception ignore) {
}
}
switch (targetTypeName()) { switch (targetTypeName()) {
case "boolean": case "boolean":
case "Boolean": case "Boolean":
...@@ -225,8 +218,7 @@ aspect Mappings { ...@@ -225,8 +218,7 @@ aspect Mappings {
default: default:
try { try {
TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
// TODO: also support list-types, if list is first type return getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess() ? ragconnect().defaultBytesToListTreeMapping(typeDecl.getName()) : ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
return ragconnect().defaultBytesToTreeMapping(typeDecl.getName());
} catch (Exception ignore) { } catch (Exception ignore) {
} }
System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this); System.err.println("Could not find suitable default mapping for " + targetTypeName() + " on " + this);
...@@ -236,9 +228,6 @@ aspect Mappings { ...@@ -236,9 +228,6 @@ aspect Mappings {
// --- suitableSendDefaultMapping --- // --- suitableSendDefaultMapping ---
syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() { syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() {
if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) {
return ragconnect().defaultListTreeToBytesMapping();
}
switch (targetTypeName()) { switch (targetTypeName()) {
case "boolean": case "boolean":
case "Boolean": case "Boolean":
...@@ -264,6 +253,9 @@ aspect Mappings { ...@@ -264,6 +253,9 @@ aspect Mappings {
case "String": case "String":
return ragconnect().defaultStringToBytesMapping(); return ragconnect().defaultStringToBytesMapping();
default: default:
if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) {
return ragconnect().defaultListTreeToBytesMapping();
}
try { try {
TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName()); TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
return ragconnect().defaultTreeToBytesMapping(typeDecl.getName()); return ragconnect().defaultTreeToBytesMapping(typeDecl.getName());
...@@ -288,6 +280,7 @@ aspect Mappings { ...@@ -288,6 +280,7 @@ aspect Mappings {
} }
} }
syn String EndpointTarget.targetTypeName(); syn String EndpointTarget.targetTypeName();
eq AttributeEndpointTarget.targetTypeName() = getTypeName();
eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName(); eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName();
eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName(); eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName();
eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName(); eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName();
......
...@@ -24,6 +24,12 @@ aspect NewStuff { ...@@ -24,6 +24,12 @@ aspect NewStuff {
syn boolean EndpointTarget.isUntypedEndpointTarget() = false; syn boolean EndpointTarget.isUntypedEndpointTarget() = false;
eq UntypedEndpointTarget.isUntypedEndpointTarget() = true; eq UntypedEndpointTarget.isUntypedEndpointTarget() = true;
/** Tests if EndpointTarget is a AttributeEndpointTarget.
* @return 'true' if this is a AttributeEndpointTarget, otherwise 'false'
*/
syn boolean EndpointTarget.isAttributeEndpointTarget() = false;
eq AttributeEndpointTarget.isAttributeEndpointTarget() = true;
/** casts a EndpointTarget into a TokenEndpointTarget if possible. /** casts a EndpointTarget into a TokenEndpointTarget if possible.
* @return 'this' cast to a TokenEndpointTarget or 'null' * @return 'this' cast to a TokenEndpointTarget or 'null'
*/ */
...@@ -51,6 +57,13 @@ aspect NewStuff { ...@@ -51,6 +57,13 @@ aspect NewStuff {
syn UntypedEndpointTarget EndpointTarget.asUntypedEndpointTarget(); syn UntypedEndpointTarget EndpointTarget.asUntypedEndpointTarget();
eq EndpointTarget.asUntypedEndpointTarget() = null; eq EndpointTarget.asUntypedEndpointTarget() = null;
eq UntypedEndpointTarget.asUntypedEndpointTarget() = this; eq UntypedEndpointTarget.asUntypedEndpointTarget() = this;
/** casts a EndpointTarget into a AttributeEndpointTarget if possible.
* @return 'this' cast to a AttributeEndpointTarget or 'null'
*/
syn AttributeEndpointTarget EndpointTarget.asAttributeEndpointTarget();
eq EndpointTarget.asAttributeEndpointTarget() = null;
eq AttributeEndpointTarget.asAttributeEndpointTarget() = this;
} }
aspect RagConnectNavigation { aspect RagConnectNavigation {
......
...@@ -13,9 +13,10 @@ TypeEndpointTarget : EndpointTarget; ...@@ -13,9 +13,10 @@ TypeEndpointTarget : EndpointTarget;
rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*; rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*;
ContextFreeTypeEndpointTarget : EndpointTarget; ContextFreeTypeEndpointTarget : EndpointTarget;
rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget*; rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget*;
UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName>; // only used by parser UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>; // only used by parser
// to be integrated: // to be integrated:
//AttributeEndpointTarget : EndpointTarget ::= <Name> ; AttributeEndpointTarget : EndpointTarget ::= <Name> <TypeName> ;
rel AttributeEndpointTarget.ParentTypeDecl <-> TypeDecl.AttributeEndpointTarget*;
//RelationEndpointTarget : EndpointTarget ; //RelationEndpointTarget : EndpointTarget ;
//rel RelationEndpointTarget.Role <-> Role.RelationEndpointTarget* ; //rel RelationEndpointTarget.Role <-> Role.RelationEndpointTarget* ;
......
aspect Util { aspect Util {
static String ASTNode.capitalize(String s) { static String ASTNode.capitalize(String s) {
if (s == null) return null;
if (s.isEmpty()) return "";
return Character.toUpperCase(s.charAt(0)) + s.substring(1); return Character.toUpperCase(s.charAt(0)) + s.substring(1);
} }
protected T JastAddList.firstChild() { return getChild(0); } protected T JastAddList.firstChild() { return getChild(0); }
......
...@@ -23,6 +23,19 @@ aspect ParserRewrites { ...@@ -23,6 +23,19 @@ aspect ParserRewrites {
result.setTypeDecl(TypeDecl.createRef(getTypeName())); result.setTypeDecl(TypeDecl.createRef(getTypeName()));
return result; return result;
} }
when (getIsAttribute())
to AttributeEndpointTarget {
AttributeEndpointTarget result = new AttributeEndpointTarget();
String[] tokens = this.getChildName().split(":");
String attributeName = tokens[0];
String attributeTypeName = tokens[1];
result.copyOtherValuesFrom(this);
result.setName(attributeName);
result.setTypeName(attributeTypeName);
result.setParentTypeDecl(TypeDecl.createRef(getTypeName()));
return result;
}
} }
syn String UntypedEndpointTarget.combinedName() = getTypeName() + "." + getChildName(); syn String UntypedEndpointTarget.combinedName() = getTypeName() + "." + getChildName();
...@@ -37,7 +50,7 @@ aspect ParserRewrites { ...@@ -37,7 +50,7 @@ aspect ParserRewrites {
eq UntypedEndpointTarget.parentTypeName() = "<untyped.parentTypeName>"; eq UntypedEndpointTarget.parentTypeName() = "<untyped.parentTypeName>";
eq UntypedEndpointTarget.entityName() = "<untyped.entityName>"; eq UntypedEndpointTarget.entityName() = "<untyped.entityName>";
eq UntypedEndpointTarget.isAlreadyDefined() = false; eq UntypedEndpointTarget.isAlreadyDefined() = false;
eq UntypedEndpointTarget.entityIsNormalAttribute() = false; eq UntypedEndpointTarget.hasAttributeResetMethod() = false;
eq UntypedEndpointTarget.targetTypeName() = "<untyped.targetTypeName>"; eq UntypedEndpointTarget.targetTypeName() = "<untyped.targetTypeName>";
eq UntypedEndpointTarget.isTypeEndpointTarget() = false; eq UntypedEndpointTarget.isTypeEndpointTarget() = false;
} }
...@@ -62,8 +62,10 @@ EndpointDefinition endpoint_definition_type ...@@ -62,8 +62,10 @@ EndpointDefinition endpoint_definition_type
; ;
EndpointTarget endpoint_target EndpointTarget endpoint_target
= ID.type_name DOT ID.child_name {: return new UntypedEndpointTarget(type_name, child_name); :} = ID.type_name DOT ID.child_name {: return new UntypedEndpointTarget(type_name, child_name, false); :}
| ID.type_name {: return new UntypedEndpointTarget(type_name, ""); :} | ID.type_name DOT ID.child_name BRACKET_LEFT ID.attribute_type_name BRACKET_RIGHT
{: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name, true); :}
| ID.type_name {: return new UntypedEndpointTarget(type_name, "", false); :}
; ;
ArrayList string_list ArrayList string_list
......
...@@ -8,3 +8,5 @@ ...@@ -8,3 +8,5 @@
"with" { return sym(Terminals.WITH); } "with" { return sym(Terminals.WITH); }
"indexed" { return sym(Terminals.INDEXED); } "indexed" { return sym(Terminals.INDEXED); }
"add" { return sym(Terminals.ADD); } "add" { return sym(Terminals.ADD); }
"(" { return sym(Terminals.BRACKET_LEFT); }
")" { return sym(Terminals.BRACKET_RIGHT); }
...@@ -110,6 +110,10 @@ aspect RagConnectHandler { ...@@ -110,6 +110,10 @@ aspect RagConnectHandler {
senders.forEach(Runnable::run); senders.forEach(Runnable::run);
} }
void run(RagConnectToken token) {
tokenToSender.get(token).run();
}
byte[] getLastValue() { byte[] getLastValue() {
return lastValue; return lastValue;
} }
...@@ -150,6 +154,10 @@ aspect RagConnectHandler { ...@@ -150,6 +154,10 @@ aspect RagConnectHandler {
java.util.Optional.ofNullable(publishers.get(index)).ifPresent(RagConnectPublisher::run); java.util.Optional.ofNullable(publishers.get(index)).ifPresent(RagConnectPublisher::run);
} }
void run(int index, RagConnectToken token) {
java.util.Optional.ofNullable(publishers.get(index)).ifPresent(publisher -> publisher.run(token));
}
byte[] getLastValue(int index) { byte[] getLastValue(int index) {
RagConnectPublisher publisher = publishers.get(index); RagConnectPublisher publisher = publishers.get(index);
if (publisher == null) { if (publisher == null) {
......
...@@ -87,22 +87,33 @@ aspect RagConnectObserver { ...@@ -87,22 +87,33 @@ aspect RagConnectObserver {
class RagConnectObserver implements ASTState.Trace.Receiver { class RagConnectObserver implements ASTState.Trace.Receiver {
class RagConnectObserverEntry { class RagConnectObserverEntry {
final RagConnectToken connectToken;
final ASTNode node; final ASTNode node;
final String attributeString; final String attributeString;
final boolean compareParams; final boolean compareParams;
final Object params; final Object params;
final Runnable attributeCall; final Runnable attributeCall;
final java.util.List<RagConnectToken> connectList = new java.util.ArrayList<>();
RagConnectObserverEntry(RagConnectToken connectToken, ASTNode node, String attributeString, RagConnectObserverEntry(ASTNode node, String attributeString,
boolean compareParams, Object params, Runnable attributeCall) { boolean compareParams, Object params, Runnable attributeCall) {
this.connectToken = connectToken;
this.node = node; this.node = node;
this.attributeString = attributeString; this.attributeString = attributeString;
this.compareParams = compareParams; this.compareParams = compareParams;
this.params = params; this.params = params;
this.attributeCall = attributeCall; this.attributeCall = attributeCall;
} }
boolean baseMembersEqualTo(RagConnectObserverEntry other) {
return baseMembersEqualTo(other.node, other.attributeString, other.compareParams, other.params);
}
boolean baseMembersEqualTo(ASTNode otherNode, String otherAttributeString,
boolean otherCompareParams, Object otherParams) {
return this.node.equals(otherNode) &&
this.attributeString.equals(otherAttributeString) &&
this.compareParams == otherCompareParams &&
(!this.compareParams || java.util.Objects.equals(this.params, otherParams));
}
} }
{{#configExperimentalJastAdd329}} {{#configExperimentalJastAdd329}}
...@@ -124,7 +135,7 @@ aspect RagConnectObserver { ...@@ -124,7 +135,7 @@ aspect RagConnectObserver {
{{#configExperimentalJastAdd329}} {{#configExperimentalJastAdd329}}
java.util.Set<RagConnectObserverEntry> entryQueue = new java.util.HashSet<>(); java.util.Set<RagConnectObserverEntry> entryQueue = new java.util.HashSet<>();
RagConnectObserverStartEntry startEntry = null; java.util.Deque<RagConnectObserverStartEntry> startEntries = new java.util.LinkedList<>();
{{/configExperimentalJastAdd329}} {{/configExperimentalJastAdd329}}
RagConnectObserver(ASTNode node) { RagConnectObserver(ASTNode node) {
...@@ -145,35 +156,65 @@ aspect RagConnectObserver { ...@@ -145,35 +156,65 @@ aspect RagConnectObserver {
{{#configLoggingEnabledForIncremental}} {{#configLoggingEnabledForIncremental}}
System.out.println("** observer add: " + node + " on " + attributeString + (compareParams ? " (parameterized)" : "")); System.out.println("** observer add: " + node + " on " + attributeString + (compareParams ? " (parameterized)" : ""));
{{/configLoggingEnabledForIncremental}} {{/configLoggingEnabledForIncremental}}
observedNodes.add(new RagConnectObserverEntry(connectToken, node, attributeString, // either add to an existing entry (with same node, attribute) or create new entry
compareParams, params, attributeCall)); boolean needNewEntry = true;
for (RagConnectObserverEntry entry : observedNodes) {
if (entry.baseMembersEqualTo(node, attributeString, compareParams, params)) {
entry.connectList.add(connectToken);
needNewEntry = false;
break;
}
}
if (needNewEntry) {
RagConnectObserverEntry newEntry = new RagConnectObserverEntry(node, attributeString,
compareParams, params, attributeCall);
newEntry.connectList.add(connectToken);
observedNodes.add(newEntry);
}
} }
void remove(RagConnectToken connectToken) { void remove(RagConnectToken connectToken) {
observedNodes.removeIf(entry -> entry.connectToken.equals(connectToken)); RagConnectObserverEntry entryToDelete = null;
for (RagConnectObserverEntry entry : observedNodes) {
entry.connectList.remove(connectToken);
if (entry.connectList.isEmpty()) {
entryToDelete = entry;
} }
}
if (entryToDelete != null) {
observedNodes.remove(entryToDelete);
}
}
@Override @Override
public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) { public void accept(ASTState.Trace.Event event, ASTNode node, String attribute, Object params, Object value) {
oldReceiver.accept(event, node, attribute, params, value); oldReceiver.accept(event, node, attribute, params, value);
{{#configExperimentalJastAdd329}} {{#configExperimentalJastAdd329}}
// react to INC_FLUSH_START and remember entry // react to INC_FLUSH_START and remember entry
if (event == ASTState.Trace.Event.INC_FLUSH_START && startEntry == null) { if (event == ASTState.Trace.Event.INC_FLUSH_START) {
{{#configLoggingEnabledForIncremental}} {{#configLoggingEnabledForIncremental}}
System.out.println("** observer start: " + node + " on " + attribute); System.out.println("** observer start: " + node + " on " + attribute);
{{/configLoggingEnabledForIncremental}} {{/configLoggingEnabledForIncremental}}
startEntry = new RagConnectObserverStartEntry(node, attribute, value); startEntries.addFirst(new RagConnectObserverStartEntry(node, attribute, value));
return; return;
} }
// react to INC_FLUSH_END and process queued entries, if it matches start entry // react to INC_FLUSH_END and process queued entries, if it matches start entry
if (event == ASTState.Trace.Event.INC_FLUSH_END && if (event == ASTState.Trace.Event.INC_FLUSH_END) {
node == startEntry.node && if (startEntries.isEmpty()) {
{{#configLoggingEnabledForIncremental}}
System.out.println("** observer end without start! for " + node + " on " + attribute);
{{/configLoggingEnabledForIncremental}}
return;
}
RagConnectObserverStartEntry startEntry = startEntries.peekFirst();
if (node == startEntry.node &&
attribute == startEntry.attributeString && attribute == startEntry.attributeString &&
value == startEntry.flushIncToken) { value == startEntry.flushIncToken) {
// create a copy of the queue to avoid entering this again causing an endless recursion // create a copy of the queue to avoid entering this again causing an endless recursion
RagConnectObserverEntry[] entriesToProcess = entryQueue.toArray(new RagConnectObserverEntry[entryQueue.size()]); RagConnectObserverEntry[] entriesToProcess = entryQueue.toArray(new RagConnectObserverEntry[entryQueue.size()]);
entryQueue.clear(); entryQueue.clear();
startEntry = null; startEntries.removeFirst();
{{#configLoggingEnabledForIncremental}} {{#configLoggingEnabledForIncremental}}
System.out.println("** observer process (entries: " + entriesToProcess.length + "): " + node + " on " + attribute); System.out.println("** observer process (entries: " + entriesToProcess.length + "): " + node + " on " + attribute);
{{/configLoggingEnabledForIncremental}} {{/configLoggingEnabledForIncremental}}
...@@ -182,6 +223,7 @@ aspect RagConnectObserver { ...@@ -182,6 +223,7 @@ aspect RagConnectObserver {
} }
return; return;
} }
}
{{/configExperimentalJastAdd329}} {{/configExperimentalJastAdd329}}
// ignore all other events but INC_FLUSH_ATTR // ignore all other events but INC_FLUSH_ATTR
......
...@@ -18,7 +18,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete ...@@ -18,7 +18,7 @@ public boolean {{parentTypeName}}.{{connectMethodName}}(String {{connectParamete
}{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken); }{{#IndexBasedListAccess}}, index{{/IndexBasedListAccess}}, connectToken);
{{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); {{updateMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
if (writeCurrentValue) { if (writeCurrentValue) {
{{writeMethodName}}({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); {{writeMethodName}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}connectToken);
} }
success = true; success = true;
break; break;
...@@ -97,9 +97,9 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam ...@@ -97,9 +97,9 @@ public boolean {{parentTypeName}}.{{disconnectMethodName}}(String {{connectParam
} }
protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) { protected boolean {{parentTypeName}}.{{updateMethodName}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) {
{{^shouldSendValue}} {{^shouldNotResetValue}}
{{tokenResetMethodName}}(); {{tokenResetMethodName}}();
{{/shouldSendValue}} {{/shouldNotResetValue}}
{{> mappingApplication}} {{> mappingApplication}}
{{lastValueSetter}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}{{lastResult}}); {{lastValueSetter}}({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}{{lastResult}});
// normally we would return true here. unless no connect method was called so far to initialize {{senderName}} yet // normally we would return true here. unless no connect method was called so far to initialize {{senderName}} yet
...@@ -110,6 +110,10 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i ...@@ -110,6 +110,10 @@ protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}i
{{senderName}}.run({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}}); {{senderName}}.run({{#IndexBasedListAccess}}index{{/IndexBasedListAccess}});
} }
protected void {{parentTypeName}}.{{writeMethodName}}({{#IndexBasedListAccess}}int index, {{/IndexBasedListAccess}}RagConnectToken token) {
{{senderName}}.run({{#IndexBasedListAccess}}index, {{/IndexBasedListAccess}}token);
}
{{#needForwardingNTA}} {{#needForwardingNTA}}
syn {{{forwardingNTA_Type}}} {{parentTypeName}}.{{forwardingNTA_Name}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) = {{realGetterMethodCall}}.{{touchedTerminalsMethodName}}(); syn {{{forwardingNTA_Type}}} {{parentTypeName}}.{{forwardingNTA_Name}}({{#IndexBasedListAccess}}int index{{/IndexBasedListAccess}}) = {{realGetterMethodCall}}.{{touchedTerminalsMethodName}}();
{{/needForwardingNTA}} {{/needForwardingNTA}}
...@@ -370,7 +370,6 @@ task compileTreeIncremental(type: RagConnectTest) { ...@@ -370,7 +370,6 @@ task compileTreeIncremental(type: RagConnectTest) {
inputFiles = [file('src/test/01-input/tree/Test.relast'), inputFiles = [file('src/test/01-input/tree/Test.relast'),
file('src/test/01-input/tree/Test.connect')] file('src/test/01-input/tree/Test.connect')]
rootNode = 'Root' rootNode = 'Root'
logWrites = true
} }
relast { relast {
useJastAddNames = true useJastAddNames = true
...@@ -413,7 +412,6 @@ task compileTreeAllowedTokensIncremental(type: RagConnectTest) { ...@@ -413,7 +412,6 @@ task compileTreeAllowedTokensIncremental(type: RagConnectTest) {
inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.relast'), inputFiles = [file('src/test/01-input/treeAllowedTokens/Test.relast'),
file('src/test/01-input/treeAllowedTokens/Test.connect')] file('src/test/01-input/treeAllowedTokens/Test.connect')]
rootNode = 'Root' rootNode = 'Root'
logWrites = true
} }
relast { relast {
useJastAddNames = true useJastAddNames = true
...@@ -604,9 +602,6 @@ task compileIndexedSendIncremental(type: RagConnectTest, dependsOn: ':ragconnect ...@@ -604,9 +602,6 @@ task compileIndexedSendIncremental(type: RagConnectTest, dependsOn: ':ragconnect
inputFiles = [file('src/test/01-input/indexedSend/Test.relast'), inputFiles = [file('src/test/01-input/indexedSend/Test.relast'),
file('src/test/01-input/indexedSend/Test.connect')] file('src/test/01-input/indexedSend/Test.connect')]
rootNode = 'Root' rootNode = 'Root'
logWrites = true
logReads = true
logIncremental = true
extraOptions = ['--experimental-jastadd-329'] extraOptions = ['--experimental-jastadd-329']
} }
relast { relast {
...@@ -621,3 +616,25 @@ task compileIndexedSendIncremental(type: RagConnectTest, dependsOn: ':ragconnect ...@@ -621,3 +616,25 @@ task compileIndexedSendIncremental(type: RagConnectTest, dependsOn: ':ragconnect
extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
} }
} }
// --- Test: attribute-incremental ---
task compileAttributeIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
ragconnect {
outputDir = file('src/test/02-after-ragconnect/attributeInc')
inputFiles = [file('src/test/01-input/attribute/Test.relast'),
file('src/test/01-input/attribute/Test.connect')]
rootNode = 'Root'
extraOptions = ['--experimental-jastadd-329']
}
relast {
useJastAddNames = true
grammarName = 'src/test/03-after-relast/attributeInc/attributeInc'
serializer = 'jackson'
}
jastadd {
jastAddList = 'JastAddList'
packageName = 'attributeInc.ast'
inputFiles = [file('src/test/01-input/attribute/Test.jadd')]
extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
}
}
# Attribute
Idea: Use send definitions for attributes.
send SenderRoot.basic(String) ;
send SenderRoot.simple(String) ;
send SenderRoot.transformed(int) ;
send SenderRoot.toReferenceType(A) ;
send SenderRoot.toNTA(A) ;
AddSuffix maps A a to A {:
A result = new A();
String changedValue = a.getValue() + "post";
result.setValue(changedValue);
result.setInner(new Inner("inner" + a.getInner().getInnerValue()));
return result;
:}
AddStringSuffix maps String s to String {:
return s + "post";
:}
AddPlusOne maps int i to int {:
return i + 1;
:}
receive ReceiverRoot.FromBasic;
receive ReceiverRoot.FromSimpleNoMapping;
receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix;
receive ReceiverRoot.FromTransformedNoMapping;
receive ReceiverRoot.FromTransformedWithMapping using AddPlusOne;
receive ReceiverRoot.FromReferenceTypeNoMapping;
receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix;
receive ReceiverRoot.FromNTANoMapping;
receive ReceiverRoot.FromNTAWithMapping using AddSuffix;
aspect Computation {
syn String SenderRoot.basic() = getInput();
syn String SenderRoot.simple() = getInput() + "Post";
syn int SenderRoot.transformed() = Integer.parseInt(getInput());
syn A SenderRoot.toReferenceType() {
A result = new A();
result.setValue(getInput());
Inner inner = new Inner();
inner.setInnerValue("1");
result.setInner(inner);
return result;
}
syn nta A SenderRoot.toNTA() {
A result = new A();
result.setValue(getInput());
Inner inner = new Inner();
inner.setInnerValue("2");
result.setInner(inner);
return result;
}
}
aspect MakeCodeCompile {
}
aspect MakeCodeWork {
}
aspect NameResolution {
// overriding customID guarantees to produce the same JSON representation for equal lists
// otherwise, the value for id is different each time
@Override
protected String A.customID() {
return getClass().getSimpleName() + getValue();
}
@Override
protected String Inner.customID() {
return getClass().getSimpleName() + getInnerValue();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment