Edito

Le journal d'un codeur en 68k, C et C++.
Adresse : 2^2 * (2^0 + 2^2 * (2^0 + 2^2 * (2^0)))

Rien de très passionnant en somme.

img

"Si tu veux etre underground, tu fais du kernel" 17-02-2004, ©PpHd
"Je marche seul, sans témoin, sans personne" ©JJG

Jeudi 18 Avril 2013
pdtlib::ManageCmdline
Et hop, encore une fonction écrite. Cette fonction est faire pour parser la ligne de commande en une instruction dans le programme, appelant la callback fournie pour chaque switch valide trouvé sur la ligne. Il y a bien sûr une callback dédiée aux arguments qui ne sont pas des switches. La valeur de retour de chaque callback permet à la fonction de savoir la suite qu'elle doit donner au traitement de la ligne de commande.

Bref, ça donne un code propre et simplissime. Quelques fonctions d'accès (GetCurrentArg, GetNextArg, ResetCmdlineParsing) permettent de rester souple, voire de procéder au parsing à la main si besoin était, ou de traiter facilement les switches demandant des arguments, sans faire paniquer le parseur. :)

Les switches peuvent être de la forme +x, -x, ++xyz ou --xyz. Ca donne de la liberté.

Voici le code, assez spaghetti mais optimisé en taille. Le côté spaghetti vient du fait que tous les branchements sont courts, mais la fonction dépassant les 128 octets, il a fallu déplacer certains morceaux de code à la fin, faisant perdre un peu en lisibilité. Mais au moins, question exécution, c'est optimal.

Du reste, si vous voyez des optimisations, je suis preneur. J'ai quand même un peu réfléchi le code, mais ça reste un premier jet :
;=============================================================================== 
; 
;       pdtlib::ManageCmdline 
; 
;       in      4(sp)   CMDLINE* cl 
;               8(sp)   void*    Data 
;               12(sp)  char*    SwitchesList 
;               16(sp)  ushort   (*ArgNotSwitch) (void* Data asm (a0)) 
;               20(sp)  ushort   (*ArgSwitch1)   (void* Data asm (a0), 
;                                                 short Sign asm (d1.w) 
;                                                ) 
;               24(sp)  (*)      ... 
; 
;       out     d0.w    ushort 
; 
;       destroy d0.w 
; 
;       Return a pointer to the current arg while parsing with ManageCmdline, 
;       or NULL if parsing is finished 
; 
;       SwitchesList format: 
; 
;       dc.b 'ShortSwitch1', "Long Switch 1", 0 
;       dc.b 'ShortSwitch2', "Long Switch 2", 0 
;       dc.b ... 
;       dc.b 0, 0 
; 
;       'ShortSwitchX' or "Long Switch X" may be 0, if no such switch exists 
;       When both are 0, this is the end of the table 
; 
;=============================================================================== 
 
pdtlib##antispam##0001: 
        movem.l a2-a5,-(sp) 
        movem.l 5*4(sp),a2-a5 
 
\Loop:  movea.l a2,a0 
        bsr.s   GetNextArg 
        move.l  a0,d0 
        beq.s   \EndOfParsing 
 
        movea.l a4,a1 
        moveq.l #0,d2 
 
        move.w  #'+',d1                 ; Clear upper byte for (*ArgSwitch) 
        cmp.b   (a0)+,d1 
        beq.s   \CheckSecondSign 
 
        addq.w  #2,d1                   ; - 
        cmp.b   -1(a0),d1 
        bne.s   \ArgNotSwitch 
 
\CheckSecondSign: 
        cmp.b   (a0)+,d1 
        beq.s   \LongSwitch 
 
        ; This is a short switch 
 
        moveq.l #PDTLIB_INVALID_SWITCH,d0 
        tst.b   (a0) 
        bne.s   \EndOfParsing 
        tst.b   -(a0) 
        beq.s   \EndOfParsing 
        moveq.l #PDTLIB_SWITCH_NOT_FOUND,d0 
 
\ShortSwitchLoop: 
        tst.b   (a1) 
        bne.s   \TestShortSwitch 
                tst.b   1(a1) 
                beq.s   \EndOfParsing 
\TestShortSwitch: 
        cmpm.b  (a0)+,(a1)+ 
        beq.s   \ShortSwitchFound 
\SkipShortSwitchLoop: 
                tst.b   (a1)+ 
                bne.s   \SkipShortSwitchLoop 
                        addq.w  #4,d2 
                        subq.l  #1,a0 
                        bra.s   \ShortSwitchLoop 
 
        ; This is a long switch 
 
\LongSwitch: 
        moveq.l #PDTLIB_INVALID_SWITCH,d0 
        tst.b   (a0) 
        beq.s   \EndOfParsing 
        moveq.l #PDTLIB_SWITCH_NOT_FOUND,d0 
        pea     (a0) 
 
\LongSwitchLoop: 
        movea.l (sp),a0 
        addq.l  #1,a1 
\TestLongSwitch: 
        cmpm.b  (a0)+,(a1)+ 
        beq.s   \TestEndOfLongSwitch 
                subq.l  #1,a1 
\SkipLongSwitchLoop: 
                tst.b   (a1)+ 
                bne.s   \SkipLongSwitchLoop 
                addq.w  #4,d2 
                tst.b   (a1) 
                bne.s   \LongSwitchLoop 
                tst.b   1(a1) 
                bne.s   \LongSwitchLoop 
                bra.s   \EndOfParsing 
 
        ; This is not a switch 
 
\ArgNotSwitch: 
        movea.l a3,a0 
        jsr     (a5) 
 
        ; Check return value of callbacks 
 
\CheckReturnValue: 
        subq.w  #1,d0 
        beq.s   \Loop 
        subq.w  #1,d0 
        bne.s   \WrongReturnValue 
        moveq.l #PDTLIB_STOP_PARSING,d0 
        bra.s   \EndOfParsing 
 
\WrongReturnValue: 
        moveq.l #PDTLIB_WRONG_RETURN_VALUE,d0 
 
\EndOfParsing: 
        movem.l (sp)+,a2-a5 
        rts 
 
