c-repl/soutenance.md
2024-09-13 09:09:46 +02:00

152 lines
5.9 KiB
Markdown

# Rapport C-repl en C++
## Architecture
### Lexer (Alaric, lexer.cpp)
Première étape de l'interprétation du langage. Le code à l'état brut est transformé en un tableau de tokens.
Le lexer avance dans le texte en essayant de matcher chaque type de token, parfois à l'aide de `regex`, dans un ordre bien choisi.
Lors de cette étape, les constantes littérales sont d'ores et déjà parsées et stockées dans leur type interne.
### Parsing (Augustin, parser.cpp)
Le parser transforme le tableau de tokens en un `AST (Abstract Syntax Tree)`.
Il procède par backtracking en suivant les règles de la grammaire définie dans `types.hpp`.
### Analyse statique (Augustin, analysis.cpp)
Avant d'exécuter le programme, cette étape infère le type de chacun des éléments du programme (stocké sous forme d'`AST`) et vérifie la validité de chaque opération.
Une "mémoire virtuelle", copie affaiblie de la mémoire actuelle de la repl, sert à stocker les types des variables.
Le type de chaque noeud de l'`AST` est calculé récursivement puis la validité des types passés aux opérateurs est vérifiée,
en s'appuyant notamment sur les règles de casting définies dans `casting.cpp`.
### Évaluation (Alaric, eval.cpp)
Les noeuds de l'`AST` sont évalués de manière récursive.
Des exceptions sont utilisées en interne pour traiter les instructions `break`, `continue` et `return`.
### Mémoire (Alaric, memory.cpp)
`Memory` définit une structure de mémoire réutilisable qui sert lors de l'analyse statique et de l'évaluation.
Elle gère notamment les `scopes`.
#### Types (types.hpp)
Rassemble les définitions de type communes à tout le projet.
#### Execute (execute.cpp)
Encapsule toutes les étapes de l'interprétation du code brut aux résultats.
#### Lecture de l'entrée (main.c, input.c)
Gère le traitement des arguments en ligne de commande et l'interface utilisateur de la repl: la partie "interactive" de la repl.
#### Casting (casting.cpp)
Définit les règles de casting entre les types.
#### Erreurs (errors.cpp)
Définit les erreurs, leur affichage.
#### Utilitaires (utils.cpp, debug.cpp)
Fonctions utilitaires et de débogage.
## Appropriation
Certaines parties du langage C ne sont pas spécifiées dans le standard et varient ainsi d'un compilateur à l'autre. Nous avons parfois fait des choix arbitraires, en privilégiant l'option la plus naturelle.
La repl ne permet pas les `undefined behavior` dans la mesure où ceux-ci sont protégés par le C++.
Par exemple, il n'est pas possible de lire une variable non-initialisée: cela produira une erreur.
Il a été choisi que toute instruction ait une valeur et un type. Cela permet d'afficher une valeur de retour à l'utilisateur lorsqu'il entre une ou plusieurs instructions.
Pour les `statements`, ce type est toujours `void`, représenté en interne par la valeur `monostate`. Cependant, certaines `expression-statements` peuvent avoir un type non-`void`. Les `expressions` ont évidemment une valeur.
## Features
### Types
Les types `int`, `double`, `void` et les fonctions composées de ces mêmes types sont supportés.
### Instructions
Les instructions suivantes sont implémentées:
- Opérateurs arithmétiques binaires (`+`, `-`, `*`, `/`, `%`) et unaires (`+`, `-`)
- Opérateurs logiques (`&&`, `||`, `!`)
- Opérateurs de comparaison génériques (`==`, `!=`)
- Opérateurs de comparaison arithmétique (`<=`, `<`, `>=`, `>`)
- Opérateurs d'incrémentation gauche et droite (`++`, `--`)
- Opérateur virgule (`,`)
- Déclarations de variables avec et sans initialisation (`int x;`, `int x = 1;`)
- Assignation de variables (`x = 1`)
- Conditionnelles (`if (..) ..`, `if (..) .. else (..) ..`)
- Boucles (`for`, `while`) avec contrôle (`break`, `continue`)
- Définition de blocs (`{}`)
- Déclaration de fonctions avec et sans corps (`int foo(int x);`, `int foo(int x) { .. };`)
- Appels de fonctions (`foo(1)`)
### Scopes et closures
L'interpréteur prend en charge les `scopes` de bloc et de fonction, ainsi que pour les `for`, comme attendu dans le langage C.
Il gère également les `closures` comme il se doit dans un langage impératif non-fonctionnel.
Une implémentation qui convient à ce type langage (mais pas aux langages multi-paradigmes) est une tableau de pointeurs.
### Contrôle de flot
La repl ne comporte pas de contrôle de flot avant l'évaluation-même de l'AST.
En conséquence, les erreurs associées sont détectées à la volée lors de l'évaluation.
### Erreurs
La repl comporte un système d'erreur riche avec messages d'erreur et position dans le code.
Celles-ci sont divisées en 4 catégories en fonction de l'étape à laquelle elles interviennent:
- `SyntaxError`: lexer et parsing
- `ControlError`: en raison de l'absence de contrôle de flot préalable, à divers endroits
- `TypeError`: analyse statique
- `RuntimeError`: évaluation
Les `RuntimeErrors` sont accompagnées d'une `stack trace` contenant la position dans le code des appels de fonction récursifs.
### Librairie standard
Les fonctions suivantes sont proposées pour interagir directement avec la repl:
- `clear_memory()` réinitialise la mémoire (non supporté hors de la scope globale)
- `dump_memory()` affiche l'état actuel de la mémoire
- `dump_history()` affiche l'historique du code passé en entrée
### Interface utilisateur
Le système d'entrée est pratique : il permet de se déplacer dans la ligne actuelle et de naviguer dans l'historique du code entré.
Les lignes sont automatiquement numérotées pendant que la saisie.
Pour permettre les sauts de ligne multiples, il est nécessaire de taper `;;` pour indiquer au programme que l'on a terminé son entrée.
Le résultat de l'évaluation du programme est alors affiché.
## Tests
Une suite de tests automatisée est disponible : elle teste les différentes parties du code, les fonctionnalités du langage ainsi que chaque type d'erreur.
Des programmes plus conséquents sont disponibles dans `./example`.