1

yop,


J'ai besoin de pouvoir évaluer des expressions mathématiques pour en ressortir une valeur entière sur 32 bits. Je vais pouvoir rencontrer ça :
- notations décimales, binaires, octales ou hexa mélangées, seul le décimal n'étant pas préfixé. Les nombres peuvent être signés
- les 4 opérations de base
- les parenthèses, imbriquées ou non
- les symboles utilisés dans une expression (genre 5+TRUC)
- le signe de la négation logique : ~
- le signe négatif
En clair, je dois pouvoir évaluer ça : 32 + 5 x (TRUC / 2 x ( -$45 ) )

Donc en clair, j'arrive devant mon expression, j'ai l'adresse de son premier caractère, et là... ben c'est la merde grin Ou plutôt, je sens que ça devrait être bien marrant à coder, mais je ne sais pas trop par où attaquer les choses.

J'ai pensé à plusieurs choses : analyse récursive : l'expression du haut peut être évaluée comme étant eval(32) + eval(5 x (TRUC / 2 x (-$45) ) )
Problème, si "j'avance" bêtement comme ça dans mon expression, je vais à coup sûr rater la priorité des opérateurs.
Le stockage des valeurs intermédiaires se ferait alors sur la pile, la fonction s'appellerait elle-même jusqu'au dépilage final qui renverrait une valeur.

J'ai pensé aussi à engranger les éléments comme dans une pile RPN. Mais je me demande si ça me sera utile, avec toujours le problème des priorités non résolu.

Comment m'y prendre ? Je parle évidemment de l'algo de calcul, pas de lecture du flux à proprement parler, reconnaitre un signe ou un chiffre quelle que soit la base ou ne signe n'est pas un problème.

Voilà, et pour finir, j'implémenterai ça en assembleur.

Merci. smile

2


Pour les mauvaises langues qui croient que je-demande-du-code-et-ksa-saute, j'ai déjà écrit ça :
;==================================================================
;	EvalExpression
;------------------------------------------------------------------
; 	Evaluate an expression
;
;	input	a0	*expression
;	output	d0.l	value of the expression
;	destroy	std
;==================================================================
EvalExpression:
	rts

Hein ZeroSquare ? tripo

3

avatar
Webmaster du site Ti-FRv3 (et aussi de DevLynx)
Si moins de monde enculait le système, alors celui ci aurait plus de mal à nous sortir de si grosses merdes !
"L'erreur humaine est humaine"©Nil (2006) // topics/6238-moved-jamais-jaurais-pense-faire-ca

4

merci vince ! bisoo

5

Tu veux coder un mini analyseur en assembleur ? Ça risque d'être chaud. Toujours pas pote avec le C ? grin
avatar
Un site complet sur lequel vous trouverez des programmes et des jeux pour votre calculatrice TI 89 / Titanium / 92+ / Voyage 200 : www.ti-fr.com.
Quelques idées personnelles ici.

6

non, si on se limite aux expression ca reste faisable. On peut faire un truc à base de pile.

7

Bonjour,

Si j'ai bien compris ce que tu veux faire...

Pourquoi ne pas faire ça en plusieurs passes comme le fait l'assembleur quand il compile ton code ?


tromb Fichier joint : FxWW (2passesb.png)

