TP : Langages Formels
TP de Langages Formels
Younesse Kaddar
I.
NB : dans la suite, on a modifié la fonction pp_expr
dans expr.ml
pour afficher le type des constantes/variables :
(** Afficheur d'expressions. *)
let rec pp_expr chan = function
| Var v -> Format.fprintf chan "Var %s" v
| Int i -> Format.fprintf chan "Int %d" i
| String s -> Format.fprintf chan "String %s" s
| Any -> Format.fprintf chan "Any"
- cela nous sera utile pour distinguer l’affichage de
String "_"
et deAny
, à partir de la question 12.
Question 1.
let x=42 in x and y
provoque une parse error
, car and
est pris pour une variable.
Mettre les opérateurs avant les variables dans le lexer règle le problème :
rule token = parse
(* [...] *)
| "and" { AND }
| "or" { OR }
| "not" { NOT }
| varname as v { VAR v }
(* [...] *)
Question 2.
let varname = ['a'-'z' '_'](['a'-'z' 'A'-'Z' '0'-'9' '_' '\''])*
dans
rule token = parse
| varname as v { VAR v }
Question 3.
let int = ['0'-'9']['0'-'9' '_']* | '0'['x' 'X']['a'-'f' 'A'-'F' '0'-'9']['a'-'f' 'A'-'F' '0'-'9' '_']*
dans
rule token = parse
| int as n { INT (int_of_string n) }
Question 4.
let string = '"'([^'"']|'\"')*'"'
dans
rule token = parse
| string as s { STRING s }
Question 5.
let op_cmp = ['>' '=' '<' '^']
let op_mult = ['*' '/' '%']
let op_plus = ['+' '-']
let op_other = ['!' '$' '?' '.' ':' ';']
let remainder = (op_cmp | op_mult | op_plus |op_other)*
dans
rule token = parse
| op_plus remainder as o { BIN_PLUS o }
| op_mult remainder as o { BIN_MULT o }
| op_cmp remainder as o { BIN_CMP o }
Question 6.
Le retour à la ligne est représenté par
\n
sur Linux\r
sur Mac\r\n
sur Windows
On remplace donc, dans la lexer, la règle :
| [' ' '\t' '\r' '\n']+ { token lexbuf }
par les règles
| [' ' '\t']+ { token lexbuf }
| ['\r' '\n'] | "\r\n" { lexbuf.lex_curr_p
<- { lexbuf.lex_curr_p with
pos_bol = lexbuf.lex_curr_p.pos_cnum ;
pos_lnum = 1 +
lexbuf.lex_curr_p.pos_lnum };
token lexbuf }
./minirien "1=12
2 3"
Line 3, char 2, before "2": parse error
Question 7.
Dans la documentation officielle, il est indiqué, à la section 12.2.5 :
- entrypoint [exp1… expn] lexbuf :
(Where entrypoint is the name of another entry point in the same lexer definition.) Recursively call the lexer on the given entry point. Notice that lexbuf is the last argument. Useful for lexing nested comments, for example.
Ainsi, si on voulait supporter les commentaires pas nécessairement imbriqués bien parenthésés, on pourrait procéder de la sorte :
rule token = parse
(* [...] *)
| "(*" { comment_section lexbuf }
and comment_section = parse
| "*)" { token lexbuf }
| _ { comment_section lexbuf }
Mais pour ce que l’on souhaite (i.e le support de commentaires imbriqués bien parenthésés), cela ne suffit pas.
Utilisons l’indication de l’énoncé : on peut stocker l’état “nombre de parenthèses ouvrantes rencontrées non refermées” comme paramètre (num
) de l’analyseur lexical :
rule token = parse
(* [...] *)
| "(*" { comment_section 1 lexbuf }
and comment_section num = parse
| "(*" { comment_section (num+1) lexbuf }
| "*)" { if num>1 then comment_section (num-1) lexbuf
else token lexbuf }
| eof { failwith "Nested comments are not closed"}
| _ { comment_section num lexbuf }
./minirien "1=2 (* (* test
*)
3=4 *) and 4=5"
>> and(=(Int 1,Int 2),=(Int 4,Int 5))
./minirien "1=2 (* (* test
3=4 *) and 4=5"
Line 1, char 32: Nested comments are not closed
Si on est tatillon et qu’on veut améliorer le message d’erreur pour les entrées multi-lignes avec commentaires imbriqués, on écrira plutôt :
rule token = parse
(* [...] *)
| "(*" { comment_section 1 lexbuf }
and comment_section num = parse
| "(*" { comment_section (num+1) lexbuf }
| "*)" { if num>1 then comment_section (num-1) lexbuf
else token lexbuf }
| ['\r' '\n'] | "\r\n" { lexbuf.lex_curr_p
<- { lexbuf.lex_curr_p with
pos_bol = lexbuf.lex_curr_p.pos_cnum ;
pos_lnum = 1 + lexbuf.lex_curr_p.pos_lnum };
comment_section num lexbuf }
| eof { failwith "Nested comments are not closed"}
| _ { comment_section num lexbuf }
./minirien "1=2 (* (* test
3=4 *) and 4=5"
Line 4, char 15: Nested comments are not closed
II.
Question 8.
Comme la règle
| "=" { EQUALS }
est prioritaire (puisqu’elle arrive avant) sur la règle
| op_cmp remainder as o { BIN_CMP o }
le =
de 1=2
est associé au lexème EQUALS
, lequel est associé à la production
| LET VAR EQUALS expr IN expr { Let ($2,$4,$6) }
d’où le message d’erreur before "=": parse error
Notons que le problème ne se pose pas pour les autres opérateurs de comparaison, pour lesquels c’est bien la règle
op_cmp remainder as o { BIN_CMP o }
qui s’applique.
On ajoute la règle
| expr EQUALS expr { App ("=",[$1;$3]) }
à la grammaire
/minirien "1+1+1"
>> +(+(Int 1,Int 1),Int 1)
./minirien "1+1*1"
>> +(Int 1,*(Int 1,Int 1))
./minirien "1*1+1"
>> +(*(Int 1,Int 1),Int 1)
Le comportement est bien conforme à la grammaire, mais n’est pas satisfaisant : on aimerait établir un ordre de priorité sur les opérateurs (en l’occurrence : BIN_MULT
devrait être prioritaire sur BIN_PLUS
, lui-même prioritaire sur BIN_CMP
).
Question 9.
%left BIN_PLUS
%left BIN_MULT
Question 10.
On ajoute
%left OR
%left AND
%left NOT
%left BIN_CMP EQUALS
avant les déclarations d’associativité de BIN_PLUS
et BIN_MULT
⟶
./minirien "not 1+1 < 2 and 1 = 1 or foo"
>> or(and(not(<(+(Int 1,Int 1),Int 2)),=(Int 1,Int 1)),Var foo)
Question 11.
On a des conflits shift/reduce
31: shift/reduce conflict (shift 15, reduce 6) on BIN_MULT
31: shift/reduce conflict (shift 16, reduce 6) on BIN_PLUS
31: shift/reduce conflict (shift 17, reduce 6) on BIN_CMP
31: shift/reduce conflict (shift 18, reduce 6) on AND
31: shift/reduce conflict (shift 19, reduce 6) on OR
31: shift/reduce conflict (shift 20, reduce 6) on EQUALS
state 31
expr : LET VAR EQUALS expr IN expr . (6)
expr : expr . BIN_PLUS expr (7)
expr : expr . BIN_MULT expr (8)
expr : expr . BIN_CMP expr (9)
expr : expr . EQUALS expr (10)
expr : expr . AND expr (11)
expr : expr . OR expr (12)
causés par la règle réductible
| LET VAR EQUALS expr IN expr { Let ($2,$4,$6) }
En effet :
Par exemple, l’entrée
let x=1 in x+2*2
est ambiguë : doit-elle se lire comme
(let x=1 in x+2)*2 (* = 6 *)
ou
let x=1 in (x+2*2) (* = 5 *)
?
On aimerait que ce soit la deuxième option.
On ajoute la déclaration
%nonassoc IN
dans parse.mly
, avant toutes les déclarations précédentes (ajoutées en 9) et en 10)).
./minirien "let x=1 in x+2*2"
>> let x = Int 1 in +(Var x,*(Int 2,Int 2))
Question 12.
1.
On ajoute au parser les tokens
%token CASE OF PIPE GIVES
et au lexer les règles
| "case" { CASE }
| "of" { OF }
| "=>" { GIVES }
| "|" { PIPE }
2.
Puis, on modifie les règles grammaticales de la sorte :
expr:
| const { $1 }
| VAR { Var $1 }
| LPAR expr RPAR { $2 }
| CASE expr OF pmatch { Case ($2,List.rev $4) }
(* [...] *)
pmatch:
| prule { [$1] }
| pmatch PIPE prule { $3::$1 }
prule:
| pat GIVES expr { ($1,$3) }
pat:
| VAR { if $1="_" then Any else Var $1 }
| const { $1 }
const:
| INT { Int $1 }
| STRING { String $1 }
3.
On a les conflits suivants :
44: shift/reduce conflict (shift 18, reduce 18) on BIN_MULT
44: shift/reduce conflict (shift 19, reduce 18) on BIN_PLUS
44: shift/reduce conflict (shift 20, reduce 18) on BIN_CMP
44: shift/reduce conflict (shift 21, reduce 18) on AND
44: shift/reduce conflict (shift 22, reduce 18) on OR
44: shift/reduce conflict (shift 23, reduce 18) on EQUALS
state 44
expr : expr . BIN_PLUS expr (6)
expr : expr . BIN_MULT expr (7)
expr : expr . BIN_CMP expr (8)
expr : expr . EQUALS expr (9)
expr : expr . AND expr (10)
expr : expr . OR expr (11)
prule : pat GIVES expr . (18)
-
en effet :
-
l’entrée suivante est ambiguë :
case x of 1 => 1 | _ => 2 * 2 (* se lit-elle (case x of 1 => 1 | _ => 2) * 2 ou case x of 1 => 1 | _ => (2 * 2) : ce qu'on voudrait ? *)
-
de même qu’en 11), on résout le problème avec la déclaration (avant les autres déclarations d’associativité) :
%nonassoc GIVES
-
36: shift/reduce conflict (shift 40, reduce 4) on PIPE
state 36
expr : CASE expr OF pmatch . (4)
pmatch : pmatch . PIPE prule (17)
PIPE shift 40
EOF reduce 4
BIN_MULT reduce 4
BIN_PLUS reduce 4
BIN_CMP reduce 4
AND reduce 4
OR reduce 4
EQUALS reduce 4
IN reduce 4
OF reduce 4
RPAR reduce 4
-
en effet :
-
l’entrée suivante est ambiguë :
case x of 1 => case y of 1 => 1 | _ => 2 (* se lit-elle case x of 1 => (case y of 1 => 1) | _ => 2 ou case x of 1 => (case y of 1 => 1 | _ => 2) : ce qu'on voudrait (associativité à droite) ? *)
-
on résout le problème avec la déclaration (avant les autres déclarations) :
%right CASE OF PIPE
-
./minirien "case x+y of 0 => 0 | _ => 1"
>> case(+(Var x,Var y),[(Int 0,Int 0),(Any,Int 1)])
./minirien "case 1 of 0 => case 2 of 2 => 2 | 1 => 1"
>> case(Int 1,[(Int 0,case(Int 2,[(Int 2,Int 2),(Int 1,Int 1)]))])
Question 13.
1.
Dans lex.mll
:
let op_plus = ['+' '-']
n’a plus de raison d’être, puisqu’il nous faut maintenant particulariser l’opérateur -
.
On supprime donc l’identificateur op_plus
et on remplace la règle
| op_plus remainder as o { BIN_PLUS o }
par
| "+" remainder as o { PLUS o }
| "-" remainder as o { MINUS o }
Dans parse.mly
:
On remplace
-
le token
%token <string> BIN_PLUS
par
%token <string> PLUS %token <string> MINUS
- la déclaration
%left BIN_PLUS
par
%left PLUS MINUS
-
la règle
| expr BIN_PLUS expr { App ($2,[$1;$3]) }
par
| expr PLUS expr { App ("+",[$1;$3]) } | expr MINUS expr { App ("-",[$1;$3]) }
et on ajoute la règle
| MINUS expr { App ("-",[$2]) }
2.
Tout semblait aller pour le mieux dans le meilleur des mondes :
./minirien "x * -y"
>> *(Var x,-(Var y))
/minirien "-x + y"
>> +(-(Var x),Var y)
mais il y a un manque de symétrie :
./minirien "-x+-y"
>> +(-(Var x),-(Var y)) # comme on s'y attendait
./minirien "-x*-y"
>> -(*(Var x,-(Var y))) # on préférerait *(-(Var x),-(Var y)), pour garder la symétrie entre x et y
et bien pire encore :
./minirien "-1%3"
>> -(%(Int 1,Int 3)) # on voulait plutôt %(-(Int 1),Int 3)
3.
Pour remédier à cela, on crée un nouveau token
%token MMINUS
non associatif, auquel on assigne la plus grande priorité :
%right CASE OF PIPE
(* [...] *)
%left BIN_MULT
%nonassoc MMINUS
Puis, on modifie la règle concernée de la sorte :
| MINUS expr %prec MMINUS { App ("-",[$2]) }
Bingo !
./minirien "-x*-y"
>> *(-(Var x),-(Var y))
Question 14.
1.
On modifie les règles de l’analyseur syntaxique de la sorte :
expr:
(* [...] *)
| expr PLUS expr { match ($1, $3) with
| (Int n, Int m) -> Int (n+m)
| _ -> App ("+",[$1;$3]) }
| expr MINUS expr { match ($1, $3) with
| (Int n, Int m) -> Int (n-m)
| _ -> App ("-",[$1;$3]) }
| MINUS expr %prec MMINUS { match $2 with
| Int n -> Int (-n)
| _ -> App ("-",[$2]) }
| expr BIN_MULT expr { let op_list = ["*", ( * ); "/", (/); "%", (mod)] in
match ($1, $3) with
| (Int n, Int m) when (try
let _ = List.assoc $2 op_list in
true
with Not_found -> false)
-> let op = List.assoc $2 op_list in
Int (op n m)
| (Int n, Int m) when $2="/" -> Int (n/m)
| (Int n, Int m) when $2="%" -> Int (n mod m)
| _ -> App ($2,[$1;$3]) }
./minirien "1+(2*3)+x"
>> +(Int 7,Var x)
./minirien "6/2-5+4%3*x"
>> +(Int -2,*(Int 1,Var x))
2.
Pour les variables muettes, j’ai tenté cette approche, mais en vain :
%{
open Expr
module VarSet = Set.Make(struct
type t = string
let compare = Pervasives.compare
end)
let vars = ref VarSet.empty
let adding_var = ref false
%}
/* [...] */
%%
(* [...] *)
expr:
(* [...] *)
| VAR { if VarSet.mem $1 !vars || !adding_var
then Var $1
else failwith ("Variable "^$1^" not defined") }
(* [...] *)
| LET VAR EQUALS expr IN expr { let expr_equals = $4 in
(
adding_var := true;
vars := VarSet.add $2 !vars;
adding_var := false;
Let ($2,expr_equals,$6)
)
}
3.
Je n’ai pas le temps d’implémenter cette dernière amélioration, mais l’idée serait d’utiliser une référence pour compter le nombre de parenthèses ouvrantes déjà rencontrées :
- à chaque nouvelle parenthèse ouvrante (resp. fermante), on incrémente (resp. décrémente) le compteur de 1 (et on lève une exception si le compteur devient strictement négatif).
-
à chaque crochet fermant :
- si le compteur est négatif (ou nul), on renvoie un erreur
- sinon, on remet le compteur à zéro
Leave a comment