Compilation

The compiler converts an input program in Céu to an output in C, which is further embedded in an environment satisfying a C API, which is finally compiled to an executable:

Command Line

The single command ceu is used for all compilation phases:

Usage: ceu [<options>] <file>...

Options:

    --help                      display this help, then exit
    --version                   display version information, then exit

    --pre                       Preprocessor Phase: preprocess Céu into Céu
    --pre-exe=FILE                  preprocessor executable
    --pre-args=ARGS                 preprocessor arguments
    --pre-input=FILE                input file to compile (Céu source)
    --pre-output=FILE               output file to generate (Céu source)

    --ceu                       Céu Phase: compiles Céu into C
    --ceu-input=FILE                input file to compile (Céu source)
    --ceu-output=FILE               output source file to generate (C source)
    --ceu-line-directives=BOOL      insert `#line´ directives in the C output

    --ceu-features-lua=BOOL         enable `lua´ support
    --ceu-features-thread=BOOL      enable `async/thread´ support
    --ceu-features-isr=BOOL         enable `async/isr´ support

    --ceu-err-unused=OPT            effect for unused identifier: error|warning|pass
    --ceu-err-unused-native=OPT                unused native identifier
    --ceu-err-unused-code=OPT                  unused code identifier
    --ceu-err-uninitialized=OPT     effect for uninitialized variable: error|warning|pass

    --env                       Environment Phase: packs all C files together
    --env-types=FILE                header file with type declarations (C source)
    --env-threads=FILE              header file with thread declarations (C source)
    --env-ceu=FILE                  output file from Céu phase (C source)
    --env-main=FILE                 source file with main function (C source)
    --env-output=FILE               output file to generate (C source)

    --cc                        C Compiler Phase: compiles C into binary
    --cc-exe=FILE                   C compiler executable
    --cc-args=ARGS                  compiler arguments
    --cc-input=FILE                 input file to compile (C source)
    --cc-output=FILE                output file to generate (binary)

All phases are optional. To enable a phase, the associated prefix must be enabled. If two consecutive phases are enabled, the output of the preceding and the input of the succeeding phases can be omitted.

Examples:

# Preprocess "user.ceu", and converts the output to "user.c"
$ ceu --pre --pre-input="user.ceu" --ceu --ceu-output="user.c"
# Packs "user.c", "types.h", and "main.c", compiling them to "app.out"
$ ceu --env --env-ceu=user.c --env-types=types.h --env-main=main.c \
      --cc --cc-output=app.out

C API

The environment phase of the compiler packs the converted Céu program and additional files in the order as follows:

  1. type declarations (option --env-types)
  2. thread declarations (option --env-threads, optional)
  3. a callback prototype (fixed, see below)
  4. Céu program (option --env-ceu, auto generated)
  5. main program (option --env-main)

The Céu program uses standardized types and calls, which must be previously mapped from the host environment in steps 1-3.

The main program depends on declarations from the Céu program.

Types

The type declarations must map the types of the host environment to all primitive types of Céu.

Example:

#include <stdint.h>
#include <sys/types.h>

typedef unsigned char bool;
typedef unsigned char byte;
typedef unsigned int  uint;

typedef ssize_t  ssize;
typedef size_t   usize;

typedef int8_t    s8;
typedef int16_t  s16;
typedef int32_t  s32;
typedef int64_t  s64;

typedef uint8_t   u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

typedef float    f32;
typedef double   f64;

Threads

If the user program uses threads and the option --ceu-features-thread is set, the host environment must provide declarations for types and functions expected by Céu.

Example:

#include <pthread.h>
#include <unistd.h>
#define CEU_THREADS_T               pthread_t
#define CEU_THREADS_MUTEX_T         pthread_mutex_t
#define CEU_THREADS_CREATE(t,f,p)   pthread_create(t,NULL,f,p)
#define CEU_THREADS_CANCEL(t)       ceu_dbg_assert(pthread_cancel(t)==0)
#define CEU_THREADS_JOIN_TRY(t)     0
#define CEU_THREADS_JOIN(t)         ceu_dbg_assert(pthread_join(t,NULL)==0)
#define CEU_THREADS_MUTEX_LOCK(m)   ceu_dbg_assert(pthread_mutex_lock(m)==0)
#define CEU_THREADS_MUTEX_UNLOCK(m) ceu_dbg_assert(pthread_mutex_unlock(m)==0)
#define CEU_THREADS_SLEEP(us)       usleep(us)
#define CEU_THREADS_PROTOTYPE(f,p)  void* f (p)
#define CEU_THREADS_RETURN(v)       return v

TODO: describe them

Céu

The converted program generates types and constants required by the main program.

External Events

For each external input and output event <ID> defined in Céu, the compiler generates corresponding declarations as follows:

  1. An enumeration item CEU_INPUT_<ID> that univocally identifies the event.
  2. A define macro _CEU_INPUT_<ID>_.
  3. A struct type tceu_input_<ID> with fields corresponding to the types in of the event payload.

Example:

Céu program:

input (int,u8&&) MY_EVT;

Converted program:

