View of xos/usr/lib/libc/printf.c


XOS | Parent Directory | View | Download

/* Copyright (C) 2008  Emmanuel Varoquaux
 
   This file is part of XOS.
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
#include "stdio.h"
 
#include "ctype.h"
 
#include <string.h>
#include <limits.h>
#include <errno.h>
 
#define abs(n) ((n) < 0 ? -(n) : (n))
 
/* Fonction d'impression */
 
struct printer_struct;
 
struct printer_vtbl_struct {
  int (*print)(struct printer_struct *, char);
};
 
struct printer_struct {
  const struct printer_vtbl_struct *vptr;
  unsigned int n;
  union {
    struct {
      FILE *stream;
    } stream_printer;
    struct {
      char *str;
    } string_printer;
    struct {
      char *buf;
      size_t size;
    } buffer_printer;
  };
};
 
static void printer_init(struct printer_struct *printer)
{
  printer->n = 0;
}
 
static int printer_print(struct printer_struct *printer, char c)
{
  if (printer->vptr->print(printer, c) < 0)
    return -1;
  printer->n++;
  if (printer->n > INT_MAX) {
    errno = EOVERFLOW;
    return -1;
  }
  return 0;
}
 
static unsigned int printer_count(const struct printer_struct *printer)
{
  return printer->n;
}
 
static inline int print_rep(char c, unsigned int n, struct printer_struct *printer)
{
  while (n) {
    if (printer_print(printer, c) < 0)
      return -1;
    n--;
  }
  return 0;
}
 
static inline int print_string(const char *s, struct printer_struct *printer)
{
  while (*s)
    if (printer_print(printer, *s++) < 0)
      return -1;
  return 0;
}
 
static inline int print_string_n(const char *s, unsigned int n, struct printer_struct *printer)
{
  while (n) {
    if (printer_print(printer, *s++) < 0)
      return -1;
    n--;
  }
  return 0;
}
 
/* Impression dans un flux */
 
static int stream_printer_print(struct printer_struct *printer, char c)
{
  return __fputc(c, printer->stream_printer.stream) == EOF ? -1 : 0;
}
 
static const struct printer_vtbl_struct stream_printer_vtbl = {
  .print = stream_printer_print
};
 
static void stream_printer_init(struct printer_struct *printer, FILE *stream)
{
  printer->vptr = &stream_printer_vtbl;
  printer_init(printer);
  printer->stream_printer.stream = stream;
}
 
/* Impression dans une chaine de caracteres */
 
static int string_printer_print(struct printer_struct *printer, char c)
{
  *printer->string_printer.str++ = c;
  return 0;
}
 
static const struct printer_vtbl_struct string_printer_vtbl = {
  .print = string_printer_print
};
 
static void string_printer_init(struct printer_struct *printer, char *str)
{
  printer->vptr = &string_printer_vtbl;
  printer_init(printer);
  printer->string_printer.str = str;
}
 
static void string_printer_end(struct printer_struct *printer)
{
  *printer->string_printer.str = '\0';
}
 
/* Impression dans une chaine de caracteres avec longueur maximale */
 
static int buffer_printer_print(struct printer_struct *printer, char c)
{
  if (printer->buffer_printer.size > 1) {
    if (printer->buffer_printer.buf)
      *printer->buffer_printer.buf++ = c;
    printer->buffer_printer.size--;
  }
  return 0;
}
 
static const struct printer_vtbl_struct buffer_printer_vtbl = {
  .print = buffer_printer_print
};
 
static void buffer_printer_init(struct printer_struct *printer, char *buf, size_t size)
{
  printer->vptr = &buffer_printer_vtbl;
  printer_init(printer);
  printer->buffer_printer.buf = buf;
  printer->buffer_printer.size = size;
}
 
static void buffer_printer_end(struct printer_struct *printer)
{
  if (printer->buffer_printer.size && printer->buffer_printer.buf)
    *printer->buffer_printer.buf = '\0';
}
 
/* Options de conversion */
 
