Réaliser un interpréteur en Pascal

Avec Free Pascal sous Lazarus


précédentsommairesuivant

V. Les composants du langage

Cette partie traite des composants du langage GVLOGO, depuis les variables locales jusqu'aux primitives, en passant par les variables globales, les procédures et les paquets.

V-A. Les variables locales

V-A-1. Définition

Les variables locales associent un nom à une valeur dont la visibilité est limitée. Pour GVLOGO, une variable locale est une variable dont la portée est circonscrite à la procédure en cours d'exécution.

L'intérêt de ce concept est d'éviter les confusions qui naissent lorsque l'accès à une variable est global : utilisation accidentelle d'une donnée indésirable ou modification intempestive d'une valeur utile à un autre module.

LOGO étant un langage non typé, toute valeur assignée à la variable sera acceptée : nombre, mot, liste, booléen.

V-A-2. Les variables locales dans GVLOGO

Les variables locales sont de deux ordres dans GVLOGO :

  • il peut s'agir des paramètres attendus par une procédure : ces paramètres recueillent les valeurs fournies par le programme et sont détruits à la sortie de la procédure, garantissant ainsi, entre autres, que le programme n'est pas assujetti au choix de noms de paramètres prédéterminés pour nommer les siens ;
  • ce peut être aussi une variable définie explicitement comme locale grâce à la primitive LOCALE: cette dernière doit apparaître dans le corps de la procédure et être suivie du nom de la variable (qui ne peut être un homonyme d'un des paramètres de la procédure).

V-A-3. Exemples

Voici tout d'abord un exemple de variables locales définies comme paramètres d'une procédure :

Image non disponible

Ici, les variables n et cotesont locales. Elles n'ont par conséquent de réalité qu'à l'intérieur des procédures pour desquelles elles sont définies. En particulier, les deux variables n des procédures ci-dessus sont totalement indépendantes l'une de l'autre. Une même variable ne peut apparaître deux fois sur la ligne des paramètres d'une procédure.

Voici à présent la définition explicite d'une variable locale :

Image non disponible

La variable choixn'aura d'existence que dans cette procédure. Sa valeur sera réinitialisée à chaque appel : elle sera équivalente à la chaîne vide.

En LOGO, les variables prennent deux formes : soit leur nom est précédé d'un " et il s'agit alors de travailler avec la variable, soit il est précédé de : et il s'agit de travailler avec le contenu auquel elle renvoie. Autrement dit, soit on se réfère à l'adresse mémoire de la variable, soit à son contenu. Cette distinction est aussi valable pour les variables globales.

V-A-4. Implémentation des variables locales

Le choix pouvait être fait d'incorporer les variables locales au noyau de GVLOGO. Cependant, contrairement aux variables globales dont la gestion est simple, les variables locales posent un problème particulier : du fait de la récursivité des appels de procédures et de leur éventuelle imbrication, des variables de même nom et en nombre non précisé peuvent coexister au même instant. En fait, rien n'empêche une procédure d'en appeler une autre dont les paramètres auront des noms homonymes de la procédure d'accueil. Il faut donc créer un système d'empilement capable de mémoriser toutes les valeurs pendantes et de les restituer au bon moment. On peut à cette fin se servir des piles utilisées par l'interpréteur, mais, dans le cadre de notre étude et pour une meilleure lisibilité du code, il a semblé préférable de proposer une unité réservée à part entière aux variables locales.

V-A-4-a. La classe TGVLocVars

La classe chargée de gérer les variables locales s'appelle TGVLocVars. Le principe de son fonctionnement est le suivant :

  • réservation de la place pour X variables locales ;
  • création des X variables sur deux piles (une pour les noms, une pour les valeurs, toutes deux synchronisées) ;
  • enregistrement/lecture des valeurs des variables ;
  • effacement du groupe des X variables à la sortie.

Le travail se fait donc par groupes. Voici l'interface de cette classe :

 
Sélectionnez
type
  // *** classe pour les variables locales ***
  TGVLocVars = class(TObject)
  strict private
    fCount: Integer; // compteur de variables attendues
    fWhere: Integer; // résultat dernière recherche
    fError: TGVErrors; // enregistrement d'une erreur
    fNames: TGVStringStack; // liste des noms de variables locales
    fValues: TGVStringStack; // liste des valeurs des variables locales
    fStack: TGVIntegerStack; // pile des groupes de variables locales
    fOnChange: TNotifyEvent; // gestionnaire de changement
    procedure SetCount(AValue: Integer); // compte des variables attendues
    // localisation d'une variable locale
    function Where(const Name: string): Integer;
  protected
    procedure Change; // changement notifié
  public
    // création
    constructor Create;
    // destruction
    destructor Destroy; override;
    // nettoyage
    procedure Clear;
    // réservation pour N variables locales
    procedure AddLocNumber(N: Integer);
    // réservation pour une nouvelle variable locale
    procedure AddNewLocNumber;
    // est-ce une variable locale ?
    function IsLocVar(const Name: string): Boolean;
    // nombre de variables locales
    function LocVarsCount: Integer;
    // affectation à une variable locale
    function AddLocVar(const Name, Value: string): Boolean;
    // mise à jour d'une variable locale
    function UpdateLocVar(const Name, Value: string): Boolean;
    // mise à jour directe d'une variable locale (existence préétablie)
    procedure DirectUpdateLocVar(const Value: string);
    // valeur d'une variable locale
    function ValLocVar(const Name: string; out Value: string): Boolean;
      overload;
    function ValLocVar(const Name: string): string; overload;
    // valeur directe d'une variable (existence préétablie)
    function DirectValLocVar: string;
    // destruction du dernier groupe de variables locales
    procedure DelLastGroup;
    // liste des variables locales
    function LocVarsToList: string;
    // événement si changement
    property OnChange: TNotifyEvent read fOnChange write fOnChange;
    // notification d'une erreur
    property Error: TGVErrors read fError write fError;
    // compteur de variables attendues
    property Count: Integer read fCount write SetCount default 0;
  end;

On utilise d'abord la méthode chargée d'allouer la place des variables. S'il s'agit des paramètres d'une procédure, la méthode est AddLocNumber:

 
Sélectionnez
procedure TGVLocVars.AddLocNumber(N: Integer);
// *** réservation pour N variables locales ***
begin
  if Count = 0 then // compteur à zéro ?
  begin
    fStack.Push(N); // on empile le nombre de nouvelles variables
    Count := N; // compteur à jour
  end
  else
    // [### Erreur: mauvais compteur ###]
    Error.SetError(CIE_MemLocVar, ToString);
end;

On empile le nombre de paramètres voulus et on fixe le compteur Countà ce nombre.

S'il s'agit d'une variable locale explicite, la place à réserver exige qu'on soit à l'intérieur d'une procédure : on incrémente alors le sommet de la pile des variables attendues. Afin de faciliter l'identification de cet état, même une procédure qui n'aura aucun paramètre en réservera 0 !

C'est ce travail que fait la méthode AddNewLocNumber:

 
Sélectionnez
procedure TGVLocVars.AddNewLocNumber;
// *** nouvelle place pour une variable locale ***
begin
  if fStack.IsEmpty then // pas de variables locales en cours ?
    // [### Erreur: allocation interdite ###]
    Error.SetError(CE_LocVarForbidden, P_Loc)
  else
  begin
    Count := 1; // place allouée
    fStack.Push(fStack.Pop + 1); // sommet de pile incrémenté
  end;
end;

Les autres méthodes concernent les variables proprement dites. On peut les ajouter, les modifier, les consulter et les supprimer. Un système de notifications permet de signaler tout changement, ce qui est indispensable pour l'exploitation future de l'unité par l'interpréteur.

Une fois la place réservée, on peut ajouter la ou les variables :

 
Sélectionnez
function TGVLocVars.AddLocVar(const Name, Value: string): Boolean;
// *** ajout d'une variable locale ***
var
  LW: TGVWord;
begin
  Result := False; // erreur présumée
  if Count = 0 then // plus de variables attendues ?
  begin
    // [### Erreur: mauvais compteur ###]
    Error.SetError(CIE_MemLocVar, ToString);
    Exit;
  end
  else
    Count := Count - 1; // on décompte la variable
  LW := TGVWord.Create; // mot de travail créé
  try
    LW.Text := Name; // mot normalisé
    LW.Text := LW.WithoutColon; // on supprime les deux points
    LW.Text := LW.WithoutQuote; // et les éventuels guillemets
    if LW.IsValidIdent then // le nom est-il correct ?
    begin
      // on crée la variable locale
      fNames.Push(LW.Text); // on empile le nom
      fValues.Push(Value); // puis la valeur
      Result := True;
      Change; // notifie le changement si effectif
    end
    else
      // [### Erreur: nom incorrect ###]
      Error.SetError(CE_BadName, Name);
  finally
    LW.Free; // libération du mot de travail
  end;
end;

La modification de la valeur associée à une variable locale s'opère en recherchant son nom dans la liste des variables grâce à la méthode Where. En effet, il ne faut pas chercher au-delà de la portée fixée par la réservation initiale :

 
Sélectionnez
function TGVLocVars.Where(const Name: string): Integer;
// *** localisation d'une variable locale ***
var
  Li: Integer;
  LW: TGVWord;
begin
  Result := -1; // non trouvée par défaut
  LW := TGVWord.Create; // création du mot de travail
  try
    LW.Text := Name; // mot normalisé
    LW.Text := LW.WithoutColon; // sans les deux points
    LW.Text := LW.WithoutQuote; // sans les guillemets
    // on balaie les variables locales s'il y en a
    if  (not fStack.IsEmpty) and (fStack.Peek <> 0) then
    begin
      for Li :=  1 to fStack.Peek do
        if AnsiSameText(fNames[fNames.Count - Li], LW.Text) then // trouvé ?
        begin
          fWhere := fNames.Count - Li; // emplacement trouvé et mémorisé
          Result := fWhere; // résultat
          Break; // on a fini
        end;
    end;
  finally
    LW.Free; // libération du mot de travail
  end;
end;

La modification s'appuie alors sur cette recherche grâce à la méthode DirectUpdateLocVar:

 
Sélectionnez
procedure TGVLocVars.DirectUpdateLocVar(const Value: string);
// *** mise à jour directe d'une variable locale ***
// => suppose un test préalable de son existence
begin
  fValues[fWhere] := Value; // nouvelle valeur
  Change; // changement notifié
end;

La recherche d'une valeur suit le même schéma en utilisant la méthode DirectValLocVar:

 
Sélectionnez
function TGVLocVars.DirectValLocVar: string;
// *** valeur directe d'une variable ***
// => suppose un test préalable de son existence
begin
  Result := fValues[fWhere];
end;

La libération du dernier groupe de variables se fait avec la méthode DelLastGroup:

 
Sélectionnez
procedure TGVLocVars.DelLastGroup;
// *** suppression des dernières variables locales ***
var
  Li: Integer;
begin
  if fStack.IsEmpty or (fNames.Count < fStack.Peek) then
    // [### Erreur: pas assez de variables internes ###]
    Error.SetError(CIE_LowStack, ToString)
  else
  if fStack.Peek = 0 then // pas de variables locales ?
    fStack.Pop // on dépile l'indicateur
  else
    for Li := 1 to fStack.Pop do  // on supprime les variables
    begin
      fNames.Pop; // nom dépilé
      fValues.Pop; // valeur dépilée
    end;
  Change; // changement notifié
end;

V-A-4-b. Test de l'unité GVLocVars

Le programme de test est une nouvelle fois décliné en deux versions :

  • Lazarus pour Windows 32 ;
  • Lazarus pour Linux.

Pour l'utiliser correctement, il ne faut pas oublier de réserver la place des variables que l'on souhaite ajouter : sinon, il faut relire ce qui précède…

Image non disponible

V-B. Le noyau

V-B-1. Définition

Le noyau est contenu dans l'unité GVKernel, qui est la plus volumineuse du projet.

Le noyau est le centre névralgique du programme GVLOGO. Toute définition nouvelle, toute action sur les objets de GVLOGO transitent par lui. Il concentre en son sein l'ensemble des définitions des objets (variables, procédures, listes de propriétés et paquets) ainsi que les méthodes pour les manipuler. Il fournit par ailleurs les outils de travail applicables aux primitives du langage.

Le noyau est fondé sur une exploitation systématique des listes de propriétés. Si la vitesse d'exécution peut être plus faible qu'avec un traitement particulier pour chaque type d'objet, le noyau présente l'avantage d'offrir une souplesse remarquable : il devient par exemple possible de définir depuis un programme GVLOGO des extensions du logiciel lui-même en créant de manière dynamique des procédures ou des variables !

V-B-1-a. La structure de GVKernel

L'objet central de la classe TGVLogoKernelest un champ privé nommé fWorkZone de type TGVPropList. Les listes de propriétés servent à décrire et à stocker les objets utilisés par un programme GVLOGO. Cette zone de travail est accompagnée des outils habituels de gestion des erreurs et des notifications :

 
Sélectionnez
  // *** TGVKernel ***
  TGVLogoKernel = class(TObject)
  strict private
    fError: TGVErrors; // enregistrement d'une erreur
    fErrPos: Integer; // position de l'erreur
    fProtected: Boolean; // drapeau de protection
    fWorkZone: TGVPropList; // zone de travail général
    fOnChange: TNotifyEvent; // notification des changements
    fTempList: TGVListUtils; // liste de travail

Une série de méthodes protégées permettent de gérer cet espace :

 
Sélectionnez
  protected
    // gestion des changements
    procedure Change;
    // est-ce un type d'objet donné ?
    function IsObj(const Name, Kind: string): Boolean;
    // compte d'objets d'un type donné
    function CountObj(const Kind: string): Integer;
    // ajoute un objet de type donné
    function AddObj(const Name, Kind, Value: string): TGVError;
    // supprime un objet de type donné
    function RemoveObj(const Name, Kind: string): TGVError;
    // supprime une liste d'objets de type donné
    function RemoveSomeObjs(const L, Kind: string): TGVError;
    // sauvegarde générique
    function Save(const FileName, Lst, Kind: string): TGVError;
    // chargement générique
    function Load(const FileName, Kind: string): TGVError;
    // supprime tous les objets d'un certain type
    procedure RemoveAllObjs(const Kind: string);
    // liste d'objets d'un type donné
    function ObjsToList(const Kind: string): string;

Ces méthodes sont complétées par d'autres méthodes publiques qui permettent à l'utilisateur de manipuler l'espace dans son ensemble :

 
Sélectionnez
  public
    // *** gestion ***
    // création
    constructor Create;
    // destruction
    destructor Destroy; override;
    // remise à zéro de l'espace de travail
    procedure Clear;
    // renvoie le nombre d'objets
    function Count: Integer;
    // renvoie tout l'espace de travail
    procedure Dump(Lst: TStrings);
    // l'objet existe-t-il ?
    function Exists(const Name: string): Boolean;
    // l'objet est-il protégé ?
    function IsProtected(const Name: string): Boolean;
    // renvoie les objets de l'espace de travail
    function ToList: string;
    // charge tout l'espace de travail
    function LoadAll(const FileName: string): Boolean;
    // sauvegarde tout l'espace de travail
    function SaveAll(const FileName: string): Boolean;

Viennent ensuite des groupes de méthodes qui s'appuient sur les méthodes générales pour les adapter aux particularités des variables, des procédures, des primitives, des paquets et des listes de propriétés.

V-B-1-b. Les méthodes de gestion de l'espace de travail

Les méthodes générales de gestion de l'espace de travail manipulent donc des listes de propriétés. Les objets manipulés sont en effet différenciés par une propriété choisie parmi celles proposées dans GVConsts :

 
Sélectionnez
  // *** noyau ***
  CVr = CDot + 'VAR'; // variable
  CBurried = CDot + 'BUR'; // enterré
  CInPackage = CDot + 'INP'; // dans un paquet
  CPackage = CDot + 'PKG'; // un paquet
  CProc = CDot + 'PRC'; // une procédure
  CExtLP = CDot + 'GVE'; // extension d'un espace de travail

La constante CDotétant un point, on voit que chaque type d'objet est caractérisé par une propriété dont le nom est ce point suivi de trois lettres. La valeur de la propriété est donnée par la liste qui suit ce nom.

L'exemple très simple du stockage d'une variable illustrera ce principe :

pas|[.VAR [61]]

pasest le nom de l'objet. Le signe | est le séparateur déjà rencontré lors de l'étude des listes de propriétés : il sépare le nom de la liste de sa définition. La liste [.VAR [61]]est bien constituée d'un nombre pair d'éléments : l'élément .VARnomme la propriété d'une variable dont la valeur est fournie par la sous-liste [61], en l'occurrence 61.

Tout objet suit cette structure de base.

Il est alors possible d'écrire des méthodes génériques. Voici par exemple la méthode qui compte n'importe quel type d'objet :

 
Sélectionnez
function TGVLogoKernel.CountObj(const Kind: string): Integer;
// *** compte des objets d'un type donné ***
var
  LS: string;
begin
  Result := 0; // pas d'objets
  for LS in fWorkZone do // on balaie la zone de travail
    if (LS <> EmptyStr) and // nom de la liste non vide
      // si c'est un objet du même type (CExtLP ne filtre rien)
      ((Kind = CExtLP) or fWorkZone.IsProp(LS, Kind)) then
        Inc(Result); // on incrémente le résultat
end;

Même si certaines méthodes comme Loadou Savesont bien plus longues, leur fonctionnement reste fondé sur le même principe.

Ajouter un objet à l'espace de travail fWorkZoneest un peu plus difficile puisque l'identification doit être unique et qu'il existe un système de protection des objets. Il faut donc créer l'objet s'il n'existe pas ou le mettre à jour seulement s'il n'est pas protégé et qu'il ne désigne pas un objet d'un autre type : il serait regrettable de détruire une procédure nécessaire au fonctionnement d'un programme par la création d'une vulgaire variable… D'où l'extrait suivant de la méthode AddObj:

 
Sélectionnez
  if IsPrim(Name) then // est-ce une primitive ?
  begin
    // [### Erreur: une primitive ne peut pas être modifiée ###]
    Result := CE_CantModifyPrim;
    Exit; // on sort
  end;
  LWord := TGVWord.Create; // mot de travail créé
  try
    Result := CE_None; // on suppose qu'il n'y a pas d'erreur
    if Exists(Name) then // le nom est-il pris ?
    begin
      if IsObj(Name, Kind) then // est-ce un objet de même nature ?
      begin
        if IsProtected(Name) then // protégé ?
          // [### Erreur : l'objet est protégé ###]
          Result := CE_Protected
        else
        begin
          // sinon mettre à jour
          if fWorkZone.UpDateListP(Name, Kind, Value) then
            Change // notifie le changement
          else
            // [### Erreur: mauvaise liste ###]
            Result := CE_UnknownListWord;
        end;
      end
      else
        // [### Erreur : l'objet est d'une autre nature ###]
        Result := CE_BadObj;
    end
    else
    begin // le nom n'est pas pris

Les primitives ne sont pas incluses dans le noyau. Elles ne sont pas représentées sous forme de liste de propriétés, même si une première version optait pour cette solution qui permettait facilement de changer leur nom en cours d'exécution de programme. En effet, ce genre de manipulation pouvait rendre le système instable et le ralentissait à cause de la multiplication des analyses de listes. En revanche, avec le mécanisme retenu, les primitives sont en interaction avec les objets du noyau grâce à une série de méthodes étudiées plus loin.

V-B-1-c. Le programme de test

Toujours deux versions fournies : une pour Windows et une pour Linux.

Il serait d'ailleurs temps de préciser que seuls les exécutables diffèrent, car le code est strictement le même. Le slogan de Lazarus (« Write once, compile anywhere ») tient ses promesses ! La seule difficulté rencontrée tient à la taille des contrôles : Linux a tendance à prendre plus de place pour l'affichage des étiquettes de TLabelpar exemple.

Image non disponible

V-B-2. Les variables globales

V-B-2-a. Définition

Les variables globales sont des variables dont la visibilité est sans limite au sein d'un programme. Par ailleurs, sauf destruction explicite, leur durée de vie est aussi celle de l'exécution du programme.

Leur emploi est déconseillé dans les programmes de grande ampleur, car elles sont source de confusion possible.

V-B-2-b. Les variables globales dans GVLOGO

Une variable globale en GVLOGO est définie dans le même temps qu'elle est affectée : DONNE "Ma.Variable [sa valeur] crée la variable Ma.Variable en lui affectant la valeur [sa valeur].

Pendant de la primitive DONNE, CHOSErenvoie la valeur d'une variable si elle existe : ECRIS CHOSE "Ma.Variable. Dans la pratique, on préférera utiliser les deux points qui précèdent le nom de la variable pour en récupérer la valeur : ECRIS :Ma.Variable. En cas d'inexistence de la variable, une erreur est générée.

Si une variable locale porte le même nom qu'une variable globale, la première est toujours prioritaire au sein de la procédure où elle est active.

V-B-2-c. L'implémentation des variables globales

Les variables globales font partie intégrante du noyau de GVLOGO.

Voici la partie de l'interface de TGVLogoKernelqui les concerne :

 
Sélectionnez
    // *** traitement des variables ***
    // est-ce une variable?
    function IsVar(const Name: string): Boolean;
    // nombre de variables
    function VarsCount: Integer;
    // affectation à une variable
    function AddVar(const Name, Value: string): Boolean;
    // valeur d'une variable
    function ValVar(const Name: string; out Value: string): Boolean; overload;
    function ValVar(const Name: string): string; overload;
    // destruction d'une variable
    function RemoveVar(const Name: string): Boolean;
    // destruction d'une liste de variables
    function RemoveSomeVars(const Lst: string): Boolean;
    // destruction de toutes les variables
    function RemoveAllVars: Boolean;
    // liste des variables
    function VarsToList: string;
    // chargement d'un fichier de variables
    function LoadVars(const FileName: string): Boolean;
    // sauvegarde d'un fichier de variables
    function SaveVars(const FileName, Lst: string): Boolean;
    // sauvegarde de toutes les variables
    function SaveAllVars(const FileName: string): Boolean;

La plupart de ces méthodes font simplement appel aux méthodes génériques en fournissant .VAR comme argument Kind.

V-B-3. Les procédures

V-B-3-a. Définition

Une procédure est une extension du langage obtenue en agençant des primitives, des données et d'autres procédures afin d'obtenir un résultat particulier.

V-B-3-b. Les procédures dans GVLOGO

Afin de définir une nouvelle procédure, il faut procéder ainsi dans l'éditeur :

  • commencer la définition par le mot réservé POUR;
  • faire suivre sur la même ligne ce mot du nom de la nouvelle procédure qui doit être un identificateur correct et non utilisé (sauf pour le nom d'une autre procédure non protégée) ;
  • faire suivre ce nom, si nécessaire et sur la même ligne, des noms des paramètres qu'attendra la procédure, chacun étant précédé du signe : et obéissant aussi aux règles de validité des identificateurs ;
  • ajouter autant de lignes que voulu au corps de la procédure, c'est-à-dire sa définition proprement dite ;
  • terminer la définition par le mot réservé FIN.

Après interprétation de l'éditeur, la nouvelle procédure sera intégrée au langage comme s'il s'agissait d'une primitive. Simplement, contrairement aux primitives, elle pourra être modifiée et supprimée.

Plusieurs primitives permettent de travailler avec les procédures :

.SUP.PROCSsupprime la procédure ou la liste de procédures indiquées.

Les résultats peuvent être imprévisibles suivant l'état de l'interpréteur.

CHARGE.PROCS charge le fichier de procédures fourni en paramètre.

COPIE.PROC copie la définition de la procédure (premier paramètre) vers une nouvelle procédure (second paramètre).

DEF.PROC renvoie la définition de la procédure dont le nom a été fourni en paramètre.

EDITE.PROC renvoie vers l'éditeur la définition de la procédure fournie en paramètre.

NB.PARAMS.PROC renvoie le nombre de paramètres de la procédure fournie en paramètre.

PARAMS.PROC renvoie la liste des paramètres de la procédure fournie en paramètre.

PROCEDURE? renvoie une valeur booléenne qui indique si l'objet fourni en paramètre est une procédure ou non.

PROCEDURES renvoie la liste des procédures.

SAUVE.PROCS enregistre la définition des procédures définies dans le fichier dont le nom sera celui fourni par l'unique paramètre.

V-B-3-c. L'implémentation des procédures

La structure interne d'une procédure est plus complexe que celle d'une variable. Voici par exemple la définition d'un carré :

Image non disponible

Et sa représentation interne sous forme de liste de propriétés :

Image non disponible

On reconnaît la structure de base d'une liste de propriétés avec le nom de la procédure et le nom de propriété .PRC séparé par le signe caractère |. La liste qui suit contient une liste elle-même divisée en deux sous-listes : la première contient les paramètres alors que la seconde est composée de listes qui contiennent chacune une ligne de définition de la procédure. Cette structure est capable d'accueillir d'autres propriétés : par exemple, le fait d'appartenir à un paquet.

Cette relative complexité se retrouve dans les nombreuses méthodes de GVKernelchargées de manipuler les procédures.

Voici la partie interface de TGVKernelconcernant les procédures :

 
Sélectionnez
    // *** traitement des procédures ***
    // est-ce une procédure ?
    function IsProc(const Name: string): Boolean;
    // renvoie le nombre de procédures enregistrées
    function ProcsCount: Integer;
    // renvoie la liste des procédures
    function ProcsToList: string;
    // vérifie la validité de la ligne de paramètres
    function IsValidParamsLine(Lst: string): Boolean;
    // vérifie la validité du corps de la procédure
    function IsValidBodyDef(Lst: string): Boolean;
    // enregistre une procédure
    function AddProc(const Name, Lst: string): Boolean;
    // supprime une procédure
    function RemoveProc(const Name: string): Boolean;
    // destruction d'une liste de procédures
    function RemoveSomeProcs(const Lst: string): Boolean;
    // supprime toutes les procédures
    function RemoveAllProcs: Boolean;
    // renvoie le nombre de paramètres d'une procédure
    function ParamsCount(const Name: string): Integer;
    // renvoie la liste des paramètres
    function ParamsLine(const Name: string): string; overload;
    function ParamsLine(const Name: string; out ParLine: string)
      : Boolean; overload;
    // renvoie un paramètre par son numéro
    function ParamNum(const Name: string; Num: Integer): string; overload;
    function ParamNum(const Name: string; Num: Integer; out ParNum: string)
      : Boolean; overload;
    // renvoie le nombre de lignes du corps d'une procédure
    function ProcLinesCount(const Name: string): Integer;
    // renvoie une ligne du corps de la procédure spécifiée
    function ProcLine(const Name: string; Line: Integer): string; overload;
    function ProcLine(const Name: string; Line: Integer; out PrLine: string)
      : Boolean; overload;
    // renvoie la définition d'une procédure
    function ProcListDef(const Name: string): string; overload;
    function ProcListDef(const Name: string; out PrListDef: string)
      : Boolean; overload;
    // envoie la procédure vers un éditeur
    function ProcToEdit(const Name: string; Lst: TStrings): Boolean;
    // envoie une liste de procédures vers l'éditeur
    function ProcsToEdit(const LstP: string; Lst: TStrings): Boolean;
    // envoie toutes les procédures vers l'éditeur
    function AllProcsToEdit(Lst: TStrings): Boolean;
    // envoi d'un éditeur vers des procédures
    function EditToProc(Editor: TStrings; FromLine, ToLine: Integer): Boolean;
    // charge des procédures
    function LoadProcs(const FileName: string): Boolean;
    // sauve des procédures
    function SaveProcs(const FileName, Lst: string): Boolean;
    // sauvegarde toutes les procédures
    function SaveAllProcs(const FileName: string): Boolean;
    // définition valide ?
    function IsValidDef(const LSt: string): Boolean;
    // copie d'une procédure dans une autre
    function CopyDef(const FromProc, ToProc: string): Boolean;

Afin de ne pas trop alourdir le document présent, le lecteur est renvoyé au code source largement commenté. Son attention se portera en particulier sur les méthodes chargées de communiquer avec un éditeur via une propriété de type TStringscomme EditToProcou encore celles qui sont chargées d'analyser une partie de la définition comme ProcListDef:

 
Sélectionnez
function TGVLogoKernel.ProcListDef(const Name: string; out PrListDef: string
  ): Boolean;
// *** liste de définition d'une procédure ***
var
  Lst: TGVList;
begin
  Result := False;
  if IsProc(Name) then // est-ce une procédure ?
  begin
    Lst := TGVList.Create; // liste de travail
    try
      Lst.Text := RProp(Name, CProc); // définition
      PrListDef := Lst[1]; // recherche de la définition
      Result := True; // tout est OK
    finally
      Lst.Free; // libération de la liste de travail
    end;
  end
  else
    // [### Erreur: ce n'est pas une procédure ###]
    Error.SetError(CE_UnKnownProc, Name);
end;

Nombreuses sont en effet les méthodes construites autour de ce schéma :

  • s'il s'agit d'une procédure :

    • construire une liste de travail ;
    • effectuer le travail ;
    • libérer la liste de travail ;
  • sinon signaler une erreur.

V-B-4. Les paquets

V-B-4-a. Définition

Un paquet est un regroupement d'objets GVLOGO (variables, procédures, listes de propriétés) dont le nom est un identificateur correct et unique. Les paquets permettent de simplifier certaines actions comme les sauvegardes ciblées ou de protéger des objets contre toute modification ou suppression.

V-B-4-b. Les paquets dans GVLOGO

Pour GVLOGO, les paquets sont un type d'objet dont une propriété est .PCK.

La création d'un paquet requiert la primitive PAQUET:

PAQUET "Mon.paquet

Pour un objet isolé, son placement dans un paquet s'opère grâce à la primitive VERS.PAQUET:

VERS.PAQUET "CARRE

Pour une liste d'objets, on utilisera LISTE.VERS.PAQUET.

Un paquet peut être enterré grâce à la primitive ENTERRE: à partir du moment où un paquet est enterré, les objets qui lui appartiennent ne peuvent plus être modifiés, à moins de l'emploi de la primitive DETERRE.

Voici la liste des primitives utilisables pour la gestion des paquets :

APPARTIENT.A renvoie le nom du paquet auquel appartient l'objet indiqué en paramètre ou le mot vide.

DEPAQUETTE permet de sortir l'objet dont le nom est fourni en paramètre du paquet auquel il appartient.

DETERRE rend modifiables les éléments du paquet dont le nom est fourni en paramètre.

EDITE.PAQUET envoie vers l'éditeur l'ensemble des objets appartenant au paquet indiqué par le paramètre.

ELEMENTS.PAQUET renvoie la liste des éléments du paquet dont le nom est fourni en paramètre.

ENTERRE protège le paquet dont le nom est fourni par le premier paramètre.

ENTERRE? indique par une valeur booléenne si l'objet fourni en paramètre est enterré ou non.

LISTE.VERS.PAQUET place, dans le paquet indiqué par le premier paramètre, l'ensemble des objets contenus dans la liste fournie en second paramètre.

PAQUET crée le paquet dont le nom sera celui fourni en paramètre.

PAQUET.VERS.LISTE renvoie la liste des éléments d'un paquet.

SAUVE.PAQUET sauve les objets du paquet indiqué en paramètre dans un fichier qui portera le nom de ce paquet.

SUPPRIME.PAQUET supprime le paquet indiqué en paramètre, sans supprimer les objets qu'il comprenait.

VERS.PAQUET place l'objet fourni par le second paramètre dans le paquet indiqué par le premier.

V-B-4-c. L'implémentation des paquets

La primitive PAQUETcrée la liste de propriétés voulue :

Mon.paquet|[.PKG [VRAI]]

La primitive VERS.PAQUET ajoute la propriété .INPà l'objet placé dans un paquet. Par exemple, si la procédure carre a été placée dans le paquet Mon.paquet, on aura créé :

carre|[.PRC [[[:n] [[// un carré] [polygone 4 :n]]]] .INP [Mon.paquet]]

La procédure carrepossède alors deux propriétés : .PRC qui indique qu'il s'agit d'une procédure et .INPqui marque son appartenance au paquet Mon.paquet.

Lorsqu'un paquet est enterré grâce à la primitive ENTERRE, une nouvelle propriété lui est attribuée : .BUR. Par exemple, en imaginant qu'on ait enterré le paquet Mon.paquet:

Mon.paquet|[.PKG [VRAI] .BUR [VRAI]]

Tout objet appartenant à ce paquet est à présent enterré lui aussi, donc non modifiable.

Un objet ne peut appartenir qu'à un unique paquet. Le fait de placer un objet dans un paquet alors qu'il appartenait à un autre, remplace la première affectation, sauf si le paquet est protégé. La propriété .BURest applicable à tous les objets du paquet, mais n'apparaît que dans la liste représentant ce dernier.

Voici la liste des méthodes qui se rapportent aux paquets :

 
Sélectionnez
    // *** traitement des paquets ***
    // l'objet est-il un paquet ?
    function IsPck(const Name: string): Boolean;
    // renvoie le nombre de paquets
    function PcksCount: Integer;
    // le paquet est-il enfoui ?
    function IsBurriedPck(const Name: string): Boolean;
    // enfouit un paquet
    function BurryPck(const Name: string): Boolean;
    // déterre un paquet
    function UnBurryPck(const Name: string): Boolean;
    // empaquette un objet
    function ToPck(const Name, Obj: string): Boolean;
    // empaquette une liste
    function ListToPck(const Name, Lst: string): Boolean;
    // renvoie la liste des objets d'un paquet
    function PckToList(const Name: string): string;
    // renvoie le nombre d'éléments d'un paquet
    function CountItemsPck(const Name: string): Integer;
    // renvoie la liste des paquets
    function PcksToList: string;
    // crée un paquet
    function CreatePck(const Name: string): Boolean;
    // détruit un paquet
    function RemovePck(const Name: string): Boolean;
    // sauvegarde un paquet
    function SavePck(const Name: string): Boolean;
    // paquet vers éditeur
    function PckToEdit(const Name: string; Lst: TStrings): Boolean;
    // dépaquette un objet
    function UnPackObj(const Name: string): Boolean;
    // l'objet appartient-il à un paquet ?
    function IsInPck(const Name: string): Boolean;
    // l'objet est-il enterré ?
    function IsBurried(const Name: string): Boolean;
    // à quel paquet appartient un objet ?
    function BelongsTo(const Name: string): string; overload;
    function BelongsTo(const Name: string; out Which: string): Boolean;
      overload;

Ces méthodes ne sont pas très complexes, en dehors du fait qu'il faut tenir compte de la propriété .BURde protection avant toute modification. Une seconde difficulté naît du fait qu'un objet placé dans un paquet ne connaît que le nom du paquet auquel il appartient et non son état enterré/déterré, pas plus que le paquet ne connaît la liste des objets qu'il comprend : il faut par conséquent balayer la liste des objets dans ce dernier cas et analyser les propriétés du paquet dans le premier.

V-B-5. Les primitives

V-B-5-a. Définition

Une primitive est un élément connu d'origine par GVLOGO, qui permet de réaliser une action ou de renvoyer une valeur. Les primitives ne sont pas modifiables et leur nombre ne peut pas être étendu depuis GVLOGO.

V-B-5-b. Les primitives dans GVLOGO

GVLOGO comprend plus de 250 primitives à l'heure actuelle. Leur nom est insensible à la casse. En dehors de rares primitives dont le nom est précédé d'un point, elles sont correctement adaptées (on l'espère !) au noyau, à l'affichage, aux messages d'erreur et au fonctionnement de l'interpréteur. Enfin, on ne peut pas utiliser leur identificateur pour nommer un autre objet.

V-B-5-c. L'implémentation des primitives

Les primitives sont implémentées dans un fichier spécifique Prims.inc qui est inclus dans celui de l'interpréteur. Le noyau comprend cependant un ensemble de méthodes permettant de travailler avec elles :

 
Sélectionnez
    // *** traitement des primitives ***
    // l'objet est-il une primitive ?
    function IsPrim(const Name: string): Boolean;
    // nombre de primitives
    function PrimsCount: Integer; inline;
    // numéro de primitive
    function NumPrim(const Name: string): Integer;
    // nombre de paramètres d'une primitive
    function NumParamsPrim(const Name: string): Integer;
    // liste des primitives
    function PrimsToList: string;
    // primitive par numéro
    function PrimByNum(const N: Integer): string; overload;
    function PrimByNum(const N: Integer; out PrimBNum: string)
      : Boolean; overload;

Les commentaires du code source devraient suffire à en comprendre les objectifs. Leur implémentation ne pose pas de problèmes autres que ceux étudiés jusque-là.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Gilles Vasseur. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.