Céu is a programming language for reactive applications and intends to offer a higher-level and safer alternative to C. The two main peculiarities of Céu are the synchronous execution model and the use of organisms as abstractions.
Reactive applications interact in real time and continuously with external stimuli from the environment. They represent a wide range of software areas and platforms: from games in powerful desktops, "apps" in capable smart phones, to the emerging internet of things in constrained embedded systems.
Céu supports concurrent lines of execution---known as trails---that react continuously to input events from the environment. Waiting for an event halts the running trail until that event occurs. The environment broadcasts an occurring event to all active trails, which share a single global time reference (the event itself). The synchronous concurrency model of Céu greatly diverges from conventional multithreading (e.g. pthreads and Java threads) and the actor model (e.g.
erlang and Go). On the one hand, trails can share variables in a deterministic and seamless way (e.g. no need for locks or semaphores). On the other hand, there is no real parallelism (e.g. multi-core execution) in the standard synchronous operation mode of the language. Céu is a language for real-time concurrency with complex control specifications, but not for algorithm-intensive or distributed applications.
Céu integrates well with C, being possible to define and call C functions from within Céu programs.
Céu is free software.
Céu is grounded on a precise definition of logical time as a discrete sequence of external 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 time (here the human notion of time, see Bounded execution).
The execution model for Céu programs is as follows:
The synchronous execution model of Céu is based on the hypothesis that internal reactions run infinitely faster in comparison to the rate of external events. An internal reaction is the set of computations that execute when an external event occurs. Conceptually, a program takes no time on step 2 and is always idle on step 3. In practice, if a new external 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 on the same event), Céu schedules them in the order they appear in the program text. This policy is somewhat arbitrary, but provides a priority scheme for trails, and also ensures deterministic and reproducible execution for programs. Note that, at any time, at most one trail is executing. Trails are created with parallel compositions.
The program and diagram below illustrate the behavior of the scheduler of Céu:
1: input void A, B, C;
2: par/and do // A, B, and C are external input events
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 splits in three trails (a par/and
rejoins after all trails terminate). Following the order of declaration for the trails, they are scheduled as follows (t0 in the diagram):
await A
(line 5);await B
(line 10);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):
A
.await B
(line 17).During this reaction, new instances of events A
, B
, and C
occur (t1 in the diagram) and are enqueued to be handled in the reactions that follow. 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):
With all trails terminated, 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 event it handles. Inside a reaction, trails only react to that identifying event (or remain suspended).
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 differing in how they rejoin and proceed to the statement in sequence:
par/and
rejoins after all trails in parallel terminate;par/or
rejoins after any trail in parallel terminates;par
never rejoins (even if all trails terminate).The termination of a trail inside a par/or
aborts the other trails in parallel, which must be necessarily awaiting (from rule 2 of Execution model). Before aborting, a trail has a last opportunity to execute all active finalization statements.
As mentioned in the introduction and emphasized in the execution model, trails inside parallel compositions do execute with real parallelism. It is more accurate to think of parallel compositions as trails awaiting in parallel, given that conceptually trails are always awaiting.
Reaction chains should run in bounded time to guarantee that programs are responsive and can handle upcoming input events from the environment. For any loop statement in a program, Céu requires that every possible path inside its body contains at least one await
or break
statement, thus avoiding tight loops (i.e., unbounded loops that do not await).
In the example below, the if
true branch may never execute, resulting in a tight loop (which the compiler complains about):
loop do
if <cond> then
break;
end
end
For time-consuming algorithms that require unrestricted loops (e.g., cryptography, image processing), Céu provides Asynchronous execution.
TODO (deterministic scheduler + optional static analysis)
Céu provides inter-trail communication through internal events. Trails use the await
and emit
operations to manipulate internal events, i.e., a trail that emits an event can awake trails previously awaiting the same event.
An emit
starts a new internal reaction in the program:
emit
, the scheduler saves the statement following it to execute later.If an awaking trail emits another internal event, a new internal reaction starts. The scheduler uses a stack policy (first in, last out) for saved continuation statements from rule 1.
Example:
1: par/and do
2: await e;
3: emit f;
4: with
5: await f;
6: with
7: ...
8: emit e;
9: end
The emit e
in trail-3 (line 8) 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 (all trails have terminated) and the program terminates.
Céu uses an abstraction mechanism that reconciles data and control state into the single concept of an organism. Organisms provide an object-like interface (data state) as well as multiple lines of execution (control state).
A class of organisms is composed of an interface and a single execution body. The interface exposes public variables, methods, and internal events, like in object oriented programming. The body can contain any valid code in Céu (including parallel compositions) and starts on organism instantiation, executing in parallel with the program. Organism instantiation can be either static or dynamic.
The example below (in the right) blinks two LEDs in parallel with different frequencies. Each blinking LED is a static instance organism of the Blink
class:
|
|
The Blink
class (lines 1-11) exposes the led
and freq
fields, which correspond to the LED port and blinking frequency to be configured for each instance. The application creates two instances, specifying the fields in the constructors (lines 13-16 and 18-21). A constructor starts the instance body to execute in parallel with the application. When reaching the await 1min
(line 23), each instance already has its body switching between _on()
and _off()
every freq
milliseconds (lines 5-10).
The code in the left is semantically equivalent to the one in the right, which expands the organisms bodies (lines 13-18 and 22-27) in a par/or
with the rest of the application (await 1min
, in line 30). Note the await FOREVER
statements (lines 19 and 28) that avoid the organisms bodies to terminate the par/or
. The _Blink
type corresponds to a simple datatype without execution body (i.e., conventional structs or records or objects).
See also Organism declarations, Class and Interface declarations, and Dynamic execution.
Keywords in Céu are reserved names that cannot be used as identifiers (e.g., variable and class names):
and async atomic await bool
break byte call call/rec char
class continue do else else/if
emit end escape event every
f32 f64 false finalize float
FOREVER free function global if
in input input/output int interface
isr loop native not nothing
null or outer output output/input
par par/and par/or pause/if pool
return s16 s32 s64 s8
sizeof spawn sync then this
thread true u16 u32 u64
u8 uint until var void
watching with word
@const @hold @nohold @plain @pure
@rec @safe
Céu uses identifiers to refer to variables, internal events, external events, classes/interfaces, and native symbols.
ID ::= <a-z, A-Z, 0-9, _> +
ID_var ::= ID // beginning with a lowercase letter (variables and internal events)
ID_ext ::= ID // all in uppercase, not beginning with a digit (external events)
ID_cls ::= ID // beginning with an uppercase letter (classes)
ID_nat ::= ID // beginning with an underscore (native symbols)
Examples:
var int a; // "a" is a variable
emit e; // "e" is an internal event
await E; // "E" is an external input event
var T t; // "T" is a class
_printf("hello world!\n"); // "_printf" is a native symbol
Boolean types have the values true
and false
.
Integer values can be written in different bases and also as ASCII characters:
Examples:
// all following are equal to the decimal 127
v = 127;
v = 0777;
v = 0x7F;
// newline ASCII character = decimal 10
c = '\n';
TODO (like C)
The null
literal represents null pointers.
A sequence of characters surrounded by "
is converted into a null-terminated string, just like in C:
Example:
_printf("Hello World!\n");
Céu provides C-style comments.
Single-line comments begin with //
and run to end of the line.
Multi-line comments use /*
and */
as delimiters. Multi-line comments can be nested by using a different number of *
as delimiters.
Examples:
var int a; // this is a single-line comment
/** comments a block that contains comments
var int a;
/* this is a nested multi-line comment
a = 1;
*/
**/
Céu is statically typed, requiring all variables and events to be declared before they are used.
A type is composed of an identifier with an optional modifier:
Type ::= ID_type ( {`*´} | `&´ | `[´ `]´ | `[´ NUM `]´ )
A type identifier can be a native identifier, a class identifier, or one of the primitive types:
ID_type ::= ( ID_nat | ID_cls |
bool | byte | char | f32 | f64 |
float | int | s16 | s32 | s64 |
s8 | u16 | u32 | u64 | u8 |
uint | void | word )
Examples:
var u8 v; // "v" is of 8-bit unsigned integer type
var _rect r; // "r" is of external native type "rect"
var char* buf; // "buf" is a pointer to a "char"
var T t; // "t" is an organism of class "T"
Céu has the following primitive types:
void // void type
word // type with the size of platform dependent word
bool // boolean type
char // char type
byte // 1-byte type
int uint // platform dependent signed and unsigned integer
s8 u8 // signed and unsigned 8-bit integer
s16 u16 // signed and unsigned 16-bit integer
s32 u32 // signed and unsigned 32-bit integer
s64 u64 // signed and unsigned 64-bit integer
float // platform dependent float
f32 f64 // 32-bit and 64-bit floats
See also the literals for these types.
Types defined externally in C can be prefixed by _
to be used in Céu programs.
Example:
var _message_t msg; // "message_t" is a C type defined in an external library
Native types support annotations which provide additional information to the compiler.
TODO (brief description)
Types can be suffixed with the following modifiers: *
, &
, []
, and [N]
.
TODO (like C)
TODO (more or less like C++)
TODO (more or less like pointers)
One-dimensional vectors are declared by suffixing the variable type with the vector length surrounded by [
and ]
. The first index of a vector is zero.
Example:
var int[2] v; // declares a vector "v" of 2 integers
Note: currently, Céu has no syntax for initializing vectors.
A block is a sequence of statements separated by semicolons (;
):
Block ::= { Stmt `;´ }
Note: statements terminated with the end
keyword do not require a terminating semicolon.
A block creates a new scope for variables, which are only visible for statements inside the block.
Compound statements (e.g. if-then-else) create new blocks and can be nested for an arbitrary level.
A block can be explicitly created with the do-end
statement:
Do ::= do Block end
nothing
is a innocuous statement:
Nothing ::= nothing
The syntax for the definition of variables is as follows:
Dcl_var ::= var Type ID_var [`=´ SetExp] { `,´ ID_var [`=´ SetExp] }
A variable must have an associated type and can be optionally initialized (see Assignments).
Variables are only visible inside the block they are defined.
Examples:
var int a=0, b=3; // declares and initializes integer variables "a" and "b"
var int[2] v; // declares a vector "v" of size 2
An organism is a variable whose type is the identifier of a class declaration. An optional constructor can initialize the organism fields:
Dcl_org ::= var Type ID_var [ with
Block
end ]
Example:
class T with
var int v;
do
<body-of-T>
end
var T t with // "t" is an organism of class "T"
this.v = 0; // whose field "v" is initialized to "0"
end
After the declaration, the body of an organism starts to execute in parallel with the rest of the application. The table below shows the equivalent expansion of an organism declaration to a par/or
composition containing the class body:
|
|
Given that an organism is a variable, the block it is declared restricts its life. In the expansion, the par/or
makes the organism to go out of scope when <code-pos-declaration>
terminates.
TODO (assumes code-pos-declaration closes the block exactly on the end) TODO (vectors of organisms: copy the declaration N times)
Inside constructors the expression this
refers to the new organism, while the expression outer
refers to the organism creating the new organism:
class U with
var int v;
do
...
end
class T with
var int v;
do
var U u with
this.v = outer.v; // "this" is of class "U", "outer" is of class "T"
end;
end
See also Event handling.
External events are used as interfaces between programs and devices from the real world:
Being reactive, programs in Céu have input events as their sole entry points through await statements.
An external event is either of type input or output, never being both at the same time. For devices that perform input and output (e.g. radio transceivers), the underlying platform must provide different events for each functionality.
The declaration of input and output events is as follows:
Dcl_ext ::= input (Type|TypeList) ID_ext { `,´ ID_ext }
| output Type ID_ext { `,´ ID_ext }
TypeList ::= `(´ Type { `,´ Type } `)´
Events communicate values between the environment and the application (and vice-versa). The declaration includes the type of the value, which can be also a list of types when the event communicates multiple values.
Note: void
is a valid type for signal-only events.
The visibility of external events is always global, regardless of the block they are declared.
Examples:
input void A,B; // "A" and "B" are input events carrying no values
output int MY_EVT; // "MY_EVT" is an output event carrying integer values
The availability of external events depends on the platform in use. Therefore, external declarations just make pre-existing events visible to a program.
Refer to Environment for information about interfacing with external events in the platform level.
TODO (emit + await)
Internal events have the same purpose of external events, but for communication within trails in a program.
The declaration of internal events is as follows:
Dcl_int ::= event (Type|TypeList) ID_var { `,´ ID_var }
In contrast with external events, an internal event is for input and output at the same time.
Internal events cannot be of a vector type.
Note: void is a valid type for signal-only internal events.
TODO (like functions in any language)
Dcl_fun ::= function [@rec] ParList `=>´ Type ID_var
[ do Block end ]
ParList ::= `(´ ParListItem [ { `,´ ParListItem } ] `)´
ParListItem ::= [@hold] Type [ID_var]
TODO (like return in any language)
Return ::= return [Exp]
TODO (more or less like dynamically loaded functions)
TODO (special/restricted functions)
A class
is a template for creating organisms. It contains an interface and a body common to all instances of the class. The interface connects an organism with the rest of the application, exposing internal variable, events, and methods that other organisms can manipulate directly. The body specifies the behavior of the organism and executes when it is instantiated.
An interface
is a template for classes that shares the same interface (as described above, the term interface is overloaded here). The body and methods implementations may vary across classes sharing the same interface.
The declaration of classes and interfaces is as follows:
Dcl_cls ::= class ID_cls with
Dcls // interface
do
Block // body
end
Dcl_ifc ::= interface ID_cls with
Dcls // interface
end
Dcls ::= { (Dcl_var | Dcl_int | Dcl_pool | Dcl_fun | Dcl_imp) `;´ }
Dcl_imp ::= interface ID_cls { `,´ ID_cls }
Dcls
is a sequence of variables, events, pools, and functions (methods) declarations. It can also refer other interfaces through a Dcl_imp
clause, which copies all declarations from the referred interfaces (similarly to the implements
clause of Java).
A pool is a container for dynamic instances of organisms of the same type:
Dcl_pool ::= pool Type ID_var { `,´ ID_var }
The type has to be a class or interface identifier followed by a vector modifier. For pools of classes, the number inside the vector brackets represents the maximum number of instances supported by the pool. For pools of interfaces, the number represents the maximum number of bytes for all instances (as each instance may have a different size). The number inside the vector modifier brackets is optional, though. In this case, the number of instances in the pool is unbounded.
Examples:
pool T[10] ts; // a pool of at most 10 instances of class "T"
pool T[] ts; // an unbounded pool of instances of class "T"
pool I[100] is; // a pool of at most 100 bytes of instances of interface "I"
pool I[] is; // an unbounded pool of instances of interface "I"
The life of all organisms inside a pool is restricted to the block it is declared. When the pool goes out of scope, all organism bodies are aborted.
See Dynamic execution for organisms allocation.
Native declarations provide additional information about external C symbols. A declaration is an annotation followed by a list of symbols:
Dcl_nat ::= native [@pure|@const|@nohold|@plain] Nat_list
Nat_list ::= (Nat_type|Nat_func|Nat_var) { `,` (Nat_type|Nat_func|Nat_var) }
Nat_type ::= ID_nat `=´ NUM
Nat_func ::= ID_nat `(´ `)´
Nat_var ::= ID_nat
A type declaration may define its size in bytes to help the compiler organizing memory. A type of size 0
is an opaque type and cannot be instantiated as a variable that is not a pointer.
Functions and variables are distinguished by the ()
that follows function declarations.
Native symbols can have the following annotations:
@plain states that the type is not a pointer to another type. @const states that the variable is actually a constants (e.g. a #define
). @pure states that the function has no side effects. @nohold states that the function does not hold pointers passed as parameters.
The static analysis of Céu relies on annotations.
Examples:
native _char=1, _FILE=0; // "char" is a 1-byte type, while `FILE` is "opaque"
native @plain _rect; // "rect" is not a pointer type
native @const _NULL; // "NULL" is a constant
native @pure _abs(), _pow(); // "abs" and "pow" are pure functions
native @nohold _fprintf(), _sprintf(); // functions receive pointers but do not hold references to them
A variable or function can be declared as @safe
with a set of other functions or variables:
Dcl_det ::= @safe ID with ID { `,´ ID }
Example:
native _p, _f1(), _f2();
@safe _f1 with _f2;
var int* p;
@safe p with _p;
par do
_f1(...); // `f1` is safe with `f2`
*p = 1; // `p` is safe with `_p`
...
with
_f2(...); // `f2` is safe with `f1`
*_p = 2; // `_p` is safe with `p`
...
end
See also Static analysis.
Céu supports many kinds of assignments:
Set ::= Exp `=´ SetExp
SetExp ::= Exp | <do-end> | <if-then-else> | <loop>
| <every> | <par> | <await> | <emit (output)>
| <thread> | <spawn> )
The expression on the left side must be assignable.
The simpler form of assignment uses expressions as values.
Example:
var int a,b;
a = b + 1;
A whole block can be used as an assignment value by escaping from it. The following block statements can be used in assignments: do-end´](#do-end) [
if-then-else](#conditional), [
loop](#repetition), [
every](#every), and [
par`.
An escape
statement escapes the deepest block being assigned to a variable. The expression following it is then assigned to the respective variable:
Escape ::= escape Exp
Every possible path inside the block must reach a escape
statement whose expression becomes the final value of the assignment.
Example:
a = loop do // a=1, when "cond" is satisfied
...
if cond then
escape 1; // "loop" is the deepest assignment block
end
...
end
Every program in Céu contains an implicit do-end
surrounding it, assigning to a special integer variable $ret
holding the return value for the program execution.
Therefore, a program such as
escape 1;
should read as
var int $ret =
do
escape 1;
end;
See Await statements.
See Emit statements.
See Threads.
See Dynamic execution.
The syntax for function calls is as follows:
Call ::= [ call|call/rec ] Exp * `(´ [ExpList] `)´
ExpList = Exp { `,´ Exp }
The called expression has to evaluate to a internal, external), or native function. The call
operator is optional, but recursive functions must use the call/rec
operator (see Static analysis).
Examples:
_printf("Hello World!\n"); // calls native "printf"
o.f(); // calls method "f" of organism "o"
F(1,2,3); // calls external function "F"
Events are the most fundamental concept of Céu, accounting for its reactive nature. Programs manipulate events through the await
and emit
statements. An await
halts the running trail until that event occurs. An event occurrence is broadcast to all trails trails awaiting that event, awaking them to resume execution.
Céu supports external and internal events. External events are triggered by the environment, while internal events, by the emit
statement. See also Synchronous execution model for the differences between external and internal reactions.
The await
statement halts the running trail until the referred wall-clock time, input event, or internal event occurs.
Await ::= ( await ID_ext |
await Exp |
await (WCLOCKK|WCLOCKE)
) [ until Exp ]
| await FOREVER
VarList ::= `(´ ID_var { `,´ ID_var } `)´
WCLOCKK ::= [NUM h] [NUM min] [NUM s] [NUM ms] [NUM us]
WCLOCKE ::= `(´ Exp `)´ (h|min|s|ms|us)
Examples:
await A; // awaits the input event `A`
await a; // awaits the internal event `a`
await 10min3s5ms100us; // awaits the specified time
await (t)ms; // awaits the current value of the variable `t` in milliseconds
await FOREVER; // awaits forever
An await
may evaluate to zero or more values which can be captured with the optional assignment syntax.
The optional until
clause tests an additional condition required to awake. It can be understood as the expansion below:
loop do
await <...>;
if <Exp> then
break;
end
end
For await statements with internal or external events, the running trail awakes when the referred event is emitted. The await
evaluates to the type of the event.
input int E;
var int v = await E;
event (int,int*) e;
var int v;
var int* ptr;
(v,ptr) = await e;
For await statements with wall-clock time (i.e., time measured in minutes, milliseconds, etc.), the running trail awakes when the referred time elapses.
A constant time is expressed with a sequence of value/unit-of-time pairs (see WCLOCKK
above). An expression time is specified with an expression in parenthesis followed by a single unit of time (see WCLOCKE
above).
The await
evaluates to the residual delta time (dt) (i.e. elapsed time minus requested time), measured in microseconds:
var int dt = await 30ms; // if 31ms elapses, then dt=1000
Note: dt
is always greater than or equal to 0.
The await FOREVER
halts the running trail forever. It cannot be used in assignments, because it never evaluates to anything.
The emit
statement triggers the referred wall-clock time, input event, or internal event, awaking all trails waiting for it.
Emit ::= emit Exp [ `=>´ (Exp | `(´ ExpList `)´)
| emit ID_ext [ `=>´ (Exp | `(´ ExpList `)´)
| emit (WCLOCKK|WCLOCKE)
Emit statements with internal or external events expect parameters that match the event type (unless the event is of type void
).
Examples:
output int E;
emit E => 1;
event (int,int) e;
emit e => (1,2);
External input events can only be emitted inside asynchronous blocks.
The emission of internal events start new internal reactions.
TODO (emit output evaluates to "int")
Emit statements with wall-clock time expect expressions with units of time, as described in Await time.
Like input events, time can only be emitted inside asynchronous blocks.
Conditional flow uses the if-then-else
statement:
If ::= if Exp then
Block
{ else/if Exp then
Block }
[ else
Block ]
end
The block following then
executes if the condition expression after the if
evaluates to a non-zero value. Otherwise, the same process holds each else/if
alternative. Finally, it they all fail, the block following the else
executes.
A loop
continuously executes its body block:
Loop ::= loop [ Iterator ] do
Block
end
Iterator ::= [`(´ Type `)´] ID_var [in Exp]
A loop
terminates when it reaches a break
or its (optional) iterator terminates.
A break
escapes the innermost enclosing loop.
Example:
loop do // loop 1
...
loop do // loop 2
if <cond-1> then
break; // escapes loop 2
end
end
...
if <cond-2> then
break; // escapes loop 1
end
...
end
A loop
may specify an iterator that yields a new value on each loop iteration.
For iterators in which Exp
is empty or is of type int
, ID_var
is incremented after each loop iteration. ID_var
is automatically declared read-only, with visibility restricted to the loop body, and is initialized to zero. The optional Exp
limits the number of iterations, and is evaluated once before the loop starts.
Example:
loop i in 10 do
_printf("i = %d\n", i); // prints "i = 0" up to "i = 9"
end
For iterators in which Exp
evaluates to a pool, ID_var´ evaluates to the instances on the pool, one at a time, from the oldest to the newest.
ID_var` is automatically declared read-only, with visibility restricted to the loop body.
The optional typecast tries
The every
statement continuously awaits an event and executes its body:
Every ::= every (Exp|VarList) in (WCLOCKK|WCLOCKE|ID_ext|Exp) do
Block
end
An every
expands to a loop
as illustrated below:
|
|
The body of an every
cannot contain an await
, ensuring that no occurrences of <event>
are ever missed.
The finalize
statement postpones the execution of its body to happen when its associated block goes out of scope:
Finalize ::= finalize
[Exp `=´ SetExp]
with
Block
end
The presence of the optional attribution clause determines which block to associate with the finalize
:
Example:
input int A;
par/or do
var _FILE* f;
finalize
f = _fopen("/tmp/test.txt");
with
_fclose(f);
end
every v in A do
fwrite(&v, ..., f);
end
with
await 1s;
end
The program open f
and writes to it on every occurrence of A
. The writing trail is aborted after one second, but the finalize
safely closes the file, because it is associated to the block that declares f
.
The static analysis of Céu enforces the use of finalize
for unsafe attributions.
The parallel statements par/and
, par/or
, and par
split the running trail in multiple others:
Pars ::= (par/and|par/or|par) do
Block
with
Block
{ with
Block }
end
They differ only on how trails terminate (rejoin).
See Synchronous execution model for a detailed description of parallel execution.
The par/and
statement stands for parallel-and and rejoins when all trails terminate:
The par/or
statement stands for parallel-or and rejoins when any of the trails terminate:
The par
statement never rejoins and should be used when the trails in parallel are supposed to run forever:
The watching
statement aborts its body when its associated event occurs:
Watching ::= watching (WCLOCKK|WCLOCKE|ID_ext|Exp) do
Block
end
A wacthing
expands to a par/or
as illustrated below:
|
|
TODO (supports org refs)
TODO
Pause ::= pause/if Exp do
Block
end
The spawn
statement creates instances of organisms dynamically:
Dyn ::= spawn ID_cls [in Exp]
[ with Constructor end ]
The spawn
returns a pointer to the allocated organism, or null
in the case of failure.
The optional in
clause allows the statement to specify in which pool the organisms will live. If absent, the organism is allocated on an implicit pool in the outermost block of the class the allocation happens.
On allocation, the body of the organism starts to execute in parallel with the rest of the application, just like happens for static organisms. The constructor clause is also the same as for static organisms.
A dynamic organism is also automatically deallocated when its execution body terminates.
See Static analysis for the restrictions on manipulating pointers and references to organisms.
Asynchronous execution permit that programs execute time consuming computations without interfering with the synchronous side of applications (i.e., everything, except asynchronous statements).
Async ::= async [thread] [RefVarList] do
Block
end
RefVarList ::= `(´ [`&´] ID_var { `,´ [`&´] ID_var } `)´
Asynchronous blocks (async
) are the simplest alternative for asynchronous execution.
An async
body can contain non-awaiting loops (tight loops), which are disallowed on the synchronous side to ensure that programs remain reactive.
The optional list of variables copies values between the synchronous and asynchronous scopes. With the prefix &
, the variable is passed by reference and can be altered from inside the async
.
The next example uses an async
to execute a time-consuming computation, keeping the synchronous side reactive. In a parallel trail, the program awaits one second to kill the computation if it takes too long:
var int fat;
par/or do
var int v = ...
// calculates the factorial of v
fat = async (v) do
var int fat = 1;
loop i in v do // a tight loop
// v varies from 0 to (v-1)
fat = fat * (i+1);
end
return fat;
end;
with
await 1s; // watchdog to kill the async if it takes too long
fat = 0;
end
return fat;
An async
has the following restrictions:
loop
iteration on its body.await
events.emit
internal events.An async
is allowed to trigger input events and the passage of time, providing a way to test programs in the language itself:
input int A;
// tests a program with a simulation in parallel
par do
// original program
var int v = await A;
loop i do
await 10ms;
_printf("v = %d\n", v+i);
end
with
// input simulation
async do
emit A=>0; // initial value for "v"
emit 1s35ms; // the loop executes 103 times
end
return 0;
end
Every time the async
emits an event, it suspends (due to rule 1
of previous section). The example prints the v = <v+i>
message exactly 103 times.
TODO
TODO
Sync ::= sync do
Block
end
Native blocks define new types, variables, and functions in C:
Native ::= native do
<code_in_C>
end
Example:
native do
#include
int inc (int i) {
return i+1;
}
end
_assert(_inc(0) == 1);
If the code in C contains the terminating end
keyword of Céu, the native
block should be delimited with any matching comments to avoid confusing the parser:
native do
/*** c code ***/
char str = "This `end` confuses the parser";
/*** c code ***/
end
The syntax for expressions in Céu is as follows:
Exp ::= Prim
| Exp (or|and) Exp
| Exp (`|´|`^´|`&´) Exp
| Exp (`!=´|`==´) Exp
| Exp (`<=´|`<´|`>´|`>=´) Exp
| Exp (`<<´|`>>´) Exp
| Exp (`+´|`-´) Exp
| Exp (`*´|`/´|`%´) Exp
| not Exp
| `&´ Exp
| (`-´|`+´) Exp
| `~´ Exp
| `*´ Exp
| `(´ Type `)´ Exp
| Exp `(´ [ExpList] `)´ [finalize with Block end]
| Exp `[´ Exp `]´
| Exp (`.´|`:´) ID
Prim ::= `(´ Exp `)´
| sizeof `(´ (Type|Exp) `)´
| ID_var | ID_nat
| null | NUM | String
| global | this | outer
| (call | call/rec) Exp
Most operators follow the same semantics of C.
Note: assignments are not expressions in Céu.
TODO: global, this, outer,
The arithmetic operators of Céu are
+ - % * / + -
which correspond to addition, subtraction, modulo (remainder), multiplication, division, unary-plus, and unary-minus.
The relational operators of Céu are
== != > < >= <=
which correspond to equal-to, not-equal-to, greater-than, less-than, greater-than-or-equal-to, and less-than-or-equal-to.
Relational expressions evaluate to 1 (true) or 0 (false).
The logical operators of Céu are
not and or
The bitwise operators of Céu are
~ & | ^ << >>
which correspond to not, and, or, xor, left-shift, and right-shift.
Céu uses square brackets to index vectors:
Index ::= Exp `[´ Exp `]´
The expression on the left side is expected to evaluate to a vector.
Vector indexes start at zero.
The operator *
dereferences its pointer operand, while the operator &
returns a pointer to its operand:
Deref ::= `*´ Exp
Ref ::= `&´ Exp
The operand to &
must be an [[#sec.exps.assignable|assignable expression]].
The operators .´ and
:´ access the fields of structs.
Dot ::= Exp `.´ Exp
Colon ::= Exp `:´ Exp
The operator .
expects a struct
as its left operand, while the operator :
expects a reference to a struct
.
Example:
native do
typedef struct {
int v;
} mystruct;
end
var _mystruct s;
var _mystruct* p = &s;
s.v = 1;
p:v = 0;
Note: struct
must be declared in C, as Céu currently has no support for it.
TODO
TODO (index clash)
Céu uses parenthesis for type casting:
Cast ::= `(´ ID_type `)´
A sizeof
expression returns the size of a type or expression, in bytes:
Sizeof ::= sizeof `(´ (Type|Exp) `)´
Céu follows the same precedence of C operators:
/* lower to higer precedence */
or
and
|
^
&
!= ==
<= >= < >
>> <<
+ - // binary
* / %
not &
+ - // unary
<> // typecast
() [] : . // call, index
An assignable expression (also known as an l-value) can be a variable, vector index, pointer dereference, or struct access. L-values are required in [[#sec.stmts.assignments|assignments]] and [[#sec.exps.pointers|references]].
Examples:
var int a;
a = 1;
var int[2] v;
v[0] = 1;
var int* p;
*p = 1;
var _mystruct s;
s.v = 1;
var _mystruct* ps;
ps:v = 1;
TODO (introduction)
TODO (weakly typed, like C)
TODO (index clash)
TODO
TODO
TODO (index clash)
TODO
As a reactive language, Céu depends on an external environment (the host platform) to provide input and output events to programs. The environment is responsible for sensing the world and notifying Céu about changes. The actual events vary from environment to environment, as well as the implementation for the notification mechanism (e.g. polling or interrupt-driven).
The final output of the compiler of Céu is a program in C that follows a standard application programming interface. The interface specifies some types, macros, and functions, which the environment has to manipulate in order to guide the execution of the original program in Céu.
The example below illustrates a possible main
for a host platform:
#include "_ceu_app.c"
int main (void)
{
char mem[sizeof(CEU_Main)];
tceu_app app;
app.data = &mem;
ceu_app_init(&app);
while(app->isAlive) {
ceu_sys_go(app, CEU_IN__ASYNC, CEU_EVTP((void*)NULL));
ceu_sys_go(app, CEU_IN__WCLOCK, CEU_EVTP(<how-much-time-since-previous-iteration>));
if (occuring(CEU_IN_EVT1)) {
ceu_sys_go(app, CEU_IN__EVT1, param1);
}
...
if (occuring(CEU_IN_EVTn)) {
ceu_sys_go(app, CEU_IN__EVTn, paramN);
}
}
return app->ret;
}
int occurring (int evt_id) {
<platform dependent>
}
tceu_app
is a type that represents an application in Céu. The field app.data
expects a pointer to the memory of the application, which has to be previously declared.
TODO
TODO
TODO (index clash)
TODO
TODO (index clash)
TODO
TODO
Céu provides a command line compiler that generates C code for a given input program. The compiler is independent of the target platform.
The generated C output should be included in the main application, and is supposed to be integrated with the specific platform through the presented [[#sec.env.api|API]].
The command line options for the compiler are as follows:
./ceu <filename> # Ceu input file, or `-` for stdin
--output <filename> # C output file (stdout)
--defs-file <filename> # define constants in a separate output file (no)
--join (--no-join) # join lines enclosed by /*{-{*/ and /*}-}*/ (join)
--dfa (--no-dfa) # perform DFA analysis (no-dfa)
--dfa-viz (--no-dfa-viz) # generate DFA graph (no-dfa-viz)
--m4 (--no-m4) # preprocess the input with `m4` (no-m4)
--m4-args # preprocess the input with `m4` passing arguments in between `"` (no)
The values in parenthesis show the defaults for the options that are omitted.
Use of the unsafe operator ":=" for non-pointer attributions.
Instead, use =
.
Example:
var int v := 1;
>>> ERR [1101] : file.ceu : line 1 : wrong operator
Use of finalize
for non-pointer attributions.
Instead, do not use finalize
.
Example:
var int v;
finalize
v = 1;
with
<...>
end
>>> ERR [1102] : file.lua : line 3 : attribution does not require "finalize"
Use of the unsafe operator :=
for constant pointer attributions.
Instead, use =
.
Example:
var int ptr := null;
>>> ERR [1103] : file.ceu : line 1 : wrong operator
Use of finalize
for constant pointer attributions.
Instead, do not use finalize
.
Example:
var int ptr;
finalize
ptr = null;
with
<...>
end
>>> ERR [1104] : file.lua : line 3 : attribution does not require `finalize´
Use of normal pointer *
to hold pointer to acquired resource.
Instead, use []
.
Example:
var int* ptr = _malloc();
>>> ERR [1105] : file.ceu : line 1 : destination pointer must be declared with the `[]´ buffer modifier
Omit @hold
annotation for function parameter held in the class or global.
Instead, annotate the parameter declaration with @hold
.
Examples:
class T with
var void* ptr;
function (void* v)=>void f;
do
function (void* v)=>void f do
ptr := v;
end
end
>>> ERR [1106] : file.ceu : line 6 : parameter must be `hold´
/*****************************************************************************/
native do
void* V;
end
function (void* v)=>void f do
_V := v;
end
>>> ERR [1106] : file.ceu : line 5 : parameter must be `hold´
Access to pointer across an await
statement. The pointed data may go out of scope between reactions to events.
Instead, don't do it. :)
(Or check if the pointer is better represented as a buffer pointer ([]
).)
Examples:
event int* e;
var int* ptr = await e;
await e; // while here, what "ptr" points may go out of scope
escape *ptr;
>>> ERR [1107] : file.ceu : line 4 : pointer access across `await´
/*****************************************************************************/
var int* ptr = <...>;
par/and do
await 1s; // while here, what "ptr" points may go out of scope
with
event int* e;
ptr = await e;
end
escape *ptr;
>>> ERR [1107] : file.ceu : line 8 : pointer access across `await´
Use of finalize
inside constructor.
Instead, move it to before the constructor or to inside the class.
Examples:
class T with
var void* ptr;
do
<...>
end
var T t with
finalize
this.ptr = _malloc(10);
with
_free(this.ptr);
end
end;
>>> ERR [1008] : file.ceu : line 7 : `finalize´ inside constructor
/*****************************************************************************/
class T with
var void* ptr;
do
<...>
end
spawn T with
finalize
this.ptr = _malloc(10);
with
_free(this.ptr);
end
end;
>>> ERR [1008] : file.ceu : line 7 : `finalize´ inside constructor
Call missing finalize
clause.
Call passes a pointer. Function may hold the pointer indefinitely. Pointed data goes out of scope and yields a dangling pointer.
Instead, finalize
the call.
Example:
var char[255] buf;
_enqueue(buf);
>>> ERR [1009] : file.ceu : line 2 : call requires `finalize´'
Call a function that does not require a finalize
.
Instead, don't use it.
Example:
_f() finalize with
<...>
end;
>>> ERR [1010] : file.ceu : line 1 : invalid `finalize´
Block ::= { Stmt `;´ }
Stmt ::= <empty-string>
| nothing
| escape Exp
| return [Exp]
| break
| continue
/* Declarations */
/* variable, organisms, events, and pools */
| var Type ID_var [`=´ SetExp] { `,´ ID_var [`=´ SetExp] }
| var Type ID_var with
Block
end
| input (Type|TypeList) ID_ext { `,´ ID_ext }
| output Type ID_ext { `,´ ID_ext }
| event (Type|TypeList) ID_var { `,´ ID_var }
| pool Type ID_var { `,´ ID_var }
/* functions */
| function [@rec] ParList `=>´ Type ID_var
[ `do´ Block `end´ ]
where
ParList ::= `(´ ParListItem [ { `,´ ParListItem } ] `)´
ParListItem ::= [@hold] Type [ID_var]
/* classes & interfaces */
| class ID_cls with
Dcls
do
Block
end
| interface ID_cls with
Dcls
end
where
Dcls ::= { (<var> | <event> | <pool> | <function> | Dcl_imp) `;´ }
Dcl_imp ::= interface ID_cls { `,´ ID_cls }
/* native symbols */
| native [@pure|@const|@nohold|@plain] Nat_list
where
Nat_list ::= (Nat_type|Nat_func|Nat_var) { `,` (Nat_type|Nat_func|Nat_var) }
Nat_type ::= ID_nat `=´ NUM
Nat_func ::= ID_nat `(´ `)´
Nat_var ::= ID_nat
/* deterministic annotations */
| @safe ID with ID { `,´ ID }
/* Assignments */
| (Exp|VarList) `=´ SetExp
/* Function calls */
| [call|call/rec] Exp * `(´ [ExpList] `)´ ExpList = Exp { `,´ Exp }
/* Event handling */
/* await */
| (
await ID_ext |
await Exp |
await (WCLOCKK|WCLOCKE)
) [ until Exp ]
| await FOREVER
/* emit */
| emit Exp [ `=>´ (Exp | `(´ ExpList `)´)
| emit (WCLOCKK|WCLOCKE)
| emit ID_ext [ `=>´ (Exp | `(´ ExpList `)´)
/* Dynamic execution */
| spawn * ID_cls * [in Exp]
[ with Constructor end ]
/* Flow control */
/* explicit block */
| do Block end
/* conditional */
| if Exp then
Block
{ else/if Exp then
Block }
[ else
Block ]
end
/* loops */
| loop [ [`(´ Type `)´] ID_var [in Exp] ] do
Block
end
| every (Exp|VarList) in (WCLOCKK|WCLOCKE|ID_ext|Exp) do
Block
end
/* finalization */
| finalize [Exp `=´ SetExp] with
Block
end
/* parallel compositions */
| (par/and|par/or|par) do
Block
with
Block
{ with
Block }
end
| watching (WCLOCKK|WCLOCKE|ID_ext|Exp) do
Block
end
/* pause */
| pause/if Exp do
Block
end
/* asynchronous execution */
| async [thread] [RefVarList] do
Block
end
| sync do
Block
end
where
RefVarList ::= `(´ [`&´] ID_var { `,´ [`&´] ID_var } `)´
VarList ::= `(´ ID_var { `,´ ID_var } `)´
SetExp ::= Exp | <do-end> | <if-then-else> | <loop>
| <every> | <par> | <await> | <emit (output)>
| <thread> | <spawn> )
WCLOCKK ::= [NUM h] [NUM min] [NUM s] [NUM ms] [NUM us]
WCLOCKE ::= `(´ Exp `)´ (h|min|s|ms|us)
ID ::= <a-z, A-Z, 0-9, _> +
ID_var ::= ID // beginning with a lowercase letter
ID_ext ::= ID // all in uppercase, not beginning with a digit
ID_cls ::= ID // beginning with an uppercase letter
ID_nat ::= ID // beginning with an underscore
Type ::= ID_type ( {`*´} | `&´ | `[´ `]´ | `[´ NUM `]´ )
ID_type ::= ( ID_nat | ID_cls |
| bool | byte | char | f32 | f64 |
| float | int | s16 | s32 | s64 |
| s8 | u16 | u32 | u64 | u8 |
| uint | void | word )
Exp ::= Prim
| Exp (or|and) Exp
| Exp (`|´|`^´|`&´) Exp
| Exp (`!=´|`==´) Exp
| Exp (`<=´|`<´|`>´|`>=´) Exp
| Exp (`<<´|`>>´) Exp
| Exp (`+´|`-´) Exp
| Exp (`*´|`/´|`%´) Exp
| not Exp
| `&´ Exp
| (`-´|`+´) Exp
| `~´ Exp
| `*´ Exp
| `(´ Type `)´ Exp
| Exp `(´ [ExpList] `)´ [finalize with Block end]
| Exp `[´ Exp `]´
| Exp (`.´|`:´) ID
Prim ::= `(´ Exp `)´
| sizeof `(´ (Type|Exp) `)´
| ID_var | ID_nat
| null | NUM | String
| global | this | outer
| (call | call/rec) Exp
/* The operators follow the same precedence of C. */
or /* lowest priority */
and
|
^
&
!= ==
<= >= < >
>> <<
+ - // binary
* / %
not &
+ - // unary
<> // typecast
() [] : . // call, index
Céu is distributed under the MIT license reproduced below:
Copyright (C) 2012 Francisco Sant'Anna
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.