enum {LM_DEFAULT, LM_SHORT, LM_LONG};
 
struct conversion_options_struct {
  struct {
    unsigned left_justify : 1;
    unsigned sign : 1;
    unsigned space : 1;
    unsigned alternative : 1;
    unsigned zero_pad : 1;
  } flags;
  unsigned int field_width;
  int precision;
  int length_modifier;
};
 
static const struct conversion_options_struct default_conversion_options = {
  .flags = {
    .left_justify = 0,
    .sign = 0,
    .space = 0,
    .alternative = 0,
    .zero_pad = 0
  },
  .field_width = 0,
  .precision = -1,
  .length_modifier = LM_DEFAULT
};
 
static inline int is_flag(char c)
{
  return (int)strchr("-+ #0", c);
}
 
static void set_flag(struct conversion_options_struct *conversion_options, char c)
{
  switch (c) {
  case '-':
    conversion_options->flags.left_justify = 1;
    break;
  case '+':
    conversion_options->flags.sign = 1;
    break;
  case ' ':
    conversion_options->flags.space = 1;
    break;
  case '#':
    conversion_options->flags.alternative = 1;
    break;
  case '0':
    conversion_options->flags.zero_pad = 1;
    break;
  }
}
 
static inline int is_length_modifier(char c)
{
  return (int)strchr("hlzt", c);
}
 
static void set_length_modifier(struct conversion_options_struct *conversion_options, char c)
{
  switch (c) {
  case 'h':
    conversion_options->length_modifier = LM_SHORT;
    break;
  case 'l':
    conversion_options->length_modifier = LM_LONG;
    break;
  case 'z':
    if (sizeof (size_t) > sizeof (unsigned int))
      conversion_options->length_modifier = LM_LONG;
    break;
  case 't':
    if (sizeof (ptrdiff_t) > sizeof (unsigned int))
      conversion_options->length_modifier = LM_LONG;
    break;
  }
}
 
/* Construction d'un entier decimal */
 
static inline int is_digit(char c)
{
  return __isdigit((unsigned char)c);
}
 
/* Le comportement est indefini si add_digit() conduit a un debordement. */
static void add_digit(unsigned int *n, char c)
{
  *n = *n * 10 + (c - '0');
}
 
/* Conversion simple d'un nombre non signe */
 
struct number_conversion_struct {
  unsigned long number;
  unsigned int base;
  const char *digits;
  unsigned int number_width;
  unsigned int number_of_zeros;
};
 
static void number_conversion_init(struct number_conversion_struct *number_conversion, unsigned long number, unsigned int base, const char *digits, unsigned int precision, unsigned int number_of_zeros)
{
  unsigned long n;
 
  number_conversion->number = number;
  number_conversion->base = base;
  number_conversion->digits = digits;
  number_conversion->number_width = 0;
  for (n = number; n; n /= base)
    number_conversion->number_width++;
  if (number_conversion->number_width + number_of_zeros < precision)
    number_conversion->number_of_zeros = precision - number_conversion->number_width;
  else
    number_conversion->number_of_zeros = number_of_zeros;
}
 
static unsigned int number_conversion_get_width(const struct number_conversion_struct *number_conversion)
{
  return number_conversion->number_of_zeros + number_conversion->number_width;
}
 
static int number_conversion_print(const struct number_conversion_struct *number_conversion, struct printer_struct *printer)
{
  unsigned long mask;
  unsigned long n, d;
 
  if (print_rep('0', number_conversion->number_of_zeros, printer) < 0)
    return -1;
  if (!number_conversion->number_width)
    return 0;
  mask = 1;
  while (mask <= number_conversion->number / number_conversion->base)
    mask *= number_conversion->base;
  n = number_conversion->number;
  do {
    d = n / mask;
    if (printer_print(printer, number_conversion->digits[d]) < 0)
      return -1;
    n -= d * mask;
    mask /= number_conversion->base;
  }
  while (mask);
  return 0;
}
 
static const char digits_lc[] = "0123456789abcdef";
static const char digits_uc[] = "0123456789ABCDEF";
 
