CODING ★ RSX - VARIABLE LOCALES ET RECURSIVITE|CPC INFOS) ★

VARIABLES LOCALES ET RECURSIVITEVARIABLES LOCALES ET RECURSIVITE - PARTIE 2


Grâce au programme qui vous est proposé ici, vous allez pouvoir amener votre CPC favori et son BASIC Locomotive au niveau des fameux PASCAL ou C.
Qu'est-ce qui manque à notre cher BASIC et qu'on retrouve chez le C, le PASCAL et même certains BASIC de haut niveau comme le QUICK BASIC ? Les ceusses qui  connaissent ces langages doivent avoir trouvé la réponse.
Les autres doivent en avoir une idée grâce au titre de l'article : les variables locales et, via elles, la récursivité.

NOTIONS SUR LES FONCTIONS ET PROCEDURES

Je ne vais pas faire Ici un cours de PASCAL ou autre, mais je vais au moins présenter vite fait des notions utiles, à savoir les fonctions et les procédures, ainsi que les variables locales. Ceux qui connaissent déjà n'ont qu'à sauter au paragraphe suivant.

En général un programme PASCAL est composé d'un module principal, précédé de fonctions et procédures. Peu de choses différencient ces trois catégories :

    • Le module principal est celui qui est exécuté au démarrage et par lequel les fonctions et procédures sont appelées, directement par lui-même, ou entre elles.
    • Une fonction est à elle-seule un véritable programme, qui rend par son nom un résultat d'une certaine nature. En cela une fonction peut être directement utilisée dans des formules, des tests, voire comme argument d'une autre fonction. Les fonctions sont similaires par leur apparence aux fonctions du BASIC du CPC "FN xxx". Il y a des arguments en entrée et on a un résultat en sortie. Seulement elles peuvent comporter autant de lignes que l'on veut et surtout elles peuvent s'appeler elles-mêmes : c'est ce qu'on appelle la récursivité. Un exemple :

        On définit ainsi la fonction mfactortelle", notée "I* :

        pour n entier. n!=n*(n-1)'(n-2)*...,3*2*1

        ainsi 3!-6,5!-120,10!=3 628 800

        réécrivons la fonction :

        n!-n*(n-1)*(n-2)*...*3*2*1

        n!-n*((n-1)#(n-2),..."3*2*1)

      n!-n*(n-1) !

Et voilà la récursivité. La fonction factorlelle définit ni comme le produit n*(n-1). avec (n-1) l=(n-1)*(n-2) !
Et ainsi de suite jusqu'à la fin, qui se fait en écrivant que 1!-1.

- Une procédure est tout à fait similaire à un sous-programme du BASIC du CPC. En plus on peut lui transmettre des arguments en entrée, et la procédure peut retourner si on le veut des valeurs en sortie via des variables appartenant à cette liste d'arguments en entrée. Vous m'avez suivi ? Une procédure a la possibilité comme une fonction d'être récurslve.

Quelle est la caractéristique importante des fonctions et procédures, qui autorise la récursivité ? C'est l'existence de variables locales : dans le corps d'une fonc-

tion ou d'une procédure, on définit des variables qui n'ont de valeur qu'à l'Intérieur môme de la fonction ou la procédure. Ainsi la valeur de la variable "I" dan sle module principal ne sera pas modifiée par l'utilisation de cette variable dans une fonction ou une procédure (sauf si c'est désiré). Comment réallse-t-on cela grosso modo ? Eh bien c'est comme si on stockait lors de l'entrée dans la routine appelée les valeurs à ce moment des variables utilisées dans la routine. Au retour de la routine on réinjecte les anciennes valeurs dans les variables.

COMMENT FAIT-ON EN BASIC LOCOMOTIVE ?

Le seul moyen de créer avec le BASIC du CPC un module relativement Indépendant du corps principal du programme, c'est de l'écrire sous forme de sous-programme, qui est appelé par un GOSUB et qui se termine sur un RETURN.

