Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Atelier tous langages] Calculatrice(s)

objectif : apprenez à parser un langage simple !

    14 avril 2009 à 18:10:31

    Après les ateliers Cod'Art, Compression et Brainfuck, auxquels vous êtes encore et toujours invités à participer si vous en avez envie et quelque chose à apporter, voici l'atelier Calculatrice

    Le but est encore une fois de permettre à chacun de participer, dans le but de s'exercer, découvrir des langages amusants, de nouvelles façons de faire les choses, etc.

    Le sujet choisi est le parsing, c'est à dire la reconstituion d'une expression structurée (ici une "formule mathématique") à partir d'une chaîne de caractère. Par exemple on vous donne "1+2", vous devez reconnaître que c'est la somme de un et de deux (étape de parsing), et indiquer que le résultat est trois (étape d'évaluation).

    Sujet : Écrivez un programme qui prend en entrée standard une chaîne de caractère représentant une expression mathématique (dans un des formats présentés ci-dessous), et renvoyez en sortie standard le résultat de l'évaluation de cette expression.

    Différents langages à parser



    Afin de contenter les débutants et les moins débutants, nous avons choisi de présenter une gamme de langages différents à parser. Autrement dit, vous aurez la possibilité de réaliser plusieurs calculatrices, chacune acceptant des expressions mathématiques sous une forme différente. Par ordre croissant de difficulté :

    • les maths à pile (notation polonaise inversée) : "1 2 +" signifie 1 + 2, "1 2 3 * +" signifie 1 + (2 * 3), "1 2 * 3 +" signifie (1 * 2) + 3, etc.
    • les maths en arbre (notation polonaise), la même chose à l'envers : "+ 1 2", "+ 1 * 2 3", "+ * 1 2 3", etc.
    • les maths en LISP (et pas en slip) : comme la notation à pile, mais avec des parenthèses, et on peut mettre plus de nombres par opérations : "(* (+ 1 2 3) (- 4 5))" signifie (1 + 2 + 3) * (4 - 5), "(- 1 2 3)" signifie (1 - 2 - 3), etc.
    • les maths comme d'habitude : "1 + 2 * 3", "(1 + 2) + (3 * 4 - 5) / 6", etc. Attention à la priorité des opérateurs !
    • Les maths comme d'habitude, mais avec l'opérateur "puissance", ^, en plus. Attention, "2^3^4" => 2^(3^4) !


    Ces langages ne sont pas rigoureusement définis ici (tout simplement parce que je ne connais pas de définition qui soit à la fois rigoureuse et accessible à tous), n hésitez pas à demander des précisions si vous avez des doutes, en vous assurant tout de même que personne n'a déjà posé la question avant (et faites aussi un petit effort pour réfléchir avant de demander, hein).

    On ne vous demande pas de produire une calculette multi-fonction, pas la peine d'ajouter plein d"opérations inutiles (modulo, exponentielle, logarithme...) : "+, -, *, /" suffiront largement, le reste alourdirait votre code sans le rendre plus intéressant.

    Pas la peine non plus de vous embêter à accepter les chiffres négatifs ou à virgule, encore une fois ça n'est pas l'intérêt de l'exercice.

    N'essayez de supporter tous les langages dans votre programme. Attaquez-vous à l'un d'entre eux, codez quelque chose de bien, et, si ça apporte quelque chose au sujet, venez le poster (sinon, gardez-le pour vous et soyez heureux). Vous pourrez vous attaquer aux autres ensuite. Si vous avez des difficultés ou des questions, n'hésitez pas à créer un topic à part pour votre programme !

    Pensez à préciser sur lequel de ces langages vous travaillez si vous postez quelque chose.

    Règles



    On veut un topic sympa et créatif, pas un jeu-concours à la con avec des règles psychorigides. Tant que vous faites des efforts pour être courtois, respectueux et intéressant, ça devrait aller. Je me permets tout de même de recopier les indications données pour le dernier atelier :

    Citation : Conseils

    On veut le code, on veut tripoter



    Publiez toujours la source de votre programme (normalement vous n'avez besoin de donner rien d'autre); il faudrait, dans la mesure du possible, que chacun puisse l'essayer chez soi (donc évitez les langages propriétaires, les machins pas portables du tout, etc.).

    Essayez de présenter votre source de la manière la plus lisible et la plus claire possible (pas de "oh j'ai pas eu le temps de mettre des vrais noms de variables"); sinon, vous vous exposez aux commentaires du public, et ça peut vous poursuivre pendant longtemps (hein delroth ?).

    Les autres participants peuvent reprendre la source, la modifier, l'améliorer, etc. On n'impose pas de licence ou quoi (et si vous précisez une licence libre elle sera respectée, enfin j'espère), mais si vous n'êtes pas prêts à vous faire tripoter, ne postez rien.


    Éviter les répétitions



    Si vous avez fait un programme dans votre langage préféré, mais qu'il existe déjà une proposition pour ce langage (ou un langage quasiment identique) et que le votre n'apporte visiblement rien, vous pouvez vous retenir de poster. Cherchez plutôt un aspect intéressant à explorer (une optimisation, etc.).

    Le but c'est surtout d'avoir un certain nombre de versions intéressantes à comparer, et de pouvoir voir par exemple les forces ou les faiblesses de certains langages ou styles de programmation. Pas de montrer à tout le monde qu'on est le meilleur programmeur de la planète (même si c'est cool aussi).

    • Partager sur Facebook
    • Partager sur Twitter
      14 avril 2009 à 18:19:20

      Un vieux code en OCaml qui parse des expressions mathématiques usuelles. Il triche un peu, marche pas comme un vrai parser mais au moins il est court.

      let op_position a chaine = 
        let rec aux a chaine n lvl = 
          let taille = String.length chaine in
            if chaine = "" then -1
            else match chaine.[0] with
      	| '(' -> aux a (String.sub chaine 1 (taille - 1)) (n+1) (lvl+1)
      	| ')' -> aux a (String.sub chaine 1 (taille - 1)) (n+1) (lvl-1)
      	| _ -> 
      	    if chaine.[0] = a & lvl = 0 then n
      	    else aux a (String.sub chaine 1 (taille - 1)) (n+1) lvl in
          aux a chaine 0 0
      let rec calculer chaine =
        try int_of_string chaine with 
          | _ -> 
      	let taille = String.length chaine in
      	let rec aux chars ops chaine =
      	    if chars = [] then calculer (String.sub chaine 1 (taille-2))
      	    else 
      	      let taille = String.length chaine in
      	      let pos = op_position (List.hd chars) chaine in
      		if (pos <> -1) then 
      		  ((List.hd ops) (calculer (String.sub chaine 0 pos))
      		     (calculer (String.sub chaine (pos+1) (taille-pos-1))))
      		else aux (List.tl chars) (List.tl ops) chaine
      	  in aux ['+';'-';'*'] [( + );( - );( * )] chaine
      
      • Partager sur Facebook
      • Partager sur Twitter
        14 avril 2009 à 18:33:05

        Un code un peu moins laid, qui parse les maths à pile :
        let rec parse stack = parser
        | [< '( ' ' | '\t' | '\n'); rest >] -> parse stack rest
        | [< n = parse_chiffre; num = parse_int n; rest >] -> parse (num :: stack) rest
        | [< op = parse_op; rest >] ->
            let (a::b::stack) = stack in parse (op a b :: stack) rest
        | [< >] -> stack
        and parse_int n = parser
        | [< k = parse_chiffre; rest >] -> parse_int (n * 10 + k) rest
        | [< >] -> n
        and parse_chiffre = parser
        | [< ' ('0' .. '9') as chiffre >] -> int_of_char chiffre - int_of_char '0'
        and parse_op = parser
        | [< ''+' >] -> (+)
        | [< ''-' >] -> (-)
        | [< ''*' >] -> ( * )
        | [< ''/' >] -> (/)
        
        let () =
          let entree = Stream.of_channel stdin in
          let [resultat] = parse [] entree in
          Stream.empty entree;
          print_int resultat; print_newline ()
        


        (Compilation : ocamlc -pp camlp4o ...)

        Aucun effort n'a été fait sur les rapports d'erreur, c'est le point à améliorer.


        Edit : un code un peu plus court qui utilise Genlex, mais qui procède exactement pareil :
        open Genlex
        let lexer input = make_lexer ["+";"-";"*";"/"] input
        
        let rec parse stack = parser
        | [< '(Int n); rest >] -> parse (n :: stack) rest
        | [< '(Kwd op); rest >] ->
            let a::b::stack = stack in parse (op_fn op a b :: stack) rest
        | [< >] -> stack
        and op_fn = function
        | "+" -> (+) | "-" -> (-) | "*" -> ( * ) | "/" -> (/)
        
        let () =
          let entree = lexer (Stream.of_channel stdin) in
          let [resultat] = parse [] entree in
          Stream.empty entree;
          print_int resultat; print_newline ()
        


        Edit :
        Pour ceux qui ne l'ont pas remarqué, le code d'Haveo (en plus de ne pas être très joli) est faux : il renvoie <math>\(2\)</math> quand on lui donne "1-2-3" !
        Un conseil : n'essayez pas tout de suite le quatrième langage, ce n'est pas très facile.
        • Partager sur Facebook
        • Partager sur Twitter
          14 avril 2009 à 19:17:05

          Ça marche un poil mieux avec un hack, mais on voit que ça tiendra pas le coup face a une quelconque évolution du langage.

          let op_position a chaine = 
            let rec aux a chaine n lvl = 
              let taille = String.length chaine in
                if chaine = "" then -1
                else match chaine.[0] with
          	| '(' -> aux a (String.sub chaine 1 (taille - 1)) (n+1) (lvl+1)
          	| ')' -> aux a (String.sub chaine 1 (taille - 1)) (n+1) (lvl-1)
          	| _ -> 
          	    if chaine.[0] = a & lvl = 0 then n
          	    else aux a (String.sub chaine 1 (taille - 1)) (n+1) lvl in
              aux a chaine 0 0
          let rec calculer chaine =
            try int_of_string chaine with 
              | _ -> 
          	let taille = String.length chaine in
          	let rec aux chars ops chaine =
          	    if chars = [] then calculer (String.sub chaine 1 (taille-2))
          	    else 
          	      let taille = String.length chaine in
          	      let pos = op_position (List.hd chars) chaine in
          		if (pos <> -1) then 
          		  ((List.hd ops) (calculer (String.sub chaine 0 pos))
          		     (calculer (String.sub chaine (pos+1) (taille-pos-1))))
          		else aux (List.tl chars) (List.tl ops) chaine
          	  in aux ['+';'-';'*'] [( + );( - );( * )] chaine
          let eval chaine = 
            let s = Str.global_replace (Str.regexp "-\\([0-9]\\)") "+(0-\\1)" "1-2-3" in
              calculer s
          

          • Partager sur Facebook
          • Partager sur Twitter
            14 avril 2009 à 19:21:57

            La raison pour lequel le truc de Haveo ne marche pas, c'est qu'il ne gère par les précédences des opérateurs : "1-2-3" est compris comme "1-(2-3)" dans son code. Le gros hack (remplacer les "-n" par des "+(0-n)") ne marche que pour la soustraction, ne donnerait pas grand chose de bon pour la division (en restant en entiers), et ne marcherait carrément pas pour d'autres structures syntaxiques dont la précédence est à droite (comme le @ en OCaml).


            Voici un code pour le deuxième langage; pendant le parsing, on construit un arbre représentant la structure mathématique (par exemple + 1 * 2 3 devient Op(Num 1, (+), Op (Num 2, (*), Num 3)), et ensuite on évalue l'arbre (fonction récursive 'eval') :
            type arbre = Num of int
                         | Op of arbre * op * arbre
            and op = int -> int -> int
            
            let rec parse = parser
            | [< '( ' ' | '\t' | '\n'); rest >] -> parse rest
            | [< n = parse_chiffre; num = parse_int n >] -> Num num
            | [< op = parse_op; a = parse; b = parse >] -> Op (a, op, b)
            and parse_int n = parser
            | [< k = parse_chiffre; rest >] -> parse_int (n * 10 + k) rest
            | [< >] -> n
            and parse_chiffre = parser
            | [< ' ('0' .. '9') as chiffre >] -> int_of_char chiffre - int_of_char '0'
            and parse_op = parser
            | [< ''+' >] -> (+)
            | [< ''-' >] -> (-)
            | [< ''*' >] -> ( * )
            | [< ''/' >] -> (/)
            
            let rec eval = function
            | Num n -> n
            | Op (a, op, b) -> op (eval a) (eval b)
            
            let () =
              let entree = Stream.of_channel stdin in
              let resultat = parse entree in
              print_int (eval resultat); print_newline ()
            


            On peut en fait faire un peu plus court, sans passer par l'arbre mais en évaluant pendant le parsing :
            let rec parse = parser
            | [< '( ' ' | '\t' | '\n'); rest >] -> parse rest
            | [< n = parse_chiffre; num = parse_int n >] -> num
            | [< op = parse_op; a = parse; b = parse >] -> op a b
            and parse_int n = parser
            | [< k = parse_chiffre; rest >] -> parse_int (n * 10 + k) rest
            | [< >] -> n
            and parse_chiffre = parser
            | [< ' ('0' .. '9') as chiffre >] -> int_of_char chiffre - int_of_char '0'
            and parse_op = parser
            | [< ''+' >] -> (+)
            | [< ''-' >] -> (-)
            | [< ''*' >] -> ( * )
            | [< ''/' >] -> (/)
            
            let () =
              let entree = Stream.of_channel stdin in
              let resultat = parse entree in
              print_int resultat; print_newline ()
            


            Mais cette méthode ne passe pas à l'échelle, c'est à dire qu'elle ne sera plus appliquable pour des langages de programmation plus compliqués, ou on fait des manipulations plus complexes (boucles, etc.) et on veut vraiment séparer la phase de parsing et la phase d'évaluation.
            • Partager sur Facebook
            • Partager sur Twitter
              14 avril 2009 à 19:30:12

              Un évaluateur pour le langage n°4, qui transforme d'abord en RPN puis interprète ensuite.

              #!/usr/bin/env python3
              
              from collections import namedtuple
              from operator import add, sub, mul, floordiv
              
              precs = {
                  '+': 10,
                  '-': 10,
                  '*': 9,
                  '/': 9
              }
              funcs = {
                  '+': add,
                  '-': sub,
                  '*': mul,
                  '/': floordiv
              }
              
              Token = namedtuple('Token', 'type value')
              
              def tokenize(expr):
                  expr = expr.replace(' ', '')
                  i = 0
                  while i < len(expr):
                      if expr[i].isdigit():
                          n = 0
                          while i < len(expr) and expr[i].isdigit():
                              n = n * 10 + int(expr[i])
                              i += 1
                          yield Token(type='int', value=n)
                          i -= 1
                      elif expr[i] in precs:
                          yield Token(type='op', value=expr[i])
                      elif expr[i] == '(':
                          yield Token(type='lparen', value=None)
                      elif expr[i] == ')':
                          yield Token(type='rparen', value=None)
                      else:
                          raise ValueError('unexpected character {0}'.format(expr[i]))
                      i += 1
              
              def eval_rpn(rpn):
                  st = []
                  for tok in rpn:
                      if tok.type == 'int':
                          st.append(tok.value)
                      elif tok.type == 'op':
                          if len(st) < 2:
                              raise ValueError('missing operand')
                          v2 = st.pop()
                          v1 = st.pop()
                          st.append(funcs[tok.value](v1, v2))
                      else:
                          raise RuntimeError('wtf?')
                  if len(st) == 1:
                      return st[0]
                  else:
                      raise ValueError('missing operator')
              
              def eval_infix(expr):
                  rpn = []
                  ops_stack = []
                  for tok in tokenize(expr):
                      if tok.type == 'int':
                          rpn.append(tok)
                      elif tok.type == 'op':
                          while ops_stack and ops_stack[-1].type == 'op' and \
                                  precs[tok.value] <= precs[ops_stack[-1].value]:
                              rpn.append(ops_stack.pop())
                          ops_stack.append(tok)
                      elif tok.type == 'lparen':
                          ops_stack.append(tok)
                      else:
                          while ops_stack[-1].type != 'lparen':
                              rpn.append(ops_stack.pop())
                              if not ops_stack:
                                  raise ValueError('missing left parenthesis')
                          ops_stack.pop()
                  while ops_stack:
                      if ops_stack[-1].type == 'lparen':
                          raise ValueError('missing right parenthesis')
                      rpn.append(ops_stack.pop())
                  return eval_rpn(rpn)
              


              Et en trichant un peu :
              import ast
              eval_infix = ast.literal_eval
              


              Sinon, pour le langage n°5 :
              #!/usr/bin/env python3
              
              from collections import namedtuple
              from operator import add, sub, mul, floordiv, pow
              
              leftassoc = {
                  '+': True,
                  '-': True,
                  '*': True,
                  '/': True,
                  '^': False
              }
              precs = {
                  '+': 10,
                  '-': 10,
                  '*': 9,
                  '/': 9,
                  '^': 20
              }
              funcs = {
                  '+': add,
                  '-': sub,
                  '*': mul,
                  '/': floordiv,
                  '^': pow
              }
              
              Token = namedtuple('Token', 'type value')
              
              def tokenize(expr):
                  expr = expr.replace(' ', '')
                  i = 0
                  while i < len(expr):
                      if expr[i].isdigit():
                          n = 0
                          while i < len(expr) and expr[i].isdigit():
                              n = n * 10 + int(expr[i])
                              i += 1
                          yield Token(type='int', value=n)
                          i -= 1
                      elif expr[i] in precs:
                          yield Token(type='op', value=expr[i])
                      elif expr[i] == '(':
                          yield Token(type='lparen', value=None)
                      elif expr[i] == ')':
                          yield Token(type='rparen', value=None)
                      else:
                          raise ValueError('unexpected character {0}'.format(expr[i]))
                      i += 1
              
              def eval_rpn(rpn):
                  st = []
                  for tok in rpn:
                      if tok.type == 'int':
                          st.append(tok.value)
                      elif tok.type == 'op':
                          if len(st) < 2:
                              raise ValueError('missing operand')
                          v2 = st.pop()
                          v1 = st.pop()
                          st.append(funcs[tok.value](v1, v2))
                      else:
                          raise RuntimeError('wtf?')
                  if len(st) == 1:
                      return st[0]
                  else:
                      raise ValueError('missing operator')
              
              def should_move_op(o1, o2):
                  if not leftassoc[o1]:
                      return precs[o1] < precs[o2]
                  else:
                      return precs[o1] <= precs[o2]
              
              def eval_infix(expr):
                  rpn = []
                  ops_stack = []
                  for tok in tokenize(expr):
                      if tok.type == 'int':
                          rpn.append(tok)
                      elif tok.type == 'op':
                          while ops_stack and ops_stack[-1].type == 'op' and \
                                  should_move_op(tok.value, ops_stack[-1].value):
                              rpn.append(ops_stack.pop())
                          ops_stack.append(tok)
                      elif tok.type == 'lparen':
                          ops_stack.append(tok)
                      else:
                          while ops_stack[-1].type != 'lparen':
                              rpn.append(ops_stack.pop())
                              if not ops_stack:
                                  raise ValueError('missing left parenthesis')
                          ops_stack.pop()
                  while ops_stack:
                      if ops_stack[-1].type == 'lparen':
                          raise ValueError('missing right parenthesis')
                      rpn.append(ops_stack.pop())
                  return eval_rpn(rpn)
              
              • Partager sur Facebook
              • Partager sur Twitter
              Anonyme
                14 avril 2009 à 19:59:41

                Pour le fun et pour montrer ce qui arrive quand on code n'importe comment, voici un interpréteur de formules en notation polonaise inversée... sur une seule ligne :
                perl -lne 'my@s;sub f{$a=pop@s;$b=pop@s;eval sprintf(q{push@s,%s%s%s},$a,shift,$b)};split/\s+/;for$x(@_){if($x=~/\d/){unshift@s,$x}elsif(grep{$x eq$_}qw[+ - * /]){f$x}};print@s'
                


                Et pour vous montrer que le Perl n'est pas qu'un langage "écriture seule", voici le même programme en joli :
                #!/usr/bin/perl -l
                
                use strict;
                use warnings;
                
                my @stack;
                sub eval_op {
                    my ($x, $y) = (pop @stack, pop @stack);
                    my $code = sprintf( q[push @stack ,%s %s %s], $x, shift, $y );
                    eval $code;
                }
                
                while (my $line = <STDIN>) {
                    my @line = split /\s+/, $line;
                    for my $tok (@line) {
                        if ($tok =~ /\d/) { unshift @stack, $tok }
                        elsif (grep { $tok eq $_ } qw[+ - * /]) { eval_op $tok }
                        else { die "Bad token" }
                    }
                    print @stack;
                    undef @stack;
                }
                
                • Partager sur Facebook
                • Partager sur Twitter
                  14 avril 2009 à 20:04:28

                  Un programme en python... pour les math comme d'habitude avec une jolie petite interface graphique!


                  #-*- coding:Latin-1 -*-
                  
                  from Tkinter import *
                  
                  def evaluer():
                      operation =entree.get()
                      entree.delete(0, END)
                      entree.insert(END, str(eval(operation)))
                  
                  fen = Tk()
                  fen.title('Calc')
                  
                  entree = Entry(fen)
                  entree.grid(row = 0, columnspan = 3)
                  
                  Button (fen, text = '=', width =3, command =evaluer).grid(row =2, column =2)
                  
                  fen.mainloop()
                  
                  • Partager sur Facebook
                  • Partager sur Twitter
                    14 avril 2009 à 20:09:12

                    Oui, sauf que le but c'est de coder un parseur, pas de nous faire découvrir la fonction eval de ton langage préféré.

                    Pensez bien à préciser quel langage vous traitez, aussi. Visiblement c'est le 1 pour zulon.

                    Hypertux : en plus ça marche pas :
                    >>> eval("2^3")
                    1
                    
                    • Partager sur Facebook
                    • Partager sur Twitter
                      14 avril 2009 à 20:25:34

                      Intéressant cet atelier. Voici mon implémentation des maths à pile en C++ :
                      #include <iostream>
                      #include <stack>
                      #include <string>
                      
                      using namespace std;
                      
                      bool isOp(char c){
                          string op = "+-/*";
                          if(op.find(c) != string::npos)
                              return true;
                      
                          return false;
                      }
                      
                      void interprete(string epf){
                          stack<int> stack;
                          int val1 = 0, val2 = 0, res = 0;
                      
                          for(unsigned int i = 0; i < epf.length(); i++){
                              if(!isOp(epf.at(i)))
                                  stack.push(epf.at(i) - '0');
                              else{
                                  val1 = stack.top();
                                  stack.pop();
                      
                                  val2 = stack.top();
                                  stack.pop();
                      
                                  switch(epf.at(i)){
                                      case '+':
                                          res = val2 + val1;
                                      break;
                      
                                      case '-':
                                          res = val2 - val1;
                                      break;
                      
                                      case '*':
                                          res = val2 * val1;
                                      break;
                      
                                      case '/':
                                          res = val2 / val1;
                                      break;
                                  }
                      
                                  stack.push(res);
                              }
                          }
                      
                          cout << "Resultat : " << stack.top() << endl;
                      }
                      
                      int main(){
                          string epf;
                      
                          cout << "Entrez votre expression : ";
                          cin >> epf;
                      
                          interprete(epf);
                      
                          return 0;
                      }
                      
                      • Partager sur Facebook
                      • Partager sur Twitter
                        14 avril 2009 à 20:32:34

                        Pas mal, mais il faudrait gérer les nombres à plusieurs chiffres, et si possible les espaces !
                        • Partager sur Facebook
                        • Partager sur Twitter
                          14 avril 2009 à 20:42:07

                          Une solution (toujours en caml) pour les langages "comme d'habitude" (4 et 5) :
                          open Genlex
                          let lexer = make_lexer [ "+"; "-"; "*"; "/"; "^"; "("; ")" ]
                          
                          (* generic parsers *)
                          let rec op_right opfunc operators next_level = parser
                            | [< e = next_level; s >] -> match s with parser
                                | [< 'Kwd op when List.mem op operators;
                                     e' = op_right opfunc operators next_level >] -> opfunc op e e'
                                | [< >] -> e
                          and op_left opfunc operators next_level =
                            let rec loop e = parser
                              | [< 'Kwd op when List.mem op operators;
                                   e' = next_level; s >] -> loop (opfunc op e e') s
                              | [< >] -> e in
                            parser [< acc = next_level; e = loop acc >] -> e
                          
                          (* mathematical grammar *)
                          let rec parse stream = sum_expr stream
                          and sum_expr s = op_left binop ["+"; "-"] mul_expr s
                          and mul_expr s = op_left binop ["*"; "/"] pow_expr s
                          and pow_expr s = op_right binop ["^"] simple_expr s
                          and simple_expr = parser
                          | [< 'Int n >] -> n
                          | [< '(Kwd "("); e = parse; '(Kwd ")") >] -> e
                          and binop = function
                          | "+" -> (+) | "-" -> (-) | "/" -> (/)
                          | "*" -> ( * )
                          | "^" ->
                              let rec pow a = function
                              | 0 -> 1
                              | n -> pow (a * a) (n / 2) * (if n mod 2 = 0 then 1 else a) in
                              pow
                          
                          let () =
                            let result = parse (lexer (Stream.of_channel stdin)) in
                            print_int result; print_newline ()
                          
                          • Partager sur Facebook
                          • Partager sur Twitter
                            14 avril 2009 à 20:43:30

                            Concernant les nombres à plusieurs chiffres, j'ai déjà essayé d'y réfléchir mais je n'ai pas trouvé de solution "correcte"...
                            • Partager sur Facebook
                            • Partager sur Twitter
                              14 avril 2009 à 20:45:35

                              Jack'da : l'idée est de "continuer à lire tant que c'est encore des chiffres". Si ça te parle plus que mes solutions récursives, tu peux t'inspirer du code python :

                              if expr[i].isdigit():
                                          n = 0
                                          while i < len(expr) and expr[i].isdigit():
                                              n = n * 10 + int(expr[i])
                                              i += 1
                              
                              • Partager sur Facebook
                              • Partager sur Twitter
                                14 avril 2009 à 23:11:51

                                Un code C pour le premier langage.

                                hm après des tests rapides je n'ai pas trouvé de bug mais si vous en voyez :)

                                remarque : il gère en principe bien les nombres négatifs. Ce n'est pas vraiment fait exprès ça vient du fait que strtol() (conversion chaine->entier) les prends en compte.
                                Un effet secondaire : 1 2 +3 4 * != 1 2 + 3 4 *

                                #include <stdio.h>
                                #include <stdlib.h>
                                #include <string.h>
                                
                                #define MAX 256
                                
                                /* ################################## */
                                
                                int pile[MAX];
                                int taille_pile;
                                
                                void push(int i)
                                {
                                     pile[taille_pile++] = i;
                                }
                                
                                /* un appel a getList() vide la pile */
                                int getList(int **p)
                                {
                                     int tmp = taille_pile;
                                     taille_pile = 0;
                                     *p = pile;
                                     return tmp;
                                }
                                
                                /* ################################## */
                                
                                typedef enum { NOMBRE, OPERATEUR } Type;
                                typedef struct {
                                     Type type;
                                     union
                                     {
                                	  int n;
                                	  char op;
                                     } v;
                                } Elemt;
                                
                                /* facon strtok() */
                                int nextElemt(char* expression,Elemt *e)
                                {
                                     static char* p = NULL;
                                     char* suite = NULL;
                                     int ret = 1;
                                     long n;
                                     char op;
                                
                                     if(expression)
                                	  p = expression;
                                
                                     n = strtol(p,&suite,10);
                                     /* une conversion reussi ? */
                                     if(suite != p)
                                     {
                                	  e->type = NOMBRE;
                                	  e->v.n = n;
                                	  p = suite;
                                     }
                                     /* un operateur ? */
                                     else if(sscanf(p," %c",&op)==1 && strchr("+-/*",op))
                                     {
                                	  e->type = OPERATEUR;
                                	  e->v.op = op;
                                	  p = strchr(p,op) + 1;
                                     }
                                
                                     else if(!*p) /* fin de chaine, RAS */
                                	  ret = 0;
                                     else  /* caractere inconnu */
                                	  ret = -1;
                                	  
                                     return ret;
                                }
                                
                                /* ################################## */
                                
                                int calc(int a, int b, char op)
                                {
                                     int ret = 0;
                                     switch(op)
                                     {
                                     case '+':
                                	  ret = a+b;
                                	  break;
                                
                                     case '-':
                                	  ret = a-b;
                                	  break;
                                
                                     case '*':
                                	  ret = a*b;
                                	  break;
                                
                                     case '/':
                                	  ret = a/b;
                                	  break;
                                
                                     default:
                                	  break;
                                     }
                                
                                     return ret;
                                }
                                
                                void process(char *expr)
                                {
                                     Elemt e;
                                     int ok;
                                     int resultat;
                                     int taille;
                                
                                     ok = nextElemt(expr,&e);
                                     while(ok && ok!=-1)
                                     {
                                	  if(e.type == NOMBRE)
                                	       push(e.v.n);
                                	  else
                                	  {
                                	       int *liste;
                                	       int i;
                                	       
                                	       /* init au neutre */
                                	       resultat = (e.v.op=='*' || e.v.op=='/') ? 1 : 0;
                                	  
                                	       taille = getList(&liste);
                                	       if(taille==1)
                                	       {
                                		    fprintf(stderr,"Erreur : operande manquant\n");
                                		    return;
                                	       }
                                	       for(i= 0;i<taille; i++)
                                		    resultat = calc(resultat,liste[i],e.v.op);
                                	       push(resultat);
                                	  }
                                	  ok = nextElemt(NULL,&e);
                                     }
                                
                                     if(ok == -1)
                                	  fprintf(stderr,"Erreur : caractere inconnu\n");
                                     else if(taille_pile != 1)
                                	  fprintf(stderr,"Erreur : operateur manquant\n");
                                     else
                                	  printf("%d\n",resultat);
                                }
                                
                                /* ################################## */
                                
                                int main(void)
                                {
                                     char ligne[MAX];
                                     char *p;
                                
                                     while(fgets(ligne,MAX,stdin))
                                     {
                                	  if( (p=strchr(ligne,'\n')) )
                                	  {
                                	       *p = '\0';
                                
                                	       memset(pile,0,MAX);
                                	       taille_pile = 0;
                                	    
                                	       process(ligne);
                                	  }
                                	  else
                                	  {
                                	       fprintf(stderr,"Erreur : %d caracteres maxi\n",MAX-2);
                                	       break;
                                	  }
                                     }
                                
                                     return 0;
                                }
                                


                                edit : à la dernière relecture, l'algo est foireux pour la division.
                                Je verrais plus tard, bonne nuit'.
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  15 avril 2009 à 9:57:06

                                  freecircus :

                                  pour commencer avec les chipotages, ta gestion de la pile est un peu imparfaite (s'il y a plus de MAX opérandes, boum), mais on comprend que tu fais avec ton langage, et c'est pas la peine de t'embêter à prendre un tableau dynamique ou une liste chaînée, on te fait confiance pour savoir le faire et c'est pas le but de l'exercice.

                                  Par ailleurs, tu aurais pu t'éviter la complexité supplémentaire du type union, en procédant ainsi : au lieu d'une fonction qui "prend un Elem dans la chaine", et qui renvoie soit un entier soit un opérateur, tu peux avoir une "fonction qui prend un entier", et une "fonction qui prend un opérateur". Tu commences par appeler la fonction qui prend un entier, et si elle renvoie une erreur tu demandes un opérateur.
                                  Ceci dit, ta méthode est très bien aussi et s'adaptera bien aux autres exercices (où il sera utile d'avoir construit une structure représentant notre expression).


                                  Le vrai reproche maintenant : tu interprètes les opérateurs comme des opérateurs n-aires (autant d'arguments que possible), et non pas des opérateurs binaires. Ça n'est pas une très bonne idée parce que :
                                  - la norme pour les notations "sans parenthèses" c'est de faire du binaire seulement, ou plutôt que l'arité de chaque opérateur soit fixée
                                  - ça chie pour la division, comme tu as remarqué (mais on aura aussi ce problème avec la notation LISP)
                                  - surtout (et c'est la raison pour laquelle les arités fixées sont la norme), ça introduit des ambiguités : "1 2 3 + 4 *" pourrait être ou bien ((1 2 3 +) 4 *), ou bien (1 (2 3 +) 4 *). Dans ton cas, c'est la première interprétation qui sera choisie, mais c'est un détail lié à ton implémentation, qui n'est pas forcément naturel. Avec des opérateurs binaires seulement, il y a une unique façon d'interpréter les termes, qui ne demande pas de parenthèses pour résoudre les ambiguités. Je pense qu'on devrait s'y tenir.
                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    15 avril 2009 à 10:39:58

                                    Citation : Un codeur du côté obscur

                                    Bon, moi aussi j'ai décidé de participer ! J'ai opté pour le langage numéro 2, c-à-d celui avec la notation préfixée.
                                    J'ai fait ça en erlang (pour changer), je pense que c'est assez lisible.
                                    Je gère les entiers et les flottants, les 4 opérations "basiques" (à savoir +,-,*,/) et hum... c'est à peu près tout.
                                    Si jamais il manque des trucs, signalez.
                                    Voici le code :

                                    -module(calc).
                                    -export([eval/1]).
                                    
                                    -import(lists, [member/2, reverse/1]).
                                    
                                    eval(Str) -> num(parse([], Str)).
                                    
                                    num(X) when is_list(X) ->
                                        case member($., X) of
                                            true -> list_to_float(reverse(X));
                                            _ -> list_to_integer(reverse(X))
                                        end;
                                    num(X) -> X.
                                    
                                    op(S, Xs) ->
                                        X = num(parse([], Xs)),
                                        Y = num(parse([], Xs)),
                                        case S of
                                            $+ -> X + Y;
                                            $- -> X - Y;
                                            $* -> X * Y;
                                            $/ -> X / Y
                                        end.
                                    
                                    parse(Acc, []) -> Acc;
                                    parse(Acc, [X|Xs]) ->
                                        case X of
                                            $  when Acc =/= [] -> Acc;
                                            $  -> parse(Acc, Xs);
                                            $+ -> op($+, Xs);
                                            $- -> op($-, Xs);
                                            $* -> op($*, Xs);
                                            $/ -> op($/, Xs);
                                            Y when Y >= $0, Y =< $9 -> parse([Y|Acc], Xs);
                                            Y when Y == $. -> parse([Y|Acc], Xs);
                                            _ -> error
                                        end.
                                    



                                    Si y'a des passages qui vous semblent louches, n'hésitez pas à demander.

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Anonyme
                                      15 avril 2009 à 10:42:43

                                      Citation : bluestorm

                                      Pensez bien à préciser quel langage vous traitez, aussi. Visiblement c'est le 1 pour zulon.


                                      Oui c'est bien le 1, je l'ai dit en disant "notation polonaise inversée" non ? (C'est quand même plus parlant qu'un numéro arbitraire).

                                      J'ai d'ailleurs trouvé une simplification à mon premier code (merci Cygal pour la confirmation) :
                                      #!/usr/bin/perl -wl
                                      use strict;
                                      
                                      while (<STDIN>) {
                                          my @text = split /\s+/;
                                          my @num = reverse( grep /\d/, @text );
                                          my @op = grep m{^[+*\/-]$}, @text;
                                          for my $op (@op) {
                                              my ($x, $y) = (pop @num, pop @num);
                                              eval( sprintf( q[push @num, %s %s %s], $x, $op, $y ) );
                                          }
                                          print @num;
                                          undef @num;
                                      }
                                      

                                      Bon, c'est pas très marrant parce qu'il n'y a pas beaucoup de parsage à faire pour la RPN (un simple split sur les espaces est suffisant). Je vais essayer de m'attaquer au parsage du langage 3, voire du 4.
                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        15 avril 2009 à 10:58:01

                                        Vinchz : le code est assez faux.

                                        Déjà, petit détail, calc:eval("1") foire, il faut utiliser calc:eval("1 ") ou calc:eval("1" ++ [fin]). Pourquoi utiliser un symbole fin au lieu de matcher la fin de la liste tout simplement ?

                                        Ensuite, la gestion de l'accumulateur dans la boucle principale est assez maladroite : en effet, les entiers ne sont pris en compte que s'ils sont suivis par un espace, et si on met un opérateur juste derrière ils sont "oubliés" :
                                        38> calc:eval("+ 1+ 2 2 4 ").
                                        8
                                        


                                        Je pense qu'il est plus propre de mettre la gestion des nombres (et des flottants aussi s'il tient à faire son kéké) dans une fonction séparée de la boucle principale, pour ne pas avoir ce genre de problèmes justement. L'accumulateur n'a rien à faire dans la fonction principale, il sert uniquement pour lire un chiffre.

                                        Enfin (bug principal), la fonction "op" est fausse : elle parse deux fois la même liste, au lieu d'enlever les tokens de la première liste avant de parser à nouveau. Concrètement, cela veut dire que quel que soit le deuxième terme, c'est le premier qui sera pris en compte deux fois de suite :
                                        41> calc:eval("+ 1 * 2 3 ").
                                        2
                                        

                                        Pour corriger ça il faudrait modifier substantiellement parse pour renvoyer à chaque fois, en plus de la valeur calculée, le reste des tokens non lus (hint : c'est une monade).
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          15 avril 2009 à 11:03:56

                                          bluestorm : C'était une ancienne version, j'ai édité en même temps que tu postais.
                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            15 avril 2009 à 11:06:44

                                            Oui, ben la remarque sur "fin" a été corrigée, mais le reste (entre autre le fait que l'algorithme est faux :p ) est toujours d'actualité.

                                            Edit :
                                            zulon, l'ordre d'évaluation est-il garanti ici ?
                                            my ($x, $y) = (pop @num, pop @num);
                                            

                                            Je trouve ça un peu dangereux parce que si une implémentation évalue de droite à gauche, tu peux vite te retrouver avec toutes tes opérations non commutatives fausses. Je pense qu'une version avec séquencage explicite serait plus raisonnable.
                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                            Anonyme
                                              15 avril 2009 à 11:11:35

                                              Ach non il ne l'est pas (c'est ça de n'avoir pas de spécification, vivement Perl 6). J'avais d'ailleurs commencé par un séquençage explicite mais j'ai abandonné, je ne sais plus pourquoi :-° . C'est pas très long à corriger :
                                              #!/usr/bin/perl -wl
                                              use strict;
                                              
                                              while (<STDIN>) {
                                                  my @text = split /\s+/;
                                                  my @num = reverse( grep /\d/, @text );
                                                  my @op = grep m{^[+*\/-]$}, @text;
                                                  for my $op (@op) {
                                                      my $x = pop @num; my $y = pop @num;
                                                      eval( sprintf( q[push @num, %s %s %s], $x, $op, $y ) );
                                                  }
                                                  print @num;
                                                  undef @num;
                                              }
                                              
                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                15 avril 2009 à 11:55:23

                                                Salut !

                                                Idée sympathique... je n'ai hélas que peu de temps à y consacrer, et je vois qu'il y a déjà beaucoup de codes OCaml. Je vous en propose un qui utilise ocamllex et ocamlyacc pour changer un peu des flots de données. Le code analyse les maths dans leur notation "habituelle".

                                                Attention : ce code est une esquisse. Il manque la gestion des règles d'associativité (et c'est un gros manque : songez que 2 * 4 - 3 renvoie 2), et probablement d'autres choses qui ne me viennent pas à l'esprit pour l'instant. N'hésitez pas à le reprendre et à l'améliorer comme bon vous semble, je ne pense pas le reprendre et le développer encore faute de temps...

                                                Lexeur (lexer.mll) :
                                                { open Parser }
                                                
                                                let num = '-'? ['0'-'9']+ ('.' ['0'-'9']+)? (['e' 'E'] '-'? ['0'-'9']+)?
                                                
                                                rule token = parse
                                                  | '+'         { ADD }
                                                  | '-'         { REM }
                                                  | '*'         { MUL }
                                                  | '/'         { DIV }
                                                  | '^'         { POW }
                                                  | '('         { LPA }
                                                  | ')'         { RPA }
                                                  | num         { NUM (float_of_string (Lexing.lexeme lexbuf)) }
                                                  | _           { token lexbuf }
                                                  | eof         { EOF }
                                                


                                                Parseur (parser.mly) :
                                                %token LPA RPA EOF
                                                %token ADD REM MUL DIV POW
                                                %token <float> NUM
                                                
                                                %start eval
                                                %type <float option> eval
                                                
                                                %%
                                                
                                                eval:
                                                  | /* Empty */         { None }
                                                  | expr EOF            { Some $1 }
                                                  | error               { failwith "Syntax error" }
                                                
                                                expr:
                                                  | NUM                 { $1 }
                                                  | LPA expr RPA        { $2 }
                                                  | expr ADD expr       { $1 +. $3 }
                                                  | expr REM expr       { $1 -. $3 }
                                                  | expr MUL expr       { $1 *. $3 }
                                                  | expr DIV expr       { $1 /. $3 }
                                                  | expr POW expr       { $1 ** $3 }
                                                


                                                Fichier principal (calc.ml) :
                                                let _ =
                                                  let rec loop () =
                                                    let expr = read_line (print_string "Expression à évaluer : ") in
                                                    match Parser.eval Lexer.token (Lexing.from_string expr) with
                                                    | None -> print_endline "Terminé"
                                                    | Some res -> print_float res; print_newline (); loop ()
                                                  in loop ()
                                                


                                                Compilation et exécution : ocamlbuild calc.native --

                                                Cordialement,
                                                Cacophrène
                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  15 avril 2009 à 12:29:26

                                                  Cacophrène : merci pour ta participation :)


                                                  Il faut savoir que Cacophrène a utilisé ocamlyacc/ocamllex, qui sont des "générateurs de parseurs" (à la yacc/lex, bison/flex). Le code a donc l'air beaucoup plus simple que les autres, parce que ce n'est pas lui qui fait la partie difficile, mais un outil spécialisé (là il ne gère pas exactement les priorités et compagnie, mais c'est très facile à rajouter).

                                                  C'est intéressant de pouvoir comparer les deux approches (en particulier on remarque ironiquement que mon code pour le langage 5, qui est complet, est en réalité plus court, bien que clairement moins compréhensible), mais je ne vous encourage pas à vous reposer sur des générateurs de parseurs par la suite.

                                                  Le but de ce topic aussi d'apprendre à faire ce genre de choses "à la main", afin d'acquérir des connaissances pour gérer de "vrais langages" par la suite.
                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    15 avril 2009 à 13:12:47

                                                    Salut !

                                                    Citation : bluestorm

                                                    ce n'est pas lui qui fait la partie difficile, mais un outil spécialisé


                                                    C'est bon la paresse ! :p

                                                    Citation : bluestorm

                                                    on remarque ironiquement que mon code pour le langage 5, qui est complet, est en réalité plus court, bien que clairement moins compréhensible


                                                    On ajoute malicieusement que c'est parce que camlp4 nous fournit une syntaxe vraiment très pratique pour les flots de données. S'il n'y avait pas cet outil combiné à la puissance du filtrage, ce serait sans doute moins court ;)

                                                    Citation : bluestorm

                                                    je ne vous encourage pas à vous reposer sur des générateurs de parseurs par la suite


                                                    +1.
                                                    Mieux vaut commencer par apprendre à le faire sans outil pour mâcher le travail, c'est bien plus enrichissant. Mais par la suite, pour des exemples grandeur nature, surtout ceux qui ont besoin d'être extensibles, ce sera quand même bien agréable d'avoir des outils dédiés.

                                                    Ce qui fait que pour le parsing en général, on a plein d'outils : les fonctions à la scanf, les expressions régulières, les flots de données, la définition de grammaires... reste plus qu'à choisir en fonction du problème à résoudre.

                                                    Cordialement,
                                                    Cacophrène
                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      15 avril 2009 à 14:19:11

                                                      Bonjour !
                                                      J'ai tenter un premier parser pour la NPI (langage 1) en Haskell.
                                                      module NPI where
                                                      
                                                      parse :: String -> [String] -> Double
                                                      parse [] stack =  (read (head (filter (not.null) stack)))::Double
                                                      parse (x:xs) stack 
                                                          | x `elem` "+-*/"     = parse xs (parse_op x stack)
                                                          | x `elem` ['0'..'9'] = parse xs (parse_num x stack)
                                                          | x `elem` " \n\t"    = parse xs (parse_other stack)
                                                         where parse_other (hd:stack)
                                                                  | hd == "" = (hd:stack)
                                                                  | otherwise = ("":hd:stack)
                                                               parse_num x (hd:stack) = (hd ++ (x:[])):stack 
                                                               parse_op x stack = 
                                                                  let op = case x of
                                                      			  '+' -> (+)
                                                                                '-' -> (-)
                                                                                '*' -> (*) 
                                                                                '/' -> (/)
                                                      	        num_stack = map (\s -> (read s::Double)) (filter (not.null) stack)
                                                                  in (show ((num_stack!!0) `op` (num_stack!!1))):[]
                                                      
                                                      main = do
                                                          input <- getLine
                                                          let out = parse input [""]
                                                          putStrLn (show out)
                                                      
                                                      Ce code ne gère pas du tout les erreurs d'entrée (si je met "a b +" en entrée, le programme a la gentillesse de m'indiquer que mon pattern matching n'est pas exhaustif ).
                                                      Le code ne gère pas non plus les non plus les nombres a virgules, je vais essayer de l'implémenter par la suite, ça n'a pas l'air trop compliqué.

                                                      Si vous voyez des passages vraiment hideux, ou qui ne marchent pas, n'hésitez pas à me le dire.

                                                      A+
                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        15 avril 2009 à 14:39:19

                                                        cgizmo : ta gestion des nombres, comparable à celle qui est faite en Erlang au dessus, n'est pas très jolie. Tu essaies de mettre des chaines dans la stack, au lieu de mettre des entiers, ce qui donne des trucs un peu laids (show après l'opération) et/ou un peu lents (hd ++ (x:[]), berk).

                                                        Je trouve dommage d'empiler des chaines, y compris les chaines vides (avec un filter (not . null) ?), je pense que tu devrais gérer la lecture des entiers à part, et ne mettre que ça sur la pile.

                                                        Edit: le code de cgizmo, corrigé :
                                                        parse :: String -> [Integer] -> Integer
                                                        parse [] [result] = result
                                                        parse (x:xs) stack 
                                                            | x `elem` "+-*/"     = parse_op xs x stack
                                                            | x `elem` ['0'..'9'] = parse_num xs (to_int x) stack
                                                            | x `elem` " \n\t"    = parse xs stack
                                                            where
                                                                parse_num input@(x:xs) n stack
                                                                          | x `elem` ['0'..'9'] = parse_num xs (n * 10 + to_int x) stack
                                                                          | otherwise = parse input (n:stack)
                                                                to_int x = Char.ord x - Char.ord "0"
                                                                parse_op input op_char (a:b:stack) = parse input (op a b : stack)
                                                                         where op = case op_char of
                                                        			    '+' -> (+)
                                                                                    '-' -> (-)
                                                                                    '*' -> (*)
                                                                                    '/' -> div
                                                        


                                                        Edit : j'en profite pour poster une version des maths en arbre (langage 2) en Haskell, avec une monade :
                                                        import qualified Char
                                                        import Control.Monad.State
                                                        
                                                        eval str = evalState parse str
                                                        parse = do
                                                              c:input <- get
                                                              put input
                                                              choose c
                                                        choose c
                                                               | c `elem` " \t\n" = parse
                                                               | c `elem` ['0'..'9'] = get >>= parse_num (to_int c)
                                                               | c `elem` "+-*/" = do
                                                                 a <- parse
                                                                 b <- parse
                                                                 return $ parse_op c a b
                                                        parse_num n [] = return n
                                                        parse_num n (c:input)
                                                                  | c `elem` ['0' .. '9'] = put input >> get >>= parse_num (n * 10 + to_int c)
                                                                  | otherwise = return n
                                                        to_int c = Char.ord c - Char.ord '0'
                                                        parse_op '+' = (+)
                                                        parse_op '-' = (-)
                                                        parse_op '*' = (*)
                                                        parse_op '/' = div
                                                        

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          15 avril 2009 à 16:12:56

                                                          @bluestorm : j'ai apporté les modifications que tu avais suggéré sur mon code, mais je viens juste de me rendre compte que tu l'avais corriger (merci). Je vais quand même poster ma version, pour recevoir les éventuelles critiques.

                                                          module NPI where
                                                          
                                                          isNumber :: Char -> Bool
                                                          isNumber x = x `elem` "0123456789."
                                                          
                                                          parse :: String -> [Double] -> Double
                                                          parse [] stack =  head stack
                                                          parse (x:xs) stack
                                                              | isNumber x      = parse (snd (break (`elem` terminator) xs)) $ (parse_num xs (x:[])):stack
                                                              | x `elem` "+-*/" = parse xs $ parse_op x stack
                                                              |otherwise        = parse xs stack
                                                             where parse_num (x:xs) acc
                                                                      | isNumber x = parse_num xs $ x:acc
                                                                      | otherwise  = ((read . reverse) acc)::Double
                                                                   parse_op x stack = 
                                                                      let op = case x of
                                                                                  '+' -> (+)
                                                                                  '-' -> (-)
                                                                                  '*' -> (*) 
                                                                                  '/' -> (/)
                                                                      in ((stack !! 1) `op` (stack !! 0)) : []
                                                                   terminator = " \n\t"
                                                          
                                                          main = do
                                                              input <- getLine
                                                              let out = parse input []
                                                              putStrLn (show out)
                                                          
                                                          Cette version "gère" les erreurs : elles les ignore, mais c'est encore assez brouillon. Par contre, elle gère les nombres a virgule.
                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            15 avril 2009 à 16:17:48

                                                            J'adore pas l'utilisation de "read" : c'est un peu overkill (tu utilises une machinerie bien lourde alors que la gestion chiffre par chiffre à la (n * 10 + c) est naturelle est plus rapide).

                                                            Je n'aime pas trop (stack !! 1) et (stack !! 0) non plus, tu devrais utiliser un pattern matching pour ça. D'ailleurs il y a un bug dans ton code ici, : [] vide la stack à chaque fois (par exemple "1 2 3 + +" ne marche pas, parce que le 1 est "oublié").

                                                            Edit : La gestion des espacements (snd . break) est bof, j'aimais mieux avant : maintenant "1 2+" va faire des bêtises.
                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              15 avril 2009 à 16:23:33

                                                              Citation : bluestorm

                                                              Je n'aime pas trop (stack !! 1) et (stack !! 0) non plus, tu devrais utiliser un pattern matching pour ça. D'ailleurs il y a un bug dans ton code ici, : [] vide la stack à chaque fois (par exemple "1 2 3 + +" ne marche pas, parce que le 1 est "oublié").

                                                              J'ai raté la partie sur wikipédia qui disais que la syntaxe "1 2 3 + +" est correcte, j'ai donc implémenté un parseur qui attend comme entrée un String du type : "nombre nombre opérateur nombre opérateur" et ainsi de suite. Je vais essayer de modifier mon code pour qu'il suive ce que tu m'as dit.

                                                              Citation : bluestorm

                                                              Edit : La gestion des espacements (snd . break) est bof, j'aimais mieux avant : maintenant "1 2+" va faire des bêtises.

                                                              Oui, c'est assez moche, mais je ne me suis rendu compte du problème qu'a la fin, et je n'avais pas envi de changer toute la structure du programme :-° .

                                                              EDIT : Pour contourner le problème du dernier point, j'ai changer ca (snd (break (`elem` terminator) xs)) en ca (snd (break (not . isNumber) xs))
                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              [Atelier tous langages] Calculatrice(s)

                                                              × 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