\TestEndOfLongSwitch: 
        tst.b   -1(a0) 
        bne.s   \TestLongSwitch 
 
        ; LongSwitchFound 
 
        addq.l  #4,sp 
\ShortSwitchFound: 
        movea.l a3,a0 
        movea.l 9*4(sp,d2.w),a1 
        jsr     (a1) 
        bra.s   \CheckReturnValue


Fun fact : hier soir, j'ai fini de coder la vérification des valeurs de retour, le lancement des callbacks, et surtout le traitement des switches courts. J'ai relu ma fonction sans y trouver de bug apparent. Mais la nuit, j'ai fait un rêve, dans lequel je traçais ma fonction et j'y trouvais un bug. Le matin, ça m'est venu en tête, et ça m'a turlupiné toute la journée, je me suis retourné tout le code dans la tête sans parvenir à trouver ce qui clochait. Et sur le chemin du retour du boulot, je me suis souvenu de ce que j'avais rêvé, et le bug était bien là. Je crois qu'il faut que j'arrête, certains pourraient me prendre pour un fou :D
Posté à
21:49
 par Folco -
Lundi 15 Avril 2013
CreateSymStr
Et voilà, j'ai compté les cycles, les neg/movea / exg/subq sont plus rapides que les lea, qui se font plomber par le temps de calcul des adresses effectives. C'est ça qui est marrant en assembleur, une petite optimisation permet en plus de donner à son code une tournure infiniment plus l33t, qui fait toute la différence entre le vieux codard routard et le p'tit jeune qui débute.
;=============================================================================== 
; 
;       CreateSymStr 
; 
;       in      a0      char* FileName as a C-string 
; 
;       out     a0      SYM_STR of FileName 
; 
;       destroy d0/a1 
; 
;       Return the SYM_STR of FileName. FileName may contain a path. 
;       Return NULL if FileName is too long 
; 
;       Warning: stack pointer is decreased with 20 after the call, 
;       even if it fails 
; 
;=============================================================================== 
 
CreateSymStr: 
        move.l  (sp),d0 
        lea     -20(sp),sp 
        movea.l sp,a1 
        move.l  d0,(a1)+ 
        clr.b   (a1)+ 
        moveq.l #17,d0 
\Loop:  move.b  (a0)+,(a1)+ 
        beq.s   \End 
        dbf.w   d0,\Loop 
                neg.w   d0 
                move.w  d0,a1 
\End:   exg.l   a0,a1 
        subq.l  #1,a0 
        rts
Posté à
15:36
 par Folco -
Dimanche 14 Avril 2013
Et le code de AddFile
Histoire de poster quelque chose, parce que je viens de réécrire le code et que ça fait un bail que j'ai rien écrit ici :
;=============================================================================== 
; 
;       pdtlib::CreateFile 
; 
;       in      a0      char*  FileName 
;               d1.w    HANDLE hd 
; 
;       out     d0.w    0 is something fails, else != 0 
; 
;       destroy d0.l 
; 
;       Create a file whose name is FileName, and the associated HANDLE is hd 
; 
;=============================================================================== 
 
pdtlib##antispam##0005: 
        moveq.l #0,d0                   ; Return code 
        movem.l d0-d2/a0-a2,-(sp) 
        movea.l sp,a2 
 
        lea     -60(sp),sp 
        pea     (sp) 
        ROMT    ER_catch 
        tst.w   d0 
        bne.s   \End                    ; Exception caught 
 
        movea.l 12(a2),a0 
        bsr.s   CreateSymStr 
        move.l  a0,-(sp) 
        beq.s   \End2                   ; Invalid SYM_STR 
 
        ROMT    SymAdd 
        move.l  d0,(sp) 
        beq.s   \End2                   ; HS_NULL 
 
        ROMT    DerefSym 
        move.w  6(a2),12(a0) 
        not.l   (a2)                    ; Success 
 
\End2:  ROMT    ER_success 
\End:   movea.l a2,sp 
        movem.l (sp)+,d0-d2/a0-a2 
        rts

Voilà, c'est pas testé, mais ça devrair marcher.

Et pour la route, le code de CreateSymStr qui est utilisé ici :
;=============================================================================== 
; 
;       CreateSymStr 
; 
;       in      a0      char* FileName as a C-string 
; 
;       out     a0      SYM_STR of FileName 
; 
;       destroy d0/a1 
; 
;       Return the SYM_STR of FileName. FileName may contain a path. 
;       Return NULL if FileName is too long 
; 
;       Warning: stack pointer is decreased with 20 after the call, 
;       even if it fails 
; 
;=============================================================================== 
 
CreateSymStr: 
        move.l  (sp),d0 
        lea     -20(sp),sp 
        movea.l sp,a1 
        move.l  d0,(a1)+ 
        clr.b   (a1)+ 
        moveq.l #17,d0 
\Loop:  move.b  (a0)+,(a1)+ 
        beq.s   \End 
        dbf.w   d0,\Loop 
                lea     1,a1    ; neg d0/movea ? 
\End:   lea     -1(a1),a0       ; exg/subq ? 
        rts

Il reste deux petites optimisations éventuelles en vitesse, mais j'ai pas encore vérifié, en tout cas pour la taille ça change rien. Les programmeurs en C devraient crier au scandale. :)
Posté à
15:12
 par Folco -
Jeudi 15 Septembre 2011
pdtlib::AddFile
Je parlais hier d'une fonction de ma librarie, AddFile :
short Pdtlib::AddFile (const char* Filename, short Handle)

Plutôt que de montrer son implémentation, j'ai retrouvé la gymnastique que devait faire eexec pour faire une opération aussi simple qu'ajouter un fichier au file system :
[box=AddFile]
CreateSymStr: 
	lea.l	SYM_STR_FRAME+1(%fp),%a2		|buffer 
	moveq.l	#8+1+8+1-1,%d0				|counter: "folder\filename\0" 
CopySymStr2: 
	move.b	(%a0)+,(%a2)+				|copy filename 
	bne.s	Continue				|end reached 
		subq.l	#1,%a2				|SYM_STR* 
		rts 
