Concepts : le modèle avant la syntaxe
arche-lang décrit un système à l'aide de quelques idées, chacune très simple.
Maîtrisez ces cinq-là : le reste du langage n'est que détail. Chaque section
comporte un schéma et un modèle qui compile (le test test/teach.test.js les
vérifie tous).
La syntaxe tient sur une page dans l'aide-mémoire. Le langage complet est décrit dans la spécification et dans la référence du langage.
1. Définition et instance : composant et part#
Un component est une définition : un type d'objet, écrit une seule fois. Une
part en est une instance, placée dans un system. Deux parts d'un même
composant sont deux objets distincts, chacun avec son propre état et ses propres
variables.
flowchart LR
Lamp["component Lamp<br/>(la définition)"]
subgraph Room["system Room"]
desk["part desk: Lamp"]
ceiling["part ceiling: Lamp"]
end
Lamp -. instance de .-> desk
Lamp -. instance de .-> ceiling
component Lamp "Lamp" {
var level: uint8 = 0
}
system Room "Room" {
part desk: Lamp
part ceiling: Lamp
}
Un système, c'est ce qu'Arche affiche sur son canevas et ce que le simulateur exécute. Un système est lui aussi une définition : une part peut être un système, si bien qu'un modèle s'imbrique.
2. Port, message, lien#
Une part communique avec une autre par ses ports. Un port possède une direction
(in, out, inout), un type (le support physique : can, ethernet,
gpio…) et les messages qu'il envoie ou reçoit. Un message, ce sont les
données qui circulent. Un lien a.p -> b.q relie deux ports de deux parts.
archec vérifie que ce qu'une extrémité envoie, l'autre le reçoit.
flowchart LR
button["button: Button"] -- "click → cmd<br/>PRESS { count }" --> lamp["lamp: Lamp"]
message PRESS "Press" { count: uint8 }
component Button "Button" {
out click: can sends PRESS
}
component Lamp "Lamp" {
in cmd: can receives PRESS
}
system Room "Room" {
part button: Button
part lamp: Lamp
button.click -> lamp.cmd
}
Les ports appartiennent au composant ; le lien appartient au système. Le même
Lamp peut être câblé d'une façon dans un système et d'une autre façon dans un
autre.
3. Machines à états et déclencheurs#
Un composant peut avoir une machine à états. Elle démarre dans son premier
état. Une transition A -> B on TRIGGER [guard] est franchie quand le
déclencheur survient et que la garde est vraie. Un déclencheur est un message
qui arrive sur un port, ou un événement nommé. Le corps d'une fonction peut
aussi changer d'état, avec goto, et tester l'état courant, avec
state == On.
stateDiagram-v2
[*] --> Off
Off --> On: PRESS
On --> Off: PRESS
On --> Off: main, après 10 s (goto Off)
message PRESS "Press" { count: uint8 }
component Button "Button" {
out click: can sends PRESS
}
component Lamp "Lamp" {
in cmd: can receives PRESS
states {
state Off
state On
Off -> On on PRESS
On -> Off on PRESS
}
fn main "Night mode" ```arche
wait 10s
if state == On {
goto Off
}
}
system Room "Room" { part button: Button part lamp: Lamp button.click -> lamp.cmd }
Le corps est écrit dans le langage d'action, dans un bloc ```` ```arche ````.
archec vérifie les noms, les types, les états et les ports qu'il utilise, puis
le traduit dans le Python qu'exécute le simulateur, ainsi qu'en C, en Rust et en
Go. `main` s'exécute dès le démarrage, comme thread propre de la part.
## 4. Exigence, satisfait, vérifiée par, plan de test
Une `requirement` (exigence) énonce ce qui doit être vrai. Une part la
`satisfies` (satisfait) : c'est dans cette part qu'elle est remplie. Un cas d'un
plan de test la `verifies` (vérifie) : ce test montre qu'elle est respectée.
`verified-by`, sur l'exigence, désigne le plan de test depuis l'autre côté.
archec signale toute exigence que rien ne satisfait ou que rien ne vérifie.
```mermaid
flowchart LR
req["requirement REQ-ON"]
lamp["component Lamp"] -- satisfait --> req
tp["test-plan TP-ON<br/>case TC-ON-01"] -- vérifie --> req
req -. vérifiée par .-> tp
message PRESS "Press" { count: uint8 }
requirement REQ-ON "Turn on" {
text "The lamp shall turn on within 50 ms of a press."
verified-by TP-ON
}
component Button "Button" {
out click: can sends PRESS
}
component Lamp "Lamp" {
satisfies REQ-ON
in cmd: can receives PRESS
states {
state Off
state On
Off -> On on PRESS
On -> Off on PRESS
}
}
system Room "Room" {
part button: Button
part lamp: Lamp
button.click -> lamp.cmd
}
test-plan TP-ON "Turning on" {
case TC-ON-01 "A press turns it on" {
verifies REQ-ON
at 0ms do send(button.click, PRESS(count=1))
within 50ms expect lamp.state == "On"
}
}
archec test exécute le cas dans le simulateur. Une étape de test est écrite en
Python, tel que le simulateur l'exécute : do agit, expect vérifie.
5. Système, déploiement, code généré#
Le système logique décrit ce que font les parts. Un second système, la
plateforme, décrit ce sur quoi elles s'exécutent : processeurs et bus. Un
deployment place chaque part du premier sur le second — un binaire sur un
processeur, un thread qui exécute la part — sans modifier ni l'un ni l'autre.
Les générateurs lisent le déploiement et écrivent le code : un programme par
binaire.
flowchart LR
logical["system Room<br/>(ce qu'il fait)"] --> dep["deployment Board"]
hw["system RoomHw<br/>(ce sur quoi il s'exécute)"] --> dep
dep --> gen["archec gen --target c<br/>embassy · go · ros2 …"]
gen --> code["lamp_fw : C, Rust, Go…"]
message PRESS "Press" { count: uint8 }
component Button "Button" {
out click: can sends PRESS
}
component Lamp "Lamp" {
in cmd: can receives PRESS
var level: uint8 = 0
fn dim "Dim" {
```arche
if level > 0 {
level -= 1
}
```
wcet 50us
}
}
system Room "Room" {
part button: Button
part lamp: Lamp
button.click -> lamp.cmd
}
component Mcu "Lamp MCU" {
kind processor
cores 1
inout can0: can "CAN"
}
system RoomHw "Room hardware" {
part mcu: Mcu
}
deployment Board "On the board" {
system Room
platform RoomHw
binary lamp_fw "lamp.elf" on mcu {
thread ctl "Control" { runs lamp; period 10ms; step dim }
}
}
archec gen room.arche --target c -o generated
écrit generated/c/ : lamp.c, la part, avec dim traduite depuis son corps
```arche (if (self->level > 0) { self->level -= 1; }), les types des
messages, et lamp_fw/, le programme du binaire : son main, le thread qui
appelle dim toutes les 10 ms, et un Makefile. Voir la page
génération de code pour toutes les cibles.
Pour aller plus loin#
- L'aide-mémoire : les déclarations essentielles, une ligne chacune.
- Le tutoriel : une pompe à eau, du modèle au firmware.
- Démarrer un projet :
archec new, un projet qui passe les vérifications sans erreur.