Pour ces quatre nouveaux hooks, je ne suis pas très inspirés. Il s'agit de hook destiné au traitement des entrées sorties. Trois d'entre eux sont appelés lors d'une impression de caractères, le quatrième pour de l'acquisition.
Routines en sorties
Voici les trois premières :
- $47DF,
prthk
: début de commande PRINT.- Est appelé en tant que première instruction de l'exécution de l'instruction
PRINT
. - À ce moment là,
HL
pointe vers les arguments dePRINT
et le flagZ
est à 1 s'il n'y a rien dans ces arguments. - Si vous rendez la main à la routine, elle déroulement l'affichage.
- Est appelé en tant que première instruction de l'exécution de l'instruction
- $47E2,
outhk
: début d'impression de caractère -> pour rerouter vers de nouvelles sorties- Est appelé pour chaque caractère envoyé sur un périphérique de sortie (en
$3bd0
) - La variable système
(prtflg)
désigne le périphérique. - Le caractère à afficher est dans le registre
A
. - Attention, ce caractère est à comprendre par rapport aux modes d'affichage : est-ce qu'on est en caractères normaux ? semi-graphiques ? redéfinis par l'utilisateur ?
- Est appelé pour chaque caractère envoyé sur un périphérique de sortie (en
- $47E5,
crdhk
: début de retour chariot -> pour rerouter vers de nouvelles sorties- Est appelé par chaque demande de retour à la ligne lors de l'émission des caractères sur le périphérique (en
$3c57
). - N'est pas appelé lors d'un retour chariot dans l'éditeur par contre.
- La variable système
(prtflg)
désigne le périphérique.
- Est appelé par chaque demande de retour à la ligne lors de l'émission des caractères sur le périphérique (en
La variable système (prtflg) désigne le périphérique en sortie selon les valeurs suivantes :
- 0 - L'écran
- 1 - L'imprimante
- 255 - La cassette (en écriture)
Les appels aux hooks se situent avant le tri vers les trois routines. La sélection ne se fait pas systématiquement dans le même ordre, ce qui signifie que les valeurs 2 à 254 n'envoient pas au même endroit pour les différentes fonctions.
Le retour chariot enverra sur l'imprimante et la sortie de caractère sur la cassette. PRINT
ne considère que deux cas, l'imprimante et l'écran, et s'occupera de la position du chariot ou du curseur suivant le cas. Pour l'affichage en lui-même, c'est la routine d'envoi de caractère et de retour chariot qui sont utilisés.
Ainsi, si vous voulez supporter un nouveau périphérique en sortie, il faudra probablement effectuer un premier traitement au niveau du PRINT
suivant si vous voulez être traité comme un écran ou une imprimante ; puis rendre la main, ou bien tout faire seul et jeter la première adresse de retour de la pile.
Du côté de la sortie de caractère et du retour chariot, il vous faudra prendre la main et jeter la première adresse de retour de la pile quoi qu'il arrive.
N'ayant pas de périphérique à tester (on pourrait imaginer une sortie série), je n'ai pas essayé.
Précautions
Pendant l'exécution d'une routine de sortie, n'appelez-pas les routines de la ROM qui elles-mêmes font de l'affichage. Vous vous appelleriez vous-même...
De toute façon, les routines d'affichages ne sont pas ré-entrantes et ne supportent pas d'être exécutées dans deux contextes simultanément. Cela explique pourquoi, si vous avez essayé comment moi d'utiliser des routines d'affichage pendant l'interruption d'affichage, des choses étranges se produisent.
En effet, si l'interruption à lieu pendant que la ROM est en train d'exécuter une de ces routines (un PRINT
tout simplement), il y a de bonnes chances que cela se passe mal. Même si vous sauvez tous les registres. Le contexte est beaucoup plus gros que ça, avec des buffers de constructions de chaînes.
Mieux vaut avoir vos routines d'affichages spécialisées.
Routine en entrée
Pour l'entrée, il n'y a qu'un hook, celui de l'instruction INPUT
:
- $47EB,
inphk
: début de commande INPUT- Appelé en second lors de l'exécution de l'instruction BASIC
INPUT
, la première instruction étant la vérification queINPUT
n'a pas été appelée en direct, hors programme. - Comme pour
PRINT
,HL
pointe sur les arguments. - La variable système
(getflg)
désigne le périphérique en entrée.0
pour le clavier,255
pour la cassette. Il n'y a pas de périphérique écran en entrée...
- Appelé en second lors de l'exécution de l'instruction BASIC
Il est donc possible d'aller router l'INPUT
vers un nouveau périphérique externe.
Le manque d'idée...
Oui... décidément, comme je n'ai pas de nouveau périphérique à router, je manque d'idée. Le programme que j'écris se contente donc de compter les appels aux différents hooks puis d'offrir une routine pour afficher les compteurs.
Installation
defc out_number = $0726
defc out_str = $36aa
defc xcursor = $4805
defc prthk = $47df
defc outhk = $47e2
defc crdhk = $47e5
defc inphk = $47eb
defc inthk = $47D0
org $7A00 ; Spécification de l'adresse mémoire d'implentation
push AF ; Sauvegarde des registres sur la pile
push HL
ld hl, call_text ; Affichage du CALL pour la routine d'Affichage
call out_str
ld hl, call_routine
call out_number
ld A,$C3 ; Mise en place des JP
ld (prthk), A
ld (outhk), A
ld (crdhk), A
ld (inphk), A
ld HL,prt_routine ; Mise en place des adresses de saut
ld (prthk+1),HL
ld HL,out_routine
ld (outhk+1),HL
ld HL,cr_routine
ld (crdhk+1),HL
ld HL,inp_routine
ld (inphk+1),HL
pop HL ; Restauration des registres depuis la pile
pop AF
ret ; Retour au programme appelant
call_text:
defm "CALL ", $00
Dans cette première partie, on effectue comme d'habitude l'installation des routines. Comme il y a quatre routines à mettre en place, on me d'un coup les quatre JP
, puis dans la foulée les quatre adresses.
Mais auparavant, on affiche à l'écran un message indiquant l'instruction CALL
à lancer pour provoquer l'affichage des compteurs.
Comme indiqué ci-dessus, appeler les routines d'affichages textes et nombres pendant une interruption ne fonctionne pas sans de nombreuses précautions. J'ai donc préféré offrir une routine qui affiche les compteurs sur un appel explicite. Et plutôt que de placer cet appel à une adresse fixe, elle est à la suite des autres routines. Son adresse étant variable en fonction des modifications des autres routines, je m'aide en affichant l'adresse à appeler.
Comptage
Le comptage se résume à quatre fois la même routine, à l'adresse de la variable de comptage près.
prt_count:
defw $0000
out_count:
defw $0000
cr_count:
defw $0000
inp_count:
defw $0000
prt_routine:
push hl
ld hl, (prt_count)
inc hl
ld (prt_count), hl
pop hl
ret
out_routine:
push hl
ld hl, (out_count)
inc hl
ld (out_count), hl
pop hl
ret
cr_routine:
push hl
ld hl, (cr_count)
inc hl
ld (cr_count), hl
pop hl
ret
inp_routine:
push hl
ld hl, (inp_count)
inc hl
ld (inp_count), hl
pop hl
ret
Au tout début, je réserve quatre emplacements de 16 bits pour les compteurs. Puis viennent les quatre routines qui chacune va augmenter le compteur associer lors d'un appel.
Prenons prt_routine
. Tout d'abord, HL
est sauvegardé. Comme d'habitude au début d'une instruction, ce registre pointe vers la ligne en train d'être exécutée, il est essentiel de préserver sa valeur.
HL
est ensuite chargé avec la valeur de compteur, cette valeur est incrémentée puis replacée dans le compteur.
On restaure HL
et on revient. Vraiment simple.
L'affichage des compteurs
La dernière partie est une routine qui sera appelée explicitement par un CALL
pour afficher la valeur des compteurs.
call_routine:
push hl
push bc
push de
push af
ld hl, (xcursor)
push hl
ld hl, $0020
ld (xcursor), hl
ld hl, (prt_count)
call out_number
ld hl, $0120
ld (xcursor), hl
ld hl, (out_count)
call out_number
ld hl, $0220
ld (xcursor), hl
ld hl, (cr_count)
call out_number
ld hl, $0320
ld (xcursor), hl
ld hl, (inp_count)
call out_number
pop hl
ld (xcursor), hl
pop af
pop de
pop bc
pop hl
ret
Vu qu'on appelle out_number
, mieux vaut sauver pour commencer tous les registres (sauf IX et IY, mais ces registres sont particuliers pour la ROM). On les restaure en fin de routine.
Puis on récupère les coordonnées du curseur dans HL
. Attention ici, l'adresse xcursor
contient une valeur sur 8 bits. Elle est suivi par ycursor
qui contient aussi une valeur sur 8 bits. Grâce au LD HL, (xcursor)
, c'est donc les coordonnées X
et Y
qui sont chargées dans HL
en une seule instruction.
Ces coordonnées seront elles-aussi restaurées en fin de routine. Pour remettre le curseur là où il se situait avant le CALL
.
Vient ensuite une répétition de quatre fois la même séquence, aux valeurs près.
Tout d'abord, HL
prend les coordonnées d'affichages du nombre que l'on va écrire. Dans la valeur sur 16 bits, Y
arrive en premier, suivi de X
. Ainsi $0020
signifie ligne 0, colonne 32.
Cette position est placée dans la variable système (xcursor)
. HL
prend ensuite la valeur du compteur à afficher, et enfin CALL out_number
se charger d'afficher le nombre entier contenu dans HL
à la position courante.
BASIC
Voici un programme BASIC qui va monter la routine en mémoire. Suivi d'un RUN
pour l'exécuter puis du CALL
pour lancer le hook.
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,21,37,7A,CD,AA,36,21,6D,7A,CD,26,7,3E,C3,32,DF,47,32
310 DATA E2,47,32,E5,47,32,EB,47,21,45,7A,22,E0,47,21,4F,7A,22,E3,47
320 DATA 21,59,7A,22,E6,47,21,63,7A,22,EC,47,E1,F1,C9,43,41,4C,4C,20
330 DATA 0,0,0,0,0,0,0,0,0,E5,2A,3D,7A,23,22,3D,7A,E1,C9,E5
340 DATA 2A,3F,7A,23,22,3F,7A,E1,C9,E5,2A,41,7A,23,22,41,7A,E1,C9,E5
350 DATA 2A,43,7A,23,22,43,7A,E1,C9,E5,C5,D5,F5,2A,5,48,E5,21,20,0
360 DATA 22,5,48,2A,3D,7A,CD,26,7,21,20,1,22,5,48,2A,3F,7A,CD,26
370 DATA 7,21,20,2,22,5,48,2A,41,7A,CD,26,7,21,20,3,22,5,48,2A
380 DATA 43,7A,CD,26,7,E1,22,5,48,F1,D1,C1,E1,C9
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"
.
Une fois le programme chargé et les routines installées via le CALL
, vous pouvez regarder les effets sur les compteurs avec un programme similaire à celui qui suit (pensez à faire un NEW
pour partir à vide) :
10 CALL xxxxx
20 PRINT "HELLO"
30 INPUT A$
40 INPUT A$
50 CALL xxxxx
En remplaçant bien entendu les xxxxx
par la valeur indiquée lors du CALL &"7A00"
Le résultat
Et voici le résultat d'une session comme indiquée ci-dessus.