A simile such as “Like ravenous wolves come swooping down on lambs, […] the Achaeans mauled the Trojans.” is a bridge from the familiar to the unfamiliar. If you are familiar with how wolves swoop on lambs, then the analogy helps you visualize the Achaeans mauling the Trojans.
This blog post is an attempt to use the similarity between Céu and Shenzhen I/O to describe Céu. That is, we are leaning on your presumed familiarity with Shenzhen I/O.
In Shenzhen I/O, there are two notions of time. If you “step” your design, then you might, for example, advance one microcontroller’s state forward by one line of code. However, if you “advance” your design, then you will run all of the microcontrollers until they all are sleeping (and it is a bug if they do not eventually sleep), and then advance the outside world, including inputs, forward one step.
Céu has exactly the same distinction. When an event from the external world arrives, Céu processes it, taking as many “steps” as necessary, but it is a bug (and the compiler will warn you about a tight loop), if your code does not eventually sleep.
The first example circuit in the Shenzhen I/O manual, the square wave generator, consists of a single MC4000 microcontroller, with its p1 pin connected to an output pin.
It contains this code:
# On for three, # off for three: mov 100 p1 slp 3 mov 0 p1 slp 3
This corresponds very closely to this Céu code:
loop do // on for three, // off for three: _square = 100; await 3ms; _square = 0; await 3ms; end
Both loop endlessly, changing p1 from 100 to zero, and waiting for a specific amount of time.
One difference is that the time units are ambiguous on the Shenzhen I/O side, and specific on the Céu side.
Another difference is that Céu is embedded in a C context. The underscore prefix in Céu means
“This is opening the trapdoor to access the C context”. So
p1 would have to be a C variable visible to Céu.
That is, I have chosen to render “mov 100 p1” as assigning to a C variable, “square”.
The second example circuit, the touch-activated light controller, has two microcontrollers. The first MC4000 has its p0 simple I/O pin connected to the touch input, and its x1 XBus pin connected to the second MC4000.
It has this code:
# Detect a rising # edge in touch: teq acc 0 + teq p0 100 + mov 1 x1 - mov 0 x1 # store touch: mov p0 acc slp 1
The second MC4000 receives XBus messages from the first MC4000 at its x0 pin, and its p1 pin is connected to the light output. It has this code:
# Advance state: slx x0 teq x0 1 + add 50 # Wrap after 100: tgt acc 100 + mov 0 acc # Update light: mov acc p1
This corresponds closely to this Céu code:
event int e; // "e" is an internal event that carries values of type "int" par do var int acc = 0; loop do // Detect a rising // edge in touch: if acc == 0 and _touch == 100 then emit e1 (1); else emit e1 (0); end // store touch: acc = _touch; await 1ms; end with var int acc = 0; loop do // Advance state: var x0 = await e; if x0 == 1 then acc = acc + 50; end // Wrap after 100: if acc > 100 then acc = 0; end // Update light: _light = acc end end
par syntax allows the programmer to split control flow, creating two so-called
which behave analogously to separate microcontrollers in Shenzhen I/O.
The second microprocessor or trail is processing every event sent down the bus; this is a pattern that occurs
moderately often. Céu has syntactic sugar for this case.
every x in y do ...loop body... end is syntactic sugar for
loop do var x = await y; ...loop body... end.
Zachtronics’s Shenzhen I/O is a game. Céu is a real programming language. Both are examples of the synchonous programming model, which is characterized by this “the computer is much faster than the outside world” idea.
The Céu compiler is written in Lua, and it generates C Code.