Projet de L2 : réimplémentation d’un sous-ensemble de Scheme en OCaml. Les fonctions de bases sont fournies : parser et types de bases.
On souhaite ré-implémenter un interprète d’un sous-ensemble de Scheme en OCaml.
Le langage Scheme est un dialecte de Lisp à notation préfixe. La grammaire du langage est donc identique pour toute expression, aussi nommées s-expression ou sexpr : (function-name arg-1 arg-2 arg-3 ... arg-n)
. Ainsi on écrira (+ 1 2)
pour 1 + 2
, (/ 1 2 3 4)
pour 1 / 2 / 3 / 4
ou (lambda (x) (+ 1 x))
pour function x -> x + 1
.
L’interprétation d’un langage se fait en 3 étapes : l’analyse lexicale, durant laquelle on vérifie que les code est correct vis-à-vis du langage ; l’analyse syntaxique, durant laquelle on vérifie que la syntaxe et la grammaire du langage sont respectées et qui génère un arbre de syntaxe abstrait – ou AST ; l’interprétation, durant laquelle on utilise l’AST construit pour évaluer le programme en tant que tel.
Un AST est une structure utilisable en informatique pour représenter une langue ou un langage.
Dans notre sous-ensemble, on dénote 9 primitives : +
, -
, *
, /
, =
, <
, if
, lambda
et let.
Le parser du langage est fourni. Le but va être de créer l’interprète capable d’évaluer le code, étape par étape.
Deux modules sont fournis : Types
et Parser
. Types
définit tous les types utilisés par l’AST pour parser le code, et Parser
dispose d’une fonction parse
, de signature string -> sexpr list
. Ceux-ci se trouvent dans /src
.
Si besoin, vous trouverez une transcriptions des types plus bas.
- Cloner le dépôt
# SSH Users
git clone git@github.com/ghivert/scheme-project.git
# HTTPS Users
git clone https://github.com/ghivert/scheme-project.git
opam init
- Installer dune
opam install dune
- Aller dans le dossier
/src
cd src
- Lancer la compilation
dune build main.exe && ../_build/default/src/main.exe
- Analyser la structure d’un programme dans l’interprète. Pour cela, n’hésitez pas à afficher des programmes parsés directement dans le top-level. Utilisez
Parser.parse "(+ 1 3)"
par exemple. - Le fichier
main.ml
vous est fourni. Il contient le squelette de code nécessaire pour implémenter le projet. Nous allons commencer par ajouter le support des opérateurs arithmétiques :+
,-
,*
et/
.- Écrire le corps de la fonction
eval
. Celle-ci devra prendre une sexpr en entrée et l’évaluer. Si c’est un atom, renvoyer l’atom, si c’est un Call, appelereval_call
sur le corps de l’expression. On ignorera l’env pour le moment. - Écrire le corps de
eval_call
. Si l’expression est unSpecial
, alors renvoyer l’évaluation deeval_special
, sinon appliquerapply
sur tout le corps duCall
. On ignorera l’env pour le moment. - Écrire le corps de
apply
. Vérifier que l’expression est une primitive, et l’appliquer sur ses arguments à l’aide deapply_primitive
. On ignorera l’env pour le moment. - Écrire le corps de
apply_primitive
. Celui-ci prends une primitive et des arguments, et cherche à matcher la primitive avec un opérateur arithmétique. S’il le trouve, alors il applique séquentiellement l’opération sur tous les arguments et retourne la valeur.
- Écrire le corps de la fonction
- On souhaite rajouter le support de
=
et<
. Proposez une implémentation en appliquant la même démarche qu’au-dessus. Ces deux opérateurs suivent le même schéma que pour les opérateurs arithmétiques.
-
On souhaite rajouter le support de
if
. La syntaxe deif
est la suivante :(if condition consequence alternant)
aveccondition
,consequence
etalternant
des sexpr. Attention, il ne faut évaluer qu’un seul des opérandes :consequence
sicondition
est vraie, sinonalternant
. Indice: Il faut rajouter le support deSpecial If
danseval
.- Rajouter le support de
Special
danseval_call
. Pour cela, écrire la fonctionis_special
puiseval_special
avec le cas duif
. - Évaluer la condition. Si elle est vraie, évaluer la conséquence, sinon évaluer l’alternant.
- Rajouter le support de
-
On souhaite rajouter le support de
lambda
.lambda
crée une fonction anonyme de la même manière quefunction
en OCaml. La syntaxe delambda
est la suivante :(lambda (x) (print x))
. Il est nécessaire de rajouter le support de l’environnement.- Rajouter le support de
Lambda
danseval_special
. Il s’évalue vers unAtom
. Pensez à gérer les arguments de la fonction. Vous pouvez vous aider de la fonctionstring_of_symbol
. - Rajouter le support de
Fun
dansapply
. Il faut appliquer la fonction avecapply_function
. - Écrire le corps de
apply_function
. Récupérer la liste de arguments de la fonction, mettez-là dans un nouvel environnement qui hérite du précédent (portée lexicale), et évaluer la fonction dans le nouvel environnement. - Rajouter la gestion des
Symbol
danseval
.
- Rajouter le support de
-
Rajouter le support de
let
. La syntaxe delet
est la suivante :(let (x 10) body)
avecbody
une sexpr. À l’évaluation, il est possible de transformerlet
enlambda
. Proposez une solution.
Indice : gérerlet
danseval_special
uniquement. -
Proposez une implémentation pour
cons
,car
etcdr
.
let cons : sexpr -> sexpr -> sexpr
(* Tel que (cons x y) construit la liste avec x en tête de la liste
y et lance Invalid_argument "cons" si y n'est pas une liste *)
let car : sexpr -> sexpr
(* Telle que (car x) accède à la tête de la liste x
et lance Invalid_argument "cons" si x n'est pas une liste *)
let cdr : sexpr -> sexpr
(* Telle que (cdr x) accède aux reste de la liste x
et lance Invalid_argument "cons" si x n'est pas une liste. *)
- Pour cela, if faut rajouter un
Atom
dans les types de typeList of sexpr list
et dans le parser unPrimitive
Cdr
,Car
etCons
.
On pourra alors écrire des listes du type(cons 1 (cons 2 (cons 3)))
.
type sexpr
= Atom of atom
| Special of special
| Symbol of string
| Call of sexpr list
and atom
= Int of int
| Bool of bool
| Str_ of string
| Primitive of primitive
| Fun of env * code
and special
= If
| Lambda
| Let
and primitive
= Add
| Sub
| Mul
| Div
| Eq
| Lt
and env = (string * sexpr) list
and code = (string list * sexpr)
open Types
let split_expressions (chars : string) : string list =
let open_matcher = Str.regexp "("
and close_matcher = Str.regexp ")"
and newline_matcher = Str.regexp "\n"
in
chars
|> Str.global_replace newline_matcher " "
|> Str.global_replace open_matcher " ( "
|> Str.global_replace close_matcher " ) "
|> Str.split @@ Str.regexp " "
|> List.filter @@ fun x -> x <> ""
let match_atom (atom : string) : atom =
let ft = Str.first_chars atom 1
and lt = Str.last_chars atom 1
in if ft = "\"" && lt = "\"" then
Str_ (String.sub atom 1 @@ String.length atom - 2)
else if atom = "true" then
Bool true
else if atom = "false" then
Bool false
else
Int (int_of_string atom)
let try_to_match_atom (atom : string) : sexpr =
try
Atom (match_atom atom)
with Failure _ ->
Symbol atom
let sexpr_of_string x : sexpr =
match x with
| "+" -> Atom (Primitive Add)
| "-" -> Atom (Primitive Sub)
| "*" -> Atom (Primitive Mul)
| "/" -> Atom (Primitive Div)
| "=" -> Atom (Primitive Eq)
| "<" -> Atom (Primitive Lt)
| "if" -> Special If
| "lambda" -> Special Lambda
| "let" -> Special Let
| others -> try_to_match_atom others
let rec group_expressions (acc : sexpr list) (chars : string list) =
match chars with
| [] -> (acc, [])
| hd :: tl ->
if hd = "(" then
let (expr, next) = group_expressions [] tl in
group_expressions (expr @ acc) next
else if hd = ")" then
([ Call (List.rev acc) ], tl)
else
group_expressions (sexpr_of_string hd :: acc) tl
let parse (program : string) : sexpr list =
program
|> split_expressions
|> group_expressions []
|> fst
|> List.rev