/* 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 . */ #include "stdio.h" #include "ctype.h" #include #include #include #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); }