Author: Christian.Queinnec@lip6.fr

## Basic revisité

Nous voulons à présent utiliser le couple `ocamllex` et `ocamlyacc` afin de remplacer la fonction `parse` donnée page ?? pour Basic, par des fonctions engendrées à partir des fichiers de spécification du lexique et de la syntaxe du langage.
Pour y arriver, nous ne pourrons pas reprendre tels quels le type des unités lexicales que nous avions défini. Nous allons devoir définir un type plus précis permettant de distinguer les différents opérateurs, les commandes et les mots clés.
Nous aurons également besoin d'isoler les déclarations de type concernant la syntaxe abstraite dans un fichier `basic_types.mli`. Il contiendra les déclarations du type `phrases` et de tous les types nécessaires à celui-là.

### Le fichier `basic_parser.mly`

##### L'en-tête

du fichier contient un appel aux déclarations de types pour la syntaxe abstraite ainsi que deux fonctions auxiliaires de conversion de chaînes de caractères vers leur équivalent en syntaxe abstraite.

    %{
    open Basic_types ;;

    let phrase_of_cmd c =
     match c with
       "RUN" -> Run
     | "LIST" -> List
     | "END" -> End
     | _ -> failwith "line : unexpected command"
    ;;

    let op_bin_of_rel r =
     match r with
       "=" -> EGAL
     | "<" -> INF
     | "<=" -> INFEQ
     | ">" -> SUP
     | ">=" -> SUPEQ
     | "<>" -> DIFF
     | _ -> failwith "line : unexpected relation symbol"
    ;;

    %}

##### Les déclarations

contiennent trois parties : la déclaration des lexèmes; la déclaration de leurs règles d'associativité et de priorité; la déclaration de la règle axiome `line` qui correspond à l'analyse d'une ligne de programme ou de commande.

Les unités lexicales sont les suivantes :

     %token <int> Lint
     %token <string> Lident
     %token <string> Lstring
     %token <string> Lcmd
     %token Lplus Lmoins Lmult Ldiv Lmod
     %token <string> Lrel
     %token Land Lor Lneg
     %token Lpar Rpar
     %token <string> Lrem
     %token Lrem Llet Lprint Linput Lif Lthen Lgoto
     %token Legal
     %token Leol

Leurs noms parlent d'eux-mêmes et ils sont décrits par le fichier `basic_lexer.mll` (voir page ??).

Les règles de priorité entre opérateurs reprennent les valeurs données par les fonctions `priority_ob` et `priority_ou` définies lorsque nous avions donnée la grammaire de notre Basic (voir page ??).

     %right Lneg
     %left Land Lor
     %left Legal Lrel
     %left Lmod
     %left Lplus Lmoins
     %left Lmult Ldiv
     %nonassoc Lopp

Le symbole `Lopp` nous servira à traiter le moins unaire. Ce n'est pas un terminal de la grammaire, mais un << pseudo non terminal >> permettant de gérer la surcharge entre opérateurs lorsque deux utilisations d'un même symbole ne doivent pas avoir la même priorité selon le contexte. Et c'est le cas du symbole moins (`-`). Nous reviendrons sur ce point lorsque nous aurons énoncé les règles de la grammaire.

Le non terminal axiome est `line`. La fonction engendrée retournera l'arbre de syntaxe abstraite de la ligne analysée.

     %start line
     %type <Basic_types.phrase> line

##### Les règles

de grammaire se décomposent en trois non terminaux : `line` pour une ligne; `inst` pour une instruction du langage; `exp` pour les expressions. L'action associée à chacune des règles se contente de construire le morceau de syntaxe abstraite correspondant.

     %%
    line :
       Lint inst Leol               { Ligne {num=$1; inst=$2} }
     | Lcmd        Leol                    { phrase_of_cmd $1 }
     ;

    inst :
       Lrem                         { Rem $1 }
     | Lgoto Lint                   { Goto $2 }
     | Lprint exp                   { Print $2 }
     | Linput Lident                { Input $2 }
     | Lif exp Lthen Lint           { If ($2, $4) }
     | Llet Lident Legal exp        { Let ($2, $4) }
     ;

    exp :
       Lint                         { ExpInt $1 }
     | Lident                       { ExpVar $1 }
     | Lstring                      { ExpStr $1 }
     | Lneg exp                     { ExpUnr (NON, $2) }
     | exp Lplus exp                { ExpBin ($1, PLUS, $3) }
     | exp Lmoins exp               { ExpBin ($1, MOINS, $3) }
     | exp Lmult exp                { ExpBin ($1, MULT, $3) }
     | exp Ldiv exp                        { ExpBin ($1, DIV, $3) }
     | exp Lmod exp                        { ExpBin ($1, MOD, $3) }
     | exp Legal exp                { ExpBin ($1, EGAL, $3) }
     | exp Lrel exp                        { ExpBin ($1, (op_bin_of_rel $2), $3) }
     | exp Land exp                 { ExpBin ($1, ET, $3) }
     | exp Lor exp                  { ExpBin ($1, OU, $3) }
     | Lmoins exp %prec Lopp        { ExpUnr(OPPOSE, $2) }
     | Lpar exp Rpar                { $2 }
     ;
     %%

