#include "stdio.h"
#include "ctype.h"
#include <string.h>
#include <limits.h>
#include <errno.h>
#define abs(n) ((n) < 0 ? -(n) : (n))
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;
}
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;
}
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';
}
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';
}
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;
}
}
static inline int is_digit(char c)
{
return __isdigit((unsigned char)c);
}
static void add_digit(unsigned int *n, char c)
{
*n = *n * 10 + (c - '0');
}
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";
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;
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);
}
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);
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;
}
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);
}