Pour une procédure, il est évident que l'adaptation est Immédiate. Pour une fonction, ce n'est pas le cas et il faut en fait la transformer en procédure. Cela peut compliquer parfois les choses, mais c'est toujours possible. Il suffit de transmettre en sortie, via une variable, le résultat.

La marche à suivre est la suivante :

Pour l'appel :

1) Indication des arguments d'entrée
2) Appel du sous-programme

Dans la procédure :

1 ) Indication des variables locales
2) Lecture des arguments d'entrée
.
.
.
3) Indication des arguments desortie
4) Restauration des variables locales
5) Retour de sous-programme

Retour d'appel : Lecture des arguments de sortie

Tout cela se fait grâce à des Instructions RSX décrites dans les paragraphes suivants.

INITIALISATION

Pour stocker les valeurs des variables locales avant leur utilisation dans une procédure et les valeurs à transmettre en entrée ou sortie, il faut réserver de l'espace en mémoire. C'est trivial mais II fallait le dire. On va, â cet effet, créer une plie (stock en anglais) spéciale. La commande est : ISTACK. debpile. longplle

"debplle" est l'adresse de début de la plie et "longplle* la longueur de la pile. SI jamais la plie déborde sur la routine, le message "Illégal stack" s'affiche et est suivi à ta ligne suivante du message "Cannot CONTinue". En cours de programme cela provoque un "Break" et donc un arrêt. Ce deuxième message d'erreur standard du BASIC s'affiche après tout message spécifique des RSX. dans le but d'arrêter tout programme.

INDICATION DES ARGUMENTS D'ENTREE

Commande : |GIVE,typ.@var,typ2,@var2...
On transmet les arguments via des variables. Cela se fait par paire de valeurs, "typ" est la longueur en octets de ta variable transmise, soit 2 pour un entier. 5 pour un réel. 3 pour un pointeur de chaîne. "@var" (lire "arobas var") transmet l'adresse de ta variable "var". Cette commande empile les valeurs Indiquées (GIVE=donne) depuis le haut de ta plie vers le bas. comme ceux qui liront le listing en tangage d'assemblage s'en rendront compte. En ce qui concerne un tableau. Il faut Indiquer ta longueur pour tous les éléments à transmettre et l'adresse du premier élément transmis.

INDICATION DES VARIABLES LOCALES

Commande : |LOCAL,typ,@var,typ2,@var2...
Les arguments de cette commande sont définis exactement comme ceux de la précédente. Cette fols-cl. les valeurs des variables sont empilées depuis le bas de ta pile vers le haut.

LECTURE DES ARGUMENTS D'ENTREE

Commande : |GET, typ, @var, typ2, @var2,... Idem qu'avant. Il doit y avoir identité entre  les types des arguments transmis en entrée et ces derniers. Même si le type d'une variable n'est indiqué que par "typ", il faut aussi que les variables concernées soient de même type pour que la transmission et la lecture de l'argument aient un sens.

INDICATION ET LECTURE
DES ARGUMENTS DE SORTIE

Celui qui a bien suivi aura compris que cela se fait respectivement avec les commandes |GIVE et |GET.

RESTAURATION
DES VARIABLES LOCALES

Commande : |ENDLOCAL, typ, @var, typ2, @var2,... Il s'agit de l'injection dans les variables locales de leurs valeurs avant appel de la procédure. La liste des arguments doit être identique à celle de la commande |LOCAL. Aussi le plus simple est de recopier grâce à la touche COPY cette liste.

LES MESSAGES D'ERREURS

Des messages d'erreurs ont été prévus pour éviter des catastrophes. Il y a en fait à chaque fols deux messages : un propre aux nouvelles commandes. suM du message du BASIC, "Cannot CONTinue", ce afin de provoquer l'arrêt d'un programme.

Illégal stock : ta pile définie mord sur les RSX. Cela apparaît pour de mauvaises valeurs après ISTACK ou bien lorsqu'on utilise une des autres commandes alors qu'une plie correcte n'a pas encore été définie.

No argument : on utilise une des RSX sans argument !

Odd number of arguments : on a transmis un nombre Impair d'arguments, alors que toutes les fonctions en réclament un nombre pair.