Continue: 
	dbf.w	%d0,CopySymStr2				|else loop 
		moveq.l	#ERROR_NAME,%d7 
 
 
		.include	"errors.s" 
		.include	"byte.s"		|included here, because it allow a short branch to jump to it 
 
EndOfCopy2: 
 
	|=============================================================== 
	|	Here, create a try/endrty structure using ER_catch, 
	|	because SymAdd can fail without coming back to the program 
	|=============================================================== 
	pea.l	ERROR_FRAME(%fp)			|*frame 
	RC	ER_catch				|set the frame handler 
	moveq.l	#ERROR_ADD_SYM,%d7			|default: SymAdd has failed 
	tst.w	%d0					|test it 
	bne	ThrowError				|failed... 
	pea.l	(%a2)					|else we can add it 
	RC	SymAdd					|create symbol 
	move.l	%d0,(%sp)				|push and test its HSYM 
	beq	ThrowError				|shit... 
		RC	DerefSym			|get SYM_ENTRY * 
		move.w	%d4,12(%a0)			|and store handle 
		RC	ER_success			|pop error frame 
		bset.b	#FLAG_SYM_ADDED,%d6		|set success flag
[/box]
Et voilà le merdier. C'est quand même plus sympa quand ça tient en deux lignes. :)



ps -> La CSS par défaut est nulle à chier et dépend des fins de ligne...
Posté à
20:08
 par Folco -
Mercredi 14 Septembre 2011
Cache-cache
Mon assembleur n'a jamais avancé vite. Je le reprends et le retrouve ces temps-ci, avec joie, parce que :
- cette version n'est pas trop mal écrite, même après un an, je comprends tout. Mes procédures et algo ne sont donc pas trop pourris
- il y avait un bug, dans un algo justement, à la simlpe relecture il m'a sauté aux yeux, la correction a été faite du premier coup, la vérification validée dans la foulée
- j'avais de temps à autre fait quelques petites modifs, non-testés, introduisant deux bugs. Deux minutes de débogage ont réussi à les corriger en toute simplicité
- le dernier truc que j'avais codé était la lecture du fichier de conf, mais j'avais eu la flemme de tester. Tout a marché au poil
Bref, un vrai plaisir, c'est la première fois que je reprends du code de longue date avec plaisir, sans m'y perdre et m'auto-flageller pour ce que j'y trouve, c'est bon signe.


Mais bon, je dois rester moi-même, c'est-à-dire ne surtout pas avancer vers le coeur du moteur, la fabrication d'un fichier objet. Tout sauf ça, malheureux !

J'ai donc lancé l'écriture de mon gestionnaire de mémoire cache dédié à ce programme.

Etant donné les polémiques autour de l'utilisation intensive de la mémoire flash dans un programme, l'utilisation de la mémoire cache ne se fait qu'à la demande express de l'utilisateur.
En effet, bien que toutes les options soient positionnables à la compilation de as, ou encore définissables dans un fichier de conf parsé à chaque lancement, le switch pour activer le swap doit être explicitement entré en ligne de commande lors de l'appel.

Pour info, j'ai réécrit le comportement de la configuration et des options que j'avais décrit ici. C'est bien marrant, et côté implémentation, c'est ridiculement simple, bien que le résultat soit souple et puissant. Bref, un régal.


Venons-en au swap. Alors, comment marche-t-il ?

Le but est de pouvoir déplacer en flash des données du programme afin de libérer de la place en RAM. Ok, mais il y a plusieurs contraintes :
- on ne doit déplacer que ses propres données, le côté multi-task de PedroM rendant dangereux le fait de déplacer les données de Monsieur Toutlemonde.
- pour envoyer un handle en flash, il faut lui créer un fichier, l'ajouter à la VAT, et archiver le tout
- les parties du programme qui créent, redimensionnent, effacent ou écrivent dans les handles doivent avoir un moyen simple et petit (en taille**) de s'assurer qu'ils sont en RAM
- un handle pourra appartenir à un fichier une fois qu'il aura été swappé, c'est donc une sollicitude à avoir avant de l'effacer ou de le re-swapper, savoir où il en est dans le file system, car les procédures à suivre sont différentes
- pour avoir un swap efficace, il faut une ram le plus fluide possible, donc adapter toutes les structures du programme en conséquence


[*] Pour ce qui est cachable, on préfèrera dans l'ordre :
- les handles internes au programme (table des symboles, des adressages, etc...)
- les objets dans /obj, as s'attribuant d'autorité ce répertoire
- les fichiers sources présents dans la ligne de commande
- les fichiers de la liste des fichiers ouverts (tous ne sont pas présents dans la ligne de commande, par exemple les headers)

[*] Pour créer un fichier, ultra simple, grâce à la magie de PedroM et de Pdtlib :
        suba.l  a0,a0 
        jsr     TMPNAM(a6) 
        move.w  d3,-(sp)            ; handle 
        pea     (a0) 
        jsr     ADD_FILE(a6)

Et voilà, pas de nom à créer à la main ou à contrôler, PedroM travaille pour vous. Et le pdtlib::AddFile vous crée gentiment votre fichier, pour peu que vous lui donniez un nom (C-string, et non SYM_STR affreuse), un handle. Il s'occupe d'ER_throw tout seul, et ça, c'est du bonheur en assembleur, je vous assure. :)

[*] Pour s'assurer que son handle est en RAM, rien de plus simple :
        bsr     CacheToRam

Et voilà, le cache handler s'occupe de passer le handle en RAM (si nécessaire), swappe ce qu'il faut s'il le faut, ne détruit aucun registre, et surtout ne renvoie rien : il lance une exception tou seul en cas de pépin. Parce que c'est toujours très chiant de vérifier des valeurs de retour en assembleur et de réparer ses registres abimés. Sisi, à la 10ème utilisation d'une fonction, c'est lourd.

[*] Un handle appartient-il à un fichier ou non ? Comment faire ? Parcourir la VAT à la recherche des fichiers, trouver leur handle, comparer avec celui qui nous intéresse ? Non, PreOS déchire toujours tout :
        RAMC    RAM_kernel::Hd2Sym 
        move.l  a0,d1 
        beq.s   \NotAFile

