;Copyright (C) 2008 Pierre Baudemont ; ; This file is part of bf. ; ; bf is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 2 of the License, or ; (at your option) any later version. ; ; bf is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with bf; if not, write to the Free Software ; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA include "OS.h" POINT equ $2e VIRGULE equ $2c PLUS equ $2b MOINS equ $2d DROITE equ $3e GAUCHE equ $3c CROCHET_OUVRANT equ $5b CROCHET_FERMANT equ $5d xdef _nostub xdef _ti92plus ; -- début du programme ; d5 : indice du message à afficher en fin de programme ; a2 : pointeur sur le buffer écran texte ; a3 : pointeur sur le tableau alloué à BrainFuck ; a4 : tableau des messages MSGS ; a5 : adr texte à interpréter ; a6 : table des ROM CALLs ; sauvegarde des registres movem.l d3-d7/a2-a6,-(a7) ; sauvegarde de l'écran (a0 pointe vers la zone allouée dans le tas) ; NB : procédure de screen.a jsr save_scr move.l a0,d0 tst.l d0 beq nomem ; erreur si d4 nul ; on empile a0, pour la restauration de l'écran en fin de prog move.l a0,-(a7) ; tableau des messages lea.l MSGS,a4 ; pour les ROM CALL move.l $c8,a6 ; on efface l'écran move.l ScreenClear*4(a6),a0 jsr (a0) ; on récupère l'argument 0 (le premier) ; nom de la var TXT à ouvrir clr.w -(a7) ; argument: n° de l'arg à récupérer (ici 0, ie le premier) move.l EX_getArg*4(a6),a0 jsr (a0) ; appel à la ROM CALL, résultat dans a0 addq.l #2,a7 ; nettoyage de la pile ; test de a0, nul en cas d'erreur move.l a0,d0 tst.l d0 beq error ;a0 nul : erreur, on quitte le programme ; on vérifie que c'est bien une chaîne de caractères ; a0 pointe actuellement sur le TAG de l'argument cmpi.b #$2d,(a0) ; STR_TAG equ #$2D bne error ; si ce n'est pas une chaîne, on arrête le prog ; voir la doc de TIGCC, sur la pile estack d'expression ; le pointeur dans a0 pointe en fait le tag STR_TAG, fin de l'arg ; qui est stocké sous la forme (pour une chaîne de caractères) : ; | octet nul | ... chaîne ... | octet nul | STR_TAG ; ^ ; | ; nous sommes ICI ; ATTENTION: on va utiliser cette chaîne comme un nom de symbole SYM_STR ; qui est une chaîne encadrée de 0 et dont on doit donner un pointeur ; sur le 0 final... ; tout ce qu'on doit donc faire ici est de remonter le tag... subq.l #1,a0 ; on remonte le tag, on pointe l'octet nul ; maintenant on a un pointeur correct en a0 ; on charge en a0 l'adresse de la var TXT en RAM ; avec vat_var_open de vat.a move.l a0,-(a7) jsr vat_var_open addq.l #4,a7 ; nettoyage de la pile ; test de a0 : nul en cas d'erreur move.l a0,d0 tst.l d0 beq error ;a0 nul : erreur, on quitte le programme ; on passe la taille du texte et la chierie initiale lea 5(a0),a0 ; on stocke l'adr du texte à interpréter dans a5 move.l a0,a5 ; allocation de 4096 octets sur le tas pour le tableau BrainFuck move.l #4096,-(a7) move.l HeapAllocPtr*4(a6),a0 jsr (a0) addq.l #4,a7 ; nettoyage de la pile ; si a0 nul, alloc impossible, on quitte clr.l d5 ; message d'indice 0 (erreur alloc mem), affiché en cas d'erreur move.l a0,d0 tst.l d0 beq fin ; sinon on conserve le pointeur dans a3 move.l a0,a3 move.l a3,DebutTableau move.l a3,d0 addi.l #4095,d0 move.l d0,FinTableau ; changement de la Font de l'écran, passage en F_6x8 move.w #1,-(a7) ; F_6x8 move.l FontSetSys*4(a6),a0 jsr (a0) addq.l #2,a7 ; ancienne font dans d0, stockée dans OldFont move.w d0,OldFont ; initialisation de la zone allouée à 0 move.l #4096,-(a7) ; on positionne 4096 octets clr.w -(a7) ; avec la valeur 0 move.l a3,-(a7) ; dans la zone allouée move.l memset*4(a6),a0 jsr (a0) lea.l 10(a7),a7 ;nettoyage de la pile ; allocation du buffer écran texte (15x40 = 600 octets) move.l #600,-(a7) move.l HeapAllocPtr*4(a6),a0 jsr (a0) addq.l #4,a7 ; si a0 nul, alloc impossible, on quitte clr.l d5 ; message d'indice 0 (erreur alloc mem), affiché en cas d'erreur move.l a0,d0 tst.l d0 beq fin ; sinon pointeur conservé dans a2 move.l a0,a2 ; initialisation de la zone allouée à 0 move.l #600,-(a7) ; on positionne 600 octets clr.w -(a7) move.l a2,-(a7) ; dans la zone allouée move.l memset*4(a6),a0 jsr (a0) lea.l 10(a7),a7 ;nettoyage de la pile ;------------------------------------------------------------------------------ ; INTERPRETATION (une seule passe) ; ; ; Registres : (ceux avec une * sont utilisés dans cette section) ; ----------- ; ; * d3 : coord x de l'affichage du prochain caractère ; * d4 : coord y de l'affichage du prochain caractère ; d5 : indice du message à afficher en fin de programme ; * d6 : ligne courante dans le buffer écran txt ; * d7 : indice de la première ligne à afficher dans le buffer écran txt ; * a2 : buffer écran txt (pour le scrolling vertical si '.' trop nombreux) ; * a3 : pointeur sur le tableau alloué à BrainFuck ; a4 : tableau des messages MSGS ; * a5 : adr texte à interpréter ; a6 : table des ROM CALLs ; ; Sur V200, l'écran texte fait (font F_6x8) fait 15x40 ;------------------------------------------------------------------------------ ; RàZ de d3 et d4 (seul le mot de poids faible est nécessaire pour DrawChr) clr.w d3 ; on commence en x=0 clr.w d4 ; on commence en y=0 clr.w d6 ; offset ligne du buffer écran, ini à 0 ; indice de la première ligne du buffer écran à afficher lors d'un scroll move.w #40,d7 ; stocké dans d7 ; mise en place d'un "fond de pile", nécr pour le mécanisme des crochets clr.l -(a7) main_loop: ; branchement selon le caractère lu ;------------------------------------------------------------------------------ ;case_plus: cmpi.b #PLUS,(a5) bne case_moins ; on incrémente la valeur actuellement pointée addq.b #1,(a3) bra end_loop ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ case_moins: cmpi.b #MOINS,(a5) bne case_droite ; on décrémente la valeur actuellement pointée subq.b #1,(a3) bra end_loop ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ case_droite: cmpi.b #DROITE,(a5) bne case_gauche ; test de dépassement par la droite cmpa.l FinTableau,a3 bne cas_droite_normal move.l DebutTableau,a3 ; en cas de dépassement, retour au début du tableau bra end_loop ; fin du case_droite cas_droite_normal: addq.l #1,a3 bra end_loop ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ case_gauche: cmpi.b #GAUCHE,(a5) bne case_crochet_ouvrant ; test de dépassement par la gauche cmpa.l DebutTableau,a3 bne cas_gauche_normal move.l FinTableau,a3 bra end_loop cas_gauche_normal: subq.l #1,a3 bra end_loop ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ case_crochet_ouvrant: cmpi.b #CROCHET_OUVRANT,(a5) bne case_crochet_fermant ; on teste si l'octet actuellement pointé est nul ; si c'est le cas, la boucle est terminée tst.b (a3) beq cherche_fin_boucle ; on doit trouver la suite de la boucle ; sinon on empile l'adr de retour, ie a5, et on poursuit l'exec move.l a5,-(a7) bra end_loop ; on avance dans le fichier jusqu'à trouver le ']' correspondant ; pour ce faire on compte le nombre de '[' au passage (initialement 0) ; ainsi quand on rencontre un ']', si ce compteur est nul, c'est la fin ; de notre boucle, sinon on décrémente le compteur (c'était une boucle ; interne) cherche_fin_boucle: clr.l d0 ; on a de quoi faire avec un compteur sur 32bits... boucle_recherche: addq.l #1,a5 tst.b (a5) ; attention si on atteint la fin du txt ! (syn err) beq erreur_syn_ouvrant cmpi.b #CROCHET_OUVRANT,(a5) beq incr_compteur cmpi.b #CROCHET_FERMANT,(a5) bne boucle_recherche ; compteur != 0 => continuer tst.l d0 beq end_loop ; compteur nul, on termine la boucle ! ; sinon on décrémente le compteur, et on continue subq.l #1,d0 bra boucle_recherche incr_compteur: addq.l #1,d0 bra boucle_recherche erreur_syn_ouvrant: move.l #8,d5 ; (message indice 2, adr : 2*4) bra erreur_syntaxe ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ case_crochet_fermant: cmpi.b #CROCHET_FERMANT,(a5) bne case_point ; ici on utilise la pile pour retrouver l'adresse du crochet ouvrant qui ; correspond, même si on aurait pu procéder comme ci-dessus ; cela permet d'aller bcp + vite, car sur de nombreuses itérations ; la mth précédente aurait un coût innaceptable. ; de plus c'est possible car on "sait", comme on l'a déjà visité, où ; se trouve le crochet ouvrant qui correspond move.l (a7)+,a5 ; on charge l'adr du '[' qui correspond ; ATTENTION: si on a chargé $0 (fond de pile), err de syn move.l a5,d0 tst.l d0 beq erreur_syn_fermant ; sinon on peut sauter bra end_loop_crochet ; pour ne pas faire a5++ (et éviter de faire ici a5--) erreur_syn_fermant: clr.l -(a7) ; on repose le fond de pile (on le défait en fin d'exec) move.l #12,d5 ;(message indice 3, adr : 3*4) bra erreur_syntaxe ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ case_point: cmpi.b #POINT,(a5) bne case_virgule ; si on pointe $0A (retour chariot), on passe une ligne ; et on n'affiche rien cmpi.b #$0A,(a3) beq last_col cmpi.b #$0D,(a3) ; idem pour $0D beq last_col ; passage de ligne ; affichage de l'octet actuellement pointé move.w #4,-(a7) ; A_REPLACE ; le caractère à afficher (word reconstitué dans la pile) clr.w -(a7) ; word pour le caractère alloué dans la pile move.b (a3),1(a7) ; on place le poids faible move.w d4,-(a7) ; y move.w d3,-(a7) ; x move.l DrawChar*4(a6),a0 jsr (a0) lea.l 8(a7),a7 ; nettoyage de la pile ; affichage dans le buffer écran, ligne à utiliser pointée par d6 move.w d6,d0 move.w d3,d1 divu.w #6,d1 ; pour avoir un offset correct, pas un décalage en pixels add.w d1,d0 move.b (a3),0(a2,d0.w) ; modif des coordonnées cmpi.w #234,d3 ; 234 = 39 * 6 (col 39) ; test la colonne courante beq last_col ; si dernière colonne, changer de ligne addq.w #6,d3 ; sinon on passe à la colonne suivante bra end_loop last_col: clr.w d3 ; on repart en x = 0 ; ligne du buffer écran txt augmente (avec un pas de 40) addi.w #40,d6 cmpi.w #600,d6 ; #600 : dernière ligne du buffer bne pas_derniere_ligne clr.w d6 ; le buffer écran est "circulaire" pas_derniere_ligne: ; nettoyage de la nouvelle ligne courante du buffer ; qui pouvait contenir d'anciens caractères move.l #40,-(a7) ; on positionne 40 octets clr.w -(a7) ; avec 0 pea.l 0(a2,d6.w) ; dans le buffer écran à l'offset qui-va-bien move.l memset*4(a6),a0 jsr (a0) lea.l 10(a7),a7 ; modif ligne écran cmpi.w #112,d4 ; si dernière ligne, on arrête de descendre bne ligne_suivante ; sinon passage à la ligne suivante ; ici on est en bas de l'écran, il faut scroller ; on doit alors afficher le contenu du buffer écran ; à partir de la ligne indiquée par d7 ; pour scroller, et ainsi "gagner" une nouvelle ligne ; on commence par vider l'écran move.l ScreenClear*4(a6),a0 jsr (a0) ; nettoyage de l'écran ; -- boucle d'affichage -- ; NB: on va réutiliser des registres déjà pris mais "restaurables" ; afin d'éviter de passer par la pile, on gagne ainsi un peu de temps ; on va utiliser d7 comme offset ligne dans le buffer ; NB: comme on va boucler sur tout le buffer, ; à la fin d7 aura donc récupéré sa valeur initiale clr.w d4 ; ligne écran ; NB: on peut utiliser ce registre, car ici on sait que d4 valait 14*8=112 boucle_affichage: ; sur les lignes ; affichage d'une ligne avec DrawStr ; marche bien car on nettoie le buffer avec 0, qui est aussi la fin de chaîne move.w #4,-(a7) ; A_REPLACE pea.l 0(a2,d7.w) ; adresse de la chaîne à afficher (ligne du buffer) move.w d4,-(a7) ; y clr.w -(a7) ; ligne : on commence tjs en x=0 move.l DrawStr*4(a6),a0 jsr (a0) lea 10(a7),a7 ; nettoyage de la pile ; on incrémente d4, ligne écran addq.w #8,d4 ; boucle sur d7 add.w #40,d7 cmpi.w #600,d7 ; buffer circulaire... bne test_fin_boucle clr.w d7 ; ...si on était en fin de buffer, retour au début test_fin_boucle: cmpi.w #120,d4 ; boucle terminée quand on a affiché 15 lignes beq sortie_boucle bra boucle_affichage sortie_boucle: ; on restaure d4 move.w #112,d4 ; incrémentation de d7 add.w #40,d7 cmpi.w #600,d7 ; le buffer est circulaire... bne end_loop clr.w d7 ; ...donc retour au début si on était à la fin bra end_loop ligne_suivante: addq.w #8,d4 ; on passe à la ligne suivante bra end_loop ;------------------------------------------------------------------------------ ;------------------------------------------------------------------------------ case_virgule: cmpi.b #VIRGULE,(a5) bne end_loop ; ngetchx fait exactement ce qu'on veut move.l ngetchx*4(a6),a0 jsr (a0) move.b d0,(a3) bra end_loop ;------------------------------------------------------------------------------ ; NB: en BrainFuck, tout ce qui, dans le texte source, diffère des 8 caractères ; reconnus, est considéré comme du commentaire end_loop: addq.l #1,a5 end_loop_crochet: tst.b (a5) beq interpretation_finie bra main_loop ;------------------------------------------------------------------------------ interpretation_finie: move.l #4,d5 ; message de fin "normale" (indice: 1(n°) * 4) erreur_syntaxe: ; le sommet de pile doit être $0 (fond de pile), sinon erreur tst.l (a7) beq suite ; ici il nous faut donc vider la pile jusqu'au fond qu'on avait posé move.l #8,d5 ; msg indice 3 (*4), pour signaler l'erreur ; vidage de la pile jusqu'au fond vidage_pile: tst.l (a7) beq suite ; une fois qu'on a $0, le fond, c'est bon addq.l #4,a7 bra vidage_pile suite: addq.l #4,a7 ; on enlève notre "fond de pile" ; libération de la mémoire occupée par le buffer écran texte move.l a2,-(a7) move.l HeapFreePtr*4(a6),a0 jsr (a0) addq.l #4,a7 ; libération de la mémoire occupée par le tableau BrainFuck move.l DebutTableau,-(a7) ; pour HeapFreePtr il faut l'adr du début du tab ! move.l HeapFreePtr*4(a6),a0 jsr (a0) addq.l #4,a7 ; restauration de la font initiale de l'écran move.w OldFont,-(a7) ; font initiale conservée dans OldFont move.l FontSetSys*4(a6),a0 jsr (a0) addq.l #2,a7 fin: ; attente de l'appui d'une touche move.l 0(a4,d5.l),-(a7) ; affichage du d5 ème message move.l ST_helpMsg*4(a6),a0 ; dans la barre de statut jsr (a0) addq.l #4,a7 ; nettoyage de la pile move.l ngetchx*4(a6),a0 ; pour attendre l'appui d'une touche jsr (a0) error: ; restauration de l'écran jsr restore_scr addq.l #4,a7 ; on enlève de la pile l'adresse de la sauvegarde nomem: ; restauration des registres movem.l (a7)+,d3-d7/a2-a6 rts ;------------------------------------------------------------------------------- section ".data" ; équivalent d'un tableau de char* MSGS: dc.l MsgErr dc.l MsgQuit dc.l MsgErrSynManqueFermant dc.l MsgErrSynManqueOuvrant MsgErr: dc.b 'Memory Allocation ERROR ! ' ; se poursuit avec l'autre MsgQuit: dc.b 'Press any key to quit',0 MsgErrSynManqueFermant: dc.b 'Syntax Error : ] missing ! Press any key to quit',0 MsgErrSynManqueOuvrant: dc.b 'Syntax Error : [ missing ! Press any key to quit',0 DebutTableau: dc.l 0 FinTableau: dc.l 0 OldFont: dc.w 0