Céu provides Structured Synchronous Reactive Programming, extending classical structured programming with two main functionalities:
- Event Handling:
awaitstatement to suspend a line of execution and wait for an input event from the environment.
emitstatement to signal an output event back to the environment.
- 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.
As a reactive language, Céu depends on an external host platform, known as an
environment, which exposes
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
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:
- The program initiates the boot reaction from the first line of code in a single trail.
- 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.
- The program goes idle.
- 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
- trail-3 executes up to
await B(line 17).
During the reaction t1, new instances of events
C occur and
are enqueued to be handled in the reactions in sequence.
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.
par/and rejoins after all trails terminate, the program also
terminates and does not react to the pending event
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:
par/andrejoins after all trails in parallel terminate;
par/orrejoins after any trail in parallel terminates, aborting all other trails automatically;
parnever 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.
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
to contain at least one
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.
TODO (shared memory + deterministic scheduler + optional static analysis)
Céu supports inter-trail communication through
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
emit starts a new internal reaction in the program which relies on a
emitsuspends the current trail and its continuation is pushed into the stack (i.e., the statement in sequence with the
- All trails awaiting the emitted event awake and execute in sequence
rule 2for external reactions). If an awaking trail emits another internal event, a nested internal reaction starts with
- The top of stack is popped and the last emitting trail resumes execution from its continuation.
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
emit e in trail-3 (line 7) starts an internal reaction that awakes the
await e in trail-1 (line 2).
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.
par/and rejoins and the program terminates.