Et voilà. La magie des ramcalls. Qui a dit que c'était le mal ??

[*] La fluidité de la RAM pose un autre problème. Outre le fait de ne rien y locker, et donc de redéréférencer ce qu'il faut après toute modification du heap, on se retrouve devant une contrainte habituelle en embarqué : la performance.
Je m'étais fixé comme objectif de ne lire qu'une fois chaque octet d'un fichier source, par souci d'efficacité, les lectures en RAM étant longues.
Inconvénient : ça nécessite de stocker les symboles dans une table, qui elle par contre, pourra être lue de multiples fois. Pour en avoir parlé avec Nitro, vénérable yAronaute auteur de as premier du nom, la table des symboles représente le plus gros overhead lors de l'assemblage avant la création du fichier objet (où l'on peut stripper tous les symboles non-nécessaires).

Donc exit la table des symboles où ils sont tous recopiés. Et tant pis pour les lectures dans les sources.
Alors la solution, je l'ai trouvée :
- une table des fichiers ouverts, contenant uniquement les noms de fichiers
- un format de table de symboles qui colle à cette table : un symbole est une structure de ce type :
struct {char NumFile; short offset;};

Et voilà, 3 octets par symbole, qui dit mieux ?
On voit tout de suite les inconvénients :
- nombre de fichiers ouverts : 256. C'est 1000 fois assez, mais je déteste les limitations qui descendent sous 32 bits
- lecture du symbole non-alignée. Mais 3 octets, ça va encore aller
- aucune donnée stockée par rapport au symbole. C'est à dire que question perf, c'est l'abomination de la désolation. Pour trouver un symbole, il faut tous les vérifier. Pour savoir trois fois de suite ce que vaut 'tios::HeapDeref', il va falloir se bouffer 3 fois tios.h. Autant dire que l'optimisation en vitesse de recherche devra être cruciale pour ne pas vider les piles entre deux symboles. Et ça restera pas terrible. Mais c'est pas grave, priorité mémoire.



Voilà. Je pensais poster tout le code des wrappers, qui n'est pas bien long de toute façon. En fait, quelques lignes montrent qu'une lib bien foutue et un bon OS permettent de réduire énormément le temps de dev et les lignes de code. Par contre, je posterai la fonction qui gère à proprement parler le swap. Pour le coup, elle est sympa. Surprise, à bientôt. :)




Greetings : L'implémentation d'un cache a été rendu possible grâce à :
- la faculté de pedrom::unlink à effacer un fichier en flash sans passer par la RAM (sinon, impossible de déswapper, et le programme termine dans un état bloquant où il ne peut plus manipuler de mémoire sans rien effacer d'important)
- l'application d'un patch à EM_moveSymFromExtMem/ToExtMem qui permet de ne pas faire varier le handle d'un fichier lors de son archivage/désarchivage (ça n'empêchait pas le cache, mais ça le compliquait sérieusement, entre autre à cause de l'archivage des sources qui aurait foutu un bordel monstre)

Merci PpHd. :)




* Ahahah.
** Comme quoi, la taille ça compte, en fait
Posté à
23:47
 par Folco -
Mercredi 13 Juillet 2011
Syntax highlight 68k pour Code::Blocks
J'ai déjà écrit un lexer de code 68000 pour Scintilla, utilisé par un IDE orienté C/C++, j'ai nommé Code::Blocks.

Fidèle à mon habitude de reprendre pour perfectionner au lieu d'avancer vers la suite, j'ai ré-écrit un bon 50% de ce lexer pour avoir quelque chose de plus propre. Au final, je suis content, c'est :
- nettement plus simple à lire et à comprendre
- nettement plus petit
- (pas forcément) nettement plus rapide (ok, on est pas en embarqué, donc osef)


