using.md 5.7 KB
Newer Older
René Schöne's avatar
René Schöne committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# Using `ragconnect` -- an example

## Preparation

The following examples are inspired by the real test case [read1write2](https://git-st.inf.tu-dresden.de/jastadd/ragconnect-tests/-/tree/master/ragconnect.tests/src/test/01-input/read1write2)
The idea is to have two nonterminals, where input information is received on one of them, and - after transformation - is sent out by both.

Let the following grammar be used:

```bnf
A ::= <Input:String> /<OutputOnA:String>/ B* ;
B ::= /<OutputOnB:String>/ ;
```

To declare receiving and sending tokens, a dedicated DSL is used:

```java
// endpoint definitions
receive A.Input ;
send A.OutputOnA ;
send B.OutputOnB using Transformation ;

// mapping definitions
Transformation maps String s to String {:
  return s + "postfix";
:}
```

This defines `A.Input` to receive updates, and the other two tokens to send their value, whenever it changes.
Additionally, a transformation will be applied on `B.OutputOnB` before sending out its value.

Such mapping definitions can be defined for receiving tokens as well.
In this case, they are applied before the value is set.
If no mapping definition is given, or if the required type (depending on the communication protocol, see later) does not match, a "default mapping definition" is used to avoid boilerplate code converting from or to primitive types.

Furthermore, let the following attribute definitions be given:

```java
syn String A.getOutputOnA() = "a" + getInput();

syn String B.getOutputOnB() = "b" + input();
inh String B.input();
eq A.getB().input() = getInput();
```

In other words, `OutputOnA` depends on `Input` of the same node, and `OutputOnB` depends on `Input` of its parent node.
Currently, those dependencies have to be explicitely written down. It is expected, that in future version, this won't be necessary anymore.
This happens also in the DSL (dependencies have to be named to uniquely identify them):

```java
// dependency definitions
A.OutputOnA canDependOn A.Input as dependencyA ;
B.OutputOnB canDependOn A.Input as dependencyB ;
```

## Using generated code

After specifying everything, code will be generated if [setup properly](adding).
Let's create an AST in some driver code:

```java
A a = new A();
// set some default value for input
a.setInput("");
B b1 = new B();
B b2 = new B();
a.addB(b1);
a.addB(b2);
```

Now, we have to set the dependencies as described earlier.

```java
// a.OutputOnA -> a.Input
a.addDependencyA(a);
// b1.OutputOnB -> a.Input
b1.addDependencyB(a);
// b2.OutputOnB -> a.Input
b2.addDependencyB(a);
```

Finally, we can actually _connect_ the tokens.
Depending on the enabled protocols, [different URI schemes are allowed](#communication-protocol-characteristics).
In this example, we use the default protocol: MQTT.

```java
a.connectInput("mqtt://localhost/topic/for/input");
a.connectOutputOnA("mqtt://localhost/a/out", true);
b1.connectOutputOnB("mqtt://localhost/b1/out", true);
b2.connectOutputOnB("mqtt://localhost/b2/out", false);
```

The first parameter of those connect-methods is always an URI-like String, to identify the protocol to use, the server operating the protocol, and a path to identify the concrete token.
In case of MQTT, the server is the host running an MQTT broker, and the path is equal to the topic to publish or subscribe to.
Please note, that the first leading slash (`/`) is removed for MQTT topics, e.g., for `A.Input` the topic is actually `topic/for/input`.

For sending endpoints, there is a second boolean parameter to specify whether the current value shall be send immediately after connecting.

## Communication protocol characteristics

| Protocol | URI scheme | Default port | Type for mapping definitions | Remarks |
|-|-|-|-|-|
| `mqtt` | `mqtt://<broker-host>[:port]/<topic>` | 1883 | `byte[]` | First leading slash not included in topic. |
| `rest` | `rest://localhost[:port]/<path>` | 4567 | `String` | Host is always `localhost`. |

## Compiler options

The compiler is JastAdd-compliant, i.e., it accepts all flags available for JastAdd, though there is no process how to chain pre-processors _yet_.
Additional options are as follows.

| Name | Required (Default) | Description |
|-|-|-|
| `--rootNode` | Yes | Root node in the base grammar. |
| `--protocols` | No (`mqtt`) | Protocols to enable, currently available: `mqtt, rest`. |
| `--printYaml` | No (false) | Print out YAML instead of generating files. |
| `--verbose` | No (false) | Print more messages while compiling. |
| `--logReads` | No (false) | Enable logging for every received message. |
| `--logWrites` | No (false) | Enable logging for every sent message. |
| `--version` | No (false) | Print version info and exit (reused JastAdd option) |
| `--o` | No (`.`) | Output directory (reused JastAdd option) |

All files to be process have to be passed as arguments. Their type is decided by the file extension (`ast` and `relast` for input grammars, `connect` and `ragconnect` for ragconnect definitions file).

## Remarks

When constructing the AST and connecting it, one should always set dependencies before connecting, especially if updates already arriving for receiving endpoints.
Otherwise, updates might not be propagated after setting dependencies, if values are equal after applying transformations of mapping definitions.

As an example, when using the following grammar and ragconnect-definitions ...

```bnf
A ::= <Input:int> /<Output:String>/ ;
```

```java
receive A.Input using Round ;
send A.Output ;

A.Output canDependOn A.Input as dependency1 ;

Round maps float f to int {:
  return Math.round(f);
:}
```

... connecting first could mean to store the first rounded value and not propagating this update, since no dependencies are set, and not propagating further updates leading to the same rounded value even after setting the dependencies.