Skip to main content
Version: 0.5.0

Preambles

This article has examples in the following target languages:

Preamble​

Reactions may contain arbitrary target-language code, but often it is convenient for that code to invoke external libraries or to share procedure definitions. For either purpose, a reactor may include a preamble section.

For example, the following reactor uses the math C library for its trigonometric functions:

main reactor { preamble {= #include <math.h> =} reaction(startup) {= printf("The cosine of 1 is %f.\n", cos(1)); =} }

This will print:

The cosine of 1 is 0.540302.

By putting the #include in the preamble, the library becomes available in all reactions of this reactor. If you wish to have the library available in all reactors in the same file, you can provide the preamble outside the reactor, as shown here:

preamble {= #include <math.h> =} reactor Cos { reaction(startup) {= printf("The cosine of 1 is %f.\n", cos(1)); =} } reactor Sin { reaction(startup) {= printf("The sine of 1 is %f.\n", sin(1)); =} } main reactor { c = new Cos() s = new Sin() }

You can also use the preamble to define functions that are shared across reactions within a reactor, as in this example:

main reactor { preamble {= int add_42(int i) { return i + 42; } =} reaction(startup) {= printf("42 plus 42 is %d.\n", add_42(42)); =} reaction(startup) {= printf("42 plus 1 is %d.\n", add_42(1)); =} }

Not surprisingly, this will print:

42 plus 42 is 84.
42 plus 1 is 43.

(The order in which these are printed is arbitrary because the reactions can execute in parallel.)

To share a function across reactors, however, is a bit trickier. A preamble that is put outside the reactor definition can only contain declarations not definitions of functions or variables. The following code, for example will fail to compile:

preamble {= int add_42(int i) { return i + 42; } =} reactor Add_42 { reaction(startup) {= printf("42 plus 42 is %d.\n", add_42(42)); =} } reactor Add_1 { reaction(startup) {= printf("42 plus 1 is %d.\n", add_42(1)); =} } main reactor { a = new Add_42() b = new Add_1() }

The compiler will issue a duplicate symbol error because the function definition gets repeated in the separate C files generated for the two reactor classes, Add_42 and Add_1. When the compiled C code gets linked, the linker will find two definitions for the function add_42.

To correct this compile error, the file-level preamble should contain only a declaration, not a definition, as here:

preamble {= int add_42(int i); =} reactor Add_42 { reaction(startup) {= printf("42 plus 42 is %d.\n", add_42(42)); =} } reactor Add_1 { reaction(startup) {= printf("42 plus 1 is %d.\n", add_42(1)); =} } main reactor { preamble {= int add_42(int i) { return i + 42; } =} a = new Add_42() b = new Add_1() }

The function definition here is put into the main reactor, but it can be put in any reactor defined in the file.

Most header files contain only declarations, and hence can be safely included using #include in a file-level preamble. If you wish to use a header file that includes both declarations and definitions, then you will need to include it within each reactor that uses it.

If you wish to share variables across reactors, similar constraints apply. Note that sharing variables across reactors is strongly discouraged because it can undermine the determinacy of Lingua Franca, and you may have to implement mutual-exclusion locks to access such variables. But it is occassionaly justfiable, as in the following example:

preamble {= extern const char shared_string[]; =} reactor A { reaction(startup) {= printf("Reactor A says %s.\n", shared_string); =} } reactor B { reaction(startup) {= printf("Reactor B says %s.\n", shared_string); =} } main reactor { preamble {= const char shared_string[] = "Hello"; =} a = new A() b = new B() }

Notice the use of the extern keyword in C, which is required because the definition of the shared_string variable will be in a separate (code-generated) C file, the one for main, not the ones for A and B.

One subtlety is that if you define symbols that you will use in input, output, or state declarations, then the symbols must be defined in a file-level preamble. Specifically, the following code will fail to compile:

main reactor { preamble {= typedef int foo; =} state x:foo = 0 reaction(startup) {= lf_print("State is %d", self->x); =} }

The compiler will issue an unknown type name error. To correct this, just move the declaration to a file-level preamble:

preamble {= typedef int foo; =} main reactor { state x:foo = 0 reaction(startup) {= lf_print("State is %d", self->x); =} }