Partage
  • Partager sur Facebook
  • Partager sur Twitter

Exemples De Bytecode

Sujet résolu
    22 juin 2018 à 8:02:06

    Bonjour, je suis officiellement l'utilisateur qui poste le plus de topics sur le même sujet en 24 heures !

    Pardonnez-moi :')

    Illustration de la Compilation en Butecode et de L'Interpretation du Bytecode

    J'aimerai des exemples de Bytecode de langages interprétés [j'ai trouvé que celui de Java] (Vous savez, ce code intermédiaire, chez les langages interprétés : script -> bytecode -> interprétation)

    Et j'aimerai aussi que vous me donniez un avis à : En quoi le bytecode rend l'interprétation d'un script plus rapide ?

                                                                             A quoi ressemblent les bytecodes des langages interprétés ?

                                                                             Quelle vitesse (en moyenne) une compilation bytecode avant l'interprétation est sensé procurer ?

    Merci ! :D

    • Partager sur Facebook
    • Partager sur Twitter
      22 juin 2018 à 9:43:18

      Lu'!

      Le bytecode c'est bien souvent juste un encodage plus compact de mnémoniques de bas niveaux (finalement, on peut voir le code machine comme un bytecode que le processeur est capable d'interpréter lui-même directement : c'est ce qu'il fait). Typiquement, on va faire correspondre à des chaînes de caractères un entier qui tient dans un seul caractère. Par exemple, on pourra décréter ceci :

      • LOAD : 1
      • STORE : 2
      • ADD : 3
      • SUB : 4
      • ...

      Du côté des bytecode existants, tu peux regarder ce qui est fait pour : C#, LLVM, Ocaml, Lua, P-Code, et il y en a pas mal d'autres. Je serai étonné que Wikipedia ne propose pas une telle liste.

      jomtek. a écrit:

      (1) Et j'aimerai aussi que vous me donniez un avis à : En quoi le bytecode rend l'interprétation d'un script plus rapide ?

      (2) A quoi ressemblent les bytecodes des langages interprétés ?

      (3) Quelle vitesse (en moyenne) une compilation bytecode avant l'interprétation est sensé procurer ?

      (1) Le bytecode est une représentation plus compacte, donc elle est plus rapide à lire à la base (par exemple au lieu de lire N caractères, on a besoin de n'en lire qu'un) et analyser, mais il est également plus facile d'avoir de gros programmes qui soient quasiment intégralement chargés dans le cache du processeur (donc avec des délais d'accès très courts). De plus, comme on le produit à l'aide d'une phase de compilation, on peut tout à fait introduire de l'optimisation dans cette phase du travail pour améliorer la manière dont le programme calcule, etc.

      (2) Un langage n'est pas interprété ou compilé, c'est son implémentation qui peut être sous la forme d'une interprétation ou d'une compilation directe vers le natif. Après, à quoi ça ressemble : à un gros paquet d'octets qui sont une traduction d'un langage simple.

      (3) Il n'y a aucune règle. Ça va complètement dépendre de la difficulté à analyser le langage d'entrée, de la quantité de vérification de sûreté et du travail nécessaire pour l'optimisation. Vraiment, on ne peut pas définir de règle générale.

      • Partager sur Facebook
      • Partager sur Twitter

      Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

        22 juin 2018 à 13:00:31

        Ksass`Peuk a écrit:

        Lu'!

        Le bytecode c'est bien souvent juste un encodage plus compact de mnémoniques de bas niveaux (finalement, on peut voir le code machine comme un bytecode que le processeur est capable d'interpréter lui-même directement : c'est ce qu'il fait). Typiquement, on va faire correspondre à des chaînes de caractères un entier qui tient dans un seul caractère. Par exemple, on pourra décréter ceci :

        • LOAD : 1
        • STORE : 2
        • ADD : 3
        • SUB : 4
        • ...

        Du côté des bytecode existants, tu peux regarder ce qui est fait pour : C#, LLVM, Ocaml, Lua, P-Code, et il y en a pas mal d'autres. Je serai étonné que Wikipedia ne propose pas une telle liste.

        jomtek. a écrit:

        (1) Et j'aimerai aussi que vous me donniez un avis à : En quoi le bytecode rend l'interprétation d'un script plus rapide ?

        (2) A quoi ressemblent les bytecodes des langages interprétés ?

        (3) Quelle vitesse (en moyenne) une compilation bytecode avant l'interprétation est sensé procurer ?

        (1) Le bytecode est une représentation plus compacte, donc elle est plus rapide à lire à la base (par exemple au lieu de lire N caractères, on a besoin de n'en lire qu'un) et analyser, mais il est également plus facile d'avoir de gros programmes qui soient quasiment intégralement chargés dans le cache du processeur (donc avec des délais d'accès très courts). De plus, comme on le produit à l'aide d'une phase de compilation, on peut tout à fait introduire de l'optimisation dans cette phase du travail pour améliorer la manière dont le programme calcule, etc.

        (2) Un langage n'est pas interprété ou compilé, c'est son implémentation qui peut être sous la forme d'une interprétation ou d'une compilation directe vers le natif. Après, à quoi ça ressemble : à un gros paquet d'octets qui sont une traduction d'un langage simple.

        (3) Il n'y a aucune règle. Ça va complètement dépendre de la difficulté à analyser le langage d'entrée, de la quantité de vérification de sûreté et du travail nécessaire pour l'optimisation. Vraiment, on ne peut pas définir de règle générale.

        Merci

        Et j'ai une petite question pour terminer :

        Disons que j'ai cette ligne de code

        print(File.Read("C:\fichier.txt"));
        // Disons que le contenu du fichier "C:\fichier.txt" est "Je suis un texte !"

        Le bytecode donnera-t-il plutôt ça :

        P:"Je suis un texte !"

        ou ça

        P:20.56:"C:\fichier.txt"
        // (20.56 représentant la classe File pour 20, et la fonction Read pour 56), ce qui nous donne
        File.Read





        -
        Edité par jomtek. 22 juin 2018 à 13:01:39

        • Partager sur Facebook
        • Partager sur Twitter
          22 juin 2018 à 13:58:57

          Si l'utilisateur change le contenu du fichier texte, je pense qu'il s'attend à ce que le message change sans avoir à recompiler le bytecode.

          • Partager sur Facebook
          • Partager sur Twitter

          Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

            22 juin 2018 à 15:41:39

            Fait attention, l'exemple que tu proposes n'est pas réellement du bytecode, et ton programme pourrait se comporter d'une manière non-voulue si le reste de l'implémentation du langage suit le même schéma.

            Ici, tu interprètes directement le comportement de la méthode Read de la classe File :

            print(File.Read("path"));

            Normalement, l'interprète devrait agir comme ça :

            "path" --> File.Read(path : string) --> print(text : string)

            Donc, on passe l'argument "path" à la méthode statique Read de la classe File, et la valeur est renvoyée en tant qu'argument à la fonction print, dont le paramètre demande une chaine de caractère.

            Une notation bytecode pourrait ressembler à ceci (je ne suis pas familiarisé avec le bytecode Java/Lua/Python/..., donc je le fais à l'asm-like) :

            mov edi, "path"
            call File.Read
            pop edi
            mov BYTE [esp-2], [eax]
            pop eax
            mov BYTE edi, [esp-2]
            call print
            pop edi
            
            File.Read:
                ; ...
            
            print:
                ; ...

            Comme tu le vois, toutes les actions sont retranscrites : on charge les arguments, et on les passes aux fonctions.

            Si le résultat est directement celui-ci :

            mov edi, "Je suis un texte !"
            call print
            pop edi

            Il faut s'assurer que cela ne provoquera pas d'erreurs lorsque l'utilisateur créera ses propres classes.

            Sauf si File.Read est inaccessible et pré-codé directement dans l'interprète, il ne faut pas brûler des étapes, et anticiper les actions du développeur.

            -
            Edité par vanaur 22 juin 2018 à 15:43:36

            • Partager sur Facebook
            • Partager sur Twitter

            Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

              22 juin 2018 à 17:39:06

              vanaur a écrit:

              Fait attention, l'exemple que tu proposes n'est pas réellement du bytecode, et ton programme pourrait se comporter d'une manière non-voulue si le reste de l'implémentation du langage suit le même schéma.

              Ici, tu interprètes directement le comportement de la méthode Read de la classe File :

              print(File.Read("path"));

              Normalement, l'interprète devrait agir comme ça :

              "path" --> File.Read(path : string) --> print(text : string)

              Donc, on passe l'argument "path" à la méthode statique Read de la classe File, et la valeur est renvoyée en tant qu'argument à la fonction print, dont le paramètre demande une chaine de caractère.

              Une notation bytecode pourrait ressembler à ceci (je ne suis pas familiarisé avec le bytecode Java/Lua/Python/..., donc je le fais à l'asm-like) :

              mov edi, "path"
              call File.Read
              pop edi
              mov BYTE [esp-2], [eax]
              pop eax
              mov BYTE edi, [esp-2]
              call print
              pop edi
              
              File.Read:
                  ; ...
              
              print:
                  ; ...

              Comme tu le vois, toutes les actions sont retranscrites : on charge les arguments, et on les passes aux fonctions.

              Si le résultat est directement celui-ci :

              mov edi, "Je suis un texte !"
              call print
              pop edi

              Il faut s'assurer que cela ne provoquera pas d'erreurs lorsque l'utilisateur créera ses propres classes.

              Sauf si File.Read est inaccessible et pré-codé directement dans l'interprète, il ne faut pas brûler des étapes, et anticiper les actions du développeur.

              -
              Edité par vanaur il y a environ 1 heure

              Donc disons que j'ai un code comme celui-ci :

              string axax(string input){
              return input.substr(1,input.length() - 1);
              }
              print(axax("test"));

              Le résultat en bytecode sera donc 

              P:axax, "test"
              
              axax{
              R:arg[0].substr(1, arg[0].length() - 1)}
              }
              
              substr{
              blabla
              }
              
              length{
              blabla
              }
              

              ?

              Oui les fonctions que j'appelle "les fonctions intégrées" sont directement implémentées dans l'interpréteur. Elles sont d'ailleurs inaccessibles par le programmeur.

              Voici comment mon compilateur code -> bytecode opère (pour l'instant) :

              (1) - La reformulation du code (enlevage de commentaires et de lignes vides, mais aussi de tabulations et d'espaces en début et fin de ligne)

              (2) - L'analyse syntaxique du code (Identification de chaque "token" (mot-clé) et analyse de plusieurs paramètres (quantité d'arguments, orthographe de chaque appel de fonction/void, etc...) )

              (3) - Compilation en bytecode (Traduction du code analysé et reformulé de code jusqu'à bytecode par le compilateur)

              (4) - Optimisation du bytecode (Effacement de lignes inutiles, analyse de logique du bytecode, etc...)

              (5) - Interprétation du bytecode par l'interpréteur (Programme en console, chargement de classes, identificateur de Tokens du Bytecode, etc...)

              Est-ce la bonne liste d'épreuves pour un langage de programmation suffisamment rapide ?

              Merci de votre aide au passage



              • Partager sur Facebook
              • Partager sur Twitter
                22 juin 2018 à 18:25:58

                string axax(string input){
                    return input.substr(1, input.length() - 1);
                }

                Ici, nous avons plusieurs procédures :

                • Création d'une fonction (axax) avec un paramètre du type string ;
                • Appel de la méthode substr dont les deux paramètres sont les délimiteurs de la sous-chaine ;
                • Appel de la méthode length ;
                • Renvois de valeur.

                Voici à quoi pourrait ressembler un bytecode (asm-like) :

                axax:
                    ; Début de la fonction
                    push ebp
                    mov ebp, esp
                    ; On prend l'argument de la fonction (input)
                    mov BYTE [ebp-2], edi
                    pop edi
                    ; On définit le 1er argument de la méthode substr
                    mov DWORD edi, 1
                    ; On définit le 2ème argument de la méthode substr
                    mov BYTE esi, [ebp-2]
                    call length()
                    pop esi
                    mov esi, [eax]
                    sub esi, 1
                    ; On appel la méthode substr
                    call substr
                    pop edi
                    pop esi
                    ; La valeur renvoyée par substr est stockée dans 'eax'
                    mov esp, ebp
                    pop ebp
                    ; Fin de la fonction
                    ret

                (Enfin, c'est à quelques choses prêt, juste pour illustrer, en vrai ça ne fonctionne pas)

                Si on veut appeler cette fonction, on ferra :

                mov BYTE edi, "_text_"
                call axax
                ; eax contient maintenant "text"

                Comme ça, tu as une petite idée.

                Personnellement, je préfère un bytecode à la syntaxe proche de l'assembleur. Il n'y a aucun niveau d'abstraction, et en matière de rapidité, c'est largement acceptable si le code est natif. Par exemple, un programme bytecode 100% asm-like sera presque indépendant, et plus d'être intuitif.

                Tu pourrais intégrer des fonctions standard dans un fichier de bytecode qui s'ajoute automatiquement à ton programme par exemple. En effet, il est préférable que la majorité des fonctions soient codé dans le langage, et non pas dans l'interprète. Tu perdrais en rapidité d’exécution pour des choses bête.

                Voici comment mon compilateur code -> bytecode opère (pour l'instant) :

                La 1ere étape que tu décris se nomme "tokenization". Tu dis qu'elle enlève les commentaires et lignes vide, mais si elle ne fait que ça, c'est une perte de performance : tu lis le code plusieurs fois. Mais j'imagine qu'elle se charge également de transformer le code en jetons.

                Concernant la 2ème étape, je pense que tu vas trop vite. Tu dis que tu analyses déjà le code, les arguments, des types, ... Mais il faudrait, avant ça, restructurer le code sous la forme d'un arbre syntaxique abstrait (AST), exemple :

                var value = 4*(5-2)

                Donnerait :

                 value
                   |
                   *
                  / \
                 5   -
                    / \
                   3   2

                Et pour analyser ça (c'est une représentation, en vrai, on ne s'embête pas avec ce design), on ferrait branche par branche entre chaque noeuds du programme :

                (* 4 (- 5 2)) → value

                Je préfère transformer les AST mathématique en S-expression, c'est beaucoup plus simple pour évaluer le bytecode à générer :

                mov value, 5
                sub value, 2
                mult value, 4
                ; value = 12

                Ensuite, lors de l'étape d'optimisation, on ferrait par exemple ceci directement :

                mov value, 12

                Simplement parce qu'il n'y a aucunes inconnues dans l'expression. Cela évitera à ton programme de passer par des procédés intermédiaire inutile.

                Généralement, les étapes à suivre les voici :

                 - Tokenization ;
                 - Lexing ;
                 - Parsing (AST building) ;
                 - Analyse ;
                 - Bytecode generation ;
                 - Bytecode optimisation ;
                 - Bytecode executing.

                Concernant ton bytecode :

                P:axax, "test"
                 
                axax {
                    R:arg[0].substr(1, arg[0].length() - 1)}
                }
                 
                substr {
                    blabla
                }
                 
                length {
                    blabla
                }

                Je ne trouve pas qu'il soit très optimal, ni explicite au niveau des appels / déclarations de fonction. Il serait préférable de le modifier, ou d'adopter un autre format de bytecode.

                -
                Edité par vanaur 22 juin 2018 à 18:48:38

                • Partager sur Facebook
                • Partager sur Twitter

                Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                  22 juin 2018 à 20:36:56

                  D'accord, je vais m'intéresser un peu plus aux AST et à la création de Jetons.

                  1) Est-ce que mon bytecode peut être de l'assembleur, afin de pouvoir être compilé directement en langage machine par un compilateur d'assembleur extérieur ?

                  2) Quels types d'assembleurs existe-t-il ?

                  3) Quel type d'assembleur dois-je choisir pour que le logiciel généré soit compatible en x86 et x64 ?

                  4) Si je mets mon bytecode généré (qui est de l'assembleur imaginons) dans un fichier texte sur une distribution Linux, pourrais-je l'executer à partir de Linux ? Avec un compilateur/interpréteur d'assembleur ?

                  Merci de votre aide, je ne savais pas que créer un langage de programmation était aussi grand. Tout me parait beaucoup moins complexe (du moins à la théorie lol).

                  • Partager sur Facebook
                  • Partager sur Twitter
                    22 juin 2018 à 22:44:07

                    jomtek. a écrit:

                    1) Est-ce que mon bytecode peut être de l'assembleur, afin de pouvoir être compilé directement en langage machine par un compilateur d'assembleur extérieur ?

                    Bien sûr. Ce sera alors un vrai compilateur, et plus un interpréteur. Mais dans ce cas, il n'y a plus de bytecode.

                    jomtek. a écrit:

                    2) Quels types d'assembleurs existe-t-il ?

                    Un assembleur, c'est un programme qui ne fait que masquer les instructions opcode par des mots-clefs et une syntaxe compréhensible par l'homme. Donc il en existe autant qu'il y a de processeur différent. Généralement, les compilateurs visent l'assembleur x86-64 d'Intel, parce que c'est le plus répandu, mais en tant que débutant, je te conseillerais un langage assembleur plus simple, mais tout aussi performant. NASM par exemple.

                    jomtek. a écrit:

                    3) Quel type d'assembleur dois-je choisir pour que le logiciel généré soit compatible en x86 et x64 ?

                    Justement, un assembleur qui vise l'architecture x86-64, comme dit plus haut.

                    jomtek. a écrit:

                    4) Si je mets mon bytecode généré (qui est de l'assembleur imaginons) dans un fichier texte sur une distribution Linux, pourrais-je l'executer à partir de Linux ? Avec un compilateur/interpréteur d'assembleur ?

                    Il faudrait utiliser un assembleur multiplate-forme. Je ne sais pas trop pour les systèmes UNIX, mais je pense que NASM est également compatible.

                    jomtek. a écrit:

                    Merci de votre aide, je ne savais pas que créer un langage de programmation était aussi grand. Tout me parait beaucoup moins complexe (du moins à la théorie lol).

                    Et tu n'es encore qu'au balbutiement ^^ . Créer un langage de programmation, ce n'est pas trop compliqué. Mais, lorsque tu veux l'implémenter, là c'est plus difficile. Généralement, la pratique ne correspond pas avec la théorie pour ce type de projet :

                    En théorie, l'utilisateur écrira du code qui compilera ensuite un fichier exécutable selon l'architecture. En pratique, il faut déceler toutes les éventualités que le développeur pourrait commettre, avoir une solution à tout pour ne pas le planter avec le compilateur; il faudra également adapter le code du compilateur, du langage cible et des bibliothèques selon le système d'exploitation ciblé. Il faudra faire attention à bien gérer la mémoire, et les fuites de mémoire avec l'allocation dynamique (malloc/calloc/realloc/free) ainsi qu'avec les objets dérivés de classes/code abstraites. Mesurer chaque taille qu'une valeur prendra dans un registre ou en mémoire, et faire attention au stack overflow/underflow avec les registres qui ont tous une taille limitée selon la taille des données à manipuler. Sans compter les exceptions que le programme pourrait générer; si on ne communique pas avec le système, on ne saura pas quelles exceptions ont étés commises; ce qui doit être su par le développeur afin qu'il ne perde pas de temps à chercher où une potentielle erreur pourrait naître. Il y a également les bibliothèques et DLL hôte qui doivent être utilisé de manière adapté avec l'éventuelle technologie SSSO (Same Source Same Output) que devrait offrir le langage et le compilateur. Il serait en effet contre-productif d'avoir deux codes différents entre Windows et Linux pour un programme Hello, world!

                    Enfin, je n'ai fait qu'effleurer les grandes problématiques du sujet...

                    Si je me souviens bien, ton compilateur est/sera codé en VB.NET ? Si tu désires l'utiliser sur Linux ou Mac, cela ne risque pas de fonctionner. Il faudrait donc penser à un autre langage. Mais je pense que tu as déjà eu cette discussion.

                    N'hésite pas non-plus à aller jeter un coup d’œil aux projets existant sur GitHub. Les compilateurs GNU y sont tous en open source. C'est l'occasion de voir comment ça fonctionne vraiment.

                    • Partager sur Facebook
                    • Partager sur Twitter

                    Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                      22 juin 2018 à 23:17:30

                      Tu as pratiquement tout dit. Et je t'en remercie.

                      Au passage une chose que je tenais vraiment à faire : m'excuser des messages sur mes anciens topics, j'étais assez stupide. Voilà je veux juste m'excuser des âneries que j'ai dites.

                      Pour le VB.NET, c'est l'un des seuls langages que je connais réellement au point d'y créer un compilateur.

                      Je suis actuellement entrain d'apprendre le C++.

                      Au fait je veux juste que les programmes développés sous Windows puissent être exécutables sur pratiquement toutes les plateformes (ou la plupart de celles qui sont assez connues).

                      Tu m'as énormément aidé, et je compte continuer ce projet encore et encore lol x)

                      Je vous tiendrai informés des progrès (s'il y'en a bien-sûr).

                      vanaur a écrit:

                      jomtek. a écrit:

                      1) Est-ce que mon bytecode peut être de l'assembleur, afin de pouvoir être compilé directement en langage machine par un compilateur d'assembleur extérieur ?

                      Bien sûr. Ce sera alors un vrai compilateur, et plus un interpréteur. Mais dans ce cas, il n'y a plus de bytecode.

                      jomtek. a écrit:

                      2) Quels types d'assembleurs existe-t-il ?

                      Un assembleur, c'est un programme qui ne fait que masquer les instructions opcode par des mots-clefs et une syntaxe compréhensible par l'homme. Donc il en existe autant qu'il y a de processeur différent. Généralement, les compilateurs visent l'assembleur x86-64 d'Intel, parce que c'est le plus répandu, mais en tant que débutant, je te conseillerais un langage assembleur plus simple, mais tout aussi performant. NASM par exemple.

                      jomtek. a écrit:

                      3) Quel type d'assembleur dois-je choisir pour que le logiciel généré soit compatible en x86 et x64 ?

                      Justement, un assembleur qui vise l'architecture x86-64, comme dit plus haut.

                      jomtek. a écrit:

                      4) Si je mets mon bytecode généré (qui est de l'assembleur imaginons) dans un fichier texte sur une distribution Linux, pourrais-je l'executer à partir de Linux ? Avec un compilateur/interpréteur d'assembleur ?

                      Il faudrait utiliser un assembleur multiplate-forme. Je ne sais pas trop pour les systèmes UNIX, mais je pense que NASM est également compatible.

                      jomtek. a écrit:

                      Merci de votre aide, je ne savais pas que créer un langage de programmation était aussi grand. Tout me parait beaucoup moins complexe (du moins à la théorie lol).

                      Et tu n'es encore qu'au balbutiement ^^ . Créer un langage de programmation, ce n'est pas trop compliqué. Mais, lorsque tu veux l'implémenter, là c'est plus difficile. Généralement, la pratique ne correspond pas avec la théorie pour ce type de projet :

                      En théorie, l'utilisateur écrira du code qui compilera ensuite un fichier exécutable selon l'architecture. En pratique, il faut déceler toutes les éventualités que le développeur pourrait commettre, avoir une solution à tout pour ne pas le planter avec le compilateur; il faudra également adapter le code du compilateur, du langage cible et des bibliothèques selon le système d'exploitation ciblé. Il faudra faire attention à bien gérer la mémoire, et les fuites de mémoire avec l'allocation dynamique (malloc/calloc/realloc/free) ainsi qu'avec les objets dérivés de classes/code abstraites. Mesurer chaque taille qu'une valeur prendra dans un registre ou en mémoire, et faire attention au stack overflow/underflow avec les registres qui ont tous une taille limitée selon la taille des données à manipuler. Sans compter les exceptions que le programme pourrait générer; si on ne communique pas avec le système, on ne saura pas quelles exceptions ont étés commises; ce qui doit être su par le développeur afin qu'il ne perde pas de temps à chercher où une potentielle erreur pourrait naître. Il y a également les bibliothèques et DLL hôte qui doivent être utilisé de manière adapté avec l'éventuelle technologie SSSO (Same Source Same Output) que devrait offrir le langage et le compilateur. Il serait en effet contre-productif d'avoir deux codes différents entre Windows et Linux pour un programme Hello, world!

                      Enfin, je n'ai fait qu'effleurer les grandes problématiques du sujet...

                      Si je me souviens bien, ton compilateur est/sera codé en VB.NET ? Si tu désires l'utiliser sur Linux ou Mac, cela ne risque pas de fonctionner. Il faudrait donc penser à un autre langage. Mais je pense que tu as déjà eu cette discussion.

                      N'hésite pas non-plus à aller jeter un coup d’œil aux projets existant sur GitHub. Les compilateurs GNU y sont tous en open source. C'est l'occasion de voir comment ça fonctionne vraiment.



                      • Partager sur Facebook
                      • Partager sur Twitter
                        23 juin 2018 à 0:16:34

                        jomtek. a écrit:

                        Au passage une chose que je tenais vraiment à faire : m'excuser des messages sur mes anciens topics, j'étais assez stupide. Voilà je veux juste m'excuser des âneries que j'ai dites.

                        Il n'y a pas raison de s'excuser :) Si tu savais les conneries que je fessais et disais à l'époque :-° :

                        Des erreurs et des conneries, tu n'es pas le seul à en faire ;)

                        Sur-ce, bonne soirée, et n'oublie pas de mettre ton sujet [résolu].

                        • Partager sur Facebook
                        • Partager sur Twitter

                        Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                          23 juin 2018 à 2:25:59

                          vanaur a écrit:

                          jomtek. a écrit:

                          Au passage une chose que je tenais vraiment à faire : m'excuser des messages sur mes anciens topics, j'étais assez stupide. Voilà je veux juste m'excuser des âneries que j'ai dites.

                          Il n'y a pas raison de s'excuser :) Si tu savais les conneries que je fessais et disais à l'époque :-° :

                          Des erreurs et des conneries, tu n'es pas le seul à en faire ;)

                          Sur-ce, bonne soirée, et n'oublie pas de mettre ton sujet [résolu].


                          Merci

                          ça montre que pour l'instant on passe par le même chemin :D

                          (Moi aussi je suis passé par pire que toi)

                          https://www.youtube.com/watch?v=Y6HGaNj0kEw

                          sans vouloir te déranger, ça te dirai de parler sur discord ?

                          Je te l'envoie en mp ;)

                          -
                          Edité par jomtek. 23 juin 2018 à 2:27:48

                          • Partager sur Facebook
                          • Partager sur Twitter
                            23 juin 2018 à 12:35:47

                            vanaur a écrit:

                            Personnellement, je préfère un bytecode à la syntaxe proche de l'assembleur. Il n'y a aucun niveau d'abstraction, et en matière de rapidité, c'est largement acceptable si le code est natif. Par exemple, un programme bytecode 100% asm-like sera presque indépendant, et plus d'être intuitif.

                            C'est très péremptoire en plus d'être discutable. Si le code est interprété, avoir un bytecode qui est à grain très fin implique de passer plus de temps à faire des échanges entre "machine virtuelle" et exécution native, et ce yoyo coûte du temps (plein). Alors qu'avoir un bytecode haut niveau permet de compenser ce temps en passant de "longues" périodes d'exécution dans le monde natif avant de retourner voir ce qu'il y a à faire ensuite dans le bytecode.

                            vanaur a écrit:

                            Tu pourrais intégrer des fonctions standard dans un fichier de bytecode qui s'ajoute automatiquement à ton programme par exemple. En effet, il est préférable que la majorité des fonctions soient codé dans le langage, et non pas dans l'interprète. Tu perdrais en rapidité d’exécution pour des choses bête.

                            Euh non, clairement pas, ton interprète à juste à faire un call natif à une bibliothèque déjà compilée. Entre un appel dans une bibliothèque qui va directement aller exécuter du code natif et une interprétation de la fonction que tu auras écrite puis passée sous la forme d'un bytecode, tu vas clairement payer plus cher à faire l'interprétation du bytecode. C'est pas pour rien que l'on essaie d'avoir des builtin pour tous ces éléments là.

                            vanaur a écrit:

                            jomtek. a écrit:

                            1) Est-ce que mon bytecode peut être de l'assembleur, afin de pouvoir être compilé directement en langage machine par un compilateur d'assembleur extérieur ?

                            Bien sûr. Ce sera alors un vrai compilateur, et plus un interpréteur. Mais dans ce cas, il n'y a plus de bytecode.

                            Et LLVM c'est quoi ?

                            vanaur a écrit:

                            Généralement, les compilateurs visent l'assembleur x86-64 d'Intel, parce que c'est le plus répandu, mais en tant que débutant, je te conseillerais un langage assembleur plus simple, mais tout aussi performant. NASM par exemple.

                            Ca n'a pas de sens de parler des performances d'un langage assembleur puisque c'est justement un binding d'op-codes.

                            vanaur a écrit:

                            jomtek. a écrit:

                            4) Si je mets mon bytecode généré (qui est de l'assembleur imaginons) dans un fichier texte sur une distribution Linux, pourrais-je l'executer à partir de Linux ? Avec un compilateur/interpréteur d'assembleur ?

                            Il faudrait utiliser un assembleur multiplate-forme. Je ne sais pas trop pour les systèmes UNIX, mais je pense que NASM est également compatible.

                            Tada ! On vient de rassembler le pire des deux mondes. On doit faire une compilation vers un bytecode qui n'est pas multi-plateforme. Comme ça, on doit recompiler sur la plateforme et payer l'interprétation et se taper l'écriture de deux backend pour la générations du bytecode (plateform-specific) et se taper l'écriture d'interpréteur spécifiques pour le bytecode spécifique.

                            Bref : si vous voulez faire du bytecode faites un trucs qui n'est pas platform-specific, donc PAS du x86-64. Ca sert à rien de s'ajouter plus de problèmes que nécessaire. Une manière d'être peinard, c'est d'utiliser LLVM, mais c'est pas la seule. Typiquement, se faire un bytecode maison, c'est plutôt tranquille.

                            vanaur a écrit:

                            En théorie, l'utilisateur écrira du code qui compilera [...] Il serait en effet contre-productif d'avoir deux codes différents entre Windows et Linux pour un programme Hello, world!

                            Bon sur ce gros paragraphe, on est un peu hors de propos. C'est complètement dépendant du langage utilisé pour l'implémentation du traducteur/interpréteur bytecode et du bytecode lui même. Si on fait un bytecode façon machine P-Code, les registres, on s'en tamponne de même que les questions de stack, et si on utilise un langage comme OCaml ou Haskell pour réaliser l'implémentation, pas de soucis avec l'allocation mémoire, on pourra compiler en multi-plateforme sans avoir à changer des choses, on aura probablement pas besoin d'objet, et la mécanique d'exception ou de monades nous permettra de récupérer les erreurs très simplement.

                            Bref, là c'est inventer des problèmes qu'on n'est franchement pas obligé de traiter.

                            -
                            Edité par Ksass`Peuk 23 juin 2018 à 12:38:32

                            • Partager sur Facebook
                            • Partager sur Twitter

                            Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                              23 juin 2018 à 13:05:11

                              vanaur a écrit: > - Tokenization ;

                              • Lexing ;
                              • Parsing (AST building) ;
                              • Analyse ;
                              • Bytecode generation ;
                              • Bytecode optimisation ;
                              • Bytecode executing.

                              Tu fais quelle différence entre lexer et tokenisation ?

                              Sinon, jomtek, si tu veux voir des exemples de représentations bytecode tu peux regarder du côté des three address code, notamment les quadruples.

                              -
                              Edité par entwanne 23 juin 2018 à 13:05:31

                              • Partager sur Facebook
                              • Partager sur Twitter
                                23 juin 2018 à 14:13:55

                                Personnellement, après réflexion, je pense que la tokenisation vient avant le Lexing.

                                De plus, je ne pense pas qu'il y'a besoin d'un arbre astrait (AST).

                                • La Tokenisation

                                Donc je pense que le Lexer et la Tokenisation vont de pair. Le Tokenizer va tenter de créer une "liste" (comme vous le voyez en haut), et si il n'y parvient pas, alors là le Lexer va rentrer en jeu : Il va trouver l'erreur dans la liste et afficher une erreur de débogage au programmeur. Si il y parvient, le Lexer va ensuite analyser la logique de la suite de Tokens.

                                Exemple

                                fqkgei("a" / "a"); // ligne de code erronée
                                
                                /*
                                
                                		Token List
                                	[id: fqkgei] - Cette ID n'est pas reconnue en tant que void ni en tant que fonction ni en tant que variable. Le Lexer est censé retourner une erreur.
                                	[str: "a"] - Cette string est correctement écrite (avec les guillemets respectifs)
                                	[op: /] - Cet opérateur est correctement écrit (il figure dans la liste d'opérateurs Lazen), mais le token avant lui n'est pas un long/int/double/décimal. Le Lexer est censé retourner une erreur.
                                	[str: "a"] - Cette string est correctement écrite (avec les guillemets respectifs), mais le token avant elle est un opérateur, l'opérateur ne fonctionne pas avec les strings. Le Lexer est censé retourner une erreur.
                                
                                
                                */

                                Là, notre Lexer a fait son boulot. Il a analysé le code entièrement à l'aide de la liste de Tokens qu'il a créé.

                                Une fois que la liste de tokens est analysée (sans aucun erreur), alors là entre en jeu le "compilateur bytecode".

                                Après l'analyse de chaque ligne, on obtient un résultat comme ceci qui sera transmit au compilateur bytecode :

                                [id: print];[numeric: 5];[op: /];[numeric: 5]

                                Pour chaque ligne bien évidemment...

                                • Le compilateur bytecode va le décortiquer ligne par ligne afin de le compiler directement
                                • Il va ensuite tenter de calculer chaque expression au préalable (on appelle ça l'optimisation du bytecode). Il va calculer uniquement les expressions qui ne contiennent pas de paramètres pouvant changer (paramètres pouvant changer = fichier, date, temps, contenu d'un site internet, etc...)
                                • Il va ensuite compiler (ajouter au résultat bytecode) chaque fonction/void se trouvant dans le code sous cette forme :
                                • print(axax());
                                  
                                  string axax(){
                                  	return "je suis une fonction !";
                                  }
                                  
                                  /*
                                  	P:Func:axax
                                  	axax{
                                  		R:je suis une fonction !
                                  	}
                                  */
                                • Il va ensuite relier chaque fonction/void implémentée directement dans Lazen à des mots-clés courts, exemple
                                • print("hey !");
                                  
                                  /*
                                  	P:hey!
                                  	
                                  	On peut voir que la traduction du mot-clé "print" est égale à "P" en bytecode.
                                  	Cela va permettre à l'interpréteur bytecode d'offrir un temps d'execution beaucoup plus rapide que de l'interprétation directe.
                                  	C'est d'ailleurs le but-même du bytecode. Offrir une meilleure performance.
                                  */

                                Une fois que notre bytecode est compilé avec succès, on a un petit bytecode tout chaud et tout prêt à être interprété par l'interpréteur bytecode.
                                print(5 / 5); // ligne de code correcte
                                
                                /*
                                		Token List
                                	[id: print] - L'identifieur est reconnu par le Lexer
                                	[numeric: 5] - Le numeric est correctement écrit.
                                	[op: /] - L'opérateur est correctement écrit (il figure dans la liste des opérateurs Lazen), le Lexer détecte qu'il se trouve entre deux numerics, tout va bien.
                                	[numeric: 5] - Le numeric est correctement écrit.
                                
                                	=
                                
                                	P:5/5 - Si notre traducteur bytecode détecte que l'expression (5 / 5) est invariable si on la recalcule (ne contient pas de lecture de fichier, de date/temps ou autres paramètres pouvant changer)
                                			alors il va l'ajouter à la liste d'expressions précalculées (ce qui va énormément aider pour les boucles, exemple en bas, dans ce cas on parle d'optimisation de bytecode)
                                	exit
                                
                                
                                
                                	Liste d'expressions pré-calculées
                                
                                		5/5=1
                                		etc...
                                */

                                 (Regardez la "Liste d'expressions pré-calculées" en bas du code)

                                 Notre résultat final en bytecode est celui-ci :

                                P:5/5
                                exit

                                Maintenant entre en jeu la dernière étape, "la machine virtuelle (l'interpréteur bytecode)". 

                                Il est plus efficace de créer l'interpréteur bytecode en un langage natif (disons C++, Haskell, etc...)

                                Il va fonctionner exactement comme un interpréteur de script direct, sauf qu'il sera plus rapide, dû par exemple à la liste des expressions pré-calculées et aux mots-clés beaucoup plus courts que le code original. (Il va lire le bytecode ligne par ligne et l'interpréter directement).

                                Et voilà !


                                Et pour finir, quels sont les avantages de la compilation bytecode -> interprétation bytecode par rapport à l'interprétation script directe ?

                                • Le bytecode va nous offrir un programme plus léger et soigné.
                                • Créer un compilateur bytecode est une expérience très enrichissante pour son programmeur.
                                • Un langage dont le code est compilé en bytecode est ou du moins semble plus sérieux qu'un langage dont le code est interprété directement. (à mon avis)
                                • Le bytecode va nous offrir une performance surprenante par rapport à l'interprétation directe.
                                • Si vous pensez à réaliser un compilateur bytecode, vous pourrez en profiter pour donner la syntaxe d'un assembleur multi plate-forme (NASM par exemple) à votre bytecode, afin que le programme (en l'occurence l'assembleur) (fichier .asm) que votre langage rend puisse être interprété/compilé sur n'importe quel OS supportant l'assembleur NASM. (attention à la différence entre x86 et x64), vous pourrez aussi compiler le fichier .asm que votre langage rendra en langage machine à l'aide d'un compilateur d'assembleur extérieur (le compilateur de l'assembleur NASM par exemple) afin que lorsque votre langage compile le code, qu'il rende un .exe tout chaud.
                                          Merci de m'avoir lu ! Je vous tiendrai informés ;)

                                entwanne a écrit:

                                vanaur a écrit: > - Tokenization ;

                                • Lexing ;
                                • Parsing (AST building) ;
                                • Analyse ;
                                • Bytecode generation ;
                                • Bytecode optimisation ;
                                • Bytecode executing.

                                Tu fais quelle différence entre lexer et tokenisation ?

                                Sinon, jomtek, si tu veux voir des exemples de représentations bytecode tu peux regarder du côté des three address code, notamment les quadruples.

                                -
                                Edité par entwanne il y a 9 minutes



                                -
                                Edité par jomtek. 23 juin 2018 à 14:28:14

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  23 juin 2018 à 15:08:44

                                  Ksass`Peuk a écrit:

                                  Euh non, clairement pas, ton interprète à juste à faire un call natif à une bibliothèque déjà compilée. Entre un appel dans une bibliothèque qui va directement aller exécuter du code natif et une interprétation de la fonction que tu auras écrite puis passée sous la forme d'un bytecode, tu vas clairement payer plus cher à faire l'interprétation du bytecode. C'est pas pour rien que l'on essaie d'avoir des builtin pour tous ces éléments là.

                                  L'op ne semblait pas parler d'une bibliothèque native pré-compilé, mais de fonctions pré-inscrite dans le code d'analyse de la VM. Raison pour laquelle j'ai conseillé d'écrire les fonctions standard dans un fichier d'entête.

                                  Ksass`Peuk a écrit:

                                  Et LLVM c'est quoi ?

                                  Je parlais de bytecode à interpréter. LLVM use effectivement d'un langage intermédiaire pour générer ces exécutables. Il existe une multitude de langages qui ont le même principe d'ailleurs, ne serait-ce que tout les .NET.

                                  Ksass`Peuk a écrit:

                                  Ca n'a pas de sens de parler des performances d'un langage assembleur puisque c'est justement un binding d'op-codes.

                                  Je sais. Mais c'est pour informer l'op que peu importe l'assembleur utilisé, il n'y a pas de différences de performances (puisque c'est du binding comme tu l'as dit). C'est utile à savoir pour quelqu'un qui n'a jamais touché à l'assembleur je pense.

                                  Ksass`Peuk a écrit:

                                  Bon sur ce gros paragraphe, on est un peu hors de propos. C'est complètement dépendant du langage utilisé pour l'implémentation du traducteur/interpréteur bytecode et du bytecode lui même. Si on fait un bytecode façon machine P-Code, les registres, on s'en tamponne de même que les questions de stack, et si on utilise un langage comme OCaml ou Haskell pour réaliser l'implémentation, pas de soucis avec l'allocation mémoire, on pourra compiler en multi-plateforme sans avoir à changer des choses, on aura probablement pas besoin d'objet, et la mécanique d'exception ou de monades nous permettra de récupérer les erreurs très simplement.

                                  Bref, là c'est inventer des problèmes qu'on n'est franchement pas obligé de traiter.

                                  Je ne parlais plus de bytecode à ce moment-là, mais aux "problèmes" auquel il faut faire attention lors de la réalisation d'un compilateur dont le langage cible est directement l'assembleur. Donc, si, dans ce cas-là, les registres, la mémoire, le stack, ... c'est important.

                                  entwanne a écrit:

                                  Tu fais quelle différence entre lexer et tokenisation ?

                                  Il y a bel et bien une différence :

                                  • Tokenization : découpe chaque jeton du code d'origine ;
                                  • Lexing : attribue une unité lexicale aux jetons précédemment évalués par le tokenizer.

                                  Généralement, on couple les deux à la fois. Ce qui fait qu'il n'y a plus vraiment de différences entre tokenization et lexing. Alors on peut voir ça comme des phases : d'abord on prend un jeton (tokenization), et ensuite on lui attribue une unité lexicale (lexing).

                                  jomtek. a écrit:

                                  De plus, je ne pense pas qu'il y'a besoin d'un arbre astrait (AST).

                                  C'est une façon de faire à laquelle on peut effectivement se passer, mais il faut avoir de bonnes raisons de le faire parce que c'est un des meilleurs moyens de manipuler les jetons à ma connaissance.

                                  @jometk.: Tu confonds lexing et parsing. Un lexer n'effectue généralement pas d'analyse très poussé des jetons. Le lexer va justement se charger d'analyser les jetons et la sémantique en construisant un potentiel AST.

                                  @jometk.: À quelle étape comptes-tu "pré-calculer" tes expressions en fait ?

                                  Normalement, une compilation suit ce schéma :

                                  À la place des carrés rouges, si tu décides finalement de faire un interprète, ce sera ta VM qui entrera en jeu.

                                  -
                                  Edité par vanaur 23 juin 2018 à 16:03:49

                                  • Partager sur Facebook
                                  • Partager sur Twitter

                                  Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                                    23 juin 2018 à 17:59:35

                                    vanaur a écrit: > Il y a bel et bien une différence : > > Tokenization : découpe chaque jeton du code d'origine ; > Lexing : attribue une unité lexicale aux jetons précédemment évalués par le tokenizer. > > Généralement, on couple les deux à la fois. Ce qui fait qu'il n'y a plus vraiment de différences entre tokenization et lexing. Alors on peut voir ça comme des phases : d'abord on prend un jeton (tokenization), et ensuite on lui attribue une unité lexicale (lexing).

                                    Que sont tes tokens s'ils ne sont pas des unités lexicales ?

                                    L'analyse lexicale, ou tokenisation, c'est le découpage de l'entrée en jetons par reconnaissance de certains motifs associés à des catégories lexicales.

                                    D'ailleurs je ne vois pas ce qu'un AST viendrait faire dans le lexer.

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      23 juin 2018 à 18:42:57

                                      entwanne a écrit:

                                      Que sont tes tokens s'ils ne sont pas des unités lexicales ?

                                      S'ils ne sont pas reconnus, ont peu leurs attribuer un statut "unknown", s'ils sont des nombres, des identifiants, des chaines de caractères, des symboles ou des caractères simples, on leur attribue une identité correspondante. J'ai suivi tout un PDF là-dessus, je n'ai pas su le retrouver (quelle coïncidence me direz-vous... je pense qu'il se nommait "Understanding and design of a C compiler", ou quelque chose comme ça), et s'était clairement expliqué et détaillé. Il faisait d'ailleurs bien une petite distinction entre Tokenizer et Lexer (j'ai d'ailleurs trouvé ça, qui l'explique plus ou moins de la même manière que moi).

                                      Fondamentalement parlant, lexer et tokenizer, c'est en effet la même chose. À la différence près qu'un lexer attribue normalement un contexte supplémentaire au jeton. Or, le tokenizer (en son propre terme), lui, cherche là où il pourrait séparer les lexèmes (espaces, symboles, ...)

                                      Et, si vous voulez savoir, je réalise moi-même un compilateur. J'utilise un tokenizer ET un lexer. Il fonctionne parfaitement ensemble, et Visual Studio ne crache pas sur les noms des termes utilisés.

                                      entwanne a écrit:

                                      D'ailleurs je ne vois pas ce qu'un AST viendrait faire dans le lexer.

                                      Faute de frappe, excusez-moi, je voulais parler de parser évidemment...

                                      Ca doit être la fatigue >_<

                                      -
                                      Edité par vanaur 23 juin 2018 à 18:46:59

                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                                        23 juin 2018 à 18:58:13

                                        Mais la définition donnée dans le lien que tu cites est imprécise : le tokenizer n'est pas là pour reconnaître les motifs de ce qui sépare les mots, mais bien ceux des mots eux-mêmes. Les séparateurs peuvent d'ailleurs former des tokens comme les autres, ça dépend de la sémantique qu'on veut leur accorder.

                                        Aussi, si l'on s'en tient à ce qui est dit le lexer ne serait pas une étape de compilation suivant la tokenisation, mais simplement un tokenizer plus évolué.

                                        Je peux te renvoyer vers le dragon book lui aussi cité, qui explique cela assez bien.

                                        -
                                        Edité par entwanne 23 juin 2018 à 18:59:50

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          23 juin 2018 à 19:14:32

                                          entwanne a écrit:

                                          Les séparateurs peuvent d'ailleurs former des tokens comme les autres, ça dépend de la sémantique qu'on veut leur accorder.

                                          En effet, cela dépend des cas.

                                          entwanne a écrit:

                                          Aussi, si l'on s'en tient à ce qui est dit le lexer ne serait pas une étape de compilation suivant la tokenisation, mais simplement un tokenizer plus évolué.

                                          Là je suis d'accord :) et donc, si on repart de ce que tu viens de dire : tokenizer et lexer, c'est plus ou moins différent. Mais dans un lexer, on retrouvera toujours le travail qu'effectuerait un tokenizer.

                                          J'ai le Dragon Book sous la main, voilà ce qu'il dit :

                                          Et :

                                          -
                                          Edité par vanaur 23 juin 2018 à 19:15:04

                                          • Partager sur Facebook
                                          • Partager sur Twitter

                                          Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                                            23 juin 2018 à 19:32:15

                                            vanaur a écrit: > et donc, si on repart de ce que tu viens de dire : tokenizer et lexer, c'est plus ou moins différent. Mais dans un lexer, on retrouvera toujours le travail qu'effectuerait un tokenizer.

                                            Ce n'est pas moi qui le dit, je reprends des propos avec lesquels je ne suis pas forcément entièrement d'accord.

                                            Et ce n'est pas parce que les deux notions seraient différentes qu'il s'agirait pour autant de deux étapes distinctes et consécutives lors de la compilation, puisque si l'on s'accordait sur ces définitions il n'y aurait pas de simplement tokenisation mais directement une analyse lexicale.

                                            Aussi, j'ai l'impression que tu colles des passages du dragon book sans les avoir lus. La définition associée au scanning ne décrit pas un tokenizer et dit même explicitement que ce n'est pas cela. Quant au second passage, il décrit juste le travail de tokenisation réalisé par le lexer.

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              23 juin 2018 à 20:04:27

                                              Je n'ai en effet pas terminé de lire le Dragon Book, mais j'ai bien lu ces passages. Il n'emploie d'ailleurs nulle part le terme "tokenizer".

                                              Mais bon, je suis d'accord avec toi pour dire que Lexer et tokenizer ne sont pas nécessairement deux étapes distinctes dans la réalisation d'un compilateur : on les retrouve très généralement ensemble sous la forme d'un seul procédé.

                                              -
                                              Edité par vanaur 23 juin 2018 à 20:05:40

                                              • Partager sur Facebook
                                              • Partager sur Twitter

                                              Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                                                23 juin 2018 à 20:52:40

                                                vanaur a écrit:

                                                @jometk.: À quelle étape comptes-tu "pré-calculer" tes expressions en fait ?

                                                --------------------------------------------------------------------------------------------

                                                Immédiatement après la génération du bytecode.

                                                Donc ça nous donne ça

                                                (Oui je sais que normalement c'est le Tokenizer qui est censé "reformuler" le code)

                                                • Code reformulator (enlever les commentaires, les lignes vides, etc...)
                                                • Tokenization (découpage) / Lexing (attribution de types)
                                                • Syntaxic Analyser / Parser (analyse logique de la suite de tokens à l'aide des types attribués par le Lexer)
                                                • Compiler (générateur de bytecode)
                                                • Bytecode Optimisation #1 (calculateur d'expression)
                                                • Bytecode Optimisation #2 (un peu plus d'optimisations sur le bytecode)
                                                • Virtual Machine Executing (interprétation du bytecode)
                                                Donnez-moi votre avis sur cette liste.

                                                -
                                                Edité par jomtek. 23 juin 2018 à 20:54:47

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  23 juin 2018 à 22:12:07

                                                  vanaur a écrit: > Je n'ai en effet pas terminé de lire le Dragon Book, mais j'ai bien lu ces passages. Il n'emploie d'ailleurs nulle part le terme "tokenizer".

                                                  Je ne sais plus, je l'ai en version papier donc ce n'est pas pratique pour trouver les occurrences d'un mot. Mais il parle bien de tokenization dans un des morceaux que tu cites.

                                                  vanaur a écrit: > Mais bon, je suis d'accord avec toi pour dire que Lexer et tokenizer ne sont pas nécessairement deux étapes distinctes dans la réalisation d'un compilateur : on les retrouve très généralement ensemble sous la forme d'un seul procédé.

                                                  Non nous ne sommes pas d'accord, puisque ce que je dis c'est que ce sont une seule et même étape, les deux noms faisant référence à exactement la même chose.

                                                  jomtek. a écrit: > Donnez-moi votre avis sur cette liste.

                                                  Je ne comprends pas ce que tu cherches à obtenir. On dirait simplement que tu essaies de plaquer çà et là les noms de concepts que tu as vus. Code donc ton interpréteur/compilateur, bytecode ou non, écris les spécifications de ton langage, consulte la littérature sur le sujet, le reste viendra naturellement.

                                                  Ça veut dire quoi « Bytecode Optimisation #2 (un peu plus d'optimisations sur le bytecode) » ? Dans un premier temps, surtout, oublie les optimisations. Celles-ci peuvent d'ailleurs se faire à toute étape de la compilation, tout dépend de ce qu'elles doivent réaliser. Il y a par exemple des optimisations qui sont plus simples à réaliser sur un AST que sur une liste d'opcodes, donc attendre la fin de la génération t'empêcherait de les appliquer au mieux.

                                                  Et dans ta liste des étapes je ne vois rien qui corresponde à la sémantique du langage.

                                                  -
                                                  Edité par entwanne 23 juin 2018 à 22:13:50

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    23 juin 2018 à 23:50:34

                                                    entwanne a écrit:

                                                    Non nous ne sommes pas d'accord, puisque ce que je dis c'est que ce sont une seule et même étape, les deux noms faisant référence à exactement la même chose.

                                                    Je ne vais pas plus m'attarder sur le sujet dans ce cas, mais pour moi, tokenizer et lexer, même si c'est assez proche au niveau de la conception, ce n'est pas exactement la même chose au niveau du principe.

                                                    @jomtek.: J'ai retrouvé quelque chose sur le bytecode qui pourrait également t'être utile. Peu importe le langage source.

                                                    -
                                                    Edité par vanaur 23 juin 2018 à 23:51:05

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter

                                                    Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                                                      24 juin 2018 à 0:52:36

                                                      Ouais, y en a un qui découpe l'entrée en jetons, et l'autre qui transforme l'entrée en tokens.

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        24 juin 2018 à 1:58:45

                                                        @vanaur

                                                        Le Tokenizer est le code qui va découper les objets de chaque ligne pour les regrouper en tant que "tokens".

                                                        Ensuite intervient le Lexer, qui va venir typer chaque Token regroupé par le Tokenizer.

                                                        Merci pour ton lien. J'ai lu un peu et je me suis arrêté car j'avais une question :

                                                        • Est-ce qu'un arbre abstrait (AST) est obligé d'avoir la forme d'un arbre ? Enfin je veux dire, est-ce que je peux le réaliser sous forme de texte, imaginons que ceci :

                                                         

                                                        Devienne cela :

                                                        Est-ce que dans la deuxième image je peux encore parler d'AST ?

                                                        Car je pourrais très bien gérer comme la dernière image le montre.

                                                        Et de plus, la génération d'un AST et d'une liste de tokens pour chaque expression ne ferait pas ne serais-ce qu'un petit peu ralentir l'exécution du code ?

                                                        @entwanne 

                                                        Je cherche à savoir si j'ai bien tout compris ce que j'ai appris grâce à ce post, car je ne me vois pas ne pas savoir ce que je suis entrain de coder (plus tard).

                                                        Je pense aussi, après avoir lu tes messages, que je vais faire une seule optimisation bytecode.

                                                        -
                                                        Edité par jomtek. 24 juin 2018 à 2:14:07

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          24 juin 2018 à 11:11:06

                                                          entwanne a écrit:

                                                          Ouais, y en a un qui découpe l'entrée en jetons, et l'autre qui transforme l'entrée en tokens.

                                                          ...

                                                          jomtek. a écrit:

                                                          Le Tokenizer est le code qui va découper les objets de chaque ligne pour les regrouper en tant que "tokens". Ensuite intervient le Lexer, qui va venir typer chaque Token regroupé par le Tokenizer.

                                                          C'est ce que je pensais aussi au début, mais @entwanne est catégorique à ce sujet (ce n'est pas une critique), et il a raison. La nuit porte conseil comme on dit. Donc, finalement, retiens que tokenizer et lexer effectuent le même travail.

                                                          jomtek. a écrit:

                                                          Est-ce qu'un arbre abstrait (AST) est obligé d'avoir la forme d'un arbre ? Enfin je veux dire, est-ce que je peux le réaliser sous forme de texte, imaginons que ceci

                                                          J'ai déjà répondu :

                                                          vanaur avait écrit:

                                                          ...Et pour analyser ça (c'est une représentation, en vrai, on ne s'embête pas avec ce design), on ferrait branche par branche entre chaque noeuds du programme...

                                                          jomtek. a écrit:

                                                          Est-ce que dans la deuxième image je peux encore parler d'AST ?

                                                          Ce n'est pas un AST, c'est typiquement le résultat du lexer (ou du tokenizer..) : Un flux de jetons possédant une unité lexical.

                                                          jomtek. a écrit:

                                                          Car je pourrais très bien gérer comme la dernière image le montre.

                                                          Tu pourrais, mais cela ne serait pas très optimal. A ce stade-ci, on fera généralement appel au parser.

                                                          jomtek. a écrit:

                                                          Et de plus, la génération d'un AST et d'une liste de tokens pour chaque expression ne ferait pas ne serais-ce qu'un petit peu ralentir l'exécution du code ?

                                                          Je ne suis pas expert en interprétation, mais il est préférable que les performances l'emportent sur la rapidité. D'ailleurs, je pense qu'une interprétation brute et directe de ce flux de jeton ne soit pas plus rapide que la réalisation d'un arbre si l'on veut que le travail soit bien fait, et cela peux même être "répréhensible".

                                                          jomtek. a écrit:

                                                          Je pense aussi, après avoir lu tes messages, que je vais faire une seule optimisation bytecode.

                                                          Comme l'a également dit entwanne, ne t'intéresse pas aux optimisations pour le moment. D'ailleurs, ce n'est pas que le bytecode qui peux être optimisé, mais tout ce qui pourrait nécessiter une optimisation.

                                                          Pour le moment, concentre-toi sur la réalisation du lexer (ou du tokenizer..), et bien sûr, assure-toi d'avoir une grammaire et une sémantique adaptées.

                                                          J'ai encore retrouvé quelque chose d'assez bien fait. Je pense qu'il pourrait t'en expliquer davantage sur les AST.

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter

                                                          Le meilleur moyen de prédire l'avenir, c'est de l'inventer | N'oubliez pas [résolu] et +1 | Excusez mon ôrtograffe, j'essaie de l'améliorer...

                                                            24 juin 2018 à 11:18:02

                                                            jomtek. a écrit: > Est-ce qu'un arbre abstrait (AST) est obligé d'avoir la forme d'un arbre ?

                                                            Oui, car dans « arbre syntaxique abstrait » il y a « arbre », tu dois donc nécessairement avoir des relations parent-enfants (non, ce n'est pas incestueux).

                                                            jomtek. a écrit: > Est-ce que dans la deuxième image je peux encore parler d'AST ?

                                                            Non, tu n'as là qu'une suite de tokens où tu ne peux pas faire la différence entre (1+2)*(3-4) et (1+(2*3))-4 ou d'autres encore.

                                                            Mais d'autres organisations linéaires peuvent prendre la forme d'un arbre, sur le principe des heapq par exemple :

                                                            [op: *]
                                                            [op: +]
                                                            [op: -]
                                                            [num: 1]
                                                            [num: 2]
                                                            [num: 3]
                                                            [num: 4]
                                                            

                                                            On voit que chaque nœud en position i (i débutant à 0) possède ses enfants en 2*i+1 et 2*i+2. Ce n'est bien sûr valable que pour des arbres binaires.

                                                            Dans ton cas, tu pourrais aussi utiliser des notations préfixées/suffixées, qui sans être des AST en conservent toutes la structures (et permettent donc de les reconstruire). Par exemple si ta suite de jetons devenait la suivante :

                                                            [num: 1]
                                                            [num: 2]
                                                            [op: +]
                                                            [num: 3]
                                                            [num: 4]
                                                            [op: -]
                                                            [op: *]
                                                            

                                                            On conserverait la priorité des opérations (et donc la structure de l'arbre).

                                                            jomtek. a écrit: > Je cherche à savoir si j'ai bien tout compris ce que j'ai appris grâce à ce post, car je ne me vois pas ne pas savoir ce que je suis entrain de coder (plus tard).

                                                            Alors la réponse est non, tes questions sur les AST le prouvent une fois de plus. Mais ce n'est pas grave, continue ton projet, trompe-toi et identifie tes erreurs.

                                                            -
                                                            Edité par entwanne 24 juin 2018 à 11:22:11

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              24 juin 2018 à 13:37:09

                                                              Merci.

                                                              Je pense que je vais me débrouiller sans arbre syntaxique.

                                                              Disons que l'expression (1+2)*(3-4)

                                                              Donnera la suite de tokens suivant

                                                              (1+2)*(3-4)
                                                              
                                                              		[dnum: (1]
                                                              		[op: +]
                                                              		[cnum: 2)]
                                                              		[op: *]
                                                              		[dnum: (3]
                                                              		[op: -]
                                                              		[cnum: 4)]
                                                              

                                                              dnum est un numéric commençant par une parenthèse

                                                              cnum est un numéric finissant par une parenthèse

                                                              et je ferais ça pour les id aussi (did, cid)

                                                              Bon je crois que je vais arrêter d'envoyer des messages jusqu'à ce que fasse la pratique.

                                                              Merci, grâce à vous j'ai une meilleure vision de la compilation (c'était mon but, mais vous m'avez tout expliqué, en français !).

                                                              entwanne a écrit:

                                                              jomtek. a écrit: > Est-ce qu'un arbre abstrait (AST) est obligé d'avoir la forme d'un arbre ?

                                                              Oui, car dans « arbre syntaxique abstrait » il y a « arbre », tu dois donc nécessairement avoir des relations parent-enfants (non, ce n'est pas incestueux).

                                                              jomtek. a écrit: > Est-ce que dans la deuxième image je peux encore parler d'AST ?

                                                              Non, tu n'as là qu'une suite de tokens où tu ne peux pas faire la différence entre (1+2)*(3-4) et (1+(2*3))-4 ou d'autres encore.

                                                              Mais d'autres organisations linéaires peuvent prendre la forme d'un arbre, sur le principe des heapq par exemple :

                                                              [op: *]
                                                              [op: +]
                                                              [op: -]
                                                              [num: 1]
                                                              [num: 2]
                                                              [num: 3]
                                                              [num: 4]
                                                              

                                                              On voit que chaque nœud en position i (i débutant à 0) possède ses enfants en 2*i+1 et 2*i+2. Ce n'est bien sûr valable que pour des arbres binaires.

                                                              Dans ton cas, tu pourrais aussi utiliser des notations préfixées/suffixées, qui sans être des AST en conservent toutes la structures (et permettent donc de les reconstruire). Par exemple si ta suite de jetons devenait la suivante :

                                                              [num: 1]
                                                              [num: 2]
                                                              [op: +]
                                                              [num: 3]
                                                              [num: 4]
                                                              [op: -]
                                                              [op: *]
                                                              

                                                              On conserverait la priorité des opérations (et donc la structure de l'arbre).

                                                              jomtek. a écrit: > Je cherche à savoir si j'ai bien tout compris ce que j'ai appris grâce à ce post, car je ne me vois pas ne pas savoir ce que je suis entrain de coder (plus tard).

                                                              Alors la réponse est non, tes questions sur les AST le prouvent une fois de plus. Mais ce n'est pas grave, continue ton projet, trompe-toi et identifie tes erreurs.

                                                              -
                                                              Edité par entwanne il y a environ 1 heure



                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Exemples De Bytecode

                                                              × 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