commit 6bcbbfd725b4761be9e5df539bd8dfec9686de4e Author: Николай Пузанов Date: Sun Dec 13 09:51:25 2020 +0300 Initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6b41f99 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +all: test + +test: test.c uprintf.c uprintf.h + gcc -Os -m32 -o test test.c uprintf.c + +clean: + rm -rf test diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa21445 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +## Simple printf replacement + +The conversion specificator in the format string as in printf begins +with a % character. This character can be followed by a type specifier +with an optional of the padding width, padding character and direction +of the padding. + +By default numbers are right-justified with '0' and strings are +left-justified with a space. To specify an alignment character precede +the width indicator with a single quote (') and an alignment +character. + +Next comes the width indicator - a signed decimal number indicating +the minimum length of the output string. If the string is less than +this number, the missing length is taken up by alignment +characters. If the width is specified as a negative number, the +alignment is right-justified. If no alignment character is specified +for a number, it is always right-justified with '0'. Instead of a +number for width you can be use a '*' symbol, which means that the +width must be taken from the function argument. + +The type specifier can be prefixed with 'l' for a long integer (64 +bits) and / or prefix 'u' for an unsigned number. + +Type specifiers: + - d, i Decimal integer. + - o Octal whole. + - b Binary integer. + - x Hexadecimal integer (lowercase). + - X Hexadecimal integer (uppercase). + - c Symbol. + - s String. If width is specified, then left alignment. + +Example: + +``` +p ("I: +% '= - 6i + \ n", 10); + +Output: +I: + ==== 10+ +``` + +## Простая замена printf + +Спецификаторы в форматной строке так же как и в printf начинаются со +знака %. После этого знака может идти спецификатор типа с опциональным +указанием ширины выравнивания, символа для выравнивания и направления +выравнивания. + +По умолчанию числа выравниваются символом '0' по правому краю, а +строки пробелом по левому краю. Чтобы задать символ выравниявания, +перед указателем ширины нужно поставить одинарную кавычку (') и символ +выравнивания. + +Далее идет указатель ширины - десятичное число со знаком, обозначающее +минимальную длину выводимой строки. Если строка получается меньше +этого числа, недостающая длина добирается символами выравнивания. Если +ширина указана в виде отрицательного числа, то выравнивание +выполняется по правому краю. Если для числа не указан символ +выравнивания, то оно всегда выравнивается символом '0' по правому +краю. Вместо числа может стоять символ '*', говоряший о том, что +ширину нужно брать из аргумента функции. + +Спецификатор типа может иметь префикс 'l', обозначающий длинное целое +(64 бита) и/или префикс 'u', обозначающий беззнаковое число. + +Спецификаторы типа: + - d, i Десятичное целое. + - o Восьмиричное целое. + - b Двоичное целое. + - x Шестнадцатиричное целое (строчными). + - X Шестнадцатиричное целое (прописные). + - c Символ. + - s Строка. Если указана ширина, то выравнивание по левому краю. + +Пример: + +``` +p("I:+%'=-6i+\n", 10); + +Вывод: +I:+====10+ +``` diff --git a/test.c b/test.c new file mode 100644 index 0000000..850dcc4 --- /dev/null +++ b/test.c @@ -0,0 +1,26 @@ +#include +#include "uprintf.h" + +void put_char(char c) +{ + fputc(c, stdout); +} + +int main(void) +{ + p("Hello %s!\n", "ALL"); + p("1: |%4i:%4i:%4i|\n", 1, -2, 3); + p("2: |%-4i:%-4i:%-4i|\n", 1, -2, 3); + p("3: |%' 4i:%'.4i:%''4i|\n", 1, -2, 3); + p("4: |%' -4i:%'.-4i:%''-4i|\n", 1, -2, 3); + + p("5: |%4s:%4s:%4s|\n", "a", "b", "c"); + p("6: |%-4s:%-4s:%-4s|\n", "a", "b", "c"); + + p("7: |%*s|\n", 5, "s"); + p("8: |%'-*s|\n", -5, "s"); + + /* TODO more tests */ + + return 0; +} diff --git a/uprintf.c b/uprintf.c new file mode 100644 index 0000000..c1ac216 --- /dev/null +++ b/uprintf.c @@ -0,0 +1,349 @@ +#include +#include +#include "uprintf.h" + +/* + * ------------------- Простая замена printf ------------------------ + * + * Спецификаторы в форматной строке так же как и в printf начинаются + * со знака %. После этого знака может идти спецификатор типа с + * опциональным указанием ширины выравнивания, символа для + * выравнивания и направления выравнивания. + * + * Первым должен идти спецификатор символа выравнивания - знак ' + * (одинарная скобка). После него - символ для выравнивания. + * + * По умолчанию числа выравниваются символом '0' по правому краю, а + * строки пробелом по левому краю. + * + * Далее идет указатель ширины - десятичное число со знаком, + * обозначающее минимальную длину выводимой строки. Если строка + * получается меньше этого числа, недостающая длина добирается + * символами выравнивания. Если ширина указана в виде отрицательного + * числа, то выравнивание выполняется по правому краю. Если для числа + * не указан символ выравнивания, то оно всегда выравнивается символом + * '0' по правому краю. Вместо числа может стоять символ '*', + * говоряший о том, что ширину нужно брать из аргумента функции. + * + * Спецификатор типа может иметь префикс 'l', обозначающий длинное целое + * (64 бита) и/или префикс 'u', обозначающий беззнаковое число. + * + * Спецификаторы типа: + * d, i Десятичное целое. + * o Восьмиричное целое. + * b Двоичное целое. + * x Шестнадцатиричное целое (строчными). + * X Шестнадцатиричное целое (прописные). + * c Символ. + * s Строка. Если указана ширина, то выравнивание по левому краю. + * + * Пример: + * p("I:+%'=-6i+\n", 10); + * Вывод: I:+====10+ + * + */ + +static const char abet[2][16] = {{ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }, + { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }}; + +typedef enum { + PS_REGULAR = 0, + PS_JSYM_SPEC, + PS_JSYM, + PS_WIDTH_SIGN, + PS_WIDTH_ARG, + PS_WIDTH, + PS_TYPE, +} pstate; + +static int l_strlen(const char *str) +{ + int l = 0; + while (*str++) l++; + return l; +} + +/* Helper functions for p() */ +static void print_string(put_char_func pc, const char *str, int width, char wchr) +{ + int sl; + + if (width < 0) + { + sl = l_strlen(str); + for (int w = -width; w > sl; w --) + pc(wchr); + } + + for (sl = 0; *str; str ++, sl ++) + pc(*str); + + if (width > 0) + { + for (int w = width; w > sl; w --) + pc(wchr); + } +} + +static void print_decimal(put_char_func pc, uint64_t u, int negative, unsigned int base, int width, int lcase, char wchr) +{ + if (base > 16) base = 16; + if (base < 2) base = 2; + if (lcase != 0) lcase = 1; + + char s[66]; + int si = 64; + + s[si--] = 0; + + do { + int l = (int)(u % base); + u = u / base; + s[si--] = abet[lcase][l]; + } while (u > 0); + + if (negative) { + if (wchr == '0') { + pc('-'); + + if (width > 0) width --; + else if (width < 0) width ++; + } + else + s[si--] = '-'; + } + + si++; + + print_string(pc, s+si, width, wchr); +} + +static void print_unsigned(put_char_func pc, uint64_t s, unsigned int base, int width, int lcase, char wchr) +{ + print_decimal(pc, s, 0, base, width, lcase, wchr); +} + +static void print_signed(put_char_func pc, int64_t s, unsigned int base, int width, int lcase, char wchr) +{ + if (s < 0) + print_decimal(pc, (uint64_t)-s, 1, base, width, lcase, wchr); + else + print_decimal(pc, (uint64_t)s, 0, base, width, lcase, wchr); +} + +static int l_isdigit(char c) +{ + return (c >= '0' && c <= '9') ? 1 : 0; +} + +/* Like a vprintf */ +void pv(put_char_func pc, const char *fmt, va_list ap) +{ + /* Initialization for supress gcc warnings */ + int width = 0, + wsign = 1, + lng = 0, + sgn = 0, + ab = 0; + + /* Width adjustment character */ + char wchr = 0; + + unsigned int base = 0; + char c; + int64_t d; + + pstate st = PS_REGULAR; + + for(;;) + { + c = *fmt; + + if (c == 0) break; + + switch (st) + { + /* ---------------------------------------------------------- */ + case PS_REGULAR: + fmt ++; + + if (c == '%') { + st = PS_JSYM_SPEC; + lng = 0; + sgn = 1; + width = 0; + wsign = 1; + base = 10; + ab = 0; + wchr = 0; + } + else + pc(c); + + break; + + /* ---------------------------------------------------------- */ + case PS_JSYM_SPEC: + if (c == '\'') { + fmt ++; + st = PS_JSYM; + } + else + st = PS_WIDTH_SIGN; + break; + + /* ---------------------------------------------------------- */ + case PS_JSYM: + fmt ++; + wchr = c; + + st = PS_WIDTH_SIGN; + break; + + /* ---------------------------------------------------------- */ + case PS_WIDTH_SIGN: + if (c == '-') { + fmt ++; + wsign = -1; + } + + st = PS_WIDTH_ARG; + break; + + /* ---------------------------------------------------------- */ + case PS_WIDTH_ARG: + if (c == '*') { + fmt ++; + width = va_arg(ap, int); + st = PS_TYPE; + } else + st = PS_WIDTH; + break; + + /* ---------------------------------------------------------- */ + case PS_WIDTH: + if (l_isdigit(c)) { + fmt ++; + width = width * 10 + (c - '0'); + } else + st = PS_TYPE; + break; + + /* ---------------------------------------------------------- */ + case PS_TYPE: + fmt ++; + + switch (c) + { + case 'l': + lng = 1; + continue; + + case 'u': + sgn = 0; + continue; + + case 'd': + case 'i': + case 'b': + case 'o': + case 'x': + case 'X': + if (( lng) && ( sgn)) d = (int64_t)va_arg(ap, long long); + else if (( lng) && (!sgn)) d = (int64_t)va_arg(ap, unsigned long long); + else if ((!lng) && ( sgn)) d = (int64_t)va_arg(ap, int); + else d = (int64_t)va_arg(ap, unsigned int); + + ab = 0; + + switch (c) + { + case 'd': + case 'i': base = 10; break; + case 'b': base = 2; break; + case 'o': base = 8; break; + case 'x': ab = 1; + case 'X': base = 16; break; + default: break; + } + + if (!wchr) { + wchr = '0'; + wsign = -1; + } + + if (sgn) + print_signed(pc, d, base, (wsign > 0) ? width : -width, ab, wchr); + else + print_unsigned(pc, (uint64_t)d, base, (wsign > 0) ? width : -width, ab, wchr); + break; + + case 'c': + pc((char)va_arg(ap, int)); + break; + + case 's': + if (!wchr) wchr = ' '; + print_string(pc, va_arg(ap, char*), (wsign > 0) ? width : -width, wchr); + break; + + case '%': + pc('%'); + break; + + default: + pc('%'); + pc(c); + } + + st = PS_REGULAR; + break; + + /* ---------------------------------------------------------- */ + default: + st = PS_REGULAR; + } + } +} + +/* Universal printf */ +void pp(put_char_func pc, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + pv(pc, fmt, ap); + va_end(ap); +} + +/* Print to console */ +void p(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + pv(put_char, fmt, ap); + va_end(ap); +} + +/* Print to string */ +int psn(char *str, int size, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + int n = 0; + + /* Nested function. GCC specific */ + void put_char_str(char c) + { + if (n < size) { + *str++ = c; + n++; + } + } + + pv(put_char_str, fmt, ap); + + va_end(ap); + + return n; +} diff --git a/uprintf.h b/uprintf.h new file mode 100644 index 0000000..1104de5 --- /dev/null +++ b/uprintf.h @@ -0,0 +1,15 @@ +#ifndef _UPRINTF +#define _UPRINTF +#include +#include + +typedef void (*put_char_func)(char c); +extern void put_char(char c); + +void pv(put_char_func pc, const char *fmt, va_list ap); +void pp(put_char_func pc, const char *fmt, ...); + +void p(const char *fmt, ...); +int psn(char *str, int size, const char *fmt, ...); + +#endif // _UPRINTF