Johnicholas Hines

Céu is a language for structured synchronous reactive programming. Céu is a project of LabLua, the same lab that developed the elegant and tiny Lua language. Lua means “moon” in Portuguese, and Céu means “sky”.

Just a few days ago (March 18, 2018) a new version of Céu was released, version 0.30. I had used earlier versions successfully for embedded programming, so I thought I would try to see what the new version looks and feels like.

I started from a Ubuntu 14.04 LTS image, and cloned the Céu repository:

git clone https://github.com/fsantanna/ceu.git  # git was not installed
sudo apt-get install git  # okay, git is now installed
git clone https://github.com/fsantanna/ceu.git

Then I built it, which meant installing make, lua, and libreadline.

ls
cd ceu/
ls
make
sudo apt-get install make
make
sudo apt-get install lua  # lua comes in several versons on Ubuntu
sudo apt-get install lua-5.3  # lua-5.3 is not available by default in Ubuntu 14.04
cd ~
curl -R -O http://www.lua.org/ftp/lua-5.3.4.tar.gz  # just get the source
tar zxf lua-5.3.4.tar.gz
cd lua-5.3.4
make linux test
sudo apt-get install gcc
make linux test
apt-cache search readline
sudo apt-get install libreadline libreadline-dev
sudo apt-get install libreadline6 libreadline6-dev
make linux test
echo 'PATH=$PATH:~/lua-5.3.4/src' >> ~/.bashrc
lua
source ~/.bashrc
lua
cd ~/ceu
make
cd ~/lua-5.3.4/src/
ln -s lua lua5.3
ls
ls -la
cd ~/ceu
make

(After I got this working, I realized that I probably didn’t need lua-5.3 specifically, and I could have installed an earlier version of lua using apt-get. Oh well.)

Next I put the ceu executable on my PATH and tried to write some Céu code.

echo 'PATH=$PATH:~/ceu/src/lua' >> ~/.bashrc
emacs hello.ceu

This the Céu code that I eventually ended up with:

native/nohold _printf;
native/pre do
##include <stdio.h> // include the relevant header for "printf"
end

input int I;
output int O;

// _printf("Boot reaction\n");

// This is probably bad style - leaning on the order of execution to communicate "side band" information from one trail to another.
var bool unhandled = true;
par/or do
  loop do
    await I;
    // _printf("Control flow always reaches here immediately after an I event.\n");
    unhandled = true;
  end
with
  par/and do
    var int a;
    loop do
     a = await I;
     if a % 3 == 0 then
       break;
     end
    end
    // _printf("Control flow only reaches here after an I event containing a multiple of 3.\n");
    emit O(-1);
    unhandled = false;
  with
    var int b;
    loop do
     b = await I;
     if b % 5 == 0 then
       break;
     end
    end
    // _printf("Control flow only reaches here after an I event containing a multiple of 5.\n");
    emit O(-2);
    unhandled = false;
  end
with
  loop do
    var int c;
    loop do
      c = await I;
      if unhandled then
       break;
      end
    end
    // _printf("Control flow only reaches here after an I event was not handled by the 3 or 5 previous trails.\n");
    emit O(c);
  end
with
  loop do
    await I;
    // _printf("Control flow reaches here after an I event.\n");
    emit O(-3);
  end 
end

emit O(-3);

escape 0;

It’s a variant on FizzBuzz. Céu programs are always embedded in an environment, which sends events into the program (here, just the I event), and implements events that the Céu program sends in response. Here, I events, which have integer payloads, are sent into the program, and the program sends back O events, which also have integer payloads.

One of the primary syntactic elements of Céu programming is par - which comes in three flavors, par, par/and, and par/or. The par/and runs multiple blocks in parallel, and the entire par/and block is over when each of the subblocks have finished. This is similar to & in Cardelli and Pike’s Squeak language, or || in Esterel. (BTW, both of those languages are now dead - I don’t consider them viable competitors to Céu.) In contrast par/or runs only until one of the subblocks has finished; the others are terminated. The par construct never finishes, regardless of what its subblocks do, which is more useful than it seems.

Now that we have some Céu code, the executable can compile it into C code. I had to install lpeg in order to run the compiler.

ceu --ceu --ceu-input=hello.ceu
apt-cache search lpeg
sudo apt-get install lua-lpeg
ceu --ceu --ceu-input=hello.ceu
cd ~
wget http://www.inf.puc-rio.br/~roberto/lpeg/lpeg-1.0.1.tar.gz
tar -xvzf lpeg-1.0.1.tar.gz 
cd lpeg-1.0.1/
make
emacs makefile 
make LUADIR=~/lua-5.3.4/src
cd ~/learn_ceu_030/
cp ~/lpeg-1.0.1/lpeg.so .
ceu --ceu --ceu-input=hello.ceu

The next step is to write an environment. The environment is a piece of C code that plumbs the input events in (and the output events out of) the Céu code. You may be able to use an environment someone else wrote, like an “Arduino environment” (if you have an Arduino) or a “libuv environment” (if you want to interact with the world like node.js interacts with the world), or a “SDL environment” (if you want to play with graphics). Almost any serious use of Céu is going to involve writing a custom environment, and writing a little custom environment is really the best way to learn how Céu works.

