Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Atelier tous langages] Codez un interpréteur de Brainfuck

étudier, contribuer, discuter

Anonyme
    24 août 2008 à 11:59:04

    Haha, on lance des ateliers pendant que je suis en vacances pour pas que je poste de Perl ? :D
    Voilà un code vraiment très simple :
    #!/usr/bin/perl -w
    
    use strict;
    
    my ($icode, $idata, $length, @code, @data, $op) = (0, 0);
    
    my %ops = (
               '>' => sub { $idata++ },
               '<' => sub { $idata-- if $idata > 0 },
               '+' => sub { $data[$idata]++ },
               '-' => sub { $data[$idata]-- if $data[$idata] > 0},
               '.' => sub { print chr $data[$idata] },
               ',' => sub { $data[$idata] = ord <STDIN> },
               '[' => sub {
                   return if $data[$idata];
                   my $lvl = 1;
                   while ($lvl > 0) {
                       $icode++;
                       $lvl += ($code[$icode] eq '[' ? 1  :
                                $code[$icode] eq ']' ? -1 :
                                0);
                   }
               },
               ']' => sub { 
                   my $lvl = 1;
                   while ($lvl > 0) {
                       $icode--;
                       $lvl += ($code[$icode] eq ']' ? 1  :
                                $code[$icode] eq '[' ? -1 :
                                0);
                   }
                   $icode--;
               },
              );
    
    {
        local $_ = join '', <>;
        $length = length;
        @code = split '';
    }
    
    for (; $icode < $length; $icode++) {
        $op = $ops{$code[$icode]} and $op->();
    }
    

    Bizarrement il ne marche pas avec le code d'eelte (j'ai une succession de 0 et de 1), mais marche très bien avec celui-ci par exemple.

    Je ne comprends pas vraiment l'intérêt des zippers ou du reste par rapport à un tableau, est-ce que ça donne vraiment un gain de temps ?
    • Partager sur Facebook
    • Partager sur Twitter
      24 août 2008 à 12:07:48

      Non, ça ne donne pas de gain de temps du tout (et on en perd même dans certains cas, en gros dès qu'on veut un accès arbitraire) mais c'est fonctionnel pur (contrairement à un tableau) donc utilisable sans problème dans tous les langages fonctionnels, et l'usage pour le BF est relativement adapté.

      C'est aussi une structure persistente, ce qui permet de faire des "sauvegardes" peu chères des états précédents, ce qui peut être utile dans d'autres contextes comme la concurrence (si on veut faire des opérations atomiques).
      • Partager sur Facebook
      • Partager sur Twitter
      Anonyme
        24 août 2008 à 12:18:32

        OK, je vois. Mais pour la sauvegarde, sauvegarder la position dans le tableau (un entier tout bête) n'est pas très cher non plus, si ? (désolé si j'embête avec mes questions).
        • Partager sur Facebook
        • Partager sur Twitter
          24 août 2008 à 12:24:39

          Avec un Zipper, tu implémentes aussi une mémoire infinie beaucoup plus facilement qu'avec un tableau (et de façon beaucoup moins chère, puisque quoi qu'il en soit, aller à gauche et à droite sont en O(1), ce qui n'est pas le cas pour le tableau).
          • Partager sur Facebook
          • Partager sur Twitter
            24 août 2008 à 12:32:07

            zulon > non, parce que si tu modifies le tableau ensuite, ta sauvegarde n'a plus de sens. Enfin il te reste une position mais tu n'as plus "l'état" de la mémoire à l'instant qui t'intéressait, ce que permet de faire facilement et efficacement un zipper.

            asmanur > oui, c'est bien pour une mémoire infinie, par contre on se fait humilier pour les mémoires cycliques :p
            Ceci dit je pense que dans l'absolu la mémoire infinie est un meilleur choix que la mémoire cyclique, donc on s'y retrouve.
            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              24 août 2008 à 12:42:50

              Ah d'accord, j'avais mal compris. Tout s'explique :-° . Mais vu qu'en BF "traditionnel" le tableau où sont stockées les instructions n'est jamais modifié...
              • Partager sur Facebook
              • Partager sur Twitter
                24 août 2008 à 12:47:47

                Je parlais de l'utilisation d'un zipper pour la mémoire.

                Pour le code, effectivement ça a moins de sens, il vaudrait mieux stocker une liste dont on dépile les éléments.

                C'est d'ailleurs une suggestion plutôt amusante, je vais voir si je peux faire quelque chose.

                Edit : voilà

                let (* refutable *) right (left, curr::right) =
                  (curr::left, if right = [] then [0] else right)
                
                let left = function
                | (prev::left, right) -> (left, prev::right)
                | ([], right) -> ([], 0::right)
                
                let (* refutable *) curr (_, curr::_) = curr
                let (* refutable *) apply f (left, curr::right) = (left, f curr :: right)
                let single x = ([], [x])
                
                let id x = x
                
                let rec bf code mem = match code with
                | [] -> ()
                | '['::tl -> (fun code' -> bf code' mem)
                    (let rec skip level k = function
                     | [] -> failwith "boucle non fermee"
                     | ']'::tl when level = 0 -> (k, tl)
                     | c::tl ->
                         let level_change = match c with
                         | '[' -> 1 | ']' -> -1
                         | _ -> 0 in
                         skip (level + level_change) (fun li -> k (c :: li)) tl in
                     let k, tl' = skip 0 id tl in
                     if curr mem = 0 then tl' else k code)
                | hd::tl -> (fun change -> bf tl (change mem))
                    (match hd with
                     | '<' -> left | '>' -> right
                     | '+' -> apply ((+) 1) | '-' -> apply ((+) (-1))
                     | ',' -> apply (fun _ -> read_int ())
                     | '.' -> apply (fun c -> print_char (char_of_int c); c)
                     | _ -> id)
                
                • Partager sur Facebook
                • Partager sur Twitter
                  24 août 2008 à 13:46:12

                  Bonjour :)

                  Voici ma petite contribution en C# :

                  using System;
                  using System.Collections.Generic;
                  
                  namespace ConsoleApplication
                  {
                  	class MainClass
                  	{
                  		public static void Main(string[] args)
                  		{
                  			string code = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
                  			int code_lenght = code.Length;
                  			int mem_indic = 0;
                  			int[] memarray = new int[code_lenght];
                  			char[] charcode = new char[code.Length];
                  			int n = 1;
                  			
                  			for(int i = 0;i<code_lenght;i++)
                  			{
                  				memarray[mem_indic] = (256 + memarray[mem_indic] ) % 256;
                  				
                  				switch(code[i].ToString())
                  				{
                  					case ">":
                  						mem_indic++;
                  					break;
                  					case "<":
                  						mem_indic--;
                  					break;
                  					case ".":
                  						Console.WriteLine(memarray[mem_indic]);
                  					break;
                  					case ",":
                  						memarray[mem_indic] = Console.Read();
                  					break;
                  					case "+":
                  						memarray[mem_indic]++;
                  					break;
                  					case "-":
                  						memarray[mem_indic]--;
                  					break;
                  					case "]":
                  						n = 1;
                  						while(n == 1)
                  						{
                  							i--;
                  							switch(code[i].ToString())
                  							{
                  								case "]":
                  									n++;
                  								break;
                  								case "[":
                  									n--;
                  								break;
                  							}
                  						}
                  						i--;
                  					break;
                  					case "[":
                  						if(memarray[mem_indic] == 0)
                  						{
                  							n = 1;
                  							while(n == 1)
                  							{
                  								i++;
                  								switch(code[i].ToString())
                  								{
                  									case "]":
                  										n--;
                  									break;
                  									case "[":
                  										n++;
                  									break;
                  								}
                  							}
                  						}
                  					break;
                  				}
                  			}
                  			Console.ReadLine();
                  		}
                  		
                  	}
                  }
                  
                  • Partager sur Facebook
                  • Partager sur Twitter
                    24 août 2008 à 13:58:56

                    Citation : bluestorm

                    | '['::tl -> (fun code' -> bf code' mem)
                        (let rec skip level k = function
                         | [] -> failwith "boucle non fermee"
                         | ']'::tl when level = 0 -> (k, tl)
                         | c::tl ->
                             let level_change = match c with
                             | '[' -> 1 | ']' -> -1
                             | _ -> 0 in
                             skip (level + level_change) (fun li -> k (c :: li)) tl in
                         let k, tl' = skip 0 id tl in
                         if curr mem = 0 then tl' else k code)
                    

                    En manque de where ? :-°
                    Sinon j'ai du mal à piger, pour les boucles, t'es sur que ça boucle ? J'ai l'impression que le test est fait qu'une seule fois, lors de la rencontre du '[' et donc que le code est exécuté une fois au maximum.

                    mario> ton code semble faux, il te manque un test dans ']', faut tester la case courante avant de repartir en arrière (à moins que tu le fasses dans '[' ... ce qui fait que quand t'as un ']' tu pars chercher le '[' pour repartir chercher le '] correspondant ...)

                    • Partager sur Facebook
                    • Partager sur Twitter
                      24 août 2008 à 14:05:14

                      Nope, il y a un piège.


                      edit : c'est d'ailleurs précisément ça qui m'a amusé au départ :-°



                      mario56 > ton code n'est pas très intéressant, car c'est une simple traduction, avec des trucs laids au début, et un style d'indentation lourdingue, de la version PHP. Tu devrais essayer de chercher quelque chose qui illustre un peu mieux les qualités (de ton langage.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        24 août 2008 à 14:13:54

                        Citation : bluestorm

                        Nope, il y a un piège.


                        edit : c'est d'ailleurs précisément ça qui m'a amusé au départ :-°


                        Ah putain c'est k code et pas k tl' :DD

                        C'est de la merde niveau perf par contre non ?
                        • Partager sur Facebook
                        • Partager sur Twitter
                          24 août 2008 à 14:15:46

                          Boarf, non, je pense que par rapport aux zippers on voit pas trop la différence. Si ça te fait pas rire, un accumulateur et List.rev_append (et ça, ça sera même plus performant que les zippers, parce qu'on stockera beaucoup moins de code useless).
                          • Partager sur Facebook
                          • Partager sur Twitter
                            24 août 2008 à 14:24:06

                            Moi je m'étonne ne pas avoir vu d'implémentations utilisant une liste doublement chainée circulaire pour la mémoire. Ptet que j'vais faire ça cette aprem.
                            • Partager sur Facebook
                            • Partager sur Twitter
                              24 août 2008 à 14:27:31

                              Citation : bluestorm

                              Boarf, non, je pense que par rapport aux zippers on voit pas trop la différence. Si ça te fait pas rire, un accumulateur et List.rev_append (et ça, ça sera même plus performant que les zippers, parce qu'on stockera beaucoup moins de code useless).


                              Sauf que tu dois te taper le parsage des [] à chaque itération non ?
                              (Je veux dire de la merde par rapport aux /vrais parsers/, bien sur :p)
                              • Partager sur Facebook
                              • Partager sur Twitter
                                24 août 2008 à 14:30:01

                                Ptin le mec qui a inventé le brainfuck avait vraiment rien à faire de son temps ^^ !
                                Ce langage est génial :D !
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  24 août 2008 à 14:35:04

                                  wgmpgp :

                                  let right = Dllist.next
                                  let left = Dllist.prev
                                  let curr = Dllist.get
                                  let apply f li = Dllist.set li (f (curr li)); li
                                  let single x = Dllist.of_enum (Enum.init 30000 (fun _ -> x))
                                  


                                  (ocamlfind ocamlc -package extlib -linkpkg ...)

                                  asmanur :
                                  Oui, mais tu notes que je peux utiliser le code actuel pour parser, en changeant la manière dont j'utilise la continuation. Mais bien sûr, la méthode avec des flots est plus élégante.
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    24 août 2008 à 16:45:43

                                    sans le -O2 (gcc brainfuck.c)
                                    real 0m0.983s
                                    user 0m0.592s
                                    sys 0m0.004s

                                    avec le -O2 (gcc brainfuck.c --ansi --pedantic -O2)

                                    real 0m0.668s
                                    user 0m0.384s
                                    sys 0m0.000s

                                    euh... non ce n'est pas le -O2 qui y change grand chose...
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      24 août 2008 à 16:54:28

                                      Citation : bluestorm

                                      Nanoc : il faudrait surtout des benchs séparés pour les interpréteurs et les compilateurs/traducteurs.


                                      Pour les interpréteurs, j'utilise

                                      +++++++++++++++++++++++++[>+++++<-]>[<+++++>-]+<+[>[>+>+<<-]++>>[<<+>>-]>>>[-]++>[-]+>>>+[[-]++++++>>>]<<<[[<++++++++<++>>-]+<.<[>----<-]<]<<[>>>>>[>>>[-]+++++++++<[>-<-]+++++++++>[-[<->-]+[<<<]]<[>+<-]>]<<-]<<-]




                                      c'est sensé faire quoi ?
                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      Python c'est bon, mangez-en. 

                                        24 août 2008 à 17:13:26

                                        J'en sais rien, mais c'est assez lent (mais pas trop) donc c'est cool. Pour vérifier que ton truc marche bien il faut tester sur d'autres choses avant.
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          24 août 2008 à 17:31:47

                                          Salut, j'ai voulu participer en utilisant PHP mais mon programme n'affiche que des symboles au lieu de lettres.

                                          Je met le code :
                                          <?php
                                          $pointeur = 0;
                                          $memoire = array();
                                          
                                          function initialisation()
                                          {
                                             for($i = 0; $i < 30000; $i++)
                                             {
                                                $memoire[$i] = 0;
                                             }
                                          }
                                          
                                          function interpreter($code)
                                          {
                                             $nbr_char = strlen($code);
                                             for($i = 0; $i < $nbr_char; $i++)
                                             {
                                                switch($code[$i])
                                          	  {
                                          	     case '<':
                                          		    $pointeur--;
                                          		    if($pointeur < 0)
                                          			{
                                          			   $pointeur = 30000;
                                          			}
                                          		 break;
                                          		 
                                          		 case '>':
                                          		    $pointeur++;
                                          		    if($pointeur > 30000)
                                          			{
                                          			   $pointeur = 0;
                                          			}
                                          		 break;
                                          		 
                                          		 case '+':
                                          		    $memoire[$pointeur]++;
                                          		 break;
                                          		 
                                          		 case '-':
                                          		    $memoire[$pointeur]--;
                                          		 break;
                                          		 
                                          		 case '.':
                                          		    echo chr($memoire[$pointeur]);
                                          		 break;
                                          		 
                                          		 case '[':
                                          		    $n = 1;
                                          		    if($memoire[$pointeur] == 0)
                                          			{
                                          			   while($n != 0)
                                          			   {
                                          			      $i++;
                                          				  if($code[$i] == '[')
                                          				  {
                                          				     $n++;
                                          				  }
                                          				  elseif($code[$i] == ']')
                                          				  {
                                          				     $n--;
                                          				  }
                                          			   }
                                          			}
                                          		 break;
                                          		 
                                          		 case ']':
                                          		    $n = 1;
                                          		    while($n != 0)
                                          			{
                                          			   $i--;
                                          			   if($code[$i] == ']')
                                          			   {
                                          				  $n++;
                                          			   }
                                          			   elseif($code[$i] == '[')
                                          			   {
                                          				  $n--;
                                          			   }
                                          			}
                                          			$i--;
                                          		 break;
                                          	  }
                                             }
                                          }
                                          
                                          ?>
                                          


                                          PS : Impossible d'afficher ces symboles sur le SdZ, ce sont des caractères invalides d'après l'erreur retournée :-° .
                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            24 août 2008 à 17:37:47

                                            global $memoire;

                                            met toi en error_reporting E_ALL, t'auras des notices.
                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              24 août 2008 à 17:39:28

                                              :euh: j'ai pas tout compris. Me mettre en error_reporting E_ALL ?
                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                24 août 2008 à 17:49:05

                                                En effet, ça m'a permis de trouver les erreurs :) .
                                                Je ne connaissais pas cette fonction, merci beaucoup coucou747 :) .
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  24 août 2008 à 19:23:11

                                                  Au niveau des optimisations, je suis en train de bosser sur quelques séquences qui se retrouvent à plusieurs endroits et qui sont optimisables. Il y a en particulier:

                                                  1) [-] qui remet l'octet pointé à 0
                                                  2) [>+<-] qui additionne le contenu d'une case avec ce qui est dans sa voisine de gauche (+= en C) et met a zéro le contenu de la case de gauche
                                                  3) Des variantes sur le même thème de cette dernière séquence.
                                                  4) Les suites de caractères identiques.

                                                  J'ai pris comme base le code de Mattex (disponible ici) et j'y ait ajouter la première optimisation en parcourant une première fois tout le code pour faire des remplacement avant de l'interpréter. Sur le code affichent les carrés jusqu'à 10'000 je passe de 0.140s à 0.120s.
                                                  L'optimisation est donc intéressante.
                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                  Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
                                                    24 août 2008 à 19:29:57

                                                    Intéressant. Je pense qu'avec ce raisonnement on peut même exprimer toutes les boucles "pures" et "statiques" (ne se déplaçant pas entre les tests) comme une fonction des valeurs d'entrée des cases concernées, ce qui donnerait un fort gain de perf (en généralisant ton optimisation).

                                                    On pourrait commencer par les boucles non imbriquées, c'est sans doute le plus abordable. Comme transformer une boucle BF pure et statique en une fonction de la valeur de la case de test ?
                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      24 août 2008 à 19:35:29

                                                      ouais enfin... pour mon code, ca fonctionnera vu comment il est foutu, mais pour beaucoup d'autres, t'auras pas un tres gros gain...

                                                      regardez comment j'ai genere le code brainfuck :

                                                      http://blogs.codes-sources.com/coucou7 [...] -a-ocaml.aspx
                                                      (le code ocaml est en piece jointe dans un .txt)
                                                      regardez le temps de "compilation"

                                                      vous croyez serieusement que vous pourrez faire l'operation inverse en un temps correct ? (bon, mon temps de calcul est en grosse partie du a l'optimisation en taille finale : la recherche du point fixe, mais j'ai quand meme de serieux doutes quand aux perfs...)
                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        24 août 2008 à 19:37:02

                                                        Tant que le programme ne dépend pas d'une entrée de l'utilisateur (i.e. si il n'y a pas de ","), alors son comportement complet est entièrement connu à la compilation. Dans l'absolu il est donc possible de comprendre son fonctionnement et de le remplacer par une suite de "printf" sans aucun branchement ni accès mémoire.

                                                        Ca doit pouvoir se faire mais sans aide ça dépace mes capacités pour l'implémentation d'un tel traducteur.
                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                        Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
                                                          24 août 2008 à 19:44:09

                                                          ... ca revient a interpreter le code, et a mettre : puts(sortie);

                                                          c'est un peu de la triche non ?
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            24 août 2008 à 19:47:52

                                                            Non. C'est le compilateur ultime.

                                                            Imagine un compilateur C qui te créerait un programme qui se déroule instantannément. Ca doit être le fantasme des gens qui écrivent GCC...
                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                            Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.

                                                            [Atelier tous langages] Codez un interpréteur de Brainfuck

                                                            × 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