J'avais posté le code de la version initiali ici : http://www.yaronet.com/posts.php?sl=&s=135345&p=5&h=131#131
Comme personne n'en a rien à foutre, voici la nouvelle version :
[box=LexA68k.cxx]
static void ColouriseA68kDoc (unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler) 
{ 
    // Used to buffer a string, to be able to compare it using built-in functions 
    char Buffer[100]; 
 
 
    // Used to know the length of an operator 
    int OpType; 
 
 
    // Get references to keywords lists 
    WordList &cpuInstruction = *keywordlists[0]; 
    WordList &registers      = *keywordlists[1]; 
    WordList &directive      = *keywordlists[2]; 
    WordList &extInstruction = *keywordlists[3]; 
    WordList &alert          = *keywordlists[4]; 
    WordList &doxygenKeyword = *keywordlists[5]; 
 
 
    // Instanciate a context for our source 
    StyleContext sc(startPos, length, initStyle, styler); 
 
 
    /************************************************************ 
    * 
    *   Parse the source 
    * 
    ************************************************************/ 
 
    for ( ; sc.More(); sc.Forward()) 
    { 
        /************************************************************ 
        * 
        *   A style always terminates at the end of a line, even for 
        *   comments (no multi-lines comments) 
        * 
        ************************************************************/ 
        if (sc.atLineStart) { 
            sc.SetState(SCE_A68K_DEFAULT); 
        } 
 
 
        /************************************************************ 
        * 
        *   If we are not in "default style", check if the style continues 
        *   In this case, we just have to loop 
        * 
        ************************************************************/ 
 
        if (sc.state != SCE_A68K_DEFAULT) 
        { 
            if (   ((sc.state == SCE_A68K_NUMBER_DEC)        && isdigit(sc.ch))                      // Decimal number 
                || ((sc.state == SCE_A68K_NUMBER_BIN)        && IsBin(sc.ch))                        // Binary number 
                || ((sc.state == SCE_A68K_NUMBER_HEX)        && isxdigit(sc.ch))                     // Hexa number 
                || ((sc.state == SCE_A68K_MACRO_ARG)         && isdigit(sc.ch))                      // Macro argument 
                || ((sc.state == SCE_A68K_STRING1)           && (sc.ch != '\''))                     // String single-quoted 
                || ((sc.state == SCE_A68K_STRING2)           && (sc.ch != '\"'))                     // String double-quoted 
                || ((sc.state == SCE_A68K_MACRO_DECLARATION) && IsIdentifierChar(sc.ch))             // Macro declaration (or global label, we don't know at this point) 
                || ((sc.state == SCE_A68K_IDENTIFIER)        && IsIdentifierChar(sc.ch))             // Identifier 
                || ((sc.state == SCE_A68K_LABEL)             && IsIdentifierChar(sc.ch))             // Label (local) 
                || ((sc.state == SCE_A68K_COMMENT_DOXYGEN)   && IsDoxygenChar(sc.ch))                // Doxygen keyword 
                || ((sc.state == SCE_A68K_COMMENT_SPECIAL)   && isalpha(sc.ch))                      // Alert 
                || ((sc.state == SCE_A68K_COMMENT)           && !isalpha(sc.ch) && (sc.ch != '\\'))) // Normal comment 
            { 
                continue; 
            } 
 
        /************************************************************ 
        * 
        *   Check if current state terminates 
        * 
        ************************************************************/ 
 
            // Strings: include terminal ' or " in the current string by skipping it 
            if ((sc.state == SCE_A68K_STRING1) || (sc.state == SCE_A68K_STRING2)) { 
                sc.Forward(); 
            } 
 
 
            // If a macro declaration was terminated with ':', it was a label 
            else if ((sc.state == SCE_A68K_MACRO_DECLARATION) && (sc.chPrev == ':')) { 
                sc.ChangeState(SCE_A68K_LABEL); 
            } 
 
 
            // If it wasn't a Doxygen keyword, change it to normal comment 
            else if (sc.state == SCE_A68K_COMMENT_DOXYGEN) { 
                sc.GetCurrent(Buffer, sizeof(Buffer)); 
                if (!doxygenKeyword.InList(Buffer)) { 
                    sc.ChangeState(SCE_A68K_COMMENT); 
                } 
                sc.SetState(SCE_A68K_COMMENT); 
                continue; 
            } 
             
 
            // If it wasn't an Alert, change it to normal comment 
            else if (sc.state == SCE_A68K_COMMENT_SPECIAL) { 
                sc.GetCurrent(Buffer, sizeof(Buffer)); 
                if (!alert.InList(Buffer)) { 
                    sc.ChangeState(SCE_A68K_COMMENT); 
                } 
                // Reset style to normal comment, or to Doxygen keyword if it begins with '\'  
                if (sc.ch == '\\') { 
                    sc.SetState(SCE_A68K_COMMENT_DOXYGEN); 
                } 
                else { 
                    sc.SetState(SCE_A68K_COMMENT); 
                } 
                continue; 
            } 
 
 
            // If we are in a comment, it's a Doxygen keyword or an Alert 
            else if (sc.state == SCE_A68K_COMMENT) { 
                if (sc.ch == '\\') { 
                    sc.SetState(SCE_A68K_COMMENT_DOXYGEN); 
                } 
                else { 
                    sc.SetState(SCE_A68K_COMMENT_SPECIAL); 
                } 
                continue; 
            } 
                 
 
            // Check if we are at the end of an identifier 
            // In this case, colourise it if was a keyword. 
            else if ((sc.state == SCE_A68K_IDENTIFIER) && !IsIdentifierChar(sc.ch)) { 
                sc.GetCurrentLowered(Buffer, sizeof(Buffer));                           // Buffer the string of the current context 
                if (cpuInstruction.InList(Buffer)) {                                    // And check if it belongs to a keyword list 
                    sc.ChangeState(SCE_A68K_CPUINSTRUCTION); 
                } 
                else if (extInstruction.InList(Buffer)) { 
                    sc.ChangeState(SCE_A68K_EXTINSTRUCTION); 
                } 
                else if (registers.InList(Buffer)) { 
                    sc.ChangeState(SCE_A68K_REGISTER); 
                } 
                else if (directive.InList(Buffer)) { 
                    sc.ChangeState(SCE_A68K_DIRECTIVE); 
                } 
            } 
 
            // All special contexts are now handled.Come back to default style 
            sc.SetState(SCE_A68K_DEFAULT); 
        } 
 
 
        /************************************************************ 
        * 
        *   Check if we must enter a new state 
        * 
        ************************************************************/ 
 
        // Something which begins at the beginning of a line, and with  
        // - '\' + an identifier start char, or 
        // - '\\@' + an identifier start char 
        // is a local label (second case is used for macro local labels). We set it already as a label, it can't be a macro/equ declaration 
        if (sc.atLineStart && (sc.ch < 0x80) && IsIdentifierStart(sc.chNext) && (sc.ch == '\\')) { 
            sc.SetState(SCE_A68K_LABEL); 
        } 
 
        if (sc.atLineStart && (sc.ch < 0x80) && (sc.ch == '\\') && (sc.chNext == '\\')) { 
            sc.Forward(2); 
            if ((sc.ch == '@') && IsIdentifierStart(sc.chNext)) { 
                sc.ChangeState(SCE_A68K_LABEL); 
                sc.SetState(SCE_A68K_LABEL); 
            } 
        } 
         
        // Label and macro identifiers start at the beginning of a line 
        // We set both as a macro id, but if it wasn't one (':' at the end), 
        // it will be changed as a label. 
        if (sc.atLineStart && (sc.ch < 0x80) && IsIdentifierStart(sc.ch)) { 
            sc.SetState(SCE_A68K_MACRO_DECLARATION); 
        } 
        else if ((sc.ch < 0x80) && (sc.ch == ';')) {                            // Default: alert in a comment. If it doesn't match 
            sc.SetState(SCE_A68K_COMMENT);                                      // with an alert, it will be toggle to a normal comment 
        } 
        else if ((sc.ch < 0x80) && isdigit(sc.ch)) {                            // Decimal numbers haven't prefix 
            sc.SetState(SCE_A68K_NUMBER_DEC); 
        } 
        else if ((sc.ch < 0x80) && (sc.ch == '%')) {                            // Binary numbers are prefixed with '%' 
            sc.SetState(SCE_A68K_NUMBER_BIN); 
        } 
        else if ((sc.ch < 0x80) && (sc.ch == '$')) {                            // Hexadecimal numbers are prefixed with '$' 
            sc.SetState(SCE_A68K_NUMBER_HEX); 
        } 
        else if ((sc.ch < 0x80) && (sc.ch == '\'')) {                           // String (single-quoted) 
            sc.SetState(SCE_A68K_STRING1); 
        } 
        else if ((sc.ch < 0x80) && (sc.ch == '\"')) {                           // String (double-quoted) 
            sc.SetState(SCE_A68K_STRING2); 
        } 
        else if ((sc.ch < 0x80) && (sc.ch == '\\') && (isdigit(sc.chNext))) {   // Replacement symbols in macro are prefixed with '\' 
            sc.SetState(SCE_A68K_MACRO_ARG); 
        } 
        else if ((sc.ch < 0x80) && IsIdentifierStart(sc.ch)) {                  // An identifier: constant, label, etc... 
            sc.SetState(SCE_A68K_IDENTIFIER); 
        } 
        else { 
            if (sc.ch < 0x80) { 
                OpType = GetOperatorType(sc.ch, sc.chNext);                     // Check if current char is an operator 
                if (OpType != NO_OPERATOR) { 
                    sc.SetState(SCE_A68K_OPERATOR); 
                    if (OpType == OPERATOR_2CHAR) {                             // Check if the operator is 2 bytes long 
                        sc.ForwardSetState(SCE_A68K_OPERATOR);                  // (>> or <<) 
                    } 
                } 
            } 
        } 
    }                                                                           // End of for() 
    sc.Complete(); 
}
[/box]
Et voilà. Juste un truc à voir : faut-il oui ou non ces p****n de 0x80 ? J'y comprends rien aux charset, et franchement autant l'algo m'intéresse, autant ce genre de détails sordides, ça me gave.
Ce code permet d'avoir un état transitoire batard (puisqu'exporté à l'utilisateur) en moins : "Current word in comment". Je l'avais introduit pour rajouter les Alertes (FIXME et cie) dans les commentaires, ainsi que les mots-clé Doxygen. J'ai réussi à m'en débarasser.