/* Conversions */
 
struct conversion_struct;
 
struct conversion_vtbl_struct {
  unsigned int (*get_width)(const struct conversion_struct *);
  int (*print)(const struct conversion_struct *, struct printer_struct *);
};
 
struct conversion_struct {
  const struct conversion_vtbl_struct *vptr;
  union {
    struct {
      const char *prefix;
      struct number_conversion_struct number_conversion;
    } integer;
    struct {
      unsigned char c;
    } byte;
    struct {
      const char *s;
      unsigned int len;
    } string;
    struct {
      int *n;
    } count;
    struct {
      const struct conversion_options_struct *conversion_options;
      char specifier;
      struct number_conversion_struct field_width_number_conversion;
      struct number_conversion_struct precision_number_conversion;
    } bad_specifier;
  };
};
 
struct field_struct {
  unsigned int min_width;
  unsigned left_justify : 1;
  struct conversion_struct conversion;
};
 
static void field_init(struct field_struct *field, const struct conversion_options_struct *conversion_options)
{
  field->min_width = conversion_options->field_width;
  field->left_justify = conversion_options->flags.left_justify;
}
 
static int field_print(const struct field_struct *field, struct printer_struct *printer)
{
  unsigned int conversion_width;
  unsigned int number_of_spaces;
 
  conversion_width = field->conversion.vptr->get_width(&field->conversion);
  number_of_spaces = conversion_width < field->min_width ? field->min_width - conversion_width : 0;
  if (!field->left_justify)
    if (print_rep(' ', number_of_spaces, printer) < 0)
      return -1;
  if (field->conversion.vptr->print(&field->conversion, printer) < 0)
    return -1;
  if (field->left_justify)
    if (print_rep(' ', number_of_spaces, printer) < 0)
      return -1;
  return 0;
}
 
static unsigned int integer_conversion_get_width(const struct conversion_struct *conversion)
{
  return strlen(conversion->integer.prefix) + number_conversion_get_width(&conversion->integer.number_conversion);
}
 
static int integer_conversion_print(const struct conversion_struct *conversion, struct printer_struct *printer)
{
  if (print_string(conversion->integer.prefix, printer) < 0)
    return -1;
  if (number_conversion_print(&conversion->integer.number_conversion, printer) < 0)
    return -1;
  return 0;
}
 
static const struct conversion_vtbl_struct integer_conversion_vtbl = {
  .get_width = integer_conversion_get_width,
  .print = integer_conversion_print
};
 
static void integer_conversion_field_init(struct field_struct *field, const char *prefix, unsigned long number, unsigned int base, const char *digits, unsigned int number_of_zeros, const struct conversion_options_struct *conversion_options)
{
  unsigned int precision;
  unsigned int w;
 
  field_init(field, conversion_options);
  field->conversion.vptr = &integer_conversion_vtbl;
  field->conversion.integer.prefix = prefix;
  if (conversion_options->precision >= 0)
    precision = conversion_options->precision;
  else {
    precision = 1; /* defaut */
    if (!conversion_options->flags.left_justify && conversion_options->flags.zero_pad)
      if (precision < (w = conversion_options->field_width - strlen(field->conversion.integer.prefix)))
        precision = w;
  }
  number_conversion_init(&field->conversion.integer.number_conversion, number, base, digits, precision, number_of_zeros);
}
 
static const char *no_prefix = "";
 
static void signed_decimal_conversion_field_init(struct field_struct *field, long number, const struct conversion_options_struct *conversion_options)
{
  const char *prefix;
 
  if (number < 0)
    prefix = "-";
  else if (conversion_options->flags.sign)
    prefix = "+";
  else if (conversion_options->flags.space)
    prefix = " ";
  else
    prefix = no_prefix;
  integer_conversion_field_init(field, prefix, abs(number), 10, digits_lc, 0, conversion_options);
}
 
