ft_printf is a project that mimics the real printf function.
Although wide in scope, it’s not a difficult project.
THINGS TO KEEP IN MIND before starting off:
Conversions to handle: csdiupxX%
Flags to handle: 0-.*
Value to return: length of the printed string (int).
Reading the man multiple times while coding will counter-intuitively save you a lot of time on special cases. I’ll just leave this one here as an example:
> man 3 printf
[..]
If the 0 and - flags both appear, the 0 flag is ignored. If a precision is given with a numeric conversion (d, i, o, u, x, and X), the 0 flag is ignored.
[..]
Since we need to print stuff, we will use the function write() extensively in this project. Remember that write() is a function of type int that returns the number of characters printed out.
This could be useful for printf return value : wink wink :
Our final repo will look similar to this:
.
├── includes/
├── libft/
├── srcs/
├── Makefile
1 — CREATE A STRUCT
printf prints stuff in a specified format, such as left aligned, right aligned, zero-padded, etc.
These infos are given by the flags, and we need to stock these infos in a struct in order to print the string correctly.
There are many ways of doing a struct, this is a just an example.
typedef struct s_print
{
va_list args; # arg to print out
int wdt; # width
int prc; # precision
int zero; # zero padding
int pnt; # .
int dash; # -
int tl; # total_length (return value)
int sign; # pos or neg number
int is_zero; # is number zero
int perc; # %
int sp; # space flag ' '
} t_print;
After defining your struct, you have to initialise it in your srcs/ft_printf.c:
t_print *ft_initialise_tab(t_print *tab)
{
tab->wdt = 0; # we set everything to 0, false
tab->prc = 0;
tab->zero = 0;
tab->pnt = 0;
tab->sign = 0;
tab->tl = 0;
tab->is_zero = 0;
tab->dash = 0;
tab->perc = 0;
tab->sp = 0;
return (tab);
}int ft_printf(const char *format, ...)
{
t_print *tab;
tab = (t_print *)malloc(sizeof(t_print));
if (!tab)
return (-1);
ft_initialise_tab(tab)
}
2 — CREATE A LOOP 🍭
Your ft_printf will loop through a string and will evaluate it.
As long as the string is not a %, you will keep printing what you read.
When you encounter a %, you will know there is a variable to print.
int ft_printf(const char *format, ...)
{
int i;
int ret;
t_print *tab;
tab = (t_print *)malloc(sizeof(t_print));
if (!tab)
return (-1);
ft_initialise_tab(tab)
va_start(tab->args, format);
i = -1;
ret = 0;
while (format[++i]) # while the string exists
{
if (format[i] == '%') # if the current char is %
i = ft_eval_format(tab, format, i + 1); # evaluate format**
else
ret += write(1, &format[i], 1); # print what you read
}
va_end(tab->args);
ret += tab->tl;
free (tab);
return (ret);
}# [**] why i + 1 ?
# because we start evaluate from the character after the %
Once you encounter a %, you’ll need to evaluate your format, for ex. with a function ft_eval_format().
Your ft_eval_format function will probably be another loop that, in pseudo code, will look like this:
while not udcsupxX%
if (format[pos] == '.')
tab->pnt = 1; # we set it to true, 1
pos++;
if (format[pos] == '-')
tab->dash = 1;
pos++;
// etc
Once you break out of this loop, it means you have encountered a conversion. You’ll then need an if-else to choose the right conversion type, like this, always in pseudo code:
if (format[pos] == 'c')
ft_print_char(tab);
if (format[pos] == 'd' || format[pos] == 'i')
ft_print_integer(tab);
//etc
Now it’s time to use the information you saved in your struct to print the string in the correct format, here’s an example for the c conversion:
void ft_print_char(t_print *tab)
{
char a;
a = va_arg(tab->args, int); # variadic function
ft_update_tab(tab, 1); # calculate special cases and length
if (tab->wdt && !tab->dash) # if width and not - flag
ft_right_cs(tab, 0); # handle right alignment
tab->tl += write(1, &a, 1); # print char
if (tab->wdt && tab->dash) # if width and - flag
ft_left_cs(tab, 0); # handle left alignment
}
There is still one thing to keep in mind, variadic functions.
3 — VARIADIC FUNCTIONS
Variadic functions are functions whose total number of elements is unknown at the beginning.
For ex, you could printf(“%s %d”, “hello”, 0), which is 2 things, or printf(“%s %d %c”, “hello”, 0, ‘w’), which is 3 things.
And so on and so forth. You don’t know how many things you’ll have to print before actually starting printing.
We already used variadic functions above, let’s do a quick recap.
How to declare, initialise, move through and close a variadic function.
va_list args; # declarationva_start(args, format)# we initialise it,
# passing the name of the variable args and
# the last known element, in our case format
# from ft_printf(const char *format,...)# in our function, every time we encounter a conversion csdipuxX%
# we need to move to that variable (string, char, int etc.)
# to read its value (see above, ft_print_char)
va_arg(args, int) # every time, we call va_arg()
# to move to the next variable in memory
# we pass the name of the variable args and
# the type of the variable to va_argva_end(args) # when we are done, we close this function in
# this way, see above ft_printf()
Here a quick table for each conversion type:
c = va_arg(args, int)
s = va_arg(args, char *)
d = va_arg(args, int)
i = va_arg(args, int)
u = va_arg(args, unsigned int)
p = va_arg(args, unsigned long)# or
#(unsigned long)va_arg(args, void *);
x = va_arg(args, unsigned int)
X = va_arg(args, unsigned int)
4 — TIPS
Length and how to fit width and precision into the right length will take a great deal of your time. Be patient, start from unit tests and read the man.
5 — TESTERS (I suggest to use them in this order, from basic to advance)
https://github.com/gavinfielder/pft # basic unit tests
https://github.com/Mazoise/42TESTERS-PRINTF # multiple flags
https://github.com/cacharle/ft_printf_test # randomly generated
6 — OTHER TEST CASES tested by moulinette
INT_MIN and INT_MAX for d and i
%2000.0008% multiple zeros for precision with % conversion
with and without width