The format loop in tci_printf walks the format string character by
character. For most characters it writes the byte directly. For % it
reads the following character and dispatches to the appropriate handler.
This is the simplest possible lexer[1]: one character of lookahead, no
backtracking, no state carried between iterations.
The loop structure
The loop from the previous page:
while (fmt[i]) {
if (fmt[i] == '%' && fmt[i + 1]) { /* fmt[i+1]: guard against trailing % */
i++; /* skip past % — i now points at specifier */
count += dispatch(fmt[i], &args);
} else
count += tci_putchar_fd(fmt[i], 1);
i++; /* advance past the current character */
}The condition fmt[i + 1] guards against a trailing % at the end of
the format string with no following character. In that case the % is
treated as a literal — written by tci_putchar_fd. This matches libc
behaviour.
tci_printf("score: %d\n", 100); /* % followed by d — dispatches to tci_print_signed */
tci_printf("100%"); /* % at end of string — fmt[i+1] is '\0', prints literal % */
tci_printf("100%%"); /* %% specifier — dispatches to tci_putchar_fd('%', 1) */The dispatch function
Replace the stub dispatch with a switch:
static int dispatch(char spec, va_list *args)
{
if (spec == 'c')
return (tci_putchar_fd((unsigned char)va_arg(*args, int), 1));
/* char is promoted to int in variadic calls; cast back to unsigned char */
if (spec == 's')
return (tci_putstr_fd(va_arg(*args, char *), 1));
if (spec == 'p')
return (tci_print_ptr(va_arg(*args, void *)));
if (spec == 'd' || spec == 'i') /* identical behaviour for both specifiers */
return (tci_print_signed(va_arg(*args, int)));
if (spec == 'u')
return (tci_print_unsigned(va_arg(*args, unsigned int)));
if (spec == 'x')
return (tci_putnbr_base(va_arg(*args, unsigned int),
"0123456789abcdef", 1)); /* lowercase digit string */
if (spec == 'X')
return (tci_putnbr_base(va_arg(*args, unsigned int),
"0123456789ABCDEF", 1)); /* uppercase digit string */
if (spec == '%')
return (tci_putchar_fd('%', 1)); /* no va_arg call — no argument consumed */
return (tci_putchar_fd('%', 1)
+ tci_putchar_fd(spec, 1)); /* unknown: preserve % and print char */
}This references four functions not yet written: tci_print_ptr,
tci_print_signed, tci_print_unsigned, and tci_putnbr_base. Add forward
declarations for all four above dispatch:
static int tci_putchar_fd(char c, int fd); /* defined in 02-write.mdx */
static int tci_putstr_fd(const char *s, int fd); /* defined in 02-write.mdx */
static int tci_print_ptr(void *ptr); /* defined: hex page */
static int tci_print_signed(int n); /* defined: integers page */
static int tci_print_unsigned(unsigned int n); /* defined: integers page */
static int tci_putnbr_base(unsigned long n, const char *base, int fd); /* defined: integers page */Add stub implementations that return 0 so the file compiles. The stubs will be replaced on the following pages as each specifier group is built:
static int tci_print_ptr(void *ptr)
{
(void)ptr;
return (0);
}
static int tci_print_signed(int n)
{
(void)n;
return (0);
}
static int tci_print_unsigned(unsigned int n)
{
(void)n;
return (0);
}
static int tci_putnbr_base(unsigned long n, const char *base, int fd)
{
(void)n;
(void)base;
(void)fd;
return (0);
}The (void)param casts suppress -Wunused-parameter warnings from
-Wextra. A stub that silently ignores its parameters would produce a
warning on every build; casting to void tells the compiler the omission
is intentional, not an oversight.
Run make re. No warnings. The dispatcher compiles even though the
specifier handlers are stubs.
Unknown specifiers
When dispatch receives an unknown specifier, it prints both the %
and the character. The C standard declares behaviour for unknown
specifiers undefined, but glibc on Linux prints both — and glibc
printf is the oracle the tester compares against. Matching it is the
right default.
tci_printf("%Q"); /* prints: %Q — % preserved, unknown char printed */
tci_printf("%W%d", 5); /* prints: %W5 — unknown %W preserved, %d consumes 5 */
tci_printf("100%%"); /* prints: 100% — %% is a known specifier */The next page implements %c and %s — the two specifiers with no
numeric conversion.