Stack full : Il n'y a plus de place dans ta plie pour stocker des valeurs avec un ILOCAL ou un IGIVE. Aucune des valeurs de la liste de la commande n'est stockée. En fait (pour les curieux), le pointeur sur le dernier élément n'est pas réactualisé. Tout se passe comme si rien n'avait été fait. Il faut accroître la taille de la plie.

Stack empty : on essaie de retirer des valeurs de la plie avec un IENDLOCAL ou un IGET. alors qu'il n'y a plus rien à retirer. C'est soit une erreur dans le nombre d'arguments, soit un retrait de trop qui est effectué. Il faut là trouver l'erreur. Les valeurs sont retirées et Injectées dans les variables aussi loin que possible, mais Ici aussi le pointeur n'est pas réactualisé. Tout se passe comme si rien n'avait été fait.

ATTENTION : un erreur à coup sûr fatale n'est pas détectée. Il faudrait être très distrait pour la commettre, alors autant être puni par un plantage I II s'agit de l'indication "0" pour le type (c'est-à-dire la longueur) d'une entité. Les spécialistes comprendront qu'un 0 dans BC pour un LDIR peut faire très mal.

LE CHARGEUR BASIC DES RSX

Le programme BASIC 'CHARGEUR.BAS" crée le programme binaire *LOCAL.BIN" définissant les RSX. Ce dernier est placé à l'adresse ad. modifiable à loisir (c'est le programme BASIC qui fait tout). On Initlallse les RSX par "CALL ad*. La sauvegarde du fichier binaire se fait automatiquement après le "pokage".

Le programme LOCAL.BIN est adapté au type de CPC (464.664 ou 6128). ce pour la génération du message d'erreur 'Cannot CONTInue", qui nécessite un saut en ROM. Tant qu'on est sûr de ne pas avoir d'erreur, la routine est valable pour tout type de CPC. Sinon II faut relancer le programme chargeur sur le type désiré d'Amstrad.

QUELQUES REMARQUES SUR LA PROGRAMMATION

- Les variables, dont on transmet l'adresse dans la liste des arguments de |LOCAL, |GIVE, |ENDLOCAL et |GET doivent auparavant avoir été Initialisées. Cela signifie qu'il faut d'abord leur avoir donné une valeur, afin que le système d'exploitation leur ait attribué une adresse en mémoire. Sans quoi elles sont véritablement Inconnues au bataillon et le message d'erreur 'Improper arguement" est délivré. Une manière pratique d'Inltlallser une variable, sans s'engager sur le contenu à lui attribuer, est l'auto-asslgnatlon. Kesaco ? Par exemple : "a$-a$". "r=r" , "l%=l%.

- L'Initialisation de variable peut se faire n'Importe où avant l'utilisation de la variable comme argument.

Ainsi pour le module principal tout au début. Pour les procédures, Juste avant la commande ILOCAL. C'est dans ce cas que c'est le plus clair, mais rien n'empêche de tout Initialiser en début de programme, ce qui fait gagner quelque temps lors de l'exécution des procédures.

- La plie n'a pas besoin d'être très Importante. Dans la plupart des cas 1 Ko suffit. Il est facile de déterminer la taille nécessaire. Il suffit de compter le nombre d'octets stockés à chaque appel de procédure et de le multiplier par le nombre de niveaux d'appels de la procédure. On le fait pour toutes. Il faut ajouter à cela le nombre maximum d'octets occupés par une transmission de valeurs par |GIVE.

-  Pour les distraits. Il est signalé qu'il est plus prudent de placer la plie au-dessus du haut de la mémoire réservée au BASIC (au-dessus du HIMEM). Sinon II risque d'y avoir du cafouillage si des variables viennent se balader dedans.

-  La manipulation de chaînes est délicate car àaS (par exemple) renvoie l'adresse de trois octets, dont le premier est ta longueur de la chaîne et les deux suivants son adresse. On ne peut opérer des empilages et dépllages de chaînes qu'en maîtrisant parfaitement leurs longueurs, sinon bonjour les dégâts !

- Il existe une limite au nombre de niveaux de sous-programmes : pas plus de 85 dans le meilleurs des cas. Le BASIC dispose en effet d'une pile personnelle de 512 octets. Un GOSUB en consomme 6, un WHILE-WEND 7. un FOR-NEXT avec entiers 16, un FOR-NEXT avec réels 22. De plus la plie est utilisée pour stocker des valeurs Intermédiaires dans des calculs ou des évaluations. Une plie BASIC pleine se traduit par un magistral "MEmory full*. qui Interdit même la lecture des valeurs des variables. Il faut passer par un CLEAR.

LES PROGRAMMES DE DEMONSTRATION

Ces programmes sont suffisamment commentés pour être compris à la seule lecture du listing. Il est juste supposé que le programme binaire des RSX a été chargé et que les RSX ont été Inltlallsées. Sinon le laconique "Unknown command" pointera son nez. Tous ces programmes font appel à la récurslvlté. qui donne toute sa valeur à la possibilité de définir des variables locales. Ils sont donnés ci-après en ordre croissant de complexité, sans pourtant être complexes.

FACT : calcul de factorlelle
Le processus de calcul a déjà été présenté :

n!=n*(n-1)!
On arrête à 1!=1

PUISS : calcule "X puissance N".

X^N=X*(X^(N-1))
On arrête à X^0-1

LEGENDRE : calcul de polynôme de Legendre,

P(n)(x) P(0)(x)=1
P(1)(x)-x
P(n)(x)-((2*n-1)*P(n-1)(x)-(n-1)*P(n-2)(x))/n

ARBRE : dessin d'arbre ternaire. C'est la simple traduction en BASIC du programme en PASCAL 'ARBRE", dû à Alain Lambert et publié dans le SVM n°46 de Janvier 1988 (cf pages 89-92). C'est le seul des 4 programmes qui soit très difficile à écrire sans récurslvitô. Pour les autres de simples procédés Itératifs suffisent. C'est le plus beau à voir. Essayez 90,0.6,0.8.0.7.40,-4,1,7...

DETOURNEMENT DE COMMANDES

- Echange de variables : on peut aisément réaliser un échange de valeurs entre deux variables, sans
passer par une variable Intermédiaire. Pour échanger les contenus de a% et b%. Il suffit de faire :

|GIVE.2.@a%,2,@b%
|GET,2.@b%,@a%

De même avec a$ et b$ :

|GIVE,3,@a$,3,@b$
|GET,3,@b$A,3,@a$

- Décalage dans un tableau : supposons qu'on veuille Insérer un nombre x en 25e position dans le tableau de réels tab. qui compte 80 éléments du n°l au n°80. Attention à ne pas décaler en dehors du tableau :

|GIVE,56*5,@tab(25)
tab(25)=x
|GET,56*5,@tab(26)

De même avec le tableau de chaînes str$. où on Insère aS. A noter qu'Ici on ne réalise l'assignation qu'après avoir décalé. Cela supprime le risque de décalage des chaînes préexistantes lors de l'assignation :

|GIVE,56*3,@str$(25)
|GET,56*3,@str$(26)
str$(25)=a$

Le gain de temps peut Ôtre ainsi considérable.

- On peut stocker par |GIVE des variables numériques ou alphanumériques, pour une sauvegarde sous forme binaire. Leur chargement, puis leur récupération par |GET est beaucoup plus rapide. Il faut aussi dans ce cas conserver le pointeur de plie WARPT (cf listing du code source). Rien n'empêche de procéder avec |LOCAL On peut faire une sauvegarde provisoire d'une portion de la mémoire, etc.

CONCLUSION

J'espère que vous avez tout compris. A vous désormais le tri rapide comme avec le procédé "Quick-sort", les calculs de déterminants, les fractales délirantes. Récursivement vôtre !

★ ANNÉE: ???
★ AUTEUR: YANNICK GOUR

CPCrulez[Content Management System] v8.7-desktop/cache
Page créée en 138 millisecondes et consultée 991 fois

L'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent  du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko.