Programmation




Autour de la conversion d'une chaîne en entier,

Refactoring par extraction fonctionnelle,

Promotion d'une fonction en classe




Régis Barbanchon

L1 Info-Math, Semestre 2

Écrivons une fonction de conversion de chaîne en entier...



La fonction doit passer les tests suivants :
#include <assert.h>
#include <errno.h> // global error number and constants

int main (void) {
  char * text;     // input string to convert into number
  int number, end; // result and final index of parsing 

  errno= 0; text= "357"; // no explicit sign
  number= Int_FromStringInBase (text, & end, 10);
  assert (errno == 0 && number == +357 && text[end] == '\0');
 
  errno= 0; text= "+357"; // explicit + sign
  number= Int_FromStringInBase (text, & end, 10);
  assert (errno == 0 && number == +357 && text[end] == '\0');

  errno= 0; text= "-280"; // explicit - sign
  number= Int_FromStringInBase (text, & end, 10);
  assert (errno == 0 && number == -280 && text[end] == '\0');

  errno= 0; text= "1001"; // base 2
  number= Int_FromStringInBase (text, & end, 2);
  assert (errno == 0 && number == 9 &&  text[end] == '\0');

  errno= 0; text= "FE"; // base 16
  number= Int_FromStringInBase (text, & end, 16);
  assert (errno == 0 && number == 254 && text[end] == '\0');
  ...
  ... 
  errno= 0; text= "72bla"; // valid with trailing chars
  number= Int_FromStringInBase (text, & end, 10);
  assert (errno == 0 && number == 72 && text[end] == 'b');

  errno= 0; text="+DEADBEEF000"; // signed 32-bit overflow
  number= Int_FromStringInBase (text, & end, 16);                   
  assert (errno == ERANGE && number == INT_MAX && text[end] == 'F');
  
  errno= 0; text="-DEADBEEF000"; // signed 32-bit underflow
  number= Int_FromStringInBase (text, & end, 16);  
  assert (errno == ERANGE && number == INT_MIN && text[end] == 'F');
 
  errno= 0; text= ""; // empty string
  number= Int_FromStringInBase (text, & end, 10);
  assert (errno == EINVAL && number == 0 && text[end] == '\0');
  
  errno= 0; text= "bla"; // invalid from the beginning
  number= Int_FromStringInBase (text, & end, 10);
  assert (errno == EINVAL && number == 0 && text[end] == 'b');

  errno= 0; text= "+bla"; // valid sign still but no digit behind
  number= Int_FromStringInBase ("+bla", & end, 10);
  assert (errno == EINVAL && number == 0 &&  text[end] == 'b');

  return 0;
}

Du simple au complexe, version 1/7

