Premiers pas › Concepts

Premiers pas

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#