Réaliser un interpréteur en Pascal

Avec Free Pascal sous Lazarus

Auteur

Gilles Vasseur

Ce tutoriel vous permet d'apprendre les bases de la programmation avec un dérivé de LOGO ou de vous perfectionner en bâtissant l'interpréteur de A à Z…

Commentez

- Titre : Réaliser un interpréteur en Pascal

- Auteur : Gilles Vasseur

- Parution : 15 mars 2015

- Public visé :

- Licence : 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.

Sommaire

- I. Le projet

- II. Les objets de GVLOGO

- II-A. Les fichiers communs

- II-A-1. La centralisation des données

- II-A-2. GVDefines.inc

- II-A-3. GVConsts

- II-A-4. GVErrConsts

- II-A-5. GVPrimConsts

- II-B. Le traitement des erreurs

- II-B-1. L'unité GVErrors

- II-B-2. L'utilisation de l'unité

- II-B-3. Le programme d'exemple

- II-C. Les mots

- II-C-1. Définitions

- II-C-2. Exemples de mots

- II-C-2-a. Mots simples

- II-C-2-b. Mots avec caractère d'échappement

- II-C-2-c. Autres caractères particuliers

- II-C-3. Opérations sur les mots

- II-C-3-a. Fabriquer des mots

- II-C-3-b. Modifier des mots

- II-C-3-c. Extraire depuis des mots

- II-C-3-d. Tester des mots

- II-C-4. Implémentation des mots

- II-C-4-a. L'objet TGVString

- II-C-4-b. La classe TGVNumber

- II-C-4-c. La classe TGVWord

- II-C-4-d. Test de l'unité GVWords

- II-D. Les listes

- II-D-1. Définitions

- II-D-2. Exemples de listes

- II-D-2-a. Listes simples

- II-D-2-b. Listes imbriquées

- II-D-2-c. Listes fautives

- II-D-3. Opérations sur les listes

- II-D-3-a. Fabriquer des listes

- II-D-3-b. Modifier des listes

- II-D-3-c. Extraire de listes

- II-D-3-d. Tester des listes

- II-D-4. Implémentation des listes

- II-D-4-a. Constantes

- II-D-4-b. La classe TGVListUtils

- II-D-4-c. La classe TGVList

- II-D-4-d. Test de l'unité TGVLists

- II-E. Les listes de propriétés

- II-E-1. Définitions

- II-E-2. Exemples de listes de propriétés

- II-E-3. Opérations sur les listes de propriétés

- II-E-4. Implémentation des listes de propriétés

- II-E-4-a. Constantes

- II-E-4-b. La classe TGVPROPLISTENUMERATOR

- II-E-4-c. La classe TGVPROPLIST

- II-E-4-d. Test de l'unité TGVPROPLists

- II-F. La tortue graphique

- II-F-1. Présentation

- II-F-2. Opérations avec la tortue

- II-F-2-a. Champ de la tortue

- II-F-2-b. Déplacements de la tortue

- II-F-2-c. Cap de la tortue

- II-F-2-d. État de la tortue

- II-F-2-e. Le crayon

- II-F-2-f. Formes prédéfinies

- II-F-2-g. Texte avec la tortue

- II-F-3. Implémentation de la tortue

- II-F-3-a. Constantes

- II-F-3-b. Un peu de mathématiques

- II-F-3-c. Se déplacer sur l'écran

- II-F-3-d. Les événements

- II-F-3-e. La classe TGVTURTLE

- II-F-3-f. Test de l'unité GVTurtles

I - Le projet

II - Les objets de GVLOGO

Cette partie décrit les objets fondamentaux manipulés par GVLOGO. Après un survol des fichiers communs seront successivement abordés le traitement des erreurs, les mots, les listes, les listes de propriétés et la tortue graphique.

II-A - Les fichiers communs

II-A-1 - La centralisation des données

Les avantages d'une centralisation de certaines données sont de deux types :

- il est possible modifier un élément à partir d'un seul point et donc inutile de parcourir tous les fichiers sources éparpillés dans plusieurs unités : on imagine le gain de temps dans la situation où l'on devrait changer un contenu utilisé plusieurs dizaines de fois ;

- le risque d'erreurs difficiles à déceler est moindre puisqu'il n'y a pas à recopier un élément pour l'utiliser ailleurs : coder en dur une chaîne de caractères, par exemple, expose à une copie erronée qui donnera des résultats inattendus lors de comparaisons.

La centralisation des données s'organise autour de quatre fichiers : GVDefines.inc, GVConsts, GVErrConsts et GVPrimConsts. Le projet final GVLOGO utilise par ailleurs un fichier qui lui est propre et sur lequel nous reviendrons en temps utile : GVLOGOConsts.

II-A-2 - GVDefines.inc

Le fichier GVDefines.inc contient les options de compilation. Il permettra par exemple d'optimiser la compilation lors d'une transposition du logiciel vers Delphi. Son contenu s'organise par conséquent autour de directives.

Actuellement, il ne prend en compte que la compilation avec Lazarus, provoquant une erreur si l'on tente d'activer Delphi :

// Lazarus (défini par défaut)
{$DEFINE CompLazarus }

// Delphi prévu... (non défini par défaut)
{.$DEFINE CompDelphi}

// Lazarus : options de compilation
{$IFDEF CompLazarus}
{$UNDEF CompDelphi}
{$MODE objfpc}{$H+}
{$ENDIF}

// Delphi : options de compilation
{$IFDEF CompDelphi}
Implémentation prévue...
{$ENDIF}

// option de débogage (non défini par défaut)
{.$DEFINE Debug}

II-A-3 - GVConsts

L'unité GVConsts servira pour toutes les unités du projet. Elle contient en effet les constantes, types et chaînes de caractères partagés par l'ensemble du projet.

Dans un premier temps, on notera les constantes les plus fréquemment utilisées dans les différentes unités :

const
// *** listes ***
CBlank = ' '; // espace
CTab = #9; // tabulation
CSlash = '/'; // slash
CBeginList = '['; // début de liste
CEndList = ']'; // fin de liste
CBeginPar = '('; // début d'expression
CEndPar = ')'; // fin d'expression
CEmptyList = CBeginList + CEndList; // liste vide

// *** séparateurs ***
CBlanks = [CTab, CBlank];
CBraces = [CBeginList, CEndList, CBeginPar, CEndPar];
CSeparators = CBlanks + CBraces;

// *** caractères ***
CLink = '$'; // caractère de lien
CUnderline = '_'; // soulignement
CDot = '.'; // point
CAsk = '?'; // point d'interrogation
CQuote = '"'; // guillemets
CColon = ':'; // deux points (début de variable)
CComma = ','; // virgule
CExclamation = '!';
CPlus = '+'; // addition
CMinus = '-'; // soustraction
CMul = '*'; // multiplication
CDiv = '/'; // division
CPower = '^'; // puisssance
CGreater = '>'; // plus grand
CLower = '<'; // plus petit
CEqual = '='; // égal
CNotEqual = '<>'; // différent
CNotEqual2 = '!='; // différent (2)
CNot = '!'; // négation
COrB = '|'; // ou binaire
CAndB = '&'; // et binaire
CGreaterOrEqual = '>='; // plus grand ou égal
CLowerOrEqual = '<='; // plus petit ou égal

// *** caractères spéciaux ***
CSpecialChar = [CTab, CBlank, CEqual, CPlus, CMinus, CMul, CDiv, CBeginPar,
CEndPar, CPower, CGreater, CLower];
CNonPrintable = [#0..#32];

// *** ensembles de caractères courants ***
CLowAlpha = ['a' .. 'z']; // caractères alphabétiques en minuscules
CHighAlpha = ['A' .. 'Z']; // caractères alphabétiques en majuscules
CAlpha = CLowAlpha + CHighAlpha; // caractères alphabétiques
// caractères alphabétiques autorisés pour un identificateur
CAlphaPlus = CAlpha + [CUnderline, CColon, CDot];
CDigit = ['0' .. '9']; // chiffres
CDigitPlus = CDigit + [CPlus, CMinus]; // chiffres et signes
CDigitPlusDot = CDigitPlus + [CDot]; // point en plus
CAlphaNum = CAlpha + CDigit;  // caractères alphanumériques
// caractères autorisés pour un identificateur
CAlphaNumPlus = CAlphaPlus + CDigit;
// caractères autorisés hors chaîne de caractères
CAllValidChars = CAlphaNumPlus + CSpecialChar;

// *** listes de propriétés ***
CExtPl = '.GPL'; // extension pour les fichiers de listes de propriétés
// entête de fichier de listes de propriétés
CHeader = '[GPL100 (c) GV 2014]';
CSep = '|'; // séparateur de liste de propriétés

// *** valeurs remarquables ***
CRLower = -1; // résultats de comparaison (plus petit)
CREqual = 0; // résultats de comparaison (égal)
CRGreater = 1; // résultats de comparaison (plus grand)
CRTrue = -1; // vrai
CStTrue = '-1';
CRFalse = 0; // faux
CStFalse = '0';

// *** tortue graphique ***
// couleurs de base
CColors: array[0..19] of TColor = (clBlack, clMaroon, clGreen, clOlive,
clNavy, clPurple, clTeal, clGray, clSilver, clRed, clLime, clYellow,
clBlue, clFuchsia, clAqua, clWhite, clMoneyGreen, clSkyBlue, clCream,
clMedGray);
DgToRad = Pi / 180; // pour les conversions en radians
RadToDg = 180 / Pi; // pour les conversions en degrés
CDefaultScale = 100; // échelle par défaut
CDefaultHeading = 90; // cap par défaut
CDefaultXY = 600; // taille écran tortue par défaut
CDefaultSize = 8; // taille d'une tortue par défaut
CMaxSize = 20; // taille maximale de la tortue
CMaxSpeed = 100; // vitesse maximum de la tortue
CDefaultPenColor = clWhite; // couleur de fond par défaut
CDefaultBackColor = clBlack; // couleur du crayon par défaut
CDefaultPenWidth = 1; // largeur du crayon par défaut

// *** piles ***
CMinStack = 8; // minimum d'espace pour une pile

// *** 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

// *** interpréteur ***
CDisabledState = 1; // désactivé
CTrueState = -1; // vrai
CFalseState = 0; // faux
CBreak = '#'; // marque de fin

Les autres éléments de cette unité seront étudiés lors de la description des unités qui y font appel.

II-A-4 - GVErrConsts

L'unité GVErrConsts abrite les éléments de base utiles pour la gestion des erreurs. Elle comprend les chaînes des messages, un type énumération pour les erreurs possibles et un tableau reprenant ces messages. Un enregistrement décrit une erreur :

// *** enregistrement d'une erreur ***
TGVErrorRec = record
Code: TGVError; // code de l'erreur
ErrItem: string; // élément fautif dans la ligne de travail
ErrPos: Integer; // position (absolue ou relative) de l'élément fautif
end;

II-A-5 - GVPrimConsts

L'unité GVPrimConsts fait de même, mais pour ce qui concerne les primitives du langage GVLOGO, c'est-à-dire les mots qui renvoient à une action/commande comprise d'origine par le langage.

L'enregistrement d'une primitive comprend son nom et le nombre de paramètres qu'elle attend :

type
// *** enregistrement d'une primitive ***
TGVPrimRec = record
Name: string; // nom
NbParams: Integer; // nombre de paramètres
end;

L'unité contient alors la liste des noms possibles et un tableau des enregistrements des primitives.

On remarquera que la partie implémentation de ces unités est vide, le rôle de ces dernières étant seulement de déclarer des types, des chaînes et des constantes.

L'utilisation de resourcestring facilitera la traduction du logiciel en une autre langue. Toutefois, l'utilisation de tableaux fait que les chaînes traitées selon ce mode ne seront pas traduites automatiquement(1).

II-B - Le traitement des erreurs

Il peut paraître surprenant de commencer par la gestion des erreurs, mais la réalisation d'une unité utilisée par tous les modules nécessaires au projet permettra de simplifier leur écriture. L'objectif est de fournir un outil simple de centralisation des erreurs : à partir d'un minimum de déclarations, tout module gèrera de manière homogène une éventuelle erreur.

II-B-1 - L'unité GVErrors

L'unité GVErrors concentre les outils nécessaires à cette gestion des erreurs. L'élément fondamental de communication est le gestionnaire d'événements relatifs aux erreurs :

type
// *** notification d'une erreur ***
TGVOnErrorEvent = procedure(Sender: TObject; ErrorRec: TGVErrorRec) of object;

Comme tout gestionnaire d'événements, il propose un modèle de procédure (ou, plus rarement, de fonction) avec une signature particulière (ici, un Sender de type TObject et un enregistrement d'erreur ErrorRec de type TGVErrorRec tel que défini plus haut dans l'unité GVErrConsts).

Voici l'interface de la classe TGVErrors définie dans GVErrors :

TGVErrors = class(TObject)
strict private
fOnError: TGVOnErrorEvent; // notification de changement
fErrorRec: TGVErrorRec; // enregistrement en cours
fErrorMessage: string; // message d'erreur en cours
fOKFlag: Boolean; // drapeau d'erreur
function GetOKFlag: Boolean; // récupération du drapeau d'erreur
procedure SetOKFlag(AValue: Boolean); // mise à jour du drapeau d'erreur
protected
// changement notifié
procedure NotifyError;
public
constructor Create; // constructeur
destructor Destroy; override; // destructeur
procedure Clear; // nettoyage des erreurs
// procédure concernant la collecte des informations
procedure SetError(const Code: TGVError; ErrItem: string; ErrPos:
Integer = CE_NoErr); virtual;
// centralisation des informations
procedure GetError(Sender: TObject; ErrorRec: TGVErrorRec);
// message associé à une erreur
property ErrorMessage: string read fErrorMessage;
// notification de changement
property OnError: TGVOnErrorEvent read fOnError write fOnError;
// drapeau d'erreur
property Ok: Boolean read GetOKFlag write SetOKFlag default True;
// enregistrement d'erreur
property Error: TGVErrorRec read fErrorRec write fErrorRec;
end;

ErrorMessage transforme le numéro d'une erreur en un message lisible. La propriété Error permet d'accéder à l'enregistrement de l'erreur. La propriété OK permet de tester si une erreur a été rencontrée ou non.

Plus intéressant est le gestionnaire OnError qui sera activé à chaque erreur rencontrée. C'est par son intermédiaire que les erreurs seront traitées par l'unité appelante. SetError et GetError permettent de gérer les données associées aux erreurs. La méthode GetError a une signature compatible avec le type TGVOnErrorEvent si bien qu'elle peut récupérer les erreurs d'une unité qui gérerait elle-même un objet de type TGVErrors !

Voici par exemple comment l'interpréteur récupère et centralise les erreurs détectées dans les objets qu'il manipule :

constructor TGVAutomat.Create;
// *** création ***
begin
// piles
fWkStack := TGVStringStack.Create; // travail
fParamsStack := TGVIntegerStack.Create; // paramètres
fDatasStack := TGVStringStack.Create; // données
fCommandsStack := TGVStringStack.Create; // commandes
fExeStack := TGVStringStack.Create; // exécution
// drapeaux
fStop := False; // pas d'arrêt
fFollow := False; // pas de suivi
fTurtleOutPut:= False; // écriture sur l'écran normal
fState := asWaiting; // en attente
// modules
Error := TGVErrors.Create; // traitement des erreurs
fKernel:= TGVLogoKernel.Create; // noyau
fKernel.Error.OnError := @Error.GetError; // gestionnaire centralisé d'erreurs
fLocVars := TGVLocVars.Create; // variables locales
fLocVars.Error.OnError := @Error.GetError; // gestionnaire centralisé d'erreurs
fEval := TGVEval.Create; // évaluateur
fEval.Kernel := fKernel; // noyau et évaluateur liés
fEval.LocVars := fLocVars; // idem pour les variables locales
fEval.Error.OnError := @Error.GetError; // gestionnaire centralisé d'erreurs
fMessage := TGVAutomatMessage.Create; // messages
end;

Une fois assuré le traitement local des erreurs par la création de l'objet Error (instance de la classe TGVErrors), les objets fKernel, fLocVars et fEval voient leurs gestionnaires d'erreurs reliés à celui de leur hôte. À présent, toute erreur déclenchée dans leur sein sera répercutée dans Error de l'interpréteur.

II-B-2 - L'utilisation de l'unité

Le processus de déclaration est toujours le même. Voici, par exemple, la classe TGVNumber qui traite les nombres :

// *** classe des nombres ***
TGVNumber = class(TObject)
strict private
fError: TGVErrors; // traitement des erreurs
fNum: Double; // nombre de travail
fSt: string; // chaîne brute d'entrée
fValid: Boolean; // drapeau de validité
function GetInt: Integer; // renvoie un entier
function GetDouble: Double; // renvoie un réel
function GetStr: string; // acquiert une chaîne à convertir en nombre
procedure SetStr(const St: string); // forme une chaîne à partir d'un nombre
public
constructor Create; // constructeur
destructor Destroy; override; // destructeur
procedure Clear; // remise à zéro
function IsValid: Boolean; // est-ce un nombre ?
function IsInt: Boolean; // est-ce un entier ?
function IsZero: Boolean; // nombre 0 ?
function IsNegate: Boolean; // nombre négatif ?
property Text: string read GetStr write SetStr; // une chaîne de nombre
property AsDouble: Double read GetDouble; // un réel
property AsInt: Integer read GetInt; // un entier
// notification d'une erreur
property Error: TGVErrors read fError write fError;
end;

On crée une propriété qui renvoie à une variable privée. Dans le constructeur Create, on crée l'instance de classe tandis qu'on la libère dans le destructeur Destroy. Il suffit à présent de générer quand nécessaire une erreur pour qu'elle puisse être exploitée : on utilisera la méthode SetError qui requiert un numéro d'erreur, le nom de l'élément ayant déclenché l'erreur et facultativement un emplacement de l'erreur dans l'élément.

Dans le programme appelant, on crée une méthode d'affichage des erreurs :

procedure GetError(Sender: TObject; ErrorRec: TGVErrorRec);

Voici le listing de cette méthode qui affiche les données associées à l'erreur :

procedure TMainForm.GetError(Sender: TObject; ErrorRec: TGVErrorRec);
// gestionnaire d'erreurs
begin
// message en toutes lettres
mmoMain.Lines.Add('// >>> ' + (Sender as TGVErrors).ErrorMessage);
with mmoMain.Lines, ErrorRec do
begin
Add('// Code: ' + IntToStr(Ord(Code))); // code de l'erreur
Add('// Elément : ' + ErrItem); // élément fautif dans la ligne de travail
if ErrPos <> CE_NoErr then // position pertinente ?
Add('// Position : ' + IntToStr(ErrPos)); // position de l'erreur
Add('');
end;
end;

Lorsqu'on crée la fiche de travail, on relie cette méthode à l'unité de gestion des erreurs :

procedure TMainForm.FormCreate(Sender: TObject);
// création de la fiche
begin
Automat := TGVAutomat.Create; // automate créé
Automat.OnStateChange := @GetStateChange; // état changé
Automat.Error.OnError := @GetError; // gestionnaire d'erreurs
Automat.Message.OnMessageChange := @GetMessage; // gestionnaire de messages
Automat.Follow := False; // pas de trace par défaut
fDeepTrace := False;
// on crée la tortue
GVTurtle := TGVTurtle.Create(imgTurtle.Width, imgTurtle.Height);
Automat.Turtle := GVTurtle; // tortue liée à l'automate
GVTurtle.OnChange := @TurtleState;  // gestionnaire de changement
GVTurtle.OnBeforeChange := @TurtleBeforePaint; // idem avant de dessiner
GVTurtle.Error.OnError := @GetError; // gestionnaire d'erreurs
GVTurtle.ReInit; // initialisation
GVTurtle.Kind := tkPng; // tortue image
end;

On remarquera que le même gestionnaire est ici partagé par deux unités : c'est tout l'intérêt de notre centralisation des erreurs que de n'avoir qu'une seule méthode de traitement à coder.

À présent, tout ce qu'il reste à faire, c'est de vérifier l'état d'erreur de l'unité. Si une erreur a été levée, elle a été traitée.

Il n'est par conséquent pas besoin de tester quoi que ce soit d'autre :

procedure TGVMainForm.btnFirstClick(Sender: TObject);
// test de FIRST
var
LS: string;
begin
fWord.Text := LabEdtFirst.Text; // on affecte
LS := fWord.First; // on effectue l'opération
if fWord.Error.OK then // pas d'erreur ?
lblResult.Caption := LS; // on affiche
end;

Dans la méthode donnée en exemple, on récupère le contenu d'un composant TLabelEdit qu'on transforme en mot formaté. Le second mot issu lui aussi d'un TLabelEdit est comparé au premier. S'il n'y a pas eu d'erreur, on affiche le résultat, à savoir le mot qui précède l'autre par ordre alphabétique. En cas d'erreur, un message aura automatiquement été affiché.

II-B-3 - Le programme d'exemple

Les versions proposées pour le programme d'exemple sont au nombre de deux :

- une version Lazarus Win32 ;

- une version Lazarus Linux.

On trouvera les fichiers sources et les exécutables dans le sous-dossier « 00 – testgverrors » du dossier « tests ».

Toutes les unités produites seront ainsi testées par un programme autonome. Cette démarche permet d'isoler les éventuels problèmes : il sera bien plus facile de repérer une erreur dans un module que dans le programme final qui comportera des milliers de lignes !

En dehors de la gestion des erreurs déjà étudiée, le programme est tout à fait trivial. Un composant TMemo affiche les messages d'erreurs. Chacune d'entre elles est générée de manière aléatoire.

II-C - Les mots

II-C-1 - Définitions

GVLOGO travaille essentiellement à partir de mots et de listes. Ce chapitre se propose d'étudier les mots.

Un mot est une suite quelconque de caractères.

Un mot est délimité par un espace, les signes [, ], (, ). Ces cinq caractères particuliers sont appelés des délimiteurs : ils indiquent à GVLOGO comment séparer les éléments du langage.

Afin d'inclure un délimiteur dans un mot, on le fait précéder d'un autre caractère appelé caractère d'échappement : c'est le $. Si ce caractère d'échappement doit apparaître en tant que tel dans un mot, il faut le faire précéder de lui-même !

Il existe un mot particulier qui ne comprend aucun caractère : on l'appelle le mot vide. On le représente par un guillemet anglais suivi d'un espace.

Les mots "VRAI et "FAUX, qui peuvent s'écrire simplement VRAI et FAUX, sont réservés au renvoi de valeurs booléennes. Pour GVLOGO, la valeur « vrai » est représentée par le nombre -1 alors que la valeur « faux » l'est par le nombre 0.

II-C-2 - Exemples de mots

II-C-2-a - Mots simples

Voici des mots simples qui ne poseront pas de problèmes particuliers :

- libellule

- porte-avions

- entendre

- EnTenDRe

Les nombres ne sont que des mots particuliers qui seront traités comme des mots ordinaires ou comme des nombres, suivant l'opération en cours :

- 1245

- 1E2(2)

- 13456,58

Les mots accentués et les caractères exotiques sont utilisables eux aussi, à condition de ne pas être choisis en tant qu'identificateur:

- éléphant

- %&/ç\

Les caractères particuliers comme ceux qui sont accentués ne sont pas toujours bien gérés par les langages de programmation et les systèmes d'exploitation. Les normes se sont multipliées et des hiéroglyphes étranges peuvent parfois apparaître. Le travail de développement de Lazarus est largement déterminé par ce problème. Nous y reviendrons lorsque nous aborderons l'implémentation des mots. Disons que, pour le moins, il s'agit d'un point délicat susceptible de troubler le programmeur.

II-C-2-b - Mots avec caractère d'échappement

Un caractère pourtant interdit dans un mot GVLOGO peut toujours être introduit s'il est précédé du caractère d'échappement :

- Victor$ Hugo

- GVLOGO$ est$ un$ langage$ facile$ à$ apprendre

- $[encore$ un$ mot$]

- $(un$ autre$)

- 15$ $$

Lors de l'écriture des mots ainsi construits, le caractère d'échappement, sauf contre-ordre, n'apparaîtra pas à l'écran :

- ECRIS $[encore$ un$ mot$] donnera [encore un mot]

- ECRIS 15$ $$ donnera 15 $

Dès qu'une suite de mots est en jeu, l'emploi des listes est préconisé. Le caractère d'échappement est une commodité, sans plus. Il est plus facile d'écrire et de comprendre ECRIS [15 $] que ECRIS 15$ $$.

II-C-2-c - Autres caractères particuliers

En plus des délimiteurs, certains caractères sont utilisés par le langage lui-même et déterminent son comportement selon le contexte :

- le " (guillemet anglais) indique que le mot qui suit doit être pris tel quel, sans chercher à l'interpréter (par exemple, ECRIS "Bonjour.) ;

