Triceraprog
La programmation depuis le Crétacé

VG5000µ, les hooks de périphériques ()

L'article précédent présentait un accrochage sur un hook d'interruption. Dans cet article, je vais regarder du côté des hooks de commandes de périphériques.

Ces hooks sont initialisés différements des 10 premiers, selon le code qui suit :

             ld       a,$c3
             ld       (inst_lpen),a
             ld       (inst_disk),a
             ld       (inst_modem),a
             ld       hl,no_device
             ld       ($47f2),hl
             ld       ($47f5),hl
             ld       ($47f8),hl

L'instruction placée au début de chaque vecteur de redirection est un JP et l'adresse par défaut est celle d'une routine indiquant que le périphérique n'est pas géré.

Il s'agit d'une extension de commandes mise à disposition par le VG5000µ, permettant de traiter les commandes LPEN, MODEM et DISK. Il est d'ailleurs amusant de voir que dans le manuel d'utilisation, ces trois commandes sont mentionnées dans le rappel des instructions reconnues, mais qu'elles ne sont pas décrites.

Dans la table des instructions, le décodage de ces tokens envoie directement sur chacun des trois vecteurs, sans traitement particulier. C'est donc du code de décodage de paramètres qu'il faut écrire si l'on veut traiter ces instructions.

Mise en place de la routine

Le code est similaire à celui de la mise en place de la routine sur l'interruption. Seule l'adresse du hook change.

    defc dskhk = $47f4  ; Adresse du hook

    org $7A00           ; Spécification de l'adresse mémoire d'implémentation

    push AF             ; Sauvegarde des registres sur la pile
    push HL

    ld A,$C3            ; Mise en place de la routine sur le HOOK
    ld (dskhk),A        ; Il y a normalement déjà un 'JP' ici, mais on s'en assure
    ld HL,cmd_routine
    ld (dskhk+1),HL     ; l'adresse de la routine


    pop HL              ; Restauration des registres depuis la pile
    pop AF

    ret                 ; Retour au programme appelant

cmd_routine:
    ret

Passons donc rapidement sur la partie la plus intéressante : le traitement de l'instruction.

Traitement de l'instruction

Lorsque l'interpréteur appelle la routine associée à une instruction, plusieurs choses sont mises en place. Les principales sont :

  • HL pointe vers la chaîne en train d'être interprétée, sur le prochain caractère à lire,
  • Le flag "Z" est à 1 si l'on est à la fin de la chaîne,
  • L'adresse de retour sur la pile est positionnée pour traiter l'instruction suivante.

Le contrat en sortie est :

  • HL doit pointer sur le caractère après le dernier consommé par l'instruction,
  • S'il s'agit d'une fonction, son résultat doit être dans l'accumulateur flottant (numérique, ou pointeur de chaîne).

Pour s'amuser avec le paramètre, je vais écrire le décodage d'un paramètre de type chaîne, qui sera ensuite affiché à l'écran, suivi par un message.

    defc type_eq_str = $2851
    defc read_expr = $2861
    defc out_str = $36aa
    defc out_str1 = $36ad

cmd_routine:
    push HL             ; Sauvegarde du pointeur d'exécution

    call read_expr      ; Lecture de l'expression suivant la commande
    ex (sp), hl         ; Remplacement de la valeur du pointeur d'exécution

    call type_eq_str    ; Vérification du type du paramètre
    call out_str1       ; Affichage de la chaîne

    ld hl, answer
    call out_str        ; Affichage du message de la commande

    pop HL              ; Récupération du pointeur d'exécution

    ret

answer:
    defm " <-- PARAM", $00

La structure est assez simple, car les routines nécessaires sont toutes disponibles dans la ROM.

Tout d'abord, on sauve le pointeur vers la chaîne interprétée. HL est actuellement sur le début du paramètre de DISK (en tout cas, ce qui suit).

L'appel à read_expr se charge de lire une expression valide (ou échouer avec une erreur). À la sortie de la routine, HL est positionné à la fin de ce qui a été consommé par l'expression.

Ce pointeur est celui qu'il faudra conserver pour l’interpréteur. Du coup, on échange la valeur précédente de HL actuellement sur la pile avec la nouvelle valeur. Hop, le tour est joué.

L'appel à type_eq_str vérifie si le type de l'expression est bien une chaîne, et affiche un message d'erreur sinon (et dans ce cas l'interprétation est arrêtée immédiatement).

Si c'est bien une chaîne, l'appel à out_str1 affiche le résultat de l'expression de type chaîne de caractères qui vient d'être évaluée.

L'appel à out_str qui suit affiche la chaîne terminée par un 0 définie dans le programme.

Et voilà, voir le résultat à la fin de l'article.

BASIC

Voici un programme BASIC pour charger et lancer la routine.

10 CLEAR 50,&"79FF"
20 S=&"7A00"
30 READ A$
40 IF A$="FIN" THEN END
50 A$="&"+CHR$(34)+A$+CHR$(34):A=VAL(A$)
60 POKE S,A
70 S=S+1
80 GOTO 30
300 DATA F5,E5,3E,C3,32,F4,47,21,10,7A,22,F5,47,E1,F1,C9,E5,CD,61,28
310 DATA E3,CD,51,28,CD,AD,36,21,23,7A,CD,AA,36,E1,C9,20,3C,2D,2D,20
320 DATA 50,41,52,41,4D,0
1000 DATA FIN
RUN
CALL &"7A00"

Pour éviter d'avoir à tout rentrer au clavier, voici le fichier .k7. À charger avec CLOAD, suivi d'un RUN et du CALL &"7A00".

Le résultat

La commande DISK