Skip to content

The RagConnect Specification Language

To declare ports and mappings, a domain-specific language (DSL) is used.

Ports

A port marks an element of an AST as sending or receiving element. The kind of the element determines, whether an port for it can be receiving, sending, or both at the same time.

To declare a new ports, use the following syntax:

("send"|"receive") ["indexed"] ["with add"] <Non-Terminal>[.<Target>["(<AttributeType>)"]] ["using" <Mapping-Name> (, <Mapping-Name>)*] ";"

A breakdown of the parts of that syntax:

  • The first word (send or receive) defines the kind of port - sending or receiving, respectively.
  • The optional indexed applies only for list children and lets the port act on elements of that list. This only works for receiving ports, and is further changed by with add.
    • A lonely indexed assigns each incoming "topic" to an index in a list. This can be useful if multiple instances of this port are connected, or the communication protocol supports wildcard topics. For the former case, the connect method with an explicit index can be used, whereas the "normal" connect method without the index acts as a method for "wildcard-connect".
    • Combining indexed with add, incoming data is required to be an element of the list, and will be appended to the list.
  • The second optional keyword with add can also be used only for receiving ports targeting a list children. 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.
  • 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 port 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 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, a port can use one or more mappings. They will be applied before sending, or after receiving a message. Mappings will always be applied in the order they are listed after using.

Context-Free Ports

A port with only a non-terminal and without a target is called context-free port. Specifying such a port has several consequences:

  • The given non-terminal can be connected to in all contexts it occurs as if there were ports for all those contexts.
  • There is a special method available on the given non-terminal to connect itself, which selects the correct connect-method depending on its context.
  • Context-sensitive ports for this non-terminal can still be specified to modify mappings in this context. If the context is a list, the port must use indexed and cannot use with add.

Example:

// grammar
Root ::= A SingleA:A [OptA:A] ListA:A* ;
A ::= <Value> ;

// connect
receive A;
receive Root.SingleA using MyMapping; // specialized port

Implied, additional connect specifications:

receive Root.A;
receive Root.OptA;
receive indexed Root.ListA;

Application code:

A a = root.getOptA();
// new method on A:
a.connect("<some-uri-to-connect>");
// equivalent to (implicitly generated):
root.connectOptA("<some-uri-to-connect>");

Mappings

A mapping is a side effect-free function with one argument (the value that will be transformed) and one result (the transformed value), that will be applied on a value to be sent for a sending port, a received value for a receiving port, or the result of another mapping. Mappings can be shared between ports.

To declare a mapping, use the following syntax:

<Mapping-Name> "maps" <From-Type> <Input-Variable-Name> "to" "To-Type" "{:"
  <Java-Block>
":}"

A breakdown of the parts of that syntax:

  • The <Mapping-Name> identifies the mapping.
  • The <From-Type is the type of the input. The type of the first mapping of a receiving port must be byte[].
  • To refer to the input, <Input-Variable-Name> defines the name of it.
  • The <To-Type> is the type of the result. The type of the last mapping of a sending port must be byte[].
  • Finally, the <Java-Block> is the actual definition of the mapping using normal Java syntax. The previously defined input variable can be used via its name here. This block can contain multiple statements, but must end with a return statement. The validity of this block is not verified by RagConnect itself, but later in the compilation process by the Java compiler.

Note: There are default mappings provided for all primitive Java types (using their "normal" byte representation), and for all non-terminal types (using their JSON representation converted from/to bytes). Those default mappings apply to both sending and receiving ports, and match their counterparts, e.g., the mapping from int to byte[] uses the same byte representation as the mapping back from byte[] to int. Default mappings are always inserted if either no mapping is present, or if the type of the first/last mapping is not byte[] as stated above. Their main intent is to allow quick prototyping without constraining a more complex use case.

Dependency definitions

Note

Deprecated since 1.0.0

A dependency definition describes a possible dependency on type-level from a token to an attribute. Whenever the token changes, the attribute is eagerly re-computed and ports attached to it are triggered.

Such a dependency must be added on instance-level for every token that could have an influence to the attribute. An alternative for those explicit dependency definitions is incremental dependency tracking.

To declare a dependency definition, use the following syntax:

<Non-Terminal-1>.<Target> "canDependOn" <Non-Terminal-2>.<Token-Name> "as" <Dependency-Name> ";"

A breakdown of the parts of that syntax:

  • <Non-Terminal-1>.<Target> denotes the attribute (and the non-terminal it is defined on) which depends on the token
  • <Non-Terminal-2>.<Token-Name> denotes the token (and the non-terminal it is defined on) that (potentially) influences the attribute value
  • <Dependency-Name> identifies the dependency definition and is used for the generated method, which will be defined on Non-Terminal-1 as <Non-Terminal-1>.add<Dependency-Name>(<Non-Terminal-2> influencingNode)

Last update: September 6, 2022 12:51:30