From a7d613c95d91d12fc9693027114554d6cfc15342 Mon Sep 17 00:00:00 2001 From: Jaskarn Singh <81379241+Jaskarn7@users.noreply.github.com> Date: Sat, 3 Apr 2021 12:03:22 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20printf=20statement=20written=20in=20c?= =?UTF-8?q?=20(as=20minprintf)=20without=20using=20stdio.h=20li=E2=80=A6?= =?UTF-8?q?=20(#820)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * printf statement written in c (as minprintf) without using stdio.h library. * Added proper documentation in minprintf.h * Modified and more Documented code. * Requested changes commited * Referance links added * updating DIRECTORY.md * Renamed the file * updating DIRECTORY.md * Test file added * updating DIRECTORY.md * Requested changes commited * Requested changes commited Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 + developer_tools/min_printf.h | 352 ++++++++++++++++++++++++++++++ developer_tools/test_min_printf.c | 42 ++++ 3 files changed, 396 insertions(+) create mode 100644 developer_tools/min_printf.h create mode 100644 developer_tools/test_min_printf.c diff --git a/DIRECTORY.md b/DIRECTORY.md index 3b8a713b..da3be304 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -101,7 +101,9 @@ ## Developer Tools * [Malloc Dbg](https://github.com/TheAlgorithms/C/blob/master/developer_tools/malloc_dbg.c) * [Malloc Dbg](https://github.com/TheAlgorithms/C/blob/master/developer_tools/malloc_dbg.h) + * [Min Printf](https://github.com/TheAlgorithms/C/blob/master/developer_tools/min_printf.h) * [Test Malloc Dbg](https://github.com/TheAlgorithms/C/blob/master/developer_tools/test_malloc_dbg.c) + * [Test Min Printf](https://github.com/TheAlgorithms/C/blob/master/developer_tools/test_min_printf.c) ## Exercism * Acronym diff --git a/developer_tools/min_printf.h b/developer_tools/min_printf.h new file mode 100644 index 00000000..69dec7a6 --- /dev/null +++ b/developer_tools/min_printf.h @@ -0,0 +1,352 @@ +/** + * @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 /// for `malloc` and `free` functions +#include /// for `write` function +#include /// 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 occured + 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 */ diff --git a/developer_tools/test_min_printf.c b/developer_tools/test_min_printf.c new file mode 100644 index 00000000..9d0ed215 --- /dev/null +++ b/developer_tools/test_min_printf.c @@ -0,0 +1,42 @@ +/** + * @file + * @brief File used to test min_printf function. + * @details + * The test will be executed by comparing the result of both `min_printf` and `printf` functions + * @author [Jaskarn7](https://github.com/Jaskarn7) + * @see min_printf.h +*/ + +#include "min_printf.h" /// for `min_printf` function +#include /// for `printf` function + +/** + * @brief Main function + * @details + * This function is used to test `min_printf` function. + * The numbers and string used for the test is generated randomly (The user can also specify their own value for tests) + * First integers were tested then floats and at last strings + * After running the program the user will see three pair of lines with each pair followed by an empty line + * In each pair of lines, the first line will be printed by `min_printf` function and next line by the actual `printf` function + * In each line user will see number or string covered with two colons, they are used to check from where the printing was started and where it ends + * @returns 0 on exit +*/ +int main() +{ + // print strings using `printf` and `min_printf` + min_printf(":%d: :%1.6d:\n", 12, 56); + printf(":%d: :%1.6d:\n", 12, 56); + + printf("\n"); /// Printing an empty new line + + // print floats or doubles using `printf` and `min_printf` + min_printf(":%f: :%3.6f:\n", 104.5654, 43.766443332); + printf(":%f: :%3.6f:\n", 104.5654, 43.766443332); + + printf("\n"); + + // print integers `printf` and `min_printf` + min_printf(":%s: :%4.3s:\n", "Hello, World!", "Hello, World!"); + printf(":%s: :%4.3s:\n", "Hello, World!", "Hello, World!"); + +}