Donc séparer les étiquettes des opérandes, éventuellement les opérandes en 2 groupes (prioritaires, non prioritaires),
Et les parenthèses seraient traitées ("linkées" mais le terme n'est peut-être pas approprié) en deuxième passe...


Enfin je ne sais pas trop si c'est possible/facile mais si ça peut t'aider...

8

Je pense, dans un premier temps :
- résoudre tout ce qui est élément simples : négations, négations logiques, bases etc, tout en poussant le tout dans une pile d'expression
- résoudre dans les parenthèses, jusqu'à ce qu'elles soient réduites en éléments simples (récursivement donc)
- résoudre enfin le produit de tout ça, en commençant par les opérateurs prioritaires ( x / ) jusqu'à ce qu'il n'y en ait plus

9

faut commencer par parser. transformer ton string en qc de manipulable plus facilement.

puis je verrais une stack de parenthèses, puis ensuite calculer les parenthèses les plus imbriquées.

l'estack des ti est pas mal foutue.

10

C'est bien ce que je compte faire. Je réfléchis au format de mon estack perso smile

11

Pour ce genre de problème, une solution éprouvée est d’écrire une grammaire correspondant au langage que tu veux gérer, puis de construire (presque mécaniquement) un analyseur à partir de ça.
Je pense que ça t’évitera d’avoir des fonctions compliquées qui se partagent des responsabilités croisées et qui sont impossibles à maintenir.

Le type de grammaire qui est approprié pour décrire ton langage est une grammaire algébrique, que l’on peut décrire par des règles de la forme « expr -> nombre opérateur nombre ». En partie droite des règles, tu dois finir par tomber sur des unités lexicales (un nombre, un label, un opérateur, etc.).

Donc il faut commencer par identifier quels sont les unités lexicales (les types de mots) que tu rencontreras dans ton analyse. Puis voir comment ils sont agencés entre eux (syntaxiquement) pour former des fichiers sources valides (puisque c’est ça que tu veux pouvoir analyser).

./7 C’est quoi la suite ? (j’aimerais bien savoir ce qu’ils proposent comme technique en une passe)
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

12

Suite : dans ton cas, je dirais que les unités lexicales sont les suivantes (description par des expressions régulières) :
NOMBRE_DEC = [0-9]+ /* Une suite de chiffres */
NOMBRE_BIN = %[0-1]+ /* Une suite de 0 ou 1 précédée d’un « % » */
NOMBRE_HEX = $[0-9a-fA-F]+
IDENTIFICATEUR = [a-zA-Z][a-zA-Z_0-9]* /* Une lettre suivie de n’importe quel caractère alphanumérique ASCII ou « _ » */
MULT = '*'
DIV = '/'
PLUS = '+'
MINUS = '-'
LSL = '<<'
LSR = '>>'
COMMA = ','
DOT = '.'
OPEN_B = '('
CLOSE_B = ')'
SHARP = '#'


J’oublie peut-être des trucs, mais ça se rajoute facilement. Après il y a des trucs qui peuvent se discuter : est-ce qu’on identifie les indicateurs de taille (b, w et l) dès l’analyse lexicale ou pas ? Je pense qu’on peut.

Ta première tâche sera de lire le texte caractère par caractère et d’identifier les unités lexicales les unes après les autres. Par exemple le programme suivant sera lu comme ça :
  move.w #1234, d0
  rts

IDENTIFICATEUR "move"
DOT
SIZE "w"
SHARP
NUMBER "1234"
COMMA
IDENTIFICATEUR "d0"
NEWLINE
IDENTIFICATEUR "rts"


À partir de là ce sera plus pratique pour bosser. En quelque sorte, tu as identifié les différents mots possibles du langage, et maintenant tu vas regarder quelles sont les phrases que tu peux former.
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

13

Ok super ! Mais au niveau de l'implémentation, comment est-ce que je retiens ça ?
Je pensis passer directement du source au fichier objet (composé de plusieurs handles pour les symboles, les adressages, le binaire etc...).
Je pense que je devrais donc appliquer ta méthode ligne après ligne dans mon source, non ? Et remplir mes handles au fur et à mesure.

Mais comment implémenter ça, le représenter en mémoire ? J'avais idée d'écrire les instructions dans un buffer de 10 octets (ou directement dans le handle du binaire), au fur et à mesure de ce que je lis :
"move" > j'écris le masque
".w" > j'écris sa taille
"#" > je véfirie que mon move supporte un entier immédiat, si oui, je parse le
"1234" > que j'écris à sa place après le masque de l'instruction
"," > ça, il fallait nécessairement que je le trouve après mon entier, sinon erreur
"d0" > c'est un registre, je lis le masque associé dans la table des instructions, puis je rajoute le résultat dans le masque de l'instruction
"EOL" > ok, on valide la ligne en l'écrivant dans le binaire, et on recommence.

C'est pas bon de s'y prendre comme ça ? Ya des pièges que je vois pas ?

14

Bon, en fait là il y a des petites subtilités. Pour pouvoir écrire facilement l’analyseur syntaxique à la main, le plus simple est d’écrire une grammaire « LL(1) » (ça veut juste dire que tu dois respecter certaines règles dans son écriture). Mais avec un langage non structuré et aussi simple que l’asm, ça ne devrait pas être compliqué…

L’idée est de partir d’un programme (le fichier source), et de le décrire en fonction de ses constituants (des déclarations de labels, des instructions, des directives, etc.), et de préciser petit à petit à quoi correspond chaque constituant. On utilise pour cela des règles, par exemple ceci :
prog -> operations  /* un programme est un ensemble d’opérations */
operations -> operation (NEWLINE+ operations)?  /* le « ? » indique une partie optionnelle */
operation -> label_declaration | instruction /* le « | » indique l’alternative */
label_declaration -> IDENTIFICATEUR COLON
instruction -> TAB IDENTIFICATEUR (DOT SIZE)? operandes?
operandes -> operande (COMMA operande)
operande -> data_reg | addr_reg | imm_value | abs_addr
imm_value -> SHARP expr
expr -> prod ((PLUS | MINUS) prod)*
prod -> fact ((MULT | DIV) fact)*
fact -> NUMBER | NUMBER_BIN | NUMBER_HEX
Là encore, je ne mets pas tout, juste pour que tu comprennes le principe.

L’idée est ensuite de parvenir à décrire le fichier source que tu lis en fonction de ces règles, par exemple le programme de toute à l’heure produirait la dérivation suivante :
prog
  operations
    operation
      instruction
        TAB
        IDENTIFICATEUR "move"
        DOT
        SIZE "w"
        operandes
          imm_value
            SHARP
            NUMBER "1234"
          COMMA
          data_reg
            IDENTIFICATEUR "d0"
    NEWLINE
    operation
      instruction
        IDENTIFICATEUR "rts"
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

15

y'a pas besoin de tout ce bordel pour évaluer une expression.

16

Bon ce n’est pas forcément super visible sur mon post précédent, mais en gros tu peux facilement construire une structure d’arbre pour naviguer entre les phrases du code source.

Chaque nœud de l’arbre correspond à une règle de grammaire. On écrit une fonction correspondant à chaque règle, qui construit le sous-arbre dont elle est responsable, en faisant appel aux autres fonctions construisant leurs sous-arbres respectifs. Cela permet une grande factorisation du code (l’inverse de la duplication de code !) et donc limite les risques de problèmes.

Je parle d’arbre, mais dans le cas de l’ASM tu n’as pas besoin de beaucoup d’informations, tu peux assembler le fichier ligne par ligne. Donc pour chaque nouvelle ligne du fichier source tu peux simplement réinitialiser une structure de données dans laquelle tu vas ensuite stocker les informations dont tu auras besoin pour générer du code, par exemple quelle instruction, quelles sont les opérandes, leur type, etc.

Et pour ce qui est de l’évaluation des expressions arithmétique, le code suit les règles de la grammaire :
int expr(void) {
  int p = prod();
  lex = lexemeCourant();
  while(lex == PLUS || lex == MINUS) {
    if(lex == PLUS) {
      p += prod();
    }
    if(lex == MINUS) {
      p -= prod();
    }
    lex = lexemeCourant();
  }
  return p;
}


Ce qu’il faut retenir, c’est que tes fonctions qui gèrent l’analyse syntaxique doivent suivre les règles que tu auras écrites sur papier. Après, à toi de voir ce qu’elles renvoient comme information pour pouvoir assembler correctement le code. Je n’ai pas en tête toutes tes spécifications (est-ce que tu veux pouvoir gérer les macros, le fait de pouvoir inclure des labels dans les expressions (par exemple pour exprimer une distance entre différents points du code), etc. Tout ça va modifier la nature des informations dont tu auras besoin pour générer le code correspondant).
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

17

C'est surpuissant cette approche Sasume, ça permet de parser n'importe quoi avec le même code, pour peu que tu redéfinisse une nouvelle grammaire, je me trompe ?

Et puis, même question que squalyl, en plus "polissé", ai-je besoin de passer par cette étape nécessairement ? Ne puis-je pas faire quelque chose de plus intuitif ?
L'assembleur est simplissime en soi, sur une ligne, on a :

LABEL | DIRECTIVE | COMMENT

ou alors

LABEL | INSTRUCTION | COMMENT

Avec à chaque fois, chaqun des composants qui est optionnel. Je me suis même amusé à mettre les directives dans la table des instructions : ça accélère la recherche, et comme j'avais un bit de libre dans chaque entée de la table, je l'utilise pour savoir si j'ai affaire à une instruction ou à une directive. Les instructions passent toutes à la même moulinette, mais les directives, c'est du cas par cas.

(cross)

18

Sasume (./16) :
(est-ce que tu veux pouvoir gérer les macros

Oui, l'assembleur s'appelle récursivement en cas de macro (les données en cours sont foutues sur la pile), on incrémente un compteur de macro, on doit tomber sur endm avant EOF, et le compteur ne doit pas être nul quand on trouve endm. C'est relativement simple (seul truc àlakhon, les labels locaux aux macros)
Sasume (./16) :
le fait de pouvoir inclure des labels dans les expressions (par exemple pour exprimer une distance entre différents points du code)

Ouep, ça va dans une table à cet effet, seul un linker peut calculer ça

19

Folco (./13) :
Mais comment implémenter ça, le représenter en mémoire ? J'avais idée d'écrire les instructions dans un buffer de 10 octets (ou directement dans le handle du binaire), au fur et à mesure de ce que je lis
Ça me semble bien dans l’idée, je pense que tu peux même écrire directement dans le fichier de sortie (qui ne sera peut-être pas l’exécutable si tu fais une édition de liens ensuite, ou si tu veux gérer le format kernel).

Pour ce qui est des labels, tu dois simplement construire une table de symbles au fur et à mesure de l’assemblage, dans laquelle tu notes à quoi correspond chaque déclaration de label que tu croises (où il est physiquement et où il est utilisé).
Quand tu dois calculer un saut, tu regardes si le label auquel le saut fait référence est déjà dans ta table, si oui tu calcules la distance du saut, sinon tu notes le fait qu’il est utilisé à cet endroit comme ça quand tu trouveras sa définition plus bas tu rempliras la valeur du déplacement. Ça te permet d’assembler en une seule passe.
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

20

Sasume (./19) :
Ça me semble bien dans l’idée, je pense que tu peux même écrire directement dans le fichier de sortie (qui ne sera peut-être pas l’exécutable si tu fais une édition de liens ensuite, ou si tu veux gérer le format kernel).

Exact, j'ai un handle pour le binaire, je devrais pouvoir écrire dedans directement, au moins le masque de l'instruction. Parce que j'ai pas encore regardé, mais si les adressages en <truc.size> x(an),y(am) s'écrivent "à l'envers" dans le binaire, il faut attendre d'avoir parsé la seconde opérande pour savoir où écrire les offsets. Mais c'est pas un problème, j'ai un stack frame global au programme, suffit de les mettre dedans.
Sasume (./19) :
Pour ce qui est des labels, tu dois simplement construire une table de symbles au fur et à mesure de l’assemblage, dans laquelle tu notes à quoi correspond chaque déclaration de label que tu croises (où il est physiquement et où il est utilisé).

Je note son emplacement, un checksum (pour rechercher plus vite). A voir si j'y rajoute un tag d'exportation au début du linking, pour éviter de rechercher à chaque fois dans la table des xdef.
Sasume (./19) :
Quand tu dois calculer un saut, tu regardes si le label auquel le saut fait référence est déjà dans ta table, si oui tu calcules la distance du saut, sinon tu notes le fait qu’il est utilisé à cet endroit comme ça quand tu trouveras sa définition plus bas tu rempliras la valeur du déplacement. Ça te permet d’assembler en une seule passe.

C'est dur de calculer même si le label est avant, on peut très bien avoir des références non résolues entre les deux (sauts relatifs longs ou courts. Je ne parle pas encore d'adressage de BSS sur 2 ou 4 octets trinon.

Quitte à devoir faire un linker, autant tout faire à ce moment, à mon avis.

21

Folco (./17) :
C'est surpuissant cette approche Sasume, ça permet de parser n'importe quoi avec le même code, pour peu que tu redéfinisse une nouvelle grammaire, je me trompe ?
Et puis, même question que squalyl, en plus "polissé", ai-je besoin de passer par cette étape nécessairement ? Ne puis-je pas faire quelque chose de plus intuitif ?
Oui, squalyl n’a pas tort, je t’ai présenté une méthode utilisée pour gérer l’analyse syntaxique des langages un peu plus complexes, comme le C. Pour ton assembleur tu peux te démerder en simplifiant un peu. Il n’empêche que ça offre un certain confort. Séparer l’analyse du texte, caractère par caractère, de l’analyse de la syntaxe, unité lexicale par unité lexicale est vraiment très pratique. Mais tu peux effectivement l’appliquer ligne par ligne plutôt qu’au fichier entier.
Avec à chaque fois, chaqun des composants qui est optionnel. Je me suis même amusé à mettre les directives dans la table des instructions : ça accélère la recherche, et comme j'avais un bit de libre dans chaque entée de la table, je l'utilise pour savoir si j'ai affaire à une instruction ou à une directive. Les instructions passent toutes à la même moulinette, mais les directives, c'est du cas par cas.
Excellent top
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

22

Folco (./20) :
Exact, j'ai un handle pour le binaire, je devrais pouvoir écrire dedans directement, au moins le masque de l'instruction. Parce que j'ai pas encore regardé, mais si les adressages en <truc.size> x(an),y(am) s'écrivent "à l'envers" dans le binaire, il faut attendre d'avoir parsé la seconde opérande pour savoir où écrire les offsets. Mais c'est pas un problème, j'ai un stack frame global au programme, suffit de les mettre dedans.
C’est bon, les opérandes sont toujours après l’instruction (ou au même endroit quand le tout tient sur 16 bits).
Quitte à devoir faire un linker, autant tout faire à ce moment, à mon avis.
Bonne idée, à mon avis smile (sauf si ça génère des tables énormes de sauts locaux)
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

23

Je repense à un détail, si tu veux permettre des expressions du genre (label2 - label1) / 4, tu pourrais stocker l’expression sous une forme abstraite pour pouvoir l’évaluer au moment où tu sauras où sont label2 et label1. Donc l’idée d’arbre n’est pas forcément surdimensionnée !
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

24

J'ai pensé aux différences de label, sans savoir exactement encore comme résoudre ça j'avoue...

25

(label2 - label1) / 4 est une erreur même dans TIGCC, c'est assez déraisonnable de vouloir permettre ça.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

26

Pourquoi ne pas permettre n'importe quel division de différence de label ? Suffit de retenir où enregistrer cette expression, et à quel range on a droit, non ?
Si je veux écrire un truc débile genre "trap #(label1-label4)/17", qu'est-ce qui en empêcherait en théorie ?

27

Parce qu'une différence d'adresses est un relogement dans TIGCC parce que seul le linker connaît l'offset final après optimisation, et il n'y a pas de 17èmes de relogements.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

28

Marrant que ce soit un relogement. Pourquoi ne pas en faire un entier à réévaluer après le linking ? Une fois que l'on connait la position de chaque label ?

29

Il faudrait évaluer ce genre de calculs dans l'éditeur des liens. C'est faisable, mais dédouble le parseur et complique ton format objet.
avatar
Un site complet sur lequel vous trouverez des programmes et des jeux pour votre calculatrice TI 89 / Titanium / 92+ / Voyage 200 : www.ti-fr.com.
Quelques idées personnelles ici.

30

Thibaut (./29) :
Il faudrait évaluer ce genre de calculs dans l'éditeur des liens.
O
Ouep
Thibaut (./29) :
mais dédouble le parseur

Pas vraiment, faut juste pouvoir appeler la fonction qui va bien, ie faut y penser avant
Thibaut (./29) :
complique ton format objet

nop, c'est juste une entrée avec le flag kivabien dans la table d'adressages à résoudre