Overview

Céu provides Structured Synchronous Reactive Programming, extending classical structured programming with two main functionalities:

  • Event Handling:
    • An await statement to suspend a line of execution and wait for an input event from the environment.
    • An emit statement to signal an output event back to the environment.
  • Concurrency:
    • A set of parallel constructs to compose concurrent lines of execution.

The lines of execution in Céu, known as trails, react all together to input events one after another, in discrete steps. An input event is broadcast to all active trails, which share the event as their unique and global time reference.

The program that follows blinks a LED every second and terminates on a button press:

input  void BUTTON;
output bool LED;
par/or do
    await BUTTON;
with
    loop do
        await 1s;
        emit LED(true);
        await 1s;
        emit LED(false);
    end
end

The synchronous concurrency model of Céu greatly diverges from multithreaded and actor-based models (e.g. pthreads and erlang). On the one hand, there is no real parallelism at the synchronous kernel of the language (i.e., no multi-core execution). On the other hand, accesses to shared variables among trails are deterministic and do not require synchronization primitives (i.e., locks or queues).

Céu provides static memory management based on lexical scopes and does not require a garbage collector.

Céu integrates safely with C, particularly when manipulating external resources (e.g., file handles). Programs can make native calls seamlessly while avoiding common pitfalls such as memory leaks and dangling pointers.

Céu is free software.

Environments

As a reactive language, Céu depends on an external host platform, known as an environment, which exposes input and output events programs can use.

An environment senses the world and broadcasts input events to programs. It also intercepts programs signalling output events to actuate in the world:

As examples of typical environments, an embedded system may provide button input and LED output, and a video game engine may provide keyboard input and video output.

Synchronous Execution Model

Céu is grounded on a precise definition of logical time (as opposed to physical) as a discrete sequence of input events: a sequence because only a single input event is handled at a logical time; discrete because reactions to events are guaranteed to execute in bounded physical time (see Bounded Execution).

The execution model for Céu programs is as follows:

  1. The program initiates the boot reaction from the first line of code in a single trail.
  2. Active trails, one after another, execute until they await or terminate. This step is named a reaction chain, and always runs in bounded time. New trails can be created with parallel compositions.
  3. The program goes idle.
  4. On the occurrence of a new input event, all trails awaiting that event awake. It then goes to step 2.

The synchronous execution model of Céu is based on the hypothesis that reaction chains run infinitely faster in comparison to the rate of input events. A reaction chain, aka external reaction, is the set of computations that execute when an input event occurs. Conceptually, a program takes no time on step 2 and is always idle on step 3. In practice, if a new input event occurs while a reaction chain is running (step 2), it is enqueued to run in the next reaction. When multiple trails are active at a logical time (i.e. awaking from the same event), Céu schedules them in the order they appear in the program text. This policy is arbitrary, but provides a priority scheme for trails, and also ensures deterministic and reproducible execution for programs. At any time, at most one trail is executing.

The program and diagram below illustrate the behavior of the scheduler of Céu:

 1:  input void A, B, C;  // A, B, and C are input events
 2:  par/and do
 3:      // trail 1
 4:      <...>            // <...> represents non-awaiting statements
 5:      await A;
 6:      <...>
 7:  with
 8:      // trail 2
 9:      <...>
10:      await B;
11:      <...>
12:  with
13:      // trail 3
14:      <...>
15:      await A;
16:      <...>
17:      await B;
18:      par/and do
19:          // trail 3
20:          <...>
21:      with
22:          // trail 4
23:          <...>
24:      end
25:  end

The program starts in the boot reaction and forks into three trails. Respecting the lexical order of declaration for the trails, they are scheduled as follows (t0 in the diagram):

  • trail-1 executes up to the await A (line 5);
  • trail-2 executes up to the await B (line 10);
  • trail-3 executes up to the await A (line 15).

As no other trails are pending, the reaction chain terminates and the scheduler remains idle until the event A occurs (t1 in the diagram):

  • trail-1 awakes, executes and terminates (line 6);
  • trail-2 remains suspended, as it is not awaiting A.
  • trail-3 executes up to await B (line 17).

During the reaction t1, new instances of events A, B, and C occur and are enqueued to be handled in the reactions in sequence. As A happened first, it is used in the next reaction. However, no trails are awaiting it, so an empty reaction chain takes place (t2 in the diagram). The next reaction dequeues the event B (t3 in the diagram):

  • trail-2 awakes, executes and terminates;
  • trail-3 splits in two and they both terminate immediately.

Since a par/and rejoins after all trails terminate, the program also terminates and does not react to the pending event C.

Note that each step in the logical time line (t0, t1, etc.) is identified by the unique occurring event. Inside a reaction, trails only react to the same shared global event (or remain suspended).

Parallel Compositions and Abortion

The use of trails in parallel allows programs to wait for multiple events at the same time. Céu supports three kinds of parallel compositions that differ in how they rejoin and proceed to the statement in sequence:

  1. a par/and rejoins after all trails in parallel terminate;
  2. a par/or rejoins after any trail in parallel terminates, aborting all other trails automatically;
  3. a par never rejoins, even if all trails terminate.

As mentioned in the introduction and emphasized in the execution model, trails in parallel do not execute with real parallelism. Therefore, parallel compositions support awaiting in parallel, rather than executing in parallel.

Bounded Execution

Reaction chains must run in bounded time to guarantee that programs are responsive and can handle incoming input events. For this reason, Céu requires every path inside the body of a loop statement to contain at least one await or break statement. This prevents tight loops, i.e., unbounded loops that do not await.

In the example below, the true branch of the if may never execute, resulting in a tight loop when the condition is false:

loop do
    if <cond> then
        break;
    end
end

Céu warns about tight loops in programs at compile time. For time-consuming algorithms that require unrestricted loops (e.g., cryptography, image processing), Céu provides Asynchronous Execution.

Deterministic Execution

TODO (shared memory + deterministic scheduler + optional static analysis)

Internal Reactions

Céu supports inter-trail communication through await and emit statements for internal events. A trail can await an internal event to suspend it. Then, another trail can emit and broadcast an event, awaking all trails awaiting that event.

Unlike input events, multiple internal events can coexist during an external reaction. An emit starts a new internal reaction in the program which relies on a runtime stack:

  1. The emit suspends the current trail and its continuation is pushed into the stack (i.e., the statement in sequence with the emit).
  2. All trails awaiting the emitted event awake and execute in sequence (see rule 2 for external reactions). If an awaking trail emits another internal event, a nested internal reaction starts with rule 1.
  3. The top of stack is popped and the last emitting trail resumes execution from its continuation.

Example:

1:  par/and do      // trail 1
2:      await e;
3:      emit f;
4:  with            // trail 2
5:      await f;
6:  with            // trail 3
8:      emit e;
9:  end

The emit e in trail-3 (line 7) starts an internal reaction that awakes the await e in trail-1 (line 2). Then, the emit f (line 3) starts another internal reaction that awakes the await f in trail-2 (line 5). Trail-2 terminates and the emit f resumes in trail-1. Trail-1 terminates and the emit e resumes in trail-3. Trail-3 terminates. Finally, the par/and rejoins and the program terminates.