static void octal_conversion_field_init(struct field_struct *field, unsigned long number, const struct conversion_options_struct *conversion_options)
{
  integer_conversion_field_init(field, no_prefix, number, 8, digits_lc, conversion_options->flags.alternative ? 1 : 0, conversion_options);
}
 
static void unsigned_decimal_conversion_field_init(struct field_struct *field, unsigned long number, const struct conversion_options_struct *conversion_options)
{
  integer_conversion_field_init(field, no_prefix, number, 10, digits_lc, 0, conversion_options);
}
 
static void hexadecimal_conversion_field_init(struct field_struct *field, unsigned long number, const struct conversion_options_struct *conversion_options)
{
  integer_conversion_field_init(field, conversion_options->flags.alternative && number ? "0x" : no_prefix, number, 16, digits_lc, 0, conversion_options);
}
 
static void uc_hexadecimal_conversion_field_init(struct field_struct *field, unsigned long number, const struct conversion_options_struct *conversion_options)
{
  integer_conversion_field_init(field, conversion_options->flags.alternative && number ? "0X" : no_prefix, number, 16, digits_uc, 0, conversion_options);
}
 
static unsigned int byte_conversion_get_width(const struct conversion_struct *conversion)
{
  return 1;
}
 
static int byte_conversion_print(const struct conversion_struct *conversion, struct printer_struct *printer)
{
  return printer_print(printer, conversion->byte.c);
}
 
static const struct conversion_vtbl_struct byte_conversion_vtbl = {
  .get_width = byte_conversion_get_width,
  .print = byte_conversion_print
};
 
static void byte_conversion_field_init(struct field_struct *field, unsigned char c, const struct conversion_options_struct *conversion_options)
{
  field_init(field, conversion_options);
  field->conversion.vptr = &byte_conversion_vtbl;
  field->conversion.byte.c = c;
}
 
static unsigned int string_conversion_get_width(const struct conversion_struct *conversion)
{
  return conversion->string.len;
}
 
static int string_conversion_print(const struct conversion_struct *conversion, struct printer_struct *printer)
{
  return print_string_n(conversion->string.s, conversion->string.len, printer);
}
 
static const struct conversion_vtbl_struct string_conversion_vtbl = {
  .get_width = string_conversion_get_width,
  .print = string_conversion_print
};
 
static void string_conversion_field_init(struct field_struct *field, const char *s, const struct conversion_options_struct *conversion_options)
{
  static const char *null_string = "(null)";
 
  unsigned int len;
 
  field_init(field, conversion_options);
  field->conversion.vptr = &string_conversion_vtbl;
  field->conversion.string.s = s ? : conversion_options->precision < 0 || (unsigned int)conversion_options->precision >= strlen(null_string) ? null_string : "";
  len = strlen(field->conversion.string.s);
  field->conversion.string.len = conversion_options->precision >= 0 && len > (unsigned int)conversion_options->precision ? (unsigned int)conversion_options->precision : len;
}
 
static void pointer_conversion_field_init(struct field_struct *field, const void *p, const struct conversion_options_struct *conversion_options)
{
  struct conversion_options_struct pointer_conversion_options;
 
  pointer_conversion_options = *conversion_options;
  if (p) {
    pointer_conversion_options.flags.alternative = 1;
    pointer_conversion_options.length_modifier = LM_LONG;
    hexadecimal_conversion_field_init(field, (unsigned long)p, &pointer_conversion_options);
  }
  else {
    pointer_conversion_options.precision = -1;
    string_conversion_field_init(field, "(nil)", &pointer_conversion_options);
  }
}
 
static unsigned int count_conversion_get_width(const struct conversion_struct *conversion)
{
  return 0;
}
 
static int count_conversion_print(const struct conversion_struct *conversion, struct printer_struct *printer)
{
  *conversion->count.n = printer_count(printer);
  return 0;
}
 
static const struct conversion_vtbl_struct count_conversion_vtbl = {
  .get_width = count_conversion_get_width,
  .print = count_conversion_print
};
 
static void count_conversion_field_init(struct field_struct *field, int *n)
{
  field_init(field, &default_conversion_options);
  field->conversion.vptr = &count_conversion_vtbl;
  field->conversion.count.n = n;
}
 
