From c3850911ceaa74d2b4d7ff9622d943766b1ebb38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiesner=20Andr=C3=A1s?= Date: Fri, 23 Sep 2022 20:58:56 +0200 Subject: [PATCH] initial, working version --- embformat.c | 594 ++++++++++++++++++++++++++++++++++++++++++++++++++++ embformat.h | 8 + 2 files changed, 602 insertions(+) create mode 100644 embformat.c create mode 100644 embformat.h diff --git a/embformat.c b/embformat.c new file mode 100644 index 0000000..b30f3a7 --- /dev/null +++ b/embformat.c @@ -0,0 +1,594 @@ +/* (C) Wiesner András, 2022 */ + +#include "embformat.h" + +#include +#include +#include + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +#define FORMAT_DELIMITER ('%') + +// format length ((nothing),l) +typedef enum { + LEN_NORMAL, + LEN_LONG +} FmtLength; + +// format flags ((nothing),0) +typedef enum { + FLAG_NO = 0, + FLAG_LEADING_ZEROS = 1, + FLAG_LEADING_SPACES = 2, + FLAG_PREPEND_PLUS_SIGN = 4 +} FmtFlags; + +// format type +typedef enum { + UNKNOWN = -1, + LITERAL_PERCENT, + SIGNED_INTEGER, + UNSIGNED_INTEGER, + DOUBLE, + DOUBLE_EXPONENTIAL, + UNSIGNED_HEXADECIMAL_INT, + STRING, + CHARACTER +} FmtType; + +struct _FmtWord; +typedef int (*printfn)(va_list *va, struct _FmtWord *fmt, char *outbuf, size_t free_space); + +// pair of type and designator character +typedef struct { + char designator; + FmtType type; + printfn fn; +} FmtTypeDesignatorPair; + +// structure holding format word properties +typedef struct _FmtWord { + FmtFlags flags; + int width; + int precision; + FmtLength length; + FmtType type; + FmtTypeDesignatorPair *pTypeDes; +} FmtWord; + +// --------------------------------------------- + +// copy a maximum of n characters AND insert '\0' into dst as the n+1-th character +static int string_copy(char *dst, const char *src, size_t n) { + size_t i; + int cnt = 0; + for (i = 0; i < n && src[i] != '\0'; i++) { + dst[i] = src[i]; + cnt++; + } + dst[i] = '\0'; + return cnt; +} + +// get the length of a string +static size_t string_length(char *str) { + size_t len = 0; + while (*str != '\0') { + len++; + str++; + } + return len; +} + +// calculate power +static unsigned long int power(unsigned long int a, unsigned long int n) { + if (n == 0) { + return 1; + } + unsigned long int a_orig = a; + for (size_t i = 0; i < n - 1; i++) { + a *= a_orig; + } + return a; +} + +// round to closest base^1 value +static unsigned long int round_to_base(unsigned long int a, unsigned long int base) { + unsigned long int mod = a % base; + if (2 * mod < base) { // avoid floating-point arithemtics! + return a - mod; + } else { + return a + (base - mod); + } +} + +// --------------------------------------------- + +// insert leading characters +// str: inout string +// n_str: length of the input string (NOT buflen, stringlen) +// c: character to be used for filling +// n_insert: number of character to insert +static void insert_leading_characters(char *str, size_t n_str, char c, size_t n_insert) { + size_t len = string_length(str); + for (size_t i = 0; i < len; i++) { + str[len - 1 + n_insert - i] = str[len - i - 1]; + } + for (size_t i = 0; i < n_insert; i++) { + str[i] = c; + } + str[len + n_insert] = '\0'; +} + +// get exponent in the scientific form of the input number (i. e. get the highest decimal place) +// l: input number +// base: base +// gntpd: greatest non-zero ten-power divisor +// return: exponent, if l != 0, -1 if l == 0 +static int scientific_form_exponent(unsigned long int l, unsigned int base, unsigned long int *gntpd) { + int e = -1; + *gntpd = 1; + while (l != 0) { + l /= base; + *gntpd *= base; + e++; + } + if (*gntpd > 1) { + (*gntpd) /= base; + } + return e; +} + +static int pfn_literal_percent(va_list *va, FmtWord *fmt, char *outbuf, size_t free_space) { + if (free_space >= 1) { + outbuf[0] = '%'; + outbuf[1] = '\0'; + } + return 1; +} + +#define INT_PRINT_OUTBUF_SIZE (47) + +static char sNumChars[] = "0123456789abcdef"; + +static int print_number(unsigned long int u, bool negative, FmtWord *fmt, char *outbuf, size_t free_space) { + // output buffer fitting the full long int range + char outstrbuf[INT_PRINT_OUTBUF_SIZE + 1]; + outstrbuf[0] = '\0'; + char *outstr = outstrbuf; + + // separate sign, process only non-negative numbers + bool sign_prepended = false; + if (negative) { + *outstr = '-'; + outstr++; + sign_prepended = true; + } else if (fmt->flags & FLAG_PREPEND_PLUS_SIGN) { + *outstr = '+'; + outstr++; + sign_prepended = true; + } + + // base of the numeral system + int base = 10; // for safety... + if (fmt->type == UNSIGNED_INTEGER || fmt->type == SIGNED_INTEGER) { + base = 10; + } else if (fmt->type == UNSIGNED_HEXADECIMAL_INT) { + base = 16; + } + + // get number of digits the number consists of + unsigned long int gntpd; + int digits = scientific_form_exponent(u, base, &gntpd) + 1; + + // convert int to string + while (gntpd > 0) { + unsigned long int place_value = (u / gntpd); // integer division! + u -= place_value * gntpd; // substract value corresponding to the place + *outstr = sNumChars[place_value]; // print place value string + + gntpd /= base; // divide by the base (advance to lower place) + outstr++; // advance in the string + *outstr = '\0'; // move the terminating zero + } + + // pad with zeros if requested + if (fmt->width != -1 && (fmt->flags & FLAG_LEADING_ZEROS || fmt->flags & FLAG_LEADING_SPACES)) { + int pad_n = MAX(fmt->width - digits - sign_prepended, digits); + if (pad_n > 0) { + // pad differently based on padding character + if (fmt->flags & FLAG_LEADING_ZEROS) { // -00000nnn + insert_leading_characters(outstrbuf + sign_prepended, INT_PRINT_OUTBUF_SIZE - sign_prepended, '0', pad_n); // insert leading characters + } else if (fmt->flags & FLAG_LEADING_SPACES) { // _____-nnn + insert_leading_characters(outstrbuf, INT_PRINT_OUTBUF_SIZE, ' ', pad_n); // insert leading characters + } + outstr += pad_n; // advance pointer to the end of the string + } + } + + // copy string to output + int copy_len = string_copy(outbuf, outstrbuf, free_space); + return copy_len; +} + +static int pfn_integer(va_list *va, FmtWord *fmt, char *outbuf, size_t free_space) { + unsigned long int u; + bool negative = false; + if (fmt->type == SIGNED_INTEGER) { // for signed integers + long int si; + if (fmt->length == LEN_NORMAL) { // ...without length specifiers + si = va_arg(*va, int); + } else { // ...with length specifiers + si = va_arg(*va, long int); + } + + // absolute value + if (si < 0) { + negative = true; + u = -si; + } + } else if (fmt->type == UNSIGNED_INTEGER || fmt->type == UNSIGNED_HEXADECIMAL_INT) { // for UNsigned integers + if (fmt->length == LEN_NORMAL) { // ...without length specifiers + u = va_arg(*va, unsigned int); + } else { // ...with length specifiers + u = va_arg(*va, unsigned long int); + } + } + + // print number + int copy_len = print_number(u, negative, fmt, outbuf, free_space); + return copy_len; +} + +#define DECIMAL_POINT ('.') + +static int pfn_double(va_list *va, FmtWord *fmt, char *outbuf, size_t free_space) { + // get passed double variable + double d = va_arg(*va, double); + bool negative = d < 0; + if (negative) { + d *= -1; + } + + // normalize if exponential form is required + int exponent = 0; + if (fmt->type == DOUBLE_EXPONENTIAL) { + double q = d < 1.0 ? 10.0 : 0.1; // quotient + int d_exp = d < 1.0 ? -1 : 1; // shift per iteration in exponent + while (!(d >= 1.0 && d < 10.0)) { + d *= q; + exponent += d_exp; + } + } + + + // formatting of the integer part + FmtWord int_fmt = { + .flags = fmt->flags, + .type = UNSIGNED_INTEGER, + .width = fmt->width + }; + + // separate into integer and fractional part + unsigned long int int_part = (unsigned long int) d; // integer part + int copy_len = print_number(int_part, negative, &int_fmt, outbuf, free_space); + free_space -= copy_len; + outbuf += copy_len; + + int sum_copy_len = copy_len; // summed copy length + + // fractional part + if (fmt->precision > 0) { + // place decimal point + *outbuf = DECIMAL_POINT; + outbuf++; + free_space--; + sum_copy_len++; + + *outbuf = '\0'; + + // prepare format for + FmtWord frac_fmt = { + .flags = FLAG_NO, + .width = -1, + .type = UNSIGNED_INTEGER + }; + + // extract fractional part as integer + double d_frac = d - (double) int_part; + d_frac *= (double) power(10, fmt->precision); + unsigned long int frac_part = (unsigned long int) d_frac; + frac_part = round_to_base(frac_part, 10); + + // print fractional part + copy_len = print_number(frac_part, false, &frac_fmt, outbuf, free_space); + free_space -= copy_len; + outbuf += copy_len; + sum_copy_len += copy_len; + } + + // print exponential if format requires + if (fmt->type == DOUBLE_EXPONENTIAL) { + // print 'e' + *outbuf = 'e'; + outbuf++; + *outbuf = '\0'; + free_space--; + sum_copy_len++; + + // print value of exponent + FmtWord exponent_fmt = { + .flags = FLAG_PREPEND_PLUS_SIGN | FLAG_LEADING_ZEROS, + .width = 2, + .type = UNSIGNED_INTEGER + }; + + unsigned int exponent_abs = exponent > 0 ? exponent : -exponent; + + copy_len = print_number(exponent_abs, exponent < 0, &exponent_fmt, outbuf, free_space); + free_space -= copy_len; + outbuf += copy_len; + sum_copy_len += copy_len; + } + + return sum_copy_len; +} + +// print character +static int pfn_char(va_list *va, FmtWord *fmt, char *outbuf, size_t free_space) { + int c = va_arg(*va, int); + if (free_space >= 1) { + outbuf[0] = (char) c; + outbuf[1] = '\0'; + } + return 1; +} + +// print string +static int pfn_string(va_list *va, FmtWord *fmt, char *outbuf, size_t free_space) { + char *str = va_arg(*va, char *); + return string_copy(outbuf, str, free_space); +} + +// format swting assignment table +static FmtTypeDesignatorPair sTypeDesAssignment[] = { + {'%', LITERAL_PERCENT, pfn_literal_percent}, + {'d', SIGNED_INTEGER, pfn_integer}, + {'i', SIGNED_INTEGER, pfn_integer}, + {'u', UNSIGNED_INTEGER, pfn_integer}, + {'f', DOUBLE, pfn_double}, + {'e', DOUBLE_EXPONENTIAL, pfn_double}, + {'x', UNSIGNED_HEXADECIMAL_INT, pfn_integer}, + {'s', STRING, pfn_string}, + {'c', CHARACTER, pfn_char}, + {'\0', UNKNOWN} // termination +}; + +// ------------------------------------------------------------ + +// try to fetch number from the start of the string +// return: number found OR -1 on failure +static char *fetch_number(char *str, int *num) { + char *start = str; + char *end = start; + *num = 0; + while ('0' <= *end && '9' >= *end) { + *num *= 10; + *num += *end - '0'; + end++; + } + + if (end == start) { + *num = -1; + } + + return end; +} + +// get number of arguments from the format string +static int get_number_of_arguments_by_format_string(char *format_str) { + char *iter = format_str; + int cnt = 0; + while (*iter != '\0') { + if (*iter == FORMAT_DELIMITER) { + if (*(iter + 1) == '%') { // filter out '%%' literals + iter++; + } else { + cnt++; + } + } + iter++; + } + return cnt; +} + +// seek for format delimiter ('%') +static char *seek_delimiter(char *str) { + // iterate over characters until either '%' is found or end of string is reached + while (*(str) != '\0' && *(str) != FORMAT_DELIMITER) { + str++; + } + + // return based on what we found + if (*str == FORMAT_DELIMITER) { + return str; + } else { + return NULL; + } +} + +// get the the formatting word +// str: input string +// begin: pointer to pointer to the begin of the format word +// length: length of the format word (number of bytes to be copied after) +// return: pointer to unprocessed input string +static char *locate_format_word(char *str, char **begin, size_t *length) { + char *delim_pos = seek_delimiter(str); // seek for format specifier begin + if (delim_pos == NULL) { // if not found... + return NULL; // ...then return + } + + // here we have pointer to the '%' character of a format "word" + + // a valid format string has a length at least of 1 byte (i. e. something must follow the '%') + char *word_start = delim_pos + 1; + *begin = word_start; + + // search the ending of the format word, which can be either a whitespace or a new '%' character (or the '\0') + char *word_iter = word_start; + while ((*word_iter > ' ' || word_iter == word_start) && (*word_iter != FORMAT_DELIMITER || word_iter == word_start) && *word_iter != '\0') { + word_iter++; + } + + // now, word_iter points to the first character following the previously found delimiter now belonging to the format word + char *unproc_text = word_iter; + + // calculate the length of the word + *length = word_iter - word_start; + + return unproc_text; +} + +// fetch format word +// str: input text +// word: output format word (if return value != NULL) +// preceding_text: text before format word +// maxlen: maximum STRING LENGTH of the output +// return: pointer to unprocessed text OR NULL on failure +static char *fetch_format_word(char *str, char *word, char **word_begin, size_t maxlen) { + // locate word + size_t len; + char *unproc_text = locate_format_word(str, word_begin, &len); + + // copy + string_copy(word, *word_begin, MIN(maxlen, len)); + + return unproc_text; +} + +// fetch type from designator character +static FmtTypeDesignatorPair *get_type_by_designator(char designator) { + FmtTypeDesignatorPair *iter = sTypeDesAssignment; + while (iter->designator != '\0' && iter->designator != designator) { + iter++; + } + return iter; +} + +#define DEFAULT_PRINT_PRECISION (6) + +static char sFlags[] = " 0+"; + +// decide whether the passed character is a flag indicator +static bool is_flag(char c) { + char *iter = sFlags; + while (*iter != '\0') { + if (*iter == c) { + return true; + } + iter++; + } + return false; +} + +// process format word into format structure +// str: input format string to process +// word: output format specifier +// rewind: number of characters to rewind input (some characters may be considered wrongly as being part of the format word) +// return: 0 on success, -1 on failure +static int process_format_word(char *str, FmtWord *word, int * rewind) { + // 1.: look for flags + word->flags = FLAG_NO; + while (is_flag(*str)) { + switch (*str) { + case '0': + word->flags |= FLAG_LEADING_ZEROS; + break; + case ' ': + word->flags |= FLAG_LEADING_SPACES; + break; + case '+': + word->flags |= FLAG_PREPEND_PLUS_SIGN; + break; + } + } + + // 2.: look for width + str = fetch_number(str, &(word->width)); + + // 3.: look for precision + word->precision = DEFAULT_PRINT_PRECISION; // default double precision + if (*str == '.') { + str++; + str = fetch_number(str, &(word->precision)); + } + + // 4.: look for length + word->length = LEN_NORMAL; + if (*str == 'l') { + word->length = LEN_LONG; + str++; + } + + // 5.: get type from the designator + word->pTypeDes = get_type_by_designator(*str); + word->type = word->pTypeDes->type; + *rewind = string_length(str + 1); + + if (word->type == UNKNOWN) { + return -1; + } else { + return 0; + } +} + +#define MAX_FORMAT_WORD_LEN (15) + +unsigned long int embfmt(char *str, unsigned long int len, char *format, ...) { + // get number of expected arguments and initiate va_list usage + int argc = get_number_of_arguments_by_format_string(format); + va_list args; + va_start(args, argc); + + // process format string + long int free_space = len; + char *unproc_text = format; + char *unproc_text_next = NULL; + char word_str[MAX_FORMAT_WORD_LEN + 1]; + char *word_begin; + size_t sum_copy_len = 0; + while ((*unproc_text) && (unproc_text_next = fetch_format_word(unproc_text, word_str, &word_begin, MAX_FORMAT_WORD_LEN)) && free_space > 0) { + // print preceding text + word_begin--; + int copy_len = MIN((word_begin - unproc_text), free_space); + string_copy(str, unproc_text, copy_len); + free_space -= copy_len; + str += copy_len; + sum_copy_len += copy_len; + + // print data + FmtWord word; + int rewind; + process_format_word(word_str, &word, &rewind); + if (word.type != UNKNOWN) { + int copy_len = word.pTypeDes->fn(&args, &word, str, free_space); // variable with the same name! + free_space -= copy_len; + str += copy_len; + sum_copy_len += copy_len; + } + + // advance pointer in unprocessed text + unproc_text = unproc_text_next - rewind; + } + + // also copy last part of the string not containing any formatting sequences + if (unproc_text != NULL) { + sum_copy_len += string_copy(str, unproc_text, free_space); + } + + va_end(args); + + return sum_copy_len; +} \ No newline at end of file diff --git a/embformat.h b/embformat.h new file mode 100644 index 0000000..984f84b --- /dev/null +++ b/embformat.h @@ -0,0 +1,8 @@ +/* (C) Wiesner András, 2022 */ + +#ifndef EMBFORMAT_EMBFORMAT_H +#define EMBFORMAT_EMBFORMAT_H + +unsigned long int embfmt(char *str, unsigned long int len, char *format, ...); + +#endif //EMBFORMAT_EMBFORMAT_H