DEUG MIAS 2ième année
MIO2-2
Option Informatique Approfondie
Projet Évaluateur Basic -- Notes de cours
1999-2000
Pascal Manoury
§ I -- Introduction
Un évaluateur ou un interpréteur est une programme
qui prend en entrée une suite de lignes et exécute l'action
associée àchacune d'elle. Dans le cas d'un interpréteur Basic,
les lignes traitées sont, pour la plupart, des lignes de programmes.
etc.
§ II -- Valeurs
Nous considérons deux types de valeur : les valeurs entières et
les valeurs booléennes. Pour représenter en Pascal les valeurs
manipulées par notre Basic, nous définissons (en Pascal) les
types de données suivants :
Type
basicType = (Int, Bool) ;
basicData =
Record
Case flag : basicType of
Int : (iVal : integer) ;
Bool : (bVal : boolean)
End ;
value = ^basicData ;
Le type énumérébasicType
donne la liste des types des
valeurs manipul'ees par notre langage Basic. Les valeurs entières
et booléenne de Basic sont représentées par leur équivalent en
Pascal : le type Pascal integer
et le type Pascal
boolean
. Une donnée Basic est donc soit un entier,
soit un booléen. Pour réaliser ce type union, on utilise en
Pascal, les enregistrements variants. C'est le type
basicData
. Enfin, le type value
est un pointeur, et non
pas un enregistrement, de façon àpouvoir être retournécomme résultat d'une fonction.
1. Constructeurs
Une valeur Basic peut être directement construite àpartir d'un
entier Pascal (type Pascal integer
) ou d'un booléen
Pascal (type Pascal boolean
) en utilisant l'une des deux
fonctions suivantes :
Function newInt(n : integer) : value ;
Var aux : value ;
Begin
new(aux) ;
aux^.flag := Int ;
aux^.iVal := n ;
newInt := aux ;
End ;
Function newBool(b : boolean ) : value ;
Var aux : value ;
Begin
new(aux) ;
aux^.flag := Bool ;
aux^.bVal := b ;
newBool := aux ;
End ;
Pour des besoins ultérieurs, nous définissons une valeur
particulière : << la valeur qui n'existe pas >>. Nous l'appelons
NoValue
et l'implantons par le pointeur nul, puisque les
valeurs sont des pointeurs :
Const
NoValue = Nil ;
2. Accesseurs
Les composants d'une valeur Basic sont accessibles au moyen de
l'une des trois fonctions suivantes :
Function typeOf(v : value) : basicType ;
Begin
typeOf := v^.flag ;
End ;
Function iValOf(v : value) : integer ;
Begin
iValOf := v^.iVal ;
End ;
Function bValOf(v : value) : boolean ;
Begin
bValOf := v^.bVal ;
End ;
La fonction typeOf
nous renseigne sur le type Basic de la
valeur représentée par l'argument v
. Suivant que la
valeur de retour de cette fonction est Int
ou Bool
, on
utilisera, respectivement, iValOf
ou bValOf
comme
fonction d'accès aux valeurs Pascal sous-jacentes.
Exercice (1) : Écrire une fonction qui affiche une valeur.
Solution (1) :
Procedure WriteValue(v : value) ;
Begin
If v = NoValue then
Write("NoValue")
Else
case typeOf(v) of
Int : Write(iValOf(v)) ;
Bool : Write(bValOf(v))
End
End ;
§ III -- Syntaxe abstraite
3. Les expressions
Les expression du langage Basic dénotent soit des valeurs
booléenes, soit des valeurs entières. Pour définir l'ensemble
des exressions, on se donne donc l'ensemble des valeurs Basic, un
ensemble de variables et un ensemble d'opérateurs. On ne fait pas de
différence, pour l'instant, ensemble opérateurs unaires et
opérateurs binaires ; entre opérateurs arithmétiques et
opérateurs booléens. On définit dons récursivement l'ensemble
des expressions par les trois clauses suivantes :
- Les valeurs sont des expressions (on les appelles alors
constantes).
- Les variables sont des expressions.
- Si · est un opérateur et si e1 et e2 sont des
expressions, alors · e1 et e1· e2 sont des
expressions (on les appelle, respectivement, expression uniar
et expression binaire).
En Pascal, l'ensemble des constantes est représentépar
l'ensemble des valeurs du type Pascal value
, les variables
sont représentées par des chaînes de caractères et l'ensemble
des opérateurs par les type énuméréexpOp
. Pour
représenter les arbres d'expressions, on utilise la structure
suivante :
- une expression (type Pascal
exp
) est une pointeur sur
un noeud ;
- un noeud est un enregistrement variant (type Pascal
expNode
) qui contient la représentation d'une constante,
d'une variable, d'une expression unaire ou d'une expression binaire ;
- les différentes catégories d'expression sont discriminées
par un champ de type
expCase
.
Voici les déclaration Pascal correspondantes :
Type
expOp = (Add, Mul, Sub, Dvd, Neg, Conj, Disj, Eq, Lt, Gt) ;
expCase = (Evar, Cste, UnOp, BinOp) ;
exp = ^expNode ;
expNode =
Record
Case flag : expCase of
Evar : (name : String) ;
Cste : (val : value) ;
UnOp : (op1 : expOp ;
arg : exp);
BinOp : (op2 : expOp ;
arg1 : exp ;
arg2 : exp) ;
End ;
(3.a) Constructeurs
Pour chacune des catégories d'expression possibles, on définit une
fonction de construction de l'arbre correspondant. Pour pouvoir
construire une constante directement àpartir d'un entier ou d'un
booléen Pascal , on donne deux constructeurs pour les
constantes. On a donc, au total, cinq constructeur d'expression dont
la définition Pascal suit :
Function newEvar(x : String) : exp ;
Var aux : exp ;
Begin
new(aux) ;
aux^.flag := Evar ;
aux^.name := x ;
newEvar := aux ;
End ;
Function newCsteA(n : integer) : exp ;
Var aux : exp ;
Begin
new(aux) ;
aux^.flag := Cste ;
aux^.val := newInt(n) ;
newCsteA := aux ;
End ;
Function newCsteB(b : boolean ) : exp ;
Var aux : exp ;
Begin
new(aux) ;
aux^.flag := Cste ;
aux^.val := newBool(b) ;
newCsteB := aux ;
End ;
Function newUnOp(op : expOp; e : exp) : exp ;
Var aux : exp ;
Begin
new(aux) ;
aux^.flag := UnOp ;
aux^.op1 := op ;
aux^.arg := e ;
newUnOp := aux ;
End ;
Function newBinOp(op : expOp; e1 : exp; e2 : exp) : exp ;
Var aux : exp ;
Begin
new(aux) ;
aux^.flag := BinOp ;
aux^.op2 := op ;
aux^.arg1 := e1 ;
aux^.arg2 := e2 ;
newBinOp := aux ;
End ;
(3.b) Accesseurs
Les fonctions d'accès aux composants d'une expression envisagent tous
les cas. En particulier, on a une fonction d'accés àl'argument
d'une expression unaire et deux fonctions d'accés aux argument d'une
expression binaire. L'utilisation des fonctions d'accés est correcte
qu'une fois que l'on a indentifier àquelle catégorie d'expression
on a affaire. Pour ce, on utilise la fonction d'accés
expCaseOf
.
Function expCaseOf (e : exp) : expCase ;
Begin
expCaseOf := e^.flag ;
End ;
Function nameOf (e : exp) : String ;
Begin
nameOf := e^.name ;
End ;
Function valOf (e : exp) : value ;
Begin
valOf := e^.val ;
End ;
Function opOf (e : exp) : expOp ;
Begin
Case expCaseOf(e) of
UnOp : opOf := e^.op1 ;
BinOp : opOf := e^.op2
End
End ;
Function argOf (e : exp) : exp ;
Begin
argOf := e^.arg ;
End ;
Function arg1Of (e : exp) : exp ;
Begin
arg1Of := e^.arg1 ;
End ;
Function arg2Of (e : exp) : exp ;
Begin
arg2Of := e^.arg2 ;
End ;
Exercice (2) : Écrire une procédure WriteExp
qui affiche la notation
infixe complètement parenthésée de l'arbre de syntaxe abstraite
d'une expression.
Solution (2) :
Procedure WriteOp(op : expOp) ;
Begin
Case op of
Add : Write('+') ;
Mul : Write('*') ;
Sub : Write('-') ;
Dvd : Write('/') ;
Neg : Write('~ ') ;
Conj : Write(' && ') ;
Disj : Write(' || ') ;
Eq : Write(' = ') ;
Lt : Write(' < ') ;
Gt : Write(' > ')
End ;
End ;
Procedure WriteExp(e : exp) ;
Begin
case expCaseOf(e) of
Evar : Write(nameOf(e)) ;
Cste : WriteValue(valOf(e)) ;
UnOp : Begin
Write('(');
WriteOp(opOf(e));
WriteExp(argOf(e));
Write(')')
End ;
BinOp : Begin
Write('(');
WriteExp(arg1Of(e));
WriteOp(opOf(e));
WriteExp(arg2Of(e));
Write(')')
End ;
End ;
End ;
4. Les instructions
Notre langage comprend sept instructions décrites dans le tableau
suivant :
Syntaxe |
Sémantique |
ident := expr |
affectation : la variable ident reçoit la valeur de
l'expression expr |
GOTO num |
saut inconditionnel : l'exécution se poursuit
àla ligne numéro num |
IF expr GOTO num |
saut conditionnel : si la
valeur de l'expression expr est TRUE alors l'exécution
se poursuit àla ligne numéro num sinon elle se poursuit àla ligne suivante |
INPUT ident |
entrée : la variable ident
reçoit la valeur lue au clavier |
PRINT expr |
sortie : affiche àl'écran la valeur de
expr |
PRINT string |
sortie : affiche àl'écran la chaîne
de caractères string |
PRINTLN |
sortie : passe àla ligne suivante de l'écran |
END |
arrêt : termine l'exécutuion |
On utilise, ici encore, des enregistrements àchamp variant pour
définir le type des instructions. On se donnera, dans la syntaxe
abstraite, une seule instruction de sortie dont l'arguement sera une
expression, une chaîne de caractères ou un saut de ligne.
Types
instType = (STO, JMP, CJMP, IN, OUT, EXIT) ;
outType = (OutString, OutExp, OutLn) ;
outContent =
Record
Case outCase : outType of
OutString : ( s : String ) ;
OutExp : ( e : exp ) ;
OutLn : ()
End
End ;
inst =
Record
Case instCase : instType of
STO : ( stoName1 : String; stoExp : exp ) ;
JMP : ( jmpLine : Integer ) ;
CJMP : ( cjmpExp : exp ; cjmpLine : Integer ) ;
IN : ( inName : String ) ;
OUT : ( outArg : outContent ) ;
EXIT : ()
End
End ;
Contrairement àl'habitude, nous ne donnons pas de constructeur
d'instruction. En effet, la représentation abstraite d'une
instruction est un enregistrement et non un pointeur sur un
enregistrement. On ne peut donc pas créer dynamiquement de valeur de
ce type. En revanche, nous verrons comment définir une procédure
d'initialisation d'instruction.
5. Le programme
Un programme est une suite d'instructions précédée chacune d'un
numéro (dit numéro de ligne). Nous représentons cette
suite par un tableau dont la taille est limitée par la constante
MAXPROGLEN. Le programme lui-même est contenu dans la variable
globale progMem et la variable globale progLen contiendra
l'indice de la dernière instruction du programme. Enfin, le
booléen err__OutOfProgMem servira d'indicateur de
débordement de la mémoire allouée aux programmes.
Const MAXPROGLEN ;
Types
progLine =
Record
num : Integer ;
lineInst : inst
End ;
progTab = array [1..MAXPROGLEN] of progLine ;
Var
progMem : progTab ;
progLen : Integer ;
err__OutOfProgMem : Boolean ;
On fait l'hypothèse que les lignes d'un programmes sont rangées
consécutivement (i.e. sans trou) dans le tableau progMem
àpartir de l'indice 1 et qu'elles sont triées par ordre croissant
de numéro. Ainsi progLen donne àla fois le dernier indice
de ligne d'un programme et la longueur du programme (i.e. son
nombre de lignes).
(5.c) Pseudo-constructeur
Pour maintenir la validitéde cette hypothèse, on se donne une
procédure chargée d'inserer correctement une nouvelle ligne dans
la table progMem et de tenir àjour la valeur de progLen.
Procedure addLine(n : Integer; x : inst) ;
Begin
i := progLen ;
While (i > 1) and (progMem[i].num > n) do i := i-1 ;
If progMem[i].num = n then progMem[i].inst := x
Else Begin
err__OutOfProgMem := (progLen < MAXPROGLEN) ;
If not err__OutOfProgMem then Begin
If progMem[i].num < n then i := i+1 ;
progLen := progLen+1 ;
For j:=progLen downto (i-1) do progMem[j] := progMem[j-1] ;
progMem[i].num := n ;
progMem[progLen].inst := x
End ;
End ;
End ;
Cette procédure a deux particularités :
- si la table est plein, un appel àaddLine ne change rien
àla table mais positionne l'indicateur d'erreur err__OutOfProgMem àla valeur TRUE ;
- si le numéro de ligne àrajouter (n) est déjàprésent dans la table, l'ancienne instrcution est remplacée par la
nouvelle (x).
(5.d) Assemblage
La procédure d'insertion des lignes dans un programme peut faire
apparaître une disparitéentre le numéro de ligne (champ num des enregistrements de type progLine) et l'indice de
l'instruction associée dans le tableau. Pour ne pas avoir àretrouver la correspondance entre un numéro de ligne et l'indice
correspondant du tableau lors de l'exécution des instruction de
saut, il faut procéder àun réajustement des arguments de ces
instructions avant de lancer l'exécution. C'est l'objet de la
de la fonction et de la procédure suivantes :
Var err__IndexNotFound : Boolean ;
Function findIndex(n : Integer) : Integer ;
Var i : Integer ; notFound : Boolean ;
Begin
i := 0 ;
notFound := true ;
While (i < progLen) and notFound do
Begin
i := i+1 ;
notFound := progMem[i].num <> n
End ;
err__IndexNotFound := notFound ;
If err__IndexNotFound then findIndex := n else findIndex := i ;
End ;
Var err_assProg : Boolean ;
Procedure assProg() ;
Var i : Integer ;
Begin
i := 0 ;
While (i < progLen) and (not err_AssProg) do
Begin
i := i+1 ;
With progMem[i].inst do
Case instCase of
JMP : jmpLine := findIndex(jmpLine) ;
CJMP : cjmpLine := findIndex(cjmpLine)
End ;
err_AssProg := err__IndexNotFound ;
End ;
If err_AssProg then
Writeln('line ', progMem[i].num, ': GOTO error') ;
End ;
Notez que chacune d'elle est suceptible de positionner àTRUE
un indicateur d'erreur. Lorsque la fonction findIndex échoue,
elle renvoit comme valeur le numéro de ligne qui lui a étépasséen argument.
Exercice (3) : Àtoutes fins utile, écrire une fonction d'initialisation de
la table progMem pour que tous les numéro de ligne valent 0 et toutes les instructions représentent l'instruction d'arrêt.
Exercice (4) : Écrire pour chacune des instructions une procédure d'ajout
d'une ligne de programme. On utilisera les profils suivants :
Procedure addSto(n : Integer; x : String; e : expr) ;
Procedure addJmp(n : Integer; j : Integer) ;
Procedure addCjmp(n : Integer; e : expr; j : Integer) ;
Procedure addIn(n : Integer; x : String) ;
Procedure addOutExp(n : Integer; e : expr) ;
Procedure addOutStr(n : Integer; x : String) ;
Procedure addOutLn(n : Integer) ;
Procedure addExit(n : Integer) ;
Exercice (5) : Écrire une procédure d'affichage du programme contenu dans la
table progMem (pensez que l'on a pu modifier le contenu
originale des instruction par la procédure assProg).
§ IV -- Environnement
Un environnement est une structure qui permet d'associer une valeur
àune variable, plus précisément, au nom d'une variable.
Pour représenter l'environnement d'évaluation de nos programmes
Basic, on utilise une structure de liste chaînée dont
chaque maillon est une couple contenant un nom et une valeur. Ces
couples sont implantés par les enregistrements du type Pascal
envItem
oùles noms sont des chaînes de caractères et les
valeurs de objets de type value
. Les listes contenant
l'environnement sont implantées par le type Pascal
envList
. Voici les déclarations correspondantes :
Type
envItem =
Record
name : String ;
val : value ;
End ;
envList = ^envCell ;
envCell =
Record
hd : envItem ;
tl : envList
End ;
6. Constructeurs
Nous avons vu, en introduction, comment manipuler des structures de
liste en général. On pourrait donc s'attendre ici àretrouver
un ensemble de constructeurs analogue : un constructeur pour créer
une liste vide et un second pour rajouter un élément (ici, une
nouvelle liaison) en tête de liste.
En fait, nous allons faire ici entorse ànotre principe de
constructeurs fonctionnels, pour introduire, dans la catégorie des
constructeurs d'environnement, une procédure. En effet, au
delàde l'évaluation des expressions, un environnement est aussi
utilisépour l'interprétation de l'ensemble des instructions de
notre Basic. En particulier, il est << affecté >> par
l'instruction d'affectation qui modifie la valeur associée àun
nom de variable. Nous voulons que notre structure d'environnement
reflète ce comportement. C'est pourquoi l'ajout, dans un
environnement e
, d'une nouvelle liaison entre un nom x
et une valeur v
aura deux effets possibles :
- Soit aucune liaison concernant
x
n'est présente dans
e
et alors on crée une nouvelle liaison (que l'on
insèrera en fin de liste).
- Soit il existe déjàdans
e
une liaison concernant
x
et alors, on se contente de modifier cette liaison en
remplaçant l'ancienne valeur liée àx
par
v
.
Cette fonctionnalitéest implantée par la procédure
setValue
qui utilise la fonction locale
newBinding
. On créera un environnement vierge par la fonction
newEnv
. Voici le code des constructeurs d'environnement :
Function newEnv() : envList ;
Begin
newEnv := Nil ;
End ;
Procedure setValue(x : String; v : value; var e : envList) ;
Function newBinding(x : String; v : value) : envList ;
Var aux : envList ;
Begin
new(aux) ;
aux^.hd.name := x ;
aux^.hd.val := v ;
aux^.tl := Nil ;
newBinding := aux
End ;
Var aux : envList ;
Begin
If e = Nil then e := newBinding(x, v)
else if e^.hd.name = x then e^.hd.val := v
else setValue(x, v, e^.tl)
End ;
Remarque (1) : si l'on a dérogéau principe de fonctionnalitéen
introduisant une procédure comme constructeur, on n'a pas dérogéau principe d'abstraction de type car on peut toujours ne donner au
programmeur que ces deux seuls constructeurs et il pourra construire
tous les environnements souhaités sans avoir àconnaître le
détail d'utilisation des types Pascal sous-jacents.
7. Accesseurs et reconnaisseur
Ici encore, l'accés aux données contenues dans un environnement est
guidépar l'usage que nous en ferons. Et l'usage essentiel d'un
environnement est d'y rechercher la valeur associée àun nom de
variable. C'est le rôle de la fonction getValue
. Notez que
cette fonction peut avoir comme valeur de retour la constante
NoValue
. Comme il sera aussi utile de pouvoir explorer les
environnements pour ce qu'ils sont (des listes), nous donnons
également les fonctions d'accès aux composants de la tête de
liste (fonctions hdName
et hdVal
) ainsi que la fonction
d'accés àla suite d'un environnement. Pour utiliser correctement
ces trois dernières fonctions, il faut savoir si l'environnnement
contient quelque chose ou non, c'est pourquoi, nous donnons enfin le
test isNewEnv
. Voici le code Pascal correspondant àtout
cela :
Function isNewEnv(e : envList) : boolean ;
Begin
isNewEnv := (e = Nil) ;
End ;
Function hdName(e : envList) : String ;
Begin
hdName := e^.hd.name ;
End ;
Function hdVal(e : envList) : value ;
Begin
hdVal := e^.hd.val ;
End ;
Function tlEnv(e : envList) : envList ;
Begin
tlEnv := e^.tl ;
End ;
Function getValue(x : String; e : envList) : value ;
Begin
If isNewEnv(e) then getValue := NoValue
else if hdName(e) = x then getValue := hdVal(e)
else getValue := getValue(x, tlEnv(e))
End ;
Exercice (6) : Écrire une fonction WriteEnv
qui affiche le contenu d'un
environnement.
Solution (6) :
Procedure WriteEnv(e : envList) ;
Var aux : value ;
Begin
if not isNewEnv(e) then
Begin
Write('(') ;
Write(hdName(e)) ;
Write(',') ;
WriteValue(hdVal(e)) ;
Write(')') ;
WriteEnv(tlEnv(e)) ;
End
End ;
§ V -- Évaluation
Le principe général d'évaluation est de parcourir l'arbre de
syntaxe abstraite en associant àchaque étape de ce parcours
(i.e. chaque noeud de l'arbre) le code Pascal correspondant àl'instruction oùl'opération Basic représentée.
8. Expressions
L'évaluation d'une expression a pour résultat une valeur Basic
(type Pascal value
) qui peut représenter soit un entier,
soit un booléen. Et la façon dont nous avons défini les
expressions n'interdit pas le mélange des genres : l'addition de
0
et TRUE
, ou la négation de 4+x
. Si l'on ne
veut pas que le processus d'évaluation s'interrompe brutalement
lorsque nous tentons, par exemple, d'additionner une entier et un
booléen, il faut vérifier la cohérence des valeurs obtenues
avant de leur appliquer une opération. Prendre une telle
précaution s'appelle : vérification dynamique de type.
Pour procéder àune telle vérification, nous nous donnons deux
fonctions, checkedInt
et checkedBool
, qui seront
chargées, respectivement, de vérifier si une valeur (de type
Pascal value
) représente un entier ou un booléen. Si
telle est bien le cas, elles affecterons, respectivement, une variable
de type (Pascal) integer
ou boolean
donnant la valeur
représentée. Voici le code de ces deux fonctions :
Function checkedInt(v : value; var n : integer) : boolean ;
Begin
If v = NoValue then
checkedInt := false
Else
Begin
n := iValOf(v) ;
checkedInt := (typeOf(v) = Int)
End
End ;
Function checkedBool(v : value; var b : boolean) : boolean ;
Begin
If v = NoValue then
checkedBool := false
Else
Begin
b := bValOf(v) ;
checkedBool := (typeOf(v) = Bool)
End
End ;
Remarque (2) : notez comment l'on a procédépour écrire une fonction qui
retourne deux valeurs : d'une part l'information sur la
cohérence du type (la valeur booléenne de retour des fonctions) ;
d'autre part la valeur attendue pour la suite de l'évaluation que
l'on récupère par effet de bord sur le dernier argument.
Ces deux fonctions sont utilisées par les fonctions applyUnOp
et applyBinOp
qui effectuent l'application d'un opérateur àdeux valeur (Basic) déjàcalculées :
Function applyUnOp(op : expOp; v : value) : value ;
Var
b : boolean ;
res : value ;
Begin
res := NoValue ;
If checkedBool(v, b) then
Case op of
Neg : res := newBool(not b)
End ;
applyUnOp := res
End ;
Function applyBinOp(op : expOp; v1 : value; v2 : value) : value ;
Var
n1, n2 : integer ;
b1, b2 : boolean ;
res : value ;
Begin
res := NoValue ;
If checkedInt(v1, n1) and checkedInt(v2, n2) then
Case op of
Add : res := newInt(n1 + n2) ;
Mul : res := newInt(n1 * n2) ;
Sub : res := newInt(n1 - n2) ;
Dvd : res := newInt(n1 div n2) ;
Eq : res := newBool(n1 = n2) ;
Lt : res := newBool(n1 < n2) ;
Gt : res := newBool(n1 > n2)
End
Else If checkedBool(v1, b1) and checkedBool(v2, b2) then
Case op of
Conj : res := newBool(b1 and b2) ;
Disj : res := newBool(b1 or b2)
End ;
applyBinOp := res ;
End ;
La fonction proprement dite d'évaluation d'une expression met en
oeuvre notre principe de parcours récursif de l'arbre
d'expression. Elle est paramétrée par un environnement
d'évaluation :
Function evalExp(e : exp; env : envList) : value ;
Begin
Case expCaseOf(e) of
Evar : evalExp := getValue(nameOf(e), env) ;
Cste : evalExp := valOf(e) ;
UnOp : evalExp := applyUnOp(opOf(e), evalExp(argOf(e), env)) ;
BinOp : evalExp := applyBinOp(opOf(e),
evalExp(arg1Of(e), env),
evalExp(arg2Of(e), env))
End
End ;
Notez que la valeur de retour de la fonction evalExp
peut
être la valeur particulière NoValue
.
9. Programmes
Pour exécuter un programme Basic, il faut disposer de deux
ressources globales : un environnement pour l'évaluation des
expressions et un compteur ordinal qui indique le numéro de la
ligne àexécuter. On se donne pour cela deux variables globales :
Var
progEnv : envList ;
progCo : Integer ;
Le principe de l'exécution d'un programme est simple. Pour chaque
cas d'instruction, il convient d'appeler la fonction d'évaluation
des expressions lorsqu'une valeur est requise, la procédure de mise
àjour de l'environnement lorsqu'une telle opération est requise,
et, sauf, dans le cas de l'instruction d'arrêt, ajuster la valeur du
compteur ordinal. Ceci, fait, on rappelle récursivement la
procédure d'exécution des programmes.
En fait, le point le plus délicat àréaliser est l'instruction
INPUT d'entrée d'une valeur. On veut garantir que la valeur
saisie est bien un entier. Il faut, pour cela contrôler la chaîne
de caractère saisie par l'utilisateur et la convertir en valeur
entière. Nous reviendrons sur ce point lorsque nous aborderons
l'analyse lexicale. Donnons nous en attendant une fonction simpliste
qui retourne un entier positif ou nul en redemandant la saisie tant
que l'on obtient pas une chaîne convertible :
Var err__IntOfString : Boolean ;
Function intOfString(s : String) : Integer ;
Var i, r : Integer ; neg : Boolean ;
Begin
r := 0 ;
For i := 1 to length(s) do
Begin
err__IntOfString := err__IntOfString or (not (c in ['0'..'9'])) ;
r := r*10 + (ord(c) - odr('0')) ;
End ;
intOfString := res ;
End ;
Function execIn() : Integer ;
Var
res : Integer ;
buf : String ;
Begin
Repeat
Write('? ') ;
Readln(buf) ;
err__IntOfString := false ;
res := intOfString(buf) ;
Until not err__IntOfString ;
End ;
Pour alléger l'écriture, on isole également dans une procédure
l'exécution de l'instruction de sortie OUT :
Procedure execOut(x : outContent) ;
Begin
With x do
Case outCase of
OutString : Write(s) ;
OutExp : writeValue(evalExp(e, progEnv)) ;
OutLn : Writeln
End ;
End ;
La procédure d'exécution d'un programme s'écrit alors :
Procedure execProg () ;
Var
b : Boolean ;
i : Integer ;
v : value ;
Begin
With progMem[progCo] do
Begin
Case lineInst of
STO : Begin
v := evalExp(stoExp, progEnv) ;
If v <> NoValue then Begin
setValue(stoName, v, progEnv) ;
progCo := progCo+1 ;
execProg()
End
Else Writeln('Unevaluable expression line ', num)
End ;
JMP : Begin
progCo := jmpLine ;
execProg()
End ;
CJMP : Begin
If checkedBool(evalExp(cjmpExp, progEnv), b) then
Begin
If b then progCo := cjmpLine
Else progCo := progCo+1 ;
execProg()
End
Else
Writeln('Type error line ', num) ;
End ;
IN : Begin
setValue(inName, newInt(execIn()), progEnv) ;
progCo := progCo+1 ;
execProg()
End ;
OUT : Begin
execOut(outArg, progEnv) ;
progCo := progCo+1 ;
execProg()
End ;
End ;
End ;
End;
This document was translated from LATEX by HEVEA.