LFG - Ocaml - Exercices ATTENTION: En Ocaml, pour pouvoir utiliser le type stream, il faut charger le module approprié: #load "camlp4o.cma" ;; La fonction de conversion: Stream.of string : string -> char Stream.t Exercice 1 On donne la grammaire E T + E T, T a (E). Débrouillez-vous pour obtenir une grammaire LL(1). Ecrire un analyseur pour cette grammaire qui retourne un élément du type expr suivant type expr = A Plus of expr*expr ;; Exercice 2 Le jeu de morpion est un jeu entre deux joueurs appelés et O. commence le jeu en mettant un dans une des cases d un quadrillage de dimension 3 3 et c est le tour de O de mettre un O dans une des cases restantes. Ensuite c est le tour de et ainsi de suite. Le but de chaque joueur est d être le premier à établir une ligne ou une colonne ou une diagonale de trois cases contenant sa lettre. Celui qui réussit est déclaré gagnant. Voici un exemple d une partie de morpion où gagne. (1) (2) O (3) O (4) O O (5) O O (6) O O O (7) O O O gagne Notation du jeu de MORPION Dans plusieurs jeux de société, les joueurs sérieux se servent d une notation permettant de garder l historique d une partie pour pouvoir analyser le jeu plus tard ou le publier. C est surtout le cas du jeu d échecs. Voici une notation permettant de noter une partie de morpion: On donne un nom à chaque case a1 b1 c1 a2 b2 c2 a3 b3 c3 On écrit le nom de la case remplie à chaque coup. Une partie de morpion est donc représentée par une chaîne de caractères. Par exemple, la partie ci-dessus est codée par "c1b3a1b1b2c3a3". Une meilleure vision d un tel code est comme une suite de positions. Les facteurs gauches d un code sont des codes pour des positions antérieures. La chaîne vide, "" est le code pour la position initiale. Dans l exemple cidessus, la position (1) est codée par "c1", la position (2) par "c1b3",..., la position (7) par "c1b3a1b1b2c3a3". Le but de cet exercice est d écrire un programme Ocaml qui analyse une chaîne de caractères, supposée représenter une partie, ou suite de positions, de morpion, et d en extraire certaines informations, précisées dans la suite. 1
1. Définir une grammaire LL(1) qui engendre un langage permettant de coder les suites de positions du jeu de morpion. On se contentera d une grammaire qui permet de distinguer les coups de des coups de O et qui permet de distinguer le premier coup comme un coup de. Les terminaux seront les noms des cases. (Note: certains mots vont représenter des parties impossibles, sont trop longs ou contiennent des doublons. La grammaire ne permettra pas de distinguer le résultat d une partie.) 2. Ecrire un analyseur pour votre grammaire qui retourne un élément du type jeu suivant type jeu = J of coup list and coup = of string O of string ;; Par exemple, votre analyseur appliqué à la chaîne "c1b3a1b1b2c3a3" retournera - : jeu = J [ "c1"; O "b3"; "a1"; O "b1"; "b2"; O "c3"; "a3"]. 3. Ecrire une fonction appartient: a -> a list -> bool telle que l appel appartient e l teste si l élément e appartient à la liste l. 4. En utilisant la fonction appartient, écrire une fonction supprimer doublons: a list -> a list qui supprime les doublons dans une liste. 5. En utilisant la fonction supprimer doublons et la fonction List.length, écrire une fonction est valide: jeu -> bool qui teste si un élément du type jeu représente une partie de morpion valide. (Indice: quel est le résultat de l appel supprimer doublons l si l ne contient pas de doublon?). 6. Il y a trois lignes, trois colonnes et deux diagonales dans un quadrillage de dimension 3 3, donc il y a huit conditions à vérifier pour voir si a gagné une partie. Ecrire une fonction test victoire : jeu -> bool qui teste si a gagné une partie de morpion et une fonction test victoire O: jeu -> bool qui teste si O a gagné une partie de morpion. En déduire une fonction gagnant: jeu -> unit qui affiche le gagnant d une partie de morpion, si la partie n est pas nulle. 7. Dans le pot, /home/etudold/=pot/lcin5u44/, vous trouverez le fichier afficher morpion.ml qui contient la fonction afficher jeu : jeu -> unit qui permet d afficher une partie de morpion. En vous servant de cette fonction et des fonctions précédantes, écrivez une fonction qui prend en entrée une chaîne de caractères représentant une partie de morpion et qui affiche la partie. Exercice 3 La notation préfixée d une expression arithmétique consiste en l écriture de l expression avec l opérateur devant ses arguments. Par exemple, l addition de 3 et 4 s ecrit +34. Dans cet exercice nous considérons l analyse des expressions arithmétiques données en notation préfixée où les opérateurs sont +,,, / et seuls les nombres de 0 à 9 apparaissent. 1. Donner une grammaire pour les expressions arithmétiques en notation préfixée décrites ci-dessus. 2. Ecrire un analyseur pour cette grammaire qui retourne un élément du type arbre suivant type arbre = V B of arbre*string*arbre ;; Par exemple, l expression +34 sera convertie en B (B (V, "3", V), "+", B (V, "4", V)). On vous rappelle la fonction Char.escaped: char -> string qui transforme un caractère en une chaîne de caractères. 3. Ecrire une fonction valeur: arbre -> float qui évalue un élément de type arbre. Tester vos fonctions avec let test_string = "/*+32-41-7*/-+43-433/42" ;; Exercice 4 Le but de cet exercice est d écrire un analyseur qui reconnaît la table de transitions d un automate où l état initial est noté i; les états finaux sont notés f0, f1,... ; les autres états sont notés s0, s1,... ; les éléments du vocabulaire sont notés v0, v1,... ; le mot vide est noté mv; les transitions sont notés en utilisant. et =. Par exemple, s0.v0=s1 ou i.v6=f2. Ainsi, i.v0=s1 s1.v0=f2 s1.v1=i est un automate correcte. 1. Ecrire une grammaire appropriée pour les automates. 2
2. Ecrire l analyseur. L entrée sera de type string et retournera un élément de type automate, qui est défini par type automate = A of transition list and transition = T of etat*vocabulaire*etat and vocabulaire = V of int Mv and etat = I S of int F of int ;; Par exemple, l automate "i.v0=s1 s1.v0=f2 s1.v1=i" retournera - : automate = A [T (I, V 0, S 1); T (S 1, V 0, F 2); T (S 1, V 1, I)] Il sera utile d incorporer la fonction nettoyage suivante qui enlève les espaces, les tabulations et les retours à la ligne d un stream. let rec nettoyage = parser [< \t \n ; l = (nettoyage) >] -> l [< x; l = (nettoyage) >] -> [< x; l >] [< >] -> [< >] ;; 3
Exercice 5 En effectuant l analyse LALR pour la grammaire ab, on obtient la table a b # s 0 s 1 s 2 s 1 succès s 2 s 3 s 2 s 3 s 4 s 4 ab ab Le programme Ocaml suivant est un analyseur qui utilise cette table et retourne la dérivation d un mot donné en entrée. (Vous trouverez ce programme dans le pot - dans le fichier ab.lalr.ml ). La fonction principale est la fonction g qui modélise l analyse ascendante décrite en cours. Les arguments de g sont a les actions. Ici, c est une liste de chaînes de caractères. Les réductions y sont ajoutées en forme de chaînes de caractères. A la fin on retourne a. p la pile. En effet, on n a besoin que des états de l automate dans cette pile, sans les caractères, car toute l information nécessaire figure dans les réductions. Ici, la pile est représentée par une liste. Les états sont écrits dans l ordre inverse de celui des notes du cours, c.à.d. les nouvels états sont ajoutés en tête de la liste. m le mot à analyser. La fonction auxiliaire reste permet d enlever le premier caractère du mot. (* Fonction auxiliaire *) let reste s = String.sub s 1 (String.length s - 1) ;; (* Transitions pour les non-terminaux *) let t s x = match (s,x) with "s0","" -> "s1" "s2","" -> "s3" _ -> "Echec";; Analyseur pour la grammaire ab (* Implementation de la table LR *) let analyser s = let rec g a p m = match p with "s0"::p when m.[0] = a -> g a ("s2"::"s0"::p) (reste m) "s0"::p when m.[0] = # -> g ("->^"::a) ("s1"::"s0"::p) m "s1"::_ when m.[0] = # -> a "s2"::p when m.[0] = a -> g a ("s2"::"s2"::p) (reste m) "s2"::p when m.[0] = b -> g ("->^"::a) ("s3"::"s2"::p) m "s3"::p when m.[0] = b -> g a ("s4"::"s3"::p) (reste m) "s4"::_::_::s::p when m.[0] = b or m.[0] = # -> g ("->ab"::a) ((t s "")::s::p) m _ -> "Echec "::m::". Analyse partielle:"::a in g [] ["s0"] (s^"#") ;; analyser "aabb";; - : string list = ["->ab"; "->ab"; "->^"] 1. Trouvez la table d analyse LALR pour la grammaire ab. 2. Etudiez le programme ci-dessus et en vous inspirant, écrivez un analyseur LALR pour la grammaire ab. 4
Exercice 6 La notation postfixée d une expression arithmétique consiste en l écriture de l expression avec l opérateur après ses arguments. Par exemple, l addition de 1 et 1 s ecrit 11+. Dans cet exercice nous considérons l analyse des expressions arithmétiques données en notation postfixée où seuls l opérateur + et le nombre 1 apparaissent. 1. Donner une grammaire pour les expressions arithmétiques en notation postfixée décrites ci-dessus. 2. Ecrire un analyseur LALR (programme Ocaml) pour cette grammaire (dans le même style que l exercice précédent). 5
Solutions Exercice 1 : Solution type expr = A Plus of expr * expr ;; let rec axiome = parser [< res_e = (e); # >] -> res_e and e = parser [< res_t = (t); res_eprime = (eprime) >] -> res_eprime res_t and eprime = parser [< + ; res_e = (e) >] -> (function x -> Plus (x, res_e)) [< >] -> (function x -> x) and t = parser [< ( ; res_e = (e); ) >] -> res_e [< a >] -> A ;; val e : char Stream.t -> expr = <fun> val eprime : char Stream.t -> expr -> expr = <fun> val t : char Stream.t -> expr = <fun> let convertir s = axiome (Stream.of_string s) ;; val convertir : string -> expr = <fun> convertir "a#" ;; - : expr = A convertir "a+a#" ;; - : expr = Plus (A, A) convertir "a+(a+a)#" ;; - : expr = Plus (A, Plus (A, A)) convertir "(a+a)+(a+a)#" ;; - : expr = Plus (Plus (A, A), Plus (A, A)) convertir "aa+a#" ;; Exception: Stream.Error "". Exercice 2 : Solution Question 1 J -> -> C O ^ O -> C ^ C -> L N L -> a b c N -> 1 2 3 Question 2 type jeu = J of coup list and coup = of string O of string ;; let rec j = parser [< res_x = (x) >] -> J res_x and x = parser [< res_c = (c) ; res_o = (o) >] -> ( res_c) :: res_o [< >] -> [] and o = parser [< res_c = (c) ; res_x = (x) >] -> (O res_c) :: res_x [< >] -> [] 6
and c = parser [< a.. c as lettre ; 1.. 3 as chiffre >] -> (Char.escaped lettre) ^ (Char.escaped chiffre) ;; Question 3 let rec appartient e = function [] -> false h::t -> h=e or appartient e t ;; Question 4 let rec supprimer_doublons = function l -> match l with [] -> [] t::q -> if appartient t q then supprimer_doublons q else t::(supprimer_doublons q) ;; Question 5 let est_valide (J l) = let rec cases = function [] -> [] ( s)::q -> s::(cases q) (O s)::q -> s::(cases q) in let cl = cases l in (cl = supprimer_doublons cl) or (List.length cl > 9) ;; Question 6 let test_victoire_ = function (J l) -> ((appartient ( "a1") l) & (appartient ( "b1") l) & (appartient ( "c1") l)) or ((appartient ( "a2") l) & (appartient ( "b2") l) & (appartient ( "c2") l)) or ((appartient ( "a3") l) & (appartient ( "b3") l) & (appartient ( "c3") l)) or ((appartient ( "a1") l) & (appartient ( "a2") l) & (appartient ( "a3") l)) or ((appartient ( "b1") l) & (appartient ( "b2") l) & (appartient ( "b3") l)) or ((appartient ( "c1") l) & (appartient ( "c2") l) & (appartient ( "c3") l)) or ((appartient ( "a1") l) & (appartient ( "b2") l) & (appartient ( "c3") l)) or ((appartient ( "a3") l) & (appartient ( "b2") l) & (appartient ( "c1") l)) ;; let test_victoire_o = function (J l) -> ((appartient (O "a1") l) & (appartient (O "b1") l) & (appartient (O "c1") l)) or ((appartient (O "a2") l) & (appartient (O "b2") l) & (appartient (O "c2") l)) or ((appartient (O "a3") l) & (appartient (O "b3") l) & (appartient (O "c3") l)) or ((appartient (O "a1") l) & (appartient (O "a2") l) & (appartient (O "a3") l)) or ((appartient (O "b1") l) & (appartient (O "b2") l) & (appartient (O "b3") l)) or ((appartient (O "c1") l) & (appartient (O "c2") l) & (appartient (O "c3") l)) or ((appartient (O "a1") l) & (appartient (O "b2") l) & (appartient (O "c3") l)) or ((appartient (O "a3") l) & (appartient (O "b2") l) & (appartient (O "c1") l)) ;; let gagnant jj = if test_victoire_ jj then print_string " gagne\n\n\n" else if test_victoire_o jj then print_string " O gagne\n\n\n" else print_string " Match Nul\n\n\n" ;; Question 7 let morpion s = let rs = j (Stream.of_string s) in if est_valide rs then (afficher_jeu rs ; gagnant rs) else failwith "jeu non valide" ;; L appel morpion "c1b3a1b1b2c3a3" ;; retourne 7
================= - - - - - - - - ================= - - - - - - O - ================= - - - - - O - ================= O - - - - O - ================= O - - - O - ================= O - - - O O ================= O - - O O ================= gagne - : unit = () Le programme: type jeu = J of coup list and coup = of string O of string ;; let rec j = parser [< res_x = (x) >] -> J res_x and x = parser [< res_c = (c) ; res_o = (o) >] -> ( res_c) :: res_o [< >] -> [] and o = parser [< res_c = (c) ; res_x = (x) >] -> (O res_c) :: res_x [< >] -> [] and c = parser [< a.. c as lettre ; 1.. 3 as chiffre >] -> (Char.escaped lettre) ^ (Char.escaped chiffre) ;; let rec appartient e = function [] -> false h::t -> h=e or appartient e t ;; let rec supprimer_doublons = function l -> match l with [] -> [] t::q -> if appartient t q then supprimer_doublons q else t::(supprimer_doublons q) ;; let est_valide (J l) = let rec cases = function 8
[] -> [] ( s)::q -> s::(cases q) (O s)::q -> s::(cases q) in let cl = cases l in (cl = supprimer_doublons cl) or (List.length cl > 9) ;; let test_victoire_ = function (J l) -> ((appartient ( "a1") l) & (appartient ( "b1") l) & (appartient ( "c1") l)) or ((appartient ( "a2") l) & (appartient ( "b2") l) & (appartient ( "c2") l)) or ((appartient ( "a3") l) & (appartient ( "b3") l) & (appartient ( "c3") l)) or ((appartient ( "a1") l) & (appartient ( "a2") l) & (appartient ( "a3") l)) or ((appartient ( "b1") l) & (appartient ( "b2") l) & (appartient ( "b3") l)) or ((appartient ( "c1") l) & (appartient ( "c2") l) & (appartient ( "c3") l)) or ((appartient ( "a1") l) & (appartient ( "b2") l) & (appartient ( "c3") l)) or ((appartient ( "a3") l) & (appartient ( "b2") l) & (appartient ( "c1") l)) ;; let test_victoire_o = function (J l) -> ((appartient (O "a1") l) & (appartient (O "b1") l) & (appartient (O "c1") l)) or ((appartient (O "a2") l) & (appartient (O "b2") l) & (appartient (O "c2") l)) or ((appartient (O "a3") l) & (appartient (O "b3") l) & (appartient (O "c3") l)) or ((appartient (O "a1") l) & (appartient (O "a2") l) & (appartient (O "a3") l)) or ((appartient (O "b1") l) & (appartient (O "b2") l) & (appartient (O "b3") l)) or ((appartient (O "c1") l) & (appartient (O "c2") l) & (appartient (O "c3") l)) or ((appartient (O "a1") l) & (appartient (O "b2") l) & (appartient (O "c3") l)) or ((appartient (O "a3") l) & (appartient (O "b2") l) & (appartient (O "c1") l)) ;; let gagnant jj = if test_victoire_ jj then print_string " gagne\n\n\n" else if test_victoire_o jj then print_string " O gagne\n\n\n" else print_string " Match Nul\n\n\n" ;; let rec afficher_jeu (J l) = let rec aff_j = let maj jj = let rec chf = let modify f e r = function x -> if x=e then r else f x in function J [] -> (function _ -> "-") J (( s)::t) -> modify (chf (J t)) s "" J ((O s)::t) -> modify (chf (J t)) s "O" and p0 = [ "a1"; "b1"; "c1"; "a2"; "b2"; "c2"; "a3"; "b3"; "c3" ] in List.map (chf jj) p0 and aff_p = function a::b::c::d::e::f::g::h::i::[] -> print_string ("\n"^a^"\t"^b^"\t"^c^ "\n"^d^"\t"^e^"\t"^f^ "\n"^g^"\t"^h^"\t"^i^ "\n=================") _ -> failwith "jeu impossible" in function J [] -> print_string "\n=================" J (h::t) -> (aff_j (J t); aff_p (maj (J (h::t)) )) in aff_j (J (List.rev l)) ;; let test_jeu = "c1b3a1b1b2c3a3" ;; let morpion s = let rs = j (Stream.of_string s) in if est_valide rs then (afficher_jeu rs ; gagnant rs) 9
else failwith "jeu non valide" ;; morpion test_jeu ;; Exercice 3 : Solution 1. E -> O E E F O -> + - * / F-> 0... 9 2. let rec arith = parser [< ro = (o); ra1 = (arith); ra2 = (arith) >] -> ro (ra1,ra2) [< res_f = (f) >] -> res_f and o = parser [< + >] -> (function (x,y) -> B (x, "+", y)) [< - >] -> (function (x,y) -> B (x, "-", y)) [< * >] -> (function (x,y) -> B (x, "*", y)) [< / >] -> (function (x,y) -> B (x, "/", y)) and f = parser [< 0.. 9 as chiffre >] -> B (V, Char.escaped chiffre, V) ;; val arith : char Stream.t -> arbre = <fun> val o : char Stream.t -> arbre * arbre -> arbre = <fun> val f : char Stream.t -> arbre = <fun> 3. let rec valeur e = match e with B (x,"+",y) -> (valeur x) +. (valeur y) B (x,"-",y) -> (valeur x) -. (valeur y) B (x,"*",y) -> (valeur x) *. (valeur y) B (x,"/",y) -> (valeur x) /. (valeur y) B (_,r,_) -> float_of_string r ;; val valeur : arbre -> float = <fun> let convert s = arith (Stream.of_string s) ;; val convert : string -> arbre = <fun> let test_string = "/*+32-41-7*/-+43-433/42" ;; let cts = convert test_string ;; val cts : arbre = B (B (B (B (V, "3", V), "+", B (V, "2", V)), "*", B (B (V, "4", V), "-", B (V, "1", V))), "/", B (B (V, "7", V), "-", B (B (B (B (B (V, "4", V), "+", B (V, "3", V)), "-", B (B (V, "4", V), "-", B (V, "3", V))), "/", B (V, "3", V)), "*", B (B (V, "4", V), "/", B (V, "2", V))))) let v = valeur cts ;; val v : float = 5. 10
Exercice 4 : Solution 1. La grammaire: A -> T T A T -> E. V = E E -> i s N f N V -> v N mv N -> 0 1 N1... 9 N1 N1 -> ^ 0 N1 1 N1... 9 N1 2. L analyseur: let rec nettoyage = parser [< \t \n ; l = (nettoyage) >] -> l [< x; l = (nettoyage) >] -> [< x; l >] [< >] -> [< >] ;; val nettoyage : char Stream.t -> char Stream.t = <fun> let rec aut = parser [< res_a = (a) >] -> A res_a and a = parser [< res_t = (t); res_a_1 = (a_1) >] -> res_t :: res_a_1 and a_1 = parser [< res_a = (a) >] -> res_a [< >] -> [] and t = parser [< res_e1 = (e);. ; res_v = (v); = ; res_e2 = (e); ; >] -> T (res_e1, res_v, res_e2) and e = parser [< i >] -> I [< s ; res_n = (n) >] -> S res_n [< f ; res_n = (n) >] -> F res_n and v = parser [< m ; v >] -> Mv [< v ; res_n = (n) >] -> V res_n and n = parser [< 1.. 9 as digit; res_n_1 = (n_1) >] -> int_of_string ((Char.escaped digit)^res_n_1) [< 0 >] -> 0 and n_1 = parser [< 0.. 9 as digit; res_n_1 = (n_1) >] -> ((Char.escaped digit)^res_n_1) [< >] -> "" ;; val aut : char Stream.t -> automate = <fun> val a : char Stream.t -> transition list = <fun> val a_1 : char Stream.t -> transition list = <fun> val t : char Stream.t -> transition = <fun> val e : char Stream.t -> etat = <fun> val v : char Stream.t -> vocabulaire = <fun> val n : char Stream.t -> int = <fun> val n_1 : char Stream.t -> string = <fun> let automaton s = aut (nettoyage (Stream.of_string s)) ;; val automaton : string -> automate = <fun> automaton " i.v0=s1 s1.v0=f2 s1.v1=i 11
" ;; - : automate = A [T (I, V 0, S 1); T (S 1, V 0, F 2); T (S 1, V 1, I)] Exercice 5 : Solution (* Fonction auxiliaire *) let reste s = String.sub s 1 (String.length s - 1) ;; (* Transitions - non-terminaux *) let t s x = match (s,x) with "s4","" -> "s5" "s2","" -> "s3" "s0","" -> "s1" _ -> "" ;; (* Implementation de la table LR *) let analyser s = let rec g a p m = match p with "s0"::p when m.[0] = # -> g (" -> mv"::a) ((t "s0" "")::"s0"::p) m "s0"::p when m.[0] = a -> g a ("s2"::"s0"::p) (reste m) "s1"::_ when m.[0] = # -> a "s2"::p when m.[0] = a -> g a ("s2"::"s2"::p) (reste m) "s2"::p when m.[0] = b -> g (" -> mv"::a) ((t "s2" "")::"s2"::p) m "s3"::p when m.[0] = b -> g a ("s4"::"s3"::p) (reste m) "s4"::p when m.[0] = # -> g (" -> mv"::a) ((t "s4" "")::"s4"::p) m "s4"::p when m.[0] = a -> g a ("s2"::"s4"::p) (reste m) "s4"::p when m.[0] = b -> g (" -> mv"::a) ((t "s4" "")::"s4"::p) m "s5"::_::_::_::s::p when m.[0] = # -> g (" -> ab"::a) ((t s "")::s::p) m "s5"::_::_::_::s::p when m.[0] = b -> g (" -> ab"::a) ((t s "")::s::p) m _ -> failwith "probleme du mot" in g [] ["s0"] (s^"#") ;; analyser "aaabbabbab" ;; - : string list = [" -> ab"; " -> ab"; " -> mv"; " -> mv"; " -> ab"; " -> ab"; " -> mv"; " -> mv"; " -> ab"; " -> mv"; " -> mv"] Exercice 6 : Solution 1. E -> E E O F O -> + F-> 1 2. 12