Dans la série des hooks sur VG5000µ, voyons en cette fois la paire probablement la plus simple. Ça sera donc rapide.
Le hook CALL
Du nom de calhk
et d'adresse $47D3
, ce hook est utilisé en interne par la commande BASIC CALL
.
Le code de cette instruction est extrêmement simple :
inst_call: call eval_num_ex
call deint_impl
ld a,$c3
ld (calhk),a
ld (calhk+1),de
jp calhk
En premier, l'argument passé à CALL
est évalué, puis est ensuite transformé en entier sur 16 bits.
Cette adresse, précédée par l'opcode pour JP
à une adresse absolue 16 bits est placée dans le hook calhk
. La routine saute enfin vers cette adresse, qui agit comme un tremplin vers l'adresse indiquée à l'instruction CALL
.
Comme c'est le RET
de la routine appelée qui fera office de RET
pour l'ensemble de l'instruction CALL
, la préservation de l'environnement est à la charge de la routine appelée. Essentiellement, il vous faut préserver HL
, qui pointe sur la fin de l'instruction. Si vous ne le faite pas, il y a de bonnes chances que vous obteniez un Erreur de Syntaxe
en retour d'instruction.
Pour un exemple d'utilisation, voyez les articles précédents, qui montent une routine assembleur en mémoire puis l’appellent.
Récupérer des paramètres
Que HL pointe juste après le CALL
se révèle pratique pour récupérer des arguments potentiels. Dans l'article sur les hooks de périphériques, j'étais allé chercher un argument de type chaîne de caractères. Cette fois, je vais aller chercher trois arguments de type nombre. Le premier entier sur 8 bits, le second sur 16 bits, et le troisième un nombre quelconque (dans les limites du VG5000µ).
Les deux premiers arguments seront obligatoires, le troisième optionnel.
Grâce à ça, il est possible d'ajouter des commandes au BASIC, sans leur donner un nom cependant.
defc out_number = $0726
defc out_fp = $0731
defc deint_impl = $2552
defc eval_num_ex = $284d
defc type_eq_num = $2850
defc read_expr = $2861
defc getbyt_impl = $2aa5
defc out_str = $36aa
org $7A00 ; Spécification de l'adresse mémoire d’implantation
cmd_routine:
rst $08 ; Vérification obligatoire du caractère qui suit
defb ','
call getbyt_impl ; Récupération d'un entier 8 bits dans A
ld C,A ; Sauvegarde du premier paramètre dans C
ld B,$00 ; BC contient le premier paramètre
rst $08 ; Vérification obligatoire du caractère qui suit
defb ',' ; La virgule de séparation
push BC ; Sauvegarde du premier paramètre dans la pile
call eval_num_ex
call deint_impl ; Lecture d'un entier signé 16 bits dans DE
push DE ; Sauvegarde du second paramètre dans la pile
ld A,'('
rst $18 ; Affichage de la parenthèse ouvrante
ex (SP), HL ; Récupération du deuxième paramètre, sauvegarde du pointeur
call out_number ; Affiche le contenu de HL
ld A,','
rst $18 ; Affichage de la virgule
pop HL
ex (SP), HL ; Récupération du premier paramètre, sauvegarde du pointeur
call out_number ; Affiche le contenu de HL
pop HL ; Récupération du pointeur d'exécution
rst $10 ; Lecture du caractère suivant
jr Z,no_third ; Fin de la ligne, il n'y a pas de troisième paramètre
dec HL ; Sinon, on revient en arrière pour vérifier le caractère suivant
rst $08
defb ',' ; Qui doit être une virgule
ld A,','
rst $18 ; Affichage de la virgule
call read_expr ; Lecture du troisième paramètre comme une expression
push HL ; Sauvegarde du pointeur d’exécution
call type_eq_num ; Vérification du type de paramètre (numérique)
call out_fp ; Construction de la chaîne de caractères correspondant au nombre
call out_str ; Affichage de la chaîne
pop HL ; Restauration du pointeur d’exécution
no_third:
ld A,')'
rst $18 ; Affichage de la parenthèse fermante
ret
C'est assez long, il y a peut-être plus optimisé, le principal ici est que la suite d'instructions soit lisible et que les morceaux différents allant chercher les arguments puissent être pris indépendamment pour réutilisation.
Le code est commenté, mais nécessite peut-être quelques autres éclaircissement :
rst $08
vérifie que le caractère codé juste après leRST
est pointé parHL
. Si ce n'est pas le cas, une erreur de syntaxe est levée. LeDEFB
qui suit leRST
est bien entendu sauté, le retour de la routine se fait juste après. La ROM se sert beaucoup de cette séquence pour vérifier la syntaxe de commandes et d'expressions.rst $18
affiche le caractère présent dans A.read_expr
évalue une expression de n'importe quel type et met le résultat dans l'accumulateur flottant.eval_num_ex
lire une expression (viaread_expr
) et vérifie dans la foulée si elle est numérique.getbyt_impl
appelleeval_num_ex
, la converti en entier, et vérifie que le résultat tient sur 8 bits. Le résultat est dans A.deint_impl
effectue une troncature entière sur 16 bits du nombre présent dans l'accumulateur flottant. Le résultat est dans DE.out_number
affiche le nombre (entier positif) présent dans HL.out_fp
écrit dans un buffer temporaire une chaîne représentant le nombre présent dans l'accumulateur flottant. Le pointeur vers la chaîne est renvoyé dansHL
et la chaîne se termine par0
.out_str
affiche la chaîne pointée parHL
se terminant par0
.
On notera au passage que lors de l'affichage, les deux premiers paramètres sont inversés... c'était juste plus simple à écrire.
Le hook RST
Le second hook d'appel est rsthk
, à l'adresse $47DC
. Son fonctionnement n'est pas atteignable depuis le BASIC.
La fonction RST
du Z80 est une sorte de CALL
sur 1 octet. Il permet de brancher à une série de 8 adresses prédéfinies : $0000, $0008, $0010, $0018, $0020, $0028, $0030, $0038. Suivant la syntaxe de l'assembleur, soit l'adresse, soit le numéro du restart (entre 0 et 7) est accepté.
Toutes ces adresses sont allouées à des fonctions souvent appelées, cela permet de gagner de la place. Comme il n'y a pas beaucoup d'instructions possibles entre deux adresses de restart, la routine est souvent placée ailleurs, et certains emplacements sont remplis avec des données.
Par exemple, entre RST $00
, qui fait un démarrage complet de la machine et RST $08
, qui fait un test de syntaxe, se situe la chaîne ".1.1" pour la ROM 1.1. C'est cette chaîne qui est affichée au démarrage de la machine.
Les différents RST
Puisqu'on y est, voici les différents RST sur VG5000µ, brièvement.
- $00 : redémarrage complet de la machine
- $08 : vérification de la présence d'un caractère pointé par
HL
, ou lancement d'une erreur de syntaxe (pour vérifier une virgule entre deux arguments par exemple) - $10 : acquisition d'un caractère depuis la chaîne pointée par
HL
(avec quelques flags concernant sa nature, et en sautant les espaces) - $18 : envoie d'un caractère sur le périphérique sélectionné (écran, imprimante, modem)
- $20 : comparaison de
HL
etDE
, qui a lieu très souvent. - $28 : renvoie -!, 0 ou 1 en fonction du signe de l'accumulateur flottant
- $30 : libre
- $38 : vecteur d'interruption (utilisé par l'affichage)
Un RST
libre
Le restart $30
est donc libre, et son code est le suivant :
usrrst: jp rsthk
Net et précis. Il suffit donc d'ajouter en rsthk
un JP
suivi d'une adresse absolue, et vous pouvez alors utiliser RST $30
dans vos routines pour un appel très fréquent.
Cependant, il y a une limite : il n'existe qu'un seul hook libre pour tout le monde. À garder en tête si vous mélanger des routines qui veulent chacune utiliser cette instruction.
À noter aussi que si RST $30
ne prend qu'un octet dans votre routine, le saut utilise deux indirections pour un total de 31 T States contre 17 pour un CALL
.
Le résultat
Voici le résultat de l'appel de la routine via CALL
avec décodage de paramètre.