-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.ml
562 lines (484 loc) · 16 KB
/
main.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
(*
* projet.ml
*
* -- Projet Programmation Fonctionnelle
* -- Analyse Syntaxique
*
* @authors Ariane ANCRENAZ
* Tanguy SAUTON
* Mathieu VINCENT
*
* Polytech Grenoble 2018 - INFO4 - PF
*)
(* #load "dynlink.cma" *)
(* #load "camlp4/camlp4o.cma" *)
#use "topfind";;
#camlp4o;;
(*
* Parties du sujet réalisées:
* - Partie 1 : Expressions
* - Partie 2 : Typage (Option 1)
* - Partie 3 : Extensions fonctionnelles (sauf récursivité)
*)
(** Définition de l'AST **)
(* Opérateurs binaires *)
type oper2 =
| Moins
| Plus
| Mul
| Div
| Ou
| Et
| Egal
(* Opérateurs unaires *)
type oper1 =
| Non
(* Expressions du langages (Noeud de l'AST) *)
type expr =
| Int of int
(*
int -> entier
*)
| Bool of bool
(*
bool -> booléen
*)
| Op1 of oper1 * expr
(*
oper1 -> Symbole de l'opération unaire
expr -> expression à évaluer
*)
| Op2 of oper2 * expr * expr
(*
oper2 -> Symbole de l'opération binaire
expr -> Partie gauche de l'opération
expr -> Partie droite de l'opération
*)
| IfThenElse of expr * expr * expr
(* "Si ... Alors ... Sinon ..."
expr -> expression à évaluer (booléen)
expr -> expression du ALORS
expr -> expression du SINON
*)
| LetIn of string * (string list) * expr * expr
(* Définition de fonction (les variables sont des fonctions sans arguments)
string -> nom de fonction
string list -> paramètres de la fonction: pour une variable, ce champ est []
expr -> l'expression de la fonction
expr -> expression à évaluer
*)
| Call of string * (expr list)
(* Appel de fonction
string -> nom de la fonction à appeler
expr list -> arguments de la fonction ([] si variable)
*)
(** Environnement fonctionnel **)
(* L'environnement est constitué de déclarations de fonction
string -> nom de la fonction
string list -> paramètres de la fonction (noms)
expr -> expression de la fonction
NB : Les valeurs les plus récentes sont stockées en tête *)
type env = (string * (string list) * expr) list
exception UnknownIdentifier of string
(*
* get : (string -> env -> string list * expr)
* Récupération d'une fonction dans un environnement
* On retourne le nom des paramètres et l'expression de la fonction
*)
let rec get (name : string) (e : env) =
match e with
|[] -> raise (UnknownIdentifier name)
|(id,params,expr)::q -> if (id = name) then (params,expr) else (get name q)
(** Typage des expressions **)
(* Type à 2 valeurs représentant les entiers et les booléens *)
type typage =
| TBool of bool
| TInt of int
(* Exception levée en cas de typage incorrect *)
exception TypeMismatch of string
(* isInt : (typage -> int)
Vérifie si une valeur (de type typage)
est entière et retourne cet entier
En cas d'erreur de typage, une exception TypeMismatch est levée
*)
let isInt (n : typage) = match n with
|TInt(n) -> n
|_ -> raise (TypeMismatch "Was expecting integer")
(* isBool : (typage -> bool)
Vérifie si une valeur (de type typage)
est booléenne et retourne ce booléen
En cas d'erreur de typage, une exception TypeMismatch est levée
*)
let isBool (n : typage) = match n with
|TBool(b) -> b
| _ -> raise (TypeMismatch "Was expecting boolean")
(* egal : (expr -> expr -> bool)
Compare deux expressions du même type
et retourne true/false si l'évaluation
est identique
En cas d'erreur de typage, une exception TypeMismatch est levée
*)
let egal e1 e2 =
match e1, e2 with
|TInt(n1),TInt(n2) -> (n1 == n2)
|TBool(b1),TBool(b2) -> (b1 == b2)
|_ -> raise (TypeMismatch "Different types for comparison")
(** Evalutation des expressions **)
(* Exception pour gérer les erreurs de paramètres *)
exception ArgsMismatch of string
(* eval : (expr -> env -> typage)
Evalue un noeud de l'AST
En cas d'erreur avec les arguments, une exception ArgsMismatch est levée
En cas d'erreur de typage, une exception TypeMismatch est levée
*)
let rec eval exp env =
match exp with
| Int n -> TInt(n)
| Bool b -> TBool(b)
| Op2 (Moins, x, y) -> TInt((isInt (eval x env)) - (isInt (eval y env)))
| Op2 (Plus, x, y) -> TInt((isInt (eval x env)) + (isInt (eval y env)))
| Op2 (Mul, x, y) -> TInt((isInt (eval x env)) * (isInt (eval y env)))
| Op2 (Div, x, y) -> TInt((isInt (eval x env)) / (isInt (eval y env)))
| Op2 (Ou, x, y) -> TBool((isBool (eval x env)) || (isBool (eval y env)))
| Op2 (Et, x, y) -> TBool((isBool (eval x env)) && (isBool (eval y env)))
| Op1 (Non, x) -> TBool(not (isBool (eval x env)))
| Op2 (Egal, x, y) -> TBool(egal (eval x env) (eval y env))
| IfThenElse (cond,x,y) -> let a = (eval cond env) in let a2 = (isBool a) in (if a2 then (eval x env) else (eval y env))
| Call(fname,pargs) -> let (params,fexpr) = (get fname env) in
let envf = (load_params pargs params env []) in
(eval fexpr envf)
| LetIn(name,params,expr,suite) -> eval suite ((name,params,expr)::env)
(* load_params (string list -> -> env -> env -> env)
* Fonction auxiliaire pour le chargement des arguments
* NB : Pourquoi 2 environnements différents ?
* Sinon pb avec => (fun y x -> ...) appelé avec (f x y)
*)
and load_params args params oldenv newenv =
match args,params with
|[],[] -> newenv@oldenv (* Nombre d'argument correct, on rajoute le nouvel environnement en tête de l'ancien *)
|[],_ -> raise (ArgsMismatch "Not enough arguments")
|_ ,[] -> raise (ArgsMismatch "Too many arguments")
|(a::qargs),(p::qparams) ->
let ev = (eval a oldenv) in (* Evaluation des arguments *)
begin
match ev with (* Ajout dans le nouvel environnement *)
| TInt(n) -> load_params qargs qparams oldenv ((p,[],Int(n))::newenv)
| TBool(b) -> load_params qargs qparams oldenv ((p,[],Bool(b))::newenv)
end
(** Affichage des expression **)
let string_oper2 o =
match o with
| Moins -> "-"
| Plus -> "+"
| Mul -> "*"
| Div -> "/"
| Ou -> " | "
| Et -> " & "
| Egal -> " = "
let string_oper1 o =
match o with
| Non -> "!"
let rec print_strings p =
match p with
| [] -> print_string ""
| x::l -> print_string x ; print_string " " ;print_strings l
let rec print_exprs l =
match l with
|[] -> print_string ""
|x::l -> print_expr x ; print_string " " ; print_exprs l
and print_expr e =
match e with
| Int n -> print_int n
| Bool b -> print_string (string_of_bool b)
| Op2 (o, x, y) ->
(print_char '(';
print_expr x;
print_string (string_oper2 o);
print_expr y;
print_char ')')
| Op1 (o,x) ->
(print_char '(';
print_string (string_oper1 o);
print_expr x;
print_char ')')
|IfThenElse (c,x,y) ->
(print_string ("if ");
print_expr c;
print_string (" then {");
print_expr x;
print_string ("} else {");
print_expr y;
print_char '}')
| LetIn (v,p,x,y) ->
(print_string ("let ");
print_string v;
print_string (" = ");
print_string "fun ";
print_strings p ;
print_string "-> " ;
print_expr x ;
print_string (" in ");
print_expr y)
| Call(v,p) ->
print_string v;
print_string " ";
print_exprs p
(** FLOTS **)
(* Pour le test *)
let rec list_of_stream = parser
| [< 'x; l = list_of_stream >] -> x :: l
| [< >] -> []
(* ANALYSEUR LEXICAL sur un flot de caractères *)
(* Schéma de Horner *)
let chiffre = parser [<' '0'..'9' as x >] -> x
let valchiffre c = int_of_char c - int_of_char '0'
let rec horner n = parser
| [< c = chiffre ; s >] -> horner (10 * n + valchiffre c) s
| [< >] -> n
(* Lecture des identifieurs *)
let lettre = parser [< ''a'..'z' | 'A'..'Z' as x >] -> x
let alphanum = parser
| [< x = lettre >] -> x
| [< x = chiffre >] -> x
let rec alphanums = parser
| [< x = alphanum; l = alphanums >] -> x::l;
| [< >] -> []
(* Transtypage char list -> string *)
let rec lettres_to_bytes (l : char list) (i : int) (b : bytes) : string =
match l with
| [] -> Bytes.to_string b
| x::q -> Bytes.set b i x ; lettres_to_bytes q (i+1) b
(* Un identifieur commence toujours par une lettre suivie de caractères alphanumériques *)
let ident = parser
| [< c = lettre ; l = alphanums>] ->
let b = Bytes.make ((List.length l)+1) c in
(lettres_to_bytes l 1 b)
(* Type des lexèmes *)
type token =
| Tent of int
| Tmoins
| Tplus
| Tparouvre
| Tparferme
| Tmul
| Tdiv
| Tbool of bool
| Tou
| Tet
| Tnon
| Tsi
| Tsinon
| Talors
| Tegal
| Tident of string
| Tsoit
| Tdans
| Tfun
| Tfleche
| Tparam of string list
(*
Pour passer d'un flot de caractères à un flot de lexèmes,
on commence par une fonction qui analyse lexicalement les
caractères d'un lexème au début d'un flot de caractères.
La fonction next_token rend un token option, c'est-à-dire :
- soit Some (tk) où tk est un token
dans le cas où le début du flot correspond lexème
- soit None
Le type option est prédéfini ainsi dans la bibliothèque standard OCaml :
type 'a option =
| None (* indique l'absence de valeur *)
| Some of 'a (* indique la présence de valeur *)
*)
(* Passage des identifieurs aux tokens *)
let id_to_token id =
match id with
| "vrai" -> Tbool(true)
| "faux" -> Tbool(false)
| "non" -> Tnon
| "si" -> Tsi
| "alors" -> Talors
| "sinon" -> Tsinon
| "soit" -> Tsoit
| "dans" -> Tdans
| "fun" -> Tfun
| str -> Tident(str)
let fleche = parser
|[<' '>' >] -> true
|[< >] -> false
let rec next_token = parser
| [< ' ' '|'\n'; tk = next_token >] -> tk (* élimination des espaces *)
| [< ' '0'..'9' as c; n = horner (valchiffre c) >] -> Some (Tent (n))
| [< ' '-'; b = fleche >] -> if b then Some (Tfleche) else Some(Tmoins)
| [< ' '+' >] -> Some (Tplus)
| [< ' '(' >] -> Some (Tparouvre)
| [< ' ')' >] -> Some (Tparferme)
| [< ' '*' >] -> Some (Tmul)
| [< ' '/' >] -> Some (Tdiv)
| [< ' '=' >] -> Some (Tegal)
| [< ' '&'; ' '&'>] -> Some (Tet)
| [< ' '|'; ' '|'>] -> Some (Tou)
| [< s = ident >] -> Some (id_to_token s)
| [< >] -> None
(* Tests *)
(*
let s = Stream.of_string "soit f = fun x ~> x * x dans f 2"
let _ = next_token s
*)
(*
(* L'analyseur lexical parcourt récursivement le flot de caractères s
en produisant un flot de lexèmes *)
let rec tokens s =
match next_token s with
| None -> [< >]
| Some tk -> [< 'tk; tokens s >]
let lex s = tokens s
(* tests *)
let s = Stream.of_string "soit f = fun x -> x*x dans f 2"
let stk = lex s
let ltk = list_of_stream stk
(*
Alternativement, la primitive Stream.from conduit au même résultat,
on l'utilise comme ci-dessous.
*)
(*
A savoir : cette dernière version repose sur une représentation
interne des flots beaucoup plus efficace. Pour plus de détails
sur Stream.from, consulter le manuel OCaml.
Dans un compilateur réaliste devant traiter de gros textes,
c'est la version à utiliser.
*)
*)
let lex s = Stream.from (fun _ -> next_token s)
(*
let _ = list_of_stream (lex (Stream.of_string "356 - 10 - 4"))
*)
(** ANALYSEUR SYNTAXIQUE sur un flot de lexèmes **)
(* Grammaire:
E = expression
C = conjonction
SC = suite de conjonctions
SD = suite de disjonctions
L = littéral
EC = expression comparable
CMP = comparaison
T = terme
SA = suite d’additions
F = facteur
SM = suite de multiplications
FUN = fonction
PARAMS = suite de paramètres (ident)
SE = suite d'expressions
E ::= ’si’ E ’alors’ E ’sinon’ E | 'soit' ident '=' FUN 'dans' E | C SD
FUN ::= 'fun' PARAMS '~>' E | E
PARAMS ::= ident PARAMS | ϵ
SD ::= ’||’ C SD | ϵ
C ::= L SC
SC ::= ’&&’ L SC | ϵ
L ::= ’non’ L | EC CMP
CMP ::= ’=’ EC | ϵ
EC ::= T SA
SA ::= ’+’ T SA | ’-’ T SA | ϵ
T ::= F SM
SM ::= ’*’ F SM | ’/’ F SM | ϵ
SE ::= E SE | ϵ
F ::= entier | ’vrai’ | ’faux’ | ’(’ E ’)’ | ident SE
*)
let rec p_expr = parser
| [< 'Tsi ; e1 = p_expr ; 'Talors ; e2 = p_expr ; 'Tsinon ; e3 = p_expr >] -> IfThenElse (e1,e2,e3)
| [< 'Tsoit ; 'Tident(v) ; 'Tegal ; (p,x) = p_fun ; 'Tdans ; e1 = p_expr >] -> LetIn(v,p,x,e1)
| [< c = p_conj ; sd = p_s_disj c >] -> sd
and p_fun = parser
| [< 'Tfun ; p = p_param []; 'Tfleche ; x = p_expr>] -> (p,x)
| [< e = p_expr>] -> ([],e)
and p_param c = parser
| [< 'Tident(x) ; l = p_param (x::c)>] -> l
| [< >] -> c
and p_s_disj c = parser
| [< 'Tou ; p = p_conj ; sd = p_s_disj (Op2(Ou,c,p))>] -> sd
| [< >] -> c
and p_conj = parser
| [< l = p_litt ; c = p_s_conj l>] -> c
and p_s_conj c = parser
| [< 'Tet ; p = p_litt ; sc = p_s_conj (Op2(Et,c,p)) >] -> sc
| [< >] -> c
and p_litt = parser
| [< 'Tnon ; p = p_litt>] -> Op1(Non,p)
| [< ec = p_expr_comp; cmp = p_comp ec >] -> cmp
and p_comp e = parser
|[< 'Tegal ; ec = p_expr_comp>] -> (Op2(Egal,e,ec))
|[< >] -> e
and p_expr_comp = parser
| [< t = p_terme; e = p_s_add t >] -> e
and p_s_add a = parser
| [< ' Tmoins; t = p_terme; e = p_s_add (Op2(Moins,a,t)) >] -> e
| [< ' Tplus; t = p_terme; e = p_s_add (Op2(Plus,a,t)) >] -> e
| [< >] -> a
and p_terme = parser
| [< f = p_fact; sm = p_s_mul f >] -> sm
and p_s_mul a = parser
| [< ' Tmul; t = p_fact; e = p_s_mul (Op2(Mul,a,t)) >] -> e
| [< ' Tdiv; t = p_fact; e = p_s_mul (Op2(Div,a,t)) >] -> e
| [< >] -> a
and p_s_expr c = parser
| [<x = p_expr ; l = p_s_expr (x::c)>] -> l
| [< >] -> c
and p_fact = parser
| [< ' Tent(n)>] -> Int(n)
| [< ' Tparouvre ; exp = p_expr; ' Tparferme>] -> exp
| [< ' Tbool(b) >] -> Bool(b)
| [< ' Tident(v) ; se = p_s_expr []>] -> Call(v,se)
(* Constructeur d'AST *)
let ast s = p_expr (lex (Stream.of_string s))
(** Tests **)
let e1 = ast "soit f = fun x y -> x - y dans (f 3 4) + 3"
let _ = eval e1 []
let _ = print_expr e1
let test1 = ast "soit x = 5 dans x + (soit x = 2 dans x) - x"
let _ = eval test1 []
let test2 = ast "soit x = 5 dans (soit y = 2 dans x + y)"
let _ = eval test2 []
let test3 = ast "si vrai alors 2 sinon 3"
let _ = print_expr test3
let _ = eval test3 []
let test4 = ast "soit x = 5 dans si faux alors si vrai || faux alors vrai sinon faux sinon x"
let _ = print_expr test4
let _ = eval test4 []
let test5 = ast "soit x = 5 dans x + (si vrai && faux || vrai alors 3 sinon 2)"
let _ = print_expr test5
let _ = eval test5 []
let test6 = ast "soit x = 4 dans x + (si vrai alors soit x = 4 dans x sinon (si faux alors soit x = 2 dans x sinon soit x = 3 dans x))"
let _ = print_expr test6
let _ = eval test6 []
let test7 = ast "soit var = 42 dans var + (soit vra1 = 2 dans vra1)";;
let _ = print_expr test7
let _ = eval test7 []
let test8 = ast "soit var1 = 4 dans var1 + (si non vrai alors soit var2 = 1 dans var2 sinon soit var3 = 2 dans var3)"
let _ = print_expr test8
let _ = eval test8 []
let test9 = ast "non non non non vrai"
let _ = print_expr test9
let _ = eval test9 []
let test10 = ast "soit x = 10 dans x * (si non non vrai && non faux alors soit y = 5 dans y sinon (si vrai && non non faux alors soit z = 8 dans z sinon soit w = 4 dans w))"
let _ = print_expr test10
let _ = eval test10 []
let x = 5 in x + (if true && false || true then 3 else 2)
let x = 4 in x + (if true then let x = 4 in x else (if false then let x = 2 in x else let x = 3 in x))
let x = 10 in x*(if (not (not true)) && not false then let y = 5 in y else (if true && (not (not false)) then let z = 8 in z else let w = 4 in w))
(* Tests d'exceptions *)
(* Exceptions de typage *)
let e = ast "si 2 alors vrai sinon faux"
let _ = eval e []
let e = ast "si (2 = vrai) alors 1 sinon 2"
let _ = eval e []
(* Exceptions d'identifieurs*)
let e = ast "let x = 2 in x"
let _ = eval e []
(* Exceptions d'arguments*)
let e = ast "soit f = fun a b c -> a+b*c dans f 1 2"
let _ = eval e []
let e = ast "soit f = fun a -> a dans f 1 2"
let _ = eval e []
(* Les variables sont des fonctions *)
let var1 = eval (ast "soit f = 2 dans f") []
let var1fun = eval (ast "soit f = fun -> 2 dans f") []
let _ = assert(var1 = var1fun)