Skip to main content
Sign in
Snippets Groups Projects

Changes

Page history
  • New page
  • Templates
  • Update home, add code generation help authored by René Schöne's avatar René Schöne
    ## 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