static unsigned int percent_conversion_get_width(const struct conversion_struct *conversion)
{
  return 1;
}
 
static int percent_conversion_print(const struct conversion_struct *conversion, struct printer_struct *printer)
{
  return printer_print(printer, '%');
}
 
static const struct conversion_vtbl_struct percent_conversion_vtbl = {
  .get_width = percent_conversion_get_width,
  .print = percent_conversion_print
};
 
static void percent_conversion_field_init(struct field_struct *field, const struct conversion_options_struct *conversion_options)
{
  field_init(field, &default_conversion_options);
  field->conversion.vptr = &percent_conversion_vtbl;
}
 
static unsigned int bad_conversion_get_width(const struct conversion_struct *conversion)
{
  unsigned int width;
 
  width = 1;
  if (conversion->bad_specifier.conversion_options->flags.alternative)
    width++;
  if (conversion->bad_specifier.conversion_options->flags.sign)
    width++;
  else if (conversion->bad_specifier.conversion_options->flags.space)
    width++;
  if (conversion->bad_specifier.conversion_options->flags.left_justify)
    width++;
  else if (conversion->bad_specifier.conversion_options->flags.zero_pad)
    width++;
  width += number_conversion_get_width(&conversion->bad_specifier.field_width_number_conversion);
  if (conversion->bad_specifier.conversion_options->precision >= 0)
    width += 1 + number_conversion_get_width(&conversion->bad_specifier.precision_number_conversion);
  switch (conversion->bad_specifier.conversion_options->length_modifier) {
  case LM_SHORT:
  case LM_LONG:
    width++;
    break;
  }
  width++;
  return width;
}
 
static int bad_conversion_print(const struct conversion_struct *conversion, struct printer_struct *printer)
{
  if (printer_print(printer, '%') < 0)
    return -1;
  if (conversion->bad_specifier.conversion_options->flags.alternative) {
    if (printer_print(printer, '#') < 0)
      return -1;
  }
  if (conversion->bad_specifier.conversion_options->flags.sign) {
    if (printer_print(printer, '+') < 0)
      return -1;
  }
  else if (conversion->bad_specifier.conversion_options->flags.space) {
    if (printer_print(printer, ' ') < 0)
      return -1;
  }
  if (conversion->bad_specifier.conversion_options->flags.left_justify) {
    if (printer_print(printer, '-') < 0)
      return -1;
  }
  else if (conversion->bad_specifier.conversion_options->flags.zero_pad) {
    if (printer_print(printer, '0') < 0)
      return -1;
  }
  if (number_conversion_print(&conversion->bad_specifier.field_width_number_conversion, printer) < 0)
    return -1;
  if (conversion->bad_specifier.conversion_options->precision >= 0) {
    if (printer_print(printer, '.') < 0)
      return -1;
    if (number_conversion_print(&conversion->bad_specifier.precision_number_conversion, printer) < 0)
      return -1;
  }
  switch (conversion->bad_specifier.conversion_options->length_modifier) {
  case LM_SHORT:
    if (printer_print(printer, 'h') < 0)
      return -1;
    break;
  case LM_LONG:
    if (printer_print(printer, 'l') < 0)
      return -1;
    break;
  }
  if (printer_print(printer, conversion->bad_specifier.specifier) < 0)
    return -1;
  return 0;
}
 
static const struct conversion_vtbl_struct bad_conversion_vtbl = {
  .get_width = bad_conversion_get_width,
  .print = bad_conversion_print
};
 
static void bad_conversion_field_init(struct field_struct *field, char specifier, const struct conversion_options_struct *conversion_options)
{
  field_init(field, &default_conversion_options);
  field->conversion.vptr = &bad_conversion_vtbl;
  field->conversion.bad_specifier.conversion_options = conversion_options;
  field->conversion.bad_specifier.specifier = specifier;
  number_conversion_init(&field->conversion.bad_specifier.field_width_number_conversion, conversion_options->field_width, 10, digits_lc, 0, 0);
  if (field->conversion.bad_specifier.conversion_options->precision >= 0)
    number_conversion_init(&field->conversion.bad_specifier.precision_number_conversion, conversion_options->precision, 10, digits_lc, 1, 0);
}
 
