I. Le projet▲
II. Les objets de GVLOGO▲
III. Récréation : EasyTurtle (logiciel de dessin)▲
IV. Les outils de programmation▲
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 cote sont 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 choix n'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 TGVLogoKernel est 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]]
pas est 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 .VAR nomme 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 Load ou Save sont bien plus longues, leur fonctionnement reste fondé sur le même principe.
Ajouter un objet à l'espace de travail fWorkZone est 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 TLabel par exemple.
V-B-2. Les variables globales▲
V-B-2-a. Définition▲
Les variables globales sont des variables dont la visibilité est sans limites 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 sources de confusions possibles.
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, CHOSE renvoie 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 TGVLogoKernel qui 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, si nécessaire et sur la même ligne, ce nom 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.PROCS supprime 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 GVKernel chargées de manipuler les procédures.
Voici la partie interface de TGVKernel concernant 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
;
//
envoie
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 TStrings commeEditToProc ou 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 PAQUET cré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 carre possède alors deux propriétés : .PRC qui indique qu'il s'agit d'une procédure et .INP qui 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é .BUR est 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
d'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é .BUR de 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'erreurs 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 l'objectif. Leur implémentation ne pose pas de problèmes autres que ceux étudiés jusque-là.