B2C : Convertisseur Basic en C
Posté le 02/07/2016 11:24
Mon nouveau projet est donc de faire un convertisseur basic -> C
Ce serait possible parce que toutes les fonctions du basic sont transposables en C (même les gotos), par exemple Locate(x, y, str) est remplacé par locate(x,y);Print(str), F-line remplacé par ML_line(), etc.
Par contre là où ça diffère de AldeBasicLib c'est que ce sera un convertisseur total. Par exemple si le programme voit "Mat M" il génèrera une matrice M adéquate. Le but étant de lancer le programme, de sélectionner un .g1r, et d'avoir un fichier .c prêt à compiler.
Cela pourrait être utile pour les gros jeux qui prennent toutes la mémoire principale et qui seraient mieux en C (je pense à CloneLab, Arkenstone, peut être Calcraft...)
Utilisation
Nécessite : Java, le SDK casio (ou un truc permettant de compiler des addins)
Clonez le repo
http://git.planet-casio.com/Zezombye/B2C/ et exécutez B2C.java en remplaçant les arguments si besoin.
À noter que B2C n'étant pas fini, il est très probable que le programme que vous essayez de convertir vous fasse plein d'erreurs, que ce soit de B2C ou du SDK.
Programmes basic déjà convertis
NOTE: Ces programmes sont compatibles SH3. Si vous voulez les tester, convertissez les en SH4 si besoin !
Je n'ai absolument rien changé dans ces programmes (à part l'icône et le nom) donc le code est celui qu'on obtient avec B2C.
Démineur de Cakeisalie5 :
http://www.mediafire.com/file/z6t5jmfh72wfnag/DEMNR.G1A (
original)
Puissance 4 de Zezombye :
http://www.mediafire.com/file/i1ucweo66ibjy67/PUISS4.G1A (
original)
Fonctions actuellement implémentées :
- Commentaires
- Gotos&Lbls (heureusement que ça marche exactement de la même façon en C qu'en basic)
- If, Then, Else, IfEnd, ⇒
- Do, LpWhile
- While, WhileEnd
- For, To, Step, Next
- Prog, Return, Break, Stop
- Locate
- GetKey
- -> (assignement de variable/matrice/liste/dim)
- Dim, Mat, List
- L'opérateur '~'
- Variables A-Z, r, theta, Ans
- Opérateurs de calcul : + (unaire et binaire), - (unaire et binaire), *, /, ^, sqrt, racine n-ième
- Multiplication implicite (normalement)
- Opérateurs de comparaison : <, >, ≤, ≥, =, ≠
- Opérateurs logiques : And, Not, Or, Xor
- Ran# (fonction de la libc), RanInt# (par l'interpréteur casio actuellement, à changer)
- Int
- Fill(), ClrMat, ClrText
- Str, StrCmp, StrInv, StrJoin, StrLeft, StrRight, StrLen, StrLwr, StrUpr, StrMid, StrRotate, StrSrc, StrShift
Fonctions à implémenter :
- Nombres complexes
- Fonctions graphiques
- Gestion des pictures et captures
- Sauvegarde des variables
Note : les 3 fonctions suivantes ne seront pas implémentées fidèlement, il n'y aura qu'une implémentation rudimentaire.
- Les strings sans assignement (écrire "TEST" puis une nouvelle ligne)
- La fonction disp (◢)
- L'opérateur ?-> pour l'input de l'utilisateur
Fonctions qui ne seront peut être pas implémentées :
- L'écriture d'un nombre via la console (par exemple écrire "2+3" comme seule instruction affiche "5" sur l'interpréteur basic, mais il est difficile de savoir s'il faut l'afficher ou seulement la mettre dans Ans)
Comment B2C optimise le programme (autre qu'en compilant au lieu d'interpréter) par rapport à Casio
- Les opérations sont implémentées (enfin pour l'instant il n'y a que l'addition et la soustraction pour les nombres non complexes) nativement au lieu de passer par l'interpréteur; ainsi, additionner 10000x des nombres prend 1.5s pour l'implémentation de l'addition, et 4.4s si on passe par l'interpréteur.
- Les listes de Casio séparent les parties imaginaires et réelles. Par exemple, pour la liste {3, 1+2i, -4i, 6}, Casio la stockera en {3, 1, 0, 6, ?, 2, -4, ?} avec '?' 12 octets de padding. B2C ne fait pas cette séparation et stocke la partie imaginaire à côté de la partie réelle, ce qui fait qu'une fonction peut appeler directement "list_1
" au lieu de passer par une fonction intermédiaire qui recolle les 2 parties.
L'inconvénient est que les listes ne possédant pas de complexes sont 2 fois plus grandes. (en fait, si le programme possède des nombres complexes, la taille du BCDvar est de 24 octets, et sinon 12)
- Pour les matrices : même chose qu'avec les listes.
- Les strings de Casio implémentent des caractères, qui peuvent être sur 1 ou 2 octets. Cette différence fait qu'on ne peut pas avoir un accès en O(1) car on ne peut pas savoir directement la position du i-ème caractère du string en calculant. Si on veut accéder à str_1[255], l'interpréteur casio doit itérer sur tout le string.
B2C permet un accès beaucoup plus rapide en stockant chaque caractère, multi-byte ou non, sur 2 octets. Ainsi, les fonctions des strings qui travaillent sur les caractères casio (toutes sauf StrCmp) sont plus rapides. StrCmp est possiblement plus lent que si B2C implémentait les strings comme le fait casio, mais la différence est négligeable.
Pour résumer : B2C bouffe un peu plus de RAM, mais il est beaucoup plus rapide.
Citer : Posté le 01/08/2016 22:50 | #
Le timer n'a pas l'air de marcher :/
installTimer(INTERRUPTION_TIMER, (void*)&exitTimerHandler, INT_MAX);
startTimer(INTERRUPTION_TIMER);
//...
}
void exitTimerHandler() {
if (IsKeyDown(KEY_CTRL_EXIT)) {
uninstallTimer(INTERRUPTION_TIMER);
B2C_exit(NO_ERROR);
}
}
Ca ne fait tout simplement rien quand j'appuie sur exit.
Ajouté le 01/08/2016 à 22:56 :
J'en vois pas l'intérêt pour le coup, un double suffit amplement… Et puis c'est plus facile à manipuler, sûrement beaucoup, beaucoup, beaucoup plus rapide.
En fait non parce que je dois faire des calculs exacts, par exemple "A = 0.1; if (A+0.2 == 0.3)". Ce serait impossible avec des doubles, du coup je fais avec les variables BCD.
Pour l'injection c'est plus ou moins ce que je fais :
installTimer(6, (void*)&timerHandler, 1);
startTimer(6);
GetKey(&key);
uninstallTimer(6);
}
void timerHandler() {
short menuCode = 0x0308;
putMatrixCode(&menuCode);
}
En fait faudrait que je trouve un moyen d'injecter une autre touche, mais je pense que c'est impossible parce que le programme s'arrête dès que j'appuie sur menu.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 01/08/2016 23:18 | #
ou que tu l'injecte en continu
installTimer(6, (void*)&timerHandler, 1);
[b]startTimer(6);[/b]
GetKey(&key);
uninstallTimer(6);
}
void timerHandler() {
short menuCode = 0x0308;
[b]putMatrixCode(&menuCode);[/b]
}
No comment.
Sinon, je comprend pas pourquoi tu passe le callback par un pointeur sur la fonction. Ceci suffit largement :
Et pour finir, ça m'étonne énormément que le code que je t'ai pondu ne marche pas chez toi… Je regarderai si t'as pas résolu le problème d'ici que je sois rentré chez moi
Citer : Posté le 02/08/2016 07:41 | #
Au niveau du retour au menu ton code fonctionne... ce qui est bizarre étant donné que SimLo précise bien que pour une injection il faut utiliser un timer o_o
Et au niveau du timer pour AC/on ça fonctionne aussi, d'ailleurs après quelques tests ça fonctionne que parce que tu remets le timer à 0 si on appuie pas sur AC/on. Si j'initialise le timer avec INT_MAX mais que je le réinitialise pas ça marche pas. Bon ben merci DS
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 02/08/2016 19:31 | #
Ouh là c'est le bordel ici. Zezombye :
List list;
//...
return &list;
}
Ne fais JAMAIS ça. C'est faux, ça ne peut pas fonctionner. On ne renvoie pas l'adresse d'une variable locale. Cake, t'aurais dû voir ça tout de suite x) (merci Fife86 de l'avoir relevé)
La conversion en BCD ne nécessite pas la fonction pow(). Ce code est, pardonne-moi, horrible. Tu voudrais pas diviser par 10 à chaque étape ? x)
En effet la SDK ne supporte pas exit()... c'est dommage. Cake, je n'ai fait que sauter après le main(). C'est pas encore public mais c'est hyper joli non plus. Pour quitter sur AC/ON, il faut tester si la touche est pressée entre chaque exécution d'opcode en théorie. Tu ferais mieux d'installer un callback sur le gestionnaire d'interruptions (soupire), dommage que ce soit pas possible avec le système. Dans le programme que j'ai posté pour le dernier CPC (Atlantis il me semble), j'ai codé un retour au menu en assembleur, tout prêt, tout cuit. Attention à arrêter les timers avant et à relancer les timers après son utilisation.
Non mais si ton programme lagge à ce point il faut se poser des questions. Fidèle ou pas fidèle, tu peux pas laisser ton code comme ça. è_é Hey, mais t'as oublié toute la propagation des constantes !
Pour le ou exclusif logique, (a && !b) || (b && !a) fonctionne mais on peut faire plus optimisé avec (a || b) && !(b && a) (4 opérations au lieu de 5).
Fais gaffe à ta globale pour « A ≤ B » que la fonction calcExp() ne tente pas de la modifier.
Pour l'injection de touche, le coup du timer de SimLo est subtil. Il faut mettre un timer pour que l'injection soit faite après le lancement de GetKey() et la mise en attente. Mais ce timer ne doit être appelé qu'une fois, pas à l'infini ! Dans le timer handler il faut arrêter le timer x)
Citer : Posté le 03/08/2016 04:45 | #
Je parcours le chiffre de gauche à droite (donc du plus grand chiffre au plus petit) en appliquant la puissance, donc "1220" devient "1*10^3 + 2*10^2 + 2*10^1 + 0*10^0", je vois pas comment faire autrement :o même si je fais de droite à gauche je dois quand même récupérer les chiffres un par un, et donc les additionner en les multipliant par des puissances de 10.
La propagation des constantes, c'est quoi ça x)
Apparemment non, que je les déclare en const ou pas ça marche pas, par contre les déclarer en local marche, même si je les déclare en const.
En effet. J'ai fait des benchmarks et les résultats sont pas trop concluants :
2.11 secondes en basic sur l'émulateur casio
1.46 secondes en C sur l'émulateur casio
0.74 secondes en C sur l'émulateur du SDK
On a une diminution de 30%, c'pas top. Mais par contre je sais honnêtement pas du tout où optimiser plus que ça :/ à part les globales pour les fonctions (qui ne marchent pas pour une raison inconnue) je vois pas. Je pense que là j'ai un bottleneck au niveau du calcul de BCD (du coup, le fait que le basic lague à fond, c'est parce que leur interpréteur est pourri ou parce que leur calculateur est pourri ?)
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 03/08/2016 09:16 | #
Pour la conversion depuis le BCD il suffit de récupérer le chiffre fort et de l'ajouter au résultat. Tu prends la valeur en hexa, tu récupères le « chiffre » de gauche et tu l'ajoutes. Au tour suivant tu multiplies ton résultat par 10, et ainsi de suite :
- Étape 0: 0x1220, 0
- Étape 1: 0x2200, 1
- Étape 2: 0x2000, 12
- Étape 3: 0x0000, 122
- Étape 4: fini, 1220
Le code ressemble à ça (il faut bien sûr spécifier le nombre de digits...) :
{
int shift = (digits - 1) << 2;
unsigned int mask = 0xf << shift;
int result = 0, i:
for(i = 0; i < digits; i++)
{
result *= 10; // on libère de l'espace pour un chiffre à droite
result += (bcd & mask) >> shift; // on ajoute le chiffre de droite
bcd << = 4; // on passe au chiffre suivant (faut virer l'espace)
}
return result;
}
La propagation des constantes, c'est une technique d'optimisation archi-classique qui devrait faire que B2C_convToBCD("31") est calculé une fois pour toute à la « compilation » (tu compiles pas grand-chose en fait --' ) et pas chaque fois que le code est exécuté. Cela dit je ne pense pas que ce soit responsable de la lenteur supposée du programme généré.
D'ailleurs je vois pas pourquoi tu t'embêtes. Regarde ce code :
Getkey
LpWhile Ans≠31 And Ans≠71
Tu le transformes en un truc compliqué alors que tu pourrais tester la condition en calculant « Ans≠31 And Ans≠71 » avec 0x0645.
Je ne vois pas trop pour le coup de la globale. Faudrait creuser un peu.
Non mais c'est pas un problème d'optimisation. Tu peux interpréter du Basic beaucoup plus vite que ça. Ton programme possède soit un bottleneck soit un problème d'algorithmique. Si tu me files la source du puissance 4 généré je pourrai peut-être te trouver quelque chose.
Citer : Posté le 03/08/2016 12:04 | #
Je fais déjà ça avec 0-10 mais c'pas con de faire pour tous les litéraux. (par contre je pense que ça pourrait éventuellement faire un peu beaucoup de globales non ?)
- Étape 0: 0x1220, 0
- Étape 1: 0x2200, 1
- Étape 2: 0x2000, 12
- Étape 3: 0x0000, 122
- Étape 4: fini, 1220
...tain je suis pas super fort en optimisation moi.
J'avais pensé à faire ça mais j'étais bloqué avec les listes (du coup s'il y a List 1[2]!=3 je peux pas faire le calcul tel quel, faut que je passe par B2C_equals()). Faudrait voir s'il y a une grande optimisation par contre (je pense qu'il y en aurait une mais elle serait minime), mais ce serait assez dur à implémenter (pour chaque comparaison/opération logique, je devrais vérifier si l'un des deux côtés comporte une comparaison avec une autre variable que A-Z/r/θ/ans).
La source du puissance 4 est dans le gitlab (par contre c'est assez difficile à lire).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 03/08/2016 16:30 | #
Je fais déjà ça avec 0-10 mais c'pas con de faire pour tous les litéraux. (par contre je pense que ça pourrait éventuellement faire un peu beaucoup de globales non ?)
Non mais t'as pas compris le principe... tu dois pas garder une liste exhaustive, c'est stupide. Tu dois observer que ton calcul du « B2C_convToBCD("31") » donne un résultat constant, le calculer pendant que tu compiles et remplacer l'expression par sa valeur. Mais seulement pour les entiers qui sont demandés... ça vaut pour tous les types de calculs, et ça se propage. « B2C_convToBCD("31") + B2C_convToBCD("14") * B2C_convToBCD("61") » devrait être calculé aussi...
Sinon tu peux simplement utiliser les listes de la mémoire principale. Moins de choses à gérer, enfin presque.
Le code de ton programme est hyper lourd mais à part ça je n'ai pas vu d'algorithmique complètement foireuse. Faudrait tester la puissance de 0x0645 et vraiment trouver de quoi optimiser ton code.
Citer : Posté le 03/08/2016 17:32 | #
Ouaip mais je dois bien stocker ces nombres dans une variable, et donc stocker cette variable quelque part, non ?
Pour les listes/matrices je sais absolument pas comment les utiliser (et s'il était possible de les utiliser je pourrais dans ce cas optimiser à fond, parce que je pourrais directement faire tous les calculs). Sachant que calcExp ne gère pas les assignements.
Pour les calculs de constantes je vois pas trop l'utilité d'implémenter ça vu que n'importe quel bon codeur saurait que faire des calculs de constantes est un peu pas trop optimisé (donc j'ai très peu de chances de rencontrer "1+2*3" dans un programme, du coup il y a pas trop d'optimisations sur ce coup là).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 03/08/2016 17:43 | #
(donc j'ai très peu de chances de rencontrer "1+2*3" dans un programme
A-hum. Si tu comptes sur la chance pour que ton convertisseur fonctionne, tu risques d'avoir de mauvaises surprises.
Citer : Posté le 03/08/2016 17:51 | #
Ouaip mais je dois bien stocker ces nombres dans une variable, et donc stocker cette variable quelque part, non ?
Oui. Mais tu stockes uniquement 31 + 14 * 61 = 885, et pas tous les entiers entre 1 et 1000. Et tu calcules à la demande. Et surtout, tu propages. Par exemple :
doit être calculé et donner "World!", en supposant que la machine soit en radians. C'est pas du cas particulier, c'est une technique l'optimisation.
Pour gérer les listes il faut que tu étudies la structure du fichier dans la mémoire principale. Ça devrait pas être trop compliqué.
Ce n'est pas le code Basic qui a besoin d'être optimisé, peut-être entre autres par le calcul des constantes, mais ton code C.
Citer : Posté le 03/08/2016 20:00 | #
Oui c'est ce que je voulais faire, mais bon 100 constantes = 2.4 ko donc ça devrait pas trop prendre de mémoire.
Ouaip mais par contre je doute que ça soit super optimisé. A ma connaissance il n'y a pas de moyen d'insérer (ou de supprimer) un string à une certaine position dans un fichier donc il faudrait prendre le contenu du fichier, insérer le nombre et réécrire le fichier. Soit casio utilise un syscall pour modifier automatiquement les listes/matrices (j'en ai pas trouvé), soit l'interpréteur lit le fichier, modifie le string et réécrit tout le fichier (donc éditer une grande liste serait lent).
De toute façon même si j'arrive à implémenter les listes et matrices je suis encore bloqué avec les variables autres que A-Z (a0, b0, c0, anStart...) que je ne peux pas modifier avec setAlphaVar parce qu'il prend un char en argument (et c'est des multi bytes).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 03/08/2016 20:47 | #
Oui c'est ce que je voulais faire, mais bon 100 constantes = 2.4 ko donc ça devrait pas trop prendre de mémoire.
De toute façon stocker la constante prend moins de place que stocker le chaîne et l'appel à la fonction de conversion. Et une constante c'est 12 octets hein, donc 100 constantes c'est 1.2 ko a priori.
A ma connaissance il n'y a pas de moyen d'insérer (ou de supprimer) un string à une certaine position dans un fichier
On parle de la mémoire principale là. Le mauvais fs de la mémoire de stockage n'a pas d'existence ici.
Toutes les variables du type a0, b0, anStart ne résident pas dans le fichier ALPHA, il faut aller les modifier dans le fichier correspondant (RECUR en l'occurrence).
Citer : Posté le 04/08/2016 13:49 | #
Il y aurait une méthode/syscall pour insérer des strings dans la mémoire de stockage, sans lire puis réécrire tout le contenu du fichier ? :o
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 04/08/2016 13:52 | #
On parle de la mémoire principale là.
Non, dans la mémoire de stockage, il n'y a pas. Mais il y a peut-être dans la mémoire principale...
Citer : Posté le 04/08/2016 14:48 | #
Oui, mémoire principale je voulais dire. S'il y a une méthode pour écrire à une adresse spécifique, ce serait possible en admettant que j'arrive à avoir l'adresse d'un fichier. Dans ce cas ça prendrait pas beaucoup de temps, il y aurait effectivement de grosses optimisations à ce niveau là. je vais devoir réécrire tout mon moteur de calcul ;-;
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 04/08/2016 14:51 | #
Non mais attends la mémoire c'est plus subtil que ça. Il pourrait y avoir de la fragmentation. Ne modifie surtout pas la mémoire principale sans utiliser Bfile ou les syscalls appropriés. Je rappelle d'ailleurs que le MCS (fs de la mémoire principale, il semblerait) possède quelques incohérences sympa d'après SimLo. T'as plutôt intérêt à utiliser des API bien définies que de faire des choses expérimentales.
Citer : Posté le 04/08/2016 14:57 | #
Hmm, j'avais pas pensé à la fragmentation :o
Donc il n'y a pas d'autre méthode que d'utiliser Bfile (et donc de lire puis réécrire le fichier) ?
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 04/08/2016 18:11 | #
Il n'y a a priori pas de meilleure méthode que d'utiliser Bfile, mais ça ne veut pas dire qu'il faut réécrire le fichier. Je te l'ai dit, la mémoire principale et la mémoire de stockage ont des systèmes de fichiers différents et les fonctions de Bfile qui permettent d'y accéder diffèrent également. On ne peut pas savoir à l'avance.
Citer : Posté le 05/08/2016 18:52 | #
Je sèche pour un octet du sous-header des List :
Je sais que l'octet à 0x48 est un octet de contrôle, que l'octet à 0x55 est le nombre d'éléments, mais je ne sais absolument rien pour celui à 0x56. D'après mes tests :
- Il prend uniquement les valeurs 0x00 et 0x4F (O)
- Il ne dépend pas de la taille du fichier, des listes ayant la même taille n'ont pas cet octet identique
- Il ne dépend pas du nombre d'éléments ni du numéro de la liste (avec les mêmes techniques : des listes avec le même nombre d'éléments n'ont pas cet octet identique, etc)
- Il ne dépend pas de la parité ou non du premier chiffre
- Il semble dépendre du contenu
Qu'est ce que ça pourrait être ?
Edit: en fait la valeur de l'octet semble ne pas avoir d'impact, retirer/ajouter le 0x4F ne fait rien et l'émulateur accepte la liste sans problème.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 05/08/2016 19:08 | #
Tu précises à la fois :
- [...] des listes ayant la même taille n'ont pas cet octet identique
- Il ne dépend pas du nombre d'éléments
- Il semble dépendre du contenu
La première information est une conséquence des deux autres, à condition que tes listes diffèrent. T'as regardé si déplacer des listes dans les numéros affectait la valeur ?
- Il ne dépend pas du nombre d'éléments [...] : des listes avec le même nombre d'éléments n'ont pas cet octet identique
Ça ne veut pas dire que la valeur est indépendante du nombre d'éléments ! Ça veut dire qu'elle ne peut pas dépendre uniquement du nombre d'éléments.
Puisque c'est après le nombre d'éléments ce serait plutôt quelque chose qui ne dépend que des contenus de la liste (nom, nombre d'éléments et valeur des éléments) et indépendant de son numéro.