/* Chaine de format */
 
static inline long fetch_convert_integer(va_list *ap, const struct conversion_options_struct *conversion_options)
{
  long arg;
 
  arg = conversion_options->length_modifier == LM_LONG ? va_arg(*ap, long) : va_arg(*ap, int);
  return (conversion_options->length_modifier == LM_SHORT) ? (short)arg : arg;
}
 
static inline unsigned long fetch_convert_unsigned_integer(va_list *ap, const struct conversion_options_struct *conversion_options)
{
  unsigned long arg;
 
  arg = conversion_options->length_modifier == LM_LONG ? va_arg(*ap, unsigned long) : va_arg(*ap, unsigned int);
  return (conversion_options->length_modifier == LM_SHORT) ? (unsigned short)arg : arg;
}
 
static int apply_conversion(char specifier, const struct conversion_options_struct *conversion_options, va_list *ap, struct printer_struct *printer)
{
  struct field_struct field;
 
  switch (specifier) {
  case 'd':
  case 'i':
    signed_decimal_conversion_field_init(&field, fetch_convert_integer(ap, conversion_options), conversion_options);
    break;
  case 'o':
    octal_conversion_field_init(&field, fetch_convert_unsigned_integer(ap, conversion_options), conversion_options);
    break;
  case 'u':
    unsigned_decimal_conversion_field_init(&field, fetch_convert_unsigned_integer(ap, conversion_options), conversion_options);
    break;
  case 'x':
    hexadecimal_conversion_field_init(&field, fetch_convert_unsigned_integer(ap, conversion_options), conversion_options);
    break;
  case 'X':
    uc_hexadecimal_conversion_field_init(&field, fetch_convert_unsigned_integer(ap, conversion_options), conversion_options);
    break;
  case 'c':
    byte_conversion_field_init(&field, va_arg(*ap, int), conversion_options);
    break;
  case 's':
    string_conversion_field_init(&field, va_arg(*ap, const char *), conversion_options);
    break;
  case 'p':
    pointer_conversion_field_init(&field, va_arg(*ap, const void *), conversion_options);
    break;
  case 'n':
    count_conversion_field_init(&field, va_arg(*ap, int *));
    break;
  case '%':
    percent_conversion_field_init(&field, conversion_options);
    break;
  default:
    bad_conversion_field_init(&field, specifier, conversion_options);
  }
  return field_print(&field, printer);
}
 
enum {
  ST_READ,
  ST_READ_FLAG,
  ST_READ_FIELD_WIDTH_1,
  ST_READ_PRECISION, ST_READ_PRECISION_1, ST_READ_PRECISION_2,
  ST_READ_LENGTH_MODIFIER,
  ST_READ_SPECIFIER
};
 
