Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 2024 project subject #7

Merged
merged 7 commits into from
Oct 28, 2024
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions project/project-2024.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Projet Programmation fonctionnelle en Haskell

## Rendu

-- Date à fixer
Mesabloo marked this conversation as resolved.
Show resolved Hide resolved

## Objectif

<!-- Merci Thomas :) -->
Écrire un programme "réaliste" en Haskell, en utilisant les concepts vus en cours. La clarté et lisibilité du code, la production d'un code correct, et l'organisation du projet seront mises en avant dans l'évaluation par rapport à un code débordant de features mais avec des bugs/edge cases non gérés.

## Sujet

Le but de ce projet est de créer un parser et un interpréteur pour un petit langage de programmation inspiré de C, que nous appellerons MiniC dans la suite.
Des idées pour aller plus loin sont proposées, mais non obligatoires.

## MiniC

MiniC est un petit langage de programmation impérative dérivé de C, dont voici la syntaxe :
```
SwitchCases ⩴ ε
| '|' 'default' ':' Block
| '|' 'case' Number (',' Number)* ':' '{' Block '}' SwitchCases
Mesabloo marked this conversation as resolved.
Show resolved Hide resolved

Expression ⩴ Number | Identifier | Boolean | String
| Expression '+' Expression | Expression '-' Expression | Expression '*' Expression
| Expression '=' Expression | Expression '&&' Expression | Expression '||' Expression | `!` Expression
| '(' Expression ')'
Mesabloo marked this conversation as resolved.
Show resolved Hide resolved

Statement ⩴ 'skip' | Identifier ':=' Expression | 'if' Expression 'then' '{' Block '}' 'else' '{' Block '}' | 'switch' Expression SwitchCases | 'print' Expression
Mesabloo marked this conversation as resolved.
Show resolved Hide resolved

Block ⩴ ε | Statement ';' Block
```