Ces règles n'appellent pas de commentaire particulier sauf celle-ci :

    exp :
     ...
     | Lmoins exp %prec Lopp { ExpUnr(OPPOSE, $2) }

Elle concerne l'utilisation unaire du symbole `-`. Le mot clé `%prec` que l'on trouve dans cette règle indique que cette construction doit recevoir la priorité de `Lopp` (ici, la priorité maximale).

### Le fichier `basic_lexer.mll`

L'analyse lexicale ne contient qu'un seul ensemble : `lexer` qui correspondra au type près à l'ancienne fonction `lexer` (voir page ??). L'action sémantique associée à la reconnaissance de chaque unité lexicale est simplement le renvoi du constructeur correspondant. Comme le type des unités lexicales est déclaré dans le fichier des règles de syntaxe, il faut inclure ce dernier. On rajoute une petite fonction auxiliaire chargée de supprimer les guillemets autour des chaînes de caractères.

    {
     open Basic_parser ;;

     let string_chars s = String.sub s 1 ((String.length s)-2) ;;
    }

    rule lexer = parse
       [' ' '\t']            { lexer lexbuf }

     | '\n'                  { Leol }

     | '!'                   { Lneg }
     | '&'                   { Land }
     | '|'                   { Lor }
     | '='                         { Legal }
     | '%'                         { Lmod }
     | '+'                         { Lplus }
     | '-'                         { Lmoins }
     | '*'                         { Lmult }
     | '/'                         { Ldiv }

     | ['<' '>']             { Lrel (Lexing.lexeme lexbuf) }
     | "<="                         { Lrel (Lexing.lexeme lexbuf) }
     | ">="                         { Lrel (Lexing.lexeme lexbuf) }

     | "REM" [^ '\n']*       { Lrem (Lexing.lexeme lexbuf) }
     | "LET"                 { Llet }
     | "PRINT"               { Lprint }
     | "INPUT"               { Linput }
     | "IF"                  { Lif }
     | "THEN"                { Lthen }
     | "GOTO"                { Lgoto }

     | "RUN"                { Lcmd (Lexing.lexeme lexbuf) }
     | "LIST"               { Lcmd (Lexing.lexeme lexbuf) }
     | "END"                { Lcmd (Lexing.lexeme lexbuf) }

     | ['0'-'9']+           { Lint (int_of_string (Lexing.lexeme lexbuf)) }
     | ['A'-'z']+           { Lident (Lexing.lexeme lexbuf) }
     | '"' [^ '"']* '"'     { Lstring (string_chars (Lexing.lexeme lexbuf)) }

Remarquons que nous avons dû isoler le symbole `=` qui sert à la fois dans les expressions et dans l'instruction d'affectation.
Seules deux de ces expressions rationnelles nécessitent des explications complémentaires. La première concerne les lignes de commentaires (`"REM" [^ '\n']*`). Elle reconnaît le mot clé `REM` suivi par un nombre quelconque de caractères différents du retour chariot. La seconde concerne les chaîne de caractères (`'"' [^ '"']* '"'`) considérées comme une suite de caractères différents de `"` et encadrée par des `"`.

### Compilation, intégration

La compilation du couple analyseur lexical, analyseur syntaxique doit être faite en suivant un certain ordre. Ceci est dû à la dépendance mutuelle dans la déclaration des lexèmes. Ainsi, dans notre exemple, il faudra taper la séquence de commandes :

    ocamlc -c basic_types.mli
    ocamlyacc basic_parser.mly
    ocamllex basic_lexer.mll
    ocamlc -c basic_parser.mli
    ocamlc -c basic_lexer.ml
    ocamlc -c basic_parser.ml

Ce qui engendrera les fichiers `basic_lexer.cmo` et `basic_parser.cmo` qui pourrons être intégrés à l'application.
Nous disposons maintenant de tout le matériel nécessaire pour reformuler notre application.
Nous supprimons tous les types et toutes les fonctions des paragraphes << analyse lexicale >> (page ??) et << analyse syntaxique >> (page ??) de l'application Basic et, dans la fonction `une_commande` (page ??), nous remplaçons l'expression

        match parse (input_line stdin) with

par

        match line lexer (Lexing.from_string ((input_line stdin)^"\n")) with

Remarquons que nous devons remettre en fin de ligne le caractère `'\n'` que la fonction `input_line` avait supprimé. Ceci est nécessaire, car nous utilisons ce caractère pour marquer la fin d'une ligne de commande (`Leol`).