152 lines
5.9 KiB
Markdown
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`.
|