Dans la grammaire ci-dessus :
- `ε` dénote le mot vide.
- `Number` dénote la plus longue séquence non vide de caractères numériques (`0` jusqu'à `9`), potentiellement précédée immédiatement (sans espaces) par un `-`.
Il n'est pas question, dans un premier temps, de gérer des nombres entiers sous forme hexadécimale ou binaire.
- `Boolean` dénote les identifiants spéciaux `true` et `false`.
- `String` dénote une chaîne de caractères, c'est-à-dire une séquence de caractères telle que :
- Le premier et le dernier caractères sont des guillemets `"`.
- Tous les caractères au milieu, s'il y en a, sont soit des caractères (ASCII) simples (p.e. `a`, `0`), soit des caractères échapés commençant par `\` et suivi de :
- soit `"`, pour permettre d'écrire des guillemets à l'intérieur;
- soit `n`, pour mettre un retour à la ligne;
- soit `\`, pour permettre d'écrire des `\` à l'intérieur des chaînes de caractères;
- soit `t`, pour une tabulation;
Tout autre caractère échappé doit être invalide syntaxiquement.
Mesabloo marked this conversation as resolved.
Show resolved Hide resolved
- `Identifier` dénote la plus longue séquence non vide de caractères, telle que le premier caractère est alphabétique et les caractères suivants sont soit alphanumériques soit `_`.
Il n'est pas demandé de gérer une unique casse (`snake_case`, `camelCase`, …).
Attention, les mots-clés de la grammaire (`if`, `switch`, …) ainsi que les booléens ne doivent pas être reconnus comme des identifiants.
Ainsi, il ne doit pas être possible d'écrire `true := 0`.
De même, `switchx` doit être reconnu comme un identifiant, et non comme le mot-clé `switch` suivant de l'identifiant `x`.

Les opérateurs `+`, `-`, `*`, `=`, `&&`, `||` devront respecter les priorités opératoires et associativités suivantes :
| Opérateur | Associativité | Priorité |
|:---------:|:-------------:|:--------:|
| `!` | - | 6 |
| `*` | Gauche | 5 |
| `+`, `-` | Gauche | 4 |
| `=` | - | 3 |
| `&&` | Gauche | 2 |
| `\|\|` | Gauche | 1 |

Plus la priorité est haute, plus l'opérateur sera prioritaire lors du parsing.
Par exemple, `!x = y && z` devra être reconnue comme si parenthésée comme `(((!(x)) = (y)) && (z))`.
Mesabloo marked this conversation as resolved.
Show resolved Hide resolved

### Sémantique semi-formelle des expressions

Les expressions de MiniC suivent la sémantique usuelle des langages de programmation similaires.
En particulier :
- `e₁ + e₂`, `e₁ - e₂` et `e₁ * e₂` dénotent respectivement l'addition, la soustraction et la multiplication.
- Si `e₁` et `e₂` s'évaluent en des entiers, les opérations sont celles sur des entiers.
- Si `e₁` et `e₂` s'évaluent en des chaînes de caractères, seule l'addition est définie comme la concaténation.
- Dans tous les autres cas, une erreur devra être levée.
- `e₁ = e₂` dénote le test d'égalité. `e₁` et `e₂` doivent s'évaluer à des valeurs du même type (booléen, chaîne ou entier).
- `e₁ && e₂`, `e₁ || e₂` et `! e₁` dénotent respectivement la conjonction, la disjonction et la négation de booléens.
Les opérateurs `&&` et `||` respectent le *short-circuiting* : si `e₁` s'évalue à `true` (resp. `false`), alors `e₁ || e₂` (resp. `e₁ && e₂`) n'évaluent pas `e₂` et renvoient `true` (resp. `false`).
Si `e₁` ou `e₂` s'évalue en un entier ou une chaîne de caractères, une erreur devra être levée.

### Sémantique semi-formelle des instructions

Tout comme les expressions, les instructions suivent la sémantique usuelle de C.
- `skip` ne fait rien du tout.
- `x := e` assigne à `x` la valeur de `e`, si évaluer `e` ne lève pas d'erreur.
- `if e then B₁ else B₂` exécute `B₁` si la valeur de `e` est `true`, sinon `B₂` si la valeur de `e` est `false`.
Si `e` ne s'évalue pas à un booléen, une erreur doit être levée.
- `switch e | case i₁: B₁ | … | case iₙ: Bₙ | (default: B)?` exécute une des branches en fonction de la valeur de `e`.
Si `e` ne s'évalue pas à un entier, une erreur doit être levée.
Si la valeur de `e` correspond à une branche, i.e. que la valeur de `e` est égale à un entier `iₖ` (pour `1 ≤ k ≤ n`), alors le block `Bₖ` doit être exécutée.
Si la valeur de `e` ne correspond à aucune branche, et qu'une branche `default` est présente, alors celle-ci doit être exécutée.
S'il n'y a pas non plus de branche `default`, l'instruction `switch` ne fait rien.
- `print e` affiche la valeur de `e` sur la sortie standard.

### Parsing de MiniC

Vous avez plusieurs choix pour l'écriture du parser de MiniC, sans impact sur la notation.
- Si vous vous sentez aventureux, vous pouvez écrire le parser entièrement à la main.
Attention cependant, cela n'est pas facile et il est plus intéressant de se concentrer sur l'interpréteur.
- Vous pouvez sinon utiliser des bibliothèques telles [parsec](https://hackage.haskell.org/package/parsec) ou [megaparsec](https://hackage.haskell.org/package/megaparsec) pour vous faciliter l'écriture du parser.
Ces deux bibliothèques possèdent des tutoriels afin de vous accompagner dans l'écriture de votre parser, aussi bien au niveau des instructions que des expressions (par exemple pour la gestion des priorités opératoires).

Dans tous les cas, il vous faudra définir des types permettant de décrire l'arbre de syntaxe, afin de pouvoir ensuite écrire l'interpréteur.

### Interpréteur de MiniC

L'interpréteur de MiniC devra se composer de deux interpréteurs différents : un pour les expressions du langage, et un autre pour les instructions/block.

- L'interpréteur pour les expressions réduit une expression en sa forme normale, définie par le type `data Value = Number Integer | Boolean Bool | String String`.
- L'interpréteur des blocks réduit chaque instruction du block une à une, à la suite, jusqu'à ce qu'il n'y en ait plus.

Dans les deux cas, l'interpréteur dépendra d'un contexte, qui assigne des valeurs (de type `Value`) à des noms de variables (on peut le voir comme un *mapping partiel* des noms de variables vers des valeurs).

Il est possible, au choix :
1. De gérer les erreurs d'interprétation avec la fonction [`error`](https://hackage.haskell.org/package/base-4.20.0.1/docs/Prelude.html#v:error).
Dans ce cas, vous pourrez utiliser la monade suivante pour écrire l'interpréteur :
```haskell
type MonadInterpreter m = (MonadState Context m, MonadIO m)

run :: Block -> IO ()
run B = evalStateT (executeBlock B) emptyContext
```
2. De gérer les erreurs d'interprétation avec la monade [`MonadError`](https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-Monad-Error-Class.html#t:MonadError).
Vous pourrez utiliser cette monade pour écrire l'interpréteur :
```haskell
type MonadInterpreter m = (MonadState Context m, MonadError String m, MonadIO m)

run :: Block -> IO (Either String ())
run B = do
res <- evalStateT (runExceptT $ executeBlock B) emptyContext
case res of
Left err -> putStrLn $ "error: " ++ err
Right () -> pure ()
```

> [!WARNING]
> Attention à bien remplacer `executeBlock` par le nom de votre fonction d'exécution pour les blocks (de type `MonadInterpreter m => m ()`),
> et `emptyContext` par la représentation d'un contexte vide (sans association de variables).

> [!IMPORTANT]
> Vous aurez besoin d'ajouter `{-# LANGUAGE ConstraintKinds, FlexibleContexts #-}` au début de votre fichier (avant la déclaration du module).
> Cela vous permettra d'écrire le type `MonadInterpreter`, sans quoi le compilateur Haskell vous renverra des erreurs.

La sortie standard devra inclure les valeurs imprimées par l'instruction `print`, suivie de l'erreur s'il y en a une.
Aucun format particulier n'est attendu pour l'affichage de l'erreur.
Ne vous embêtez pas à essayer d'afficher des erreurs avec le code source originel, cela n'en vaut pas la peine ici.

## Bonus: pour aller plus loin

Voici plusieurs axes d'améliorations/extensions possibles pour MiniC :
- Dans le langage proposé, il n'y a pas de boucles.
Vous pouvez ajouter une boucle `'while' Expression '{' Block '}'` qui exécutera le block tant que la valeur de l'expression est `true`.
Ne vous préoccupez pas de la terminaison, si vous écrivez `while true { print 0; };`, la sortie standard doit inclure une infinité de `0` (jusqu'à <kbd>Ctrl</kbd>+<kbd>C</kbd> en tout cas).
- MiniC n'inclut pas d'instruction permettant d'arrêter le programme dans des circonstances extrêmes.
Vous pourrez ajouter une instruction `'panic' String` qui fait quitter le programme immédiatement avec la chaîne associée comme erreur (potentiellement préfixée par `panic:`).
- Ajouter un opérateur `/` représentant la division euclidienne sur les entiers.
Cet opérateur doit avoir la même priorité opératoire que la multiplication.
Attention au cas de division par zéro.

> [!IMPORTANT]
> N'essayez pas de faire tous les bonus!
> Cela ne vous apportera pas forcément plus de points que si vous en aviez fait moins.

## Rendu

Une archive Zip dont le nom respecte le format `<nom1>-[<nom2>-]projet-haskell.zip`, qui contient :
- le code Haskell du projet, dans un dossier `src/` à la racine, et les fichiers générés par `stack`/`cabal` à la racine de l'archive;
- un fichier `README.md` à la racine de l'archive, décrivant les aspects suivants de votre projet (en français ou en anglais au choix) :
- comment le compiler;
- comment l'exécuter (en particulier, est-ce que votre exécutable prend un fichier en argument ou du code sur l'entrée standard);
- les bibliothèques utilisées, et pourquoi;
- les potentielles fonctionnalités non implémentées, bug connus ou difficultés rencontrées;
- plusieurs fichiers contenant des exemples de programmes MiniC que vous avez testés, dans un dossier `examples/` à la racine;
- tout autre fichier potentiel que vous pensez pertinents quant à la correction.

> [!NOTE]
> Veillez à bien documenter votre code, idéalement en anglais!

## Notation


Mesabloo marked this conversation as resolved.
Show resolved Hide resolved

## Des questions ?

Si vous avez des questions relatives au projet, n'hésitez pas à m'envoyer un mail à <[email protected]>.
Loading