Was this page helpful?

Inputs and Outputs

This page is showing examples in the target language CC++PythonTypeScriptRust. You can change the target language in the left sidebar.

In this section, we will endow reactors with inputs and outputs.

Input and Output Declarations

Input and output declarations have the form:

    input <name>:<type>
    output <name>:<type>
    input <name>
    output <name>

For example, the following reactor doubles its input and sends the result to the output:

target C;
reactor Double {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        lf_set(y, x->value * 2);
    =}
}

target Cpp;

reactor Double {
    input x:int;
    output y:int;
    reaction(x) -> y {=
        if (x.is_present()){
            y.set(*x.get() * 2);
        }
    =}
}


target Python;
reactor Double {
    input x;
    output y;
    reaction(x) -> y {=
        y.set(x.value * 2)
    =}
}
target TypeScript
reactor Double {
    input x:number
    output y:number
    reaction(x) -> y {=
        y = value * 2
    =}
}

target Rust;
reactor Double {
    input x:u32;
    output y:u32;
    reaction(x) -> y {=
        ctx.set(y, ctx.get(x).unwrap() * 2);
    =}
}

Notice how the input value is accessed and how the output value is set. This is done differently for each target language. See the Target Language Details for detailed documentation of these mechanisms. Setting an output within a reaction will trigger downstream reactions at the same Logical Time that the reaction is invoked (or, more precisely, at the same tag). If a particular output port is set more than once at any tag, the last set value will be the one that downstream reactions see. Since the order in which reactions of a reactor are invoked at a logical time is deterministic, and whether inputs are present depends only on their timestamps, the final value set for an output will also be deterministic.

The type of a port is a type in the target language plus the special type time. A type may also be specified using a code block, delimited by the same delimeters {= ... =} that separate target language code from Lingua Franca code in reactions. Any valid target-language type designator can be given within these delimiters.

The reaction declaration above indicates that an input event on port x is a trigger and that an output event on port y is a (potential) effect. A reaction can declare more than one trigger or effect by just listing them separated by commas. For example, the following reactor has two triggers and tests each input for presence before using it:

target C;
reactor Destination {
    input x:int;
    input y:int;
    reaction(x, y) {=
        int sum = 0;
        if (x->is_present) {
            sum += x->value;
        }
        if (y->is_present) {
            sum += y->value;
        }
        printf("Received %d.\n", sum);
    =}
}
target Cpp;

reactor Destination {
    input x:int;
    input y:int;
    reaction(x, y) {=
        int sum = 0;
        if (x.is_present()) {
            sum += *x.get();
        }
        if (y.is_present()) {
            sum += *y.get();
        }

        std::cout << "Received: " << sum << std::endl; 
    =}
}

target Python;
reactor Destination {
    input x;
    input y;
    reaction(x, y) {=
        sum = 0
        if x.is_present:
            sum += x.value
        if y.is_present:
            sum += y.value
        print(f"Received {sum}")
    =}
}
target TypeScript
reactor Destination {
    input x:number
    input y:number
    reaction(x, y) {=
        let sum = 0
        if (x !== undefined) {
            sum += x
        }
        if (y !== undefined) {
            sum += y
        }
        console.log(`Received ${sum}.`)
    =}
}

target Rust;
reactor Destination {
    input x:u32;
    input y:u32;
    reaction(x, y) {=
        let mut sum = 0;
        if let Some(x) = ctx.get(x) {
            sum += x;
        }
        if let Some(y) = ctx.get(y) {
            sum += y;
        }
        println!("Received {}.", sum);
    =}
}

NOTE: if a reaction fails to test for the presence of an input and reads its value anyway, then the result it will get is target dependent. In the C target, the value read will be the most recently seen input value, or, if no input event has occurred at an earlier logical time, then zero or NULL, depending on the datatype of the input. In the C++ target, a smart pointer is returned for present values and nullptr if the value is not present. FIXME. In the TS target, the value will be undefined, a legitimate value in TypeScript. FIXME.

Triggers, Effects, and Uses

The general form of a reaction is

reaction (<triggers>) <uses> -> <effects> {=
    <target language code>
=}

The triggers field can be a comma-separated list of input ports, output ports of contained reactors, timers, actions, or the special events startup and shutdown. There must be at least one trigger for each reaction. A reaction with a startup trigger is invoked when the program begins executing, and a reaction with a shutdown trigger is invoked at the end of execution.

The uses field, which is optional, specifies input ports (or output ports of contained reactors) that do not trigger execution of the reaction but may be read by the reaction.

The effects field, which is also optional, is a comma-separated lists of output ports ports, input ports of contained reactors, or actions.

Setting an Output Multiple Times

If one or more reactions set an output multiple times at the same tag, then only the last value set will be seen by any downstream reactors.

If a reaction wishes to test whether an output has been previously set at the current tag by some other reaction, it can test it in the same way it tests inputs for presence.

Mutable Inputs

Normally, a reaction does not modify the value of an input. An input is said to be immutable. The degree to which this is enforced varies by target language. Most of the target languages make it rather difficult to enforce, so the programmer needs to avoid modifying the input. Modifying an input value may lead to nondeterministic results.

Occassionally, it is useful to modify an input. For example, the input may be a large data structure, and a reaction may wish to make a small modification and forward the result to an output. To accomplish this, the programmer should declare the input mutable as follows:

mutable input <name>:<type>;
mutable input <name>;

This is a directive to the code generator indicating that reactions that read this input may also modify the value of the input. The code generator will attempt to optimize the scheduling to avoid copying the input value, but this may not be possible, in which case it will automatically insert a copy operation, making it safe to modify the input. The target-spefic reference documentation has more details about how this works.

Lingua Franca is an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
Eeal  (14)
SBSoroush Bateni  (5)
CMChristian Menard  (1)
Rrevol-xut  (1)

Last updated: May 27, 2022