J'ai également ajouté la gestion des labels locaux dans les macros, chose que j'avais oubliée (\\@label). Mais j'ai probablement une erreur dedans, qui me complique l'existence de surcroit.

J'ai corrigé une instruction non reconnue (un bset.l je crois), une oubliée (bra), et quelques trucs de ce genre. Je vais tester en codant (j'ai mon assembleur arlésien qui m'est passé sous les yeux, ça m'a redonné des idées), puis on renverra le tout à Scintilla.

Voilà, c'était pour poster une news depuis... 8 mois :D
Posté à
23:54
 par Folco -
Mercredi 24 Novembre 2010
Nouvel édito
Voilà, pour faire plaisir à vince, j'annonce officiellement que j'ai changé le texte et l'image de mon édito. %)
Posté à
16:37
 par Folco -
Mardi 16 Novembre 2010
C marrant
Je suis en train de coder Par depuis un bon moment, en assembleur.
Pour ceux qui ont parcouru la partie TI de yN, ils savent déjà que c'est un archiveur dédié à PedroM.

Ce programme se décompose en trois partie :
- le core, qui opère toutes les fonctions spécifiées sur les archives
- le wrapper dll, qui permet à un programme de très simplement appeler Par
- la partie application, qui permet d'utiliser Par en ligne de commande avec pas mal d'options comme j'aime bien le faire.

Au passage, merci à tous mes aides sur yN.

Je l'ai donc écrit en assembleur. La partie dll et le core sont intégralement codés, la partie application est à moitié réalisée.

Puis j'en ai eu marre de l'assembleur.
Bien qu'en embarqué, je considère qu'un beau programme en assembleur représente le state of the art de la réalisation d'un programme, c'est quand même long et fastidieux, surtout dans la mesure où je m'embête à écrire de manière entièrement optimisée pour PedroM. Ce n'est pas la faute de PedroM qui est génial à ce niveau, bien au contraire, mais des chemins détournés qu'il faut emprunter pour écrire un programme optimal au niveau mémoire : exécution en flash, code entièrement PIC (romcalls, ramcalls et libcalls compris), pas de variables en section de code etc...

J'ai donc repris de 0, en C. Incroyable, au bout de deux jours, je suis à 90% du travail fait en deux semaines en assembleur, et à 80% du programme total. Et pourtant, je considère avoir un niveau correct en assembleur et être un débutant moyen en C (le forum le prouve assez ^^).

Le programme sortira donc écrit en C.

---

D'ailleurs, ça m'a donné une idée de programme pas encore réalisée sur TI. La communauté vivotante au niveau de yN n'étant plus capable des belles réalisations du temps passé, pourquoi ne pas partir sur un jeu communautaire ? Et tant qu'à faire, sur un jeu tournant autour de yN, au niveau des <communauty jokes> voire d'easter eggs marrants ?

Le principe est simple : je code les idées des autres.
Certains sont bons voire très bons en graphismes, je suis nul, ce sera leur contribution.
D'autres sont des scénaristes nés, ou n'ont jamais implémenté leur superbe idée faute de temps/motivation, je la coderai.

Je ne sais pas si cette idée a déjà été réalisée sur yN. En tout cas, demain, je la lance. Pour ma part, je rêve depuis toujours de réaliser un jeu de plateforme sympa, parce que je raffole d'y déplacer des personnages maniables, sautillants, alertes et agiles. Ca fait une base de départ. Après, je compte sur la communauté pour étoffer tout ça.

Comme le dit un très très vieux proverbe chinois qui remonte à la nuit des temps jadis , "Si ça passe, c'est beau" (*)

(*) Joe Bar Team
Posté à
00:00
 par Folco -
Mardi 31 Aout 2010
(Re^n)-belote
Bon, comme d'habitude, je remets mon ouvrage sur le métier, mais cette fois en C, pour l'assembleur et la lib générale qui sert aux softs en ligne de commande sous PedroM.

