Warning: This manual is under a rewriting process and still mostly reflects version 0.8.
Céu is an imperative, concurrent and reactive language in which then lines of execution, known as trails, react together continuously and in synchronous steps to external 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 execution mode of the language.
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 real 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 trails1, one after another, execute until they await or terminate. This step is named a reaction chain, and always runs in bounded time.
- The program goes idle and the environment takes control.
- On the occurrence of a new external input event, the environment awakes all trails awaiting that event. It then goes to step 2.
(1 Trails can be created with parallel compositions.)
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. 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;
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):
- trail-1 executes up to the
await A
(line 5); - trail-2, up to the
await B
(line 10); - trail-3, up to
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 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):
- trail-2 awakes, executes and terminates;
- trail-3 splits in two and they both terminate.
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:
- a
par/and
rejoins after all trails in parallel terminate; - a
par/or
rejoins after any trail in parallel terminates; - a
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 not execute with real parallelism. Therefore, inside compositions trails are awaiting in parallel, rather than executing in parallel.
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 (shared memory + 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:
- On an
emit
, the scheduler remembers the statement following it to execute later (i.e., its continuation). - All trails awaiting the emitted event awake and execute (like rule 2 for external reactions).
- The emitting trail resumes execution from its continuation.
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, just like objects. The body can contain any valid code in Céu, which starts on the organism instantiation and executes 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 Organisms instantiation.
Keywords in Céu are reserved names that cannot be used as identifiers (e.g., variable and class names):
and async async/thread atomic await
bool break byte call call/rec
char class continue data do
else else/if emit end escape
event every f32 f64 false
finalize float FOREVER function global
if in input input/output int
interface interrupt kill loop native
native/pre new not nothing null
or outer output output/input par
par/and par/or pause/if pool pre
return s16 s32 s64 s8
request sizeof spawn sync tag
then this traverse 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, data types, data tags, fields, and native symbols.
ID ::= <a-z, A-Z, 0-9, _>+
ID_var ::= `_´ | ID // [variables and internal events] beginning with a lowercase letter
ID_ext ::= ID // [external events] all in uppercase, not beginning with a digit
ID_cls ::= ID // [classes and interfaces] beginning with an uppercase letter
ID_data ::= ID // [data types] beginning with an uppercase letter
ID_tag ::= ID // [data tags] all in uppercase, not beginning with a digit
ID_field ::= ID // [fields] not beginning with a digit
ID_nat ::= ID // [native symbols] beginning with an underscore
ID_type ::= ( ID_nat | ID_cls // [types]
| <b>bool</b> | <b>byte</b> | <b>char</b> | <b>f32</b> | <b>f64</b> |
| <b>float</b> | <b>int</b> | <b>s16</b> | <b>s32</b> | <b>s64</b> |
| <b>s8</b> | <b>u16</b> | <b>u32</b> | <b>u64</b> | <b>u8</b> |
| <b>uint</b> | <b>void</b> | <b>word</b> )
Class, interface, and data [declarations] create new [types] which can be used as type identifiers.
Examples:
var int a; // "a" is a variable, "int" is a type
emit e; // "e" is an internal event
await E; // "E" is an external input event
var Sprite s; // "Sprite" is a class and a type
var List l; // "List" is a data type and a type
return list.CONS.head; // "CONS" is a tag, "head" is a field
_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:
- Decimals are written as is.
- Octals are prefixed with 0.
- Hexadecimals are prefixed with 0x.
- ASCII characters and escape sequences are surrounded by apostrophes.
TODO: "1e10"
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 a sequence of optional modifiers:
Type ::= ID_type ( `&&´ | `&´ | `?´ | `[´ [NUM] `]´ )
A type identifier can be a native identifier, a class identifier, a data type identifier or one of the primitive types:
ID_type ::= ( ID_nat | ID_cls | ID_data
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 Send s; // "s" is an organism of class "Send"
var Tree t; // "t" is a data of type "Tree"
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
Céu supports two forms of references: aliases and pointers.
TODO (like C++ references)
TODO (like C)
restrictions - across yielding statements
TODO (like Maybe)
TODO (vectors, pools)
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
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
TODO ("static code" appended in sequence to the beginning)
Pre ::= pre do Block end
nothing
is a innocuous statement:
Nothing ::= nothing
The syntax for the definition of variables is as follows:
DclVar ::= var Type ID_var [`=´ Assignment] { `,´ ID_var [`=´ Assignment] }
A variable has 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:
DclOrg ::= 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](#parallel-compositions] with the rest of the application.
The table below shows the equivalent expansion of an organism declaration to a
par/or
containing the class body:
|
|
Given that an organism is a variable, the block it is declared restricts its
lifetime.
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 enclosing 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
A pool is a container for dynamic instances of organisms and recursive data of the same type:
DclPool ::= pool Type ID_var [`=´ Assignment] { `,´ ID_var [`=´ Assignment] }
The type has to be a class, interface, or data type identifier followed by a dimension modifier. For pools of classes and recursive data types, the number inside the dimension 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. If not specified, the number of instances in the pool is unbounded and all allocations go to the heap. When specified, allocation goes to a static memory pool preallocated at compile time.
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"
pool D[100] ds; // a pool of at most 100 instances of recursive data type "D"
pool D[] ds; // an unbounded pool of instances of recursive data type "D"
The lifetime of all instances inside a pool is restricted to the block it is declared. When a pool of organisms goes out of scope, all organism bodies are automatically aborted.
See Dynamic execution for organisms allocation. See TODO for recursive data allocation.
TODO (intro/prototype/implementation)
/* internal functions */
DclFun ::= function [@rec] `(´ParList`)´ `=>´ Type ID_var
[ `do´ Block `end´ ]
/* external functions */
DclFunExt ::= output [@rec] `(´ParList`)´ `=>´ Type ID_var ID_ext { `,´ ID_ext }
| input [@rec] `(´ParList`)´ `=>´ Type ID_var ID_ext { `,´ ID_ext }
[ `do´ Block `end´ ]
Return ::= return [Exp]
ParList ::= ParItem { `,´ ParItem }
ParItem ::= [@hold] Type [ID_var]
TODO (like functions in any language)
TODO (more or less like dynamically loaded functions)
TODO (like return in any language)
TODO
DclIsr ::= interrupt `[´ NUM `]´ [@rec]
[ `do´ Block `end´ ]
See also Event handling.
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:
DclInt ::= 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.
External events are used as interfaces between programs and devices from the real world:
- input events represent input devices, such as sensors, switches, etc.
- output events represent output devices, such as LEDs, motors, etc.
Being reactive, programs in Céu have input events as their sole entry points through await statements.
The declaration of input and output events is as follows:
DclExt ::= output (Type | `(´ TypeList `)´) ID_ext { `,´ ID_ext }
| input (Type | `(´ TypeList `)´) 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)
DclReq ::= output/input `[´ [Exp] `]´ `(´ParList`)´ `=>´ Type ID_ext { `,´ ID_ext }
| input/output `[´ [Exp] `]´ `(´ParList`)´ `=>´ Type ID_ext { `,´ ID_ext }
[ `do´ Block `end´ ]
A class
is a template for creating organisms.
It contains an interface and a body common to all instances of the class.
The interface exposes internal variables, 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
interface1.
The body and method implementations may vary among classes sharing the same
interface.
(1 The term "interface" is overload with two possible meanings: the set of members exposed by a class; and a template that defines a set of members that classes can reuse.)
The declaration of classes and interfaces is as follows:
DclCls ::= class ID_cls with
Dcls // interface
do
Block // body
end
DclIfc ::= interface ID_cls with
Dcls // interface
end
Dcls ::= { (DclVar | DclInt | DclPool | DclFun | DclImp) `;´ }
DclImp ::= interface ID_cls { `,´ ID_cls }
Dcls
is a sequence of variables, events, pools, and functions (methods)
declarations.
It can also refer to other interfaces through a DclImp
clause, which copies
all declarations from the referred interfaces.
TODO
DclData ::= data ID_data with
(Struct | Union)
end
Struct ::= DclVar `;´ { DclVar `;´ }
Union ::= DclTag { or DclTag }
DclVar ::= var Type ID_var { `,´ ID_var }
DclTag ::= tag ID_tag with
DclVar `;´ { DclVar `;´ }
end
Example (structured data type)
data Foo with
var u8 a;
var int b;
var s16 c;
end
Example (tagged data type)
data Foo with
// declare 'BAR' as one possible structure of 'Foo'
tag BAR with
var u8 a;
var int b;
var s16 c;
end
or
// declare 'BAZ' as another possible structure of 'Foo'
tag BAZ with
var int d;
var s8 e;
end
end
Note: Céu vectors are not yet supported within data types!
TODO (native/pre)
Native blocks define new types, variables, and functions in C:
Native ::= (native | native/pre) 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
Native declarations provide additional information about external C symbols. A declaration is an annotation followed by a list of symbols:
DclNat ::= 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 symbol is a constant (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:
DclDet ::= @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 | `(´VarList`)´) `=´ Assignment
VarList ::= ID_var { `,´ ID_var } `)´
Assignment ::= Exp
| AssignableBlock
| <await>
| ( [ `(´ ] <emit-ext>
| <call-ext>
| <request-ext>
[ `)´ ] )
| [ `new´ ] Data
| <traverse-loop>
| <traverse-rec>
| Vector
| <lua>
| <do-org>
| <spawn>
| <thread>
The expression on the left side must be assignable.
TODO (Exp, VarList)
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
if-then-else
, loop
, every
, and
par
:
AssignableBlock ::= <do-end> | <if-then-else> | <loop> | <every> | <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.
TODO
Data ::= ID_data [`.´ ID_tag] `(´ List `)´
List ::= [ (Data|Exp) { `,´ (Data|Exp) } ]
Example (structured data type)
// calls the constructor for 'Foo' and uses the provided values for struct initialization
var Foo f = Foo(4, 7, 1);
Example (tagged data type)
// calls the constructor for 'Foo' (using tag BAZ) and uses the provided values for struct initialization
var Foo f = Foo.BAZ(2, -9);
TODO
TODO
Vector ::= Item { `..´ Item }
Item ::= Exp | `[´ [ExpList] `]´
Example
var int[3] v; // declare an empty vector of length 3
v = v .. [8]; // append value '8' to the empty vector
v = v .. [1] .. [5]; // append the values '1' and '5' to the vector
// here: v = {8, 1, 5}
TODO
TODO
See Spawn.
See Threads.
The syntax for function calls is as follows:
/* internal calls */
CallInt ::= [call|call/rec] Exp * `(´ [ExpList] `)´
/* external calls */
CallExt ::= (call+call/rec) ID_ext [ `=>´ (Exp | `(´ [ExpList] `)´)
ExpList = Exp { `,´ Exp }
TODO
TODO
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
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;
TODO (awake rule for internal events, unless "every")
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.
TODO
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.
TODO (emit+await)
Request ::= request ID_ext [ `=>´ (Exp | `(´ [ExpList] `)´)
TODO
DoOrg ::= do ID_cls [with
Constructor
end]
The spawn
statement creates instances of organisms dynamically:
Spawn ::= spawn ID_cls [in Exp] [with
Constructor
end]
TODO(option return)
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 static organisms. The constructor clause is also the same as for static organisms.
In contrast to static organisms, the lifetime of a dynamic instance is attached
to the scope of the pool and not to the scope of the spawn
instantiation.
Furthermore, a dynamic organism is automatically deallocated when its execution
body terminates.
See Static analysis for the restrictions on manipulating pointers and references to organisms.
TODO
kill * Exp * [ `=>´ Exp ]
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[`/´ Exp] [ ID_var [in Exp] ] do
Block
end
A loop
terminates when it reaches a break
or its (optional)
iterator terminates.
TODO (hard limit)
A break
escapes the innermost enclosing loop:
Break ::= break
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 continue
restarts the innermost enclosing loop:
Continue ::= continue
Example:
loop do // loop 1
...
loop do // loop 2
if <cond-1> then
continue; // restarts loop 2
end
...
end
...
if <cond-2> then
break; // restarts loop 1
end
...
end
A continue
can only be used inside an if-then-else
with an
empty else
branch and exactly one level deeper than the enclosing loop
:
loop do
par do // makes the "continue" illegal
if <cond> then
continue;
else // makes the "continue" illegal
...
end
with
...
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 of organisms, ID_var
evaluates to pointers to instances in the pool, one at a time, from the oldest
to the newest created.
ID_var
is automatically declared read-only, with visibility restricted to the
loop body.
TODO (example)
The every
statement continuously awaits an event and executes its body:
Every ::= every (Var | `(´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.
TODO (restrictions, escape/break)
TODO (exception to the rule for internal events)
TODO
TraverseLoop ::= traverse ID_var in (`[´ Exp `]´ | Exp)
[ with Dcls end ]
do
Block
end
TraverseRec ::= traverse['/' NUM] Exp
[ with Block end ]
The finalize
statement postpones the execution of its body to happen when its
associated block goes out of scope:
Finalize ::= finalize
[Exp `=´ Assignment]
with
Block
end
The presence of the optional attribution clause determines which block to
associate with the finalize
:
- The enclosing block, if the attribution is absent.
- The block of the variable being assigned, if the attribution is present.
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 opens 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 [ (Var | `(´VarList`)´) in ] (WCLOCKK|WCLOCKE|ID_ext|Exp)
Block
end
A watching
expands to a par/or
as illustrated below:
|
|
TODO (Var/VarList is implicitly declared)
TODO: strong abortion
TODO: see Await for supported events
TODO
Pause ::= pause/if Exp do
Block
end
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 | async/thread) [ `(´VarList`)´ ] do
Block
end
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:
- Only executes if there are no pending input events.
- Yields control on every
loop
iteration on its body. - Cannot use parallel compositions.
- Cannot nest other asyncs.
- Cannot
await
events. - Cannot
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(async/thread)
TODO
Sync ::= sync do
Block
end
TODO(mutual exclusion isr and sync)
Atomic ::= atomic do
Block
end
TODO
C ::= `{´ <C code> `}´
TODO
Lua ::= `[´ {`=´} `[´
{ <lua code> | `@´ Exp }
`]´ {`=´} `]´
The syntax for expressions in Céu is as follows:
Exp ::= Prim
| Exp or Exp
| Exp and Exp
| Exp (`!=´|`==´|`<=´|`<´|`>´|`>=´) Exp
| Exp `|´ Exp
| Exp `^´ Exp
| Exp `&´ Exp
| Exp (`<<´|`>>´) Exp
| Exp (`+´|`-´) Exp
| Exp (`*´|`/´|`%´) Exp
| (not|`+´|`-´|`~´|`*´|`&&´|`&´|`$$´|`$´) Exp
| `(´ Type `)´ Exp /* same precedence as previous */
| Exp `[´ Exp `]´ /* same precedence for all following */
| Exp (`.´|`:´) (ID_field | ID_tag)
| Exp (`?´|`!´)
| Exp `(´ [ExpList] `)´ [finalize with Block end]
Prim ::= `(´ Exp `)´
| sizeof `(´ (Type|Exp) `)´
| ID_var | ID_nat
| null | NUM | String
| true | false
| global | this | outer
| (call | call/rec) Exp
| <c-stat>
Note: assignments are not expressions in Céu.
TODO: global, this, outer, ID_var, ID_nat, null, NUM, String, true, false, call/call/rec/finalize, C, parens
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.
TODO
TODO
Alias ::= `&´ Exp
The operand to &
must be an [[#sec.exps.assignable|assignable
expression]].
TODO
The operator &&
returns the address of its operand, while the
operator *
dereferences its pointer operand.
Address ::= `&&´ Exp
Deref ::= `*´ Exp
The operand to &&
must be an [[#sec.exps.assignable|assignable
expression]].
TODO (tags,fields)
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)
TODO (?!)
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:
/* lowest priority */
or
and
!= == <= >= < >
|
^
&
>> <<
+ -
* / %
not + - ~ * && & $$ $ (Type)
() [] : . ? !
/* highest priority */
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´
Stmt ::= <empty-string>
| nothing
| escape Exp
| return [Exp]
| break
| continue
/* Declarations */
/* variables, organisms, pools, and internal events */
| var Type ID_var [`=´ <Assignment>] { `,´ ID_var [`=´ <Assignment>] }
| var Type ID_var with
Block
end
| pool Type ID_var [`=´ <Assignment>] { `,´ ID_var [`=´ <Assignment>] }
| event (Type | `(´ TypeList `)´) ID_var { `,´ ID_var }
/* internal functions */
| function [@rec] `(´ParList`)´ `=>´ Type ID_var
[ `do´ Block `end´ ]
/* external functions */
| output [@rec] `(´ParList`)´ `=>´ Type ID_var ID_ext { `,´ ID_ext }
| input [@rec] `(´ParList`)´ `=>´ Type ID_var ID_ext { `,´ ID_ext }
[ `do´ Block `end´ ]
/* interrupts */
| interrupt `[´ NUM `]´ [@rec]
[ `do´ Block `end´ ]
/* external events */
| output (Type | `(´ TypeList `)´) ID_ext { `,´ ID_ext }
| input (Type | `(´ TypeList `)´) ID_ext { `,´ ID_ext }
/* external requests */
| output/input `[´ [Exp] `]´ `(´ParList`)´ `=>´ Type ID_ext { `,´ ID_ext }
| input/output `[´ [Exp] `]´ `(´ParList`)´ `=>´ Type ID_ext { `,´ ID_ext }
[ `do´ Block `end´ ]
/* classes & interfaces */
| class ID_cls with
Dcls
do
Block
end
| interface ID_cls with
Dcls
end
where
Dcls ::= { (<var> | <event-int> | <pool> | <function> | DclImp) `;´ }
DclImp ::= interface ID_cls { `,´ ID_cls }
/* data types */
| data ID_data with
(Struct | Union)
end
where
Struct ::= DclVar `;´ { DclVar `;´ }
Union ::= DclTag { or DclTag }
where
DclVar ::= var Type ID_var { `,´ ID_var }
DclTag ::= tag ID_tag with
DclVar `;´ { DclVar `;´ }
end
/* C integration */
| 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
| (native/pre | native) do
<C code definitions>
end
/* deterministic annotations */
| @safe ID with ID { `,´ ID }
/* Assignments */
| (Exp | `(´VarList`)´) `=´
/* Assignment */
( Exp
| AssignableBlock
| <await>
| ( [ `(´ ] <emit-ext>
| <call-ext>
| <request-ext>
[ `)´ ] )
| [ `new´ ] Data
| <traverse-loop>
| <traverse-rec>
| Vector
| <lua>
| <do-org>
| <spawn>
| <thread>
)
where
Data ::= ID_data [`.´ ID_tag] `(´ List `)´
List ::= [ (Data|Exp) { `,´ (Data|Exp) } ]
Vector ::= Item { `..´ Item }
Item ::= Exp | `[´ [ExpList] `]´
AssignableBlock ::= <do-end> | <if-then-else> | <loop> | <every> | <par>
/* Function calls */
/* internal calls */
| [call|call/rec] Exp * `(´ [ExpList] `)´
/* external calls */
| (call+call/rec) ID_ext [ `=>´ (Exp | `(´ [ExpList] `)´)
/* Event handling */
/* internal/external await */
| ( await ID_ext
| await Exp
| await (WCLOCKK|WCLOCKE)
) [ until Exp ]
| await FOREVER
/* internal/external emit */
| emit Exp [ `=>´ (Exp | `(´ [ExpList] `)´)
| emit ID_ext [ `=>´ (Exp | `(´ [ExpList] `)´)
| emit (WCLOCKK|WCLOCKE)
/* external request */
| request ID_ext [ `=>´ (Exp | `(´ [ExpList] `)´)
/* Organism instantiation */
/* do organism */
| do ID_cls with
Block
end
/* spawn */
| spawn * ID_cls * [in Exp]
[ with Block end ]
/* kill */
| kill * Exp * [ `=>´ Exp ]
/* Flow control */
/* explicit block */
| do Block end
/* pre (top level) execution */
| pre do
Block
end
/* conditional */
| if Exp then
Block
{ else/if Exp then
Block }
[ else
Block ]
end
/* loops */
| loop[`/´ Exp] [ ID_var [in Exp] ] do
Block
end
| every (Var | `(´VarList`)´) in (WCLOCKK|WCLOCKE|ID_ext|Exp) do
Block
end
/* traverse */
| traverse ID_var in (`[´ Exp `]´ | Exp)
[ with Dcls end ]
do
Block
end
| traverse['/' NUM] Exp
[ with Block end ]
/* finalization */
| finalize [<assignment>] with
Block
end
/* parallel compositions */
| (par/and|par/or|par) do
Block
with
Block
{ with
Block }
end
| watching [ (Var | `(´VarList`)´) in ] (WCLOCKK|WCLOCKE|ID_ext|Exp)
do
Block
end
/* pause */
| pause/if Exp do
Block
end
/* asynchronous execution */
| (async | [async/thread]) [ `(´VarList`)´ ] do
Block
end
/* synchronization */
| sync do
Block
end
| atomic do
Block
end
/* C integration */
| `{´ <C code> `}´
/* Lua integration */
| `[´ {`=´} `[´
{ <lua code> | `@´ Exp }
`]´ {`=´} `]´
/* Block */
Block ::= { Stmt `;´ }
/* Identifiers */
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_data ::= ID // beginning with an uppercase letter
ID_tag ::= ID // all in uppercase, not beginning with a digit
ID_field ::= ID // not beginning with a digit
ID_nat ::= ID // beginning with an underscore
ID_type ::= ( ID_nat | ID_cls | ID_data
| bool | byte | char | f32 | f64 |
| float | int | s16 | s32 | s64 |
| s8 | u16 | u32 | u64 | u8 |
| uint | void | word )
/* Types */
Type ::= ID_type ( `&&´ | `&´ | `?´ | `[´ [NUM] `]´ )
/* Lists */
TypeList ::= Type { `,´ Type }
ExpList ::= Exp { `,´ Exp }
VarList ::= ID_var { `,´ ID_var }
ParList ::= ParItem { `,´ ParItem }
where
ParItem ::= [@hold] Type [ID_var]
/* Wall-clock values */
WCLOCKK ::= [NUM h] [NUM min] [NUM s] [NUM ms] [NUM us]
WCLOCKE ::= `(´ Exp `)´ (h|min|s|ms|us)
/* Expressions */
Exp ::= Prim
| (`$$´|`$´) Exp
| Exp (`?´|`!´)
| Exp `(´ [ExpList] `)´ [finalize with Block end]
Prim ::= `(´ Exp `)´
| ID_var | ID_nat
| null | NUM | String
| true | false
| (call | call/rec) Exp
| <c-code>
/* Operator precedence */
/* lowest priority */
or
and
!= == <= >= < >
|
^
&
>> <<
+ -
* / %
not + - ~ * && & $$ $ (Type)
() [] : . ? !
/* highest priority */
/* Other */
// single-line comment
/** nested
/* multi-line */
comments **/
# preprocessor directive
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.