+ Répondre à la discussion
Affichage des résultats 1 à 12 sur 12
  1. #1
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut [OpenSource][C++] Calcul formel en ligne de commande

    Bonjour,

    Je souhaiterais vous présenter un projet que j'ai commencé il y a peu.
    On pourrait dire qu'il ne s'agit d'une simple calculatrice en ligne de commande : ça ne serait pas faux en soit, juste incomplet.

    En effet, il s'agit d'un interpréteur extrêmement simple (la classe "State", moins de 900 lignes de code pour le moment, header inclus) qui permet de gérer des opérations entre des symboles. Il est, dans sa conception, relativement générique et permet l'ajout de nouveaux types sans modifier le code de base de l’interpréteur.

    Ainsi, il est muni par défaut de trois types fondamentaux :
    • "Symbol" : la classe de base dont doit hériter tout type. Utilisée seule, cette classe ne sert à rien pour le moment, mis à part à déclarer des symboles sans les définir.
    • "Scalar" : contient un nombre réel (double précision), muni des opérateurs mathématiques habituels : multiplication (*), division (/), addition (+) et soustraction (-).
    • "Reference" : contient une chaine de caractère qui est évaluée à chaque fois que la référence apparait dans une expression. C'est en gros un raccourcis vers une expression plus compliquée.

    Le support du type "Scalar" par défaut pourrait être retiré, l'interpréteur n'en dépendant pas explicitement (mais une calculette sans nombre, je trouve ça quand même dommage).

    Pour montrer que l'ajout de nouveaux types est possible simplement, j'ai mis également à disposition un type supplémentaire : Vector.
    Ressemblant beaucoup au std::vector du C++, le type Vector est un conteneur unidimensionnel homogène (il contient N valeurs de types identiques). Il n'y a strictement aucune restriction sur le type de valeur contenue, ainsi il est possible de créer des Vector de Vector, et toute autre combinaison qui vous passe par la tête.
    Il possède deux opérateurs généraux : l'addition (+) et la soustraction (-), plus la multiplication par un "Scalar" (commutative). Les 3-Vectors (vectors à 3 composantes) disposent également, pour le fun, du produit vectoriel (^).
    Enfin, on peut utiliser l'opérateur '[]' pour accéder aux composantes du vector, en utilisant soit la notation "canonique" vector[]index, soit la notation C++ vector[index].
    La syntaxe de construction d'un vector est la suivante : <elt1, elt2, ...> (l'utilisation des parenthèses étant réservée à leur sens mathématique premier, qui permet de grouper les expressions).

    L'interpréteur peut fonctionner soit en ligne de commande, soit en lisant les commandes directement à partir d'un fichier.
    Voici donc une suite d'exemples d'utilisation commentés, qui valent mieux qu'on long discours.
    Vous trouverez les binaires pour Windows, ainsi que les sources (tout à fait portables), à la fin du message.

    On commence par créer deux symboles 'a' et 'b' en une seule commande. Le symbole ';' permet, comme en C++, de signaler à l'interpréteur la fin d'une instruction.
    Code :
    1
    2
    3
    > a;b
            Defining Symbol : a
            Defining Symbol : b
    Le type Symbol est assez peu utile, on ne peut faire aucune opération dessus :
    Code :
    1
    2
    3
    > a*b
            Error : No match for operator 'Symbol * Symbol' in :
                                  a * b
    On peut en revanche, même si l'intérêt est limité, créer un vecteur à partir de ces deux symboles :
    Code :
    1
    2
    > v = <a,b>
            Defining Vector : v = <a, b>
    Attention cependant, l'interpréteur est tatillon, et ne supporte pas qu'un symbole soit non déclaré :
    Code :
    1
    2
    > w = <a,b,c>
            Error : Unknown symbol 'c'
    On va maintenant passer aux nombres. Pour recommencer une nouvelle session sans fermer le programme, on peut utiliser la commande '~'. Utilisée seule, elle supprime tous les symboles précédemment définis, et utilisée devant le nom d'un symbole, elle détruit celui-ci.
    Code :
    1
    2
    3
    4
    5
    6
    > ~v
            Vector 'v' erased
    > ~
            Warning : This command will erase all symbols.
                      Are you sure ? ([y|Enter]/n) y
            All symbols erased
    Pour vérifier que tout s'est bien passé, on peut demander à l'interpréteur d'afficher le contenu de la variable 'a' :
    Code :
    1
    2
    > ?a
            'a' is undefined
    Tout est ok ! On peut alors commencer sereinement. On affecte d'abord quelques valeurs numériques :
    Code :
    1
    2
    3
    > a=1; b=2
            Defining Scalar : a = 1
            Defining Scalar : b = 2
    Puis on peut évaluer une expression compliquée :
    Code :
    1
    2
    > (9+7)*(3-a)/(10-(4-b))
            Scalar : 4
    Il est possible de rappeler le dernier résultat avec la commande '_' :
    Code :
    1
    2
    3
    4
    > _*_
            Scalar : 16
    > _*_
            Scalar : 256
    On peut également mélanger joyeusement scalaires et vecteurs, ou manipuler les composantes (l'indice dans les crochet est cyclique : pour un vecteur de taille N, v[N+i] = v[i] et v[-i] = v[N-i]). En cas d’ambiguïté d'ordre d'opérateurs (comme pour la 2nde ligne), l'expression est lue de gauche à droite séquentiellement :
    Code :
    1
    2
    3
    4
    > v = (0.5*_/b)*<1, 2*a, 3*b-2*a>
            Defining Vector : v = <64, 128, 256>
    > v[4]/v[-1]/v[0]
            Scalar : 0.0078125
    Pour compléter ce tour d'horizon, il ne nous reste plus qu'à présenter l'usage des références :
    Code :
    1
    2
    > r &= b*c
            Defining Reference : r = b*c
    Les références étant évaluées seulement lorsqu'on en fait usage, il est autorisé d'y faire figurer des symboles non définis. Elles ne pourront alors être complètement évaluées que lorsque tous les symboles qu'elles contiennent sont définis :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    > r
            Reference : b*c
    > c = <1,2,3>
            Defining Vector : c = <1, 2, 3>
    > r
            Vector : <2, 4, 6>
    > ~c
            Vector 'c' erased
    > r
            Reference : b*c
    > 4*r
            No match for operator 'Scalar * Reference' in :
                       4 * b*c
    Cette dernière ligne montre ce qu'il me reste à implémenter pour que le système de référence soit plus ou moins complet. L'expression '4*r' devrait retourner une nouvelle référence...

    Si vous avez envie de crier à l'arnaque, vous en avez le droit : pour le moment, pas de calcul formel au menu... Mais c'est bien entendu sur la ToDo list !

    Le but principal de ce programme n'est pas de concurrencer Mathematica/Mapple/etc, mais de proposer une solution gratuite, open source et légère permettant de faire de l'algèbre linéaire de base (pas de matrices pour le moment, mais rien ne m'en empêche) et du calcul tensoriel formel (et accessoirement, numérique).
    Comme vous pouvez le voir sur mon profil, je suis étudiant en physique (Master 2 actuellement), et il m'arrive bien souvent d'avoir à calculer des expressions tensorielles relativement pénibles, dont j'aimerais vérifier le résultat rapidement par ordinateur. C'est de là qu'est partie l'envie de programmer ce petit bout de programme (plus ma passion un peu malsaine de ré-inventage de roue).

    Voici donc comme promis les binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v001 : [7z] (100Ko), [zip] (155Ko) (01h00, 24/09/2011)
    • Ubuntu 11.10 (32bit) v001 : pas tout de suite !

    ... et les sources :
    • Sources (multiplateforme) : [7z] (51Ko), [zip] (87Ko) (01h00, 24/09/2011)

    Le tout sous licence GNU GPL.

    Pour compiler, vous devrez d'abord compiler la bibliothèque "Utils" qui se trouve dans le sous dossier du même nom. Elle n'a aucune dépendance ni configuration, et est donc très facile à compiler (les projets Code::Block sont fournis et prêts à l'emploi, d'ailleurs). La documentation Doxygen de cette bibliothèque est fournie ici : [7z] (262 Ko), [zip] (1329 Ko).

  2. #2
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Le programme peut maintenant faire se propager les références :
    Code :
    1
    2
    3
    4
    > r &= a*b
            Defining Reference : r = a*b
    > 5 + (2*r - 3*2)
            Reference : (5)+(((2)*(a*b))-(6))
    L'excès de parenthèse sera traité par la suite.

    La prochaine étape pour le calcul formel sera donc de proposer des fonctions pour modifier le contenu d'une référence (développement, factorisation, etc).

    Dans cette optique, la version 002 apporte également le support des fonctions, qui peuvent avoir un nombre arbitraire d'arguments. La recherche se fait d'abord par le nom de la fonction, puis si celle-ci existe, on évalue les arguments un par uns pour voir si un prototype correspond aux arguments fournis :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    > rand(4,6)
            Scalar : 5.61748100222785
    > rand(1,2,3)
            Error : No matching function call to :
                      rand(Scalar, Scalar, Scalar)
                    Possible candidates :
                      rand()
                      rand(Scalar, Scalar)
    > rund(4,6)
            Error : Undefined function 'rund'
    Les appels de fonction peuvent avoir lieu n'importe où :
    Code :
    1
    2
    3
    4
    > round(rand())
            Scalar : 1
    > <min(1,2), max(3,4), min(rand(),rand())>
            Vector : <1, 4, 0.00125125888851588>
    L'ajout d'une nouvelle fonction est relativement simple. Comme dans la quasi totalité des langages interprétés, la fonction C++ doit avoir un prototype bien défini, qui permet de recevoir un nombre indéterminé d'argument, et d'en retourner également un nombre indéterminé (même si pour l'instant seules les fonctions à une seule valeur de retour sont supportées)).
    Ainsi on aura par exemple :
    Code c++ :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    mState.RegisterFunction<Scalar,Scalar>("rand",
        [] (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) -> s_bool
        {
            s_refptr<Scalar> sa = s_refptr<Scalar>::StaticCast(args[0]);
            s_refptr<Scalar> sb = s_refptr<Scalar>::StaticCast(args[1]);
            res = {s_refptr<Symbol>(new Scalar("", s_double::Random(sa->v, sb->v)))};
            return true;
        }
    );
    ... où les deux arguments templates sont les types des paramètres de la fonction. Le premier argument est le nom de la fonction dans le programme, et le second : la fonction en elle même. J'utilise ici les lambdas du C++11 pour plus de concision.
    On voit alors que celles-ci doivent avoir le prototype suivant :
    Code :
    s_bool (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e)
    La valeur de retour indique si tout s'est bien passé, et la chaine de caractère 'e' (dernier argument) contient les éventuelles erreurs/warnings. Les deux autres arguments sont respectivement les arguments de la fonction et ses valeurs de retour.
    L'emploi de 'static_cast' n'est pas problématique, car la classe State s'assure déjà en amont que les types sont corrects.

    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v002 : [7z] (109Ko), [zip] (165Ko) (12h00, 25/09/2011)
    • Ubuntu 11.10 (32bit) v002 : [7z] (133Ko), [zip] (171Ko) (14h00, 25/09/2011)

    ... et sources :
    • Sources (multiplateforme) : [7z] (57Ko), [zip] (95Ko) (15h30, 25/09/2011)

  3. #3
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Voici la version 003, qui contient quelques ajouts mineurs :
    • nouvelles fonctions : exp, log, cos, acos, sin, asin, tan, atan, abs et pi (sans argument, qui renvoie la valeur du nombre pi),
    • support d'une syntaxe allégée pour les fonctions sans arguments : on peut oublier les parenthèses (et ainsi se servir de la fonction pi() comme d'une variable standard, qu'on ne peut juste pas modifier)
    • ajout des opérateurs puissance '^' et modulo '%' pour le type Scalar
    • diverses petites corrections dont je ne me souviens plus


    Petits exemples de code :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    > exp(log(2))
            Scalar : 2
    > cos(2*pi)
            Scalar : 1
    > rand
            Scalar : 0.00125125888851588
    > pi = 2
            Error : Cannot assign a value to the function 'pi'
    > 2^8
            Scalar : 256
    > 11911 % 777
            Scalar : 256
    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v003 : [7z] (114Ko), [zip] (175Ko) (19h00, 19/10/2011)
    • Ubuntu 11.10 (32bit) v003 : [7z] (140Ko), [zip] (181Ko) (19h00, 19/10/2011)

    ... et sources :
    • Sources (multiplateforme) : [7z] (58Ko), [zip] (95Ko) (19h00, 19/10/2011)

  4. #4
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Et voilà tranquillement la version 004 qui arrive :
    • support des nombres complexes,
    • ajout de la fonction 'machine_epsilon' qui donne la précision des nombres affichés,
    • on peut maintenant donner plusieurs noms à une même fonction (pour les abréviations par exemple : 'cosh' et 'ch'),
    • ajout des fonctions cosh (ch), sinh (sh), et tanh (th)
    • correction d'un petit bug qui empêchait l'emploi de la notation abrégée pour les fonctions contenant un underscore '_'.


    Et maintenant quelques exemples pour étayer le tout.
    On a l'algèbre de base des nombres complexes :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    > a = 1 + 2*i
            Scalar : a = 1 + 2 i
    > b = a^2
            Scalar : b = -3 + 4 i
    > abs(b)
            Scalar : 5
    > a*conj(a)
            Scalar : 5
    > <re(a), im(a)>
            Vector : <1, 2>
    ... avec les fonctions 'abs' (donne la norme), 'conj' (ou 'conjugate', donne le complexe conjugué), 're' (ou 'real', donne la partie réelle) et 'im' (ou 'imaginary', donne la partie imaginaire).

    Les fonctions usuelles ont été étendues sur l'ensemble des complexes (quand c'est possible) :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    > exp(i*pi)
            Scalar : -1
    > cos(a)
            Scalar : 2.03272300701967 - 3.0518977991518 i
    > round(_)
            Scalar : 2 - 3 i
    > acos(a)
            Error : 'acos' is only defined for real numbers
    A noter que j'aurais pu définir le logarithme complexe également, mais je n'en vois pas l'utilité (sachant surtout que sa généralisation sur C n'est pas triviale).

    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v004 : [7z] (120Ko), [zip] (186Ko) (23h00, 30/11/2011)
    • Ubuntu 11.10 (32bit) v004 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (59Ko), [zip] (96Ko) (23h00, 30/11/2011)

  5. #5
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Après pas mal d'inactivité sur ce projet, j'ai décidé de le reprendre à zéro sur de meilleures bases. Le précédent code était relativement stable (je l'utilise depuis le début comme calculatrice sur tout mes PCs !), mais j'avais du mal à le faire évoluer, tant le parser était fouillis et la conception parfois bancale.

    Je vous présente donc la toute neuve v005, avec au menu (en vrac) :
    • le parsing se fait via boost::spirit,
    • on peut choisir (au runtime grâce à l'option -high-precision=true, activé par défaut) de manipuler des nombres haute précision grâce à MPFR et son binding C++ mpfr::real (ceux-ci sont codés en virgule flottante sur 128 bits, et ce n'est modifiable qu'à la compilation),
    • le nombre de chiffres affichés est configurable au runtime avec l'option -digits=10 (NB : ça n'affecte en rien la précision du calcul, juste l'affichage),
    • on peut choisir de ne pas tolérer de variables ou fonctions non définies (au runtime aussi avec l'option -allow-undefined=false, toléré par défaut),
    • on peut également désactiver le support des nombres complexes (au runtime également grâce à l'option -allow-complex=false, ils sont activés par défaut),
    • si les nombres complexes sont activés, le type scalaire (i.e. un réel pur) reste actif, et si un calcul sur des nombres complexes donne une partie imaginaire nulle, le résultat est automatiquement considéré comme étant de type réel (on ne perd pas de mémoire à stocker une partie imaginaire nulle, et les opérateurs entres complexes et réels sont définis explicitement, de sorte que 2*(1+i) ne génère effectivement qu'une addition et une multiplication réelles, et non le double comme c'était le cas avant),
    • le parsing des nombres complexes est amélioré : on peut écrire directement 1+2i au lieu de 1+2*i,
    • la syntaxe pour les vecteurs est modifiée : (a, b, c) au lieu de <a, b, c>,
    • les vecteurs ne sont plus contraints à être homogènes (ils peuvent contenir des données de différents types),
    • le type vecteur est maintenant complètement indépendant du type scalaire : on peut multiplier/diviser, additionner/soustraire un vecteur avec n'importe quoi pourvu que cette même opération soit définie pour chaque objet stocké dans le vecteur,
    • les références (qui contenaient une chaîne de caractère ré-évaluée à chaque fois) disparaissent au profit des expressions (qui contiennent un arbre d'opérateur, donc des données pré-parsées pour de meilleures performances et une manipulation plus aisée),
    • les identifiers (noms de variable ou de fonction) suivent maintenant les mêmes règles qu'en C++ (à savoir : ne peuvent commencer par un chiffre, mais sinon n'importe quel caractère alphanumérique (ASCII seulement) ou un underscore '_'),
    • il est possible de donner le même nom à une variable et à une fonction (en cas d’ambiguïté, l'interpréteur considère en priorité la variable),
    • la fin d'une instruction est signalée par un simple espace (à condition que celui-ci ne soit pas situé entre deux opérateurs, i.e. : a=2 b=3 est valide et correspond à l'ancienne syntaxe a=2;b=3;, mais a=2 +b correspond à a=2+b; et non a=2;+b;).


    Je pense avoir fait le tour !

    La compilation est un peu plus ardue que précédemment, vu qu'il faut avoir boost d'installé (j'utilise la version 1.50), ainsi que MPFR (mpfr::real est header only et fourni avec mes sources) qui lui-même dépend de GMP. Autant boost::spirit est header only (rien à compiler donc), autant ce n'est pas le cas de GMP et MPFR qui sont des bibliothèques C. Ceci dit, elles compilent très facilement sous Windows avec MSYS et MinGW (./configure, make, un café, et c'est plié). Sous linux, je pense qu'il existe des paquets pré-compilés pour votre distribution (il faut au minimum MPFR 3).
    La bibliothèque "Utils" a quant à elle disparu.

    Ce qu'il reste à faire :
    • Je dois encore nettoyer un peu le code (j'ai laissé le corps de plusieurs fonctions dans les headers pour m'éviter des allers-retours entre .cpp et .hpp, mais maintenant que l'API est à peu près stable je dois les ranger proprement),
    • Lors de la sérialisation d'une expression, j'ai trop de parenthèses inutiles (j'avais ce soucis dans la version précédente, mais là ce sera bien plus simple à régler),
    • Dans une expression, les calculs n'impliquant que des littéraux (i.e. des symboles dont le contenu est fixe : un réel, un complexe, ou un vecteur ne contenant que des littéraux, mais pas une variable ou une fonction, fussent-elles définies) peuvent être effectués une fois pour toute à l'affectation. J'ai déjà implémenté partiellement la chose dans les cas les plus simples : 1+2+a produit (1+2)+a que je simplifie facilement en (3+a), en revanche a+1+2 produit (a+1)+2 (la simplification n'est plus évidente : il faut jouer avec l'associativité et/ou la réorganisation des opérations),
    • Je suis passé aux fonctions mathématiques de la bibliothèque standard pour les complexes (cf. #include <complex>). Cependant, il est écrit dans le standard que l'instanciation de std::complex<T> avec T différent de float, double ou long double est non défini... Sauf que j'utilise std::complex<mpfr::real<128>> dans mon code. J'ai pu vérifier que les implémentations fournies par gcc et VC++ étaient purement templates, donc a priori ça fonctionne mais j'aurai aimé avoir un peu plus de certitude... Et je n'ai pas envie de ré-écrire toutes ces fonctions mathématiques dans mon coin.


    J'ai un peu laissé tombé le côté tenseur pour le moment. À voir si ça reviendra au devant de la scène plus tard En attendant...

    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v005 : [7z] (322Ko), [zip] (445Ko) (00h00, 02/10/2012)
    • *buntu 12.04 (32bit) v005 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (37Ko), [zip] (49Ko) (00h00, 02/10/2012)

  6. #6
    Rédacteur
    Avatar de CyaNnOrangehead
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    mai 2008
    Messages
    769
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : mai 2008
    Messages : 769
    Points : 1 259
    Points
    1 259

    Par défaut

    Rhaaa les nombres complexes et tout ça, je ne suis pas allez assez loin dans mes études générales pour connaitre.

    En revanche, les parseurs, tout ça, je suis un peu plus à l'aise.

    Concernant ton problème d'optimisation (1+2) a vs (a+1) +2 tu devrais utiliser les AST (Abstract Syntax Tree)
    Le principe est le suivant, tu pré-compile ton source dans un arbre.
    Ensuite pour résoudre ton arbre, il te suffit de faire un parcour en profondeur.
    Regarde du coté du design pattern Interpreter du GoF.
    Sinon un très bon bouquin sur le parsing et la compilation :
    http://pragprog.com/book/tpdsl/langu...ation-patterns

    Autre petit point, j'ai cru comprendre en lisant tes exemples que ton programme ne respecte pas la précédence des opérateurs.
    http://en.wikipedia.org/wiki/Order_of_operations (cf partie "programming languages")
    Ce qui sous entend que 1 + 2 * 3 = 1 + (2 * 3) <> (1 + 2) * 3
    La précédende insique que le signe * est traité "avant" le signe +, et du coup respecte l'ecriture arithmétique que nous connaissons.
    Mais je peu me tromper, tu l'a peu-t-être fait.

    Derniere chose, c'est dommage de ne pas avoir écrit ton parseur toi même. C'est un domaine vraiment très intéréssant. Mais ça demande ennormément de temps.

    En tous cas bonne continuation.
    Retrouvez tous mes tutoriels : http://caron-yann.developpez.com/

    Et mon projet en cours : Algoid - programming language

  7. #7
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Citation Envoyé par CyaNnOrangehead Voir le message
    Rhaaa les nombres complexes et tout ça, je ne suis pas allez assez loin dans mes études générales pour connaitre.
    Ce n'est pas très compliqué dans le fond, même si ça fait toujours peur au début : tu introduis un symbole i tel que i*i = -1 (par définition). C'est l'unité des nombres complexes purs. Les complexes et les nombres normaux "ne se mélangent pas", c'est à dire qu'on ne peut pas simplifier d'avantage 4+6i (dans cet exemple, on fait la somme d'un nombre réel 4 et d'un nombre imaginaire pur 6i, le résultat peut se mettre sous la forme (4, 6), où on met à gauche la partie réelle, et à droite la partie imaginaire).

    Ca sert beaucoup en physique théorique et en math, mais aussi en optique, en électronique, et plus généralement tous les domaines où l'on retrouve des phénomènes périodiques (c'est donc vaste)...

    Dans la vie de tout le jours, c'est complètement inutile

    Citation Envoyé par CyaNnOrangehead Voir le message
    Concernant ton problème d'optimisation (1+2) a vs (a+1) +2 tu devrais utiliser les AST (Abstract Syntax Tree)
    Le principe est le suivant, tu pré-compile ton source dans un arbre.
    Ensuite pour résoudre ton arbre, il te suffit de faire un parcour en profondeur.
    Regarde du coté du design pattern Interpreter du GoF.
    Sinon un très bon bouquin sur le parsing et la compilation :
    http://pragprog.com/book/tpdsl/langu...ation-patterns
    Je ne connaissais pas le terme, mais c'est plus ou moins ce que je fais. Tous les objets qui peuvent apparaitre dans une expression dérivent du type de base symbol. J'ai ensuite une classe binary_expression qui dérive elle aussi de symbol et qui contient :
    • l'opérateur ('*', '/', '+', '-', etc.),
    • le membre de gauche (un symbol*),
    • le membre de droite (un autre symbol*).

    Par contre, en l'état, il n'est pas simple de parcourir cet arbre (à moins de faire du dynamic_cast, mais c'est pas une bonne solution).

    Ce qui me manque, c'est l'algorithme de simplification, et aussi que je rende mon code conscient du fait que tel opérateur est associatif ou non (i.e. que (1+2)+3 == 1+(2+3), et ce pour l'addition et la multiplication seulement ! Ce n'est pas vrai pour la soustraction, la division ou le modulo par exemple).

    Citation Envoyé par CyaNnOrangehead Voir le message
    Autre petit point, j'ai cru comprendre en lisant tes exemples que ton programme ne respecte pas la précédence des opérateurs.
    Heureusement que si (je suis physicien, la précédence j'y tiens un minimum ) ! Dans mon code précédent, c'était géré explicitement (j'avais une liste d'opérateurs auxquels j'associais une priorité : à défaut de parenthèses, j'évaluais d'abord les opérateurs avec la plus haute priorité, puis je descendais vers les plus faibles). Dans le code actuel, grâce à boost::spirit, j'ai simplement écrit la grammaire du langage, du style (simplifié) :
    Code c++ :
    1
    2
    3
    factor := number || ('(' expression ')')
    term := factor || (factor '*' factor) || (factor '/' factor)
    expression := term || (term '+' term) || (term '-' term)
    La grammaire complète se trouve dans le fichier parser.hpp.

    Ceci dit, depuis le début (donc dans les deux codes), si deux opérateurs de précédence égale sont situés l'un à côté de l'autre, l'évaluation se fait de gauche à droite. C'est peut être ça qui t'a induit en erreur ?

    En rédigeant ce message (en vérifiant que mon programme donnait bien i*i == -1 ), je me suis rendu compte que l'affichage des nombres négatifs était buggé. C'est maintenant réglé :

    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v006 : [7z] (322Ko), [zip] (445Ko) (20h00, 03/10/2012)
    • *buntu 12.04 (32bit) v006 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (37Ko), [zip] (50Ko) (20h00, 03/10/2012)

  8. #8
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Citation Envoyé par CyaNnOrangehead Voir le message
    Derniere chose, c'est dommage de ne pas avoir écrit ton parseur toi même. C'est un domaine vraiment très intéréssant. Mais ça demande ennormément de temps.
    J'avais oublié de commenter cette remarque. Je suis d'accord avec toi, c'est très intéressant.
    Cependant, il faut partir sur de bonnes bases (ce qui n'était pas mon cas) et être rigoureux sur toute la ligne (là encore, ce n'était pas vraiment mon cas non plus) si on veut avoir un code clair et maintenable. En utilisant boost::spirit ici, je n'ai besoin que de 240 lignes pour avoir un parseur fonctionnel et très simple à faire évoluer, à comparer aux 800 lignes de mon code perso.

    Sinon, le problème de simplification des parenthèses inutiles est réglé. Il m'a suffit d'attribuer une priorité à chaque symbole, de sorte que les littéraux, fonctions, variables et vecteurs soient de priorité 0 (la plus élevée), les opérateurs unaires "-" et "+" aient respectivement une priorité de 1 et 2, et les opérateurs binaires "%", "/", "*", "-", "+" aient respectivement une priorité de 3, 4, 5, 6 et 7 (en gros).
    Alors, lorsque je sérialise une expression, je regarde si la priorité de l'opérateur courant est inférieure à celle des symboles sur lesquels il agit : si c'est le cas, on enveloppe ces symboles dans des parenthèses, sinon c'est inutile.

    On a donc :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    > a+b*c
    >> a+b*c
    > (a+b)*c
    >> (a+b)*c
    > (a+b)+c
    >> a+b+c
    > c-a+b
    >> c-a+b
    > c-(a+b)
    >> c-(a+b)
    ... (fantastique, hein ?) alors que l'ancien code affichait :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    > a+b*c
    >> (a+(b*c))
    > (a+b)*c
    >> ((a+b)*c)
    > (a+b)+c
    >> ((a+b)+c)
    > c-a+b
    >> ((c-a)+b)
    > c-(a+b)
    >> (c-(a+b))
    Quant au problème de simplification des opérations sur les littéraux, ça risque d'être plus complexe que je ne le pensais si je veux rester général. Dans le cas (a+1)+2, il me suffit de supposer que l'addition est associative pour dire que c'est équivalent à a+(1+2), et c'est bon. En revanche, pour (1+a)+2, l'associativité ne suffit plus... il faut faire appel à la commutativité qui me permet de passer à (a+1)+2, puis à l'associativité pour arriver enfin à a+(1+2) (là encore, ça parait trivial pour l'addition, mais c'est une chose qu'on ne peut pas faire avec la soustraction : (1-a)-2 != a-(1-2)).

    Il me faut en plus faire un choix : attribuer les propriétés d'associativité et de commutativité aux opérateurs seuls, ou également prendre en compte le type des opérandes... (i.e. : "*" peut être commutatif pour les scalaires et complexes, mais pas pour les matrices par exemple).

    Le premier cas simplifie le processus de simplification (hum...) car on a pas besoin de connaître le type des variables qui entrent en jeu dans l'expression. En revanche, si je veux introduire la multiplication matricielle par la suite, je devrai introduire un nouvel opérateur, par exemple "**", spécialement dédié à cette fonction et qui lui ne serait pas commutatif. En soit ce n'est pas trop grave, mais ça peut être déroutant pour l'utilisateur qui serait habitué à utiliser "*".

    Le second cas est plus pratique pour l'utilisateur a priori, mais complexifie la simplification (hum... bis). Pour pouvoir simplifier une expression comme celle donnée en exemple plus haut, il faudra donner des informations supplémentaires au programme (comme par exemple dire "simplifier cette expression en considérant que la variable 'a' est un scalaire" : le programme peut alors utiliser la commutativité et tout roule).

    Il me faut y réfléchir. Quoi qu'il en soit, voici la dernière version (j'en ai profité pour nettoyer le code et régler un bug avec les opérateurs unaires n'agissant pas sur des littéraux) :

    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v007 : [7z] (322Ko), [zip] (447Ko) (23h00, 03/10/2012)
    • *buntu 12.04 (32bit) v007 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (38Ko), [zip] (52Ko) (23h00, 03/10/2012)

  9. #9
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Décidément, ça bouillonne...

    J'ai repéré un bug avec mon code de simplification des parenthèses :
    Clairement ces parenthèses là sont inutiles. C'est maintenant de l'histoire ancienne : j'ai pris en compte que certains opérateurs peuvent avoir la même précédence (par exemple ici "+" et "-"), et que si l'on se retrouve avec une expression faisant intervenir deux opérateurs qui sont dans ce cas là (comme l'exemple ci-dessus) seules les parenthèses à droite peuvent être utiles.
    J'avais aussi oublié l'opérateur "^" (puissance) dans ma liste de priorité, je l'ai rajouté.

    Sinon, j'ai introduis un nouveau type d'affectation, ":=", qui fonctionne comme "=" mais qui n'évalue pas l'expression à droite.

    Enfin, j'ai implémenté un détecteur de dépendance circulaire :
    Code :
    1
    2
    3
    4
    5
    6
    7
    > a=a
    error: circular dependency detected on 'a'
    > a=b b=c c=d d=a
    >> a = b
    >> b = c
    >> c = d
    error: circular dependency detected on 'd'
    Attention : le test n'est fait qu'à l'évaluation. Si dans le code ci-dessus j'avais écrit a:=a, il n'y aurait pas eu d'erreur (seulement plus tard, quand on fera appel à a ailleurs).

    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v008 : [7z] (325Ko), [zip] (451Ko) (22h50, 04/10/2012)
    • *buntu 12.04 (32bit) v008 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (38Ko), [zip] (52Ko) (22h50, 04/10/2012)


    PS : j'ai pu tester la compilation sous Ubuntu 12.04. Avec les paquets libboost-dev (1.48), libgmp-dev (5.0.2) et libmpfr-dev (3.1.0) fournis dans les dépôts par défaut, ça passe sans encombre. Bien sûr, il faut avoir gcc 4.7 (disponible dans ce PPA, ou dans la prochaine version 12.10).

  10. #10
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    À chaque jour sa version...

    Au menu d'aujourd'hui :
    • dans l'interpréteur, j'ai supprimé les >> avant l'affichage du résultat pour plus de clarté, et de façon à ce que celui-ci soit aligné :
    • les fonctions peuvent maintenant prendre comme argument des variables (ou autres fonctions) non définies (le résultat final ne pourra alors être évalué que lorsque tous les paramètres seront définis),
    • cette correction m'a aussi donné l'idée de faire en sorte que a*(b, c) soit évalué en (a*b, a*c) au lieu de rester tel quel,
    • ajout de la fonction digits(n), équivalente à l'option de lancement -digits=n. On voit au passage que le nombre de chiffres significatifs des nombres "haute précision" tourne autour de 40 :
      Code :
      1
      2
      3
      > digits(50)
      > 0.17
        0.16999999999999999999999999999999999999994122528246 (scalar)


    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v009 : [7z] (327Ko), [zip] (453Ko) (00h40, 06/10/2012)
    • *buntu 12.04 (32bit) v009 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (39Ko), [zip] (53Ko) (00h40, 06/10/2012)

  11. #11
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Voici une nouvelle version bien chargée...
    • Il est maintenant possible de terminer une expression par un point virgule ; de façon à ne pas afficher le résultat. Attention : il n'est plus possible de séparer deux instructions par un simple espace !
    • J'ai corrigé l'affectation := faisant intervenir des vecteurs.
    • J'ai aussi corrigé le détecteur de référence cycliques : il détecte bien tout maintenant, tellement qu'il détecte aussi des faux positifs... Du coup je lui ai mis un seuil de détection : à partir de max_cycles accès consécutifs à une même variable au sein d'une même expression, il lance une exception. Cette valeur est bien sûr configurable au lancement avec -max-cycles=n et au runtime avec max_cycles(n).
    • J'ai supprimé l'affichage du type après la valeur. Il est maintenant possible d'obtenir cette information différemment (voir ci-dessous, suspens...).
    • J'ai amélioré un peu le parser de sorte qu'il est maintenant possible d'enchainer les assignations, mais aussi d'utiliser la valeur de retour d'une assignation (fonctionne comme en C/C++) :
      Code :
      1
      2
      3
      4
      5
      6
      7
      8
      9
      > a=b=0;
      > ?a
        0
      > ?b
        0
      > c=(d=1)+2
        3
      > ?d
        1
    • J'ai ajouté les fonctions log10 et log2 ainsi que real/re et imaginary/im qui donnent les parties réelles et imaginaires du nombre en argument.
    • Si on appelle une fonction avec un vecteur en premier argument, l'interpréteur applique cette fonction à chacun des éléments du vecteur (à condition que cette fonction ne soit pas explicitement définie comme pouvant prendre un vecteur comme argument). Pour le moment le calcul n'est pas fait en parallèle, mais ça pourrait très bien être implémenté par la suite.
      Code :
      1
      2
      3
      > vec=(1,2,3);
      > cos(vec)
        (0.540302, -0.416147, -0.989992)
    • Les fonctions peuvent être définies comme pouvant prendre n'importe quel type d'argument grâce au type interne any_type. En cas de surcharge, i.e. si l'on définit une_fonction(any_type) et une_fonction(scalar), la version la plus proche est sélectionnée (dans l'exemple : appeler une_fonction(12) fera toujours appel à une_fonction(scalar)). Qui plus est, il est maintenant possible de définir une fonction prenant un nombre arbitraire d'arguments (fonction variadique), grâce au type interne variadic_type. Note : une fonction ne peut avoir de type variadique qu'à la toute fin de sa signature.
    • J'ai ajouté la fonction size() qui retourne la taille du vecteur fourni en argument, reverse() qui inverse l'ordre des élements dans le vecteur, ainsi que l'opérateur [], qui permet d’accéder aux éléments d'un vecteur (cet opérateur était déjà implémenté dans l'ancienne version, j'avais oublié de le remettre dans la nouvelle) :
      Code :
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      > vec=(2, 4, 8, 16, 32);
      > vec[0]
        2
      > vec[-1]
        32
      > size(vec)
        5
      > vec[5]
        2
      > reverse(vec)
        (32, 16, 8, 4, 2)


    Voilà pour les petits changements. J'ai également implémenté quelque chose d'un peu plus conséquent : les chaînes de caractères (ou string) :
    • le parser peut maintenant reconnaitre une chaine de caractère. Celle-ci est entourée de guillemets, et peut contenir des caractères spéciaux grâce à l'échappement (comme en C/C++). Pour le moment, seuls '\n' '\r' '\t' '\\' '\"' sont reconnus :
      Code :
      1
      2
      3
      4
      > "abc"
        abc
      > "ab\"c"
        ab"c
    • Les deux seuls opérateurs définis sur une chaîne de caractère sont l'addition (concaténation) :
      Code :
      1
      2
      > "abc" + "def"
        abcdef
    • ... et l'accès aux caractères :
    • L'ajout de ce nouveau type permet de nouvelles choses, en particulier l'introduction de la fonction type() qui retourne le type de l'objet en argument :
      Code :
      1
      2
      3
      4
      5
      6
      > type("abc")
        string
      > type(12)
        scalar
      > type(12+0i)
        complex
    • ... ou de la fonction load() qui ouvre le fichier dont le chemin est donné en argument et charge tout son contenu dans une chaîne de caractères (j'ai volontairement tronqué l'output, qui était un peu trop long ) :
      Code :
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      > load("gnu.txt")
                          GNU GENERAL PUBLIC LICENSE
                             Version 2, June 1991
      
         Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
         51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
         Everyone is permitted to copy and distribute verbatim copies
         of this license document, but changing it is not allowed.
      
                                  Preamble
    • J'ai ajouté quelques fonctions de manipulation sur ces chaînes de caractères, comme split() qui découpe la chaîne donnée en premier argument chaque fois qu'un des caractères présents dans la chaîne en second argument est trouvé :
      Code :
      1
      2
      > split("voici un.exemple:de découpage", " .:")
        (voici, un, exemple, de, découpage)
    • ... la fonction split_token(), qui fonctionne sur le même principe mais qui traite le second argument comme un seul token de découpage :
      Code :
      1
      2
      > split_token("std::string:", "::")
        (std, string:)
    • ... la fonction replace() (je vous passe les détails) :
      Code :
      1
      2
      > replace("Je mange une pomme", "pomme", "poire")
        Je mange une poire
    • ... la fonction reverse() (idem) :
    • Mais aussi la fonction print() qui affiche consécutivement tous les arguments qui lui sont fournis, et ce sans aucun formatage (pas de saut de ligne automatique, en particulier) :
      Code :
      1
      2
      3
      > a=15;
      > print("a=", a, ", which is a ", type(a), ".\n")
      a=15, which is a scalar.
    • Dans le même genre, on trouve également la fonction to_string() qui, comme son nom l'indique, transforme en chaîne de caractère l'argument qui lui est fourni :
      Code :
      1
      2
      3
      4
      > a=to_string(4i+5+2)
        7+4i
      > type(a)
        string
    • Et le meilleur pour la fin : la fonction evaluate() qui transforme la chaîne de caractère en argument en objet réel. Cette chaîne ne peut pas contenir de destruction ~une_variable ou de question ?une_variable, mais peux être constituée de multiples expressions a;12;"abc" (si une seule expression, le type de retour sera celui de cette expression, sinon le type de retour sera un vecteur contenant le résultat de chacune des expressions) :
      Code :
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      > evaluate("5+6")
        11
      > evaluate("abc")
        abc
      > type(_)
        variable
      > evaluate("\"abc\"")
        abc
      > type(_)
        string
      > evaluate("15;5+i")
        (15, 5+i)


    Le tout tient en 3000 lignes de code (commentaires et espaces compris), je trouve ça assez hallucinant...

    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v010 : [7z] (347Ko), [zip] (485Ko) (23h00, 13/10/2012)
    • *buntu 12.04 (32bit) v010 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (42Ko), [zip] (58Ko) (23h00, 13/10/2012)

  12. #12
    Membre expérimenté

    Homme Profil pro
    Doctorant en astrophysique
    Inscrit en
    juin 2007
    Messages
    365
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en astrophysique

    Informations forums :
    Inscription : juin 2007
    Messages : 365
    Points : 531
    Points
    531

    Par défaut

    Voici la version 0.11, avec au menu de gros changements au niveau de la gestion des vecteurs. En effet, les vecteurs ((1, 5, 13, ...)) ne peuvent maintenant plus être hétérogènes, car ils sont ils sont maintenant implémentés comme des vecteurs C++ (ou comme des tableaux en C), c'est à dire que les différentes valeurs sont arrangées de manières continue en mémoire. Cela permet d'avoir de bien meilleures performances (également parce qu'on ne doit vérifier le type du contenu qu'une seule fois, au lieu de le faire pour chaque valeur comme avant).

    J'ai du coup introduit une nouvelle notation avec des accolades ({"abc", 1, 12i, (5,6,7)}) qui permet de construire une "collection". Comme vous pouvez le voir, les collections peuvent vraiment contenir n'importe quoi, contrairement aux vecteurs. Elles sont moins performantes (il vaut mieux utiliser un vecteur quand c'est possible), mais peuvent aussi être bien pratiques pour faire transiter des données facilement.

    Ces deux types de conteneurs coexistent au sein du programme, et sont utilisés dans des situations différentes. En particulier, toutes les fonctions mathématiques (cos, etc.) sont surchargées pour prendre un vecteur de scalaire ou complexe en argument. Également, une bonne partie des fonctions de gestion des chaines de caractères (trim, etc.) sont aussi surchargées pour prendre un vecteur de chaine de caractères en argument. Mais il n'y a pas de règle automatique : pour qu'une fonction puisse être appelée sur un vecteur, il faut qu'une surcharge explicite existe. Ce n'est pas le cas des collections : si le premier argument d'une fonction est une collection (et qu'aucune surcharge explicite correspondante n'existe), alors la fonction est appelée sur chacun des éléments de la collection.
    Enfin, dans le cas de retours multiples (par exemple avec evaluate), les valeurs de retour sont stockées dans une collection, et non un vecteur.

    Outre ces différences, il est maintenant possible de créer des vecteurs à plusieurs dimensions (je ne montre pas d'exemple, mais tout ce qui suit est aussi valable pour les collections) :
    Code :
    1
    2
    > a = ((1,2,3), (4,5,6))
      (1, 2, 3, 4, 5, 6)
    À première vue il s'agit d'un vecteur comme un autre, sauf que :
    ... il s'agit bel et bien d'un vecteur multidimensionnel ! L'organisation en mémoire est strictement la même que celle d'un vecteur monodimensionnel, dans le sens où tous les éléments sont contigus en mémoire. On peut donc tout à fait parcourir ce vecteur comme un vecteur normal (on voit alors comment les différentes dimensions sont arrangées en mémoire) :
    Code :
    1
    2
    > print(a[0]," ",a[1]," ",a[2]," ",a[3])
    1 2 3 4
    ... mais on peut aussi indexer le vecteur avec plusieurs indices :
    Autre nouveauté, on peut demander à extraire un "intervalle" plutôt qu'une seule valeur en utilisant la notation a:b, où a représente la borne inférieure et b la borne supérieure (toutes deux inclusives) :
    Et ça marche aussi avec les chaînes de caractères :
    Code :
    1
    2
    > "abcdefgh"[3:5]
      "def"
    Un intervalle est un type à part entière, et il est possible de l'utiliser comme argument de certaines fonctions. On notera par exemple la fonction indgen qui génère une suite d'entiers consécutifs et stocke le tout dans un vecteur :
    Code :
    1
    2
    > indgen(5:12)
      (5, 6, 7, 8, 9, 10, 11, 12)
    ou encore la fonction size qui, appelée sur un intervalle, retourne le nombre total de valeurs entières entre les deux bornes (comprises) :
    Ces intervalles sont de mini collections : on peut appliquer des opérations dessus, accéder à chacune des bornes via (5:12)[0] et (5:12)[1], etc. On peut créer un intervalle hétérogène, même si ça n'a pas beaucoup d'intérêt à l'heure actuelle ("abc":12).
    Pour en finir sur les vecteurs, j'ai aussi ajouté une fonction replicate qui, comme son nom l'indique, créé un certain nombre de copie de son argument et en fait un vecteur :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    > b = replicate((1,2,3), 5);
    > size(b)
      (5,3)
    > b[0,0:2]
      (1,2,3)
    > b[1,0:-1]
      (1,2,3)
    > b[2,*]
      (1,2,3)
    Ces trois dernières lignes montrent au passage les différentes manières de demander "tout l'intervalle" : soit on spécifie explicitement l'indice de début (toujours 0) et celui de fin (ici 2), soit on demande de 0 à -1 (qui est interprété comme "-1 par rapport à la fin", donc le dernier élément), ou encore une notation bien pratique * qui est strictement équivalente à la ligne précédente.

    Quelques autres changements moins importants :
    • j'ai ajouté l'opérateur exposant ^ aux vecteurs (oubli),
    • j'ai supprimé les opérations ~ et ? qui compliquaient inutilement le parser, et j'en ai fait deux fonctions reset et help,
    • dans le même ordre d'idée, j'ai renommé la commande q (utilisée pour quitter l'application) en exit (semble être plus standard),
    • la gestion des types en interne se fait maintenant sur des identificateurs entiers plutôt que sur des chaînes de caractères (c'est bien plus rapide, et j'ai trouvé quelques astuces pour que ça ne soit pas plus pénible à faire évoluer),
    • une variable type void n'est pas affichée dans la console, mais en revanche une collection de void oui, c'est maintenant corrigé,
    • on peut personnaliser le prompteur > qui est affiché devant chaque ligne où l'utilisateur peut écrire (utiliser l'option -prompt="..."),
    • et enfin, on peut maintenant demander à l'exécutable de lire un fichier de code, de l'exécuter, puis de quitter sans interaction avec l'utilisateur (utiliser -c suivi du nom du/des fichier(s))


    Parmi les choses que j'aimerai ajouter pour la suite, les deux plus importantes sont :
    1. Autoriser plusieurs types numériques dans une même session (i.e. faire cohabiter des entiers, des flottants, avec différentes précisions). Il faut pour cela définir une marque particulière qui permette de distinguer un littéral entier d'un flottant (en C++ : 10 est un entier, 10.0 est un flottant double précision et 10.0f est un flottant simple précision, par exemple), mais aussi que j'harmonise la gestion des types numériques, en particulier que je définisse des règles de conversion implicite claires (exemple : on ne peut théoriquement indexer un vecteur qu'avec un entier, mais un flottant est convertible en entier, on peut donc écrire a[0.0]).
    2. Autoriser l'affectation de valeurs aux éléments des vecteurs/collections/chaînes de caractères. Ça risque d'être plus compliqué que prévu. Pour le moment, mon interpréteur ne dispose pas vraiment de notion de lvalue ("left" value : objet pouvant se trouver à gauche d'un signe égal) dans le sens où tous mes objets sont plus ou moins des temporaires. En effet, quand on écrit a[0], l'interpréteur génère une copie de l'élément dans la case 0 du tableau a. En somme, pour pouvoir implémenter ça, il faut que j'ajoute la notion de référence (au sens du C++).


    Binaires :
    Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
    • Windows XP (32bit) v011 : [7z] (297Ko), [zip] (438Ko) (02h30, 01/05/2013)
    • *buntu 12.04 (32bit) v011 : à venir...

    ... et sources :
    • Sources (multiplateforme) : [7z] (51Ko), [zip] (72Ko) (02h30, 01/05/2013)

Liens sociaux

Règles de messages

  • Vous ne pouvez pas créer de nouvelles discussions
  • Vous ne pouvez pas envoyer des réponses
  • Vous ne pouvez pas envoyer des pièces jointes
  • Vous ne pouvez pas modifier vos messages
  •