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(Gµ). 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(Gµ). calculatrice dans laquelle l'utilisateur
peut entrer des lignes de programmes en L(Gµ) et les exécuter.
( 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 Gµ.
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
( q6, Operat ) ®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 :=
0
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
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.
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 :
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.
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 :
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 := 0 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 := 0 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] =
0
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 = 4 then
begin
symavant :=
symlu ;
if not In_TabSymb(symlu) then
begin
TabSymb[symlu]
:= adressecourante ;
adressecourante
:= adressecourante +
1
end
end;
if etat = 6 then
symprec := symlu ;
if etat = 7 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 := 1 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.