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 04/07/2016 07:51 | #
Pareil pour les viewwindows mais faut que je me renseigne sur leur utilisation vu que je les utilise pas beaucoup.
Si tu utilises fxlib et non ML pour le dessin, sache que c'est déjà implémenté (si mes souvenirs ne me trompent pas).
Compiler le Basic n'est pas crade en soit. Ce qui est dégueux, c'est de vouloir compiler des jeux déja existant. Et si j'ai bien compris, c'est tout l'intérêt du projet, non ?
Je ne pense pas qu'il soit possible de compiler en brut les jeux existants. Il faudrait au moins les porter : remplacer les Getkey non-bloquants par des structure bloquantes, remplacer les For vides par des Sleep, spécifier des détails sur les variables, etc.
Citer : Posté le 04/07/2016 08:32 | #
Pareil pour les viewwindows mais faut que je me renseigne sur leur utilisation vu que je les utilise pas beaucoup.
Si tu utilises fxlib et non ML pour le dessin, sache que c'est déjà implémenté (si mes souvenirs ne me trompent pas).
Tu penses à SaveDisp non ? Ce sera pas possible d'utiliser ça pour le viewwindow, c'est juste des VRAM séparées.
Par contre il me faudrait plus de référence sur le viewwindow, la doc en dit pas assez :/ En clair si j'ai ce code :
Text 1, 1, "Test"
Quelle serait l'opération à faire pour avoir les coordonnées du pixel où afficher le texte ?
Le Getkey faut voir si je le fais bloquant ou non, dans la plupart des cas il est effectivement utilisé comme bloquant (il est tout seul dans la boucle) mais dans les jeux en temps réel (ex : space run) il y a d'autres instructions dans la boucle du getkey qu'il faudra bien exécuter. Je verrai avec ce que me donnent les jeux.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 04/07/2016 12:35 | #
Tu penses à SaveDisp non ? Ce sera pas possible d'utiliser ça pour le viewwindow, c'est juste des VRAM séparées.
Non je pensais à un vrai ViewWindow, mais je ne trouve rien dans la doc. J'ai dû me tromper.
Par contre il me faudrait plus de référence sur le viewwindow, la doc en dit pas assez :/
Ben c'est des maths ça. Si tu veux « Plot X, Y » sur un graphe de 128x64 qui couvre [Xmin, Xmax] et [Ymin, Ymax] (et si bien sûr le point est dans l'écran, c'est-à-dire Xmin ≤ X ≤ Xmax et Ymin ≤ Y ≤ Ymax), ben faut plotter en (128 * (X - Xmin) / (Xmax - Xmin), 64 * (Y - Ymin) / (Ymax - Ymin)). Pour le texte, le point indiqué est (bien sûr) le coint haut gauche. Et oublie pas que Text utilise les paramètres de Pxl (i.e. Y, X avec Y = 0 en haut de l'écran) et pas ceux de Plot (i.e. X, Y sur un graphe dans le sens usuel).
Le Getkey faut voir si je le fais bloquant ou non, dans la plupart des cas il est effectivement utilisé comme bloquant (il est tout seul dans la boucle) mais dans les jeux en temps réel (ex : space run) il y a d'autres instructions dans la boucle du getkey qu'il faudra bien exécuter. Je verrai avec ce que me donnent les jeux.
IMHO faudra y aller au cas par cas.
Citer : Posté le 04/07/2016 12:52 | #
Ben c'est des maths ça. Si tu veux « Plot X, Y » sur un graphe de 128x64 qui couvre [Xmin, Xmax] et [Ymin, Ymax] (et si bien sûr le point est dans l'écran, c'est-à-dire Xmin ≤ X ≤ Xmax et Ymin ≤ Y ≤ Ymax), ben faut plotter en (128 * (X - Xmin) / (Xmax - Xmin), 64 * (Y - Ymin) / (Ymax - Ymin)). Pour le texte, le point indiqué est (bien sûr) le coint haut gauche. Et oublie pas que Text utilise les paramètres de Pxl (i.e. Y, X avec Y = 0 en haut de l'écran) et pas ceux de Plot (i.e. X, Y sur un graphe dans le sens usuel).
Et Xscale/Yscale dans tout ça, j'ajoute Xscale/Yscale aux coordonnées X/Y des fonctions graphiques ? Et donc pour Text c'est 64-(le calcul à faire) ?
(mais il y avait pas quelque part un PDF avec la liste de toutes les fonctions basic ?)
IMHO faudra y aller au cas par cas.
Ouaip, en fait c'est assez simple à faire. Dans le parsage des GetKey, on les remplace toutes par Getkey_Temp(). Ensuite après avoir fini de parser, on fait juste :
str = str.replaceAll("Getkey_Temp()","Getkey_NoBlock()");
(bien sur c'est du pseudocode et j'ai pas testé le regex)
Et on a pas besoin de vérifier pour les strings car c'est impossible (en basic) de mettre des \n ou des \t dans les strings (sachant que les retours à la ligne ne sont pas des \n mais des \r).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 04/07/2016 13:14 | #
Et Xscale/Yscale dans tout ça, j'ajoute Xscale/Yscale aux coordonnées X/Y des fonctions graphiques ?
C'est l'espacement des graduations ça. Tu n'en auras besoin que si tu reproduis le dessin des axes.
Et donc pour Text c'est 64-(le calcul à faire) ?
Non en l'occurrence c'est pour Plot qu'il faut faire cette adaptation ; je te rappelle que les coordonnées natives sont dans le sens de celles de Text (mais pas dans le même ordre ; c'est malin tiens).
Soyons honnêtes deux secondes : tu n'arriveras jamais à traduire des programmes compliqués tant que tu penseras pouvoir utiliser des regex pour parser ton fichier. Tu n'es pas obligé de construire un AST, mais tu seras obligé de délimiter manuellement les blocs parce qu'aucune regex ne va compter le nombre de parenthèses fermantes, de virgules, ou de IfEnd rencontrés au cours de l'évaluation pour trouver la fin du bloc en cours.
Citer : Posté le 04/07/2016 13:30 | #
Non en l'occurrence c'est pour Plot qu'il faut faire cette adaptation ; je te rappelle que les coordonnées natives sont dans le sens de celles de Text (mais pas dans le même ordre ; c'est malin tiens).
Du coup la viewWindow n'influe pas sur Text ? C'est bon à savoir. Qu'en est il des autres fonctions graphiques non-plot (PxlOn, PxlOff, PxlChg, F-line...), sont elles influencées ou non ?
Soyons honnêtes deux secondes : tu n'arriveras jamais à traduire des programmes compliqués tant que tu penseras pouvoir utiliser des regex pour parser ton fichier. Tu n'es pas obligé de construire un AST, mais tu seras obligé de délimiter manuellement les blocs parce qu'aucune regex ne va compter le nombre de parenthèses fermantes, de virgules, ou de IfEnd rencontrés au cours de l'évaluation pour trouver la fin du bloc en cours.
En l'occurence j'ai pas besoin de faire un parseur pour trouver les GetKey vides. Mon regex fera un match si et seulement si il trouve l'équivalent en C du code :
Getkey
LpWhile //...
S'il y a un if entre les deux, le match est invalidé (et c'est ce que je veux faire). Il doit vraiment n'y avoir que l'instruction Getkey. Bon après je modifierai le regex pour l'assignement de variable (il y en a qui font Getkey->G, même si j'en vois pas trop l'utilité car on peut juste utiliser Ans).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 04/07/2016 13:43 | #
Du coup la viewWindow n'influe pas sur Text ? C'est bon à savoir. Qu'en est il des autres fonctions graphiques non-plot (PxlOn, PxlOff, PxlChg, F-line...), sont elles influencées ou non ?
- PxlOn, PxlOff, PxlChg et Text prennent leurs arguments dans l'ordre Y, X. Y démarre à 1 en haut de l'écran et atteint 63 au plus bas. X démarre à 1 à gauche et atteint 127 à droite.
- PlotOn, PlotOff, PlotChg (?), Line et F-Line prennent leurs arguments dans l'ordre X, Y. Ces deux coordonnées se réfèrent au graphe, qui est défini par le ViewWindow. (Xmin, Ymin) est en bas à gauche de l'écran, et (Xmax, Ymax) est en haut à droite.
Mon regex fera un match si et seulement si il trouve l'équivalent en C du code
Mais un langage de programmation c'est beaucoup plus souple qu'une regex. Il est illusoire de croire que tu pourras détecter ces boucles aussi facilement, parce qu'il n'y a pas de représentation unique. Voici des exemples que ta regex ne trouvera peut-être (je ne sais pas trop ce que tu as codé...) pas :
Getkey
// Si tu contentes de regex, la ligne vide ne va pas partir toute seule
LpWhile Not Ans
While Not Ans // Tout le monde n'aime pas Do
Getkey
WhileEnd
LpWhile Not K
WhileEnd // Et j'en passe...
Citer : Posté le 04/07/2016 14:00 | #
Hmm, c'est vrai. Mais dans ce cas je vois pas trop comment parser ces cas spéciaux. Je doute que ça vaille la peine de les optimiser (parce que c'est une optimisation, dans ces cas là il fera juste Getkey_NoBlock() ce qui sera plus lent que Getkey_Block() mais qui fonctionnera quand même).
Je rajouterai aussi une option sur l'écran de conversion "Jeu en temps réel", qui, si désactivée, entraînera la conversion de tous les Getkey en Getkey_Block(), et si activée fera la conversion à coup de regex. (bien que le regex ne prenne pas tout, le but c'est qu'il prenne la plupart des Getkey bloquants, et je pense que 99.99% des Getkey bloquants sont en Do/Getkey/LpWhile avec peut être un assignement de variable).
D'ailleurs dans ton 3e exemple, il sera bien converti car le parseur supporte les ':' en tant que fin d'instruction le 1er exemple non car le parseur ajoute \n au bout de chaque instruction (mais je peux modifier le regex pour qu'il les prenne en compte).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 04/07/2016 14:03 | #
Mais dans ce cas je vois pas trop comment parser ces cas spéciaux.
Tu dois construire un AST et faire des analyses sémantiques un peu plus subtiles. L'optimisation ça relève du même niveau de détail que la compilation, c'est pas un remplacement de texte...
Perso j'ai toujours écrit mes Getkey avec des While. Je suis sans doute pas le seul.
Citer : Posté le 05/07/2016 20:46 | #
Je suis un peu bloqué concernant le parsage du calcul, du coup je vais poster l'algorithme que j'utiliserai ici, dites si c'est valable svp ou s'il y a une faille dedans
Donc on fait tout d'abord le parsage des parenthèses avec une fonction parseParentheses(String content) qui retourne un int[][][] délimitant chaque "zone de parenthèse". Les 2 premières cases sont le début et la fin de la zone de parenthèse, la 3e case est le niveau de la zone.
Par exemple pour le string "2*(4+5*(6+7))" la fonction retournerait {2, 12, 1} puis {7, 11, 2}. (enfin je sais pas trop le formatage pour les arrays tridimensionnels, mais vous me comprenez)
Ensuite le parseur fait un simple ordre d'opérations. Il cherche d'abord pour '^', puis '*' ou '/', puis '+' ou '-', mais il fait cela dans l'ordre décroissant des niveaux de parenthèses (le maximum étant calculé, je peux le mettre à 100 mais c'est pas propre). Donc dans ce cas il trouve le '+' du "6+7", et il remplace ça par B2C_Add(6, 7). Pourquoi utiliser une fonction customisée ? Tout simplement parce que certains programmes utilisent la technique du "Ans" (faire un calcul puis regarder la variable Ans dans laquelle est stockée le résultat du calcul). Il faut donc une fonction d'addition customisée qui stocke le résultat dans Ans.
Ensuite il continue, et le résultat final est donc B2C_mult(2, B2C_add(4, B2C_mult(5, B2C_add(6, 7)))), ce qui est cohérent avec l'ordre des opérations.
Ma question est : y a-t-il des opérations pour lesquelles cet algorithme se tromperait ? (on ignore pour l'instant les fonctions casio, telles Int, Frac, Not, !, etc).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 05/07/2016 21:11 | #
Tout d'abord le type int[][][] est équivalent au type int ***, c'est un tableau de dimension 3. Puisque tu parles de tableaux de 3 cases, une désignation appropriée est int[3], plus simplement.
Utiliser un tel tableau est pas très réaliste. Tu as peut-être plutôt intérêt à construire un arbre (vraiment). Par exemple pour la chaîne « 2 * (4 + 5 * (6 + 7)) », tu obtiendrais l'arbre suivant :
|--- 2
|--- [+]
|--- 4
|--- [*]
|--- 5
|--- [+]
|--- 6
|--- 7
Il faut faire une analyse récursive pour ça.
- Cherche des opérateurs.
- S'il n'y en a pas, observe la valeur indiquée et renvoie-là. (cas de base)
- Quand tu en trouves, isole leurs arguments et appelle récursivement le parser. Tu obtiens un ou plusieurs noeuds (selon si l'opérateur prend un argument comme le moins unaire ou le Not, ou s'il en prend deux comme + ou And) par opérateur. Tu crées un noeud par opérateur (par exemple [*] ou [+]) et tu lui ajoutes en enfants les noeuds renvoyés par l'analyse récursive. Tu renvoies ensuite les différents noeuds obtenus. Il faut bien penser à la priorité des opérateurs à cet endroit-là.
Une fois que tu as un arbre, il est trivial de le transformer en code.
Citer : Posté le 06/07/2016 00:30 | #
+1 pour ce qu'a dit Lephe auparavant.
J'en profite pour redire (encore une fois) que Planète Casio a un wiki très complet avec entre autre une page sur le Basic Casio. Page qui contient (entre autres) ce qu'à dit Lephe au dessus. Il suffisait de chercher
Citer : Posté le 06/07/2016 00:57 | #
C'est vrai que je le regarde pas trop le wiki mais dans ce cas il m'aide pas, j'ai juste un problème d'algorithmique (et je vais faire un peu comme la méthode de lephé).
Donc en fait ma fonction parseParentheses ne renverra que le premier niveau de parenthèses, avec un array d'ints déterminant le début et la fin du premier niveau de parenthèses. Ensuite je cherche les opérateurs dans l'ordre mais en ne prenant pas ceux qui sont dans les intervalles correspondant aux parenthèses. Du coup il n'y aurait pas vraiment besoin de faire un arbre.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 06/07/2016 10:18 | #
Pour isoler du texte à l'intérieur de l'expression, je te conseille un objet de ce type que j'utilise pour le parser de lightscript :
{
const char *begin;
const char *end;
} reference_t;
Où *begin est le premier caractère dans la partie et *end le premier caractère en-dehors de la partie. Par exemple :
reference_t ref = { str + 7, str + 12 }; // "World"
Tu dois pouvoir faire un truc du genre en Java. J'ai remarqué que c'était assez puissant.
Ensuite je cherche les opérateurs dans l'ordre mais en ne prenant pas ceux qui sont dans les intervalles correspondant aux parenthèses. Du coup il n'y aurait pas vraiment besoin de faire un arbre.
Si tu évites les intervalles aussi brutalement tu ne vas pas réussir ta récursion Tu ferais mieux de considérer une expression comme une suite d'opérateurs et d'opérandes. En supposant que tu n'as que des opérateurs binaires infixes (deux arguments, symbole au milieu), ce sera quelque chose comme ça :
Et il faut voir les parenthèses comme des valeurs. Ensuite tu classes les opérateurs par priorité pour obtenir l'ordre des calculs à faire. Ensuite, tu analyses récursivement les valeurs (donc les parenthèses quand il y en a) et tu recommences. Le plus subtil serait de gérer des opérateurs plus variés.
Si tu ne construis pas un arbre (ce que tu peux éviter, mais que tu feras fatalement dans la pile, de près ou de loin), tu ne pourras jamais réaliser d'analyse du code. Je ne dis pas qu'une telle analyse est nécessaire, mais si tu veux automatiser la transformation des Getkey en GetKey() bloquants par exemple, tu vas devoir en faire, et pour ça tu as besoin d'un arbre.
Citer : Posté le 06/07/2016 11:42 | #
Et il faut voir les parenthèses comme des valeurs. Ensuite tu classes les opérateurs par priorité pour obtenir l'ordre des calculs à faire. Ensuite, tu analyses récursivement les valeurs (donc les parenthèses quand il y en a) et tu recommences. Le plus subtil serait de gérer des opérateurs plus variés.
C'est plus ou moins ce que je vais faire. J'évite les intervalles justement pour considérer les parenthèses comme des valeurs, et ce qu'il y a dans les parenthèses je l'analyse récursivement.
Ouaip :
Mais pas besoin de faire de structure pour le début/fin, je retourne juste un array d'ints où on alterne début et fin. Après je le parcours de 2 en 2.
Faudra aussi que je rajoute des multiplications là où nécessaire (par exemple ABInt 3 devient A*B*Int 3).
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 06/07/2016 12:02 | #
Ça alors, un substring() pour une référence. x) Je dois avouer que je m'y attendais un peu, mais c'est complètement dés-optimiser le concept... ^^'
Fais comme tu le sens mais essaie de reste générique. Il y a aussi des opérateurs unaires, et des opérateurs préfixes (certaines fonctions, si on peut les voir comme ça).
Citer : Posté le 06/07/2016 23:05 | #
Lephé : c'est pas plutôt des opérateurs unitaires?
Citer : Posté le 06/07/2016 23:06 | #
Je ne pense pas, car sinon les opérateurs avec 2 arguments seraient appelés binitaires x)
Puis on dit tern-aire, bin-aire, donc un-aire ça me parait logique.
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 06/07/2016 23:15 | #
Lephé : c'est pas plutôt des opérateurs unitaires?
Non, c'est clairement unaire, binaire, ternaire.
Citer : Posté le 07/07/2016 22:20 | #
J'ai réussi à implémenter le calcul et l'ordre des opérations, ainsi que quelques fonctions (Int, Not, opérateurs booléens, opérateurs d'égalité). Par contre je suis bloqué : pour savoir si l'opération est un calcul, je teste si le début du string est une variable, un nombre, une parenthèse ou un '-'. Pour ça j'utilise un regex. Par contre ça ne teste pas pour le cas où le string commence par une variable non alphabétique (theta, r, mais aussi a0, a1, anStart...) ou une fonction ("Int 3.5" ne serait pas parsé). Je devrais donc faire une condition pour chaque cas spécial, mais ça me mènerait à une quinzaine de conditions.. ce qui est pas très propre.
Ma question est : comment faire pour savoir si le string à parser est un calcul (même s'il contient pas d'opérateurs hein, "Int 3.6" ou même "1" est considéré comme un calcul) sans faire plein de conditions dans mon if ?
Si vous voulez voir un peu mon code : http://git.planet-casio.com/Zezombye/B2C/blob/master/B2C.java , le dernier if de parse().
Ecrivez vos programmes basic sur PC avec BIDE
Citer : Posté le 07/07/2016 22:32 | #
Tout ligne de code est un calcul en fait... si tu es assez malin pour généraliser la notion.
Si tu veux faire plus simple, tout est un calcul, sauf les statements : If, For, Do, While et affiliés, et quelques cas particuliers comme les Goto. Certains de ces calculs ne renvoient rien, comme l'appel à la fonction sans argument ClrText ; d'autres renvoient des valeurs, que l'on peut placer dans une variable et à défaut dans Ans.