-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
min_printf.h
356 lines (309 loc) · 11.3 KB
/
min_printf.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/**
* @file
* @brief Implementation of a [function](https://www.geeksforgeeks.org/variable-length-argument-c) similar to `printf`
* @details
* `printf` statement rewritten (as `min_printf`) in C without using the `stdio.h` library
* Syntax of `min_printf` is same as `printf`
* Currently min_printf handles:
* Integers, Doubles, floats, characters and strings
* The format specifiers and escape sequence is the same as for `printf`
* User can also specify the width and precision if required, just like in the case of `printf`
* How to use it:
* - First include min_printf.h in your code
* - Then type `min_printf()`, and pass required parameters to it
* - As already specified, it's syntax is same as printf
* @author [Jaskarn Singh](https://github.com/Jaskarn7)
*/
#ifndef MIN_PRINTF_H
#define MIN_PRINTF_H
#include <stdlib.h> /// for `malloc` and `free` functions
#ifdef _WIN32
#include <io.h> /// for `write` function
#else
#include <unistd.h> /// for `write` function
#endif
#include <stdarg.h> /// for `va_start` and `va_arg` functions
#define INT_MAX_LENGTH 10 /// used as standard length of string to store integers
#define PRECISION_FOR_FLOAT 8 /// default precision for float or double if not specified
/**
* @brief struct used to store character in certain times
*/
typedef struct buffer {
char buffr_char; // Character will be stored in this variable
int buf_size; // Checks if character is present in buffr_char or not, 0 if no and 1 if yes
} Buffer;
/**
* @details
* This function return ten to the power a(The parameter specified to it) like:
* if the parameter specified is 4 i.e. -> power_of_ten(4) is called then
* this function will return ten to the power four (10000);
* @param a The power of ten which is to be returned
* @return Ten to the power a
*/
int power_of_ten(int a)
{
int n = 1; ///< This number will be returned as ten to power of a
for (int i = 1; i <= a; ++i)
n *= 10 ;
return n;
}
/**
* @brief Checks if a character is a number
* @param c character to be checked if it's a number or not
* @return `true`(1) if the character is a number
* @return `false`(0) if the character is NOT a number
*/
int is_number(char *c)
{
return (*c >= '0' && *c <= '9') ? 1 : 0;
}
/**
* @brief Returns specific required next character
* @param p pointer to a format string of `min_printf()`
* @param buffer struct for checking if buffr_char character is present or not
* @return character inside `buffer->buffr_char`, if `buffer->buf_size` is one
* @return character at which p is pointing, if `buffer->buf_size` is zero
*/
char get_ch(char *p, Buffer *buffer)
{
if (buffer->buf_size) {
buffer->buf_size = 0; ///< Since character is used, this sets `buffer->buf_size` to zero
return buffer->buffr_char; // Returns character inside buffer->buffr_char
}
return *p++;
}
/**
* @brief Stores character to the `buffer->buffr_char`
* @param c character to be stored in the `buffer->buffr_char`
* @param buffer struct where character will be stored
*/
void unget_ch(char *c, Buffer *buffer)
{
buffer->buffr_char = *c; // Character initializes inside buffer->buffr_char
buffer->buf_size = 1; // Sets bufsize to one as new character is stored in buffr_char
}
/**
* @brief Calculates the number of digits in a number
* @param n number whose digits are to be counted
* @return number of digits in n
*/
int get_number_of_digits(int n)
{
int digits = 0; // Stores encountered number of digits
while (n > 0) {
++digits; // Since number still contains a digit, so increment digit variable
n /= 10; // Removes last digit from number
}
return digits;
}
/**
* @brief Prints one character on screen
* @param s character to be printed on the screen
*/
void put_char(char s)
{
/* buf used for storing character to be printed in an array (+1 for '\0')*/
char *buf = (char *) malloc(sizeof(char) + 1);
*buf = s;
*(buf + 1) = '\0';
write(1, buf, 1);
free(buf);
}
/**
* @brief Reverses a string using [two pointer algorithm](https://www.geeksforgeeks.org/program-reverse-array-using-pointers/?ref=rp)
* @param p pointer to the string which is to be reversed
*/
void reverse_str(char *p)
{
char *l = p; // Points to first character of p
char *h = p; // Will be used to point to last character of p
char temp; // Temporarily stores a character, Used in swapping
while (*h != '\0')
++h;
--h; // Now h point to last valid character of string
/* Swap character which lower and higher are pointing until lower < higher. At that point string will be reversed.*/
while (l < h) {
temp = *l;
*l = *h;
*h = temp;
++l; // Increment lower to next character
--h; // Decrement higher to previous character from current character
}
}
/**
* @details
* The algorithm here is to first convert the number into
* string and then reverse it be passing it to reverse_str function
* and then printing on the screen
* @param n Number to be printed
* @param width Total characters to be printed (Prints ' ' if (size < width)
* @param precision Total character of number to be printed (prints 0 before number if size of number < precision)
*
*/
void print_int_value(int n, int width, int precision)
{
char *p = (char *) malloc(INT_MAX_LENGTH * sizeof(char) + 1); /* +1 for '\0' */
char *s = p; // Temporary pointer
int size = 0; //!< Used to store number of digits in number
while (n > 0) {
*s++ = n % 10 + '0'; // Converts last digit of number to character and store it in p
++size; // Increment size variable as one more digit is occurred
n /= 10; // Removes the last digit from the number n as we have successfully stored it in p
}
*s = '\0';
s = p; // Again point back s to starting of p
reverse_str(p);
/*!
* The next two conditions check weather it is required to
* add blanks before printing the number (ie: width)and is it specified how many
* zeros to be printed before the number is printed (ie: precision)
*/
if (width > 0 && size < width)
for (int i = 0; i < (width - precision); ++i)
put_char(' ');
if (precision > 0 && precision > size)
for (int i = 0; i < (precision - size); ++i)
put_char('0');
/* Prints the number.*/
while (*s != '\0')
put_char(*s++);
free(p);
}
/**
* @brief The algorithm here is also the same as the `print_int_value` function
*
* @details
* First, the digits before decimal is printed by converting the double
* to int. Then after printed a `.`, the double number is subtracted with
* the integer value of the number, leaving us with 0 before the decimal.
* Then, we multiply the number with 10 raised to the power precision (
* precision means how many digits to be printed after the decimal.)
* By default, the precision is 8 if it is not specified.
* Then, the remaining number is printed on the screen.
* @param dval double number to be printed
* @param width similar to width parameter of print_int_value()
* @param precision tells the number of digits to be printed after the decimal (By default it is 8)
*/
void print_double_value(double dval, int width, int precision)
{
int ndigits = get_number_of_digits((int) dval); // Store number of digits before decimal in dval
int reqd_blanks = width - (precision + 1) - ndigits; // Blanks to be printed before printing dval, just to cover the width
print_int_value((int) dval, reqd_blanks, 0); // Prints the part before decimal
put_char('.'); // Print decimal
/*Deletes digits before decimal and makes them zero. For example:
if dval = 1923.79022, them this will make dval = 0.79022
*/
dval = dval - (int) dval;
dval *= power_of_ten(precision); // Brings precision number of digits after decimal to before decimal
print_int_value((int) dval, 0, precision); // Prints the remaining number
}
/**
* @details
* First size of the string is calculated to check whether
* width and precision are to be taken into account or not.
* Then, the string is printed in accordingly.
* @param p pointer to string to be printed
* @param width if (width > sizeof string) then, blanks will be printed before sting to cover up the width
* @param precision total characters of the string to be printed (prints the whole string if 0 or greater than size of string)
*/
void print_string(char *p, int width, int precision)
{
int size = 0; // Stores number of character in string
char *s = p; // Temporary pointer
/* Calculates size of string p*/
while (*s != '\0') {
++size;
++s;
}
s = p; // Point s to starting of p
/* Checks how many characters to be printed.
if precision is defined then size variable is changed to precision so that only precision
number of characters were printed.
*/
if (precision != 0 && precision < size)
size = precision;
/* Prints blanks to cover the width if required*/
for (int i = 0; i < (width - size); ++i)
put_char(' ');
/* Print the string.*/
for (int i = 0; i < size; ++i)
put_char(*s++);
}
/**
* @brief Takes width and precision specified from the format of the string
* @param p pointer of the format string
* @param width variable in which width will be stored
* @param precision variable in which precision will be stored
* @return character pointer to the current pointer of string p (used to update value of p)
*/
char *get_width_and_precision(char *p, Buffer *buffer, int *width, int *precision)
{
/* Skip % if p is pointing to it.*/
if (*p == '%')
++p;
/* Calculates the width specified. */
while (*p != '.' && is_number(p))
*width = *width * 10 + (*p++ - '0');
/* Calculates the precision specified.*/
if (*p == '.' /* Since a precision is always specified after a '.'. */) {
while (is_number(++p))
*precision = *precision * 10 + (*p - '0');
unget_ch(p, buffer); // The non number will be stored in `buffer->buffr`
}
return p;
}
/**
* min_printf is the function same as printf
* @param fmt format of string
* @param ... arguments passed according to the format
*/
void min_printf(char *fmt, ...)
{
va_list ap; // Points to each unnamed arg in turn
char *p, *sval; // p will be used to point to fmt and sval will store string value
char cval; // Stores character value
int ival; // For integer values
double dval; // For double or float values
va_start(ap, fmt); // Makes ap points to first unnames argument
/* Initializing the buffer for storing character. */
Buffer *buffer = (Buffer *) malloc(sizeof(Buffer));
buffer->buf_size = 0; // Initially set buffer size to zero as no character is inserted
for (p = fmt; *p != '\0'; ++p) {
/* If p != '%' then the character is printed to screen. */
if (*p != '%') {
put_char(*p);
continue;
}
int width = 0; // Stores width specified
int precision = 0; // Stores precision specified
/* Updates values of width, precision and p. */
p = get_width_and_precision(p, buffer, &width, &precision);
/* Checks format of next argument.*/
switch (get_ch(p, buffer)) {
case 'd': // Integer
ival = va_arg(ap, int);
print_int_value(ival, width, precision);
break;
case 'c': // Character
cval = va_arg(ap, int);
put_char(cval);
break;
case 'f': // Float or Double
dval = va_arg(ap, double);
// If precision is not specified then default value is applied
if (precision == 0)
precision = PRECISION_FOR_FLOAT;
print_double_value(dval, width, precision);
break;
case 's': // String pointer
sval = va_arg(ap, char *);
print_string(sval, width, precision);
break;
default:
put_char(*p);
break;
}
}
va_end(ap);
}
#endif /* MIN_PRINTF_H */