|
|
|
|
|
## Terms/Concepts
|
|
# Terms/Concepts
|
|
|
|
|
|
- **Definition type**: they define when to read from and write to mqtt, when to update tokens, and when to call or invalidate attributes. there will be a small DSL to define those
|
|
- **Definition type**: they define when to read from and write to mqtt, when to update tokens, and when to call or invalidate attributes. there will be a small DSL to define those
|
|
- **Connection**: on instance/AST-level, an mqtt-topic can be associated to a source or target of a definition
|
|
- **Connection**: on instance/AST-level, an mqtt-topic can be associated to a source or target of a definition
|
|
- **Transformation**: a method or attribute modeling a math. function y = f(x)
|
|
- **Transformation**: a method or attribute modeling a math. function y = f(x)
|
|
- **Operator**: the atomar building blocks of a transformation, e.g., projection of an object, or a boolean operator
|
|
- **Operator**: the atomar building blocks of a transformation, e.g., projection of an object, or a boolean operator
|
|
|
|
|
|
|
|
# Ros2Rag Really Generating
|
|
|
|
|
|
|
|
Sources:
|
|
|
|
|
|
|
|
- UpdateDefinition `read Joint.CurrentPosition using ParseLinkState, LinkStateToIntPosition ;`
|
|
|
|
- DependencyDefinition `RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1 ;`
|
|
|
|
- MappingDefinition
|
|
|
|
```
|
|
|
|
ParseLinkState maps byte[] bytes to panda.Linkstate.PandaLinkState {:
|
|
|
|
return panda.Linkstate.PandaLinkState.parseFrom(bytes);
|
|
|
|
:}
|
|
|
|
```
|
|
|
|
|
|
|
|
## UpdateDefinition
|
|
|
|
|
|
|
|
```java=
|
|
|
|
abstract UpdateDefinition ::= <AlwaysApply:Boolean> ;
|
|
|
|
|
|
|
|
rel UpdateDefinition.Mapping* -> MappingDefinition;
|
|
|
|
|
|
|
|
abstract TokenUpdateDefinition : UpdateDefinition;
|
|
|
|
rel TokenUpdateDefinition.Token -> TokenComponent;
|
|
|
|
|
|
|
|
ReadFromMqttDefinition : TokenUpdateDefinition;
|
|
|
|
WriteToMqttDefinition : TokenUpdateDefinition;
|
|
|
|
```
|
|
|
|
|
|
|
|
## ReadFromMqttDefinition
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
- `read Joint.CurrentPosition using ParseLinkState, LinkStateToIntPosition ;`
|
|
|
|
|
|
|
|
Idea:
|
|
|
|
|
|
|
|
- create connectMethod to setup new mqtt connection
|
|
|
|
|
|
|
|
Input:
|
|
|
|
|
|
|
|
- AlwaysApply:Boolean
|
|
|
|
- Mapping*:List of MappingDefinition
|
|
|
|
- Token:TokenComponent
|
|
|
|
|
|
|
|
Calculated:
|
|
|
|
|
|
|
|
- parentType:TypeDecl == Token.containingTypeDecl
|
|
|
|
|
|
|
|
Code:
|
|
|
|
|
|
|
|
```java=
|
|
|
|
public void ${parentType}.connect${Token.getName()}(String topic) {
|
|
|
|
_mqttUpdater().newConnection(topic, message -> {
|
|
|
|
// TODO: iterate over mapping, calling method, store result in fresh variable of matching type, maybe surround with try/catch?
|
|
|
|
// $if not AlwaysApply:
|
|
|
|
// $if Token.isPrimitiveType:
|
|
|
|
if (get${Token.getName()}() == ${lastResult}) { return; }
|
|
|
|
// $else (use equals)
|
|
|
|
if (get${Token.getName()}().equals(${lastResult})) { return; }
|
|
|
|
// $fi
|
|
|
|
// $fi
|
|
|
|
set${Token.getName()}(${lastResult});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Warnings/Errors to check for:
|
|
|
|
|
|
|
|
- the token must be resolvable within the parent type
|
|
|
|
- the Token must not be a TokenNTA (i.e., check for `!Token.getNTA()`)
|
|
|
|
- type of first mapping must be `byte[]`
|
|
|
|
- type of last mapping must be type of the Token
|
|
|
|
- types of mappings must match (modulo inheritance)
|
|
|
|
- for all type checks, there are three cases regarding the two types to check against:
|
|
|
|
- 1) both are nonterminal types, check with grammar
|
|
|
|
- 2) both are known classes, check with `Class.forName()` and subclass-checking-methods
|
|
|
|
- 3) otherwise issue warning, that types could not be matched
|
|
|
|
|
|
|
|
## WriteToMqttDefinition
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
- `write RobotArm.AppropriateSpeed using CreateSpeedMessage, SerializeRobotConfig ;`
|
|
|
|
|
|
|
|
Idea:
|
|
|
|
|
|
|
|
- create write topic variable (type String) in parentType, initially `null`
|
|
|
|
- create last value variable (type _effectiveWriteType_ == type of last mapping), initially `null`
|
|
|
|
- create connect method to setup topic and update lastValue, and maybe write initial value (see parameter `boolean writeCurrentValue`)
|
|
|
|
- create update method to reset token NTA, apply mappings, check if equal (return `false`), and call write method otherwise (return `true`)
|
|
|
|
- create write method to publish the last value
|
|
|
|
|
|
|
|
Input:
|
|
|
|
|
|
|
|
- AlwaysApply:Boolean
|
|
|
|
- Mapping*:List of MappingDefinition
|
|
|
|
- Token:TokenComponent
|
|
|
|
|
|
|
|
Calculated:
|
|
|
|
|
|
|
|
- parentType:TypeDecl
|
|
|
|
|
|
|
|
Attributes on this used in generation process:
|
|
|
|
|
|
|
|
```java=
|
|
|
|
writeTopic() = _topic_${Token.getName()}
|
|
|
|
syn String WriteToMqttDefinition.lastValue() = "_lastValue_" + getToken().getName();
|
|
|
|
connectMethod() = connect${Token.getName()}
|
|
|
|
updateMethod() = _update_${Token.getName()}
|
|
|
|
writeMethod() = _write_${Token.getName()}_lastValue
|
|
|
|
tokenResetMethod = get${Token.getName()}_reset()
|
|
|
|
```
|
|
|
|
|
|
|
|
Code:
|
|
|
|
|
|
|
|
```java=
|
|
|
|
private String ${parentType()}.${writeTopic()} = null;
|
|
|
|
private byte[] ${parentType()}.${lastValue()} = null;
|
|
|
|
|
|
|
|
// connect method
|
|
|
|
public void ${parentType()}.${connectMethod()}(String topic, boolean writeCurrentValue) {
|
|
|
|
${writeTopic()} = topic;
|
|
|
|
${updateMethod()}();
|
|
|
|
if (writeCurrentValue) {
|
|
|
|
${writeMethod()}();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// update method
|
|
|
|
protected boolean ${parentType}.${updateMethod()}() {
|
|
|
|
${tokenResetMethod()}();
|
|
|
|
${typeOfFirstMappingParameterName} ${firstMappingParameterName} = get${Token.getName()}();
|
|
|
|
// TODO: iterate over mapping, calling method, store result in fresh variable of matching type, maybe surround with try/catch?
|
|
|
|
// $if not getAlwaysApply():
|
|
|
|
if (java.util.Arrays.equals(${lastResult}, ${lastValue()})) return false;
|
|
|
|
// $fi
|
|
|
|
${lastValue()} = ${lastResult};
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write method
|
|
|
|
protected void ${parentType}.${writeMethod()}() {
|
|
|
|
_mqttUpdater().publish(${writeTopic()}, ${lastValue()});
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Open questions:
|
|
|
|
|
|
|
|
- should update method be `public`? (to explicitly update value from outside)
|
|
|
|
- should write method be `public`? (to explicitly write value from outside)
|
|
|
|
- are there any effects when comparing `byte[]` (when not alwaysApply)
|
|
|
|
|
|
|
|
Warnings/Errors to check for:
|
|
|
|
|
|
|
|
- Token must be a TokenNTA (i.e., check for `Token.getNTA()`)
|
|
|
|
- type of first mapping must be type of Token
|
|
|
|
- type of last mapping must be `byte[]`
|
|
|
|
- types of mappings must match (modulo inheritance)
|
|
|
|
- for all type checks, there are three cases regarding the two types to check against:
|
|
|
|
- 1) both are nonterminal types, check with grammar
|
|
|
|
- 2) both are known classes, check with `Class.forName()` and subclass-checking-methods
|
|
|
|
- 3) otherwise issue warning, that types could not be matched
|
|
|
|
- no more than one write mapping for each TokenComponent
|
|
|
|
|
|
|
|
# MappingDefinition
|
|
|
|
|
|
|
|
```=
|
|
|
|
MappingDefinition ::= <ID> FromType:MappingDefinitionType <FromVariableName> ToType:MappingDefinitionType <Content> ;
|
|
|
|
abstract MappingDefinitionType ::= ;
|
|
|
|
JavaMappingDefinitionType : MappingDefinitionType ::= Type:JavaTypeUse ;
|
|
|
|
JavaArrayMappingDefinitionType : MappingDefinitionType ::= Type:JavaTypeUse ;
|
|
|
|
```
|
|
|
|
|
|
|
|
Example:
|
|
|
|
```java=
|
|
|
|
ParseLinkState maps byte[] bytes to panda.Linkstate.PandaLinkState {:
|
|
|
|
return panda.Linkstate.PandaLinkState.parseFrom(bytes);
|
|
|
|
:}
|
|
|
|
```
|
|
|
|
|
|
|
|
Idea: create static method within `ASTNode` accepting one parameter, and return their result
|
|
|
|
|
|
|
|
Input:
|
|
|
|
|
|
|
|
- ID:String
|
|
|
|
- FromType (Type or array of Type)
|
|
|
|
- FromVariableName:String
|
|
|
|
- ToType (Type or array of Type)
|
|
|
|
- Content
|
|
|
|
|
|
|
|
Code:
|
|
|
|
|
|
|
|
```java=
|
|
|
|
protected static ${ToType.printJavaType()} ASTNode.${ID}(${FromType.printJavaType()} ${FromVariableName}) {
|
|
|
|
${Content}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
# DependencyDefinition
|
|
|
|
|
|
|
|
```=
|
|
|
|
DependencyDefinition ::= <ID> ;
|
|
|
|
rel DependencyDefinition.Source -> TokenComponent ;
|
|
|
|
rel DependencyDefinition.Target -> TokenComponent ;
|
|
|
|
```
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
- `RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1 ;` (read Target canDependOn Source)
|
|
|
|
|
|
|
|
Idea:
|
|
|
|
|
|
|
|
- create new token with name _hiddenTokenName_ = `_internal` + _originalTokenName_
|
|
|
|
- create virtual getter delegating to now hidden token
|
|
|
|
- create virtual setter with observer notification
|
|
|
|
- create (internal) relation between parents of source and target
|
|
|
|
- create dependency method for convenience
|
|
|
|
|
|
|
|
Input:
|
|
|
|
|
|
|
|
- ID:String
|
|
|
|
- Source:TokenComponent
|
|
|
|
- Target:TokenComponent
|
|
|
|
|
|
|
|
Calculated:
|
|
|
|
|
|
|
|
- sourceParent:TypeDecl
|
|
|
|
- targetParent:TypeDecl
|
|
|
|
- targetUpdateDefinition:WriteToMqttDefinition
|
|
|
|
|
|
|
|
Attributes on this used in generation process:
|
|
|
|
|
|
|
|
```java=
|
|
|
|
internalRelationPrefix() = "_internal_" + ${ID};
|
|
|
|
internalTokenName() = "_internal" + ${Source.getName()};
|
|
|
|
```
|
|
|
|
|
|
|
|
Code:
|
|
|
|
|
|
|
|
```java=
|
|
|
|
// relation (to be added in grammar)
|
|
|
|
rel ${targetParent}.${internalRelationPrefix}Source* <-> ${sourceParent}.${internalRelationPrefix}Target* ;
|
|
|
|
|
|
|
|
// dependency method
|
|
|
|
public void ${targetParent}.add${ID}(${sourceParent} source) {
|
|
|
|
this.add${internalRelationPrefix}Source(source);
|
|
|
|
}
|
|
|
|
|
|
|
|
// virtual setter
|
|
|
|
public ${sourceParent.getName()} ${sourceParent.getName()}.set${Source.getName()}(${Source.getType()} value) {
|
|
|
|
set${internalTokenName()}(value);
|
|
|
|
for (${targetParent.getName()} target : get${internalRelationPrefix}Targets()) {
|
|
|
|
if (target.${targetUpdateDefinition().updateMethod()}()) {
|
|
|
|
target.${targetUpdateDefinition().writeMethod()}();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// virtual getter
|
|
|
|
public ${Source.getJavaTypeUse().generateAbstractGrammar(sb)} ${sourceParent.getName()}.get${Source.getName()}() {
|
|
|
|
return get${internalTokenName()};
|
|
|
|
}
|
|
|
|
|
|
|
|
// refine name of TokenComponent when writing out abstract grammar
|
|
|
|
refine BackendAbstractGrammar public void TokenComponent.generateAbstractGrammar(StringBuilder sb) {
|
|
|
|
// TODO, check if TokenComponent is in Source of a dependency definition
|
|
|
|
// if yes, then somehow change getName() to return internalTokenName()
|
|
|
|
// if not, return refined()
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Things to pay attention to:
|
|
|
|
|
|
|
|
- `JavaTypeUse().generateAbstractGrammar(sb)` adds the type to a given StringBuilder
|
|
|
|
|
|
|
|
Open questions:
|
|
|
|
|
|
|
|
- What is the best way to replace/change the Source-TokenComponent
|
|
|
|
- Replacing the TokenComponent in the original Program is probably not a good idea, because there may be references to the original TokenComponent, which then has no parent (and this may lead to NullPointerExceptions etc.)
|
|
|
|
- Setting a new name in the original TokenComponent defeats the purpose, because other definitions would use the internal token instead of the created virtual getter and setter
|
|
|
|
- Refining just `TokenComponent.generateAbstractGrammar` from the aspect `BackendAbstractGrammar` seems fine, but may lead to duplicate code
|
|
|
|
|
|
|
|
Warnings/Errors to check for:
|
|
|
|
|
|
|
|
- There must **not** be a write update definition for the source token
|
|
|
|
- This would create conflicting setter
|
|
|
|
- [name=rs] [time=May 6th, 2020] This could actually be wrong, as write definition do not create a setter
|
|
|
|
- There **must be** a write update definition for the target token
|
|
|
|
- Otherwise there are missing update and write methods used in the virtual setter |