Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Atelier] P'tit langage

Craque ton slip !

    24 décembre 2009 à 2:11:42

    Ca m'a l'air sympa ce truc :)
    Si j'ai le temps (et le courage :D ) je ferai un petit truc en C :)

    Bravo à ceux qui ont fait l'atelier :)
    • Partager sur Facebook
    • Partager sur Twitter
      26 décembre 2009 à 14:22:33

      Bonjour tout le monde.
      Je m'intéresse trop à cet atelier, puisque je débute à peine avec la construction des langages de programmation donc j'ai attaqué un peu la théorie et je m y trouve pas mal mais je ne veux pas abuser sans faire un peu de pratique !
      Ce que j'ai déjà pigé c'est que la compilation se compose en deux parties qui sont:
      1/ L'analyse: qui constitue à la décomposition du programme source en ces constituants ainsi que créer une sorte de représentation intermédiaire de ce dernier, et déjà on distingue trois phases d'analyse:
      • Lexicale
      • Syntaxique
      • Sémantique

      2/ la synthèse: qui dit bref la construction du programme cible.

      J'ai lu le sujet en début, je m'attendais qu'en parle de l'élaboration d'un analyseur lexical d'abord mais je crois qu'on en a plutôt parlé sur l'analyseur syntaxique?
      Donc je coince déjà, je sais que l'analyse lexicale c'est le fait de lire le code source et le transformer en un série d'unités lexicales. Mais pratiquement comment ça se passe? un petit exemple de clarification me sera bien utile.
      Merci à l'auteur, et merci à ceux qui vont pouvoir me répondre.

      PS: Je sais que je peux trouver ça ailleurs, mais ce que je cherche c'est une brève explication :) .
      • Partager sur Facebook
      • Partager sur Twitter
        26 décembre 2009 à 16:38:04

        Nous avons déjà traité la question du parsing (analyse lexicale et syntaxique) pendant l'atelier Calculatrice. Si tu t'intéresses à cela, tu devrais aller voir et peut-être participer.

        Tu peux aussi trouver des exemples d'analyses dans les codes postés pour cet atelier.
        • Partager sur Facebook
        • Partager sur Twitter
          26 décembre 2009 à 22:05:18

          crazy_inf : typiquement, un compilateur est constitué grosso modo de sept étapes :

          - Analyse lexicale : on produit à partir d'une chaine d'entrée une suite d'unités lexicales. Ces unités lexicales comportent un "flag" qui indique leur nature (mot-clef if, identifiant, opérateur ==, etc.) et éventuellement un attribut si nécessaire (par exemple, l'unité lexicale de type "constante entière" possèdera logiquement un attribut de type int stockant la valeur de la constante). Un question récurrente est "en flag à part ou un flag général et en attribut ?". Exemple : au lieu de faire un flag pour chaque mot-clef, on aurait pu faire un flag "mot-clef" et mettre le mot-clef lui-même en attribut. Une méthode pour trancher consiste à se poser la question "quelle caractéristique de l'unité lexicale va nous intéresser pour l'analyse syntaxique ?". Exemple : si l'on prend une constante, est-il important de savoir que l'on a une constante ou que cette constante vaut 2 (par exemple) ? Ce qui nous intéressera est de savoir que l'on a une constante évidemment, on fera donc un flag "constante" et on stockera sa valeur en attribut. En revanche, si l'on a un mot-clef, est-il intéressant pour l'analyse syntaxique de savoir que l'on a un mot-clef ou que ce mot-clef est if (par exemple) ? if, évidemment ; on fera donc un flag par mot-clef. C'est comme ça que je procède et que procèdent les gens que je connais.

          - Analyse syntaxique : on produit à partir de la suite d'unités lexicales un arbre syntaxique abstrait. En gros, cette arbre représente la structure syntaxique du programme d'entrée. On y retrouve un découpage clair du programme en instructions, expressions, etc. Si l'on ne peut pas construire cet arbre à partir de l'entrée, le programme de départ est syntaxiquement incorrect.

          - Production de code intermédiaire : à partir de l'arbre syntaxique abstrait, on produit du code intermédiaire (entre le code source et le code cible), qui est typiquement bas niveau, assez proche de l'assembleur avec tout de même un certain niveau d'abstraction. C'est souvent pendant cette étape qu'on fait diverses vérifications sur l'AST comme la vérification statique des types ou les vérifications syntaxiques non-faites lors de l'analyse syntaxique (l'exemple classique est l'instruction "break" qui doit forcément se trouver dans une boucle ou un switch), etc.

          - Optimisation indépendante de la machine cible : on optimise le code intermédiaire. Pour ce faire, on cherche les sous-expressions communes et on "factorise", on élimine le code inutile, ou on fait ce qu'on appelle une "propagation de copies" (on élimine les affectations inutiles ; par exemple si l'on a "t1 = t0; i = t1;" et si t1 n'est jamais réutilisé plus tard, on peut simplement écrire "i = t0;", etc. D'une manière générale, on optimise ce qui peut être optimisé indépendamment de la machine cible, c'est-à-dire la machine sur laquelle on va faire tourner le code cible.

          - Production de code cible : on produit du code cible à partir du code intermédiaire. Il faut prendre garde à l'allocation des registres, à l'ordre d'évaluation des instructions, etc. En fait, tout dépendra de la machine cible, ça peut être très varié.

          - Optimisation du code cible : on cherche à profiter des capacités et caractéristiques de la machine cible et de ce qu'elle peut nous offrir pour optimiser le code cible déjà produit. On peut par exemple chercher à faire exécuter deux instructions en même temps (cf parallélisme), etc.

          Voilà en espérant que c'est plus clair vu qu'il me semble que tu cherchais à comprendre comment un compilateur était structurée. :)

          Pour l'analyse lexicale, on se sert de ce qu'on appelle un automate à états. Tu trouveras des articles très complets là-dessus sur internet, il suffit de chercher. En gros, tu définis les lexèmes de ton langage à l'aide d'expressions régulières. Ensuite tu convertis ces expressions régulières en automate à états. Ces automates sont des "accepteurs", c'est-à-dire qu'on leur donne une chaine en entrée, et ils disent "oui" ou "non", "oui cette chaine est acceptée par l'expression régulière de référence" ou "non cette chaine n'est pas acceptée". En écrivant une expression régulière pour définir un nombre décimal ([0..9]+.[0..9]*), et en faisant passer une chaine s à travers l'automate construit à partir de cette expression, on peut donc dire si s est un nombre décimal ou non. C'est typiquement ainsi qu'on reconnait les différentes unités dans une analyse lexicale.
          Pour implémenter une analyse lexicale, il ne faut pas foncer tête baissée. On commence papier-crayon, on imagine les automates, on définit les expressions régulières. Avant de se lancer dans l'implémentation, il faut être rigoureux et penser à chaque détail d'organisation de code, pour ne pas se retrouver noyé. Après pour la méthode, chacun fait un peu comme il veut, moi j'utilise souvent la spécialisation de template (je fais du C++) pour implémenter un automate, et ça peut ressembler à ceci :
          template < int s > struct state {};
          template <> struct state < 0 >
          {
             bool analyse(iterator begin, iterator end)
             {
                if(*begin == '"')
                   return state < 1 >::analyse(++begin, end);
                ...
             }
          };
          template <> struct state < 1 >
          {
             bool analyse(iterator begin, iterator end)
             { ... }
          };
          ...
          

          Et suivant l' "état", chaque spécialisation aura sa définition de la fonction membre analyse. C'est une manière de faire, je ne dis pas que c'est la meilleur. Ce n'est en aucun cas la plus concise. D'autres préfèreront tout rassembler dans une fonction, utiliser des boucles plutôt qu'une implémentation explicite des automates, etc.
          • Partager sur Facebook
          • Partager sur Twitter
            26 décembre 2009 à 22:44:24

            Moui. Ça m'a l'air très classique et un peu rigide comme méthodologie. Je ne suis pas convaincu qu'il soit vraiment intéressant par exemple de séparer l'analyse lexicale de l'analyse syntaxique quand on explique : après tout, il s'agit uniquement d'avoir un programme qui comprend la structure (AST) du texte du code source; la manière dont c'est fait dépend de l'implémentation, et la distinction lexicale/syntaxique n'est pas toujours pertinente).

            En pratique dès qu'on fait quelque chose d'un peu différent, on se retrouve avec d'autres méthodes (pas forcément de la compilation, on peut avoir de nombreux langages intermédiaires, plusieurs phases d'analyse, etc.).

            Enfin, le monde de la compilation comprend maintenant de nombreux outils bien éprouvés, et leur utilisation permet de se passer de l'implémentation manuelle de certaines de ces phases. Avec lex/yacc par exemple, est-il vraiment utile de détailler encore l'implémentation de l'analyse lexicale ? Même sans aller aussi haut niveau, on peut espérer qu'il a à disposition une bibliothèque d'expressions régulières, et qu'il ne va pas s'embêter à écrire ses automates à la main...
            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              27 décembre 2009 à 0:08:22

              shareman a à mon avis repris la structure du Dragon Book qui est identique. Le livre explique aussi l'utilisation d'outils spécialisés comme Lex et Yacc.

              Mais en effet, détailler ces étapes sur ce sujet n'était pas vraiment utile mais c'est une info intéressante tout de même.
              • Partager sur Facebook
              • Partager sur Twitter
                27 décembre 2009 à 23:14:28

                Bonsoir!
                Merci bien pour vos réponses qui m'ont bien clarifié l'histoire.
                En tout les cas, j'ai déjà commencé à plus faire travailler mes neurons, comme déjà signalé par "shareman" (merci) j'ai défini les lexèmes de mon p'tit langage, j'ai élaboré l'automate et écris l'algorithme correspondant.
                Et maintenant j'attaque la programmation. Mais bon la question que je me pose déjà c'est "Est ce que pratiquement l'analyse lexicale s'éxecute en parallèle avec l'analyse syntaxique?".

                • Partager sur Facebook
                • Partager sur Twitter
                  28 décembre 2009 à 0:02:02

                  Citation : crazy_inf

                  Et maintenant j'attaque la programmation. Mais bon la question que je me pose déjà c'est "Est ce que pratiquement l'analyse lexicale s'éxecute en parallèle avec l'analyse syntaxique?".


                  Tout ce qu'il faut garder en tête, c'est que l'analyse syntaxique a besoin de la sortie de l'analyse lexicale. Après, tu peux tout lexer en une fois, et envoyer une file (c'est la structure de données qui me semble la plus adapté dans l'immédiat) d'unités lexicales à l'analyseur syntaxique ; ou alors tu peux faire un petit système d' "évaluation paresseuse" : l'unité lexicale suivante n'est pas produite avant qu'on ait besoin de la connaitre, et dans ce modèle c'est donc à l'analyseur syntaxique de demander systématiquement les unités lexicales suivantes à l'analyseur lexical quand il en a besoin. La dernière méthode est plus stratégique : on économise de la mémoire dans tous les cas.

                  @ bluestorm : Comme toujours, je suis plutôt d'accord avec ce que tu avances. Mais faire la distinction analyse lexicale / analyse syntaxique est plus logique à mes yeux. Tout ce qui est régulier peut être décrit à l'aide de grammaire non-contextuelle et peut donc être intégré à l'analyse syntaxique sans trop de difficultés. Mais rien qu'au niveau de la sémantique par exemple (quand on observe la grammaire du langage ou qu'on se plonge dans l'implémentation), il est plus agréable et plus compréhensible de voir apparaitre des terminaux qui ont plus de sens qu'une chose définit par un assemblage de caractères dans la grammaire elle-même (keyword_else, à la place de 'e' 'l' 's' 'e' par exemple, où le passage de 'e' 'l' 's' 'e' à keyword_else serait le boulot du lexer).

                  Ensuite (et ça s'adresse aussi à PainKetchup), évidement qu'il existe des outils pour produire tout cela automatiquement, et il est judicieux de les utiliser. Mais est-ce vraiment bénéfique ici pour celui qui souhaite en savoir plus sur la compilation (crazy_inf) ? Je ne pense pas. Toi même bluestorm, tu as écrit ton parser P'tit langage à la main en OCaml, alors que tu as Ocamlyacc à disposition.

                  @ PainKetchup : Tu mentionnes le dragon book. Alors en effet, le livre enseigne l'utilisation de lex et de yacc, mais si tu as le livre sous la main ou si tu t'en souviens, remarque que ce n'est pas ça qui est mis en avant. Les auteurs prônent leur utilisation si l'apprentissage des techniques d'implémentation n'est pas le but, si l'on cherche à développer un langage rapidement, efficacement, simplement, etc. ou si la grammaire de notre langage ne permet pas l'utilisation de techniques facilement "implémentables" (je fais notamment allusion aux analyses LR). Le livre en soi détaille chaque algorithme et l'implémentation finale (une partie frontale de compilateur pour un mini langage C) qui est le résultat de la majeure partie de ce qui est enseigné dans le livre, et qui est présentée en détail en annexe, c'est du code entièrement tapé main, avec analyseur lexicale, analyseur syntaxique et producteur de code intermédiaire écrits à la main.
                  • Partager sur Facebook
                  • Partager sur Twitter
                    28 décembre 2009 à 0:14:10

                    shareman. Content de te voir.
                    • Partager sur Facebook
                    • Partager sur Twitter
                      28 décembre 2009 à 10:14:40

                      Je ne pense pas que "else" soit moins lisible que keyword_else, ou "("  foo ")" que LPAR foo RPAR.

                      J'ai écrit mon parser en utilisant un DSL spécialisé pour le parsing (pa_gram de camlp4), parce que le résultat est plus court et plus lisible qu'un parser utilisant ocamllex+ocamlyacc pour un langage LR, quand on connaît ces deux outils.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        28 décembre 2009 à 14:51:55

                        Salut !

                        Citation : bluestorm

                        J'ai écrit mon parser en utilisant un DSL spécialisé pour le parsing (pa_gram de camlp4), parce que le résultat est plus court et plus lisible qu'un parser utilisant ocamllex+ocamlyacc pour un langage LR, quand on connaît ces deux outils.


                        Ah là là il faudrait que tu songes à écrire un tuto sur camlp4. :);)
                        (oui je sais c'est une tâche irréalisable immense)

                        Cordialement,
                        Cacophrène
                        • Partager sur Facebook
                        • Partager sur Twitter
                          30 juillet 2010 à 18:08:01

                          Je me suis essayé à cet atelier et je dois avouer qu'il est très intéressant et que j'ai appris plein de choses.

                          Bref voici un exemple de programme dans "mon" p'tit langage :

                          \n 10 =;
                          
                          "------------" str 0 \n str;
                          "Computing primes up to : " str;
                          n in =;
                          
                          if(n 2 >=) {
                              2 out " " str;
                              
                              n 1 + [crible];
                              i 0 =;
                          
                              while(i n <=) {
                                  i [crible] 1 =;
                                  i i 1 + =;
                              }
                          
                              i 3 =;
                          
                              while(i i * n <=) {
                                  if(i [crible]) {
                                      i out " " str;
                          
                                      j i =;
                                      max n i / =;
                          
                                      while(j max <=) {
                                          i j * [crible] 0 =;
                                          j j 2 + =;
                                      }            
                                  }
                                  i i 1 + =;
                              }
                          
                              if(i 2 %) {
                                  min i =;
                              }
                          
                              else {
                                  min i 1 + =;
                              }
                          
                              i min =;
                          
                              while(i n <=) {
                                  if(i [crible]) {
                                      i out " " str;
                                  }
                          
                                  i i 2 + =;
                              }
                          }
                          
                          0 \n str "------------" str 0 \n str;


                          Ce qui nous donne :

                          alexis@alexis-desktop:~/Documents/Dév/C++/Interpreter/Debug$ ./Interpreter "/home/alexis/Bureau/primes.src" 
                          ------------
                          Computing primes up to : 300
                          2 3 4 5 6 7 8 10 11 12 13 14 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 
                          ------------


                          Quelques propriétés de ce langage :
                          • Il utilise une notation postfixée bien que les conditions/boucles (if/while) ne suive pas cette règle (pas très clair ?)
                          • Il y a au minimum un espace entre chaque opérateur
                          • Il n'y a pas besoin de déclarer une variable qui peut être un entier (exemple : abc 3 =), soit un tableau d'entier (exemple : en C on écrit abc[3] = 1 et dans mon p'tit langage 3 [abc] 1 =)
                          • Il y a deux opérateurs pour l'affichage:
                            • out : qui affiche la valeur sur le haut de la pile et la dépile
                            • str : qui affiche tout les valeurs de la pile jusqu'à arriver à 0 (à noter qu'on peut empiler une chaîne grâce à la notation "abc" qui empile 0 puis 'c' puis 'b' puis 'a'.)


                          Si quelqu'un est intéressé par les sources (qui ne sont pas très belles (pas assez factorisé notamment, écris les erreur sur std::cerr plutôt que de lancer des exceptions, passe plusieurs fois dans chaque bloc mais je ne vois pas trop comment optimiser ça) => ici (C++).
                          • Partager sur Facebook
                          • Partager sur Twitter
                            30 juillet 2010 à 23:29:17

                            Merci pour ta participation. On dirait que tu as d'abord commencé par un programme style "calculatrice", et que tu as ensuite bifurqué vers un langage complet.

                            Le principal reproche à faire à ton implémentation, c'est qu'elle ne sépare pas du tout la syntaxe (le parsing, etc.) et la sémantique (le sens des programmes, les règles d'évaluation) de ton langage : tout est mis ensemble et les opérations importantes (la définition de tes constructions) sont entrecoupées de "on enlève les espaces inutiles" et autres manipulations syntaxiques.

                            Tu devrais prendre soin de mieux séparer les deux, en passant par une structure de donnée intermédiaire qui représente ton programme avant évaluation. Cette structure de données est assez simple, il suffit de pouvoir représenter les while, les if, et les listes d'opérations RPN.


                            Un autre reproche que l'on peut te faire, c'est de ne gérer que des variables globales. Ça passe sur de petits exemples, mais il est vraiment intéressant de se pencher sur la question des variables locales, et de la façon dont on les implémente.
                            • Partager sur Facebook
                            • Partager sur Twitter
                              31 juillet 2010 à 0:20:29

                              Citation : bluestorm

                              Le principal reproche à faire à ton implémentation, c'est qu'elle ne sépare pas du tout la syntaxe (le parsing, etc.) et la sémantique (le sens des programmes, les règles d'évaluation) de ton langage : tout est mis ensemble et les opérations importantes (la définition de tes constructions) sont entrecoupées de "on enlève les espaces inutiles" et autres manipulations syntaxiques.



                              Je n'ai pas regardé le code de cerium50, mais en tout cas je ne pense pas qu'il faille en faire un reproche. On n'est pas obligé de passer par un AST comme tu le suggères pour séparer clairement syntaxe et sémantique. On peut se contenter de construire l'arbre implicitement en effectuant pendant l'analyse syntaxique les différentes règles et actions sémantiques (par exemple : se contenter d'un attribut synthétisé pour les expressions au lieu de construire tout un AST, ça peut être sympa).

                              Tout dépend de ce qu'il veut faire. Il peut vouloir produire une représentation bas-niveau de son code, ou l'interpréter. Dans ce dernier cas, il peut être intéressant de tout analyser d'abord pour éviter d'avoir à exécuter une partie du code pour s'apercevoir d'une erreur de syntaxe quelque part. Mais comme dit, souvent on se sert énormément des règles et actions sémantiques. Pour la représentation bas niveau (ou autre exemple : la traduction d'expressions infixées en postfixées), les actions sémantiques sont chouettes.

                              En ce qui concerne la suppression des caractères d'espacement, il s'agirait là plutôt de séparer analyse lexicale et analyse syntaxique (même si ce n'est pas toujours nécessaire).
                              • Partager sur Facebook
                              • Partager sur Twitter
                                31 juillet 2010 à 9:52:37

                                Je suggère que tu commences effectivement par regarder le code en question.

                                if(tok[0] == '[') { //let's go for array
                                    if(tok[tok.size() - 1] != ']') {
                                        std::cerr << "A ']' is missing." << std::endl;
                                        
                                        free();
                                        return 0;
                                    }
                                    
                                    if(!get(1, "[x] need an index.")) {
                                        free();
                                        return 0;
                                    }
                                    
                                    std::string name = tok.substr(1, tok.size() - 2);
                                    unsigned int index = *top();
                                    
                                    pop();
                                    
                                    std::vector< int > *arr =
                                        &(_array->operator [](name));
                                    
                                    if(arr->size() <= index) {
                                        arr->resize(index);
                                    }
                                    
                                    push(&(arr->operator [](index)), false);
                                }
                                


                                La lecture de son code est gênée par le fait que les opérations sémantiques sont entrecoupées de manipulations syntaxiques. J'en fait donc le reproche, et c'est normal.

                                Ta réponse se place à un niveau de généralité bien supérieur à ce qu'a fait cerium50. Ça ne la rend pas inutile (des gens utilisant effectivement des générateurs de parseurs avec actions sémantiques pourraient la trouver profitable), mais tout de même légèrement hors-sujet. Tu as déjà bien décrit (en particulier dans ce message très détaillé) l'outillage "classique" d'un compilateur, et je pense que ce point est bien passé.
                                Mais la plupart des gens qui abordent le sujet le font selon leurs connaissances, et sans utiliser d'outils supplémentaires; c'est du code "fait maison" qu'il faut traiter, et pas le projet lambda de l'étudiant de L2 en info qui a suivi un cours sur l'utilisation de lex et yacc. Je pense donc que des commentaires concrets sur le code qui a été écrit et des façons de l'améliorer sont plus pertinents.


                                Entre nous (pour les gens qui s'intéressent aux questions générales sur les outils de traitement syntaxique), je ne suis pas convaincu personnellement que les actions sémantiques soient une bonne chose. Elles peuvent être pratiques dans des cas très simples, mais comme elles lient la grammaire à une sémantique particulière elles rendent plus difficile la réutilisation et l'extension de la grammaire. Sur le long terme, la grande majorité des actions sémantiques se retrouvent à être isomorphes au terme parsé (parce qu'elles imitent la forme de l'AST, soit en le construisant effectivement sous forme de donnée, soit en appelant des fonctions de construction/utilisation en suivant la structure de la grammaire). Je ne dis pas qu'il faut se débarrasser complètement des actions sématiques, c'est une idée un peu extrême, mais à mon avis c'est une direction intéressante.


                                PS : une autre remarque, pas très importante, que j'avais oubliée. Dans ton code le motif foo = top(); pop() revient très souvent, et c'est normal. Quand on conçoit l'interface d'une pile, on décide en général que pop() renvoie aussi l'élément qu'il retire de la pile. Ainsi on peut écrire directement foo = pop();, qui est plus concis.
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  31 juillet 2010 à 12:29:40

                                  Le typage est une action sémantique, non ?
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    31 juillet 2010 à 12:35:54

                                    Par "action sémantique", shareman parle des bouts de code qui sont attachés à chaque production grammaticale dans certains outils de description de grammaire comme yacc :

                                    line   : exp ';' '\n'                   {printf ("result is %d\n", $1);}
                                            ;
                                     exp    : term                           {$$ = $1;}
                                            | exp '+' term                   {$$ = $1 + $3;}
                                            | exp '-' term                   {$$ = $1 - $3;}
                                            ;
                                     term   : factor                         {$$ = $1;}
                                            | term '*' factor                {$$ = $1 * $3;}
                                            | term '/' factor                {$$ = $1 / $3;}
                                            ;
                                     factor : number                         {$$ = $1;}
                                            | '(' exp ')'                    {$$ = $2;}
                                            ;


                                    Les fragments de code C entre accolades { } sont des "actions sémantiques". Dans cet exemple, au lieu de construire un AST, les actions sémantiques font directement le calcul.

                                    On peut se contorsionner pour vérifier le typage de certains langages directement dans des actions sémantiques de la grammaire (il y a eu une branche de recherche il y a plusieurs dizaines d'années de gens qui pensaient qu'on pourrait tout faire avec des grammaires expressives et leurs actions sémantiques), mais dès que le typage devient un peu intéressant c'est en général une mauvaise idée.
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      31 juillet 2010 à 15:27:30

                                      Citation

                                      On dirait que tu as d'abord commencé par un programme style "calculatrice", et que tu as ensuite bifurqué vers un langage complet.


                                      Bizarre, je suis parti dans l'idée d'un langage simple (pas autant que du BF certes) et c'est peut être ça qui donne cette impression car au fond que présente de plus ce langage ? Des if, et des while.

                                      Citation

                                      Le principal reproche à faire à ton implémentation, c'est qu'elle ne sépare pas du tout la syntaxe (le parsing, etc.) et la sémantique (le sens des programmes, les règles d'évaluation) de ton langage : tout est mis ensemble et les opérations importantes (la définition de tes constructions) sont entrecoupées de "on enlève les espaces inutiles" et autres manipulations syntaxiques.

                                      Tu devrais prendre soin de mieux séparer les deux, en passant par une structure de donnée intermédiaire qui représente ton programme avant évaluation. Cette structure de données est assez simple, il suffit de pouvoir représenter les while, les if, et les listes d'opérations RPN.


                                      POur tout ce qui est du "bricolage" au niveau des espaces, et accolades, je pensais à utiliser std::tr1::regex qui serait certainement plus propre/lisible/concis et qui permettrait aussi de rajouter quelques contraintes sur les noms de variables assez facilement (à l'heure actuelle, on peut avoir une variable qui s'appelle else, ou bien +[@^ ce qui ne semble pas très rigoureux).

                                      Pour ce qui est de l'AST il est vrai que ça présente de nombreux avantages, notamment d'éviter de "reparser" tout ce qui se trouve entre {} à chaque fois, il suffirait de le réévaluer (niveau performance ça doit être assez intéressant).

                                      Citation

                                      c'est de ne gérer que des variables globales.


                                      Là je ne peux qu'être d'accord, il faut juste que je trouve une manière élégante de gérer la portée des variables. (j'ai bien une idée, -pour les entiers par exemples- avoir deux std::map< std::string, int* >, une globale que l'on a via le constructeur privé, et une locale, et quand on passe à un bloc plus "petit" on fusionne les deux.).

                                      Citation

                                      PS : une autre remarque, pas très importante, que j'avais oubliée. Dans ton code le motif foo = top(); pop() revient très souvent, et c'est normal. Quand on conçoit l'interface d'une pile, on décide en général que pop() renvoie aussi l'élément qu'il retire de la pile. Ainsi on peut écrire directement foo = pop();, qui est plus concis.


                                      Ça ça vient du fait que RPN est une "surcouche" de std::stack dont j'ai gardé les même méthodes.
                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        9 août 2010 à 12:23:12

                                        salut,

                                        ça fait longtemps que personne n'a posté, mais tant pis: j'avais vu l'atelier avant de partir en vac' et j'ai codé pendant.

                                        j'ai fait un interpréteur P'tit langage en C. pour le moment il gère le calcul d'expression mathématique (+, -, /, *) sur les entiers (dû à la difficulté de parser en C je gère pas les nombres à virgule). ainsi que la déclaration de variable (numérique entière encore une fois).
                                        Cependant, (+ (/ 1 3) 7) devrait rendre 7.333333.

                                        Pour mon implémentation, on a les 'statement' qui se suivent et chaque 'statement' est exploité. La fonction exploit(statement) va trouver quelle opération est le 'statement' (add, sub...) et appelé la fonction op_Add(statement) ou op_Let(statement) ou ... dans chaque fonction op_* on cherche si on a un statement et on l'exploite a son tour: l'arbre d'évaluation est implicite.

                                        précisions:
                                        • la fonction readSource met la source en forme mais c'est très faible comme mise en forme, elle ne détecte pas les erreurs de syntaxe. Il faut donc lui passer un code presque parfait.
                                        • le 'globalContext' est le contexte global :waw: , il ne contient que 100 variables (à remplacer par une structure extensible) et est unique (pas de contexte local), pour le contexte local, j'ai pas trouver de structure efficace.
                                        • il doit y avoir une ou deux fuite de mémoire, allocation manuelle :pirate:


                                        voila le code:

                                        main.c
                                        #include <stdio.h>
                                        #include <stdlib.h>
                                        #include <string.h>
                                        #include <math.h>
                                        #include "constant.h"
                                        #include "operation.h"
                                        
                                        int readSource(FILE* source, char** dest);
                                        int extractStatement(char* programme,char** statement, int *type);
                                        int exploit(char* statement);
                                        void initContext();
                                        
                                        
                                        
                                        int main(int argc, char* argv[])
                                        {
                                            char* sourceFile = NULL;
                                            char* programme = NULL;
                                            char* statement = NULL;
                                            FILE* source = NULL;
                                            initContext();
                                            int statementLength = 1, position = 0, type;
                                            if(argc == 2)
                                            {
                                                sourceFile = argv[1];
                                                printf("source file: %s\n", sourceFile);
                                            }
                                            else
                                                printf("no source file specified\n");
                                            source = fopen(sourceFile, "r");
                                            if(source== NULL)
                                                printf("specified file not found\n");
                                            readSource(source, &programme);
                                            statementLength = extractStatement(programme + position, &statement, &type);
                                            while(statement)
                                            {
                                        
                                                printf("res>> %d\n", exploit(statement));
                                                free(statement);
                                                statement = NULL;
                                                position += statementLength;
                                                statementLength = extractStatement(programme + position, &statement, &type);
                                            }
                                            return 0;
                                        }
                                        
                                        
                                        int readSource(FILE* source, char** dest)
                                        {
                                            char c, pre = '\0';
                                            int i = 0;
                                            fseek(source, 0, 2);  /*2 go to the end of file*/
                                            *dest = calloc((ftell(source)+1),sizeof(char));/*ftell gives us the file length*/
                                            rewind(source);
                                            while((c = fgetc(source)) != EOF)
                                            {
                                                if(c == ' ' && pre == ' ')  //suppression des espaces superflux
                                                    continue;
                                                else if(c == '\n')
                                                    (*dest)[i] = ' ';
                                                else
                                                {
                                                    (*dest)[i] = c;
                                                    i++;
                                                }
                                                pre = c;
                                            }
                                            return 1;
                                        }
                                        
                                        int extractStatement(char* programme,char** statement, int *type)
                                        {
                                            int nbOfPar = 0, i = 1, statementLength = 0;
                                            if(programme[0] == '(')  //si on a un statement (commence par un '(')
                                            {
                                                nbOfPar = 1;
                                                while(nbOfPar != 0)
                                                {
                                                    if(programme[i] == ')') nbOfPar--;
                                                    else if(programme[i] == '(') nbOfPar++;
                                                    else if(programme[i] == '\0') return 0; //ici on trouve la longueur du statement
                                                    i++;
                                                }
                                                (*statement) = calloc(i+1, sizeof(char));
                                                statementLength = i;
                                                while(i)
                                                    (*statement)[i] = programme[--i]; //on la copie
                                                *type = STATEMENT;
                                                return statementLength;
                                            }
                                            if(programme[0] >= 48 && programme[0] <= 58) //si on a un chiffre
                                            {
                                                int i = 0, j = 0;
                                                while(programme[i] >= 48 && programme[i]<= 58)
                                                {
                                                    i++;
                                                }
                                                statementLength = i;
                                                j = i;
                                                (*statement) = calloc(i+1, sizeof(char));
                                                while(i)
                                                {
                                                    (*statement)[--j] = programme[--i];
                                                }
                                        
                                                *type = NUMBER;
                                                return statementLength;
                                            }
                                            if((programme[0] >= 65 && programme[0] <= 90) ||(programme[0] >= 97 && programme[0] <= 122)) //si c'est un nom de variable (des lettres uniquement)
                                            {
                                        
                                                int i = 0, j = 0;
                                                while((programme[i] >= 65 && programme[i] <= 90) ||(programme[i] >= 97 && programme[i] <= 122))
                                                {
                                                    i++;
                                                }
                                                statementLength = i;
                                                j = i;
                                                (*statement) = calloc(i+1, sizeof(char));
                                                while(i)
                                                {
                                                    (*statement)[--j] = programme[--i];
                                                }
                                        
                                                *type = LITTERAL;
                                                return statementLength;
                                            }
                                            return 0;
                                        }
                                        
                                        int exploit(char* statement)
                                        {
                                            int res = 0, i = 0;
                                            char c = statement[1];
                                            while(c == ' ') c = statement[i++];//on trouve l'opération et on appelle op_*
                                            if(c == '+')
                                                res = op_Add(statement);
                                            else if(c == '-')
                                                res = op_Sub(statement);
                                            else if(c == '*')
                                                res = op_Mul(statement);
                                            else if(c == '/')
                                                res = op_Div(statement);
                                            else if(c == 'l' && statement[i+2] == 'e' && statement[i+3] == 't' && statement[i+4] == ' ')
                                                res = op_Let(statement, globalContext);
                                            else return 0;
                                            return res;
                                        }
                                        
                                        void initContext()
                                        {
                                            int i = 0;
                                            globalContext = malloc(sizeof(context));
                                            globalContext->length  = 0;
                                            while(i<100)
                                            {
                                                globalContext->var[i++] = NULL;
                                            }
                                        }
                                        

                                        operation.h
                                        #ifndef OPERATION
                                        #define OPERATION
                                        
                                        
                                        
                                        int op_Add(char* stat);
                                        int op_Sub(char* stat);
                                        int op_Div(char* stat);
                                        int op_Mul(char* stat);
                                        int op_Let(char* stat, context* c);
                                        
                                        #endif
                                        

                                        operation.c
                                        #include "constant.h"
                                        #include <stdio.h>
                                        #include <stdlib.h>
                                        #include <math.h>
                                        #include <string.h>
                                        
                                        int op_Add(char* stat)
                                        {
                                            int tmp = 0, offset = 3, res = 0, len = 0;
                                        
                                            int type = 0;
                                            char* statement;
                                            do
                                            {
                                                int i = 0;
                                                tmp = extractStatement(stat+offset, &statement, &type); // on commence après le signe de l'opération (offset)
                                                len = tmp-1;
                                                if(type == STATEMENT) // si on a un statement on l'exploit
                                                    res += exploit(statement);
                                                else if(type == LITTERAL)// si c'est un litteral (variable) on le retrouve
                                                {
                                                    while(i<globalContext->length && !strcmp(statement, globalContext->var[i++]->name));
                                                    i--;
                                                    if(i == globalContext->length)
                                                    {
                                                        printf("undeclared var near:%s\n", stat);
                                                        return 0;
                                                    }
                                                    else
                                                        res += globalContext->var[i]->value;
                                                }
                                                else// enfin, on le converti en chiffre (int)
                                                {
                                                    while(len+1)
                                                    {
                                                        res += (statement[len]-48) * pow(10, i);
                                                        i++;
                                                        len--;
                                                    }
                                                }
                                                offset += tmp;
                                                while(stat[offset] == ' ')
                                                    offset++;
                                            }while(stat[offset] != ')'); // et on fait ça tant qu'on a pas fini
                                            return res;
                                        }
                                        
                                        /*
                                        pour op_Sub et les autres on fait apreil, excépté qu'il faut faire gaffe a pas faire -1 -2 pour (- 1 2)
                                        */
                                        int op_Sub(char* stat)
                                        {
                                            int tmp = 0, offset = 3, res = 0, len = 0, first = 1;
                                            int type = 0;
                                            char* statement;
                                            do
                                            {
                                                int i = 0;
                                                tmp = extractStatement(stat+offset, &statement, &type);
                                                len = tmp-1;
                                                if(type == STATEMENT && first)
                                                    res = exploit(statement);
                                                else if(type == STATEMENT && !first)
                                                    res -= exploit(statement);
                                                else if(type == LITTERAL && first)
                                                {
                                                    while(i<globalContext->length && !strcmp(statement, globalContext->var[i++]->name));
                                                    i--;
                                                    if(i == globalContext->length)
                                                    {
                                                        printf("undeclared var near:%s\n", stat);
                                                        return 0;
                                                    }
                                                    else
                                                        res = globalContext->var[i]->value;
                                                }
                                                else if(type == LITTERAL && !first)
                                                {
                                                    while(i<globalContext->length && !strcmp(statement, globalContext->var[i++]->name));
                                                    i--;
                                                    if(i == globalContext->length)
                                                    {
                                                        printf("undeclared var near:%s\n", stat);
                                                        return 0;
                                                    }
                                                    else
                                                        res -= globalContext->var[i]->value;
                                                }
                                                else
                                                {
                                                    while(len+1)
                                                    {
                                                        if(first)
                                                            res += (statement[len]-48) * pow(10, i);
                                                        else
                                                            res -= (statement[len]-48) * pow(10, i);
                                                        i++;
                                                        len--;
                                                    }
                                                }
                                                first = 0;
                                                offset += tmp;
                                                while(stat[offset] == ' ')
                                                    offset++;
                                            }while(stat[offset] != ')');
                                            return res;
                                        }
                                        int op_Div(char* stat)
                                        {
                                            int tmp = 0, offset = 3, res = 0, len = 0, first = 1, op = 0;
                                            int type = 0;
                                            char* statement;
                                            do
                                            {
                                                int i = 0;
                                                tmp = extractStatement(stat+offset, &statement, &type);
                                                len = tmp-1;
                                                if(type == STATEMENT && first)
                                                    res = exploit(statement);
                                                else if(type == STATEMENT && !first)
                                                    op = exploit(statement);
                                                else if(type == LITTERAL && first)
                                                {
                                                    while(i<globalContext->length && !strcmp(statement, globalContext->var[i++]->name));
                                                    i--;
                                                    if(i == globalContext->length)
                                                    {
                                                        printf("undeclared var near:%s\n", stat);
                                                        return 0;
                                                    }
                                                    else
                                                        res = globalContext->var[i]->value;
                                                }
                                                else if(type == LITTERAL && !first)
                                                {
                                                    while(i<globalContext->length && !strcmp(statement, globalContext->var[i++]->name));
                                                    i--;
                                                    if(i == globalContext->length)
                                                    {
                                                        printf("undeclared var near:%s\n", stat);
                                                        return 0;
                                                    }
                                                    else
                                                        op = globalContext->var[i]->value;
                                                }
                                                else
                                                {
                                                    while(len+1)
                                                    {
                                                        if(first == 1)
                                                            res += (statement[len]-48) * pow(10, i);
                                                        else
                                                            op += (statement[len]-48) * pow(10, i);
                                                        i++;
                                                        len--;
                                                    }
                                                }
                                                if(!first && op != 0)
                                                    res /= op;
                                                else if (!first && op == 0)
                                                {
                                                    printf("ERROR: divide by zero near: %s\n", stat);
                                                    return 0;
                                                }
                                                op = 0;
                                                first = 0;
                                                offset += tmp;
                                                while(stat[offset] == ' ')
                                                    offset++;
                                            }while(stat[offset] != ')');
                                            return res;
                                        }
                                        int op_Mul(char* stat)
                                        {
                                            int tmp = 0, offset = 3, res = 0, len = 0, first = 1, op = 0;
                                            int type = 0;
                                            char* statement;
                                            do
                                            {
                                                int i = 0;
                                                tmp = extractStatement(stat+offset, &statement, &type);
                                                len = tmp-1;
                                                if(type == STATEMENT && first)
                                                    res = exploit(statement);
                                                else if(type == STATEMENT && !first)
                                                    op = exploit(statement);
                                                else if(type == LITTERAL && first)
                                                {
                                                    while(i<globalContext->length && !strcmp(statement, globalContext->var[i++]->name));
                                                    i--;
                                                    if(i == globalContext->length)
                                                    {
                                                        printf("undeclared var near:%s\n", stat);
                                                        return 0;
                                                    }
                                                    else
                                                        res = globalContext->var[i]->value;
                                                }
                                                else if(type == LITTERAL && !first)
                                                {
                                                    while(i<globalContext->length && !strcmp(statement, globalContext->var[i++]->name));
                                                    i--;
                                                    if(i == globalContext->length)
                                                    {
                                                        printf("undeclared var near:%s\n", stat);
                                                        return 0;
                                                    }
                                                    else
                                                        op = globalContext->var[i]->value;
                                                }
                                                else
                                                {
                                                    while(len+1)
                                                    {
                                                        if(first == 1)
                                                            res += (statement[len]-48) * pow(10, i);
                                                        else
                                                            op  += (statement[len]-48) * pow(10, i);
                                                        i++;
                                                        len--;
                                                    }
                                                }
                                                if(!first)
                                                    res *= op;
                                                op = 0;
                                                first = 0;
                                                offset += tmp;
                                                while(stat[offset] == ' ')
                                                    offset++;
                                            }while(stat[offset] != ')');
                                            return res;
                                        }
                                        
                                        int op_Let(char* stat, context *c)
                                        {
                                            int i = 0, j = 0, k = 0, type = 0, len = 0;
                                            char *statement = NULL;
                                            while(stat[5 + i++] != ' '); // on va au nom de variable(5 pour '(let ')
                                            variable *x = malloc(sizeof(variable));
                                            x->name = calloc(i+1, sizeof(char));
                                            for(j = 0; j<i-1; j++)
                                                x->name[j]= stat[j+5];
                                            len = extractStatement(stat+i+5, &statement, &type); // puis on prend la valeur
                                            if(type == NUMBER)
                                                while(len)
                                                {
                                                    x->value += (statement[len-1]-48) * pow(10, k);
                                                    len--;
                                                    k++;
                                                }
                                            else if(type == STATEMENT)
                                                x->value = exploit(statement);
                                            c->var[c->length] = x;
                                            c->length++;
                                            return x->value;
                                        }
                                        

                                        constant.h
                                        #define STATEMENT 1
                                        #define NUMBER 2
                                        #define STRING 3
                                        #define LITTERAL 4
                                        
                                        typedef struct variable{
                                            char* name;
                                            int value;
                                            }variable;
                                        
                                        typedef struct context{
                                            int length;
                                            variable *var[100];
                                            }context;
                                        
                                        context* globalContext;
                                        



                                        to-do vite:
                                        • les opérateurs booléens
                                        • NETTOYER LE CODE (à faire très vite)

                                        to-do après:
                                        • les conditions
                                        • les fonctions (procédure en fait)

                                        to-do c'est pas urgent
                                        • contexte local
                                        • analyseur syntaxique/ gestion efficace des erreurs
                                        • je doit oublier quelque chose


                                        voila pour moi.

                                        Hedi
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          27 novembre 2011 à 0:40:10

                                          Salut,
                                          J'ai commencé récemment à m'intéresser à l'analyse syntaxique et j'ai donc essayé d'implémenter mon mini-lisp. Pour le moment l'interpréteur est assez basique et ne gère que les opérations arithmétiques, les "define" et la structure conditionnelle (je préfère avoir des critiques sur le code avant d'attaquer des constructions plus avancées).
                                          Le parser fonctionne d'un manière classique : il prend en argument une liste de caractères et retourne un couple (résultat, restant à lire) en cas de succès et None en cas d'echec.

                                          AST du langage :

                                          trait Instruction
                                          
                                            case class Program(insts: List[Instruction])
                                          
                                            case class Literal[α](v: α) extends Instruction
                                          
                                            case class UnOp(op: Char, inst: Instruction) extends Instruction
                                          
                                            case class BinOp(op: Char,
                                                             inst1: Instruction,
                                                             inst2: Instruction) extends Instruction
                                          
                                            case class If(cond: Instruction,
                                                          thenInst: Instruction,
                                                          elseInst: Instruction) extends Instruction
                                          
                                            case class Print(inst: Instruction) extends Instruction
                                          
                                            case class Define(name: Literal[String], value: Instruction) extends Instruction
                                          


                                          Quelques définitions peu importantes :

                                          type Parser = List[Char] => Option[(Instruction, List[Char])]
                                          
                                            def skipSpaces(s: List[Char]): List[Char] =
                                              s.dropWhile(_.isWhitespace)
                                          
                                            def unOp(c: Char) = List('~', '!') contains c
                                          
                                            def arithOp(c: Char) = List('+', '*', '-', '/') contains c
                                          
                                            def logOp1(c: Char) = List('<', '>', '=') contains c
                                          
                                            def logOp2(c: Char) = List('&', '|') contains c
                                          
                                            def logOp(c: Char) = logOp1(c) || logOp2(c)
                                          
                                            def binOp(c: Char) = arithOp(c) || logOp(c)
                                          
                                            def isOp(c: Char) = unOp(c) || binOp(c)
                                          


                                          Les fonctions de parsing :

                                          def parseInt: List[Char] => (Literal[Int], List[Char]) = l => {
                                              val (l1, l2) = skipSpaces(l).span(_.isDigit)
                                              (Literal(l1.foldLeft(0)((acc, x) => acc * 10 + x.asDigit)), skipSpaces(l2))
                                            }
                                          
                                            // parse un identificateur
                                            def parseId: List[Char] => (Literal[String], List[Char]) = l => {
                                              val (l1, l2) = skipSpaces(l).span(_.isLetterOrDigit)
                                              (Literal(l1.foldLeft("")(_ + _)), skipSpaces(l2))
                                            }
                                          
                                            def parseInst: Parser = s => skipSpaces(s) match {
                                              case Nil => None
                                              case '(' :: s1 => skipSpaces(s1) match {
                                                case op :: s2 if isOp(op) => parseInst(s2) match {
                                                  case None => None
                                                  case Some((inst, ')' :: r)) if unOp(op) =>
                                                    Some((UnOp(op, inst), skipSpaces(r)))
                                                  case Some((inst1, r)) => parseInst(r) match {
                                                    case Some((inst2, ')' :: r)) if binOp(op) =>
                                                      Some((BinOp(op, inst1, inst2), skipSpaces(r)))
                                                    case _ => None
                                                  }
                                                  case _ => None
                                                }
                                                case 'p' :: 'r' :: 'i' :: 'n' :: 't' :: xs => parseInst(xs) match {
                                                  case Some((inst, ')' :: r)) => Some((Print(inst), skipSpaces(r)))
                                                  case _ => None
                                                }
                                                case 'i' :: 'f' :: xs => parseInst(xs) match {
                                                  case None => None
                                                  case Some((cond, r)) => parseInst(r) match {
                                                    case None => None
                                                    case Some((inst1, r1)) => parseInst(r1) match {
                                                      case Some((inst2, ')' :: r2)) => Some((If(cond, inst1, inst2), skipSpaces(r2)))
                                                      case _ => None
                                                    }
                                                  }
                                                }
                                                case 'd' :: 'e' :: 'f' :: 'i' :: 'n' :: 'e' :: xs =>
                                                  val (name, r) = parseId(xs)
                                                  parseInst(r) match {
                                                    case Some((inst, ')' :: r)) => Some((Define(name, inst), skipSpaces(r)))
                                                    case _ => None
                                                  }
                                                case _ => None
                                              }
                                              case c :: s1 if c.isDigit => Some(parseInt(s))
                                              case c :: s1 if c.isLetter => Some(parseId(s))
                                              case _ => None
                                            }
                                          
                                            def parseProg(s: String): Program = {
                                              var l = List.empty[Instruction]
                                              var i = 1
                                              var beg = 0
                                              var end = 0
                                              var p = false
                                              val s1 = skipSpaces(s.trim().toList)
                                              if (s1(0) != '(')
                                                throw new Exception("Erreur")
                                              for (c <- s1.tail) {
                                                end += 1
                                                if (c == '(') {
                                                  i += 1
                                                  p = false
                                                }
                                                else if (c == ')')
                                                  i -= 1
                                                if (i == 0 && !p) {
                                                  l ::= (parseInst(s1.drop(beg).take(end + 1)) match {
                                                    case Some((inst, _)) => inst
                                                    case _ => throw new Exception("Erreur")
                                                  })
                                                  beg = end + 1
                                                  p = true
                                                }
                                              }
                                              if (beg != end + 1)
                                                throw new Exception("Erreur")
                                              else
                                                Program(l)
                                            }
                                          


                                          (La fonction parseProg est hyper lourde mais elle n'est pas très intéressante, peut être que je peut rendre le code moins itératif en utilisant un accumulateur et un peu de récursivité ..)

                                          Et finalement la fonction d'évaluation ainsi que la fonction run :

                                          def evalInst(context: scala.collection.mutable.Map[String, Any], inst: Instruction): Any = inst match {
                                              case Literal(x: Int) => x
                                              case Literal(x: String) =>
                                                if (context.contains(x))
                                                  context(x)
                                                else
                                                  throw new Exception("Variable introuvable: " + x)
                                              case UnOp('~', e) => evalInst(context, e) match {
                                                case v: Int => -v
                                                case _ => new Exception("Erreur de type")
                                              }
                                              case UnOp('!', e) => evalInst(context, e) match {
                                                case b: Boolean => !b
                                                case _ => new Exception("Erreur de type")
                                              }
                                              case BinOp(op, e1, e2) if arithOp(op) =>
                                                val m: Map[Char, (Int, Int) => Int] = Map(
                                                  '+' -> (_ + _),
                                                  '-' -> (_ - _),
                                                  '*' -> (_ * _),
                                                  '/' -> (_ / _)
                                                )
                                                (evalInst(context, e1), evalInst(context, e2)) match {
                                                  case (e1: Int, e2: Int) => m(op)(e1, e2)
                                                  case _ => new Exception("Erreur de type")
                                                }
                                              case BinOp(op, e1, e2) if logOp1(op) =>
                                                val m: Map[Char, (Int, Int) => Boolean] = Map(
                                                  '<' -> (_ < _),
                                                  '>' -> (_ > _),
                                                  '=' -> (_ == _)
                                                )
                                                (evalInst(context, e1), evalInst(context, e2)) match {
                                                  case (e1: Int, e2: Int) => m(op)(e1, e2)
                                                  case _ => new Exception("Erreur de type")
                                                }
                                              case BinOp(op, e1, e2) if logOp2(op) =>
                                                val m: Map[Char, (Boolean, Boolean) => Boolean] = Map(
                                                  '&' -> (_ && _),
                                                  '|' -> (_ || _)
                                                )
                                                (evalInst(context, e1), evalInst(context, e2)) match {
                                                  case (e1: Boolean, e2: Boolean) => m(op)(e1, e2)
                                                  case _ => new Exception("Erreur de type")
                                                }
                                              case If(cond, e1, e2) => evalInst(context, cond) match {
                                                case b: Boolean => if (b) evalInst(context, e1) else evalInst(context, e2)
                                                case n: Int => if (n != 0) evalInst(context, e1) else evalInst(context, e2)
                                              }
                                              case Print(e) => println(evalInst(context, e))
                                              case Define(Literal(n), e) => context(n) = evalInst(context, e)
                                              case _ => throw new Exception("Syntax error")
                                            }
                                          
                                            def run(prg: String) {
                                              val ctxt = scala.collection.mutable.Map.empty[String, Any]
                                              parseProg(prg).insts.reverse.map(evalInst(ctxt, _))
                                            }
                                          


                                          Normalement tout fonctionne correctement (même si la gestion des erreurs reste peu précise).
                                          Exemple:

                                          run("""
                                              (define x 3)
                                              (define y (* x 3))
                                          
                                              (if
                                                  (< x y)
                                                  (print x)
                                                  (print y))
                                          
                                              (define cond1 (> x 0))
                                              (define cond2 (< y x))
                                              (if
                                                  (& cond1 cond2)
                                                  (print 6)
                                                  (print 8))
                                              """)
                                          

                                          3
                                          8


                                          Ce qui me dérange le plus dans mon parser est les filtrages de motifs successifs dans la fonction parseInst. On peut éviter tout ça en remplaçant l'utilisation de Option par des exceptions mais je pense qu'on peut trouver une meilleure solution (à vous d'illuminer mon esprit !).

                                          <HS>

                                          J'ai voulu m'amuser en utilisant l'AST et la fonction parseInst pour créer un DSL pour le langage crée. Le fait que les "arguments" en Lisp sont séparés par des espaces complexifie le DSL puisqu'on doit remplacer les espaces par quelque chose ("__" dans mon cas).
                                          Voici un exemple d'utilisation du DSL :

                                          val n = 3
                                          
                                          ~('define __ 'x __ ('* __ n __ n))
                                          ~('print __ 'x)
                                          ~('if __ ('> __ 'x __ 2) __ 
                                               ('print __ 2) __ 
                                               ('print __ 'x)))
                                          


                                          9
                                          2


                                          Et le code, si jamais il intéresse quelqu'un :

                                          object Part {
                                              val context = scala.collection.mutable.Map.empty[String, Any]
                                            }
                                          
                                            trait Part {
                                              def __(that: Part) = (this, that) match {
                                                case (Exp(l1), _) => Exp(l1 ++ List(that))
                                                case _ => Exp(List(this, that))
                                              }
                                          
                                              def toInstr: Instruction = this match {
                                                case Sym(x) => Literal(x.toString)
                                                case IntL(v) => Literal(v)
                                                case Exp(List(Sym(op), e1, e2)) if binOp(op.toString().apply(1)) =>
                                                  BinOp(op.toString().apply(1), e1.toInstr, e2.toInstr)
                                                case Exp(List(Sym(op), e)) if unOp(op.toString().apply(1)) =>
                                                  UnOp(op.toString().apply(1), e.toInstr)
                                                case Exp(List(Sym('define), Sym(x), e)) => Define(Literal(x.toString), e.toInstr)
                                                case Exp(List(Sym('print), e)) => Print(e.toInstr)
                                                case Exp(List(Sym('if), c, e1, e2)) => If(c.toInstr, e1.toInstr, e2.toInstr)
                                                case _ => throw new Exception("Erreur")
                                              }
                                              def unary_~ = evalInst(Part.context, this.toInstr)
                                            }
                                          
                                            case class Sym(s: Symbol) extends Part
                                          
                                            case class IntL(v: Int) extends Part
                                          
                                            case class Exp(l: List[Part]) extends Part
                                          
                                            implicit def symbolToSym(s: Symbol): Sym = Sym(s)
                                          
                                            implicit def intToIntL(v: Int): IntL = IntL(v)
                                          
                                          </HS>

                                          Voilà, toutes les remarques et critiques sont les bienvenues. :)
                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            27 novembre 2011 à 18:19:10

                                            Je trouve qu'il y a trop de redondances dans ton code que tu aurais pu repérer et éliminer. Je pense à :

                                            - les skipSpaces partout pendant le parsing

                                            - les new Exception("Erreur de type") partout pendant l'évaluation (et pourquoi y a-t-il des throw dans certains cas et pas d'autres ?)

                                            Sinon, plus généralement.

                                            1. la structure générale de l'AST est correcte, mais ce n'est pas une bonne idée à mon avis de stocker les opérateurs comme des Char; ce n'est pas une représentation sémantique. Tu devrais te faire des énumération pour tous les opérateurs définies par ton langage.

                                            2. Toujours dans le parser, l'idée d'utiliser Literal[String] pour représenter des *variables* est atroce. Un Literal[String] ne peut être qu'une chaîne littérale. Il te faut un cas à part "variable".

                                            3. Les fonctions de parsing ont une tête assez patibulaire. En plus des répétitions de skipSpace déjà mentionnée, tu as un problème de factorisation au niveau des options: tu as des case None => None tout partout qui ne devraient pas être répétés.

                                            4. La fonction dont tu gères les parenthèses pendant le parsing n'est pas optimale. Tu devrais pouvoir gérer la parenthèse ouvrante et la parenthèse fermante au même endroit du code (tant que ta syntaxe est bien parenthésée); actuellement tu gères une fois la parenthèse ouvrante, et de nombreuses fois la parenthèse fermante.

                                            5. Je ne comprends pas ce qu'est sensé faire parseProg et je n'ai pas envie de lire ce code.

                                            6. Le fait d'utiliser une association mutable pour les environnements va te poser des problèmes sémantiques importants. À tous les coups tu as du binding dynamique et non du binding statique. Comment se comporte par exemple le code suivant (où j'utilise (prog instr instr instr ...) pour la syntaxe concrète d'un programme) ?

                                            (prog
                                                 (define x 3)
                                                 (define y (prog
                                                   (define x 4)
                                                   (+ x x)))
                                                 (+ x y))


                                            7. Ton DSL n'est franchement pas convaincant. Il est beaucoup plus repoussant à utiliser que le langage de base. Si tu veux un DSL correctement intégré à Scala, il faut que tu écrives du code qui construise le programme à un niveau sémantique adapté. Là tu cherches à écrire le programme comme il est parsé, ça n'est pas le bon niveau d'abstraction. On ne veut pas écrire ~('define ___ 'x ___ ('* ___ n ___ n)), les séparateurs autour de 'x n'ont pas de sens. Ça aurait plus de sens d'écrire (define 'x (op '* [n, n])) par exemple.
                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              28 novembre 2011 à 2:50:27

                                              Voilà j'ai apporté quelques modifications (plus ou moins importantes) au parser.
                                              D'abord, j'ai classifié les expressions en atomes (variables + littéraux) et instruction (le reste) pour pouvoir gérer les deux parenthèses à la fois. En fait ces changements correspondent à l'ajout de la règle Expression ::= Atome | (Instruction). Désormais, lorsque le parser rencontre une parenthèse ouvrante, il parse une expression et vérifie qu'il s'agit bien d'une Instruction.

                                              Pour éviter les répétitions des case _ => None j'ai créé une fonction elseNone qui essaye d'appliquer une fonction partielle à un argument et retourne None si la fonction échoue. Un exemple sera plus parlant :

                                              elseNone(5) {
                                                case 3 => Some(3)
                                              }
                                              // est équivalent à
                                              5 match {
                                                case 3 => Some(3)
                                                case _ => None
                                              }
                                              


                                              Attaquons maintenant les choses sérieuses. Comme j'ai dit hier les filtrages de motifs imbriquées sont très lourds. Il fallait utiliser 3 filtrages imbriqués pour (opBin e1 e2) et 4 filtrages pour (if c e1 e1) ... Pour éviter tout cela j'ai eu l'idée de créer un opérateur de séquencement de parseurs qui fonctionne ainsi : p1 o p2 parse un p1 puis un p2 et retourne la liste [res-p2 res-p1] si p1 et p2 arrivent à parser quelque chose et None si au moins l'un des deux échoue. Pour pouvoir appliquer ces changement j'ai changé le type Parser en ParseRes => ParseRes avec ParseRes = Option[(List[Expression],List[Char])]. En fait, j'ai rendu le type Parser une classe fille de ParseRes => ParseRes pour :
                                              • Pouvoir définir "o" comme étant opérateur infixe
                                              • Redéfinir la méthode apply pour que, si p est de type Parser, p(Some((l,s))) sera automatiquement transformé en p(Some((l, skipSpaces(s)))) et ainsi se débarasser de skipSpaces dans le reste du code. (Je précise que l'application de fonction f(x) en Scala est équivalente à f.apply(x), d'où la redéfinition de apply). Finalement, tous les cas du parser suivent désormais le même schéma :
                                              case /* motif de la règle n°i*/ =>
                                                    val e1 = /* combinaison de 1 ou plusieurs parsers */
                                                    elseNone(e1) {
                                                      case /* motif du succès de lecture de la partie droite de i */ => 
                                                        /* résultat à retourner */
                                                    }
                                              


                                              Voici donc ma nouvelle fonction de parsing :

                                              def parseExp: Parser = (e: ParseRes) => elseNone(e) {
                                              
                                                  case Some((insts, '(' :: s)) =>
                                                    elseNone(parseExp(Some((Nil, s)))) {
                                                      case Some((List(i: Instruction), ')' :: s1))  =>
                                                        Some((i :: insts, s1))
                                                    }
                                              
                                                  case Some((insts, op :: s)) if unOp(op) =>
                                                    elseNone(parseExp(Some((Nil, s)))) {
                                                      case Some((List(i), ')' :: s1)) => Some((UnOp(op, i) :: insts, s1))
                                                    }
                                              
                                                  case Some((insts, op :: s)) if binOp(op) =>
                                                    val e1 = (parseExp o parseExp)(Some((Nil, s)))
                                                    elseNone(e1) {
                                                      case Some((List(i2, i1), ')' :: s1)) => Some((BinOp(op, i1, i2) :: insts, s1))
                                                    }
                                              
                                                  case Some((insts, 'p' :: 'r' :: 'i' :: 'n' :: 't' :: ' ' :: xs)) =>
                                                    elseNone(parseExp(Some((Nil, xs)))) {
                                                      case Some((List(inst), r)) => Some((Print(inst) :: insts, r))
                                                    }
                                              
                                                  case Some((insts, 'i' :: 'f' :: ' ' :: s)) =>
                                                    val e1 = (parseExp o parseExp o parseExp)(Some((Nil, s)))
                                                    elseNone(e1) {
                                                      case Some((List(i2, i1, cond), s1)) => Some((If(cond, i1, i2) :: insts, s1))
                                                    }
                                              
                                                  case Some((insts, 'd' :: 'e' :: 'f' :: 'i' :: 'n' :: 'e' :: ' ' :: s)) =>
                                                    val e1 = (parseId o parseExp)(Some((Nil, s)))
                                                    elseNone(e1) {
                                                      case Some((List(inst, id: Var), s1)) => Some((Define(id, inst) :: insts, s1))
                                                    }
                                              
                                                  case Some((insts, s@(c :: _))) if c.isDigit =>
                                                    elseNone(parseInt(Some((Nil, s)))) {
                                                      case Some((List(inst), s1)) => Some((inst :: insts, s1))
                                                    }
                                              
                                                  case Some((insts, s@(c :: _))) if c.isLetter =>
                                                    elseNone(parseId(Some((Nil, s)))) {
                                                      case Some((List(inst), s1)) => Some((inst :: insts, s1))
                                                    }
                                                }
                                              


                                              C'est clairement plus lisible que le code précédent mais il n'est pas totalement fonctionnel. Pour une certaine raison mystérieuse il rejette les expressions contenant des parenthèses imbriquées (Bon en meme temps il fait tard et je commence à me fatiguer ...).

                                              Le code complet :
                                              object LispParser {
                                              
                                                trait Expression
                                              
                                                trait Atom extends Expression
                                              
                                                trait Instruction extends Expression
                                              
                                                case class Program(insts: List[Instruction])
                                              
                                                case class Literal[α](v: α) extends Atom
                                              
                                                case class Var(nom: String) extends Atom
                                              
                                                case class UnOp(op: Char, inst: Expression) extends Instruction
                                              
                                                case class BinOp(op: Char,
                                                                 inst1: Expression,
                                                                 inst2: Expression) extends Instruction
                                              
                                                case class If(cond: Expression,
                                                              thenInst: Expression,
                                                              elseInst: Expression) extends Instruction
                                              
                                                case class Print(inst: Expression) extends Instruction
                                              
                                                case class Define(name: Var, value: Expression) extends Instruction
                                              
                                                def skipSpaces(s: List[Char]): List[Char] =
                                                  s.dropWhile(_.isWhitespace)
                                              
                                                def unOp(c: Char) = List('~', '!') contains c
                                              
                                                def arithOp(c: Char) = List('+', '*', '-', '/') contains c
                                              
                                                def logOp1(c: Char) = List('<', '>', '=') contains c
                                              
                                                def logOp2(c: Char) = List('&', '|') contains c
                                              
                                                def logOp(c: Char) = logOp1(c) || logOp2(c)
                                              
                                                def binOp(c: Char) = arithOp(c) || logOp(c)
                                              
                                                def isOp(c: Char) = unOp(c) || binOp(c)
                                              
                                                def isAtom(e: Expression) = e match {
                                                  case e: Atom => true
                                                  case _ => false
                                                }
                                              
                                                def elseNone[T, U](x: Option[T])
                                                                  (f: PartialFunction[Option[T], Option[U]]) =
                                                  if (f.isDefinedAt(x)) f(x) else x match {
                                                    case None => None
                                                    case _ => println(x) ; None
                                                  }
                                              
                                                type ParseRes = Option[(List[Expression], List[Char])]
                                              
                                                type ParseFunc = ParseRes => ParseRes
                                              
                                                class Parser(val f: ParseFunc) extends ParseFunc {
                                                  override def apply(x: ParseRes) = elseNone(x) {
                                                    case Some((l1, l2)) => f(Some((l1, skipSpaces(l2))))
                                                  }
                                              
                                                  def o(that: Parser): Parser = new Parser(x =>
                                                    elseNone(x) {
                                                      case Some((l, _)) => elseNone(this(x)) {
                                                        case Some((l2, s2)) => that(Some((l2 ++ l, skipSpaces(s2))))
                                                      }
                                                    })
                                                }
                                              
                                                implicit def funcToParser(f: ParseFunc): Parser = new Parser(f)
                                              
                                                def parseInt: Parser = (e: ParseRes) => elseNone(e) {
                                                  case Some((insts, w)) =>
                                                    val (l1, l2) = w.span(_.isDigit)
                                                    Some((insts ++ List(Literal((0 /: l1)(_ * 10 + _.asDigit))), l2))
                                                }
                                              
                                                def parseId: Parser = (e: ParseRes) => elseNone(e) {
                                                  case Some((insts, w@(c :: _))) if c.isLetter =>
                                                    val (l1, l2) = skipSpaces(w).span(_.isLetterOrDigit)
                                                    Some((insts ++ List(Var(("" /: l1)(_ + _))), l2))
                                                }
                                              
                                                def parseExp: Parser = (e: ParseRes) => elseNone(e) {
                                                  case Some((insts, '(' :: s)) =>
                                                    elseNone(parseExp(Some((Nil, s)))) {
                                                      case Some((List(i: Instruction), ')' :: s1))  =>
                                                        Some((i :: insts, s1))
                                                    }
                                                  case Some((insts, op :: s)) if unOp(op) =>
                                                    elseNone(parseExp(Some((Nil, s)))) {
                                                      case Some((List(i), ')' :: s1)) => Some((UnOp(op, i) :: insts, s1))
                                                    }
                                                  case Some((insts, op :: s)) if binOp(op) =>
                                                    val e1 = (parseExp o parseExp)(Some((Nil, s)))
                                                    elseNone(e1) {
                                                      case Some((List(i2, i1), ')' :: s1)) => Some((BinOp(op, i1, i2) :: insts, s1))
                                                    }
                                                  case Some((insts, 'p' :: 'r' :: 'i' :: 'n' :: 't' :: ' ' :: xs)) =>
                                                    elseNone(parseExp(Some((Nil, xs)))) {
                                                      case Some((List(inst), r)) => Some((Print(inst) :: insts, r))
                                                    }
                                                  case Some((insts, 'i' :: 'f' :: ' ' :: s)) =>
                                                    val e1 = (parseExp o parseExp o parseExp)(Some((Nil, s)))
                                                    elseNone(e1) {
                                                      case Some((List(i2, i1, cond), s1)) => Some((If(cond, i1, i2) :: insts, s1))
                                                    }
                                                  case Some((insts, 'd' :: 'e' :: 'f' :: 'i' :: 'n' :: 'e' :: ' ' :: s)) =>
                                                    val e1 = (parseId o parseExp)(Some((Nil, s)))
                                                    elseNone(e1) {
                                                      case Some((List(inst, id: Var), s1)) => Some((Define(id, inst) :: insts, s1))
                                                    }
                                                  case Some((insts, s@(c :: _))) if c.isDigit =>
                                                    elseNone(parseInt(Some((Nil, s)))) {
                                                      case Some((List(inst), s1)) => Some((inst :: insts, s1))
                                                    }
                                                  case Some((insts, s@(c :: _))) if c.isLetter =>
                                                    elseNone(parseId(Some((Nil, s)))) {
                                                      case Some((List(inst), s1)) => Some((inst :: insts, s1))
                                                    }
                                                }
                                              
                                                def parse(s: String) = parseExp(Some((Nil, s toList)))
                                              }
                                              
                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                28 novembre 2011 à 9:40:52

                                                À mon humble avis. tu souffres de featurite, la maladie qui consiste à fantasmer sur les fonctionnalités avancées de son langage de programmation et les utiliser dans des cas où elles sont franchement gadget et où une fonctionnalité plus simple suffirait.

                                                Par exemple surcharger `apply` ici, c'est vraiment de la frime; en pratique tu ne t'en sers presque pas. Ta définition de `apply` utilise `elseNone` et du filtrage sur des `Some`, et pourtant cette construction se retrouve encore partout dans ton code. C'est mieux qu'avant, mais ça reste une forme de duplication :
                                                - tu utilises `elseNone` partout (est-il possible de centraliser son appel ?)
                                                - tu passes toujours un paramètre de la forme `Some(Nil,foo)` à tes parseurs (redondance)
                                                - dans tes cas de réussite tu renvoies toujours un `Some` (là pour le coup une conversion implicite serait bénéfique)

                                                Je pense qu'une jolie façon de se débarasser de skipSpaces serait la suivante : sémantiquement la raison pour laquelle tu as besoin de skipSpaces c'est parce que mettre un espace, ou mettre beaucoup d'espaces au même endroit, c'est pareil. Il suffirait donc d'effectuer un prétraitement de ton entrée qui renvoie la liste des morceaux de chaînes séparés par un espace ou plus (en langage regexp avec \s pour les espaces et \S pour les non-espaces, on veut capturer \s*((\S)\s+)*). Ça correspond en fait à une passe de lexing toute simple, et c'est plus clair que ce que tu fais actuellement, avec des espaces explicites après 'define', 'inst' ou un opérateur.

                                                Il y a un bug dans ta gestion des parenthèses qui se voit sans tester : tu gères les parenthèses ouvrantes et fermantes au même endroit, mais après dans certains cas (unOp, binOp) tu manges quand même une parenthèse fermante. C'est asymétrique donc ça n'est pas la bonne façon de faire.

                                                L'idée de ton opérateur de séquençage des parseurs est bonne. C'est en fait ce qu'on fait avec les bibliothèques monadiques de combinateurs de parsing. Je pense par contre que le type `Option[(List[Expr],List[Char])] => Option(...)` n'est pas adapté, tu devrais utiliser type Parser[T] = List[Char] => (Option[T], List[Char]).

                                                Une remarque que j'avais oubliée : tu as une redondance au niveau de tes opérateurs. Tu as d'un côté un prédicat "est un opérateur binaire" (par exemple), et de l'autre tu associes à certains caractères (qui se trouvent être ceux acceptés par le prédicat, mais c'est une relation implicite) une sémantique ((_ + _), etc.). Tu pourrais éliminer la redondance en :
                                                - passant par une étape intermédiaire énumération / case class pour décrire les valeurs possibles des opérateurs (c'est ce que j'ai suggéré dans le point (1) de mon message précédent); c'est la forme la plus flexible pour des manipulations symboliques
                                                - ayant une seule liste qui associe chaque opérateur à sa sémantique, et sert aussi de prédicat pendant le parsing; c'est plus économe et suffisant dans le cas présent
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  1 décembre 2011 à 15:22:57

                                                  Bonjour
                                                  D'abord je m'excuse pour cette réponse tardive, j'étais un peu occupé durant cette semaine.

                                                  Concernant "apply", ce n'est pas vraiment de la "featurité", j'avais besoin de faire hériter Parser de Function1 pour définir l'opérateur infixe (et éviter d'écrire seq(seq(p1,p2),p3)). Or apply est abstraite et j'étais obligé de la redéfinir. Après, ma redéfinition était peut-être mal faite et peu utile, mais ça c'est une autre histoire.

                                                  Bref, j'ai ajouté la phase de lexing, et j'avoue que c'est bien plus propre pour la gestion des blancs. De plus, j'ai pu aussi identifier les littéraux en meme temps (donc je n'ai plus besoin de "parseInt" et "parseId").
                                                  Lexèmes:

                                                  trait Lexeme
                                                  
                                                    trait BinOperator extends Lexeme
                                                  
                                                    trait UnOperator extends Lexeme
                                                  
                                                    trait ArithOperator extends BinOperator
                                                  
                                                    trait BoolOperator extends BinOperator
                                                  
                                                    trait CompOperator extends BinOperator
                                                  
                                                    trait Other extends Lexeme
                                                  
                                                    case object Plus extends ArithOperator
                                                  
                                                    case object Minus extends ArithOperator
                                                  
                                                    case object Times extends ArithOperator
                                                  
                                                    case object Less extends CompOperator
                                                  
                                                    case object LessOrEqual extends CompOperator
                                                  
                                                    case object Greater extends CompOperator
                                                  
                                                    case object GreaterOrEqual extends CompOperator
                                                  
                                                    case object Equals extends CompOperator
                                                  
                                                    case object NotEquals extends CompOperator
                                                  
                                                    case object And extends BoolOperator
                                                  
                                                    case object Or extends BoolOperator
                                                  
                                                    case object Neg extends UnOperator
                                                  
                                                    case object Not extends UnOperator
                                                  
                                                    case class Str(name: String) extends Other
                                                  
                                                    case class Integer[T](x: Int) extends Other
                                                  
                                                    case object OpPar extends Other
                                                  
                                                    case object CloPar extends Other
                                                  
                                                    case object Error extends Other
                                                  


                                                  Fonction de lexing :

                                                  def lexemes(w: List[Char]): List[Lexeme] = w match {
                                                      case Nil => Nil
                                                      case c :: w1 if c.isWhitespace => lexemes(w1)
                                                      case '(' :: w1 => OpPar :: lexemes(w1)
                                                      case ')' :: w1 => CloPar :: lexemes(w1)
                                                      case '+' :: w1 => Plus :: lexemes(w1)
                                                      case '-' :: w1 => Minus :: lexemes(w1)
                                                      case '*' :: w1 => Times :: lexemes(w1)
                                                      case '<' :: '=' :: w1 => LessOrEqual :: lexemes(w1)
                                                      case '<' :: w1 => Less :: lexemes(w1)
                                                      case '>' :: '=' :: w1 => GreaterOrEqual :: lexemes(w1)
                                                      case '>' :: w1 => Greater :: lexemes(w1)
                                                      case '!' :: '=' :: w1 => NotEquals :: lexemes(w1)
                                                      case '=' :: w1 => Equals :: lexemes(w1)
                                                      case '!' :: w1 => Not :: lexemes(w1)
                                                      case '~' :: w1 => Neg :: lexemes(w1)
                                                      case 'a' :: 'n' :: 'd' :: c :: w1 if c.isWhitespace =>
                                                        And :: lexemes(w1)
                                                      case 'o' :: 'r' :: c :: w1 if c.isWhitespace =>
                                                        Or :: lexemes(w1)
                                                      case c :: w1 if c.isDigit =>
                                                        val (l1, l2) = w.span(_.isDigit)
                                                        // (x /: l)(f) == l.foldLeft(x)(f)
                                                        Integer((0 /: l1)(_ * 10 + _.asDigit)) :: lexemes(l2)
                                                      case c :: w1 if c.isLetter =>
                                                        val (l1, l2) = w.span(_.isLetterOrDigit)
                                                        Str(("" /: l1)(_ + _)) :: lexemes(l2)
                                                      case _ => List(Error)
                                                    }
                                                  


                                                  Pour le parser, j'ai pris le type le type que t'as proposé (en remplaçant List[Char] par List[Lexeme]). Le seul truc qui me dérange dans ce type est que le séquencement d'un Parser[T] et d'un Parser[U] résulte en un Parser[(T,U)]. Je ne veux même pas imaginer le résultat de séquencement de 6 ou 7 parsers ( un truc genre ((((((T1,T2),T3),T4) .... >_< ).
                                                  J'ai ajouté une méthode "parse" au Parser qui joue le role d'application du parser et de elseNone, ce qui m'a fait gagner quelques lignes de code.

                                                  Fonction parseExp :

                                                  def parseExp: Parser1[Expression] = (lex: List[Lexeme]) => lex match {
                                                      case OpPar :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e), CloPar :: lex2) if !isAtom(e) => (e.s, lex2)
                                                      }
                                                      case (b: BinOperator) :: lex1 => (parseExp >> parseExp).parse(lex1) {
                                                        case (Some((e1, e2)), lex2) => (BinOp(b, e1, e2).s, lex2)
                                                      }
                                                      case (u: UnOperator) :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e1), lex2) => (UnOp(u, e1).s, lex2)
                                                      }
                                                      case Str("define") :: Str(n) :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e), lex2) => (Define(Var(n), e).s, lex2)
                                                      }
                                                      case Str("print") :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e), lex2) => (Print(e).s, lex2)
                                                      }
                                                      case Str("if") :: lex1 => (parseExp >> parseExp >> parseExp).parse(lex1) {
                                                        case (Some(((b, e1), e2)), lex2) => (If(b, e1, e2).s, lex2)
                                                      }
                                                      case Integer(x) :: lex1 => (Literal(x).s, lex1)
                                                      case Str(n) :: lex1 => (Var(n).s, lex1)
                                                      case _ => (None, lex)
                                                    }
                                                  

                                                  (écrire exp.s est équivalent à écrire Some(exp). J'ai été obligé d'ajouter cette méthode car je ne peux pas utiliser une conversion implicite dans ce cas.)

                                                  Code complet :

                                                  object LispParser {
                                                  
                                                    trait Lexeme
                                                  
                                                    trait BinOperator extends Lexeme
                                                  
                                                    trait UnOperator extends Lexeme
                                                  
                                                    trait ArithOperator extends BinOperator
                                                  
                                                    trait BoolOperator extends BinOperator
                                                  
                                                    trait CompOperator extends BinOperator
                                                  
                                                    trait Other extends Lexeme
                                                  
                                                    case object Plus extends ArithOperator
                                                  
                                                    case object Minus extends ArithOperator
                                                  
                                                    case object Times extends ArithOperator
                                                  
                                                    case object Less extends CompOperator
                                                  
                                                    case object LessOrEqual extends CompOperator
                                                  
                                                    case object Greater extends CompOperator
                                                  
                                                    case object GreaterOrEqual extends CompOperator
                                                  
                                                    case object Equals extends CompOperator
                                                  
                                                    case object NotEquals extends CompOperator
                                                  
                                                    case object And extends BoolOperator
                                                  
                                                    case object Or extends BoolOperator
                                                  
                                                    case object Neg extends UnOperator
                                                  
                                                    case object Not extends UnOperator
                                                  
                                                    case class Str(name: String) extends Other
                                                  
                                                    case class Integer[T](x: Int) extends Other
                                                  
                                                    case object OpPar extends Other
                                                  
                                                    case object CloPar extends Other
                                                  
                                                    case object Error extends Other
                                                  
                                                    def lexemes(w: List[Char]): List[Lexeme] = w match {
                                                      case Nil => Nil
                                                      case c :: w1 if c.isWhitespace => lexemes(w1)
                                                      case '(' :: w1 => OpPar :: lexemes(w1)
                                                      case ')' :: w1 => CloPar :: lexemes(w1)
                                                      case '+' :: w1 => Plus :: lexemes(w1)
                                                      case '-' :: w1 => Minus :: lexemes(w1)
                                                      case '*' :: w1 => Times :: lexemes(w1)
                                                      case '<' :: '=' :: w1 => LessOrEqual :: lexemes(w1)
                                                      case '<' :: w1 => Less :: lexemes(w1)
                                                      case '>' :: '=' :: w1 => GreaterOrEqual :: lexemes(w1)
                                                      case '>' :: w1 => Greater :: lexemes(w1)
                                                      case '!' :: '=' :: w1 => NotEquals :: lexemes(w1)
                                                      case '=' :: w1 => Equals :: lexemes(w1)
                                                      case '!' :: w1 => Not :: lexemes(w1)
                                                      case '~' :: w1 => Neg :: lexemes(w1)
                                                      case 'a' :: 'n' :: 'd' :: c :: w1 if c.isWhitespace =>
                                                        And :: lexemes(w1)
                                                      case 'o' :: 'r' :: c :: w1 if c.isWhitespace =>
                                                        Or :: lexemes(w1)
                                                      case c :: w1 if c.isDigit =>
                                                        val (l1, l2) = w.span(_.isDigit)
                                                        Integer((0 /: l1)(_ * 10 + _.asDigit)) :: lexemes(l2)
                                                      case c :: w1 if c.isLetter =>
                                                        val (l1, l2) = w.span(_.isLetterOrDigit)
                                                        Str(("" /: l1)(_ + _)) :: lexemes(l2)
                                                      case _ => List(Error)
                                                    }
                                                  
                                                  
                                                    trait Expression {
                                                      def s = Some(this)
                                                    }
                                                  
                                                    trait Atom extends Expression
                                                  
                                                    trait Instruction extends Expression
                                                  
                                                    case class Program(insts: List[Instruction])
                                                  
                                                    case class Literal[α](v: α) extends Atom
                                                  
                                                    case class Var(nom: String) extends Atom
                                                  
                                                    case class UnOp(op: UnOperator, inst: Expression) extends Instruction
                                                  
                                                    case class BinOp(op: BinOperator,
                                                                     inst1: Expression,
                                                                     inst2: Expression) extends Instruction
                                                  
                                                    case class If(cond: Expression,
                                                                  thenInst: Expression,
                                                                  elseInst: Expression) extends Instruction
                                                  
                                                    case class Print(inst: Expression) extends Instruction
                                                  
                                                    case class Define(name: Var, value: Expression) extends Instruction
                                                  
                                                  
                                                    def isAtom(e: Expression) = e match {
                                                      case e: Atom => true
                                                      case _ => false
                                                    }
                                                  
                                                    type Parser[T] = List[Lexeme] => (Option[T], List[Lexeme])
                                                  
                                                    def seq[T, U](p: Parser[T], q: Parser[U]): Parser[(T, U)] = l => p(l) match {
                                                      case (Some(r1), l1) => q(l1) match {
                                                        case (Some(r2), l2) => (Some((r1, r2)), l2)
                                                        case _ => (None, l1)
                                                      }
                                                      case _ => (None, l)
                                                    }
                                                  
                                                    class Parser1[T](val p: Parser[T]) extends Parser[T] {
                                                      def >>[U](q: Parser1[U]) = new Parser1[(T, U)](seq(p, q.p))
                                                  
                                                      override def apply(l: List[Lexeme]) = parse(l) {
                                                        case x => x
                                                      }
                                                  
                                                      def parse[U](l: List[Lexeme]) =
                                                        (f: PartialFunction[(Option[T], List[Lexeme]),
                                                          (Option[U], List[Lexeme])]) =>
                                                          if (f.isDefinedAt(p(l))) f(p(l)) else (None, l)
                                                    }
                                                  
                                                    implicit def parserToParser1[T](p: Parser[T]) = new Parser1(p)
                                                  
                                                    def parseExp: Parser1[Expression] = (lex: List[Lexeme]) => lex match {
                                                      case OpPar :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e), CloPar :: lex2) if !isAtom(e) => (e.s, lex2)
                                                      }
                                                      case (b: BinOperator) :: lex1 => (parseExp >> parseExp).parse(lex1) {
                                                        case (Some((e1, e2)), lex2) => (BinOp(b, e1, e2).s, lex2)
                                                      }
                                                      case (u: UnOperator) :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e1), lex2) => (UnOp(u, e1).s, lex2)
                                                      }
                                                      case Str("define") :: Str(n) :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e), lex2) => (Define(Var(n), e).s, lex2)
                                                      }
                                                      case Str("print") :: lex1 => parseExp.parse(lex1) {
                                                        case (Some(e), lex2) => (Print(e).s, lex2)
                                                      }
                                                      case Str("if") :: lex1 => (parseExp >> parseExp >> parseExp).parse(lex1) {
                                                        case (Some(((b, e1), e2)), lex2) => (If(b, e1, e2).s, lex2)
                                                      }
                                                      case Integer(x) :: lex1 => (Literal(x).s, lex1)
                                                      case Str(n) :: lex1 => (Var(n).s, lex1)
                                                      case _ => (None, lex)
                                                    }
                                                  
                                                    def parse(s: String) = parseExp(lexemes(s toList))
                                                  }
                                                  
                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    1 décembre 2011 à 17:53:29

                                                    C'est nettement mieux, merci.

                                                    Tu as beaucoup de `case (Some (...), lex2)` dans ton parser, on pourrait penser à une redondance. Mais en fait le Some peut rester ici car il devient important si tu implémentes une gestion des erreurs syntaxiques. Tu devrais le faire, c'est nettement moins dur qu'on pourrait le croire et le type du parseur permet en fait d'obtenir des messages assez précis, puisque tu as la position dans le flux.

                                                    Un détail : je n'aime pas trop ton motif ouvert `case _ => (None, lex)`. Les filtrages universels comme ça dangereux : si tu modifies ton code pour ajouter des cas, ils sont ignorés silencieusement et tu ne t'en rends pas compte. Quels cas gère-t-elle ?

                                                    Citation

                                                    Le seul truc qui me dérange dans ce type est que le séquencement d'un Parser[T] et d'un Parser[U] résulte en un Parser[(T,U)]. Je ne veux même pas imaginer le résultat de séquencement de 6 ou 7 parsers ( un truc genre ((((((T1,T2),T3),T4) .... >_< ).



                                                    Cela dépend de la façon dont tu définis ton opérateur. Il pourrait aussi avoir le type Parser[List[T]] -> Parser[List[T]] -> Parser[List[T]] ou alors Parser[T] -> Parser[List[T]] -> Parser[List[T]], qui sont moins précis (là tu peux avoir T différent de U) mais peut-être plus agréables à utiliser. Sinon, et c'est à mon avis un meilleur choix, tu peux utiliser des conversions (implicites ou pas) pour passer de (((T,U),V),W) à (T,U,V,W) pour rendre l'écriture plus agréable.
                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      1 décembre 2011 à 18:42:46

                                                      Citation : bluestorm

                                                      Un détail : je n'aime pas trop ton motif ouvert `case _ => (None, lex)`. Les filtrages universels comme ça dangereux : si tu modifies ton code pour ajouter des cas, ils sont ignorés silencieusement et tu ne t'en rends pas compte. Quels cas gère-t-elle ?


                                                      Pas grand chose. Juste les cas où la parenthèse fermentante apparait très tôt (")", "()", "(if x y)" ..) et les cas d'erreurs lexicales. Donc ça peut être remplacé par un case CloPar :: _ | Error :: _ => (None, lex).
                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        15 janvier 2012 à 2:42:04

                                                        Bonjour/Bonsoir
                                                        Me voilà de retour avec un nouveau parser. Cette fois ci il s'agit d'un parser monadique inspiré de celui présenté par Graham Hutton et Erick Meijer (ici (pdf)). J'utilise la bibliothèque Scalaz qui m'évite la peine de redéfinir les classes de types Monade et co.
                                                        Je ne vais pas parler de l'analyse lexicale et de l'AST puisqu'ils sont presque identiques à ceux de l'ancienne version. Il faux tout de même noter quelques ajouts dans le langage :
                                                        • la gestion des arguments de nombre variable pour tous les opérateurs prédéfinis
                                                        • l'instruction (seq e1 ... eN) qui exécute les expressions e1,e2 ... eN et retourne la valeur retournée par eN (seq fonctionne comme {e1;e2; ...; eN} en Scala). Toutes les variables définies dans un seq lui sont locales.
                                                        • le support des fonctions

                                                        Voici des exemples de programmes dans mon mini langage :

                                                        (seq 
                                                          (define (xor a b)
                                                            (or (and a (! b)) (and (! a) b)))
                                                          (print (xor true true))
                                                          (print (xor true false))
                                                          (print (xor false true))
                                                          (print (xor false false))
                                                         )
                                                        
                                                        
                                                        
                                                        (seq 
                                                          (define (compose f g x) (f (g x)))
                                                          (define (g x) (* 2 x))
                                                          (define (f x) (+ 10 x))
                                                          (print (compose f g 1))
                                                          (print (compose g f 1))
                                                            
                                                          (define (fact n)
                                                            (if (= n 0)
                                                            	1
                                                            	(* n (fact (- n 1)))))
                                                            (print (fact 5)))
                                                        )
                                                        


                                                        Analyse syntaxique


                                                        Le type choisi pour l'analyseur est List[Lexeme] => List[(A, List[Lexeme])]. Rien de magique: l'entrée est la liste des lexemes à parser et la sortie est la liste de toutes les lectures possibles (donc une liste vide représente une erreur de lecture).
                                                        Après, il faut "instancier" le type parser de MonadPlus. Puisque ce type n'existe pas dans Scalaz 6 je l'ai remplacé avec la combinaison Monad[Parser] et Monoid[Parser[A]].

                                                        implicit def parserMonadInstance: Monad[Parser] = new Monad[Parser] {
                                                        
                                                            // "return" en Haskell, retourne "a" sans rien consommer
                                                            def pure[A](a: => A) = Parser(s => (a, s) :: Nil) 
                                                        
                                                            // applique f aux cas où p réussit à parser quelque chose 
                                                            def bind[A, B](p: Parser[A], f: A => Parser[B]) = Parser { 
                                                              s =>
                                                                p parse s flatMap { case (a, s1) => f(a) parse s1 }
                                                            }
                                                          }
                                                        
                                                          implicit def parserMonoidInstance[A]: Monoid[Parser[A]] = new Monoid[Parser[A]] {
                                                        
                                                            // le parser qui échoue toujours ...
                                                            val zero: Parser[A] = Parser(_ => Nil) 
                                                        
                                                            // résultat non déterministe de p | q. À l'utilisation on écrit p |+| q.
                                                            def append(p: Parser[A], q: => Parser[A]) = Parser(s => (p parse s) ++ (q parse s))
                                                          }
                                                        

                                                        Puisque les listes deviennent rapidement très grandes avec append, j'ai défini une version déterministe qui se contente de retourner le premier résultat :

                                                        def |(that: Parser[A]): Parser[A] = Parser(s =>
                                                              (this |+| that) parse s match {
                                                                case Nil    => Nil
                                                                case x :: _ => List(x)
                                                              })
                                                        


                                                        Ensuite, j'ai défini quelques petits parsers généralistes semblables à ceux du document ci-dessus : item (parse un lexeme quelconque), satisfy(predicat) (parse un item s'il vérifie le predicat), lex(lexeme) (parse un lexeme bien déterminé), many(p) (répète p zéro ou plusieurs fois) et many1(p) (repète p une ou plusieurs fois). Une fois tout ceci préparé, l'écriture des parsers devient une simple combinaison de l'opérateur "|" et des for-comprehensions:



                                                        def parseInt: Parser[Expression] = for {
                                                            r <- satisfy {
                                                              case Integer(_) => true
                                                              case _          => false
                                                            }
                                                            Integer(n) = r
                                                          } yield Literal(n)
                                                          
                                                          def parseDefine: Parser[Expression] = for {
                                                            _ <- lex(Str("define"))
                                                            x <- parseVar.as[Var]
                                                            exp <- parseExp
                                                          } yield Define(x, exp)
                                                        
                                                          def parseBinOp: Parser[Expression] = {
                                                            val operators = List(Lexing.Plus, Minus, Times, Less, LessOrEqual,
                                                              Greater, GreaterOrEqual, Equals, NotEquals, And, Or)
                                                            for {
                                                              op <- (operators.map(lex(_)).reduceLeft(_ | _)).as[BinOperator]
                                                              expr <- parseExp
                                                              exprs <- many1(parseExp)
                                                            } yield BinOp(op, expr :: exprs)
                                                          }
                                                        
                                                          def parseExp1: Parser[Expression] = parseProg | parseDefine | parseUnOp | parseBinOp |
                                                            parseIf | parsePrint | parseFuncDef | parseFunc 
                                                        
                                                          def parseFuncDef: Parser[Expression] =
                                                            for {
                                                              _ <- lex(Str("define"))
                                                              _ <- lex(OpPar)
                                                              name <- parseVar.as[Var]
                                                              args <- many(parseVar.as[Var])
                                                              _ <- lex(CloPar)
                                                              body <- parseExp
                                                            } yield DefFunc(name, args, body)
                                                        
                                                           // ....
                                                        


                                                        Ce parser est visiblement plus flexible que son antécédent et son utilisation est plus "naturelle". Cependant, je ne vois pas comment peut on gérer les erreurs avec ce machin ...

                                                        Analyse sémantique


                                                        Ma nouvelle fonction d'évaluation porte le type (Expression,Env) => Either[String, (Any,Env)], où Env est un alias pour List[(String,Any)]. Le code de la fonction eval est assez simple (mais peu lisible à cause de la lourdeur de la gestion des erreurs avec Either), la seule instruction qui mérite un peu d'explication est l'application des fonctions. Voici comment je procède: lors de la définition d'une fonction, je l'ajoute à l'environnement comme étant une variable ayant la valeur (args,body) (sans pour autant toucher à ces deux là). Après, au moment de l'appel, j'évalue les paramètres effectifs puis je remplace l'appel à la fonction par un bloc "seq" constitué d'une suite de define et du corps de la fonction. Les defines se chargent de donner aux paramètres formels les valeurs des paramètres effectifs. Un exemple sera plus parlant : soit le bout de code suivant :

                                                        (define (f x y) (+ (* x x x) (* y y y)))
                                                        (define x 2)
                                                        (define y 1)
                                                        (f y x)
                                                        


                                                        Pour évaluer (f y x) on passe par les étapes suivantes :
                                                        • on remplace les paramètres effectifs par leurs valeurs (ce qui aidera à éviter les collisions de noms avec les paramètres formels): (f 1 2)
                                                        • on associe ces valeurs aux variables formelles: (define x 1) (define y 2)
                                                        • on applique le corps de la fonction: (define x 1) (define y 2) (+ (* x x x) (* y y y))
                                                        • et pour éviter toute modification de l'environnement, on encapsule le tout dans un "seq":
                                                        • (seq (define x 1) (define y 2) (+ (* x x x) (* y y y)))
                                                        • finalement on évalue le seq avec un appel récursif à eval


                                                        Voici un petit test de l'interpréteur:
                                                        val prog = """ 
                                                        (seq
                                                            (seq 
                                                          		(define (xor a b)
                                                            			(or (and a (! b)) (and (! a) b)))
                                                            	(print (xor true true))
                                                            	(print (xor true false))
                                                            	(print (xor false true))
                                                            	(print (xor false false))
                                                            )
                                                            (seq 
                                                                (define x 3)
                                                            	(define (f y) (* y x))
                                                                (print (f 3))
                                                            	(define (compose f g x) (f (g x)))
                                                            	(define (g x) (* 2 x))
                                                            	(define (f x) (+ 10 x))
                                                            	(print (compose f g 1))
                                                            	(print (compose g f 1))
                                                            
                                                                (define (fact n)
                                                            			(if (= n 0)
                                                            				1
                                                            				(* n (fact (- n 1)))))
                                                            	(print (fact 5)))
                                                            	(define x 5)
                                                                (print x)
                                                                (print (f 2))
                                                            ))
                                                            """
                                                          val l = lexemes(prog toList)
                                                          val l1 = parseExp.parse(l)
                                                          if(!l1.isEmpty)
                                                            eval(l1(0)._1, Nil)
                                                        


                                                        false
                                                        true
                                                        true
                                                        false
                                                        9
                                                        12
                                                        22
                                                        120
                                                        5
                                                        10

                                                        (Un petit bug s'est glissé dans l'évaluation, à vous de le trouver ;) )

                                                        Enfin, le code complet de l'interpréteur :
                                                        Lexing.scala

                                                        object Lexing {
                                                        
                                                          trait Lexeme
                                                          trait BinOperator extends Lexeme
                                                          trait UnOperator extends Lexeme
                                                          trait ArithOperator extends BinOperator
                                                          trait BoolOperator extends BinOperator
                                                          trait CompOperator extends BinOperator
                                                          trait Other extends Lexeme
                                                        
                                                          case object Plus extends ArithOperator
                                                          case object Minus extends ArithOperator
                                                          case object Times extends ArithOperator
                                                          case object Div extends ArithOperator
                                                        
                                                          case object Less extends CompOperator
                                                          case object LessOrEqual extends CompOperator
                                                          case object Greater extends CompOperator
                                                          case object GreaterOrEqual extends CompOperator
                                                          case object Equals extends CompOperator
                                                          case object NotEquals extends CompOperator
                                                        
                                                          case object And extends BoolOperator
                                                          case object Or extends BoolOperator
                                                        
                                                          case object Neg extends UnOperator
                                                          case object Not extends UnOperator
                                                        
                                                          case class Str(name: String) extends Other
                                                          case class Integer[T](x: Int) extends Other
                                                          case object OpPar extends Other
                                                          case object CloPar extends Other
                                                          case object Error extends Other
                                                        
                                                          def lexemes(w: List[Char]): List[Lexeme] = w match {
                                                            case Nil                       => Nil
                                                            case c :: w1 if c.isWhitespace => lexemes(w1)
                                                            case '(' :: w1                 => OpPar :: lexemes(w1)
                                                            case ')' :: w1                 => CloPar :: lexemes(w1)
                                                            case '+' :: w1                 => Plus :: lexemes(w1)
                                                            case '-' :: w1                 => Minus :: lexemes(w1)
                                                            case '*' :: w1                 => Times :: lexemes(w1)
                                                            case '/' :: w1                 => Div :: lexemes(w1)
                                                            case '<' :: '=' :: w1          => LessOrEqual :: lexemes(w1)
                                                            case '<' :: w1                 => Less :: lexemes(w1)
                                                            case '>' :: '=' :: w1          => GreaterOrEqual :: lexemes(w1)
                                                            case '>' :: w1                 => Greater :: lexemes(w1)
                                                            case '!' :: '=' :: w1          => NotEquals :: lexemes(w1)
                                                            case '=' :: w1                 => Equals :: lexemes(w1)
                                                            case '!' :: w1                 => Not :: lexemes(w1)
                                                            case '~' :: w1                 => Neg :: lexemes(w1)
                                                            case 'a' :: 'n' :: 'd' :: c :: w1 if c.isWhitespace =>
                                                              And :: lexemes(w1)
                                                            case 'o' :: 'r' :: c :: w1 if c.isWhitespace =>
                                                              Or :: lexemes(w1)
                                                           case c :: w1 if c.isDigit =>
                                                              val (l1, l2) = w.span(_.isDigit)
                                                              Integer((0 /: l1)(_ * 10 + _.asDigit)) :: lexemes(l2)
                                                            case c :: w1 if c.isLetter =>
                                                              val (l1, l2) = w.span(_.isLetterOrDigit)
                                                              Str(("" /: l1)(_ + _)) :: lexemes(l2)
                                                            case _ => List(Error)
                                                          }
                                                        
                                                          val keywords = List("define", "if")
                                                        
                                                        }
                                                        


                                                        AST.scala

                                                        object AST {
                                                        
                                                          trait Expression 
                                                          case class Sequence(insts: List[Expression]) extends Expression
                                                          case class Literal[A](v: A) extends Expression
                                                          case class Var(nom: String) extends Expression
                                                          case class UnOp(op: Lexing.UnOperator,
                                                                          inst: Expression) extends Expression
                                                          case class BinOp(op: Lexing.BinOperator,
                                                                           insts: List[Expression]) extends Expression
                                                          case class If(cond: Expression,
                                                                        thenInst: Expression,
                                                                        elseInst: Expression) extends Expression
                                                          case class Print(inst: Expression) extends Expression
                                                          case class Define(name: Var, value: Expression) extends Expression
                                                          case class DefFunc(name: Var, args: List[Var], body: Expression) extends Expression
                                                          case class Func(name: Var, args: List[Expression]) extends Expression
                                                        
                                                        }
                                                        


                                                        Parsing.scala

                                                        object Parsing {
                                                          import scalaz._
                                                          import Scalaz._
                                                        
                                                          import Lexing._
                                                          import AST._
                                                        
                                                          case class Parser[A](parse: List[Lexeme] => List[(A, List[Lexeme])]) {
                                                            def |(that: Parser[A]): Parser[A] = Parser(s =>
                                                              (this |+| that) parse s match {
                                                                case Nil    => Nil
                                                                case x :: _ => List(x)
                                                              })
                                                            def as[T] = this.asInstanceOf[Parser[T]]
                                                          }
                                                        
                                                          val item = Parser {
                                                            case Nil     => Nil
                                                            case c :: s1 => (c, s1) :: Nil
                                                          }
                                                        
                                                          implicit def parserMonadInstance: Monad[Parser] = new Monad[Parser] {
                                                            def pure[A](a: => A) = Parser(s => (a, s) :: Nil)
                                                        
                                                            def bind[A, B](p: Parser[A], f: A => Parser[B]) = Parser {
                                                              s =>
                                                                p parse s flatMap { case (a, s1) => f(a) parse s1 }
                                                            }
                                                          }
                                                        
                                                          implicit def parserMonoidInstance[A]: Monoid[Parser[A]] = new Monoid[Parser[A]] {
                                                            val zero: Parser[A] = Parser(_ => Nil)
                                                        
                                                            def append(p: Parser[A], q: => Parser[A]) = Parser(s => (p parse s) ++ (q parse s))
                                                          }
                                                        
                                                          def satisfy(predicat: Lexeme => Boolean): Parser[Lexeme] =
                                                            for {
                                                              c <- item
                                                              r <- if (predicat(c)) c.pure[Parser] else mzero[Parser[Lexeme]]
                                                            } yield r
                                                        
                                                          def lex(l: Lexeme) = satisfy(l ==)
                                                          def many[A](p: Parser[A]): Parser[List[A]] = many1(p) | List.empty[A].pure
                                                          def many1[A](p: Parser[A]): Parser[List[A]] =
                                                            for { r <- p; rs <- many(p) } yield (r :: rs)
                                                        
                                                          def parseVar: Parser[Expression] = for {
                                                            r <- satisfy {
                                                              case Str(_) => true
                                                              case _      => false
                                                            }
                                                            Str(x) = r
                                                          } yield Var(x)
                                                        
                                                          def parseInt: Parser[Expression] = for {
                                                            r <- satisfy {
                                                              case Integer(_) => true
                                                              case _          => false
                                                            }
                                                            Integer(n) = r
                                                          } yield Literal(n)
                                                          
                                                          def parseTrue: Parser[Expression] = for {
                                                            _ <- lex(Str("true")) 
                                                          } yield Literal(true)
                                                        
                                                          def parseFalse: Parser[Expression] = for {
                                                            _ <- lex(Str("false")) 
                                                          } yield Literal(false)
                                                        
                                                          def parseDefine: Parser[Expression] = for {
                                                            _ <- lex(Str("define"))
                                                            x <- parseVar.as[Var]
                                                            exp <- parseExp
                                                          } yield Define(x, exp)
                                                        
                                                          def parseUnOp: Parser[Expression] =
                                                            for {
                                                              op <- (lex(Neg) | lex(Not)).as[UnOperator]
                                                              expr <- parseExp
                                                            } yield UnOp(op, expr)
                                                        
                                                          def parseBinOp: Parser[Expression] = {
                                                            val operators = List(Lexing.Plus, Minus, Times, Less, LessOrEqual,
                                                              Greater, GreaterOrEqual, Equals, NotEquals, And, Or)
                                                            for {
                                                              op <- (operators.map(lex(_)).reduceLeft(_ | _)).as[BinOperator]
                                                              expr <- parseExp
                                                              exprs <- many1(parseExp)
                                                            } yield BinOp(op, expr :: exprs)
                                                          }
                                                        
                                                          def parseIf: Parser[Expression] =
                                                            for {
                                                              _ <- lex(Str("if"))
                                                              cond <- parseExp
                                                              then <- parseExp
                                                              else_ <- parseExp
                                                            } yield If(cond, then, else_)
                                                        
                                                          def parsePrint: Parser[Expression] =
                                                            for {
                                                              _ <- lex(Str("print"))
                                                              expr <- parseExp
                                                            } yield Print(expr)
                                                        
                                                          def parseExp: Parser[Expression] =
                                                            (for {
                                                              _ <- lex(OpPar)
                                                              r <- parseExp1 | parseExp
                                                              _ <- lex(CloPar)
                                                            } yield r) | parseInt | parseTrue | parseFalse | parseVar
                                                        
                                                          def parseExp1: Parser[Expression] = parseProg | parseDefine | parseUnOp | parseBinOp |
                                                            parseIf | parsePrint | parseFuncDef | parseFunc 
                                                        
                                                          def parseProg: Parser[Expression] =
                                                            for {
                                                              _ <- lex(Str("seq"))
                                                              exprs <- many1(parseExp.as[Expression])
                                                            } yield Sequence(exprs)
                                                        
                                                          def parseFuncDef: Parser[Expression] =
                                                            for {
                                                              _ <- lex(Str("define"))
                                                              _ <- lex(OpPar)
                                                              name <- parseVar.as[Var]
                                                              args <- many(parseVar.as[Var])
                                                              _ <- lex(CloPar)
                                                              body <- parseExp
                                                            } yield DefFunc(name, args, body)
                                                        
                                                          def parseFunc: Parser[Expression] =
                                                            for {
                                                              name <- parseVar.as[Var]
                                                              args <- many(parseExp)
                                                            } yield Func(name, args)
                                                        }
                                                        


                                                        Semantics.scala

                                                        object Semantics {
                                                          import AST._
                                                          import Lexing._
                                                        
                                                          type Env = List[(String, Any)]
                                                        
                                                          val arith = Map[ArithOperator, (Int, Int) => Int] (
                                                            Plus -> (_ + _),
                                                            Minus -> (_ - _),
                                                            Times -> (_ * _),
                                                            Div -> (_ / _))
                                                        
                                                          val comp = Map[CompOperator, (Int, Int) => Boolean] (
                                                            Less -> (_ < _),
                                                            LessOrEqual -> (_ <= _),
                                                            Greater -> (_ > _),
                                                            GreaterOrEqual -> (_ >= _),
                                                            Equals -> (_ == _),
                                                            NotEquals -> (_ != _))
                                                        
                                                          val bool = Map[BoolOperator, (Boolean, Boolean) => Boolean] (
                                                            And -> (_ && _),
                                                            Or -> (_ || _))
                                                        
                                                          def eval(exp: Expression, env: Env): Either[String, (Any, Env)] =
                                                            exp match {
                                                              case Literal(x) => Right(x, env)
                                                              case Var(x) =>
                                                                val l = (env dropWhile (_._1 != x))
                                                                if (l.isEmpty)
                                                                  Left("Unbound variable: " + x)
                                                                else Right(l.head._2, env)
                                                        
                                                              case UnOp(Neg, exp1) => eval(exp1, env) match {
                                                                case Right((v: Int, env1)) => Right(-v, env1)
                                                                case _                     => Left("Type error: " + exp1)
                                                              }
                                                        
                                                              case UnOp(Not, exp1) => eval(exp1, env) match {
                                                                case Right((v: Boolean, env1)) => Right(!v, env1)
                                                                case _                         => Left("Type error: " + exp1)
                                                              }
                                                        
                                                              case BinOp(op: ArithOperator, exprs) =>
                                                                val values = exprs.map (eval(_, env))
                                                                if (!values.forall(_.isInstanceOf[Right[String, (Int, Env)]]))
                                                                  Left("Type error: " + exprs.dropWhile (eval(_, env).isInstanceOf[Right[String, (Int, Env)]]))
                                                                else Right(values.map{ case Right((v: Int, _)) => v } reduceLeft arith(op), env)
                                                        
                                                              case BinOp(op: CompOperator, exprs) =>
                                                                val values = exprs.map (eval(_, env))
                                                                if (!values.forall(_.isInstanceOf[Right[String, (Int, Env)]]))
                                                                  Left("Type error: " + exprs.dropWhile (eval(_, env).isInstanceOf[Right[String, (Int, Env)]]))
                                                                else
                                                                  Right(values.map{ case Right((v: Int, _)) => v }
                                                                    .sliding(2).map(c => comp(op)(c(0), c(1))).forall(_ == true), env)
                                                        
                                                              case BinOp(op: BoolOperator, exprs) =>
                                                                val values = exprs.map (eval(_, env))
                                                                if (!values.forall(_.isInstanceOf[Right[String, (Boolean, Env)]]))
                                                                  Left("Type error: " + exprs.dropWhile (eval(_, env).isInstanceOf[Right[String, (Int, Env)]]))
                                                                else
                                                                  Right(values.map{ case Right((v: Boolean, _)) => v }.reduceLeft(bool(op)), env)
                                                        
                                                              case If(cond, exp1, exp2) => eval(cond, env) match {
                                                                case Right((c: Boolean, env1)) => if (c) eval(exp1, env1) else eval(exp2, env1)
                                                                case _                         => Left("Type error: " + cond)
                                                              }
                                                        
                                                              case Print(expr) => eval(expr, env) match {
                                                                case Right((e, env1)) => Right(println(e), env1)
                                                                case l                => l
                                                              }
                                                              case Define(Var(x), exp) => eval(exp, env) match {
                                                                case Right((v, env1)) => Right((), (x, v) :: env1)
                                                                case l                => l
                                                              }
                                                        
                                                              case Sequence(exprs) =>
                                                                val u: Any = ()
                                                                Right((exprs.foldLeft((u, env)) ((acc, e) => eval(e, acc._2) match { case Right(r) => r })._1, env))
                                                        
                                                              case DefFunc(Var(name), args, body) => Right((), (name, (args map { case Var(x) => x }, body)) :: env)
                                                              case Func(name, eff_args) => eval(name, env) match {
                                                                case Right(((form_args: List[String], body: Expression), env1)) =>
                                                                  if (eff_args.size == form_args.size) {
                                                                    val def_args: List[Expression] = form_args.zip(eff_args map (eval(_, env1) match { case Right(c) => c._1 }))
                                                                      .map { case (n, e) => Define(Var(n), Literal(e)) }
                                                                    val s = Sequence(def_args ++ List(body))
                                                                    eval(s, env1)
                                                                  } else Left("Wrong number of parameters to function " + name)
                                                                case l => l
                                                              }
                                                            }
                                                        }
                                                        
                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          22 janvier 2012 à 10:26:05

                                                          Einstein++, je ne passe plus régulièrement sur le SdZ. Si tu veux des avis sur tes programmes Scala je te conseille d'aller en parler sur Progmod, un petit forum plus ciblé sur les différents paradigmes de programmation -- et que je visite régulièrement.
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter

                                                          [Atelier] P'tit langage

                                                          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                          • Editeur
                                                          • Markdown