static int print_format(const char *format, va_list ap, struct printer_struct *printer)
{
  int state;
  char c;
  struct conversion_options_struct conversion_options;
  unsigned int n;
  int i;
 
  state = ST_READ;
  n = 0;
  while ((c = *format++)) {
    switch (state) {
    case ST_READ:
      if (c == '%') {
        conversion_options = default_conversion_options;
        state = ST_READ_FLAG;
        continue;
      }
      if (printer_print(printer, c) < 0)
        return -1;
      state = ST_READ;
      continue;
    case ST_READ_FLAG:
      if (is_flag(c)) {
        set_flag(&conversion_options, c);
        state = ST_READ_FLAG;
        continue;
      }
      if (c == '*') {
        i = va_arg(ap, int);
        if (i < 0)
          set_flag(&conversion_options, '-');
        conversion_options.field_width = abs(i);
        state = ST_READ_PRECISION;
        continue;
      }
      if (is_digit(c)) {
        n = 0;
        add_digit(&n, c);
        state = ST_READ_FIELD_WIDTH_1;
        continue;
      }
    case ST_READ_PRECISION:
    read_precision:
      if (c == '.') {
        state = ST_READ_PRECISION_1;
        continue;
      }
    case ST_READ_LENGTH_MODIFIER:
    read_length_modifier:
      if (is_length_modifier(c)) {
        set_length_modifier(&conversion_options, c);
        state = ST_READ_SPECIFIER;
        continue;
      }
    case ST_READ_SPECIFIER:
      if (apply_conversion(c, &conversion_options, &ap, printer) < 0)
        return -1;
      state = ST_READ;
      continue;
    case ST_READ_FIELD_WIDTH_1:
      if (is_digit(c)) {
        add_digit(&n, c);
        state = ST_READ_FIELD_WIDTH_1;
        continue;
      }
      conversion_options.field_width = n;
      goto read_precision;
    case ST_READ_PRECISION_1:
      if (c == '*') {
        conversion_options.precision = va_arg(ap, int); /* pas de precision si l'argument est superieur a INT_MAX */
        state = ST_READ_LENGTH_MODIFIER;
        continue;
      }
      n = 0;
    case ST_READ_PRECISION_2:
      if (is_digit(c)) {
        add_digit(&n, c);
        state = ST_READ_PRECISION_2;
        continue;
      }
      conversion_options.precision = n;
      goto read_length_modifier;
    }
  }
  if (state != ST_READ)
    return -1;
  return 0;
}
 
/* Fonctions exportees */
 
int __attribute__ ((weak, visibility ("default"))) printf(const char *format, ...)
{
  struct printer_struct printer;
  va_list ap;
 
  stream_printer_init(&printer, stdout);
  va_start(ap, format);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  va_end(ap);
  return printer_count(&printer);
}
 
int __attribute__ ((weak, visibility ("default"))) fprintf(FILE *stream, const char *format, ...)
{
  struct printer_struct printer;
  va_list ap;
 
  stream_printer_init(&printer, stream);
  va_start(ap, format);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  va_end(ap);
  return printer_count(&printer);
}
 
int __attribute__ ((weak, visibility ("default"))) sprintf(char *s, const char *format, ...)
{
  struct printer_struct printer;
  va_list ap;
 
  string_printer_init(&printer, s);
  va_start(ap, format);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  va_end(ap);
  string_printer_end(&printer);
  return printer_count(&printer);
}
 
int __attribute__ ((weak, visibility ("default"))) snprintf(char *s, size_t size, const char *format, ...)
{
  struct printer_struct printer;
  va_list ap;
 
  if (size > INT_MAX) {
    errno = EOVERFLOW;
    return -1;
  }
  buffer_printer_init(&printer, s, size);
  va_start(ap, format);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  va_end(ap);
  buffer_printer_end(&printer);
  return printer_count(&printer);
}
 
int __attribute__ ((weak, visibility ("default"))) vprintf(const char *format, va_list ap)
{
  struct printer_struct printer;
 
  stream_printer_init(&printer, stdout);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  return printer_count(&printer);
}
 
int __attribute__ ((weak, visibility ("default"))) vfprintf(FILE *stream, const char *format, va_list ap)
{
  struct printer_struct printer;
 
  stream_printer_init(&printer, stream);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  return printer_count(&printer);
}
 
int __attribute__ ((weak, visibility ("default"))) vsprintf(char *s, const char *format, va_list ap)
{
  struct printer_struct printer;
 
  string_printer_init(&printer, s);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  string_printer_end(&printer);
  return printer_count(&printer);
}
 
int __attribute__ ((weak, visibility ("default"))) vsnprintf(char *s, size_t size, const char *format, va_list ap)
{
  struct printer_struct printer;
 
  if (size > INT_MAX) {
    errno = EOVERFLOW;
    return -1;
  }
  buffer_printer_init(&printer, s, size);
  if (print_format(format, ap, &printer) < 0)
    return -1;
  buffer_printer_end(&printer);
  return printer_count(&printer);
}