Triceraprog
La programmation depuis le Crétacé

VG5000µ, les hooks d'appel ()

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 le RST est pointé par HL. Si ce n'est pas le cas, une erreur de syntaxe est levée. Le DEFB qui suit le RST 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 (via read_expr) et vérifie dans la foulée si elle est numérique.
  • getbyt_impl appelle eval_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é dans HL et la chaîne se termine par 0.
  • out_str affiche la chaîne pointée par HL se terminant par 0.

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 et DE, 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.

Un CALL avec paramètres