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 :
  1. Les valeurs sont des expressions (on les appelles alors constantes).
  2. Les variables sont des expressions.
  3. 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 : 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 :



(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 :
  1. 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).
  2. 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.