Esterel è un esempio di linguaggio reattivo. Le caratteristiche di Esterel sono le seguenti:
Usando questo paradigma di istante istantaneo, si può vedere Esterel come un linguaggio che ha una macchina d'esecuzione infinitamente rapida: il programma reagisce immediatamente agli stimoli.
L'esecuzione di un programma Esterel sarà la seguente. Il programma inizia con uno stato iniziale. Dopo riceverà degli stimoli (un insieme di segnali di input) e reagirà in un istante (istantaneamente) per produrre altri stimoli (un insieme di segnali di output). Alla fine dell'istante avrà un altro stato che gli permetterà di reagire ad altri stimoli, etc...
Il comportamento di un programma può essere rappresentato da una linea con intervalli. Ogni intervallo rappresenta il tempo fra due reazioni. Sopra si possono mettere i segnali di input all'inizio della reazione. Sotto si possono mettere i segnali di output prodotti dalla reazione. Per esempio:

Il programma al primo istante ha ricevuto S1 e ha reagito emettendo il segnale S5. Al secondo istante, non c'è stato segnale di input ed il programma non ha emesso segnali. Al terzo istante, sul segnale S3, il programma ha reagito emettendo due segnali S5 e S6. Al quarto istante nessun input ma il programma ha emesso S6, etc...
La definizione degli istanti, i.e. la decisione di quanto si chiede
al programma Esterel, è a discrezione della persona che
integra il programma Esterel al suo ambiente. Un esempio di integrazione
è per esempio quello di chiamare il programma ogni tot secondi mettendo
come segnali di input tutti gli stimoli che si sono prodotti durante questo
periodo. È importante notare che questa è una decisione che
viene presa al di fuori del linguaggio Esterel.
Dentro Esterel l'unica cosa che importa è quella di definire la reazione
del programma ad un insieme di segnali.
Istruzioni
emit S1
emit STOP
emit READY
La sequenza termina quando l'ultima istruzione termina.
L'esecuzione è istantanea.
Se per caso la prima istruzione termina subito, allora la seconda istruzione
è eseguita nello stesso instante.
emit S1; emit S2
Questo programma reagisce al primo istante emettendo contemporaneamente S1 e S2 e termina.
emit S1; nothing; emit S2
emit S1; halt; emit S2
Il programma reagisce al primo istante emettendo S1 dopo si ferma per sempre.
emit S1; pause; emit S2
Il programma reagisce al primo istante emettendo S1 dopo si ferma. Nel secondo istante, emette S2 e termina.
In Esterel la sequenza ha precedenza
sul parallelo:
P ; Q || R
si legge come [ P ; Q ] || R
e non P ; [Q || R]
(le parentesi [] sono le parentesi di Esterel).
[
emit S1
||
emit S2;
pause;
emit S3
];
emit S4
Al primo istante il programma emette S1 e S2.
[
emit S1;
halt
||
emit S2;
pause;
emit S3
];
emit S4
Al primo istante il programma emette S1 e S2.
present <NAME> then <STATEMENT> end present
present <NAME> else <STATEMENT> end present
present <NAME> then <STATEMENT> end
present <NAME> else <STATEMENT> end
present S1 then emit S2 else emit S3 end; emit S4Al primo istante se il segnale S1 è presente, emette S2 e S4 e termina. Se il segnale non è presente emette S3 e S4 e termina.
present S1 then emit S2 end; emit S4Al primo istante se il segnale S1 è presente, emette S2 e S4 e termina. Se il segnale non è presente, emette S4 e termina.
L'attesa sul segnale di interruzione
si fa non subito ma all'istante successivo a quello in cui l'interruzione
è stata attivata.
Quando ci sono piú di una
interruzione nello stesso istante, ha precedenza l'interruzione che è
stata dichiarata per prima.
abort emit S1; pause; emit S2 when S3; emit S4Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
abort emit S1; halt; when S2; emit S3Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
abort
abort
emit S1;
halt;
when S2;
emit S3;
when S4;
emit S5
Al primo istante viene emesso il segnale S1, indipendentemente
dalla presenza o meno dei segnali S2 e S4.
L'attesa sul segnale di interruzione è attivata subito.
abort emit S1; pause; emit S2 when immediate S3; emit S4Al primo istante viene emesso il segnale S1 solo se il segnale S3 non è presente. Se il segnale S3 è presente, il programma emette S4 e termina.
weak abort emit S1; pause; emit S2 when S3; emit S4Al primo istante viene emesso il segnale S1 indipendentemente dalla presenza o meno di S3: l'attesa di S3 non è attivata.
L'attesa sul segnale di interruzione si fa subito.
weak abort emit S1; pause; emit S2 when immediate S3; emit S4Al primo istante, se il segnale S3 è presente, il programma emette S1 e S4 e termina. Se il segnale S3 non è presente, il programma emette S1.
Un'interruzione è catturata
solo se il corpo dell'istruzione abort non termina nell'istante.
abort emit S1; pause; emit S2 when S3 do emit S4 end abort; emit S5Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
abort pause; when S do emit S1 end abortIl segnale S1 non sarà mai emesso.
Il corpo di un loop
non può terminare subito: sarebbe un ciclo infinito istantaneo.
loop emit S1; pause; end loopIl programma emette il segnale S1 ad ogni istante.
loop emit S1; pause; pause; end loopIl programma emette il segnale S1 ogni due istanti.
loop emit S1 end loopIl programma è un ciclo infinito ed è rifiutato dal compilatore Esterel.
L'attesa comincia all'istante
successivo.
await Sè equivalente a
abort halt when S
L'attesa comincia subito.
await immediate Sè equivalente a
abort halt when immediate S
sustain Sè equivalente a
loop emit S; pause; end loop
do P upto Sè equivalente a
abort P; halt when S
loop P each Sè equivalente a
loop
abort
P;
halt
when S
end loop
every S do P end everyè equivalente a
await S; loop P each S
every immediate S do P end everyè equivalente a
await immediate S; loop P each S
await 2 Sè equivalente a
await S; await S
abort P when 2 Sè equivalente a
abort
P
when T1;
emit T2;
||
weak abort
await 2 S;
emit T
when T2
loop do P each 2 Sè equivalente a
loop
abort
P;
halt
when 2 S
end loop
every 2 S do P end everyè equivalente a
await 2 S; loop P each 2 S
repeat 2 times do P end repeatè equivalente a
P; P
trap <NAME> in
<STATEMENT>
end trap
Per sollevare una eccezione:
exit <NAME>
Per catturare una eccezione:
trap <NAME> in
<STATEMENT>
handle <NAME> do
<STATEMENT>
end trap
Quando sono sollevate piú eccezioni, ha precedenza l'eccezione che è stata dichiarata per prima.
weak abort P when Sè equivalente a
trap T in
P;
exit T
||
await S;
exit T
end trap
weak abort P when S do Q end abortè equivalente a
trap Terminate in
trap WeakAbort in
P;
exit Terminate
||
await S;
exit WeakAbort
handle WeakAbort do
Q
end trap
end trap
loop
signal S in
present S then emit O end present
pause;
emit S
end signal
end loop
Il segnale O non sarà mai emesso. Al primo istante,
il segnale S non è presente, il programma
si ferma. Al secondo istante, viene emesso il segnale locale S,
l'istruzione signal termina. Si entra in un nuovo ciclo. L'istruzione
signal è eseguita di nuovo, crea un nuovo segnale
locale S che dunque non è presente.
signal S in
loop
present S then emit O end present
pause;
emit S
end loop
end S
è equivalente a:
pause; sustain S
var X : double in P end var
var X : integer, Y: integer in P end varè equivalente a:
var X : integer in
var Y: integer in
P
end var
end var
Il tipo della variabile può essere un tipo di base: boolean, integer, float, double, o un tipo astratto.
Il linguaggio Esterel è deterministico. Dunque non si possono fare assegnazioni parallele sulla stessa variabile in uno stesso istante:
X := 0 || X := 1Non è permesso: alla fine dell'istruzione parallela non si sa se il valore di X è 0 oppure 1.
var X : integer in X := 0 || pause; X := 1 end varè permesso. Ci sono due assegnazioni ma non accadono nello stesso istante.
var X : integer := 1 in P end varè equivalente a:
var X : integer in X := 1; P end var
Si può anche sapere il valore di un segnale l'ultima volta che è
stato emesso (escluso l'istante presente) con l'espressione:
?pre(<NAME>)
Il comportamento di default di un segnale con valore è che un tale segnale può essere emesso una volta sola per istante. Nell'esempio seguente:
emit S1(1) || emit S1(2) || emit S2(?S1)il segnale S1 è emesso due volte nel primo istante. Per default, tale programma è rifutato dal compilatore Esterel. Per determinare il valore di un segnale emesso più di una volta bisogna dare una funzione (associativa e commutativa) che definisce come si fa per combinare i valori. Nell'esempio precedente, se dichiariamo la funzione + come funzione di combinazione, il programma adesso è valido e il valore di S2 è 3 (=1+2).
Per i segnali con valore, bisogna dare il suo tipo. Si può opzionalmente dare il suo valore iniziale (il valore del suo pre al primo istante) e la funzione che permette di combinare i valori del segnale. La sintassi per i segnali di input è:
input <NAME> : <TYPE>;
input <NAME> := <EXPRESSION> : <TYPE>;
input <NAME> : combine <TYPE> with <FUNCTION>;
input <NAME> := <EXPRESSION> : combine <TYPE> with <FUNCTION>;
Lo stesso vale per i segnali di output e inputoutput.
module COUNT:
input I;
output Count: integer;
var Count := 0 : integer in
every I do
Count := Count + 1;
emit Count(Count);
end every
end var
end module
Il segnale I è un segnale d'input. Il segnale Count
è un segnale d'output a valore intero.
Un programma equivalente ma che usa il costruttore pre è il seguente:
module COUNT:
input I;
output Count := 0 : integer;
every I do
emit Count(?pre(Count)+1);
end every
end var
end module
Una relazione d'esclusione indica che due segnali nella relazione non possono essere presenti nello stesso istante.
Una relazione d'implicazione indica che se un segnale è presente, allora un altro segnale è presente.
module SPEED:
input Centimeter, Second;
relation Centimeter # Second;
ouput Speed: integer;
loop
var Distance := 0 : integer in
abort
every Centimeter do
Distance := Distance + 1;
end every
when Second do
emit Speed(Distance)
end abort
end var
end loop
end module
Si può definire un nuovo tipo astratto usando il
comando type:
type <TYPE>
Si può definire una nuova costante astratta usando il
comando constant:
constante <NAME>: <TYPE>
Si può definire una nuova funzione astratta usando il
comando function:
function <NAME>(<TYPE>,...,<TYPE>) : <TYPE>
module GENERIC_SPEED:
type T;
constant Initial: T, Increment: T;
function Add(T,T): T;
input A, B;
output Speed: T;
loop
var Count := Initial : T in
abort
every immediate A do
Count := Add(Count,Increment)
end every
when B do
emit Speed(Count)
end abort
end var
end loop
end module
Si può anche modificare, cambiare, ridenominare i
tipi, costanti, funzioni e segnali:
run <NAME> [
type <NAME> / <NAME>;
constant <NAME> / <NAME>;
function <NAME> / <NAME>;
signal <NAME> / <NAME>;
]
Nella notazione <NAME> / <NAME> il primo nome è quello nuovo ed il secondo è quello definito nel modulo.
run GENERIC_SPEED [ type integer/T; constant 0/Initial, 1/Increment; function +/Add; signal Centimeter/A, Second/B; ]
Assicurarsi che un programma ha questa proprietà di stabilità per tutte le sue configurazioni è la vera difficoltà della programmazione sincrona. Il problema principale è quello della causalità. L'esempio seguente mostra dove è il problema:
present S then nothing else emit S endNon si può dare una semantica a tale programma. All'inizio dell'istante, il segnale S non è presente. Dunque l'istruzione present sceglie l'alternativa else che richiede di emettere il segnale S. Ciò vuole dire che bisogna cambiare l'alternativa e andare nell'alternativa then. Ma in tal caso ciò significa che il segnale S non è piú emesso. Dunque, bisogna cambiare alternativa: il programma non si stabilizza mai. La non-presenza di S implica la presenza di S. C'è un problema di causalità che è individuato staticamente in fase di compilazione.
Un altro problema è quando il programma può avere piú di un comportamento possibile. Un esempio è il programma seguente:
present S then emit S else nothing endPer tale programma, i comportamenti che consistono nell'emettere S e nel non emettere S sono due comportamenti accettabili. Poiché Esterel è un linguaggio deterministico, il compilatore rifiuta tale programma.
module BASIC_STOPWATCH:
input START_STOP, HS;
output TIME: integer;
var TIME := 0 : integer in
loop
emit TIME(TIME);
await START_STOP;
abort
every HS do
TIME := TIME + 1;
emit TIME(TIME);
end every
when START_STOP
end loop
end var
end module
module STOPWATCH_1: input START_STOP, HS, RESET; output TIME: integer; loop run BASIC_STOPWATCH each RESET end module
module STOPWATCH_2:
input START_STOP, HS, RESET, LAP;
output TIME: integer;
signal INTERNAL_TIME : integer in
run STOPWATCH_1[ signal INTERNAL_TIME/TIME]
||
loop
abort
abort
every INTERNAL_TIME do
emit TIME(?INTERNAL_TIME)
end
when LAP;
emit TIME(?INTERNAL_TIME);
await LAP;
when RESET
end loop
end signal
end module.
Per interpretare il pulsante BUTTON_2, definiamo un modulo che usa due segnali locali STOPWATCH_RUNNING, FROZEN_TIME per indicare se il cronometro sta avanzando e se sta mostrando un tempo intermedio:
module BUTTON_INTERPRETER:
input START_STOP, BUTTON_2;
output RESET, LAP;
signal STOPWATCH_RUNNING, FROZEN_TIME in
every BUTTON_2 do
present STOPWATCH_RUNNING then emit LAP
else
present FROZEN_TIME then emit LAP
else emit RESET
end present
end present
end every
||
loop
await START_STOP;
abort
sustain STOPWATCH_RUNNING
when START_STOP
end
||
loop
await LAP;
abort
sustain FROZEN_TIME
when LAP;
end loop
end signal
end module
Tale codice non va bene perché c'è un problema di
causalità fra LAP e FROZEN_TIME.
module BUTTON_INTERPRETER:
input START_STOP, BUTTON_2;
output RESET, LAP;
signal STOPWATCH_RUNNING, FROZEN_TIME in
every BUTTON_2 do
present STOPWATCH_RUNNING then emit LAP
else
present FROZEN_TIME then emit LAP
else emit RESET
end present
end present
end every
||
loop
await START_STOP;
abort
sustain STOPWATCH_RUNNING
when START_STOP
end
||
loop
await LAP;
abort
sustain FROZEN_TIME
when LAP;
end loop
end signal
end module
La presenza di FROZEN_TIME fa emettere LAP che a sua
volta impedisce l'emissione di FROZEN_TIME. Una soluzione è
quella di rendere l'interruzione weak, cosí l'emissione di
LAP impedisce l'emissione di FROZEN_TIME solo all'istante
successivo:
module BUTTON_INTERPRETER:
input START_STOP, BUTTON_2;
output RESET, LAP;
signal STOPWATCH_RUNNING, FROZEN_TIME in
every BUTTON_2 do
present STOPWATCH_RUNNING then emit LAP
else
present FROZEN_TIME then emit LAP
else emit RESET
end present
end present
end every
||
loop
await START_STOP;
abort
sustain STOPWATCH_RUNNING
when START_STOP
end
||
loop
await LAP;
weak abort
sustain FROZEN_TIME
when LAP;
end loop
end signal
end module
module FULL_STOPWATCH :
input START_STOP, HS, BUTTON_2;
ouput TIME: integer;
relation START_STOP # HS # BUTTON_2;
signal RESET, LAP in
run STOPWATCH_2
||
run BUTTON_INTERPRETER
end signal
end module
Il giocatore può inserire una moneta (COIN) e premere sui pulsanti READY e STOP.
La macchina ha:
All'inizio, solo la lampadina GAME_OVER è accesa.
Il gioco comincia quando il giocatore mette una moneta. In tal caso la lampadina GAME_OVER si spenge.
Per valutare il tempo di reazione, la macchina prenderà NUMBER misure e farà una media.
Per iniziare una misura, il giocatore dove premere sul pulsante READY.
Dopo, la macchina aspetta un momento aleatorio e accende la lampadina GO. Adesso il giocatore deve premere il pulsante STOP il piú velocemente possibile.
Alla fine di ogni misura, il tempo di reazione appare sullo schermo DISPLAY.
Alla fine delle NUMBER misure, la macchina aspetta PAUSE_LENGTH millisecondi per mettere sullo schermo la media del tempo. Il gioco è finito.
Il gioco può finire in anticipo, se
Il giocatore è avvertito con la campanella RING_BELL quando
Inserire una moneta COIN in un qualsiasi momento fa cominciare un nuovo gioco.
module REFLEX_GAME:
constant NUMBER, PAUSE_LENGTH, TIME_LIMIT: integer;
function RANDOM(): integer;
input MS, COIN, READY, STOP;
relation MS # COIN # READY, COIN # STOP, READY # STOP;
output GO_ON, GO_OFF, GAME_OVER_ON, GAME_OVER_OFF,
RED_ON, RED_OFF, DISPLAY: integer, RING_BELL;
emit GO_OFF; emit RED_OFF; emit GAME_OVER_ON; emit DISPLAY(0);
module AVERAGE:
input UPDATE_AVERAGE: integer;
output AVERAGE_VALUE: integer;
var MEASURE_NUMBER := 0, TOTAL_TIME := 0 : integer in
every immediate UPDATE_AVERAGE do
TOTAL_TIME := TOTAL_TIME + ?UPDATE_AVERAGE;
MEASURE_NUMBER := MEASURE_NUMBER + 1;
emit AVERAGE_VALUE(TOTAL_TIME/MEASURE_NUMBER);
end every
end var
abort
abort
every STOP do emit RING_BELL end
when READY
when TIME_LIMIT MS do
exit ERROR
end abort
abort
abort
every READY do emit RING_BELL end
when RANDOM() MS;
emit GO_ON
when STOP do
exit ERROR
end abort
abort
var TIME := 0 : integer in
abort
every MS do TIME := TIME+1 end
||
every READY do emit RING_BELL end
when STOP;
emit DISPLAY(time);
emit UPDATE_AVERAGE(TIME);
emit GO_OFF;
end var
when TIME_LIMIT MS do
exit ERROR
module REFLEX_GAME:
constant NUMBER, PAUSE_LENGTH, TIME_LIMIT: integer;
function RANDOM(): integer;
input MS, COIN, READY, STOP;
relation MS # COIN # READY, COIN # STOP, READY # STOP;
output GO_ON, GO_OFF, GAME_OVER_ON, GAME_OVER_OFF,
RED_ON, RED_OFF, DISPLAY: integer, RING_BELL;
emit GO_OFF; emit RED_OFF; emit GAME_OVER_ON; emit DISPLAY(0);
every COIN do
emit GO_OFF; emit RED_OFF; emit GAME_OVER_OFF; emit DISPLAY(0);
trap END_GAME in
trap ERROR in
signal UPDATE_AVERAGE: integer, AVERAGE_VALUE: integer in
run AVERAGE
||
repeat NUMBER times
abort
abort
every STOP do emit RING_BELL end
when READY
when TIME_LIMIT MS do
exit ERROR
end abort;
abort
abort
every READY do emit RING_BELL end
when RANDOM() MS;
emit GO_ON
when STOP do
exit ERROR
end abort;
abort
var TIME := 0 : integer in
abort
every MS do TIME := TIME+1 end
||
every READY do emit RING_BELL end
when STOP;
emit DISPLAY(time);
emit UPDATE_AVERAGE(TIME);
emit GO_OFF;
end var
when TIME_LIMIT MS do
exit ERROR
end repeat;
await PAUSE_LENGTH MS;
emit DISPLAY(?AVERAGE_VALUE);
exit END_GAME
end signal
end trap;
emit RED_ON; emit GO_OFF
end trap;
emit GAME_OVER_ON
end every
G2*. Scrivere un programma che gestisce il telecomando di un televisore. Il telecomando ha due modi:
G3*.
Scrivere un programma che gestisce un orologio per un gioco
con 2 giocatori.
L'orologio mostra per ciascun giocatore il tempo che gli rimane
per finire la partita (i segnali DISPLAY1 e DISPLAY2).
Il tempo è indicato in millisecondi.
`Per calcolare il tempo che passa, il programma riceve un segnale MS ogni millisecondo.
Quando un giocatore ha giocato la sua mossa, preme il suo pulsante
(BUTTON1 o BUTTON2): si ferma il suo tempo e parte
il tempo del suo avversario.
All'inizio della partita, ogni giocatore ha lo stesso tempo (INIT_TIME) ed i due tempi sono fermi.
La partita è finita (END) quando uno dei due tempi arriva a zero.
In ogni momento, si può ri-inizializzare l'orologio usando il pulsante
RESET.
G4*.
Scrivere un programma che gestisce una macchina per fare delle
foto-tessera.
All'inizio, la macchina
non è attiva, ciò viene indicato da una lampadina rossa (RED).
La macchina viene attivata quando un utente inserisce una moneta (INSERT_COIN).
Il fatto che la macchina sia attiva è indicato da una lampadina verde (GREEN).
Una volta inserita la moneta, l'utente deve scegliere il formato delle
foto (WHICH_FORMAT).
Quando l'utente ha scelto il formato (FORMAT), si può proseguire.
Se l'utente non sceglie entro 60 secondi (SECOND), la macchina
restituisce i soldi (RETURN_COIN) e diventa inattiva (RED).
Una sessione è composta di tre foto validate dall'utente.
Per fare una foto, la macchina aspetta 10 secondi, poi accende una
lampadina per avvertire l'utente di essere pronto (WARNING)
e dopo 2 secondi (SECOND) scatta la foto (CLICK). Dopo
3 secondi (SECOND),
mostra il risultato all'utente (SHOW). Se l'utente è d'accordo
(YES), la foto è validata, altrimenti la foto è rifiutata (NO).
Se l'utente non risponde entro 60 secondi (SECOND) o se l'utente
rifiuta più di 10 foto, la macchina restituisce i soldi
(RETURN_COIN) e diventa inattiva (RED).
Quando sono state validate tre foto, la macchina stampa le foto
(PRINT) e diventa inattiva.
G5*.
Scrivere un programma che gestisce i semafori di un incrocio.
Ci sono quattro semafori (nord, est, sud, ovest) ma il controllo è fatto
considerando i semafori a coppia. Ci sono due coppie: la coppia nord-sud
(NS) e la coppia est-ovest (EW).
Un semaforo è alternativamente verde (GREEN_NS o GREEN_EW),
giallo (ORANGE_NS o ORANGE_EW) e rosso (RED_NS o RED_EW).
Quando una coppia di semafori è verde o gialla l'altra deve essere
necessariamente rossa.
In un ciclo normale, un semaforo resta verde 30 secondi (SECOND)
, giallo 2 secondi e rosso 32 secondi.
Un pedone può premere un pulsante (WALK_NS o WALK_EW).
Questo assicura che se il semaforo è verde non lo resterà per piú
di altri 10 secondi.
G6*.
Scrivere un programma che gestisce l'apertura di una porta
automatica di un parcheggio per auto.
Prima di gestire l'apertura della porta, il programma aspetta
di avere il codice segreto (INIT_CODE).
Inizialmente la porta è chiusa.
A intervalli regolari viene segnalata la presenza di una macchina davanti
la porta (DETECT).
La porta si apre (OPEN) se il conducente della macchina
introduce un codice (CODE) che corrisponde al codice
segreto.
La porta si chiude (CLOSE) se non sono piú individuate
macchine (DETECT) per un periodo di 5 secondi (SECOND).
Ogni volta che viene introdotto un codice errato,
si accende una lampadina (RETRY).
Se dopo 2 minuti che una macchina è stata individuata
la porta non è stata aperta, viene avvertito il centralino (ERROR).