The environment comprises two files, types.h, which you may not ever need to modify, and main.c, which has all the good stuff. This is the main.c that I ended up with:

// Johnicholas mostly just copied this out of the Ceu manual.

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

int ceu_is_running; // detects program termination

// The signature is slightly different if you are compiling without trace turned on.
// The manual has a ceu_callback_main with trace turned on, a four-argument function.
// We want a ceu_callback_main without trace turned on, a three-argument function.
int ceu_callback_main (int cmd, tceu_callback_val p1, tceu_callback_val p2) 
{
  int is_handled = 0;
  switch (cmd) {
  case CEU_CALLBACK_TERMINATING:
    ceu_is_running = 0;
    is_handled = 1;
    break;
  case CEU_CALLBACK_OUTPUT:
    if (p1.num == CEU_OUTPUT_O) {
      int val = *((int*)p2.ptr);
      if (val == -1) {
        printf("Fizz");
      } else if (val == -2) {
        printf("Buzz");
      } else if (val == -3) {
        printf("\n");
      } else {
        printf("%d", val);
      }
      is_handled = 1;
    }
    break;
  }
  // The meaning of a ceu callback return code is "I handled this".
  // It is used in order to have a bunch of callbacks each specializing in a different
  // subset of output events. Then the Ceu runtime, in order to send a particular output event,
  // will walk through the callbacks up until one of them says "I handled this".
  // In this case, this is the only callback function, and so in some sense it doesn't
  // matter what we return? But returning "is_handled" works for more complicated cases,
  // too.
  return is_handled;
}

int main (int argc, char* argv[])
{
  ceu_is_running = 1;
  tceu_callback cb = { &ceu_callback_main, NULL };
  ceu_start(&cb, argc, argv);
  while (ceu_is_running) {
    int v;
    if (scanf("%d", &v) == 1) {
      ceu_input(CEU_INPUT_I, &v);
    }
    ceu_input(CEU_INPUT__ASYNC, NULL);
  }
  ceu_stop();
}

Basically, control flow enters main, and after a little setup, settles into an event loop, poll()ing or select()ing or whatever, and sending those events into the hello.ceu code with ceu_input(). In response to each of these events, the hello.ceu code calls the callback zero or more times, indicating what output events it wants to send, and the callback interprets the output event codes and payloads, routing them appropriately.

The ceu executable can combine the three files, the one generated previously from the .ceu code, and the two environment files, to make a .c file.

ceu --env --env-ceu=hello.c --env-types=types.h --env-main=main.c --output=hello_plus_env.c

Finally, we can use gcc to compile the hello_plus_env code into an executable that we can run.

gcc -o hello hello_plus_env.c

This version of FizzBuzz reads numbers that the user types on standard in, and repeats them back to the user. But it responds to the first multiple of 3 that the user types by saying “Fizz”, and responds to the first multiple of 5 that the user types by saying “Buzz”. Once it has responded both “Fizz” and “Buzz”, it shuts down; that’s because a par/and was used to combine the Fizz block and the Buzz block.

One flaw in this code is that the environment is clearly switching on whether the payload is -1, -2, -3, or some other number. This kind of magic number is not good; it is a code smell. Ideally, they would instead be their own output events, with “none” payload. Maybe you, dear reader, would like to modify the environment to instead have FIZZ, BUZZ, NEWLINE output events - if that sounds interesting, fork this.

I have not even started to touch on Céu’s more sophisticated and interesting features, nor the interesting new features in v0.30, nor on how to write a C++ class that delegates to Céu (which I think is one of the most interesting ways to integrate Céu with a large existing C++ codebase). I hope to continue blogging about how to use Céu effectively, though. Please contact me, or the ceu-lang group, if you would like to talk about it!

Pages that I opened during this project:

Inbox – johnicholas.hines@gmail.com
The world's leading software development platform · GitHub
Agile Games 2018 - Mob Programming Conference
Céu: The Programming Language
fsantanna/ceu: The Programming Language Céu
ceu-v0.30.pdf
Aloan - One Dance For Destiny - YouTube
Compute Engine - learn ceu 030
Cloud Shell - learn ceu 030
install lua5.3 ubuntu14.04 - Google Search
software installation - Installing lua5.2 vs. lua5.3 on Ubuntu 16.10 - Ask Ubuntu
lua-5.3 install ubuntu - Google Search
Lua: download
install libreadline-dev - Google Search
bashrc vs profile - Google Search
bash - How to correctly add a path to PATH? - Unix & Linux Stack Exchange
how many teats does a giraffe have - Google Search
emacs exit - Google Search
LPeg - Parsing Expression Grammars For Lua
install library ubuntu - Google Search
emacs indent-rigidly arrow keys - Google Search
EmacsWiki: Indenting Text
Rectangles - GNU Emacs Manual
capitalization - Is the convention for naming make files to use a capital 'm', such as Makefile? - Stack Overflow
What scanf function in c returns? - Stack Overflow
src refspec doesn't match any - Google Search
repository - Git error: src refspec master does not match any - Stack Overflow
Céu: The Programming Language

Opinions expressed are solely my own and do not express the views or opinions of my employer.