3.4.projet de réalisation d’un

interpréteur de micro-langage



 

Objectif : Mise en œuvre des techniques proposées dans ce document pour écrire un analyseur / interpréteur d’un micro-langage simple.
 

ENONCE :

On  donne la Grammaire Gµ suivante :

VN = { áprog.ñ, áblocñ, áégalñ, ásuite1ñ, ásuite2ñ, ámembre droitñ, áplusñ, ásuite3ñ }
VT = { ‘a’ , ‘b’ , ... , ‘z’ , ‘}‘ , ‘{‘ , ‘;’ , ‘L’, ‘E’ , ‘+’ , ‘*’ , ‘/’ , ‘-’ , ‘%’, ‘=’ }

 

Questions  :

1°) Construire une classe interpréteur de L(). On donne la sémantique suivante : 

(les spécifications sont fournies en langage algorithmique)
Lx correspond à lire(x)
Ex correspond à ecrire(x)
x = y correspond à x ¬ y
a,b,c correspondent à des variables entiers relatifs
+ correspond à l’opérateur d’addition sur les entiers relatifs.
- correspond à l’opérateur de soustraction sur les entiers relatifs.
* correspond à l’opérateur de multiplication sur les entiers relatifs.
/ correspond à l’opérateur de quotient euclidien sur les entiers relatifs.
% correspond à l’opérateur de reste euclidien sur les entiers relatifs.

On utilisera une mémoire centrale dans laquelle se trouveront les variables (dans un tableau) et une autre table contenant les noms des variables et leur adresse en mémoire centrale (table des symboles).

2°) Construire une IHM de calculatrice programmable fondée sur la classe précédente d'interpréteur du langage L(). calculatrice dans laquelle l'utilisateur peut entrer des lignes de programmes en L() et les exécuter.
 


RESOLUTION DU PROBLEME PROPOSE

     
    Construction d’un analyseur du langage L(Gµ)

    ( analyseur simplifié en pascal  Pascal.Interpr \Langana.pas   )

    Nous remarquons que la grammaire G est une grammaire de type 3 déterministe(d’état fini). En appliquant le principe de correspondance entre grammaires de type 3 et automates d’états finis, nous construisons l’AEFD associé qui sera un analyseur du langage engendré par .

    correspondance entre les éléments de VN et les états de l’automate : 

      <prog.> Û q0
      <suite2>
      Û q3
      <plus>
      Û q6
       idem pour <moins>, <mult>, <div> et <reste>
      <bloc> Û q1
      <egal>
      Û q4
      <suite3>
      Û q7

      <suite1> Û q2
      <membre droit>
      Û q5

    Soit l’AEFD A,  A = ( VT, E , q0 , qf , µ )

    E = { q0, q1, q2, q3, q4, q5, q6, q7, qf }

    état initial : q0
    état final : qf
    VT = { ‘a’, ‘b’, ‘c’, ‘}‘, ‘{‘, ‘;’, ‘L’, ‘E’, ‘+’, ‘*’ , ‘/’ , ‘-’ , ‘%’, ‘=’ }

    Nous poserons pour simplifier :
    Lettre = { ‘a’, ‘b’, .... , ‘z’ },
    Operat = { ‘+’, ‘*’ , ‘/’ , ‘-’ , ‘%’  }.

    Afin de réduire le nombre de lignes de texte nous adoptons la convention d’écriture suivante :
     
     
    ( qk , Lettre ) ¾® qi
    représente l’ensemble des 26 règles 
    ( qk ,a ) ¾® qi
    ( qk ,b ) ¾® qi
       ........
    ( qk ,z ) ¾® qi
     
    ( qk , Operat ) ¾® qi
    représente l’ensemble des 5 règles 
    ( qk ,+ ) ¾® qi
    ( qk ,- ) ¾® qi
    ( qk ,* ) ¾® qi
    ( qk ,/ ) ¾® qi
    ( qk ,% ) ¾® qi

    Nous obtenons alors un AEFD dont nous donnons les règles et le graphe.