enum {
    ...
    CEU_INPUT_MY_EVT,
    ...
};

#define _CEU_INPUT_MY_EVT_                                                         

typedef struct tceu_input_MY_EVT {                                               
    int _1;                                                                     
    u8* _2;                                                                     
} tceu_input_MY_EVT;

Data

The global CEU_APP of type tceu_app holds all program memory and runtime information:

typedef struct tceu_app {
    bool end_ok;                /* if the program terminated */
    int  end_val;               /* final value of the program */
    bool async_pending;         /* if there is a pending "async" to execute */
    ...
    tceu_code_mem_ROOT root;    /* all Céu program memory */
} tceu_app;

static tceu_app CEU_APP;

The struct tceu_code_mem_ROOT holds the whole memory of the Céu program. The identifiers for global variables are preserved, making them directly accessible.

Example:

var int x = 10;
typedef struct tceu_code_mem_ROOT {                                             
    ...
    int  x;                                                                         
} tceu_code_mem_ROOT;    

Main

The main program provides the entry point for the host platform (i.e., the main function), implementing the event loop that senses the world and notifies the Céu program about changes.

The main program interfaces with the Céu program in both directions:

  • Through direct calls, in the direction main -> Céu, typically when new input is available.
  • Through callbacks, in the direction Céu -> main, typically when new output is available.

Calls

The functions that follow are called by the main program to command the execution of Céu programs:

  • void ceu_start (void)

    Initializes and starts the program. Should be called once.

  • void ceu_stop (void)

    Finalizes the program. Should be called once.

  • void ceu_input (tceu_nevt evt_id, void* evt_params)

    Notifies the program about an input evt_id with a payload evt_params. Should be called whenever the event loop senses a change. The call to ceu_input(CEU_INPUT__ASYNC, NULL) makes asynchronous blocks to execute a step.

  • int ceu_loop (void)

    Implements a simple loop encapsulating ceu_start, ceu_input, and ceu_stop. On each loop iteration, make a CEU_CALLBACK_STEP callback and generates a CEU_INPUT__ASYNC input. Should be called once. Returns the final value of the program.

Callbacks

The Céu program makes callbacks to the main program in specific situations:

tceu_callback_ret ceu_callback (int cmd, tceu_callback_arg p1, tceu_callback_arg p2);

enum {
    CEU_CALLBACK_START,                 /* once in the beginning of `ceu_start`             */
    CEU_CALLBACK_STOP,                  /* once in the end of `ceu_stop`                    */
    CEU_CALLBACK_STEP,                  /* on every iteration of `ceu_loop`                 */
    CEU_CALLBACK_ABORT,                 /* whenever an error occurs                         */
    CEU_CALLBACK_LOG,                   /* on error and debugging messages                  */
    CEU_CALLBACK_TERMINATING,           /* once after executing the last statement          */
    CEU_CALLBACK_ASYNC_PENDING,         /* whenever there's a pending "async" block         */
    CEU_CALLBACK_THREAD_TERMINATING,    /* whenever a thread terminates                     */
    CEU_CALLBACK_ISR_ENABLE,            /* whenever interrupts should be enabled/disabled   */
    CEU_CALLBACK_ISR_ATTACH,            /* whenever an "async/isr" starts                   */
    CEU_CALLBACK_ISR_DETACH,            /* whenever an "async/isr" is aborted               */
    CEU_CALLBACK_ISR_EMIT,              /* whenever an "async/isr" emits an innput          */
    CEU_CALLBACK_WCLOCK_MIN,            /* whenever a next minimum timer is required        */
    CEU_CALLBACK_WCLOCK_DT,             /* whenever the elapsed time is requested           */
    CEU_CALLBACK_OUTPUT,                /* whenever an output is emitted                    */
    CEU_CALLBACK_REALLOC,               /* whenever memory is allocated/deallocated         */
};

TODO: payloads

The main program must implement the ceu_callback prototype above to handle the enumerated commands.

Example

Suppose the environment supports the events that follow:

input  int I;
output int O;

The main.c implements an event loop to sense occurrences of I and a callback handler for occurrences of O:

#include "types.h"      // as illustrated above in "Types"

int ceu_is_running;     // detects program termination

tceu_callback_ret ceu_callback (int cmd, tceu_callback_arg p1, tceu_callback_arg p2) {
    tceu_callback_ret ret = { .is_handled=1 };
    switch (cmd) {
        case CEU_CALLBACK_TERMINATING:
            ceu_is_running = 0;
            break;
        case CEU_CALLBACK_OUTPUT:
            if (p1.num == CEU_OUTPUT_O) {
                printf("output O has been emitted with %d\n", p2.num);
            }
            break;
        default:
            ret.is_handled = 0;
    }
    return ret;
}

int main (void) {
    ceu_is_running = 1;

    ceu_start();

    while (ceu_is_running) {
        if detects(CEU_INPUT_A) {
            int v = <...>;
            ceu_input(CEU_INPUT_A, &v);
        }
        ceu_input(CEU_INPUT__ASYNC, NULL);
    }

    ceu_stop();
}