Comme d'habitude aussi, je repars aussi par la gestion de la ligne de commande. Comme d'habitude, j'essaye de faire en sorte de me simplifier la vie au maximum.
En assembleur, j'utilisais une lib pour aider à sa gestion, mais ce n'était pas optimal : chaque programme l'utilisant aurait eu à réimplémenter un code de gestion similaire pour utiliser la lib. Cette fois, le but est d'éviter ça.

Le principe : le programme appelle une fonction de la lib, ManageArgs, avec quelques arguments, dont deux très important :
- la liste des switches valides en tant qu'options
- la liste des fonctions à appeler si l'on lit un de ces switches

Il y a aussi une fonction de callback appelée après différents évènements (succès du parsing d'un switch, switch non trouvé, argument non switch). La fonction de callback renvoie une valeur qui permet au programme de dire à la lib de continuer à tourner ou d'interrompre le traitement. Le programme devient en quelque sorte une librairie appelée par la véritable librairie. Fabuleux \o/


Voici le code. Pas de commentaires, mais un descriptif :
[box=ManageArgs]
/**************************************************** 
*		ManageArgs 
* 
*       Handle switches of the command line 
* 
*       Args 
*		CmdLine		Ptr to a CMDLINE structure 
*		OptList		List of valid options, without their sign ("switch1\0switch2\0...\switchN\0") 
*		FuncList	List of functions to execute for each option 
*       Callback    Function called after each switch parsed 
* 
*       Returns     PDT_NO_MORE_ARGS    The command line is fully parsed 
*                   PDT_SWITCH_OK       Switch is valid, its routine has been executed 
*                   PDT_INVALID_SWITCH  Switch doesn't exist in OptList 
*                   PDT_NOT_A_SWITCH    Current arg doesn't begin with + or - 
* 
*       Callback    Provides    PDT_SWITCH_OK           Switch is valid, its routine has been executed 
*                               PDT_INVALID_SWITCH      Switch doesn't exist in OptList 
*                               PDT_NOT_A_SWITCH        Current arg doesn't begin with + or - 
*                   Returns     PDT_CONTINUE_MANAGEMENT ManageArgs continue to parse command line 
*                               PDT_EXIT_MANAGEMENT     ManageArgs stop to parse command line and returns 
*                                                       last provided arg 
* 
*       SwitchFunc  Provides the sign of the current switch (+ or -) 
* 
****************************************************/ 
 
int pdtlib__ManageArgs (CMDLINE* CmdLine, const char* OptList, int (*Callback)(int Status), void (*SwitchFunc)(char Sign), ...) 
{ 
    typedef void (*funcptr)(char); 
	va_list FuncList; 
	const char* ArgPtr; 
	char Sign; 
 
    while (CmdLine->argc > CmdLine->argit + 1) 
    { 
        va_start (FuncList, SwitchFunc); 
        CmdLine->argit ++; 
        Sign = *CmdLine->argv[CmdLine->argit]; 
        if (Sign == '-' || Sign == '+') 
        { 
            ArgPtr = OptList; 
            while (*ArgPtr != 0) 
            { 
                if (strcmp (CmdLine->argv[CmdLine->argit] + 1, ArgPtr) == 0) 
                { 
                    va_arg(FuncList, funcptr)(Sign); 
                    va_end(FuncList); 
                    if (Callback(PDT_SWITCH_OK) == PDT_STOP_MANAGEMENT) 
                        return PDT_SWITCH_OK; 
                    break; 
                } 
                else 
                    ArgPtr += strlen(ArgPtr) + 1; 
            } 
            if (*ArgPtr == 0 && Callback(PDT_INVALID_SWITCH) == PDT_STOP_MANAGEMENT) 
            { 
                va_end(FuncList); 
                return PDT_INVALID_SWITCH; 
            } 
        } 
        else 
        { 
            if (Callback(PDT_NOT_A_SWITCH) == PDT_STOP_MANAGEMENT) 
            { 
                va_end(FuncList); 
                return PDT_NOT_A_SWITCH; 
            } 
        } 
    } 
    va_end(FuncList); 
    return PDT_NO_MORE_ARG; 
}
[/box]

Au final, un programme peut se faire de la manière suivante :
- une fonction main() qui appelle le gestionnaire
- pleins de fonctions pour gérer chaque switch
- une fonction pour traiter un argument qui n'est pas un switch (dans mon cas, l'assemblage d'un source).
Ca change donc complètement ma vision impérative habituelle, pour faire du programme quelque chose de très fonctionnel. C'est rigolo, j'espère juste que j'arriverai à coder dans cette optique.

Il y a aussi deux fonctions, SkipArgs et GetCurrentArgPtr, pour récupérer le pointeur vers l'argument couramment traité (pour afficher un switch invalide par exemple), ou pour indiquer au gestionnaire qu'on a géré un argument à la mano et qu'il doit l'ignorer.


Finalement, je me demande une chose : dois-je chercher à faire un assembleur, ou juste une lib de gestion de ligne de commande ? Je me demande vraiment...


edit -> je n'arrivais pas à coder l'appel d'une fonction dont le pointeur était dans la pile, en utilisant une va_list. Godzil a trouvé la solution ! Merci à lui \o/
Posté à
13:05
 par Folco -
Dimanche 07 Mars 2010
Pffffffffffft (<- jet de vapeur)
Bon, puisque je suis toujours sur mon assembleur et que je ne release jamais, autant voir les choses en grand. Et comme c'est la 7ème ou 8ème fois que je recommence, j'en ai marre de faire certaines choses. Donc n'hésitons pas à être ambitieux, voire prétentieux ou même carrément arrogant.

Déjà, les nouveautés par rapport à la "version" précédente :
- assembleur et linker stand-alone, vraiment séparés. L'assembleur fait des objets, le linker des exécutables. A l'ouest, rien de nouveau.
- ca permet d'assembler les fichiers au fur et à mesure qu'ils sont rencontrés dans la ligne de commande. Gros progrès, ça évite de gérer un gros bordel de tables, et ça permet également de gagner en RAM ! Seul truc, ça nécessite des stack frames récursifs à cause des 'include'. C'est donc jouissif.

Donc pour m'aider dans tout ça, j'ai écrit une lib : butillib, pour binutils lib. Oui, j'avais prévenu que l'humilité ne m'étouffait pas.
Le but est de faciliter un certain nombre de tâches qu'on retrouvera systématiquement dans un soft en ligne de commande sous PedroM. Les fonctions (pour le moment, j'en rajoute au besoin) :

