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 :
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 :
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 :
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:
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:
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 :
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 :
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:
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:
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:
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▲
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 :
// *** 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 :
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 :
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 :
// *** 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 :
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:
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.
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 :
// *** 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é :
Et sa représentation interne sous forme de liste de propriétés :
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 :
// *** 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:
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 :
// *** 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 :
// *** 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à .