Règles de transitions de µ :
( q0, { )®q1
( q1, } )® qf
( q1, Lettre) ®q4
( q1, L ) ®q2
( q1, E ) ®q2
( q2, Lettre) ®q3
( q3, ; ) ®q1
( q4, = ) ®q5
( q5, Lettre ) ®q6
( q6, ; ) ®q1
( q6Operat ) ®q7
( q7, Lettre ) ®q3

     

    Graphe de l’automate A :

    Employons la démarche conseillée dans le cours au chap 3.3 pour construire la matrice de transition de l'analyseur AEFD, grâce à la méthode init_table de la classe implantant l'automate :

    const 
      
    imposs =- 1 ;  
      
    fin = 20 ;  
      
    finmot = '#'
     type 
      
    T_etat = imposs..fin
      
    Vt =char
      
    T_transition =array [T_etat, char of  T_etat ;
      
      
    AutomateEF  class  ( AutomateAbstr )
     
      protected
          procedure 
    init_table ; override;
      end;

    implementation
     
     procedure 
    AutomateEF.init_table ;
     
    {initialisation de la table des transitions} 
     
    var   i : t_etat ;  
      
    j : 0..255 ;  
      
    k :char ;
      begin
       for 
    i := imposs  to  fin  do 
        for 
    j := to  255  do 
         
    table[i,chr(j)] := imposs ;
       
    table[0, '{' ] := 1
       
    table[1, '}' ] := fin
       for 
    k := 'a' to 'z' do
       begin
        
    table[1,k] := 4 ;
        
    table[2,k] := 3 ;      
        
    table[5,k] := 6 ;
        
    table[7,k] := 3 ;
       end;
       
    table[1, 'E' ] := 2
       
    table[1, 'L' ] := 2
       
    table[3, ';' ] := 1
       
    table[4, '=' ] := 5
       
    table[6, '+' ] := 7
       
    table[6, '*' ] := 7 ;
       
    table[6, '-' ] := 7 ;
       
    table[6, '%' ] := 7 ;
       
    table[6, '/' ] := 7 ;
       
    table[6, ';' ] := 1
      end;

    Programme d'analyseur utilisant la classe AutomateEF et reconnaissant le langage L(G)



program 
ProjAutomEF ;
  
{$APPTYPE CONSOLE}
 
uses
  
SysUtils,
  UAutomEF 
in  'UAutomEF.pas';
 var 
AEFD : AutomateEF ;
  
  begin
   
AEFD :=  AutomateEF.Create(3) ;
   
AEFD.Mot := '{La;Lb;Ea;a=b*c;}';
   
AEFD.Analyser ;
   
readln ;
  end.

Exécution de ce programme sur l’exemple {La;Lb;Ea;a=b*c;} :
 

Exécution de ce programme sur l’exemple {La;Lb;Ea=b;} :
  


    Spécifications pour un interpréteur du langage L(Gµ)

    ( interpréteur simplifié en pascal  Pascal.Interpr \Langintr.pas )
     

    Rappelons les spécifications proposées l’énoncé :

    Lx correspond à lire(x)
    Ex correspond à ecrire(x)
    x = y correspond à x ¬ y
    a,b,c correspondent à des variables entiers relatifs
    + correspond à l’opérateur d’addition sur les entiers relatifs.
    - correspond à l’opérateur de soustraction sur les entiers relatifs.
    * correspond à l’opérateur de multiplication sur les entiers relatifs.
    / correspond à l’opérateur de quotient euclidien sur les entiers relatifs.
    % correspond à l’opérateur de reste euclidien sur les entiers relatifs.

    L’énoncé nous propose une spécification d’implantation en Delphi pour la mémoire centrale (nous la dénommons MemC)et la table des symboles (nous la dénommons TabSymb).
     

    Le mécanisme d’accès est volontairement simple :


     

    Le tableau TabSymb est indexé directement sur les caractères (ce qui évite la construction d’une fonction de codage). Chaque cellule TabSymb[‘a’], TabSymb[‘b’], ..., TabSymb[‘z’], contient un nombre qui est l’adresse adr (numéro de case dans MemC) de la variable a, b ,..., z.

    A partir de ce numéro de case adr, la cellule MemC[adr] du tableau MemC contient la valeur numérique de la variable d’adresse adr.

    Cette approche bien que simple reflète malgré tout la réalité du fonctionnement de l’exécution d’un interpréteur dans une machine de Von Neumann.
     

    Implantation en Delphi de ces spécifications de données:

    const
     adresse=0..maxadresse;
    type
     Symbole=char;
     T_mem=array[adresse] of integer;
     T_symb=array[Symbole]of adresse;
    var
     MemC:T_mem; // la mémoire centrale
     TabSymb:T_symb; // la table des symboles
     
     

    Spécifications d’implantation pour les instructions :

    En partant des spécifications de données précédentes, nous pouvons proposer une implantation de chacune des instructions du langage L(Gµ).

     
    Nous noterons par la suite, @ la relation de traduction en pseudo pascal.

    Nous avons utilisé trois métasymboles x,y et z pour remplacer les noms des variables a, b ...etc afin de ne pas alourdir la traduction par de nombreuses lignes redondantes.
     

    Interprétation de l’instruction L :
       Lx        @ TabSymb[x] := adressecourante ;
    adressecourante := adressecourante +1 ;
    readln(MemC[TabSymb[x]])

    Interprétation de l’instruction E :
       Ex        @ adressecourante := TabSymb[x];
    writeln(MemC[adressecourante ])

    Interprétation de l’instruction x = y :
      x = y           @ MemC[TabSymb[x]]:= MemC[TabSymb[y]]

    Interprétation de l’instruction x = y oper z :
       x = y oper z     @
    oper est l'un des opérateurs suivants :
     { ‘+’ , ‘*’ ,  ‘-’ ,  ‘/’ }


    MemC[TabSymb[x]]:=MemC[TabSymb[y]]oper MemC[TabSymb[z]]

     

    Construction d’un interpréteur à partir de l’AEFD

Nous allons construire notre interpréteur à partir du graphe de l’AEFD que nous avons trouvé. Ainsi, nous passerons de la version analyseur à la version interpréteur en suivant les chemins du graphe et en repérant les points clefs où l’interprétation d’une instruction est possible. Nous adoptons comme stratégie le fait que le lancement d’une interprétation ne sera possible que lorsque l’on sera arrivé dans le graphe à un endroit où une instruction vient juste d’être reconnue.
 

Interprétation des instructions Ex et Lx
Nous observons tout d’abord sur la portion de graphe de l’AEFD comment les instructions Ex et Lx sont reconnues.

Une telle instruction est entièrement reconnue lorsque l’automate est à l’état q3. On pourrait lancer l’interprétation de Ex ou Lx, mais ce serait une erreur car il y a un autre chemin dans le graphe qui amène à l’état q3. Nous voyons qu’il n’y a que deux manières différentes d’arriver en q3 : soit en venant de q2, soit en venant de q7.

Il nous faut une variable que nous nommerons etatavant qui mémorisera l’état précédent. Le symbole qui vient d’être analysé est stocké dans une variable que nous nommons symlu.

D’autre part, lorsque nous sommes en q3, le symbole qui vient d’être analysé est un élément de {a,b,.., z}. Afin de décider s’il s’agit d’une instruction Ex ou bien Lx, il nous faut avoir mémorisé le symbole précédent qui a été analysé en passant de q1 à q2. Nous nommerons cette variable symprec.

Voici donc en pseudo-Delphi le code de lancement de l’interprétation de Lx ou de Ex. Ce code est à rajouter à l’AEFD précédent.
 
If (etat=q3)and(etatavant=q2)and(symprec=L)then
begin {on interprète le Lx}
 TabSymb[symlu] := adressecourante ;
 adressecourante := adressecourante +1 ;
 readln(MemC[TabSymb[symlu]])
end
else {on interprète le Ex}
begin
 adressecourante := TabSymb[symlu];
 writeln(MemC[adressecourante ])
end

 

Interprétation de l’instruction x = y oper z
Nous avons vu que l’autre façon d’arriver à l’état q3 est d’avoir suivi l’arc q7 à q3.



Ce chemin correspond très exactement à l’analyse d’une instruction x=y oper z. Afin de pouvoir interpréter correctement cette instruction, nous devons avoir mémorisé les noms des variables x, y et z le long du chemin d’analyse : q1 à q4 à q5 à q6 à q7 à q3. Afin de ne pas rajouter trop de nouveaux états nous avons décidé de n'avoir qu'une transition sur un ensemble d'opérateurs  (q6 oper) à q7.

Nous regroupons alors les symboles +,-,*,/,% dans un même ensemble (Operat : set of Vt), que nous initialiserons dans la procédure d'initialisation :
Operat =['+', '-', '*', '/', '%' ]

Nous notons que :
x est connu lorsque l’AEFD est à l’état q4 (à la fin de l’arc q1à q4)
y est connu lorsque l’AEFD est à l’état q6 (à la fin de l’arc q5à q6)
z est connu lorsque l’AEFD est à l’état q3 (à la fin de l’arc q7à q3)

Le stockage d’un troisième symbole de variable nécessite l’utilisation d’une nouvelle variable servant à le mémoriser. Nous la nommerons symavant.

En résumant la situation, nous aurons pour x=y oper z :

Voici donc en pseudo-Delphi, le code de lancement de l’interprétation de l’instruction x=y oper z. Ce code est aussi à rajouter au code précédent.
 
If etat=q4 then symavant := symlu ;
If etat=q6 then symprec := symlu ;
If (etat=q3)and(etatavant=q7)then{on interprète x=y oper z}
case OperVar of
 '+':MemC[TabSymb[symavant]]:=MemC[TabSymb[symprec]]+MemC[TabSymb[symlu]];
 '-':MemC[TabSymb[symavant]]:=MemC[TabSymb[symprec]]-MemC[TabSymb[symlu]];
 '*':MemC[TabSymb[symavant]]:=MemC[TabSymb[symprec]]*MemC[TabSymb[symlu]];
 '/':MemC[TabSymb[symavant]]:=MemC[TabSymb[symprec]]div MemC[TabSymb[symlu]];
 '%':MemC[TabSymb[symavant]]:=MemC[TabSymb[symprec]]mod MemC[TabSymb[symlu]];
end;

Interprétation de l’instruction x=y
Si nous observons la partie du graphe de l’AEFD correspondant à l’analyse de l’instruction x=y, nous remarquons que contrairement aux cas précédents ce n’est pas à l’état q6 que nous pouvons prendre une décision.
 

En effet, à partir de q6 il y a deux possibilités d’instructions : soit x=y, soit x=y+z. Il nous faut aller un état plus loin dans le graphe (déterministe) afin de décider si l’on est dans un cas ou dans l’autre.

Si l’état d’après q6 est q1, il s’agit d’une instruction x=y, si l’état d’après q6 est q7 il s’agit d’une instruction x=y+z.

Le chemin d’analyse d’une instruction x=y est :
q1 à q4 à q5 à q6 à q1.

Comme dans l’analyse du problème précédent nous avons :

Voici donc en pseudo-Delphi, le code de lancement de l’interprétation de l’instruction x=y. Ce code est le dernier à rajouter au code précédent.
 
If (etat=q1)and(etatavant=q6)then{on interprète x=y}
MemC[TabSymb[symavant]]:=MemC[TabSymb[symprec]]

Le lecteur pourra étendre le programme en augmentant par exemple le nombre de variables, ou le nombre d’opérateurs.

Voici ci-dessous les squelettes des méthodes Delphi d'une classe d'interpréteur InterpreteurLang permettant d'étendre l'analyseur en interpréteur :


type
   T_etat=imposs..fin;    Vt=char;

procedure InterpreteurLang.init_table;
{initialisation de la table des transitions de l'automate AEFD }
  
procedure InterpreteurLang.ClearMemC;
 // RAZ mémoire centrale
 
procedure InterpreteurLang.ClearTabSymb;
 // RAZ table des symboles
 
procedure InterpreteurLang.initialisations;
 // RAZ tout

{--------------------- INTERPRETEUR -------------------}


procedure InterpreteurLang.Interprete(chaine:string;var etat:T_etat);
{moteur d'analyse et d'interpretation de l'automate}

function InterpreteurLang.transition(q:T_etat;car:Vt):T_etat;
{par la table de transition :}
 
procedure InterpreteurLang.Symsuiv(var num:integer;var symlu:Vt);
{fournit le symbole suivant à analyser}

function InterpreteurLang.In_TabSymb(identif:Vt):boolean;
{indique si le symbole identif est deja dans TabSymb}



Comme notre interpréteur doit lire et analyser un texte de programme et écrire les resultats d'exécution, nous proposons d'écrire une classe qui contienne toutes spécifications nécessaires et utiles au fonctionnement d'un interpréteur, mais qui ne dépende pas de la façon dont on lit et dont on affiche les résultats. Pour cela nous construisons une classe abstraite TinterpretAbstr qui possède 2 méthodes abstraites (donc non implémentées) Lire et Ecrire.

Le soin d'expliquer comment lire ou écrire est délégué à une classe 'concrète' qui dérivera de TinterpretAbstr.


1°) Construction d’une classe abstraite d'interpréteur de L(G)

Nous reprenons toutes les procédures et fonctions du programme console et nous les transformons en méthodes de classe. Ci-dessous un diagramme UML-like de la signature de la classe TinterpretAbstr. Nous ajoutons à cette classe, en visibilité protected, les deux méthodes abstraites :

procedure Lire(symb:Vt;var x:integer); // symb = la variable à lire, x = sa valeur entière
procedure Ecrire(symb:Vt; x:integer);  // symb = la variable à ecrire, x = sa valeur entière


Nous reprenons dans une unit Delphi que nous nommons Uinterpreteur, les mêmes déclarations de constantes et de type que dans le programme console :

Unit  Uinterpreteur  :
  interface
 const
   imposs =- 1 ;
   fin = 8 ;
   finmot = '#';
   maxadresse = 100 ;
 type
   T_etat = imposs..fin ;
   Vt =char ;
   T_transition =array [T_etat, char of  T_etat ;
   adresse = 0..maxadresse ;
   Symbole =char ;
   T_mem =array [adresse]  of  integer ;
   T_symb =array [Symbole] of  adresse ;

Ce qui nous donne dans Uinterpreteur la signature suivante pour la classe abstraite TinterpretAbstr :

TinterpretAbstr = class
 private
   numcar :integer ;
   table : T_transition ;
   MemC : T_mem ;
   TabSymb : T_symb ;
   OperVar : Vt ;
   Operat  set  of  Vt ;
   EtatFinal  T_etat ;
  procedure  ClearTabSymb ;
  procedure  ClearMemC ;
  procedure  init_table ;
  procedure  initialisations ;
  function  transition(q : T_etat ; car : Vt) : T_etat ;
  procedure  Symsuiv( var  num :integer ;var  symlu : Vt) ;
  function  In_TabSymb(identif : Vt) :boolean ;
  procedure  Exec(chaine :string ;var  etat : T_etat) ;
 protected
  procedure  Lire(symb : Vt ;var  x :integer ) virtual ; abstract ;
  procedure  Ecrire(symb : Vt ; x :integer ) virtual ; abstract ;
 public
   mot :string ;
  function  Filtrage(Texte :string ) :string ;
  procedure  Lancer(prog :string ) ;
  constructor  Create(fin  T_etat)  ;
end;


Nous avons rajouté une méthode de filtrage du texte source permettant une saisie plus libre du texte (avec des blancs, des sauts de ligne,...) et épurant le texte entré de tous ces éléments :

function Filtrage(Texte: string): string;

Le texte suivant entré au clavier sur 8 lignes, soumis à la méthode de filtrage est restitué pour analyse une fois épuré :

{
    Lb;    Lc;
    a  =  c  + b ;
    d=a*c;
    Ea;   Eb;
    Ec;
    Ed;
}#





{Lb;Lc;a=c+b;d=a*c;Ea;Eb;Ec;Ed;}#


Nous donnons ici la partie implementation de la unit Uinterpreteur avec les corps des méthodes : 

{ TinterpretAbstr }

  
procedure  TinterpretAbstr.ClearMemC ;
  
// RAZ mémoire centrale
 
var  i : adresse ;
 begin
  for 
i := to  maxadresse  do
   
MemC[i] := 0 ;
 end;
 
  procedure 
TinterpretAbstr.ClearTabSymb ;
  
// RAZ table des symboles
 
var  i : Symbole ;
 begin
  for 
i := chr(0)  to  chr(255)  do
   
TabSymb[i] := 0 ;   {0 indique : variable non definie}
 
end;
 
  procedure 
TinterpretAbstr.init_table ;
  
{initialisation de la table des transitions de l'automate AEFD }
 
var
  
i : t_etat ;   
  
j : 0..255 ;   
  
k :char ;
  begin
   for 
i := imposs  to  fin  do
    for 
j := to  255  do
     
table[i,chr(j)] := imposs ; {par défaut tout est non reconnu}
   
table[0, '{' ] := 1 ;
   
table[1, '}' ] :=  EtatFinal ;
   for 
k := 'a' to 'z' do
   begin
    
table[1,k] := 4 ;
    
table[2,k] := 3 ;
    
table[5,k] := 6 ;
    
table[7,k] := 3 ;
   end;
   
table[1, 'E' ] := 2 ;
   
table[1, 'L' ] := 2 ;
   
table[3, ';' ] := 1 ;
   
table[4, '=' ] := 5 ;
   
table[6, '+' ] := 7 ;
   
table[6, '*' ] := 7 ;
   
table[6, '-' ] := 7 ;
   
table[6, '%' ] := 7 ;
   
table[6, '/' ] := 7 ;
   
table[6, ';' ] := 1 ;
  end;
  
  procedure 
TinterpretAbstr.initialisations ;
  
// RAZ tout
  
begin
   
ClearMemC ;
   
ClearTabSymb ;
   
init_table ;
   
Operat  := [ '+' '-' '*' '/' '%'  ]
  
end;
  
  function 
TinterpretAbstr.transition(q T_etat car Vt) T_etat ;
  
{par la table de transition :}
  
begin
   result
:= table[q,car] ;
  end;
  
  procedure 
TinterpretAbstr.Symsuiv( var  num :integer ;var  symlu : Vt) ;
  
{fournit le symbole suivant è analyser}
  
begin
   if 
num <= length(mot)  then
   begin
    
symlu := mot[num] ;
    
num := num + 1
   
end
   else           
{si on veut lire apres la fin}
    
symlu := chr(0) ; {caractere NUL invisible     }
  
end;
  
  function 
TinterpretAbstr.In_TabSymb(identif : Vt) :boolean ;
  
{indique si le symbole identif est deja dans TabSymb}
  
begin
   if 
tabsymb[identif] = then
    
in_tabsymb := false
   
else
    
in_tabsymb := true
  
end;
  
  procedure 
TinterpretAbstr.Exec(chaine string ; var  etat T_etat) ;
  
{moteur d'analyse et d'interpretation de l'automate}
 
var
  
symlu : Vt ;
  
adressecourante : adresse ;
  
symprec,symavant : Vt ;
  
etavant : T_etat ;
  begin 
{Interpreteur - Exec}
   
numcar := 1 ;
   
etat := 0 ;
   
adressecourante := 1 ;   {on commence toujours a 1}
   
mot :=  chaine ;
   while 
(etat <> imposs) and (etat <> fin)  do
   begin
    
Symsuiv(numcar,symlu) ;
    
etavant := etat ;
    
etat := transition(etat,symlu) ;
    
{------------- partie due a l'interpretation ------------}
    
if  (etat = 2) then
     
symprec := symlu ;
    if 
etat = then
    begin
     
symavant := symlu ;
     if 
not  In_TabSymb(symlu) then
     begin
      
TabSymb[symlu] := adressecourante ;
      
adressecourante := adressecourante + 1
     
end
    end;
    if 
etat = then
     
symprec := symlu ;
    if 
etat = then
     if 
symlu  in  Operat  then
      
OperVar  :=  symlu ;
    if 
(etat = 3) and (etavant = 2) and (symprec = 'L' then  { Lx; }
    
begin
     
TabSymb[symlu] := adressecourante ;
     
adressecourante := adressecourante + 1 ;
     
Lire(symlu,MemC[TabSymb[symlu]]) ;
    end
    else
     if 
(etat = 3) and (etavant = 2) and (symprec = 'E' then  { Ex; }
     
begin
      
adressecourante := TabSymb[symlu] ;
      
Ecrire(symlu,MemC[adressecourante]) ;
     end
     else
      if 
(etat = 1) and (etavant = 6) then                 { x=y; }
      
begin
       
MemC[TabSymb[symavant]] := MemC[TabSymb[symprec]]
      
end
      else
       if 
(etat = 3) and (etavant = 7) then                 { x = y oper z; }
       
case
        
opervar  of
        
'+'
: MemC[TabSymb[symavant]] := MemC[TabSymb[symprec]] + MemC[TabSymb[symlu]] ;
     
'-'
: MemC[TabSymb[symavant]] := MemC[TabSymb[symprec]] - MemC[TabSymb[symlu]] ;
     
'*'
: MemC[TabSymb[symavant]] := MemC[TabSymb[symprec]] * MemC[TabSymb[symlu]] ;
     
'/'
: MemC[TabSymb[symavant]] := MemC[TabSymb[symprec]] div  MemC[TabSymb[symlu]] ;
     
'%'
: MemC[TabSymb[symavant]] := MemC[TabSymb[symprec]] mod  MemC[TabSymb[symlu]] ;
       end;
    
{-------------------------------------------------------}
   
end;
  end;
{Interpreteur - Exec}
  
  
function  TinterpretAbstr.Filtrage(Texte string ) string ;
  
{ filtrage du texte è interpréter :conservation des éléments
  du vocabulaire vt et élimination des autres 
  }
 
var  s :string ;
  
i :integer ;
  begin
   
s := '';
   for 
i := to  length(Texte)  do
    if 
Texte[i]  in  [ 'a' .. 'z' , ';' , '{' , '}' , 'L' , 'E' , '=' ,finmot] + Operat   then
     
s := s Texte[i] ;
   result
:= s
  
end;
  
  
  procedure 
TinterpretAbstr.Lancer(prog string ) ;
  
{ uniquement pour appeler la méthode privée Exec
   et envoyer des messages è l'utilisateur selon 
   la manière dont s'est passée l'exécution.
  }
 
var  q : T_etat ;
 begin
  
Exec(prog,q) ;
  if 
q = imposs  then
   
MessageDlg( 'Blocage, erreur de syntaxe !' +
  
#13 + #10 + copy(prog,1,numcar) + '<<--' , mtError ,[mbOk], 0)
  
else
   if 
q = fin  then
    
MessageDlg( 'Exécution terminée !' , mtInformation  ,[mbOk], 0) ;
 end;
 
 
{----  le constructeur TinterpretAbstr ----}
  
constructor  TinterpretAbstr.Create(fin  T_etat)  ;
  begin
   if 
fin  in  [1..20]  then
    
etatfinal := fin
   
else
    
etatfinal := 20 ;
   
initialisations ;
  end;
  
end. 
{----  fin de la unit  ----}


2°) Construction de deux classes héritant de TinterpretAbstr

Application IHM - première classe dérivée

Nous voulons écrire une application IHM utilisant la classe d'interpréteur que nous venons de construire, selon par exemple le modèle ci-dessous :

Nous afficherons les résultats par une surcharge dynamique (redéfinition) de la méthode Ecrire à travers un TMemo : 

Nous saisirons les valeurs par une redéfinition de la méthode Lire à travers une inputBox :


 
Soit la classe Tinterpreteur héritant de notre classe abstraite, qui reçoit lors de la construction d'un objet d'interpréteur la référence d'un Tmemo déjà instancié dans l'IHM afin d"afficher les résultats :

{ Tinterpreteur cas de Delphi en mode application-IHM }


Tinterpreteur = class (TinterpretAbstr)
 
protected
  
Affiche : Tmemo ;
  procedure 
Lire(symb : Vt ;var  x :integer ) ; override;
  procedure 
Ecrire(symb : Vt ; x :integer ) ; override;
 public
  constructor 
Create(sortie : TMemo) ;
end;
  procedure 
Tinterpreteur.Ecrire(symb : Vt ; x :integer ) ;
  begin
   
Affiche.Lines.Append( '>> ' + symb + ' = ' + inttostr(x))
  
end;
  
procedure 
Tinterpreteur.Lire(symb : Vt ;var  x integer ) ;
 var 
InputString :string ;
 begin
  
InputString :=  InputBox( 'Saisie des valeurs' 'Entrez : ' + symb,  '(un entier)' ) ;
  try
   
x := strtoint(InputString) ;
  except
   
x := 1 ;
  end
 end;
 
 
{----  le constructeur Tinterpreteur ----}
  
constructor  Tinterpreteur.Create(sortie TMemo) ;
  begin
   inherited 
Create ;
   
Affiche := sortie
  
end;

Terminons en livrant le code source et l'organisation de l'IHM de saisie et de traitement (exe et projet complet) :




unit  UFInterpreteur ;
 
 interface
 
 uses
  
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls,UInterprete, Buttons
;
  

  type
  
TForm1  class (TForm)
  MemoSource
TMemo ;
  
MemoSortie TMemo ;
  
EditTexte TEdit ;
  
BitBtnExec TBitBtn ;
  
ButtonEffacer TBitBtn ;
  
Label1 TLabel ;
  
Label2 TLabel ;
  
Label3 TLabel ;
  procedure 
FormCreate( Sender TObject) ;
  procedure 
MemoSourceChange( Sender TObject) ;
  procedure 
BitBtnExecClick( Sender TObject) ;
  procedure 
ButtonEffacerClick( Sender TObject) ;
 private
 
{ Déclarations privées }
 
public
 
{ Déclarations publiques }
end;

 var
  
Form1 TForm1 ;
  
interpreteur : Tinterpreteur { objet interpréteur }
  
implementation
 
 
{$R *.DFM}
 
 
procedure  TForm1.FormCreate( Sender TObject) ;
 
{ instanciation de l'objet interpréteur
  et liaison avec le Tmemo MemoSortie
 }
 
begin
  
interpreteur := Tinterpreteur.Create(MemoSortie) ;
  
EditTexte.Text :=  interpreteur.Filtrage(MemoSource.text) ;
 end;
 
 procedure 
TForm1.MemoSourceChange( Sender TObject) ;
 
{ filtrage du texte sur l'événement OnChange du TMemo
  et stockage dans le tedit edittexte 
 }
 
begin
  
EditTexte.Text :=  interpreteur.Filtrage(MemoSource.text) ;
 end;
 
 procedure 
TForm1.BitBtnExecClick( Sender TObject) ;
 
{ OnClick du TBitBtn BitBtnExec "Exécuter" }
 
begin
  
interpreteur.Lancer(EditTexte.Text) ;
 end;
 
 procedure 
TForm1.ButtonEffacerClick( Sender TObject) ;
 
{ OnClick du TBitBtn BitBtnEffacer "Effacer" }
 
begin
  
MemoSortie.Clear
 
end;
 
end.


Application console - seconde classe dérivée

Nous voulons maintenant écrire une application console utilisant la classe d'interpréteur TinterpretAbstr, selon le modèle ci-dessous :



Nous afficherons les résultats par une redéfinition de la méthode Ecrire à travers la procédure writeln.

Nous saisirons les valeurs par une redéfinition de la méthode Lire à travers la procédure readln.

Soit la nouvelle classe Tinterpreteur héritant de notre classe abstraite :

{ Tinterpreteur cas de Delphi en mode console }
 
 
Tinterpreteur = class (TinterpretAbstr)
 
protected
  procedure 
Lire(symb : Vt ;var  x :integer ) ; override;
  procedure 
Ecrire(symb : Vt ; x :integer ) ; override;
end;

implementation
 
 procedure 
Tinterpreteur.Ecrire(symb : Vt ; x :integer ) ;
 begin
  
writeln ( '>> ' ,symb, ' = ' , x)
 
end;
 
 procedure 
Tinterpreteur.Lire(symb : Vt ;var  x integer ) ;
 begin
  
write ( 'Entrez : ' ,symb, ' :' ) ;
  
readln (x)
  
try
   
readln (x) ;
  except
   
x := 1 ;
  end
 end;


A titre d'exercice simple, il vous est demandé d'écrire une unit Uinterprete contenant la classe Tinterpreteur  précédente, puis le programme principal en Delphi mode console utilisant cette classe :

program  InterpreteurLang ;
 
  
{$APPTYPE CONSOLE}
 
uses  sysutils , Uinterprete  ;
 var
  
interpreteur : Tinterpreteur { objet interpréteur }
  
begin
   
interpreteur := Tinterpreteur.Create(8) ;
   
....
   
readln (mot) ;
   
interpreteur.Lancer(mot) ;
  end.