Gestion de la ligne de commande
- InitCmdLine
Utilisé pour initialiser une structure CMDLINE fournie par le programme (8 octets). Un pointeur sur cette structure est fourni à la lib pour les autres fonctions
- IsNextArg
Retourne vrai s'il reste des arguments à parser
- GetCurrentArgPtr
Renvoie un pointeur sur l'argument courant
- GetNextArgPtr
Skippe l'argument courant et renvoie un pointeur sur le suivant
- IsArgSwitch
Renvoie vrai si un argument est un switch (ie s'il commence par '+' ou '-')
- ExecSwitchRoutine
Là, c'est puissant. Ca exécute la routine prévue par le programme pour le switch courant. Le programme fournit deux tables de saut pc-relatives sur un word contenant les offsets des routines, et la routine qui va bien est exécutée, en tenant compte du fait que le switch puisse commencer par '+' ou '-'. Plus rien à faire dans le programme, ça c'est le pied.

Gestion des fichiers
- GetFileHandle
Renvoie tout simplement le handle d'un fichier. Plus besoin de créer la SYM_STR en fonction du fichier, l'existence du fichier et la validité du nom sont vérifiées. C'est bien commode.
- GetFilePtr
Ce n'est jamais que HeapDeref(GetFileHandle(const char *Filename)), mais c'est bien commode aussi. Je l'ai exportée parce que CheckFileType l'utilise.

- CheckFileType
Pareil, super commode, vérifie l'existence, la validité et le type d'un fichier. Le type peut-être OTH_TAG, auquel cas une signature custom peut-être vérifiée aussi si elle est fournie. Ca simplifie la vie.

Gestion des chaines
- CompareStrings
Routine postée précédemment. Super commode, plus de recherche à faire à la main. Sert à parser une ligne de commande, une fichier source, un fichier de conf, bref, n'importe quoi qui contient des strings C. Encore une grosse simplification.

Le design de la lib fait qu'elle est appelable en C. Le tout sans smc ni relogements comme d'habitude, ce qui permet d'avoir une lib read-only et ré-entrante, supportant donc un multi-tâche même préemptif sur le même binaire.


Autre fonction prévue, étant donné que tout ça ne tournerait que sous PedroM : SetPedroMAPI. La fonction peut-être appelable par un programme qui ne détecte pas PedroM au boot : plutôt que de terminer avec une erreur, il met en place une API similaire à celle de PedroM pour certaines fonctions, et le programme tourne comme si de rien n'était. Ca comporte :
- implémentation d'un trap #3 pour le déréférencement
- implémentation d'un handler de RAM_THROW sur le handler de f-line
- simulation d'une ligne de commande permettant l'exécution d'un programme de type void main(int argc, const char **argv).

Rappelons que PedroM dispose d'une lib système au format standard des dll (kernel v4), ce qui suppose alors l'implémentation d'une lib nommée "pedrom" contenant les différentes fonctions de PedroM utilisées par la toolchain pour utiliser un programme "PedroM-only" sous AMS. Certaines sont implémentées dans TIGCCLIB, ce qui facilite grandement l'implémentation d'une telle dll.

Voilà voilà, je vaporise à fond là-dedans en ce moment. :)
Posté à
12:14
 par Folco -
Jeudi 04 Mars 2010
Smack
Je trouve ça beau. Point barre, ça ne se discute pas.
;========================================================== 
;	CompareStrings 
; 
;	Check if the string RefString terminated by a char of SeparatorTable is contained 
;	in the string table StringTable 
;	Return #item in the table, or -1 if the string isn't in the table. First item is #0 
;	Put in NextChar a ptr to the next char after RefString if NextChar != NULL 
; 
;	input	a0	StringTable 
;		a1	SeparatorTable 
;		a2	RefString 
;		a3	**char (NextChar) 
;	output	d0.w	#item, else <0 
;		(a3)	updated if a3 != 0 
;	destroy	d0-d2/a0 
;========================================================== 
butillib##antispam##0006: 
	movem.l	a1/a4,-(sp)		; Save RefString 
	moveq.l	#-1,d0			; #item 
\ItemLoop: 
	addq.w	#1,d0			; Update #item 
	lea	(a2),a1			; RefString 
\Loop:	move.b	(a0)+,d1		; End of string in StringTable ? 
	beq.s	\CheckSeparator		; Yes, so check the separator at -1(a0) 
	cmp.b	(a1)+,d1		; Compare strings 
	beq.s	\Loop			; Until mismatch 
\NextString: 
	tst.b	(a0)+			; Else skip current string 
	bne.s	\NextString 
\CheckEOT: 
	tst.b	(a0)			; End of StringTable ? 
	bne.s	\ItemLoop		; No, continue 
		moveq.l	#-1,d0		; Else return error code 
		bra.s	\NoSaveNextChar 
 
 
\CheckSeparator: 
	move.b	(a1),d1			; Current char of RefString 
	beq.s	\Quit			; #0 ? So quit, EOF is always a valid separator 
	movea.l	(sp),a4			; SeparatorTable 
\LoopSeparator: 
	move.b	(a4)+,d2		; End of SeparatorTable ? 
	beq.s	\CheckEOT		; Yes, try with the next string 
	cmp.b	d1,d2			; Equal ? 
	bne.s	\LoopSeparator		; No, test with next char 
\Quit: 
	move.l	a3,d1			; Must save *NextChar ? 
	beq.s	\NoSaveNextChar		; No 
		move.l	a1,(a3)		; Else save it 
\NoSaveNextChar: 
	movem.l	(sp)+,a1/a4 
	rts
Posté à
15:31
 par Folco -

 RSS  - ©yNBlogs 2004 - 51ms

Login : - Mot de passe :