int Int_FromStringInBase10 (char const text[]) {
  int result= 0;
  for (int k= 0; text[k] != '\0'; k++) {
    char digit_characters[]= "0123456789";
    char character= text[k];
    char * found= strchr (digit_characters, character);
    int digit= found - digit_characters;
    result= 10*result + digit;
  }
  return result;
}
Dans cette 1ère version :

  • on ne gère que la base 10,
  • on ne teste pas la validité des caractères,
  • on ne renvoie pas d'information sur la fin de l'analyse,
  • on ne gère pas de signe optionnel devant les chiffres,
  • on ne teste pas le débordement de capacité,
  • on n'emet pas de diagnostic d'erreur (sur errno).


  • Remarques:

  • La fonction strchr() est déclarée dans <string.h>
  • Du simple au complexe, version 2/7

    int Int_FromStringInBase (char const text[], 
                              int base) {
      assert (2 <= base && base <= 16);
      int result= 0;
      for (int k= 0; text[k] != '\0'; k++) {
        char digit_characters[]= "0123456789ABCDEF";
        digit_characters[base]= '\0';
        char character= toupper (text[k]);
        char * found= strchr (digit_characters, character);
        int digit= found - digit_characters;
        result= base*result + digit;
      }
      return result;
    }
    
    Dans cette 2ème version :

  • on gère les bases de 2 à 16,
  • on ne teste pas la validité des caractères,
  • on ne renvoie pas d'information sur la fin de l'analyse,
  • on ne gère pas de signe optionnel devant les chiffres,
  • on ne teste pas le débordement de capacité,
  • on n'emet pas de diagnostic d'erreur (sur errno).


  • Remarques :

  • La macro assert() est déclarée dans <assert.h>
  • La fonction strchr() est déclarée dans <string.h>
  • La fonction toupper() est déclarée dans <ctype.h>
  • Du simple au complexe, version 3/7

    int Int_FromStringInBase (char const text[], 
                              int base) {
      assert (2 <= base && base <= 16);
      int result= 0;
      for (int k= 0; text[k] != '\0'; k++) {
        char digit_characters[]= "0123456789ABCDEF";
        digit_characters[base]= '\0';
        char character= text[k];
        char * found= strchr (digit_characters, character);
        if (found == NULL) break;
        int digit= found - digit_characters;
        result= base*result + digit;
      }
      return result;
    }
    
    Dans cette 3ème version :

  • on gère les bases de 2 à 16,
  • on teste la validité des caractères,
  • on ne renvoie pas d'information sur la fin de l'analyse,
  • on ne gère pas de signe optionnel devant les chiffres,
  • on ne teste pas le débordement de capacité,
  • on n'emet pas de diagnostic d'erreur (sur errno).


  • Remarques :

  • La macro assert() est déclarée dans <assert.h>
  • La fonction strchr() est déclarée dans <string.h>
  • La fonction toupper() est déclarée dans <ctype.h>
  • Du simple au complexe, version 4/7

    int Int_FromStringInBase (char const text[],
                              int * end, int base) {
      assert (2 <= base && base <= 16);
      int result= 0, k;
      for (k= 0; text[k] != '\0'; k++) {
        char digit_characters[]= "0123456789ABCDEF";
        digit_characters[base]= '\0';
        char character= text[k];
        char * found= strchr (digit_characters, character);
        if (found == NULL) break;
        int digit= found - digit_characters;
        result= base*result + digit;
      }
      if (end != NULL) * end= k;
      return result;
    }
    
    Dans cette 4ème version :

  • on gère les bases de 2 à 16,
  • on teste la validité des caractères,
  • on renvoie une information sur la fin de l'analyse,
  • on ne gère pas de signe optionnel devant les chiffres,
  • on ne teste pas le débordement de capacité,
  • on n'emet pas de diagnostic d'erreur (sur errno).


  • Remarques :

  • La macro assert() est déclarée dans <assert.h>
  • La fonction strchr() est déclarée dans <string.h>
  • La fonction toupper() est déclarée dans <ctype.h>
  • Du simple au complexe, version 5/7

    int Int_FromStringInBase (char const text[],
                              int * end, int base) {
      assert (2 <= base && base <= 16);
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      }
      int result= 0, k;
      for (k= start; text[k] != '\0'; k++) {
        char digit_characters[]= "0123456789ABCDEF";
        digit_characters[base]= '\0';
        char character= text[k];
        char * found= strchr (digit_characters, character);
        if (found == NULL) break;
        int digit= found - digit_characters;
        result= base*result + sign*digit;
      }
      if (end != NULL) * end= k;
      return result;
    }
    
    Dans cette 5ème version :

  • on gère les bases de 2 à 16,
  • on teste la validité des caractères,
  • on renvoie une information sur la fin de l'analyse,
  • on gère un signe optionnel devant les chiffres,
  • on ne teste pas le débordement de capacité,
  • on n'emet pas de diagnostic d'erreur (sur errno).


  • Remarques :

  • La macro assert() est déclarée dans <assert.h>
  • La fonction strchr() est déclarée dans <string.h>
  • La fonction toupper() est déclarée dans <ctype.h>
  • Du simple au complexe, version 6/7

    int Int_FromStringInBase (char const text[],
                              int * end, int base) {
      assert (2 <= base && base <= 16);
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      }
      int result= 0, k;
      for (k= start; text[k] != '\0'; k++) {
        char digit_characters[]= "0123456789ABCDEF";
        digit_characters[base]= '\0';
        char character= text[k];
        char * found= strchr (digit_characters, character);
        if (found == NULL) break;
        int digit= found - digit_characters;
        if (result < 0 && result < (INT_MIN + digit) / base) { 
          result= INT_MIN; break; 
        }
        if (result > 0 && result > (INT_MAX - digit) / base) { 
          result= INT_MAX; break; 
        }
        result= base*result + sign*digit;
      }
      if (end != NULL) * end= k;
      return result;
    }
    
    Dans cette 6ème version :

  • on gère les bases de 2 à 16,
  • on teste la validité des caractères,
  • on renvoie une information sur la fin de l'analyse,
  • on gère un signe optionnel devant les chiffres,
  • on teste le débordement de capacité,
  • on n'emet pas de diagnostic d'erreur (sur errno).


  • Remarques :

  • La macro assert() est déclarée dans <assert.h>
  • La fonction strchr() est déclarée dans <string.h>
  • La fonction toupper() est déclarée dans <ctype.h>
  • Les macros INT_MIN et INT_MAX sont déclarées dans <limits.h>
  • Du simple au complexe, version 7/7

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        char character= toupper (text[k]);
        char digit_characters[]= "0123456789ABCDEF";
        digit_characters[base]= '\0';
        char * found= strchr (digit_characters, character);
        if (found == NULL) break;
        int digit= found - digit_characters;
        if (result < 0 && result < (INT_MIN + digit) / base) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (result > 0 && result > (INT_MAX - digit) / base) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        result= base*result + sign*digit;
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    Dans cette 7ème version :

  • on gère les bases de 2 à 16,
  • on teste la validité des caractères,
  • on renvoie une information sur la fin de l'analyse,
  • on gère un signe optionnel devant les chiffres,
  • on teste le débordement de capacité,
  • on emet un diagnostic d'erreur (sur errno).


  • Remarques :

  • La macro assert() est déclarée dans <assert.h>
  • La fonction strchr() est déclarée dans <string.h>
  • La fonction toupper() est déclarée dans <ctype.h>
  • Les macros INT_MIN et INT_MAX sont déclarées dans <limits.h>
  • La variable errno et les constantes ERANGE et EINVAL sont déclarées dans <errno.h>
  • Il faut simplifier, raccourcir, et exprimer les buts poursuivis

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        char character= toupper (text[k]);
        char digit_characters[]= "0123456789ABCDEF";
        digit_characters[base]= '\0';
        char * found= strchr (digit_characters, character);
        if (found == NULL) break;
        int digit= found - digit_characters;
        if (result < 0 && result < (INT_MIN + digit) / base) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (result > 0 && result > (INT_MAX - digit) / base) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        result= base*result + sign*digit;
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    Le code de la fonction est devenu long :

  • on poursuit beaucoup de buts dans une même fonction,
  • chaque but poursuivi est une source d'erreur potentielle,
  • on passe son temps à dire comment on fait,
  • mais on oublie de dire ce que l'on essaye de faire.

    On souhaite maintenant :
  • isoler le plus possibles les buts poursuivis,
  • exprimer ces buts.

    On procède donc à l'extraction fonctionnelle de ces buts.
  • Du complexe au simple, version 1/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], base);
        if (digit == -1) break;
        if (result < 0 && result < (INT_MIN + digit) / base) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (result > 0 && result > (INT_MAX - digit) / base) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        result= base*result + sign*digit;
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    int Int_FromCharInBase (char character, int base) {
      assert (2 <= base && base <= 16);
      char digit_characters[]= "0123456789ABCDEF";
      digit_characters[base]= '\0';
      char * found= strchr (digit_characters, toupper (character));
      if (found == NULL) return -1;
      return found - digit_characters;
    }
    


    Un premier but poursuivi mais non exprimé est la conversion d'un caractère en chiffre.

    On extrait donc ce but vers une fonction.

    Du complexe au simple, version 2/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], base);
        if (digit == -1) break;
        if (Int_AddingDigitUnderflows (result, digit, base)) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (Int_AddingDigitOverflows  (result, digit, base)) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        result= base*result + sign*digit;
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    int Int_FromCharInBase (char character, int base) {
      assert (2 <= base && base <= 16);
      char digit_characters[]= "0123456789ABCDEF";
      digit_characters[base]= '\0';
      char * found= strchr (digit_characters, toupper (character));
      if (found == NULL) return -1;
      return found - digit_characters;
    }
    

    bool Int_AddingDigitUnderflows (int number, int digit, int base) {
      return number < 0 
        &&   number < (INT_MIN + digit) / base;
    }
    

    bool Int_AddingDigitOverflows (int number, int digit, int base) {
      return number > 0
        &&   number > (INT_MAX - digit) / base;
    }
    


    On extrait ensuite les prédicats testant les débordements (négatif et positif)
    lors de l'ajout d'un chiffre à droite d'un nombre.

    Remarque :
  • Le type bool est défini dans <stdbool.h>
  • Du complexe au simple, version 3a/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], base);
        if (digit == -1) break;
        if (Int_AddingDigitUnderflows (result, digit, base)) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (Int_AddingDigitOverflows  (result, digit, base)) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        result= Int_WithDigitAdded (result, sign, digit, base);
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    int Int_FromCharInBase (char character, int base) {
      assert (2 <= base && base <= 16);
      char digit_characters[]= "0123456789ABCDEF";
      digit_characters[base]= '\0';
      char * found= strchr (digit_characters, toupper (character));
      if (found == NULL) return -1;
      return found - digit_characters;
    }
    

    bool Int_AddingDigitUnderflows (int number, int digit, int base) {
      return number < 0 
        &&   number < (INT_MIN + digit) / base;
    }
    

    bool Int_AddingDigitOverflows (int number, int digit, int base) {
      return number > 0
        &&   number > (INT_MAX - digit) / base;
    }
    

    int Int_WithDigitAdded (int number, int sign, int digit, int base) {
      return base*number + sign*digit;
    }
    


    On extrait maintenant l'ajout proprement dit d'un chiffre à la droite d'un nombre.

    Dans cette version, la fonction retourne un résultat sans modifier son argument.

    Du complexe au simple, version 3b/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], base);
        if (digit == -1) break;
        if (Int_AddingDigitUnderflows (result, digit, base)) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (Int_AddingDigitOverflows  (result, digit, base)) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        Int_AddDigit (& result, sign, digit, base);
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    int Int_FromCharInBase (char character, int base) {
      assert (2 <= base && base <= 16);
      char digit_characters[]= "0123456789ABCDEF";
      digit_characters[base]= '\0';
      char * found= strchr (digit_characters, toupper (character));
      if (found == NULL) return -1;
      return found - digit_characters;
    }
    

    bool Int_AddingDigitUnderflows (int number, int digit, int base) {
      return number < 0 
        &&   number < (INT_MIN + digit) / base;
    }
    

    bool Int_AddingDigitOverflows (int number, int digit, int base) {
      return number > 0
        &&   number > (INT_MAX - digit) / base;
    }
    

    void Int_AddDigit (int * number, int sign, int digit, int base) {
      (* number)= base * (* number) + sign * digit;
    }
    


    On extrait toujours l'ajout proprement dit d'un chiffre à la droite d'un nombre.

    Mais dans cette version, la fonction modifie son argument passé par adresse.

    Du complexe au simple, version 4a/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int sign, start;
      ParseOptionalSign (text, & sign, & start);
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], base);
        if (digit == -1) break;
        if (Int_AddingDigitUnderflows (result, digit, base)) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (Int_AddingDigitOverflows  (result, digit, base)) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        Int_AddDigit (& result, sign, digit, base);
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    int Int_FromCharInBase (char character, int base) {...}
    

    bool Int_AddingDigitUnderflows (int number, int digit, int base) {...}
    

    bool Int_AddingDigitOverflows (int number, int digit, int base) {...}
    

    void Int_AddDigit (int * number, int sign, int digit, int base) {
      (* number)= base * (* number) + sign * digit;
    }
    

    void ParseOptionalSign (char const text[], int * sign, int * start) {
      switch (text[0]) {
      case '-': (* sign)= -1; (* start)= 1; break;
      case '+': (* sign)= +1; (* start)= 1; break;
      default : (* sign)= +1; (* start)= 0; break;
      }
    } 
    


    On extrait l'analyse de la présence optionnelle d'un caractère de signe.

    Dans cette version, la fonction modifie deux arguments passés par adresse :
  • le signe sign du nombre,
  • l'index start attendu pour le premier chiffre du nombre.
  • Du complexe au simple, version 4b/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
      int start;
      int sign= ParseOptionalSign_Alt (text, & start);
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], base);
        if (digit == -1) break;
        if (Int_AddingDigitUnderflows (result, digit, base)) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (Int_AddingDigitOverflows  (result, digit, base)) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        Int_AddDigit (& result, sign, digit, base);
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    int Int_FromCharInBase (char character, int base) {...}
    

    bool Int_AddingDigitUnderflows (int number, int digit, int base) {...}
    

    bool Int_AddingDigitOverflows (int number, int digit, int base) {...}
    

    void Int_AddDigit (int * number, int sign, int digit, int base) {
      (* number)= base * (* number) + sign * digit;
    }
    

    int ParseOptionalSign_Alt (char const text[], int * start) {
      switch (text[0]) {
      case '-': (* start)= 1; return -1;
      case '+': (* start)= 1; return +1;
      default : (* start)= 0; return +1;
      }
    } 
    


    On extrait toujours l'analyse de la présence optionnelle d'un caractère de signe.

    Dans cette version alternative, la fonction renvoie le signe par le canal de retour.

    Du complexe au simple, version 5a/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
    
      int start, sign;
      ParseOptionalSign (text, & sign, & start);
    
      int k, result;
      ParseDigits (text, sign, start, & result, & k, base);
    
      if (end != NULL) * end= k;
      return result;
    }
    


    La fonction analysant la série des chiffres a besoin de 6 paramètres.

    Dans cette version, elle en modifie deux, passés par adresse.
    int Int_FromCharInBase (char character, int base) {...}
    

    bool Int_AddingDigitUnderflows (int number, int digit, int base) {...}
    

    bool Int_AddingDigitOverflows (int number, int digit, int base) {...}
    

    void Int_AddDigit (int * number, int sign, int digit, int base) {...}
    

    void ParseOptionalSign (char const text[], int * sign, int * start) {...}
    

    void ParseDigits (char const text[], int sign, int start, 
                      int * result, int * k, int base) {
      * result= 0;
      for (* k= start; text[* k] != '\0'; (* k)++) {
        int digit= Int_FromCharInBase (text[* k], base);
        if (digit == -1) break;
        if (Int_AddingDigitUnderflows (* result, digit, base)) { 
          errno= ERANGE; * result= INT_MIN; break; 
        }
        if (Int_AddingDigitOverflows  (* result, digit, base)) { 
          errno= ERANGE; * result= INT_MAX; break; 
        }
        Int_AddDigit (result, sign, digit, base);
      }
      if (* k == start) errno= EINVAL;
    }
    

    Du complexe au simple, version 5b/5

    int Int_FromStringInBase (char const text[], 
                              int * end, int base) {
      assert (2 <= base && base <= 16); 
    
      int start;
      int sign= ParseOptionalSign_Alt (text, & start);
    
      int k;
      int result= ParseDigits_Alt (text, sign, start, & k, base);
    
      if (end != NULL) * end= k;
      return result;
    }
    


    Dans cette version alternative, la fonction analysant la série des chiffres n'a plus que 5 paramètres.

    Elle n'en modifie qu'un, passé par adresse : l'index de fin d'analyse.

    Le résultat (la chaîne convertie en nombre) passe par le canal de retour de la fonction.


    Remarques :
  • les fonctions ont de plus en plus de paramètres techniques,
  • on n'a pas trouvé de classe responsable pour les deux dernières fonctions d'analyse,
  • ceci indique qu'il manque un acteur pour l'action,
  • si on essaye d'analyser une chaîne, il faut peut être introduire un analyseur.
  • int Int_FromCharInBase (char character, int base) {...}
    

    bool Int_AddingDigitUnderflows (int number, int digit, int base) {...}
    

    bool Int_AddingDigitOverflows (int number, int digit, int base) {...}
    

    void Int_AddDigit (int * number, int digit, int base) {...}
    

    int ParseOptionalSign_Alt (char const text[], int * start) {...}
    

    int ParseDigits_Alt (char const text[], int sign, int start, 
                         int * k, int base) {
      int result= 0;
      for (* k= start; text[* k] != '\0'; (* k)++) {
        int digit= Int_FromCharInBase (text[* k], base);
        if (digit == -1) break;
        if (Int_AddingDigitUnderflows (result, digit, base)) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (Int_AddingDigitOverflows  (result, digit, base)) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        Int_AddDigit (& result, digit, base);
      }
      if (* k == start) errno= EINVAL;
      return result;
    }
    

    Promotion en classe 1/7

    int Int_FromStringInBase (char const text[], int * end, int base) {
      assert (2 <= base && base <= 16); 
    
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], p.base);
        if (digit == -1) break;
        if (result < 0 && result < (INT_MIN + digit) / base) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (result > 0 && result > (INT_MAX - digit) / base) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        result= base*result + sign*digit;
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    
    On promeut maintenant la fonction de conversion en un convertisseur de chaîne :

  • un type IntParser est introduit, avec pour champs...
  • les paramètres de la fonction (l'input),
  • le résultat de la fonction (l'output),
  • une partie des variables locales décrivant l'état d'exécution.

    typedef struct IntParser {
      char const * text;  // input
      int base;           // input 
      int sign, start, k; // state
      int result;         // output
    } IntParser; 
    


    On reprend la fonction depuis le début,
    En effectuant les extractions fonctionnelles avec ce convertisseur.
    Les fonctions extraites deviennent les méthodes de celui-ci.
  • Promotion en classe 2/7

    int Int_FromStringInBase (char const text[], int * end, int base) {
      assert (2 <= base && base <= 16); 
    
      int sign, start;
      switch (text[0]) {
      case '-': sign= -1; start= 1; break;
      case '+': sign= +1; start= 1; break;
      default : sign= +1; start= 0; break;
      } 
      int k, result= 0;
      for (k= start; text[k] != '\0'; k++) {
        int digit= Int_FromCharInBase (text[k], p.base);
        if (digit == -1) break;
        if (result < 0 && result < (INT_MIN + digit) / base) { 
          errno= ERANGE; result= INT_MIN; break; 
        }
        if (result > 0 && result > (INT_MAX - digit) / base) { 
          errno= ERANGE; result= INT_MAX; break; 
        }
        result= base*result + sign*digit;
      }
      if (k == start) errno= EINVAL;
      if (end != NULL) * end= k;
      return result;
    }
    


    À gauche, la fonction tel qu'écrite initialement.
    int Int_FromStringInBase (char const text[], int * end, int base) {
      assert (2 <= base && base <= 16); 
      IntParser p;
      p.text= text; p.base= base;
      switch (p.text[0]) {
      case '-': p.sign= -1; p.start= 1; break;
      case '+': p.sign= +1; p.start= 1; break;
      default : p.sign= +1; p.start= 0; break;
      } 
      p.result= 0;
      for (p.k= p.start; p.text[ p.k] != '\0'; p.k++) {
        int digit= Int_FromCharInBase (p.text[p.k], p.base);
        if (digit == -1) break;
        if (p.result < 0 && p.result < (INT_MIN + digit) / p.base) { 
          errno= ERANGE; p.result= INT_MIN; break; 
        }
        if (p.result > 0 && p.result > (INT_MAX - digit) / p.base) { 
          errno= ERANGE; p.result= INT_MAX; break; 
        }
        p.result= p.base * p.result + p.sign * digit;
      }
      if (p.k ==  p.start) errno= EINVAL;
      if (end != NULL) * end= p.k;
      return p.result;
    } 
    


    À droite, la même fonction avec les variables locales remplacèes par les champs de la structure.

    Promotion en classe 3/7

    int Int_FromStringInBase (char const text[], int * end, int base) {
      assert (2 <= base && base <= 16); 
      IntParser p;
      p.text= text; p.base= base;
      switch (p.text[0]) {
      case '-': p.sign= -1; p.start= 1; break;
      case '+': p.sign= +1; p.start= 1; break;
      default : p.sign= +1; p.start= 0; break;
      } 
      p.result= 0;
      for (p.k= p.start; p.text [p.k] != '\0'; p.k++) {
        int digit= Int_FromCharInBase (p.text [p.k], p.base);
        if (digit == -1) break;
        if (IntParser_AddingDigitUnderflows (& p, digit)) { 
          errno= ERANGE; p.result= INT_MIN; break; 
        }
        if (IntParser_AddingDigitOverflows (& p, digit)) { 
          errno= ERANGE; p.result= INT_MAX; break; 
        }
        p.result= p.base * p.result + p.sign * digit;
      }
      if ( ! IntParser_HasDigits (& p)) errno= EINVAL;
      if (end != NULL) * end= p.k;
      return p.result;
    } 
    
    bool IntParser_AddingDigitUnderflows (IntParser const * p, int digit) {
      return p->result < 0
        &&   p->result < (INT_MIN + digit) / p->base;
    }
    

    bool IntParser_AddingDigitOverflows (IntParser const * p, int digit) {
      return p->result > 0
        &&   p->result > (INT_MAX - digit) / p->base;
    }
    

    bool IntParser_HasDigits (IntParser const * p) {
      return p->start < p->k;
    }
    


    On commence par extraire les prédicats de débordement.

    On a également rajouté un prédicat testant l'absence de chiffre en fin d'analyse.

    Promotion en classe 4/7

    
    int Int_FromStringInBase (char const text[], int * end, int base) {
      assert (2 <= base && base <= 16); 
      IntParser p;
      p.text= text; p.base= base;
      IntParser_ParseOptionalSign (& p);
      p.result= 0;
      for (p.k= p.start; p.text [p.k] != '\0'; p.k++) {
        int digit= Int_FromCharInBase (p.text [p.k], p.base);
        if (digit == -1) break;
        if (IntParser_AddingDigitUnderflows (& p, digit)) { 
          errno= ERANGE; p.result= INT_MIN; break; 
        }
        if (IntParser_AddingDigitOverflows (& p, digit)) { 
          errno= ERANGE; p.result= INT_MAX; break; 
        }
        IntParser_AddDigit (& p, digit);
      }
      if ( ! IntParser_HasDigits (& p)) errno= EINVAL;
      if (end != NULL) * end= p.k;
      return p.result;
    } 
    
    bool IntParser_AddingDigitUnderflows (IntParser const * p, int digit) {...}
    

    bool IntParser_AddingDigitOverflows (IntParser const * p, int digit) {...}
    

    bool IntParser_HasDigits (IntParser const * p) {...}
    

    void IntParser_ParseOptionalSign (IntParser * p) {
      switch (p->text[0]) {
      case '-': p->sign= -1; p->start= 1; break;
      case '+': p->sign= +1; p->start= 1; break;
      default : p->sign= +1; p->start= 0; break;
      } 
    }
    

    void IntParser_AddDigit (IntParser * p, int digit) {
      p->result= p->base * p->result + p->sign * digit;
    }
    


    On extrait maintenant l'analyse du signe, et l'ajout d'un chiffre au nombre.

    Promotion en classe 5/7

    int Int_FromStringInBase (char const text[], int * end, int base) {
      assert (2 <= base && base <= 16); 
      IntParser p;
      p.text= text; p.base= base;
      IntParser_ParseOptionalSign (& p);
      IntParser_ParseDigits       (& p);
      if (end != NULL) * end= p.k;
      return p.result;
    } 
    


    La fonction d'analyse de la série des chiffres n'a plus qu'un seul paramètre, l'analyseur lui même.
    bool IntParser_AddingDigitUnderflows (IntParser const * p, int digit) {...}
    

    bool IntParser_AddingDigitOverflows (IntParser const * p, int digit) {...}
    

    bool IntParser_HasDigits (IntParser const * p) {...}
    

    void IntParser_ParseOptionalSign (IntParser * p) {...}
    

    void IntParser_AddDigit (IntParser * p, int digit) {...}
    

    void IntParser_ParseDigits (IntParser * p) {
      p->result= 0;
      for (p->k= p->start; p->text [p->k] != '\0'; p->k++) {
        int digit= Int_FromCharInBase (p->text [p->k], p->base);
        if (digit == -1) break;
        if (IntParser_AddingDigitUnderflows (p, digit)) { 
          errno= ERANGE; p->result= INT_MIN; break; 
        }
        if (IntParser_AddingDigitOverflows (p, digit)) { 
          errno= ERANGE; p->result= INT_MAX; break; 
        }
        IntParser_AddDigit (p, digit);
      }
      if ( ! IntParser_HasDigits (p)) errno= EINVAL;
    }
    

    Promotion en classe 6/7

    int Int_FromStringInBase (char const text[], int * end, int base) {
      IntParser p;
      IntParser_Init (& p, text, base);
      IntParser_ParseOptionalSign (& p);
      IntParser_ParseDigits       (& p);
      if (end != NULL) * end= p.k;
      return p.result;
    } 
    


    On rajoute une fonction d'initialisation.

    On fait en sorte d'initialiser tous les champs de la structure.
    bool IntParser_AddingDigitUnderflows (IntParser const * p, int digit) {...}
    

    bool IntParser_AddingDigitOverflows (IntParser const * p, int digit) {...}
    

    bool IntParser_HasDigits (IntParser const * p) {...}
    

    void IntParser_ParseOptionalSign (IntParser * p) {...}
    

    void IntParser_AddDigit (IntParser * p, int digit) {...}
    

    void IntParser_ParseDigits (IntParser * p) {...}
    

    void IntParser_Init (IntParser * p, char const text[], int base) {
      assert (2 <= base && base <= 16); 
      p->text= text;    // input
      p->base= base;    // input
      p->sign= +1;      // state, default value 
      p->start= 0;      // state, default value
      p->result= 0;     // output, default value 
    }
    

    Promotion en classe 7/7

    int Int_FromStringInBase (char const text[], int * end, int base) {
      IntParser p;
      IntParser_Run (& p, text, base);
      if (end != NULL) * end= p.k;
      return p.result;
    } 
    


    Enfin, on regroupe l'initialisation, l'analyse du signe et des chiffres dans une seule fonction de lancement.

  • La fonction Int_FromStringInBase() n'est plus qu'un wrapper délégant sa tâche IntParser_Run().
  • bool IntParser_AddingDigitUnderflows (IntParser const * p, int digit) {...}
    

    bool IntParser_AddingDigitOverflows (IntParser const * p, int digit) {...}
    

    bool IntParser_HasDigits (IntParser const * p) {...}
    

    void IntParser_ParseOptionalSign (IntParser * p) {...}
    

    void IntParser_AddDigit (IntParser * p, int digit) {...}
    

    void IntParser_ParseDigits (IntParser * p) {...}
    

    void IntParser_Init (IntParser * p, char const text[], int base) {...}
    

    void IntParser_Run (IntParser * p, char const text[], int base) {
      IntParser_Init (p, text, base);
      IntParser_ParseOptionalSign (p);
      IntParser_ParseDigits       (p);
    }
    

    Extraction d'un corps de boucle utilisant le Early-Exit (1/2)

    void IntParser_ParseDigits (IntParser * p) {
      p->result= 0;
      for (p->k= p->start; p->text [p->k] != '\0'; p->k++) {
        int digit= Int_FromCharInBase (p->text [p->k], p->base);
        if (digit == -1) break; 
        if (IntParser_AddingDigitUnderflows (p, digit)) {
          errno= ERANGE; p->result= INT_MIN; break; 
        }
        if (IntParser_AddingDigitOverflows (p, digit)) {
          errno= ERANGE; p->result= INT_MAX; break; 
        }
        IntParser_AddDigit (p, digit);
      }  
      if ( ! IntParser_HasDigits (p)) errno= EINVAL;
    }
    
  • Pour terminer, extrayons le corps de la boucle de IntParser_ParseDigits().
  • Ce corps utilise le Early-Exit avec l'instruction de sortie de boucle break.
  • Comment dans ce cas l'extraire vers une fonction IntParser_ParseCurrentDigit() ?
  • Extraction d'un corps de boucle utilisant le Early-Exit (2/2)

    void IntParser_ParseDigits (IntParser * p) {
      p->result= 0;
      for (p->k= p->start; p->text [p->k] != '\0'; p->k++) {
        int digit= Int_FromCharInBase (p->text [p->k], p->base);
        if (digit == -1) break; 
        if (IntParser_AddingDigitUnderflows (p, digit)) {
          errno= ERANGE; p->result= INT_MIN; break; 
        }
        if (IntParser_AddingDigitOverflows (p, digit)) {
          errno= ERANGE; p->result= INT_MAX; break; 
        }
        IntParser_AddDigit (p, digit);  
      }
      if ( ! IntParser_HasDigits (p)) errno= EINVAL;
    }
    


    Réponse à la question du slide précédent dans la colonne de droite...
    void IntParser_ParseDigits (IntParser * p) {
      p->result= 0;
      for (p->k= p->start; p->text [p->k] != '\0'; p->k++) {
        if (IntParser_ParseCurrentDigit (p) == -1) break; 
      }
      if ( ! IntParser_HasDigits (p) ) errno= EINVAL;
    }
    

    int IntParser_ParseCurrentDigit (IntParser * p) {
      int digit= Int_FromCharInBase (p->text [p->k], p->base);
      if (digit == -1) return -1; 
      if (IntParser_AddingDigitUnderflows (p, digit)) {
        errno= ERANGE; p->result= INT_MIN; return -1; 
      }
      if (IntParser_AddingDigitOverflows (p, digit)) {
        errno= ERANGE; p->result= INT_MAX; return -1; 
      }
      IntParser_AddDigit (p, digit);
      return 0; 
    }