Il y a quelques temps, un message sur le forum Config.cfg demandait s'il était simple ou même possible d'ajouter des instructions supplémentaires à la ROM d'un VG5000µ. C'est une question que je me posais aussi, avec dans l'idée d'ajouter des instructions graphiques utilisant l'implémentation des articles précédents].
J'ai donc continué à étudier la ROM (que je commence à bien connaître maintenant) à la recherche d'une méthode. Et on va voir que ça n'est pas gagné.
Le parseur
Lorsque la touche RET
du clavier est appuyée, il se passe plusieurs choses. Tout d'abord, un caractère NUL
(valeur 0) est placé dans le buffer d'entrée à l'emplacement du dernier caractère qui n'est pas un espace (adresses $3c4a
à $3c56
). Le RET
à la fin de cette fonction ramène hors de la boucle principal du traitement interactif.
Après un traitement de la protection de programmes qui n'est pas le sujet ici, une fonction cherche si la ligne commence par un numéro de ligne puis débute la transcription de la ligne vers une ligne « tokenisée ». La procédure de tokénisation, en bref, va repérer toutes les instructions, fonctions et opérations connues et les transformer en « tokens » : un octet dont le bit de poids fort est à 1.
Un token permet de prendre moins de place en mémoire et facilite le travail de l'évaluateur.
La procédure saute bien entendu les chaînes de caractères en repérant les paires de guillemets.
Je laisse de côté le traitement de caractères à significations particulières (le ?
qui remplace PRINT
comme dans la plupart des BASIC par exemple) pour arriver à la recherche principale. Cette routine, qui commence globalement en $23a5
, va comparer le contenu des caractères à décoder (en les passant en majuscules si nécessaire) avec une liste de noms et symboles dans une table située en $23a7
.
Premier problème, cette liste est figée. Si aucune correspondance n'est trouvée, la routine ne dispose pas de hook pour passer la main à une éventuelle liste gérée par l'utilisateur. Les caractères sans correspondance sont laissés tels quels dans le buffer. Si plus tard, à l'évaluation, ces caractères n'ont pas de sens (ne désignent pas une variable par exemple), une erreur sera émise.
On pourrait alors imaginer se servir d'un autre hook
, celui qui est appelé à chaque affichage de caractère ($47e2
) ou celui qui est appelé lors d'un retour à la ligne (\$47e5
), afin d'ajouter un parsing à la main. Mais pour cela, il faudrait choisir des tokens libres pour les nouvelles instructions ; ce qui amène à étudier les tokens.
Les tokens
Voici la liste des tokens avec leurs valeurs.
Commandes | Support - | Opérateurs | Fonctions |
---|---|---|---|
128:END | 178:TAB( | 185:+ | 195:SGN |
129:FOR | 179:TO | 186:- | 196:INT |
130:NEXT | 180:FN | 187:* | 197:ABS |
131:DATA | 181:SPC( | 188:/ | 198:USR |
132:INPUT | 182:THEN | 189:^ | 199:FRE |
133:DIM | 183:NOT | 190:AND | 200:LPOS |
134:READ | 184:STEP | 191:OR | 201:POS |
135:LET | 192:> | 202:SQR | |
136:GOTO | 193:= | 203:RND | |
137:RUN | 194:< | 204:LOG | |
138:IF | 205:EXP | ||
139:RESTORE | 206:COS | ||
140:GOSUB | 207:SIN | ||
141:RETURN | 208:TAN | ||
142:REM | 209:ATN | ||
143:STOP | 210:PEEK | ||
144:ON | 211:LEN | ||
145:LPRINT | 212:STR$ | ||
146:DEF | 213:VAL | ||
147:POKE | 214:ASC | ||
148:PRINT | 215:STICKX | ||
149:CONT | 216:STICKY | ||
150:LIST | 217:ACTION | ||
151:LLIST | 218:KEY | ||
152:CLEAR | 219:LPEN | ||
153:RENUM | 220:CHR$ | ||
154:AUTO | 221:LEFT$ | ||
155:LOAD | 222:RIGHT$ | ||
156:SAVE | 223:MID$ | ||
157:CLOAD | |||
158:CSAVE | |||
159:CALL | |||
160:INIT | |||
161:SOUND | |||
162:PLAY | |||
163:TX | |||
164:GR | |||
165:SCREEN | |||
166:DISPLAY | |||
167:STORE | |||
168:SCROLL | |||
169:PAGE | |||
170:DELIM | |||
171:SETE | |||
172:ET | |||
173:EG | |||
174:CURSOR | |||
175:DISK | |||
176:MODEM | |||
177:NEW |
On peut remarquer que cette liste comporte plusieurs parties. La première, de 128 à 177, contient des instructions, c'est-à-dire des commandes impératives, sans valeur de retour.
De 178 à 184, il s'agit de commandes de support, qui ne peuvent être trouvées qu'en conjonction de commandes maîtresses. Par exemple THEN
avec IF
; TO
et STEP
avec FOR
,...
De 185 à 194, il s'agit d'opérateurs logiques et arithmétiques. Enfin, à partir de 195 jusqu'à la fin, 223, il s'agit de fonctions, qui retournent des valeurs, et qui apparaîtrons donc, au côté des opérateurs, dans des expressions.
On commence à sentir que s'il fallait ajouter des mot-clés, en fonction de leur nature, il faudrait les placer dans le bon groupe. Sauf que cette liste est compacte. On peut imaginer ajouter des fonctions après MID$
, mais ajouter une instruction ou un opérateur décalerait toute la table, ce qui poserait un problème de compatibilité au moins avec les programmes enregistrés (les programmes BASIC enregistrés le sont sous forme tokenisée).
L'évaluateur
C'est donc vers l'évaluateur qu'il faut se tourner et se demander comment sont traitées les lignes en BASIC. Et c'est là que vient se planter le dernier clou dans le cercueil de l'ajout de commandes utilisateurs... du moins sans modifier la ROM.
Tout d'abord, examinons le décodage des tokens en $250f
.
sub a,$80
jp c,inst_let ; Comme tous les tokens sont supérieurs ou égaux
; à $80, si le caractère est inférieur, c'est le début du nom d'une variable.
; C'est un LET implicite.
cp a,$32
jp nc,stx_err_prt ; Les 50 ($32) premiers tokens seulement sont des instructions
; Si le token est après, alors c'est une Erreur de syntaxe.
Voilà... la ROM contient en dur les bornes des instructions pouvant être décodées.
Et ce n'est pas fini. Lorsque l'on regarde l'évaluation d'expression en $28d8
:
xor a,a
ld (valtyp),a ; Type numérique par défaut
rst chget
jp z,missing_op ; Cas où le caractère est NUL
jp c,str_to_num ; Cas où le caractère est un chiffre.
cp a,$26
jp z,str_hex_dec ; Saut si sur le point de parser un nombre en hexa (caractère '&')
call a_to_z_2
jr nc,str_to_var ; Saut si la valeur est entre A et Z
cp a,$b9 ; Token pour '+'
jr z,parse_value
cp a,$2e ; Caractère '.'
jp z,str_to_num
cp a,$ba ; Token pour '-'
jr z,str_to_min
cp a,$22 ; Caractère '"'
jp z,str_to_str
cp a,$b7 ; Token pour 'NOT'
jp z,str_to_not
cp a,$b4 ; Token pour 'FN'
jp z,str_to_fn
sub a,$c3 ; Token pour 'SGN', la première des fonctions
jr nc,str_to_func
Afin de déterminer le type de valeur à décoder, un certain nombre de tokens est là aussi en dur.
Et donc ?
Et donc tel quel, la ROM ne fourni pas de mécanisme d’extension pour écrire de nouvelles instructions. D'autre part, même s'il reste une trentaine de tokens libres à la fin de la liste, les constantes dans le parseur et dans l'évaluateur exigent que les tailles des groupes du tableau soient respectés.
Reste la possibilité de modifier la ROM pour ajouter les nouvelles instructions, ce qui n'est pas très compliqué, mais qui nécessitera de la conversion au chargement pour être compatible avec des programmes en BASIC enregistrés sur K7.