- les : (deux-points) indiquent que le mot qui suit est une variable dont la valeur est à rechercher (par exemple, ECRIS :salutation affichera, si elle existe, la valeur qui correspond à la variable « salutation ») ;

- le . (point) en début de mot indique une primitive dont l'emploi est risqué, car elle touche au cœur de GVLOGO (par exemple .EFFACE.TOUT détruit tous les objets en réinitialisant le noyau) ;

- le ? (point d'interrogation) est utilisé à la fin des primitives qui renvoient une valeur booléenne (VRAI/FAUX), mais, comme le point, son utilisation est une question d'habitude et n'a pas de caractère obligatoire.

II-C-3 - Opérations sur les mots

Ci-après, le lecteur trouvera les primitives connues de GVLOGO qui concernent les mots. Pour chaque primitive, avant sa définition, il est indiqué le nombre de paramètres attendus, leurs types et le type de valeur qu'elle renvoie. Au moins un exemple illustre l'emploi de la primitive décrite.

Les primitives énumérées peuvent évidemment se combiner pour obtenir le résultat escompté : c'est même ainsi qu'elles prennent tout leur intérêt ! On y reviendra dans la partie qui traitera de la réalisation de programmes en GVLOGO.

II-C-3-a - Fabriquer des mots

METS.PREMIER (raccourci : MP) : attend deux mots en entrée – renvoie un mot – le mot rendu est composé du premier paramètre placé avant le second.

Exemple :

- ECRIS METS.PREMIER "tourne "dos → tournedos

METS.DERNIER (raccourci : MD) : attend deux mots en entrée – renvoie un mot – le mot rendu est composé du premier paramètre placé après le second.

Exemple :

- ECRIS METS.DERNIER "tourne "dé → détourne

MOT : attend deux mots en entrée – renvoie un mot – le mot rendu est composé du second paramètre placé après le premier.

Exemple :

- ECRIS MOT "dé "tourne → détourne

Il s'agit donc d'un synonyme de METS.DERNIER. Cependant, cette dernière primitive fonctionne aussi avec les listes alors que MOT déclenchera une erreur si une liste est fournie en entrée.

II-C-3-b - Modifier des mots

INSERE : attend un entier suivi deux mots en entrée – renvoie un mot – Le mot rendu est composé du deuxième paramètre inséré dans le dernier paramètre à la position précisée par l'entier.

Exemple :

- ECRIS INSERE 4 "en "atttion → attention

INVERSE : attend un mot en entrée – renvoie un mot – le mot rendu est celui d'entrée dont les lettres ont été inversées.

Exemple :

- ECRIS INVERSE "billet → tellib

MAJUSCULES : attend un mot en entrée – renvoie un mot – le mot rendu est en majuscules.

Exemple :

- ECRIS MAJUCULES "éléphant → ĒLĒPHANT

MELANGE : attend un mot en entrée – renvoie un mot – le mot rendu a les mêmes lettres que celui d'origine, mais dans un ordre aléatoire.

Exemple :

- ECRIS MELANGE "alouette → eoetault

MINUSCULES : attend un mot en entrée – renvoie un mot – le mot rendu est en minuscules.

Exemple :

- ECRIS MINUSCULES "ESSAI → essai

REMPLACE : attend un entier suivi de deux mots en entrée – renvoie un mot – le mot rendu est composé de toutes les lettres du second mot en entrée, sauf celle visée par l'entier qui a été remplacée par le premier mot.

Exemple :

- ECRIS REMPLACE 3 "é "éliment → élément

TRIE : attend un mot en entrée – renvoie un mot – les lettres du mot sont triées par ordre alphabétique(3).

Exemple :

- ECRIS TRIE "important → aimnoprtt

ROTATION : attend un mot en entrée – renvoie un mot – la primitive renvoie le mot dont le premier caractère est placé à la fin.

Exemples :

- ECRIS ROTATION "essai → ssaie

II-C-3-c - Extraire depuis des mots

ELEMENT : attend un entier suivi d'un mot en entrée – renvoie un mot – le mot rendu est composé de la lettre du mot en entrée indiquée par l'entier fourni.

Exemple :

- ECRIS ELEMENT 4 "Zorro → r

DERNIER (raccourci : DER) : attend un mot en entrée – renvoie un mot – le mot renvoyé est composé de la dernière lettre du mot en entrée.

Exemple :

- ECRIS DERNIER "important → t

HASARD : attend un mot en entrée – renvoie un mot – le mot renvoyé est composé d'une lettre du mot en entrée, tirée au hasard. Si le mot en entrée est un nombre, le mot renvoyé est un nombre compris entre 1 et ce nombre.

Exemples :

- ECRIS HASARD "destin → i

- ECRIS HASARD "destin → d

- ECRIS HASARD "10 → 8

PREMIER (raccourci : PREM) : attend un mot en entrée – renvoie un mot – le mot renvoyé est composé de la première lettre du mot en entrée.

Exemple :

- ECRIS PREMIER "important → i

SAUF.DERNIER (raccourci : SD) : attend un mot en entrée – renvoie un mot – le mot renvoyé est le mot en entrée amputé de sa dernière lettre.

Exemple :

- ECRIS SAUF.DERNIER "important → importan

SAUF.PREMIER (raccourci : SP) : attend un mot en entrée – renvoie un mot – le mot renvoyé est le mot en entrée amputé de sa première lettre.

Exemple :

- ECRIS SAUF.PREMIER "important → mportant

II-C-3-d - Tester des mots

PLG? : attend deux mots en entrée – renvoie un booléen – la primitive renvoie "VRAI si le premier mot vient strictement après le second selon l'ordre alphabétique, "FAUX sinon.

Exemples :

- ECRIS PLG? "petit "grand → -1

- ECRIS PLG? 1245 138 → -1

Comme EGAL? et PLP?, PLG? se comporte différemment si les deux mots sont des nombres : ce sont leurs valeurs qui sont alors comparées.

PLP? : attend deux mots en entrée – renvoie un booléen – la primitive renvoie "VRAI si le premier mot vient strictement avant le second selon l'ordre alphabétique, "FAUX sinon.

Exemple :

- ECRIS PLP? "petit "grand → 0

COMPTE : attend un mot en entrée – renvoie un entier – la primitive renvoie le nombre de caractères du mot en entrée.

Exemple :

- ECRIS COMPTE "urticaire → 9

EGAL? : attend deux mots en entrée – renvoie un booléen – la primitive renvoie "VRAI si le premier mot est égal au second selon l'ordre alphabétique, "FAUX sinon.

Exemple :

- ECRIS EGAL? "100 "1E2 → -1

IDENTIFICATEUR? : attend un mot en entrée – renvoie un booléen – la primitive renvoie "VRAI si le mot en entrée est un identificateur correct, "FAUX sinon.

Exemples :

- ECRIS IDENTIFICATEUR? "MaFonction12 → -1

- ECRIS IDENTIFICATEUR? "12MaFonction → 0

Un identificateur est correct quand il ne comprend que des lettres non accentuées, des chiffres non placés en première position, le signe _ (souligné) et le signe . (point) en début de mot, ou le ? (point d'interrogation) à la fin.

MEMBRE? : attend deux mots en entrée – renvoie un booléen – la primitive renvoie "VRAI si le premier mot est compris dans le second, "FAUX sinon.

Exemple :

- ECRIS MEMBRE? "an "étonnant → -1

MOT? : attend un objet en entrée – renvoie un booléen – la primitive renvoie "VRAI si l'objet est un mot, "FAUX sinon.

Exemple :

- ECRIS MOT? [coucou] → 0

NOMBRE? : attend un mot en entrée – renvoie un booléen – la primitive renvoie "VRAI si le mot est un nombre, "FAUX sinon.

Exemple :

- ECRIS NOMBRE? "12458 → -1

Le nombre est ici précédé d'un guillemet anglais, comme le serait un mot ordinaire. Pour les nombres, cette écriture est facultative.

PRECEDENT : attend deux mots en entrée – renvoie un mot – la primitive renvoie le mot qui vient le premier selon l'ordre alphabétique.

Exemple :

- ECRIS PRECEDENT "un "deux → deux

SUIVANT : attend deux mots en entrée – renvoie un mot – la primitive renvoie le mot qui vient le dernier selon l'ordre alphabétique.

Exemple :

- ECRIS SUIVANT "un "deux → un

VIDE? : attend un mot en entrée – renvoie un booléen – la primitive renvoie "VRAI si le mot en entrée est le mot vide, "FAUX sinon.

Exemple :

- ECRIS VIDE? " → -1

II-C-4 - Implémentation des mots

L'implémentation des mots passe par l'unité GVConsts qui contient les constantes centralisées du projet et une unité baptisée GVWords qui abrite les classes nécessaires à la gestion proprement dite des mots.

II-C-4-a - L'objet TGVString

La difficulté essentielle dans le traitement des mots en GVLOGO réside dans la présence d'un caractère d'échappement dont il faut tenir compte pour toutes les opérations portant sur eux. L'objet TGVString accomplit le travail de normaliser tout mot qui lui est proposé. Il est capable de restituer le mot d'origine ainsi que celui transformé.

Voici sa partie interface :

type
// *** objet chaîne ***
TGVString = object
strict private
fRawStr: string; // chaîne brute entrée
fIsValid: Boolean; // validité de la chaîne d'entrée
fStr: string; // chaîne brute interne
fFmtStr: string; // chaîne formatée interne
function GetFmtStr: string; // renvoie la chaîne formatée
function GetStr: string; // renvoie la chaîne brute
procedure SetStr(const St: string); // établit une nouvelle chaîne brute
function WithEsc(const St: string): string; // avec échappement
function WithoutEsc(const St: string): string; // sans échappement
public
procedure Clear; // remise à zéro
property IsValid: Boolean read fIsValid; // validité de la chaîne d'entrée
property Str: string read GetStr write SetStr; // chaîne brute d'entrée
property FmtStr: string read GetFmtStr; // chaîne formatée
property RawStr: string read fRawStr; // chaîne brute
end;

L'interface est bâtie de manière « classique » en proposant des propriétés dont l'implémentation est protégée par des méthodes privées. La propriété Str lit une chaîne afin de la transformer en chaîne normalisée et de la restituer ainsi. La propriété en lecture seule RawStr permet de retrouver la chaîne brute, telle qu'elle a été introduite.

La méthode SetStr est chargée de transformer la chaîne fournie en entrée. Elle invoque successivement WithoutEsc qui retire un éventuel formatage et WithEsc qui normalise la chaîne. Cette façon de procéder évite de formater une chaîne qui le serait déjà : en revanche, elle exige deux analyses de la chaîne et ne repère pas les mots normalisés par hasard !

Un objet a été préféré à une classe dans le cas bien précis où aucun appel à un constructeur et/ou à un destructeur n'était nécessaire. En effet, l'objet n'utilise pas de méthodes virtuelles et n'utilise aucune classe particulière.

Encore un avantage à déléguer un travail particulier à un objet ou à une classe : un meilleur algorithme pourra être implémenté dans cette classe sans avoir à modifier la moindre ligne des autres fichiers. Autrement dit, une fois l'interface bien définie, il n'importe en rien aux classes qui s'en servent de savoir comment le problème est traité : seul le résultat compte. Il est même très judicieux d'imposer qu'un objet ou une classe n'ait aucun présupposé à faire avant d'utiliser une autre classe.

L'algorithme utilisé par WithEsc est trivial. On remarquera cependant le couple for…in qui balaie la chaîne en lieu et place d'une boucle traditionnelle for…next moins lisible :

function TGVString.WithEsc(const St: string): string;
// *** normalise un mot en tenant compte du caractère d'échappement ***
var
Lch: Char;
begin
Result := EmptyStr; // chaîne vide par défaut
for Lch in St do // balaie la chaîne de départ
begin
if Lch in CSeparators + [CLink] then
// si séparateur ou échappement suit, on insère un échappement
Result := Result + CLink;
Result := Result + Lch; // caractère en cours ajouté
end;
end;

Les utilisateurs de Delphi devront remplacer in CSeparators par la fonction CharInSet s'ils ne veulent pas obtenir un message d'avertissement.

La méthode WithoutEsc n'intéressera que pour l'utilisation d'un drapeau (variable LFlag) qui mémorise le fait d'avoir rencontré précédemment un caractère d'échappement. Ce drapeau est remis à zéro lorsqu'on a traité son repérage :

function TGVString.WithoutEsc(const St: string): string;
// *** sans caractère d'échappement ***
var
LCh: Char;
LFlag: Boolean;
begin
Result := EmptyStr;
// chaîne vide par défaut
LFlag := False; // on n'a pas eu affaire à un caractère d'échappement
for LCh in St do // on balaie la chaîne
if (LCh <> CLink) or LFlag then // caractère d'échappement initial ?
begin
Result := Result + LCh; // non : on ajoute simplement
LFlag := False; // on indique ce cas
end
else
LFlag := True; // échappement initial qu'on ignore
end;

Les utilisateurs de Delphi pourront envisager de transformer cet objet en enregistrement. En effet, cet EDI accepte des enregistrements comprenant des méthodes.

II-C-4-b - La classe TGVNumber

La classe TGVNumber traite les chaînes susceptibles de représenter des nombres.

Voici sa partie interface :

TGVNumber = class(TObject)
strict private
fError: TGVErrors; // traitement des erreurs
fNum: Double; // nombre de travail
fSt: string; // chaîne brute d'entrée
fValid: Boolean; // drapeau de validité
function GetInt: Integer; // renvoie un entier
function GetDouble: Double; // renvoie un réel
function GetStr: string; // acquiert une chaîne à convertir en nombre
procedure SetStr(const St: string); // forme une chaîne à partir d'un nombre
public
constructor Create; // constructeur
destructor Destroy; override; // destructeur
procedure Clear; // remise à zéro
function IsValid: Boolean; // est-ce un nombre ?
function IsInt: Boolean; // est-ce un entier ?
function IsZero: Boolean; // nombre 0 ?
function IsNegate: Boolean; // nombre négatif ?
property Text: string read GetStr write SetStr; // une chaîne de nombre
property AsDouble: Double read GetDouble; // un réel
property AsInt: Integer read GetInt; // un entier
// notification d'une erreur
property Error: TGVErrors read fError write fError;
end;

La partie publique comprend, outre le constructeur et le destructeur, une procédure Clear qui remet à zéro le nombre interne, quatre fonctions utiles et quatre propriétés.

L'entrée dans la classe se fait par la propriété AsString. Si possible, la chaîne est de manière interne transformée en nombre. En cas d'échec, la fonction IsValid retournera False comme résultat. L'accès en lecture d'un nombre erroné déclenchera une erreur.

La lecture peut être effectuée de trois manières : sous forme de chaîne (AsString), d'entier (AsInt) ou de nombre réel (AsDouble). Il est par ailleurs possible de tester le nombre interne pour savoir s'il s'agit d'un entier grâce à la fonction IsInt. La fonction IsNegate permet de savoir si le nombre est négatif. Enfin, la fonction IsZero compare de manière correcte des nombres réels qui seraient jugés inégaux pour des raisons de représentation interne.

L'implémentation ne pose pas de problèmes particuliers. Voici celle de SetStr qui gère l'entrée d'une nouvelle chaîne à traduire :

procedure TGVNumber.SetStr(const St: string);
// *** forme un nombre si possible ***
begin
fSt := St; // chaîne brute affectée
fValid := TryStrToFloat(St, fNum); // transformation possible ?
end;

On conserve le paramètre d'entrée et on le teste grâce à la fonction TryStrToFloat. Le drapeau privé fValid sera vérifié avant de renvoyer une valeur : l'utilisateur est ainsi assuré de toujours disposer d'une valeur correcte ou d'obtenir le déclenchement d'une erreur.

En règle générale, il faut éviter de court-circuiter les erreurs et toujours avertir l'utilisateur que la valeur dont il dispose est erronée. En croyant bien faire pour l'utilisateur final, on le prive dans le cas contraire de tout repère sur la stabilité de son système et sur la confiance qu'il peut accorder aux résultats qu'il obtient.

II-C-4-c - La classe TGVWord

La classe TGVWord est de loin la plus fournie de cette unité. Elle gère les mots, qu'ils soient des chaînes de caractères ou des nombres, en reprenant toutes les fonctionnalités définies dans la description du langage GVLOGO qui concernent les mots.

La classe utilise deux champs privés de la classe TGVString et autant de la classe TGVNumber : ils servent d'outils de travail et de comparaisons. L'emploi de ces classes évite d'avoir constamment à vérifier la présence des caractères spéciaux.

En fait, la difficulté essentielle tient aux lacunes de Lazarus dans la gestion des caractères accentués. Les fonctions préfixées par UTF8 présentes dans l'unité lazutf8 constituent une solution possible qui règle la plupart des problèmes, excepté ceux relatifs à l'ordre alphabétique : ainsi, la fonction Sort ne triera pas correctement les lettres d'un mot, rejetant les caractères accentués après les caractères normaux. Delphi ne présente pas ce problème. On notera par ailleurs que la fonction Sort utilise un tri à bulles bien suffisant pour des quantités très limitées d'objets à trier.

En voici l'interface :

TGVWord = class(TObject)
strict private
fError: TGVErrors; // traitement des erreurs
fText: TGVString; // mot à traiter
fNum, fNum2: TGVNumber; // nombres de travail
function GetFmtText: string; // texte formaté
function GetText: string; // texte brut
procedure SetText(const St: string); // établit le texte
protected
function Compare(const St: string): Integer; // comparaison de deux mots
public
constructor Create; // constructeur
destructor Destroy; override; // destructeur
procedure Clear; // nettoyage
function First: string; // premier caractère d'un mot
function Last: string; // dernier caractère d'un mot
function ButFirst: string; // sauf le premier caractère d'un mot
function ButLast: string; // sauf le dernier caractère d'un mot
// concatène les deux mots, le second en premier
function PutFirst(const St: string): string;
// concatène les deux mots, le premier d'abord
function PutLast(const St: string): string;
// supprime si nécessaire le " initial d'un mot
function WithoutQuote: string;
// supprime si nécessaire le : initial d'un mot
function WithoutColon: string;
function IsValidIdent: Boolean; // est-ce un identificateur valide ?
// les deux mots sont-ils égaux ?
function IsEqual(const St: string): Boolean;
// le premier mot est-il à placer avant le second par ordre alphabétique ?
function IsLower(const St: string): Boolean;
// renvoie le mot qui vient avant par ordre alphabétique
function Lowest(const St: string): string;
// le premier mot est-il à placer après le second par ordre alphabétique ?
function IsGreater(const St: string): Boolean;
// renvoie le mot qui vient après par ordre alphabétique
function Greatest(const St: string): string;
function IsEmptyWord: Boolean; // le mot est-il vide ?
// le mot est-il compris dans un autre ?
function IsMember(const St: string): Boolean;
function Count: Integer; // longueur du mot
function StrCount: string; // longueur de mot en chaîne
function GetItem(const N: Integer): string; // élément N d'un mot
// remplacement de l'élément N d'un mot
function Replace(const N: Integer; const St: string): string;
// suppression de l'élément N d'un mot
function DelItem(const N: Integer): string;
function Reverse: string; // mot inversé
function Shuffle: string; // mot mélangé
function AtRandom: string; // lettre au hasard
function Uppercase: string; // mot en majuscules
function Lowercase: string; // mot en minuscules
// insertion en position N
function Insert(const N: Integer; const St: string): string;
function Sort: string; // tri des lettres du mot
function IsNumber: Boolean; // est-ce un nombre ?
function IsInt: Boolean; // est-ce un entier ?
function IsBoolean: Boolean; // est-ce un booléen ?
function AsNumber: Double; // renvoie un nombre
function AsInt: Integer; // renvoie un entier
function AsBoolean: Boolean; // renvoie un booléen
function IsNegate: Boolean; // nombre négatif ?
function WithEsc: string; // chaîne formatée
function WithoutEsc: string; // chaîne brute
function IsValid: Boolean; // le mot est-il valide sans traitement ?
function Rotate: string; // rotation des caractères d'un mot
property Item[N: Integer]: string read GetItem; default; // élément N
property Text: string read GetText write SetText; // mot à traiter
property FmtText: string read GetFmtText; // mot à traiter formaté
// notification d'une erreur
property Error: TGVErrors read fError write fError;
end;

La méthode protégée Compare s'occupe des comparaisons : il faut tenir compte de la comparaison de chaînes ordinaires et de nombres. En effet, 99 vient après 121 par ordre alphabétique, mais avant par ordre de grandeur mathématique !

function TGVWord.Compare(const St: string): Integer;
// *** comparaison de deux mots ***
begin
// essai de conversion en nombre des deux mots
fNum.Text := Text;
fNum2.Text := St;
// ce sont des nombres ?
if fNum.IsValid and fNum2.IsValid then
// si oui, on les compare
Result := CompareValue(fNum2.AsDouble, fNum.AsDouble)
else
// on compare les  mots ordinaires
Result := UTF8CompareText(St, Text); // comparaison UTF8
end;

II-C-4-d - Test de l'unité GVWords

Le meilleur moyen de s'approprier les méthodes de la classe TGVWord est de les voir fonctionner dans un programme.

Les versions proposées sont au nombre de deux :

- une version Lazarus Win32 ;

- une version Lazarus Linux.

On trouvera les fichiers sources et les exécutables dans le sous-dossier « 01 – testgvwords » du dossier « tests ».

Si Delphi offre de meilleures capacités à traiter les caractères Unicode et s'il facilite une présentation plus moderne grâce aux styles, Lazarus se montre à la hauteur dans sa portabilité sur différents OS : il n'a pas été nécessaire d'apporter la moindre modification aux fichiers pour faire fonctionner le programme aussi bien sur Windows que sur Linux.

Le programme lui-même est trivial puisqu'il se contente d'affecter le résultat d'une méthode de TGVWord à un TLabel si aucune erreur n'a été déclenchée. C'est exactement ce qui était recherché : l'unité remplit parfaitement son rôle en limitant au possible les complications pour l'utilisateur final. On notera en particulier que les erreurs sont affichées correctement sans que les méthodes d'affichage aient à s'occuper de leur traitement.

Il est intéressant de modifier les paramètres des éditeurs pour voir le comportement de l'unité. Par exemple, on pourra entrer des mots tels que "Essai, cou[cou… On peut aussi entrer une phrase complète et constater qu'elle sera normalisée pour être comprise comme un mot ! Dans tout test, il est conseillé de provoquer des erreurs afin de voir comment se comporte l'unité.

Dans ce petit programme d'exemple, une erreur inhibe toutes les fonctions. Il faut cliquer sur le bouton « Effacer » pour réinitialiser l'ensemble.

Une fonction souvent méconnue est utilisée largement dans ce programme : il s'agit de IfThen. La version traitant les chaînes de caractères est définie dans l'unité strutils. Elle prend deux ou trois paramètres en entrée : le premier est une valeur booléenne, les deux suivants des chaînes de caractères. Si le troisième est omis, il est par défaut défini à la chaîne vide. Le principe de fonctionnement est semblable à celui d'un test avec if…then…else : si la valeur booléenne est vraie, le second paramètre est renvoyé, sinon c'est le troisième.

Par exemple :

procedure TGVMainForm.btnIsValidIdentClick(Sender: TObject);
// test de ISVALIDIDENT
var
LS: string;
begin
fWord.Text := LabEdtFirst.Text;
LS := IfThen(fWord.IsValidIdent, P_True, P_False);
if fWord.Error.OK then
lblResult.Caption := LS;
end;

Le test de la fonction IsValidIdent est effectué sur le composant TLabelEdit : si le résultat est correct, c'est la constante P_True qui est affectée au Caption du composant TLabel, sinon c'est P_False.

Un conseil ("hint") indique que l'unité strutils n'est pas utilisée alors que la compilation échouera si elle est omise ! En effet, elle est indispensable pour le traitement de IfTthen avec des chaînes. Il semblerait que Lazarus soit trompé par l'autre fonction IfThen présente dans l'unité math utilisée par ailleurs.

II-D - Les listes

II-D-1 - Définitions

Une liste simple est composée de mots séparés par des espaces(4). Elle est délimitée par des crochets : [ puis ].

Une liste complexe comprend des mots et des listes. Les imbrications ne sont pas limitées, pourvu que l'ordre d'ouverture et de fermeture des listes imbriquées soit correct.

Il existe une liste particulière qui ne comprend ni mot ni liste. On la nomme la liste vide et elle est représentée par des crochets [] accolés.

II-D-2 - Exemples de listes

II-D-2-a - Listes simples

Voici quelques listes simples :

- [un deux trois quatre cinq]

- [1 2 3 4 5]

- [GVLOGO est un langage facile à apprendre.]

II-D-2-b - Listes imbriquées

Voici des listes complexes :

- [élément1 [liste imbriquée 1] élément3]

- [un [deux imbriquée [encore deux mais imbriquée]] trois]

- [[]]

- [un [deux [deux bis [deux ter]]] trois]

- [un (12*45+789) trois]

La dernière liste contient une expression. Une expression est une suite de caractères à interpréter comme un calcul à effectuer(5).

II-D-2-c - Listes fautives

Une liste est fautive si elle ne contient pas des crochets correctement agencés et en bon nombre.

Voici des listes incorrectes :

- [oups ! [où est la fermeture ?]

- [fermée trop tôt][ceci est une autre liste]

- [le caractère d'échappement masque la fin de liste !$]

- [Une parenthèse se promène seule (]

Pour contourner le problème des crochets ou des parenthèses mal appariés, il suffit d'utiliser le caractère d'échappement, comme avec les mots :

- [le caractère d'échappement ne masque plus la fin de liste !$]]

- [Une parenthèse se promène seule sans problème $(]

Le caractère d'échappement disparaîtra à l'écriture grâce aux primitives ECRIS et TAPE. ECRIST le fera apparaître si nécessaire.

II-D-3 - Opérations sur les listes

Les opérations possibles sur les listes sont en majorité celles déjà vues à propos des mots. Elles sont mentionnées ici avec des exemples propres aux listes.

La primitive ECRIS retire les crochets lors de l'envoi d'une liste à l'écran, tout comme elle fait disparaître le caractère d'échappement. Ce comportement explique pourquoi les exemples qui utilisent cette primitive donnent des résultats sans crochets. Si l'on veut conserver les crochets, il faut utiliser la primitive ECRIST.

II-D-3-a - Fabriquer des listes

METS.PREMIER (raccourci : MP) : attend deux listes en entrée ou un mot et une liste – renvoie une liste – la liste rendue est composée du premier paramètre placé avant le second.

Exemples :

- ECRIS METS.PREMIER [Coucou] [Tortue !] → [Coucou] Tortue !

- ECRIS METS.PREMIER "Coucou [Tortue !] → Coucou Tortue !

METS.DERNIER (raccourci : MD) : attend deux listes en entrée ou un mot suivi d'une liste – renvoie une liste – la liste rendue est composée du premier paramètre placé après le second.

Exemples :

- ECRIS METS.DERNIER [Coucou] [Tortue !] → Tortue ! [Coucou]

- ECRIS METS.DERNIER "Coucou [Tortue !] → Tortue ! Coucou

PHRASE (raccourci : PH) : attend deux listes ou deux mots ou une combinaison des deux en entrée – renvoie une liste – la liste rendue est composée des deux éléments séparés par un espace sans les crochets et/ou sans les guillemets anglais.

Exemples :

- ECRIS PHRASE "je "viens → je viens

- ECRIS PHRASE [je] [pars] → je pars

- ECRIS PHRASE "je [reviens] → je reviens

- ECRIS PHRASE [je] "repars → je repars

II-D-3-b - Modifier des listes

INSERE : attend un entier, un mot ou une liste, puis une liste en entrée – renvoie une liste – la liste rendue est composée du deuxième paramètre inséré à la position précisée par l'entier dans la liste finale.

Exemples :

- ECRIS INSERE 2 "très [c'est bien] → c'est très bien

- ECRIS INSERE 1 [zéro] [[un][deux][trois]] → [zéro][un][deux][trois]

INVERSE : attend une liste en entrée – renvoie une liste – la liste rendue est celle d'entrée dont les éléments ont été inversés.

Exemple :

- ECRIS INVERSE [un deux trois] → trois deux un

MAJUSCULES : attend une liste en entrée – renvoie une liste – la liste rendue est en majuscules.

Exemple :

- ECRIS MAJUCULES [le soleil se lève] → LE SOLEIL SE LÈVE

MELANGE : attend une liste en entrée – renvoie une liste – la liste rendue a les mêmes éléments que celle d'origine, mais dans un ordre aléatoire.

Exemple :

- ECRIS MELANGE [un deux [troisa troisb]] → deux [troisa troisb] un

MINUSCULES : attend une liste en entrée – renvoie une liste – la liste rendue est en minuscules.

Exemple :

- ECRIS MINUSCULES [LES ENFANTS SE LÈVENT] → Les enfants se lèvent

REMPLACE : attend un entier, une liste ou un mot, puis une liste en entrée – renvoie une liste – la liste rendue est composée de toutes les éléments de la liste finale, sauf celui visé par l'entier qui a été remplacé par le deuxième paramètre.

Exemples :

- ECRIS REMPLACE 4 "couchent [Les enfants se lèvent] → Les enfants se couchent

- ECRIS REMPLACE 2 [deuxième] [[premier][seconde][troisième]] → [premier][deuxième][troisième]

TRIE : attend une liste en entrée – renvoie une liste – les éléments de la liste sont triés par ordre alphabétique(6).

Exemple :

- ECRIS TRIE [zèbre tartine accent dommage] → accent dommage tartine zèbre

ROTATION : attend une liste en entrée – renvoie une liste – la primitive renvoie la liste dont le premier élément est placé à la fin.

Exemple :

- ECRIS ROTATION [un deux trois quatre] → deux trois quatre un

II-D-3-c - Extraire de listes

ELEMENT : attend un entier suivi d'une liste en entrée – renvoie une liste ou un mot – l'objet rendu est l'élément de la liste en entrée indiqué par l'entier.

Exemple :

- ECRIS ELEMENT 2 [un deux [trois]] → deux

DERNIER (raccourci : DER) : attend une liste en entrée – renvoie un mot ou une liste – l'objet renvoyé est composé du dernier élément de la liste en entrée.

Exemple :

- ECRIS DERNIER [un deux] → deux

HASARD : attend une liste en entrée – renvoie un mot ou une liste – l'objet renvoyé est composé d'un élément de la liste en entrée, tiré au hasard.

Exemple :

- ECRIS HASARD [un [deux] trois quatre] → trois

PREMIER (raccourci : PREM) : attend une liste en entrée – renvoie un mot ou une liste – l'objet renvoyé est composé du premier élément de la liste en entrée.

Exemple :

- ECRIS PREMIER [beau laid] → beau

PREMS : attend une liste en entrée – renvoie une liste – l'objet renvoyé est composé de chaque premier élément des éléments de la liste en entrée.

Exemples :

- ECRIS PREMS [beau [laid]] → b laid

- ECRIS PREMS [[moi beau] [toi laid]] → [moi] [toi]

SAUF.DERNIER (raccourci : SD) : attend une liste en entrée – renvoie un mot ou une liste – l'objet renvoyé est composé de la liste en entrée sans son dernier élément.

Exemple :

- ECRIS SAUF.DERNIER [pas cette liste stupide] → pas cette liste

SAUF.PREMIER (raccourci : SP) : attend une liste en entrée – renvoie un mot ou une liste – l'objet renvoyé est composé de la liste en entrée sans son premier élément.

Exemple :

- ECRIS SAUF.PREMIER [pas cette liste stupide] → cette liste stupide

SAUF.PREMS : attend une liste en entrée – renvoie une liste – l'objet renvoyé est composé de la liste en entrée sans chaque premier élément de ses éléments.

Exemples :

- ECRIS SAUF.PREMS [pas cette liste stupide] → as ette iste tupide

- ECRIS SAUF.PREMS [[un chat] [une chouette]] → [chat] [chouette]

II-D-3-d - Tester des listes

PLG? : attend deux listes en entrée – renvoie un booléen – la primitive renvoie "VRAI si la première liste vient strictement après la seconde selon l'ordre alphabétique, "FAUX sinon.

Exemple :

- ECRIS PLG? [un petit chat] [un chat] → -1

PLP? : attend deux listes en entrée – renvoie un booléen – la primitive renvoie "VRAI si la première liste vient strictement avant la seconde selon l'ordre alphabétique, "FAUX sinon.

Exemple :

- ECRIS PLP? [le renard] [un renard] → -1

COMPTE : attend une liste en entrée – renvoie un entier – la primitive renvoie le nombre d'éléments de la liste en entrée.

Exemple :

- ECRIS COMPTE [un [deux encore deux] trois] → 3

EGAL? : attend deux listes en entrée – renvoie un booléen – la primitive renvoie "VRAI si la première liste est identique à la seconde, "FAUX sinon.

Exemple :

- ECRIS EGAL? [c'est la même] METSPREMIER "c'est [la même] → -1

MEMBRE? : attend une liste ou un mot, puis une liste en entrée – renvoie un booléen – la primitive renvoie "VRAI si le premier objet est compris dans le second, "FAUX sinon.

Exemples :

- ECRIS MEMBRE? [oui] [un deux [oui]] → -1

- ECRIS MEMBRE? "oui [un deux [oui]] → 0

LISTE? : attend un objet en entrée – renvoie un booléen – la primitive renvoie "VRAI si l'objet est une liste, "FAUX sinon.

Exemples :

- ECRIS LISTE? [coucou] → -1

- ECRIS LISTE? "coucou → 0

PRECEDENT : attend deux listes en entrée – renvoie une liste – la primitive renvoie la liste qui vient la première selon l'ordre alphabétique.

Exemple :

- ECRIS PRECEDENT [je suis premier] [je suis second] → je suis premier

SUIVANT : attend deux listes en entrée – renvoie une liste – la primitive renvoie la liste qui vient la dernière selon l'ordre alphabétique.

Exemple :

- ECRIS SUIVANT [je suis premier] [je suis second] → je suis second

VIDE? : attend une liste en entrée – renvoie un booléen – la primitive renvoie "VRAI si la liste en entrée est la liste vide, "FAUX sinon.

Exemples :

- ECRIS VIDE? [] → -1

- ECRIS VIDE? SAUF.PREMIER [un] → -1

II-D-4 - Implémentation des listes

L'implémentation des listes passe par un complément de l'unité GVConsts qui contient les constantes centralisées du projet et une unité baptisée GVLists qui abrite les classes nécessaires à la gestion proprement dite des listes. Cette unité utilise par ailleurs l'unité GVWords précédemment étudiée.

II-D-4-a - Constantes

Les constantes ont simplement été complétées avec les chaînes nécessaires au signalement des erreurs et un type énuméré baptisé TGVError qui reprend les erreurs sous forme codée.

II-D-4-b - La classe TGVListUtils

La classe TGVListUtils concentre les méthodes utiles au traitement des listes. Il aurait été possible de les implémenter en tant que procédures et fonctions indépendantes, mais l'utilisation d'une classe et de son interface permet une meilleure maîtrise des outils : la classe définit précisément ce pour quoi elle est faite.

En voici l'interface :

// *** classe des utilitaires pour les listes ***
TGVListUtils = class
strict private
fError: TGVErrors; // traitement des erreurs
fWord: TGVWord; // mot de travail
fPos: Integer; // position de travail
fSt: TGVString; // chaîne de travail
public
constructor Create; // constructeur
destructor Destroy; override; // destructeur
procedure Clear; // nettoyage
// conversion d'une liste en mot
function ListToWord(const St: string): string;
// conversion d'un mot en liste
function WordToList(const St: string): string;
// conversion d'une liste en chaîne
function ListToStr(const St: string): string; overload;
function ListToStr(const St: string; out ASt: string): Boolean; overload;
// conversion d'une chaîne en liste
function StrToList(const St: string): string;
// retourne la liste vide
function EmptyList: string;
// vérifie la validité d'une liste
function IsValid(const St: string): Boolean;
// teste la validité d'une valeur (mot ou liste)
function IsValidValue(const St: string): Boolean;
// liste simple ?
function IsSimpleList(const St: string): Boolean;
// notification d'une erreur
property Error: TGVErrors read fError write fError;
end;

En dehors de la fonction EmptyList qui se contente de renvoyer la liste vide, il s'agit essentiellement de méthodes de conversion et de validation.

Ainsi ListToStr et StrToList convertissent une liste en chaîne et réciproquement. ListToWord et WordToList prennent en compte le caractère d'échappement. Pour ces quatre méthodes, un contrôle de la validité des listes est effectué.

Le contrôle de la validité d'une liste s'effectue grâce à IsValid qui est de loin la plus complexe. Elle analyse une chaîne et contrôle caractère par caractère que les règles relatives aux listes sont respectées : autant de crochets ouvrants que de crochets fermants, dans le bon ordre ; autant de parenthèses ouvrantes que de parenthèses fermantes, dans le bon ordre ; prise en compte du caractère d'échappement ; interdiction des sous-listes à l'intérieur d'une expression parenthésée.

Les autres méthodes de contrôle sont plus simples. IsSimpleValue se contente de vérifier la présence des crochets pour une liste. IsValidValue exploite l'unité GVWords pour tester un mot et la méthode précédente pour une liste. TestValue est une procédure qui déclenche une exception si la chaîne en entrée n'est ni un mot ni une liste au sens de GVLOGO.

II-D-4-c - La classe TGVList

La classe TGVList implémente la gestion des listes pour GVLOGO. Elle hérite de TStringList qui gère les listes de chaînes. En fait, une liste en LOGO est une chaîne particulière de caractères : elle nécessite d'être entourée de crochets et, si elle comprend d'autres listes, de comprendre à l'intérieur autant de crochets ouvrants que de crochets fermants (dans le bon ordre évidemment !). Cette dernière remarque s'applique aussi pour les éventuelles expressions imbriquées dans la liste. Pour parvenir à cet effet, il faut surcharger les méthodes d'entrée : Add, LoadFromStream, Assign, Insert et Put. Chaque élément de la liste (mot ou sous-liste) est stocké dans une chaîne de la TStringList.

Toutes les nouvelles méthodes renvoient une chaîne ou une valeur sans modifier la liste d'entrée. En cas d'erreur lors de la construction de la liste, la liste interne est la chaîne vide et la propriété LastErrorPos renvoie l'indice de l'erreur détectée.

En voici l'interface(7) :

TGVList = class(TStringList)
strict private
fRawStr: string; // chaîne brute en entrée
fError: TGVErrors; // traitement des erreurs
fIsValid: Boolean; // validité de la liste
fPos: Integer; // position de travail
fLoading: Boolean; // chargement en cours ?
fNumLastItem: Integer; // dernier élément trouvé
fWord: TGVWord; // mot de travail
fUtil: TGVListUtils; // utilitaire pour liste
protected
procedure Put(Index: Integer; const St: string); override; // assignation
function Get(Index: Integer): string; override; // élément choisi
function GetTextStr: string; override; // récupération du texte
procedure SetTextStr(const AValue: string); override; // validation du texte
public
constructor Create; overload; // création
destructor Destroy; override; // destruction
procedure Clear; override; // nettoyage
function Add(const St: string): Integer; override; // ajout
procedure LoadFromStream(Stream: TStream); overload; override; // chargement
procedure Assign(Source: TPersistent); override; // assignation
procedure Insert(Index: Integer; const St: string); override; // insertion
// nouvelles méthodes (*** ne modifient pas la liste interne ***)
// renvoie la liste sous forme de chaîne
function ToStr: string;
// renvoie la liste sous forme de chaîne sans crochets
function ToWBStr: string;
// la liste est-elle la liste vide ?
function IsEmptyList: Boolean;
// renvoie le premier élément de la liste
function First: string;
// renvoie le dernier élément de la liste
function Last: string;
// sauf le premier de la liste
function ButFirst: string;
// sauf le dernier de la liste
function ButLast: string;
// supprime l'élément N
function DeleteItem(N: Integer): string;
// insertion d'un élément en position N
function InsertAItem(N: Integer; const St: string): string;
// remplacement de l'élément N
function ReplaceItem(N: Integer; const St: string): string;
// met en premier
function PutFirst(const St: string): string;
// met en dernier
function PutLast(const St: string): string;
// phrase à droite
function SentenceRight(const St: string): string;
// phrase à gauche
function SentenceLeft(const St: string): string;
// tri des éléments
function SortItems: string;
// inversion des éléments
function ReverseItems: string;
// mélange des éléments
function ShuffleItems: string;
// membre présent ?
function IsItem(const St: string): Boolean;
// ajout d'une paire
function TwoAdd(const St1, St2: string): string;
// suppression d'une paire
function TwoDelete(const N: Integer): string;
// liste en majuscules
function UpperCase: string;
// liste en minuscules
function LowerCase: string;
// rotation de la liste
function Rotate: string;
// élément de la liste choisi au hasard
function AtRandom: string;
// premier élément de chaque élément d'une liste
function Firsts: string;
// tout sauf le premier élément de chaque élément d'une liste
function ButFirsts: string;
// dernier élément traité
property LastItem: Integer read fNumLastItem default -1;
// chaîne brute en entrée
property RawStr: string read fRawStr; // chaîne brute en entrée
// notification d'une erreur
property Error: TGVErrors read fError write fError;
// validité de la liste
property IsValid: Boolean read fIsValid default False;
// acquisition du texte
property Text: string read GetTextStr write SetTextStr;
end;

La méthode la plus complexe est certainement Add. Elle prend en charge l'ensemble du contrôle de la validité de la liste. Cette méthode comprend une partie générale qui teste les mots et les listes avant de renvoyer une éventuelle position d'erreur ou de stocker les chaînes trouvées, et une procédure imbriquée qui examine le contenu de la chaîne. Cette dernière lit les caractères un par un et répartit le travail suivant ce qu'elle rencontre : blanc, crochet ouvrant, parenthèse ouvrante, échappement, crochet et parenthèse fermants mais orphelins, autre caractère.

Elle est en cela similaire à la méthode IsValid de TGVListUtils, sinon qu'elle stocke les éléments en vue de leur exploitation. Du point de vue de l'utilisateur, l'entrée d'une nouvelle liste se fait grâce à la propriété héritée Text de l'unité : il suffit d'affecter une chaîne à cette propriété pour qu'elle soit analysée et stockée.

La propriété LastItem n'est affectée que par l'utilisation de la méthode IsItem. Si cette dernière renvoie True, LastItem contiendra le numéro de l'élément de la liste qui correspond à celui recherché.

On notera que les éléments des listes en Pascal sont numérotés avec une base de 0 alors qu'en GVLOGO la base est de 1, ce qui paraîtra plus naturel à l'utilisateur.

II-D-4-d - Test de l'unité TGVLists

Comme lors du test de TGVWords, les versions proposées sont au nombre de deux :

- une version Win32 ;

- une version Linux.

Le programme lui-même ne pose pas de problèmes particuliers. Il ne s'agit en effet que de mettre à jour la liste de travail et d'appliquer la méthode voulue.

Il est encore une fois conseillé de modifier les différents paramètres pour observer les réactions de l'unité.

II-E - Les listes de propriétés

II-E-1 - Définitions

Les listes de propriétés sont des listes particulières associant chacune à un nom des couples propriété-valeur.

Par exemple, on peut imaginer répertorier des chiens dont les caractéristiques seront stockées ainsi : Zoé aura pour propriétés race, sexe, couleur, taille et âge dont les valeurs respectives seront caniche, femelle, blanc, très petit, 5. Médor aura les propriétés race, sexe, âge, caractère dont les valeurs seront teckel, mâle, 3, tranquille. On voit que l‘agencement des propriétés est libre, contrairement à ce qui se passerait pour un tableau.

GVLOGO fait un usage interne intensif des listes de propriétés. En effet, aussi bien les variables, les procédures, les paquets que les lignes des éditeurs sont des listes de propriétés.

Les noms donnés aux propriétés tout comme le nom des listes est indifférent aux majuscules et minuscules. Par conséquent, les propriétés MAPROP, MaProp ou encore maprop sont équivalentes. En revanche, ce nom doit être un identificateur correct.

II-E-2 - Exemples de listes de propriétés

- Liste associée à "caniche" : [couleur blanc taille [tout petit]]

- Liste associée à "labrador" : [couleur blanc vitesse [rapide] ans 8]

Les propriétés d'une liste de propriétés sont organisées en deux éléments consécutifs : le premier définit le nom de la propriété tandis que le second indique la valeur qui lui est associée. Le premier est toujours un mot unique pour la liste considérée alors que le second peut être un mot ou une liste quelconques.

II-E-3 - Opérations sur les listes de propriétés

DPROP : attend deux mots en entrée suivis d'un mot ou une liste – ne renvoie rien – la primitive crée ou met à jour la propriété de la liste de propriétés fournie en paramètre. Le premier mot est la liste de propriétés et le second la propriété. Le dernier paramètre est soit un mot soit une liste qui définit la valeur attribuée à la propriété.

Exemple :

- DPROP "MALISTE "MAPROP [une valeur] → -

RPROP : attend deux mots en entrée – renvoie un mot ou une liste – la primitive renvoie la valeur de la propriété (second paramètre) de la liste visée (premier paramètre).

Exemple :

- ECRIS RPROP "MALISTE "MAPROP → une valeur

ANPROP : attend deux mots en entrée – ne renvoie rien – la propriété (second paramètre) de la liste spécifiée (premier paramètre) est supprimée.

Exemple :

- ANPROP "MALISTE "MAPROP → -

PROPS : attend un mot en entrée – renvoie une liste – la primitive renvoie la liste des propriétés de la liste en entrée.

Exemple :

- ECRIS PROPS "MALISTE → maprop

ANNULE : attend un mot en entrée – ne renvoie rien – la liste spécifiée par le mot est détruite avec toutes ses propriétés.

Exemple :

- ANNULE "MALISTE → -

COMPTE.PROPS : attend un mot en entrée – renvoie un entier – la primitive renvoie le nombre de propriétés associées à la liste en entrée.

Exemple :

- COMPTEPROPS "MALISTE → 1

PROP? : attend deux mots en entrée – renvoie un booléen – la primitive renvoie "VRAI si le second mot en entrée est une propriété du premier, "FAUX sinon

Exemple :

- ECRIS PROP? "MALISTE "MAPROP → -1

LISTE.PROP? : attend un mot en entrée – renvoie un booléen – la primitive renvoie "VRAI si le mot en entrée est une liste de propriétés, "FAUX sinon.

Exemple :

- ECRIS LISTEPROP? "MALISTE → -1

PROCEDURE? : attend un mot en entrée – renvoie un booléen – la primitive renvoie "VRAI si le mot en entrée est une procédure, "FAUX sinon.

Exemple :

- ECRIS PROCEDURE? "MALISTE → 0

PRIMITIVE? : attend un mot en entrée – renvoie un booléen – la primitive renvoie "VRAI si le mot en entrée est une primitive, "FAUX sinon.

Exemple :

- ECRIS PRIMITIVE? "ECRIS → 0

On notera que la primitive (ou la procédure) est précédée des guillemets anglais. Si elle ne l'était pas, GVLOGO chercherait à l'exécuter, ce qui n'est pas l'effet voulu.

NOM? : attend un mot en entrée – renvoie un booléen – la primitive renvoie "VRAI si le mot en entrée est une variable, "FAUX sinon.

Exemple :

- ECRIS NOM? "MALISTE → 0

II-E-4 - Implémentation des listes de propriétés

L'unité GVPropLists implémente essentiellement deux classes : TGVPropListEnumerator pour l'énumération et TGVPropList pour les listes elles-mêmes.

II-E-4-a - Constantes

Comme pour les unités précédentes, l'unité GVConsts a été complétée afin de prendre en compte les nouveaux besoins. Les ajouts ont été effectués au niveau des constantes elles-mêmes.

On remarquera l'extension pour les fichiers et surtout l'en-tête qui permettra de vérifier qu'un fichier est conforme au format attendu : il précèdera les données proprement dites.

Les chaînes de ressources ont aussi été complétées, de même pour l'énumération des erreurs possibles.

II-E-4-b - La classe TGVPROPLISTENUMERATOR

Comme son nom l'indique, l'énumération est un moyen de dresser la liste des éléments contenus dans un objet. Aussi bien Lazarus que Delphi en font un usage étendu à travers les objets tels que les listes.

La technique d'implémentation, finalement assez simple, obéit à un schéma type : on doit fournir l'élément en cours (pointé par une propriété en lecture seule) et être capable d'indiquer si l'on peut se déplacer vers l'élément suivant.

Cela donne dans le cas des listes de propriétés l'interface suivante :

type
// *** classe pour énumération ***
TGVPropListEnumerator = class(TObject)
private
fLst: TStringList;
fIndex: Integer;
protected
function GetCurrent: string; virtual; // élément en cours
public
constructor Create(const Value: TStrings); // création
destructor Destroy; override; // destruction
function MoveNext: Boolean; // vers le suivant
property Current: string read GetCurrent; // valeur de l'élément courant
end;

Comme les listes de propriétés fonctionnent via une liste interne, il suffit d'en manipuler les éléments grâce à un index.

La gestion se fait au moyen du système intégré dans TStringList des paires Names/Values, mais utilise un caractère séparateur personnalisé : | (8). Ce caractère ne doit par conséquent pas figurer dans la liste elle-même.

Les utilisateurs de Delphi pourront imaginer une unité fondée sur la classe TDictionary.

II-E-4-c - La classe TGVPROPLIST

La classe TGVPropList fonctionne grâce à un champ privé fNames de type TStringList. Les méthodes sont organisées en trois blocs : méthodes générales, celles concernant les listes de propriétés et celles concernant les propriétés.

En voici l'interface :

// *** classe des listes de propriétés ***
TGVPropList = class(TObject)
strict private
fError: TGVErrors; // enregistrement d'une erreur
fNames: TStringList; // listes
fOnchange: TNotifyEvent; // notification de changement
function GetLPByNum(N: Integer): string; // liste par numéro
function GetLPByName(const Name: string): string; // renvoie liste par nom
procedure SetLPByName(const Name, Value: string); // fixe liste par nom
protected
procedure Change; // changement
public
// constructeur de la classe
constructor Create;
// destructeur de la classe
destructor Destroy; override; // destructeur
// énumération
function GetEnumerator: TGVPropListEnumerator;
// nettoie les listes de propriétés
procedure Clear;
// *** listes de propriétés ***
// renvoie la liste des listes de propriétés
function ListP: string;
// la liste existe-t-elle ?
function IsListP(const Name: string): Boolean;
// renvoie le numéro d'une liste de propriétés
function NumListP(const Name: string): Integer;
// crée ou met à jour la liste de propriétés
function UpDateListP(const Name, Prop, Value: string): Boolean;
// renvoie la valeur d'une liste
function ValListP(const Name: string): string;
// destruction d'une liste de propriétés
function RemoveListP(const Name: string): Boolean;
// valeur d'une liste de propriétés par numéro
function ValNumListP(N: Integer; out Name, Value: string): Boolean;
// la propriété N existe-t-elle?
function IsListPByNum(N: Integer): Boolean;
// renvoie le nombre de listes de propriétés
function CountListP: Integer;
// chargement des listes
procedure LoadFromFile(const FileName: string);
// sauvegarde des listes
procedure SaveToFile(const FileName: string);
// *** propriétés
// la propriété existe-t-elle ?
function IsProp(const Name, Prop: string): Boolean;
// renvoie le numéro d'une propriété
function NumProp(const Name, Prop: string): Integer;
// valeur d'une propriété
function ValProp(const Name, Prop: string): string;
// destruction d'une propriété
function RemoveProp(const Name, Prop: string): Boolean;
// renvoie le nombre de propriétés attachées à une liste
function CountProps(const Name: string): Integer;
// valeur d'une propriété par numéro
function ValNumProp(const Name: string; N: Integer; out Prop: string)
: Boolean; overload;
function ValNumProp(const Name: string; N: Integer): string;
// liste des propriétés d'une liste
function ListOfProps(const Name: string): string;
// nom d'une propriété par numéro
function NameOfProp(const Name: string; N: Integer): string; overload;
// changement dans la liste de propriétés
property OnChange: TNotifyEvent read fOnchange write fOnchange;
// liste de propriétés par numéro
property ListPByNum[N: Integer]: string read GetLPByNum; default;
// liste de propriétés par nom
property LPByName[const Name: string]: string read GetLPByName
write SetLPByName;
// notification d'une erreur
property Error: TGVErrors read fError write fError;
end;

On remarquera que la propriété ListPByNum est définie comme default, ce qui signifie qu'elle peut être omise en employant simplement le numéro de la liste entre crochets.

Parmi les difficultés, on relèvera :

- Les listes de propriétés sont numérotées à partir de 1 et non de 0 comme en Pascal ;

- La méthode RemoveProp détruit aussi la liste si elle ne contient plus aucune propriété ;

- La méthode UpdateListP crée et met à jour les listes et les propriétés ;

- Il est fait un usage intensif de la fonction Odd qui vérifie qu'un nombre est impair : en effet, comme les propriétés sont organisées par paires, il est nécessaire de bien différencier le nom de la propriété (élément impair) de sa valeur (élément pair).

II-E-4-d - Test de l'unité TGVPROPLists

Comme d'habitude, le programme de test est décliné en deux versions :

- Lazarus pour Windows 32 ;

- Lazarus pour Linux.

Un fichier de listes préenregistrées permet de tester immédiatement LoadFromFile : « ListPs.GPL ». Il peut être édité par n'importe quel lecteur de textes simples (genre NotePad).

II-F - La tortue graphique

II-F-1 - Présentation

La tortue graphique est sans doute l'objet le plus connu du langage LOGO. Elle a fait son succès, mais l'a aussi très longtemps limité à une utilisation avec des enfants, conduisant les utilisateurs à négliger les listes qui permettent d'aborder des sujets plus ardus.

La tortue graphique est habituellement symbolisée par un triangle sur une surface de dessin. GVLOGO propose par ailleurs une tortue plus réaliste au format « png ». Des ordres permettent de la faire dessiner. Les commandes disponibles sont multiples : l'utilisateur maîtrise son orientation, son trait, sa forme, sa couleur…

L'originalité de ce mode de dessin réside dans sa capacité à partir d'une orientation dans l'espace déterminée par la tortue elle-même : même s'il est possible de dessiner dans un repère orthonormé traditionnel, la plupart des commandes sont dépendantes de l'état en cours de la tortue. Ainsi, tourner de 90° à gauche se fera par rapport à l'orientation actuelle de la tortue : si son cap était de 45° par rapport à la surface de dessin, son nouveau cap sera de 135°.

Par défaut, la tortue est au centre de la surface de dessin qui se mesure en pixels et dépend de la taille du composant TPanel d'accueil : sa position d'origine est donc le centre de ce composant. Ses déplacements se comptent en pixels si les échelles des abscisses et des ordonnées sont fixées à 100 (valeur par défaut). Un déplacement négatif fait reculer la tortue.

Le cap de la tortue indique la direction des déplacements. Il est exprimé en degrés et va de 0 à 360. Le cap est de 90° par défaut et pointe vers le sommet de l'écran afin d'être en conformité avec le cercle trigonométrique. On visualise ce cap grâce à l'orientation du triangle qui symbolise la tortue : l'arrière de la tortue est un trait plus épais ; sa tête est donc le sommet du triangle qui marque l'intersection des deux lignes plus fines.

II-F-2 - Opérations avec la tortue

II-F-2-a - Champ de la tortue

Le champ de la tortue est la surface sur laquelle elle peut évoluer. Le coin inférieur gauche de l'écran est la position d'abscisse 0 et d'ordonnée 0. Par défaut, le point supérieur droit a pour abscisse la largeur du composant d'accueil et pour ordonnée sa hauteur(9).

CLOS : n'attend rien en entrée – ne renvoie rien – le champ de la tortue est limité à l'écran visible. Toute tentative de sortir de l'écran se soldera par un échec.

Exemple :

- CLOS → -

Le champ est défini à FENETRE par défaut.

ENROULE : n'attend rien en entrée – ne renvoie rien – le champ de la tortue s'enroule sur lui-même : quand la tortue atteint un bord, elle réapparaît sur le bord opposé.

Exemple :

- ENROULE → -

FENETRE (raccourci : FEN) : n'attend rien en entrée – ne renvoie rien – le champ de la tortue s'étend au-delà de l'écran. La tortue peut donc disparaître du champ de vision de l'utilisateur.

Exemple :

- FENETRE → -

ETAT.ECRAN : n'attend rien en entrée – renvoie une liste – la liste renvoyée indique l'état actuel de l'écran avec les données suivantes : couleur, hauteur et largeur.

Exemple :

- ECRIS ETAT.ECRAN → 0 700 677

FIXE.ECHELLE : attend une liste de deux entiers en entrée – ne renvoie rien – la primitive détermine les proportions du déplacement de la tortue. Par défaut, la valeur pour les abscisses (premier entier) et pour les ordonnées (second paramètre) est de 100. Une valeur moindre réduira les déplacements dans la proportion indiquée et une valeur supérieure l'augmentera. Par exemple, fixer le premier entier à 200 et le second à 50 fera que la tortue se déplacera deux fois plus vite horizontalement que la normale et deux fois plus lentement verticalement.

Exemple :

- FIXE.ECHELLE [200 50] → -

FIXE.ECHELLEX : attend un entier en entrée – ne renvoie rien – la primitive détermine la proportion du déplacement de la tortue selon l'axe des abscisses.

Exemple :

- FIXE.CHELLEX 200 → -

FIXE.ECHELLEY : attend un entier en entrée – ne renvoie rien – la primitive détermine la proportion du déplacement de la tortue selon l'axe des ordonnées.

Exemple :

- FIXE.ECHELLEY 50 → -

ECHELLE : n'attend rien en entrée – renvoie une liste de deux entiers – la liste renvoyée contient un premier entier qui indique l'échelle des déplacements de la tortue selon l'axe des abscisses et un second entier qui indique l'échelle selon l'axe des ordonnées.

Exemple :

- ECRIS ECHELLE → 100 100

VIDE.ECRAN (raccourci : VE) : n'attend rien en entrée – ne renvoie rien – l'espace de la tortue est nettoyé et la tortue retrouve sa place et son cap d'origine, ainsi que toutes ses valeurs par défaut.

Exemple :

- VIDE.ECRAN → -

ORIGINE : n'attend rien en entrée – ne renvoie rien – la tortue retrouve sa position d'origine en [300 300] et son cap initial de 90. L'état du crayon n'est pas modifié. Si le crayon est baissé, le déplacement de la tortue laisse une trace.

Exemple :

- ORIGINE → -

NETTOIE : n'attend rien en entrée – ne renvoie rien – l'espace de la tortue est nettoyé, sans changer la position de la tortue ni son cap, pas plus que les valeurs du crayon.

Exemple :

- NETTOIE → -

FIXE.COULEUR.FOND (raccourci : FCF) : attend un entier en entrée – ne renvoie rien – la primitive détermine la couleur de fond de l'écran de la tortue.

Exemple :

- FIXE.COULEUR.FOND 19 → -

Tableau 1 - Couleurs standards de la tortue

Noir

0

Bleu ciel

1

Bleu

2

Crème

3

Gris foncé

4

Fuchsia

5

Gris

6

Vert

7

Vert citron

8

Gris clair

9

Marron

10

Gris moyen

11

Vert menthe

12

Bleu marine

13

Vert olive

14

Violet

15

Rouge

16

Argent

17

Bleu ciel

18

Sarcelle

19

Blanc

20

Jaune

21

COULEUR.FOND (raccourci : CF) : n'attend rien en entrée – renvoie un entier – l'entier renvoyé correspond à la couleur du fond de l'écran de la tortue.

Exemple :

- ECRIS COULEUR.FOND → 19

DISTANCE : attend deux entiers en entrée – renvoie un entier – l'entier renvoyé correspond à la distance de la tortue au point de coordonnées fournies en entrée (abscisse puis ordonnée).

Exemple :

- ECRIS DISTANCE 50 50 → 190

II-F-2-b - Déplacements de la tortue

AVANCE (raccourci : AV) : attend un entier en entrée – ne renvoie rien – la tortue avance du nombre de pixels indiqués par le paramètre en entrée, sauf si les échelles ont été changées. Elle laisse une trace sur la zone de dessin à moins que son crayon ne soit levé ou qu'elle soit en mode gomme.

Exemple :

- AVANCE 45 → -

RECULE (raccourci : RE) : attend un entier en entrée – ne renvoie rien – la tortue recule du nombre de pixels indiqués par le paramètre en entrée, sauf si les échelles ont été changées. Elle laisse une trace sur la zone de dessin à moins que son crayon ne soit levé ou qu'elle soit en mode gomme.

Exemple :

- RECULE 50 → -

GAUCHE (raccourci : TG) : attend un réel en entrée – ne renvoie rien – la tortue tourne à gauche du nombre de degrés indiqué en entrée.

Exemple :

- GAUCHE 90 → -

Pour rappel : la gauche indiquée est celle par rapport à la tortue et non la gauche de l'écran ! Cette remarque vaut évidemment pour la primitive DROITE.

DROITE (raccourci : TD) : attend un réel en entrée – ne renvoie rien – la tortue tourne à droite du nombre de degrés indiqué en entrée.

Exemple :

- DROITE 90 → -

FIXE.POS (raccourci : FPOS) : attend une liste de deux entiers en entrée – ne renvoie rien – la tortue est envoyée à la position indiquée par la liste : le premier élément fixe l'abscisse tandis que le second détermine l'ordonnée.

Exemple :

- FIXE.POS [200 100] → -

FIXE.XY (raccourci : FXY) : attend deux entiers en entrée – ne renvoie rien – la tortue est envoyée à la position indiquée par les paramètres : le premier élément fixe l'abscisse tandis que le second détermine l'ordonnée.

Exemple :

- FIXE.XY 200 100 → -

FIXE.X (raccourci : FX) : attend un entier en entrée – ne renvoie rien – l'abscisse de la tortue est fixée à l'entier fourni en entrée.

Exemple :

- FIXE.X 200 → -

FIXE.Y (raccourci : FY) : attend un entier en entrée – ne renvoie rien – l'ordonnée de la tortue est fixée à l'entier fourni en entrée.

Exemple :

- FIXE.Y 100 → -

POS : n'attend rien en entrée – renvoie une liste de deux entiers – la liste renvoyée contient un premier entier qui indique l'abscisse de la tortue et un second entier qui indique son ordonnée.

Exemple :

- ECRIS POS → 200 100

XCOOR : n'attend rien en entrée – renvoie un entier – l'entier renvoyé indique l'abscisse de la tortue.

Exemple :

- ECRIS XCOOR → 200

YCOOR : n'attend rien en entrée – renvoie un entier – l'entier renvoyé indique l'ordonnée de la tortue.

Exemple :

- ECRIS YCOOR → 100

FIXE.VITESSE : attend un entier en entrée – ne renvoie rien – la vitesse de la tortue est fixée à l'entier fourni en entrée.

Exemple :

- FIXEVITESSE 100 → -

La vitesse est relative aux performances de l'ordinateur sur lequel est installé GVLOGO. Elle est automatiquement limitée à 100.

VITESSE : n'attend rien en entrée – renvoie un entier – l'entier renvoyé indique la vitesse de dessin de la tortue.

Exemple :

- ECRIS VITESSE → 100

II-F-2-c - Cap de la tortue

FIXE.CAP (raccourci : FCAP) : attend un réel en entrée – ne renvoie rien – le cap de la tortue est fixé au nombre fourni en entrée. Le cap est compris entre 0 et 360.

Exemple :

- FIXECAP 90 → -

CAP : n'attend rien en entrée – renvoie un entier – l'entier renvoyé indique le cap de la tortue.

Exemple :

- ECRIS CAP → 90

VERS : attend une liste de deux entiers en entrée – renvoie un entier – L'entier renvoyé indique le cap que devrait avoir la tortue pour pointer vers le point dont les coordonnées sont fournies en entrée.

Exemple :

- VERS [0 0] → 214

Pour rappel : le cap 90 est celui qui pointe vers le haut de l'écran. Le cap 0 pointe vers sa droite. En fait, le cap augmente en tournant dans le sens inverse des aiguilles d'une montre.

II-F-2-d - État de la tortue

MONTRE.TORTUE (raccourci : MT) : n'attend rien en entrée – ne renvoie rien – la tortue devient visible.

Exemple :

- MONTRE.TORTUE → -

CACHE.TORTUE (raccourci : CT) : n'attend rien en entrée – ne renvoie rien – la tortue devient invisible.

Exemple :

- CACHE.TORTUE → -

La tortue dessine plus rapidement lorsqu'elle est invisible.

VISIBLE? : n'attend rien en entrée – renvoie un booléen – la primitive renvoie "VRAI si la tortue est visible, "FAUX sinon.

Exemple :

- ECRIS VISIBLE? → -1

ETAT.TORTUE : n'attend rien en entrée – renvoie une liste – la liste renvoyée fournit les données relatives à la tortue : son abscisse, son ordonnée, son cap, sa taille, sa vitesse, sa visibilité et si elle est figurée par un triangle.

Exemple :

- ECRIS ETAT.TORTUE → 300 300 98 8 100 -1 0

FIXE.ETAT.TORTUE : attend une liste en entrée – ne renvoie rien – l'état de la tortue est fixé selon le contenu de la liste : abscisse, ordonnée, orientation, taille, vitesse, visibilité et type de tortue. La tortue dessine durant le changement si le crayon est baissé.

Exemple :

- FIXE.ETAT.TORTUE [100 100 0 8 100 -1 -1] → -

TORTUE.NORMALE : n'attend rien en entrée – ne renvoie rien – quand elle est visible, la tortue est symbolisée par un triangle. C'est le mode par défaut.

Exemple :

- TORTUE.NORMALE → -

TORTUE.VERTE : n'attend rien en entrée – ne renvoie rien – quand elle est visible, la tortue est dessinée à partir d'images au format PNG.

Exemple :

- TORTUE.VERTE → -

L'image est plus sympathique, mais le dessin est un peu plus lent et la précision pour les angles moindre (5°).

FIXE.TAILLE : attend un entier en entrée – ne renvoie rien – la taille de la tortue est fixée au nombre fourni en entrée.

Exemple :

- FIXE.TAILLE 10 → -

La taille ne peut excéder 20 et ne concerne que le type par défaut, à savoir le triangle. La valeur par défaut est de 8.

TAILLE : n'attend rien en entrée – renvoie un entier – l'entier renvoyé indique la taille actuelle de la tortue.

Exemple :

- ECRIS TAILLE → 8

Si la tortue est la tortue png, la taille renvoyée n'aura pas de sens puisqu'elle ne varie pas !

II-F-2-e - Le crayon

BAISSE.CRAYON (raccourci : BC) : n'attend rien en entrée – ne renvoie rien – le crayon est baissé et donc dessine avec les attributs qui lui ont été affectés. C'est la position par défaut du crayon.

Exemple :

- BAISSE.CRAYON → -

LEVE.CRAYON (raccourci : LC) : n'attend rien en entrée – ne renvoie rien – le crayon est levé et donc ne dessine pas lorsque la tortue est déplacée.

Exemple :

- LEVE.CRAYON → -

BAISSE? : n'attend rien en entrée – renvoie un booléen – la primitive renvoie "VRAI si le crayon est baissé, "FAUX sinon.

Exemple :

- ECRIS BAISSE? → -1

FIXE.COULEUR.CRAYON (raccourci : FCC) : attend un entier en entrée – ne renvoie rien – la primitive détermine la couleur d'écriture du crayon de la tortue(10).

Exemple :

- FIXE.COULEUR.CRAYON 5 → -

COULEUR.CRAYON (raccourci : CC) : n'attend rien en entrée – renvoie un entier – l'entier renvoyé correspond à la couleur du crayon de la tortue.

Exemple :

- ECRIS COULEUR.CRAYON → 5

FIXE.TAILLE.CRAYON : attend un entier en entrée – ne renvoie rien – l'épaisseur du trait du crayon de la tortue est fixée au nombre fourni en entrée.

Exemple :

- FIXE.TAILLE.CRAYON 2 → -

TAILLE.CRAYON : n'attend rien en entrée – renvoie un entier – l'entier renvoyé correspond à l'épaisseur de trait du crayon de la tortue.

Exemple :

- ECRIS TAILLE.CRAYON → 2

INVERSE.CRAYON : n'attend rien en entrée – ne renvoie rien – le crayon écrit dans la couleur complémentaire de la couleur en cours.

Exemple :

- INVERSE.CRAYON → -

GOMME : n'attend rien en entrée – ne renvoie rien – le crayon efface en avançant.

Exemple :

- GOMME → -

Si la couleur de l'écran est modifiée, les traits effacés réapparaîtront.

NORMAL : n'attend rien en entrée – ne renvoie rien – le crayon n'inverse plus et n'efface plus.

Exemple :

- NORMAL → -

ETAT.CRAYON : n'attend rien en entrée – renvoie une liste – la liste renvoyée fournit les données relatives au crayon de la tortue : sa couleur, son épaisseur, s'il écrit ou non, si l'écriture est inversée ou non.

Exemple :

- ECRIS ETAT.CRAYON → 1 1 -1 0

FIXE.CRAYON ou FIXE.ETAT.CRAYON (raccourci : FEC) : attend une liste en entrée – ne renvoie rien – l'état du crayon est fixé selon le contenu de la liste : couleur, épaisseur, écriture ou non, inversion ou non.

Exemple :

- FIXE.ETAT.CRAYON [1 2 -1 0] → -

II-F-2-f - Formes prédéfinies

GVLOGO fournit plusieurs formes prédéfinies qui seront dessinées soit à une position indiquée par l'utilisateur soit par rapport à la position de la tortue. Ces formes ne sont pas orientables suivant un angle (par exemple, celui de l'orientation de la tortue), mais de telles formes sont facilement programmables en LOGO lui-même.

Afin d'éviter un conflit entre le nom des éventuelles procédures définies par l'utilisateur et celui des primitives de dessins de formes, on emploie pour nommer ces dernières le préfixe F_.

F.RECTANGLE : attend une liste en entrée – ne renvoie rien – un rectangle est dessiné avec les coordonnées de deux points fournies en entrée(11).

Exemple :

- F.RECTANGLE [100 100 200 200] → -

F.RECTANGLE.ARRONDI : attend une liste en entrée – ne renvoie rien – un rectangle arrondi est dessiné avec les coordonnées de deux points fournies en entrée.

Exemple :

- F.RECTANGLE.ARRONDI [50 100 150 200] → -

F.CARRE : attend une liste en entrée – ne renvoie rien – un carré est dessiné avec les coordonnées du point supérieur gauche suivies de la longueur de son côté.

Exemple :

- F.CARRE [20 20 50] → -

F.ELLIPSE : attend une liste en entrée – ne renvoie rien – une ellipse est dessinée avec les coordonnées de deux points fournies en entrée.

Exemple :

- F.ELLIPSE [100 100 200 200] → -

F.CERCLE : attend une liste en entrée – ne renvoie rien – un cercle est dessiné avec les coordonnées du premier point du cercle suivies de la longueur de son rayon.

Exemple :

- F.CERCLE [20 20 50] → -

REMPLIS : n'attend rien en entrée – ne renvoie rien – les figures seront pleines et par conséquent recouvriront celles qui seront placées dans leur espace de dessin.

Exemple :

- REMPLIS → -

LAISSE.VOIR : n'attend rien en entrée – ne renvoie rien – les figures seront transparentes et par conséquent ne recouvriront pas celles qui seront placées dans leur espace de dessin.

Exemple :

- LAISSE.VOIR → -

II-F-2-g - Texte avec la tortue

La tortue peut écrire à son emplacement. Le texte aura alors sa couleur et son orientation. L'utilisateur peut aussi écrire à un emplacement défini par lui avec l'orientation, la taille et la couleur de son choix.

TEXTE.TORTUE : attend un mot ou une liste en entrée – ne renvoie rien – le texte spécifié en paramètre est écrit sur l'écran de la tortue en utilisant ses coordonnées, sa taille, sa couleur et son orientation.

Exemple :

- TEXTE.TORTUE [Bonjour mon ami !] → -

FIXE.ANGLE.TEXTE : attend un entier en entrée – ne renvoie rien – le texte sera écrit avec l'angle spécifié en entrée, selon le même principe que l'orientation de la tortue.

Exemple :

- FIXE.ANGLE.TEXTE 90 → -

ANGLE.TEXTE : n'attend rien en entrée – renvoie un entier – la primitive renvoie l'angle actuel d'écriture d'un texte sur l'écran de la tortue.

Exemple :

- ECRIS ANGLE.TEXTE → 90

FIXE.TAILLE.FONTE : attend un entier en entrée – ne renvoie rien – le texte sera écrit avec la taille de caractère spécifiée en entrée, si cette taille est possible pour la police en cours.

Exemple :

- FIXE.TAILLE.FONTE 16 → -

TAILLE.FONTE : n'attend rien en entrée – renvoie un entier – la primitive renvoie la taille actuelle d'écriture d'un texte sur l'écran de la tortue.

Exemple :

- ECRIS TAILLE.FONTE → 16

FIXE.COULEUR.TEXTE (raccourci : FCT) : attend un entier en entrée – ne renvoie rien – le texte sera écrit avec la couleur de caractère spécifiée en entrée.

Exemple :

- FIXE.COULEUR.TEXTE 5 → -

COULEUR.TEXTE : n'attend rien en entrée – renvoie un entier – la primitive renvoie la couleur actuelle d'écriture d'un texte sur l'écran de la tortue.

Exemple :

- ECRIS COULEUR.TEXTE → 5

TEXTE.LIBRE.TORTUE : attend deux entiers puis un mot ou une liste en entrée – ne renvoie rien – le texte indiqué par le mot ou la liste fourni en paramètre est écrit sur l'écran de la tortue en utilisant les entiers comme coordonnées (abscisse et ordonnée). La taille, la couleur et l'orientation sont celles définies par les primitives définies ci-dessus.

Exemple :

- TEXTE.LIBRE.TORTUE 100 150 [Bonjour mon ami !] → -

II-F-3 - Implémentation de la tortue

L'implémentation de la tortue pose trois types de problèmes :

- mathématiques : il faut se plonger dans quelques formules de trigonométrie afin de tenir compte de l'orientation de la tortue pendant ses déplacements ;

- graphiques : il faut gérer un fond indépendant du tracé et une tortue qui doit laisser une trace en se déplaçant à la manière d'un sprite(12) ;

- de communication par événements : une tortue personnalisée sera tributaire des dessins envoyés par l'application tandis que le fond dépendra du contrôle sous-jacent à la surface de la tortue (généralement un panneau). Il est aussi intéressant de notifier tout changement intervenu en rapport avec la tortue, par exemple pour afficher son état dans la barre de statut.

Afin de tenir compte de ces problèmes, l'implémentation met en œuvre une bibliothèque tierce qui ne fonctionne qu'avec Lazarus : BGRABitmap. Cette bibliothèque propose un ensemble d'outils pour créer et manipuler des images avec transparence. Elle est libre de droits et régulièrement mise à jour via le projet LazPaint. Outre les améliorations citées, résolues à travers la gestion d'un canal alpha pour la transparence, elle prend en charge l'anti-alias qui permet de réaliser des dessins de meilleure qualité, sans crénelages désagréables. Le seul point noir est qu'elle ne fonctionne qu'avec Lazarus, mais ceci n'est pas vraiment gênant puisque le projet s'appuie avant tout sur cet EDI.

II-F-3-a - Constantes

L'unité GVConsts n'est modifiée que modestement par l'ajout de quelques constantes :

// *** tortue graphique ***
// couleurs de base
CColors: array[0..19] of TColor = (clBlack, clMaroon, clGreen, clOlive,
clNavy, clPurple, clTeal, clGray, clSilver, clRed, clLime, clYellow,
clBlue, clFuchsia, clAqua, clWhite, clMoneyGreen, clSkyBlue, clCream,
clMedGray);
DgToRad = Pi / 180; // pour les conversions en radians
RadToDg = 180 / Pi; // pour les conversions en degrés
CDefaultScale = 100; // échelle par défaut
CDefaultHeading = 90; // cap par défaut
CDefaultXY = 600; // taille écran tortue par défaut
CDefaultSize = 8; // taille d'une tortue par défaut
CMaxSize = 20; // taille maximale de la tortue
CMaxSpeed = 100; // vitesse maximum de la tortue
CDefaultPenColor = clWhite; // couleur de fond par défaut
CDefaultBackColor = clBlack; // couleur du crayon par défaut
CDefaultPenWidth = 1; // largeur du crayon par défaut

Quelques types viennent compléter le tout afin de définir le type d'écran et de tortue, ainsi qu'un enregistrement possible de l'état complet de la tortue :

type
// *** type d'écrans : enroulement, fenêtre illimitée ou champ clos ***
TScreenTurtle = (teWin, teGate, teRoll);

// *** types de tortue : triangle, dessin ou personnalisée (réservée) ***
TTurtleKind = (tkTriangle, tkPng, tkOwner);

// *** définition d'une tortue ***
TTurtle = record
rSaved: Boolean; // drapeau de sauvegarde
rX: Extended; // abscisse
rY: Extended; // ordonnée
rKind: TTurtleKind; // type de tortue
rSize: Integer; // taille de la tortue
rVisible: Boolean; // drapeau de visibilité
rHeading: Extended; // direction
rPenDown: Boolean; // drapeau de crayon baissé
rPenRubber: Boolean; // drapeau d'effacement
rScaleX: Integer; // échelle des X
rScaleY: Integer; // échelle des Y
rFilled: Boolean; // remplissage
rPenWidth: Integer; // largeur de crayon
rBrush: TBrush; // type de brosse
rPen: TPen; // type de crayon
rFont: TFont; // type de fonte
end;

II-F-3-b - Un peu de mathématiques

Afin de déterminer un déplacement de la tortue sur la surface de travail, il faut connaître ses coordonnées pas à pas en fonction de son cap. Pour cela, on utilisera les fonctions sinus et cosinus :

Aussi bien Lazarus que Delphi fournissent une procédure bien pratique(13) pour les déterminer : SinCos définie pour un réel étendu de la manière suivante :

procedure sincos(theta : float;out sinus;cosinus : float) ;

Theta est l'angle exprimé en radians et les sinus et cosinus sont renvoyés dans les paramètres appropriés.

Voici par exemple comment elle est utilisée dans la procédure Move qui déplace la tortue :

procedure TGVTurtle.Move(Value: Double);
// *** la tortue se déplace ***
var
LSinT, LCosT: Extended;
LTX, LTY: Double;
begin
// calcul du cosinus et du sinus du cap
SinCos((Heading - 90) * DgToRad, LSinT, LCosT);
// calcul des nouvelles coordonnées
LTX := fX - Value * LSinT * (ScaleX / CDefaultScale);
LTY := fY + Value * LCosT * (ScaleY / CDefaultScale);
SetPos(LTX, LTY); // déplacement si possible
end;

fX et fY sont les coordonnées avant le déplacement. On remarquera la transformation des degrés en radians pour l'orientation ainsi que l'utilisation des échelles pour déterminer le nombre réel de pixels à franchir. La position sera par la suite arrondie puisqu'on ne peut dessiner que des pixels à des positions entières.

De la même manière, lorsqu'on voudra dessiner le triangle représentant la tortue, des formules similaires seront utilisées :

procedure TGVTurtle.DrawTriangleTurtle;
// *** dessin de la tortue triangulaire ***
var
LCosT, LSinT: Extended;
LX1, LX2, LX3, LY1, LY2, LY3: Integer;
begin
// on efface la surface
fTtlImg.FillRect(0,0,fWidth, fHeight, BGRAPixelTransparent, dmSet);
// calcul des coordonnées des points de la tortue
SinCos((90 + Heading) * DgToRad, LSinT, LCosT);
LX1 := Round(CoordX + Size * LCosT - LSinT);
LY1 := cY(Round(CoordY + Size * LSinT + LCosT));
LX2 := Round(CoordX - Size * LCosT - LSinT);
LY2 := cY(Round(CoordY - Size * LSinT + LCosT));
LX3 := Round(CoordX - LCosT + (Size shl 1) * LSinT);
LY3 := cY(Round(CoordY - LSinT - (Size shl 1) * LCosT));
with fTtlImg.CanvasBGRA do
begin
if PenColor <> BGRAToColor(BGRAPixelTransparent) then
Pen.Color := PenColor
else
Pen.Color := CDefaultPenColor;
MoveTo(LX1, LY1); // dessin de la tortue
Pen.Width := 2;
LineTo(LX2, LY2);
Pen.Width := 1;
LineTo(LX3, LY3);
LineTo(LX1, LY1);
end;
end;

L'utilisation de shl au lieu d'une multiplication par deux traditionnelle est très efficace pour ce qui est de la vitesse d'exécution.

En ce qui concerne la tortue personnalisée, les données sont tout à fait différentes. Le choix a été fait de partir d'une série de « clichés » de la tortue au format « png », car il supporte très bien la transparence. Malheureusement, les algorithmes qui permettent les rotations sont complexes : c'est pourquoi la solution d'images préenregistrées a été choisie. Elle est par ailleurs plutôt rapide.

procedure TGVTurtle.DrawPNGTurtle;
// *** dessin de la tortue PNG ***
var
LCosT, LSinT: Extended;
LX, LY: Integer;
begin
// on efface la surface
fTtlImg.FillRect(0,0,fWidth, fHeight, BGRAPixelTransparent, dmSet);
BeforeChange; // récupère la bonne image
// calcul des coordonnées de la tortue
SinCos((90 + Heading) * DgToRad, LSinT, LCosT);
LX := Round(CoordX + LCosT - LSinT);
LY := Round(CoordY + LSinT + LCosT);
// copie de la tortue .png
fTtlImg.CanvasBGRA.Draw(LX - (fPNGTurtle.Width shr 1),
cY(LY) - (fPNGTurtle.Height shr 1), fPNGTurtle);
end;

Une autre fonction complexe est Towards qui permet de rendre l'orientation nécessaire de la tortue pour qu'elle pointe vers un point. Elle utilise la fonction mathématique ArcTan qui détermine un angle dont la tangente est donnée.

En voici le listing(14) :

function TGVTurtle.Towards(X, Y: Integer): Double;
// *** renvoie le cap vers un point ***
var
LPX, LPY: Integer;
begin
LPX := Round(CoordX) - X; // calcul des différences entre les points
LPY := Y - Round(CoordY);
Result := 0; // suppose 0
// évalue suivant les calculs
if ((LPX = 0) and (LPY < 0)) then
Result := 270
else if ((LPX = 0) and (LPY > 0)) then
Result := 90
else if ((LPX > 0) and (LPY >= 0)) then
Result := 180 - ArcTan(LPY / LPX) * RadToDg
else if ((LPX < 0) and (LPY > 0)) then
Result := (ArcTan(LPY / Abs(LPX)) * RadToDg)
else if ((LPX < 0) and (LPY <= 0)) then
Result := 360 - (ArcTan(LPY / LPX) * RadToDg)
else if ((LPX > 0) and (LPY < 0)) then
Result := 180 + (ArcTan(Abs(LPY) / LPX) * RadToDg);
end;

Enfin, la fonction Distance permet de calculer la distance entre la tortue et un point donné. Elle fait appel à de simples calculs à partir de triangles rectangles, donc en appliquant le théorème de Pythagore.

La formule bien connue est :

avec xa, ya les coordonnées du premier point et xb, yb celles du second.

La fonction en Pascal donne(15) :

function TGVTurtle.Distance(X, Y: Integer): Double;
// *** renvoie la distance de la tortue à un point donné ***
begin
Result := Sqrt(Sqr(X - CoordX) + Sqr(Y - CoordY));
end;

II-F-3-c - Se déplacer sur l'écran

En dehors de ces quelques notions de mathématiques, le déplacement de la tortue à l'écran pose des problèmes d'affichage. La solution adoptée est celle de la transparence bien maîtrisée par la bibliothèque BGRABitmap.

Sans cette bibliothèque, le comportement de la tortue avec Lazarus était erratique : la transparence fonctionne avec Lazarus dès qu'il s'agit de dessiner une forme simple sur le canevas ; mais si l'on désire déplacer la tortue sur une surface transparente qui prend sa couleur de fond sur un contrôle, le dessin en entier disparaît… du moins, dans la limite de mes connaissances !

Le dessin met en jeu trois couches : la première s'occupe du fond, la deuxième du dessin laissé par la tortue, la troisième par la représentation de cette dernière. On est ainsi assuré que les objets manipulés n'interféreront pas. La bibliothèque fournit en effet les éléments qui permettent de superposer ces couches en gérant la transparence nécessaire.

La méthode centrale se présente ainsi :

function TGVTurtle.GetImg: TBGRABitmap;
// *** récupération de l'image ***
begin
with fActualImg do // on procède par couches
begin
PutImage(0,0,fBckImg,dmDrawWithTransparency); // le fond
PutImage(0,0,fDrwImg,dmDrawWithTransparency); // le dessin
if TurtleVisible then // tortue visible ?
begin
case Kind of
tkTriangle: DrawTriangleTurtle; // on dessine la tortue triangulaire
tkPNG: DrawPNGTurtle; // tortue PNG
end;
PutImage(0,0,fTtlImg,dmDrawWithTransparency); // on l'affiche
end;
end;
Result := fActualImg; // on renvoie l'image
end;

On utilise fBckImg pour afficher le fond, fDrwImg pour le dessin et enfin fTtlImg pour la tortue. Les trois éléments sont du type TBGRABitmap. C'est la bibliothèque BGRABitmap qui s'occupe de fondre ces trois couches afin d'obtenir le dessin souhaité.

On notera aussi que les procédures LineTo et MoveTo adaptent les coordonnées fournies afin de les rendre cohérentes avec un repère dont l'origine est située en bas à gauche de la surface de dessin et non en haut à gauche comme en Pascal.

procedure TGVTurtle.LineTo(X, Y: Double);
// *** déplacement en écrivant ***
begin
fDrwImg.CanvasBGRA.LineTo(Round(X), cY(Round(Y))); // écriture effective
end;

procedure TGVTurtle.MoveTo(X, Y: Double);
// *** déplacement sans écrire ***
begin
fDrwImg.CanvasBGRA.MoveTo(Round(X), cY(Round(Y))); // déplacement effectif
end;

C'est la fonction cY qui est chargée du travail de modification du repère :

function TGVTurtle.cY(Y: Integer): Integer;
// *** change l'ordonnée pour le nouveau repère ***
begin
Result := fHeight - Y; // inversion des ordonnées
end;

Le déplacement proprement dit de la tortue est traité par la méthode DoGo qui prend en compte le type d'écran teRoll, c'est-à-dire l'enroulement. En voici l'implémentation :

procedure TGVTurtle.DoGo(X, Y: Double);
// *** effectue un déplacement de la tortue ***
begin
// si champ clos et hors limites => erreur
if (Screen <> teGate) or IsWithinLimits(X, Y) then
begin
fX := X; // nouvelle abscisse
fY := Y; // nouvelle ordonnée
// ralentit le dessin
Sleep(CMaxSpeed - Speed);
// dessine
if PenDown then  // crayon baissé ?
LineTo(X, Y) // en écrivant
else
MoveTo(X, Y); // sans écrire
Change; // changement notifié
end;
// on continue si l'écran s'enroule
if (Screen = teRoll) and not IsWithinLimits(X,Y) then
begin
// mise à jour des coordonnées après enroulement
// débordement à droite ?
if (X > fWidth) then
begin
MoveTo(0, Y);
X := X - fWidth;
end;
// débordement en bas ?
if (Y > fHeight) then
begin
MoveTo(X, 0);
Y := Y - fHeight;
end;
// débordement à gauche ?
if (X < 0) then
begin
MoveTo(fWidth, Y);
X := fWidth + X;
end;
// débordement en haut ?
if (Y < 0) then
begin
MoveTo(X, fHeight);
Y := fHeight + Y;
end;
fX := X; // nouvelle abscisse
fY := Y; // nouvelle ordonnée
// ralentit le dessin
Sleep(CMaxSpeed - Speed);
// dessine ou déplace suivant l'état du crayon
if PenDown then
LineTo(X, Y)
else
MoveTo(X, Y);
end;
end;

Bien que ce soit une erreur que de vouloir faire sortir la tortue de son espace alors qu'elle est en mode champ clos, GVLOGO se contente de ne pas déplacer la tortue dans ce cas. On pourrait aussi déclencher une exception, mais, d'un point de vue pédagogique, il a semblé plus intéressant de laisser à l'utilisateur le soin de comprendre ce qu'il se passe dans ce cas.

II-F-3-d - Les événements

La récupération de l'image désirée de la tortue est effectuée par le programme qui exploite la classe TGVTurtle. Même si les multiples liens entre les procédures et les unités rendent l'ensemble plutôt complexe, le principe de fonctionnement est simple : quand l'instance de la classe TGVTurtle a besoin de dessiner la tortue, elle exécute la procédure BeforeChange qui envoie en paramètre essentiel l'orientation de la tortue. En retour, elle s'attend à ce que l'unité appelée ou le programme ait affecté la bonne image à sa propriété TurtleImg.

Autrement dit, il s'agit d'une sorte d'aller-retour entre les deux unités : la première envoie un pointeur vers elle-même et l'orientation de la tortue si elle trouve une procédure affectée à son champ privé fOnBeforeChange ; la seconde récupère l'orientation de la tortue, cherche l'image correspondante et l'affecte en retour à la propriété TurtleImg de l'unité génératrice de l'événement.

La méthode BeforeChange est déclarée ainsi, dans la partie protected de la définition :

procedure BeforeChange; // avant le changement

Et voici sa propre définition :

procedure TGVTurtle.BeforeChange;
// *** gestion avant le changement ***
// (permet de mettre à jour une image pour la tortue avant de la dessiner)
begin
if Assigned(fOnBeforeChange) then  // si le gestionnaire existe
fOnBeforeChange(Self, Round(Heading)); // on l'exécute
end;

On voit que la procédure fait elle-même référence au champ privé fOnBeforeChange défini ainsi :

fOnBeforeChange: TTurtleBeforeEvent; // notification avant cap changé

Quant au type TTurtleBeforeEvent, il fournit un squelette de la procédure appelante :

// avant le changement de la tortue
TTurtleBeforeEvent = procedure(Sender: TObject; cHeading: Integer) of object;

Ce squelette est essentiel puisqu'en fournissant un modèle commun il permet aux unités de communiquer entre elles.

Voici à présent comment le programme de test opère pour répondre à la requête de l'unité GVTurtles :

procedure TMainForm.TurtleBeforePaint(Sender: TObject; cHeading: Integer);
// *** image associée à la tortue ***
var
BitM: TBitmap;
begin
// charge l'image de la tortue
BitM := TBitmap.Create;
try
// les images de la tortue sont proposées tous les 5 degrés
ImageListTurtles.GetBitmap(Round(cHeading) div 5, BitM);
// celle qui correspond est assignée au bitmap
GVTurtle.PNGTurtle.Assign(BitM);
finally
BitM.Free;
end;
end;

On remarquera que fOnBeforeChange de l'unité GVTurtles et TurtleBeforePaint du programme de test ont exactement le même en-tête calqué sur TTurtleBeforeEvent. Peu importe que les noms soient différents : ce qui compte, ce sont les paramètres absolument identiques dans leur nombre, leur agencement et leurs types.

Pour que l'ensemble fonctionne, il faut évidemment affecter la procédure appelée au gestionnaire d'événements approprié avec une ligne du genre :

procedure TMainForm.FormCreate(Sender: TObject);
begin
// on crée la tortue
GVTurtle := TGVTurtle.Create(imgTurtle.Width, imgTurtle.Height);
GVTurtle.OnChange := @TurtleState;  // gestionnaire de changement
GVTurtle.OnBeforeChange := @TurtleBeforePaint; // idem avant de dessiner
GVTurtle.ReInit; // initialisation
seSetPosX.Value := imgTurtle.Width shr 1; // position au centre
seSetPosY.Value := imgTurtle.Height shr 1;
end;

Ici, GVTurtle est une instance de la classe TGVTurtle. Cette ligne figure dans la méthode FormCreate de la fiche. Le programme « sait » à présent quelle procédure il doit appeler lorsque l'événement est déclenché.

Contrairement à Delphi, Lazarus exige l'emploi de @ devant la méthode pour l'affecter au gestionnaire d'événements.

Un second événement plus simple est déclenché à chaque modification de la tortue : il s'agit de TTurtleEvent :

type
// changement de la tortue
TTurtleEvent = TNotifyEvent;

Cet événement ne transmet qu'un pointeur vers l'instance de la classe qui a déclenché l'événement, mais permet ainsi d'accéder aux propriétés de la tortue susceptibles d'avoir été modifiées : ses coordonnées, son cap, l'état de sa visibilité, celui de sa capacité à écrire et la couleur d'écriture du crayon. L'intérêt d'une telle notification est, par exemple, de renseigner en temps réel une barre de statut qui affichera en clair ces données pour informer l'utilisateur.

Du côté de l'unité GVTurtles, on retrouve :

- un champ privé qui conservera la procédure affectée si elle existe :

fOnchange: TTurtleEvent; // changement notifié

- une propriété en accès public et renvoyant à ce champ privé :

// événement après le changement de la tortue
property OnChange: TTurtleEvent read fOnchange write fOnchange;

- une procédure dans la partie protected de la définition de la classe, appelée quand nécessaire et en charge d'appeler elle-même la procédure de l'unité qui exploite la tortue :

procedure Change; // gestion du changement

- la définition de cette procédure :

procedure TGVTurtle.Change;
// *** notification de changement ***
begin
if Assigned(fOnchange) then // on exécute le gestionnaire s'il existe
fOnchange(Self);
end;

Il ressemble tout à fait au gestionnaire vu précédemment : si le champ privé fOnChange est affecté, on exécute la méthode à laquelle il renvoie.

Du côté du programme de test, on retrouve :

- la déclaration de la procédure qui sera appelée en se conformant au squelette fourni par TTurtleEvent :

public
// message de la tortue
procedure TurtleState(Sender: TObject);

- sa définition qui affiche les données fournies sur la barre de statut :

procedure TMainForm.TurtleState(Sender: TObject);
// état de la tortue
begin
GVTurtle.TurtleBitmap.Draw(imgTurtle.Canvas,0,0);
imgTurtle.Invalidate;
// données de la tortue
with GVTurtle do
statusbar.Panels[1].Text := Format('X: %.3d Y: %.3d Cap: %.3d',
[Round(CoordX), Round(CoordY), Round(Heading)]) +
' Visible: ' + IfThen(TurtleVisible, P_True, P_False) +
' Baissé: ' + IfThen(PenDown, P_True, P_False);
cbPenColor.Selected := GVTurtle.PenColor; // couleur du crayon
cbScreenColor.Selected := GVTurtle.ScreenColor; // couleur du fond
end;

- afin de créer le lien entre les deux unités, son affectation lors de la création de la fiche :

procedure TMainForm.FormCreate(Sender: TObject);
begin
// on crée la tortue
GVTurtle := TGVTurtle.Create(imgTurtle.Width, imgTurtle.Height);
GVTurtle.OnChange := @TurtleState;  // gestionnaire de changement
GVTurtle.OnBeforeChange := @TurtleBeforePaint; // idem avant de dessiner
GVTurtle.ReInit; // initialisation
seSetPosX.Value := imgTurtle.Width shr 1; // position au centre
seSetPosY.Value := imgTurtle.Height shr 1;
end;

On a là un cas très classique de gestion d'événements, avec toutes les étapes de sa mise en place. Le code source de l'unité est alors émaillé d'appels à Change lorsqu'intervient un changement concernant la tortue.

II-F-3-e - La classe TGVTURTLE

Voici l'interface de la copieuse classe TGVTurtle :

TGVTurtle = class(TObject)
strict private
fError: TGVErrors; // gestion des erreurs
// *** la tortue ***
fX: Double; // abscisse
fY: Double; // ordonnée
fHeading: Double; // cap
fSize: Integer; // taille
fTurtleKind: TTurtleKind; // type
fTurtleVisible: Boolean; // visibilité
fSpeed: Integer; // vitesse
fPNGTurtle: TBGRABitmap; // PNG en cours
fSavedTurtle: TTurtle; // sauvegarde
fOnchange: TTurtleEvent; // changement notifié
fOnBeforeChange: TTurtleBeforeEvent; // notification avant cap changé
// *** le crayon ***
fPenDown: Boolean; // levé/baissé
fPenRubber: Boolean; // gomme
fPenWidth: Integer; // largeur
fPenColor: TColor; // couleur
fTempColor: TColor; // sauvegarde temporaire de la couleur d'écriture
// *** l'écran ***
fScreen: TScreenTurtle; // mode
fHeight: Integer; // hauteur de la surface d'origine
fWidth: Integer; // largeur de la surface d'origine
fScaleX: Integer; // échelle des X
fScaleY: Integer; // échelle des Y
fScreenColor: TColor; // couleur
fBckImg: TBGRABitmap; // fond
fDrwImg: TBGRABitmap; // surface dessinée
fTtlImg: TBGRABitmap; // dessin de la tortue
fActualImg: TBGRABitmap; // surface réelle
fFilled: Boolean; // drapeau de remplissage
// *** méthodes internes des propriétés ***
function GetCoordX: Double;
function GetCoordY: Double;
function GetImg: TBGRABitmap;
function GetLocalPenColor: Integer;
procedure SetLocalPenColor(AValue: Integer);
function GetLocalScreenColor: Integer;
procedure SetLocalScreenColor(AValue: Integer);
procedure SetCoordX(AValue: Double);
procedure SetCoordY(AValue: Double);
procedure SetFilled(AValue: Boolean);
procedure SetHeading(AValue: Double);
procedure SetPenColor(AValue: TColor);
procedure SetPenDown(AValue: Boolean);
procedure SetPenWidth(AValue: Integer);
procedure SetScreen(AValue: TScreenTurtle);
procedure SetScreenColor(AValue: TColor);
procedure SetSize(AValue: Integer);
procedure SetSpeed(AValue: Integer);
procedure SetTurtleKind(AValue: TTurtleKind);
procedure SetTurtleVisible(AValue: Boolean);
procedure SetRubberPen(AValue: Boolean);
// *** états ***
function GetTurtleState: string; // état de la tortue
procedure SetTurtleState(const St: string);
function GetPenState: string; // état du crayon
procedure SetPenState(const St: string);
function GetPosState: string; // position du crayon
procedure SetPosState(const St:string);
function GetScaleState: string; // état de l'échelle
procedure SetScaleState(const St: string);
function GetScreenState: string; // état de l'écran
procedure SetScreenState(const St: string);
// *** méthodes internes générales ***
function IsWithinLimits(X, Y: Double): Boolean; // dans limites ?
procedure DoGo(X, Y: Double); // effectue un déplacement
procedure LineTo(X, Y: Double); // déplacement en écrivant
procedure MoveTo(X, Y: Double); // déplacement sans écrire
// *** figures prédéfinies générales ***
// arc d'ellipse (b : début, e : fin - en degrés)
procedure ArcAntialias(x, y, rx, ry: single; b, e: word;
c: TBGRAPixel; w: single);
// arc d'ellipse rempli (b : début, e : fin - en degrés)
procedure FillArcAntiAlias(x, y, rx, ry: single; b, e: word;
c: TBGRAPixel);
// portion d'ellipse (b : début, e : fin - en degrés)
procedure PieAntialias(x, y, rx, ry: single; b, e: word;
c: TBGRAPixel; w: single);
// portion d'ellipse remplie (b : début, e : fin - en degrés)
procedure FillPieAntiAlias(x, y, rx, ry: single; b, e: word;
c: TBGRAPixel);
// *** fonctions utiles ***
function ColorToIntColor(N: TColor): Integer; // couleur en couleur locale
function IntColorToColor(N: Integer): TColor; // couleur locale en couleur
protected
function cY(Y: Integer): Integer; virtual; // ordonnée dans nouveau repère
procedure DrawTriangleTurtle; virtual; // tortue triangulaire
procedure DrawPNGTurtle; virtual; // tortue PNG
procedure Change; // gestion du changement
procedure BeforeChange; // avant le changement
public
constructor Create(Width, Height: Integer); // création
destructor Destroy; override; // destructeur
procedure ReInit; // réinitialisation de l'objet
procedure PenReverse; // inversion de l'écriture
procedure Move(Value: Double); // la tortue se déplace
procedure SetPos(X, Y: Double); // fixe les coordonnées de la tortue
procedure Turn(Value: Double); // la tortue tourne
procedure Home; // tortue à l'origine
procedure Wipe; // nettoyage de l'écran
procedure SaveTurtle; // sauvegarde de la tortue
procedure ReloadTurtle(Clean: Boolean); // récupère une tortue sauvée
// renvoie le cap vers un point
function Towards(X, Y: Integer): Double; overload;
function Towards(const St: string): Double; overload;
// renvoie la distance de la tortue à un point donné
function Distance(X, Y: Integer): Double; overload;
function Distance(const St: string): Double; overload;
// *** gestion du texte ***
// texte affiché sur l'écran de la tortue
procedure Text(const St: string; X,Y, Angle: Integer); overload;
// texte affiché à l'emplacement de la tortue
procedure Text(const St: string); overload;
// *** figures prédéfinies ***
// dessine un rectangle
procedure Rectangle(X1, Y1, X2, Y2: Integer); overload;
// dessine un rectangle à l'emplacement de la tortue
procedure Rectangle(X2, Y2: Integer); overload;
// dessine un carré
procedure Square(X1, Y1, L: Integer); overload;
// dessine un carré à l'emplacement de la tortue
procedure Square(L: Integer); overload;
// dessine un rectangle arrondi
procedure RoundRect(X1, Y1, X2, Y2: Integer); overload;
// dessine un rectangle arrondi à l'emplacement de la tortue
procedure RoundRect(X2, Y2: Integer); overload;
// dessine une ellipse
procedure Ellipse(X1, Y1, X2, Y2: Integer); overload;
// dessine une ellipse à l'emplacement de la tortue
procedure Ellipse(X2, Y2: Integer); overload;
// dessine un cercle
procedure Circle(X1, Y1, R: Integer); overload;
// dessine un cercle à l'emplacement de la tortue
procedure Circle(R: Integer); overload;
// dessine un arc d'ellipse
procedure Arc(X1, Y1, X2, Y2, X3, Y3: Integer); overload;
// dessine un arc d'ellipse à l'emplacement de la tortue
procedure Arc(X2, Y2, X3, Y3: Integer); overload;
// dessine une section d'ellipse
procedure Pie(X1, Y1, X2, Y2, X3, Y3: Integer); overload;
// dessine une section d'ellipse à l'emplacement de la tortue
procedure Pie(X2, Y2, X3, Y3: Integer); overload;
// *** propriétés ***
// abscisse de la tortue
property CoordX: Double read GetCoordX write SetCoordX;
// ordonnée de la tortue
property CoordY: Double read GetCoordY write SetCoordY;
// type de tortue
property Kind: TTurtleKind read fTurtleKind write SetTurtleKind
default tkTriangle;
// visibilité de la tortue
property TurtleVisible: Boolean read fTurtleVisible write SetTurtleVisible
default True;
// direction de la tortue
property Heading: Double read fHeading write SetHeading;
// taille de la tortue
property Size: Integer read fSize write SetSize default CDefaultSize;
// drapeau d'écriture
property PenDown: Boolean read fPenDown write SetPenDown default True;
// type de zone de déplacement
property Screen: TScreenTurtle read fScreen write SetScreen
default teWin;
// échelle des X
property ScaleX: Integer read fScaleX write fScaleX default CDefaultScale;
// échelle des Y
property ScaleY: Integer read fScaleY write fScaleY default CDefaultScale;
property PenRubber: Boolean read fPenRubber write SetRubberPen
default False; // état de la gomme
property PenColor: TColor read fPenColor write SetPenColor
default CDefaultPenColor; // couleur du crayon
property LocalPenColor: Integer read GetLocalPenColor
write SetLocalPenColor; // couleur du crayon en couleur locale
property PenWidth: Integer read fPenWidth write SetPenWidth default
CDefaultPenWidth; // largeur du crayon
// état du remplissage
property Filled: Boolean read fFilled write SetFilled default True;
// vitesse de dessin de la tortue
property Speed: Integer read fSpeed write SetSpeed default CMaxSpeed;
property ScreenColor: TColor read fScreenColor write SetScreenColor
default CDefaultBackColor; // couleur du fond d'écran
property LocalScreenColor: Integer read GetLocalScreenColor
write SetLocalScreenColor; // couleur de l'écran en couleur locale
// événement après le changement de la tortue
property OnChange: TTurtleEvent read fOnchange write fOnchange;
// événement avant le changement de la tortue
property OnBeforeChange: TTurtleBeforeEvent read fOnBeforeChange
write fOnBeforeChange;
// surface de dessin de la tortue
property TurtleBitmap: TBGRABitmap read GetImg;
// tortue PNG
property PNGTurtle: TBGRABitmap read fPNGTurtle write fPNGTurtle;
// notification d'une erreur
property Error: TGVErrors read fError write fError;
// état de la tortue
property TurtleState: string read GetTurtleState write SetTurtleState;
// état du crayon
property PenState: string read GetPenState write SetPenState;
// position du crayon
property PosState: string read GetPosState write SetPosState;
// échelle
property ScaleState: string read GetScaleState write SetScaleState;
// écran
property ScreenState: string read GetScreenState write SetScreenState;
end;

En dehors des aspects déjà étudiés, la classe ne présente pas de problèmes majeurs. La tortue fait bien sûr appel à des propriétés afin que l'utilisateur puisse en maîtriser l'aspect et le comportement. L'interface est aussi alourdie par de nombreuses fonctions de dessin : une première version dessine en se référant à des coordonnées traditionnelles tandis qu'une seconde version surchargée grâce à overload utilise les coordonnées de la tortue pour amorcer son dessin.

On notera que les coordonnées sont des réels alors que la position finale est un entier : cela permet de calculer au plus près la position de la tortue avant de l'arrondir, évitant ainsi des approximations cumulées.

II-F-3-f - Test de l'unité GVTurtles

Comme toujours, le programme de test est décliné en deux versions :

- Lazarus pour Windows 32 ;

- Lazarus pour Linux.

L'utilisateur pourra tester la plupart des fonctions et propriétés définies pour manipuler la tortue. Le programmeur sera plus intéressé par le mécanisme des notifications d'événements tel que décrit plus haut.

Il faut avoir installé la bibliothèque BGRABitmap pour utiliser l'unité GVTurtles !

Pour avoir une idée des possibilités de dessin de la tortue, un bouton marqué « dessin » dessine une série de 36 carrés en rosace. Afin de se rendre compte de la rapidité de la tortue triangulaire par rapport à celle graphique, il est judicieux d'utiliser ce bouton spécial dans trois configurations : graphique, triangle et tortue cachée.

On remarquera aussi quatre boutons : le premier met en œuvre la méthode Text dans sa version indépendante de la tortue tandis que le second la teste avec des coordonnées fournies par cette même tortue ; le troisième illustre ce que peut réaliser la méthode Text avec une fleur de mots en couleurs ; enfin, le dernier, accompagné d'un TSpinEdit, contrôle l'épaisseur du trait de la tortue lorsqu'elle dessine. De plus, trois boutons TRadioEdit permettent de contrôler le type de champ de la tortue : illimité avec teWin (valeur par défaut), limité par les bords de l'écran (teGate) et enfin enroulé (teRoll).

(1)Cf. http://www.freepascal.org/docs-html/ref/refse11.html
Retour à l'article

(2)Il s'agit de la notation scientifique pour 10^2, soit 100.
Retour à l'article

(3)Le tri des caractères accentués sera correct avec Delphi, contrairement à Lazarus qui les classera après les caractères normaux.
Retour à l'article

(4)L'espace séparant les éléments d'une liste, une suite d'espaces sera par conséquent ignorée.
Retour à l'article

(5)On reviendra sur les expressions lors de l'étude de leur évaluation.
Retour à l'article

(6)Le tri des caractères accentués sera correct avec Delphi, contrairement à Lazarus qui les classera après les caractères normaux.
Retour à l'article

(7)On remarquera que les primitives concernant les piles et les queues ne sont pas implémentées dans cette unité : elles le seront dans l'unité TGVStacks.
Retour à l'article

(8)Ce caractère est celui qui figure sous le 6 du pavé alphanumérique. Il s'obtient en pressant la touche AltGr et la touche du 6. Il est choisi grâce à la propriété NameValueSeparator.
Retour à l'article

(9)En Pascal, les graphiques ont des coordonnées inversées par rapport aux habitudes en mathématiques. Ainsi le coin supérieur gauche a pour abscisse 0 et ordonnée 0. Plus l'ordonnée augmente plus le point visé se place vers le bas de l'écran.
Retour à l'article

(10)Pour les couleurs admises, se référer au tableau n°1 fourni avec FIXE.COULEUR.FOND.
Retour à l'article

(11)Le premier point est celui situé en haut à gauche tandis que le second est celui dessiné en bas à droite.
Retour à l'article

(12)Un sprite (parfois appelé lutin en français) est un objet qui se déplace sur une surface. On doit avoir l'impression qu'il glisse sur cette surface.
Retour à l'article

(13)… et deux fois plus rapide qu'un appel successif aux fonctions voulues !
Retour à l'article

(14)La détermination des cas a été établie dans une ancienne FAQ concernant Delphi dont je ne trouve plus trace… Par ailleurs, il existe une seconde version de Towards qui travaille avec une liste en entrée.
Retour à l'article

(15)CoordX et CoordY sont les coordonnées actuelles de la tortue.
Retour à l'article