diff --git a/sys/fmt/Makefile b/sys/fmt/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/fmt/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/fmt/fmt.c b/sys/fmt/fmt.c new file mode 100644 index 0000000000..1189cd41be --- /dev/null +++ b/sys/fmt/fmt.c @@ -0,0 +1,181 @@ +/** + * Copyright (C) 2015 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + * + * @ingroup sys_fmt + * @{ + * @file + * @brief String formatting library implementation + * @author Kaspar Schleiser + * @} + */ + +#include +#include +#include +#include + +/* work around broken sys/posix/unistd.h */ +ssize_t write(int fildes, const void *buf, size_t nbyte); + +#include "div.h" +#include "fmt.h" + +static const char _hex_chars[16] = "0123456789ABCDEF"; + +static inline int _is_digit(char c) +{ + return (c >= '0' && c <= '9'); +} + +size_t fmt_byte_hex(char *out, uint8_t byte) +{ + if (out) { + *out++ = _hex_chars[byte >> 4]; + *out = _hex_chars[byte & 0x0F]; + } + return 2; +} + +size_t fmt_strlen(const char *str) +{ + const char *tmp = str; + while(*tmp) { + tmp++; + } + return (tmp - str); +} + +size_t fmt_str(char *out, const char *str) +{ + int len = 0; + if (!out) { + len = fmt_strlen(str); + } else { + char c; + while ((c = *str++)) { + *out++ = c; + len++; + } + } + return len; +} + +size_t fmt_bytes_hex_reverse(char *out, const uint8_t *ptr, size_t n) +{ + size_t i = n; + while (i--) { + out += fmt_byte_hex(out, ptr[i]); + } + return (n<<1); +} + +size_t fmt_u32_hex(char *out, uint32_t val) +{ + return fmt_bytes_hex_reverse(out, (uint8_t*) &val, 4); +} + +size_t fmt_u64_hex(char *out, uint64_t val) +{ + return fmt_bytes_hex_reverse(out, (uint8_t*) &val, 8); +} + +size_t fmt_u32_dec(char *out, uint32_t val) +{ + size_t len = 1; + + /* count needed characters */ + for (uint32_t tmp = val; (tmp > 9); len++) { + tmp = div_u32_by_10(tmp); + } + + if (out) { + char *ptr = out + len; + while(val) { + *--ptr = div_u32_mod_10(val) + '0'; + val = div_u32_by_10(val); + } + } + + return len; +} + +size_t fmt_u16_dec(char *out, uint16_t val) +{ + return fmt_u32_dec(out, val); +} + +size_t fmt_s32_dec(char *out, int32_t val) +{ + int negative = (val < 0); + if (negative) { + if (out) { + *out++ = '-'; + } + val *= -1; + } + return fmt_u32_dec(out, val) + negative; +} + +uint32_t scn_u32_dec(const char *str, size_t n) +{ + uint32_t res = 0; + while(n--) { + char c = *str++; + if (!_is_digit(c)) { + break; + } + else { + res *= 10; + res += (c - '0'); + } + } + return res; +} + +void print(const char *s, size_t n) +{ + while (n > 0) { + ssize_t written = write(STDOUT_FILENO, s, n); + if (written < 0) { + break; + } + n -= written; + s += written; + } +} + +void print_u32_dec(uint32_t val) +{ + char buf[10]; + size_t len = fmt_u32_dec(buf, val); + print(buf, len); +} + +void print_s32_dec(int32_t val) +{ + char buf[11]; + size_t len = fmt_s32_dec(buf, val); + print(buf, len); +} + +void print_u32_hex(uint32_t val) +{ + char buf[8]; + fmt_u32_hex(buf, val); + print(buf, sizeof(buf)); +} + +void print_u64_hex(uint64_t val) +{ + print_u32_hex(val>>32); + print_u32_hex(val); +} + +void print_str(const char* str) +{ + print(str, fmt_strlen(str)); +} diff --git a/sys/include/fmt.h b/sys/include/fmt.h new file mode 100644 index 0000000000..b6ebb3828a --- /dev/null +++ b/sys/include/fmt.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2015 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_fmt string formatting + * @ingroup sys + * @brief Provides simple string formatting functions + * + * @{ + * @file + * @brief string formatting API + * @author Kaspar Schleiser + */ + +#ifndef FMT_H_ +#define FMT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Format a byte value as hex + * + * E.g., converts byte value 0 to the string 00, 255 to the string FF. + * + * Will write two bytes to @p out. + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] byte Byte value to convert + * + * @return 2 + */ +size_t fmt_byte_hex(char *out, uint8_t byte); + +/** + * @brief Formats a sequence of bytes as hex bytes, starting with the last byte + * + * Will write 2*n bytes to @p out. + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] ptr Pointer to bytes to convert + * @param[in] n Number of bytes to convert + * + * @return 2*n + */ +size_t fmt_bytes_hex_reverse(char *out, const uint8_t *ptr, size_t n); + +/** + * @brief Convert a uint32 value to hex string. + * + * Will write 8 bytes to @p out. + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] val Value to convert + * + * @return 8 + */ +size_t fmt_u32_hex(char *out, uint32_t val); + +/** + * @brief Convert a uint64 value to hex string. + * + * Will write 16 bytes to @p out. + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] val Value to convert + * + * @return 16 + */ +size_t fmt_u64_hex(char *out, uint64_t val); + +/** + * @brief Convert a uint32 value to decimal string. + * + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] val Value to convert + * + * @return nr of digits written to (or needed in) @p out + */ +size_t fmt_u32_dec(char *out, uint32_t val); + +/** + * @brief Convert a uint16 value to decimal string. + * + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] val Value to convert + * + * @return nr of digits written to (or needed in) @p out + */ +size_t fmt_u16_dec(char *out, uint16_t val); + +/** + * @brief Convert a int32 value to decimal string. + * + * Will add a leading "-" if @p val is negative. + * + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] val Value to convert + * + * @return nr of characters written to (or needed in) @p out + */ +size_t fmt_s32_dec(char *out, int32_t val); + +/** + * @brief Count characters until '\0' (exclusive) in @p str + * + * @param[in] str Pointer to string + * + * @return nr of characters in string @p str points to + */ +size_t fmt_strlen(const char *str); + +/** + * @brief Copy null-terminated string (excluding terminating \0) + * + * If @p out is NULL, will only return the number of bytes that would have + * been written. + * + * @param[out] out Pointer to output buffer, or NULL + * @param[in] str Pointer to null-terminated source string + * + * @return nr of characters written to (or needed in) @p out + */ +size_t fmt_str(char *out, const char *str); + +/** + * @brief Convert digits to uint32 + * + * Will convert up to @p n digits. Stops at any non-digit or '\0' character. + * + * @param[out] str Pointer to string to read from + * @param[in] n Maximum nr of characters to consider + * + * @return nr of digits read + */ +uint32_t scn_u32_dec(const char *str, size_t n); + +/** + * @brief Print string to stdout + * + * Writes @p n bytes from @p s to STDOUT_FILENO. + * + * @param[out] s Pointer to string to print + * @param[in] n Number of bytes to print + */ +void print(const char* s, size_t n); + +/** + * @brief Print uint32 value to stdout + * + * @param[in] val Value to print + */ +void print_u32_dec(uint32_t val); + +/** + * @brief Print int32 value to stdout + * + * @param[in] val Value to print + */ +void print_s32_dec(int32_t val); + +/** + * @brief Print uint32 value as hex to stdout + * + * @param[in] val Value to print + */ +void print_u32_hex(uint32_t val); + +/** + * @brief Print uint64 value as hex to stdout + * + * @param[in] val Value to print + */ +void print_u64_hex(uint64_t val); + +/** + * @brief Print null-terminated string to stdout + * + * @param[in] str Pointer to string to print + */ +void print_str(const char* str); + +#ifdef __cplusplus +} +#endif + +/** @} */ +#endif /* FMT_H_ */ diff --git a/tests/unittests/tests-fmt/Makefile b/tests/unittests/tests-fmt/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/tests/unittests/tests-fmt/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-fmt/Makefile.include b/tests/unittests/tests-fmt/Makefile.include new file mode 100644 index 0000000000..8e78f49e35 --- /dev/null +++ b/tests/unittests/tests-fmt/Makefile.include @@ -0,0 +1 @@ +USEMODULE += fmt diff --git a/tests/unittests/tests-fmt/tests-fmt.c b/tests/unittests/tests-fmt/tests-fmt.c new file mode 100644 index 0000000000..6c88b114a7 --- /dev/null +++ b/tests/unittests/tests-fmt/tests-fmt.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2015 Cenk Gündoğan + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + */ +#include +#include + +#include "embUnit/embUnit.h" + +#include "fmt.h" +#include "tests-fmt.h" + +static void test_fmt_byte_hex(void) +{ + char out[3] = "--"; + + TEST_ASSERT_EQUAL_INT(2, fmt_byte_hex(out, 0)); + TEST_ASSERT_EQUAL_STRING("00", (char *) out); + + TEST_ASSERT_EQUAL_INT(2, fmt_byte_hex(out, 128)); + TEST_ASSERT_EQUAL_STRING("80", (char *) out); + + TEST_ASSERT_EQUAL_INT(2, fmt_byte_hex(out, 255)); + TEST_ASSERT_EQUAL_STRING("FF", (char *) out); +} + +static void test_fmt_bytes_hex_reverse(void) +{ + char out[10]; + uint8_t val[4] = { 9, 8, 7, 6 }; + uint8_t bytes = 0; + + bytes = fmt_bytes_hex_reverse(out, val, 1); + out[bytes] = '\0'; + TEST_ASSERT_EQUAL_INT(2, bytes); + TEST_ASSERT_EQUAL_STRING("09", (char *) out); + + bytes = fmt_bytes_hex_reverse(out, val, 2); + out[bytes] = '\0'; + TEST_ASSERT_EQUAL_INT(4, bytes); + TEST_ASSERT_EQUAL_STRING("0809", (char *) out); + + bytes = fmt_bytes_hex_reverse(out, val, 3); + out[bytes] = '\0'; + TEST_ASSERT_EQUAL_INT(6, bytes); + TEST_ASSERT_EQUAL_STRING("070809", (char *) out); + + bytes = fmt_bytes_hex_reverse(out, val, 4); + out[bytes] = '\0'; + TEST_ASSERT_EQUAL_INT(8, bytes); + TEST_ASSERT_EQUAL_STRING("06070809", (char *) out); +} + +static void test_fmt_u32_hex(void) +{ + char out[9] = "--------"; + uint32_t val = 3735928559; + + TEST_ASSERT_EQUAL_INT(8, fmt_u32_hex(out, val)); + TEST_ASSERT_EQUAL_STRING("DEADBEEF", (char *) out); +} + +static void test_fmt_u64_hex(void) +{ + char out[17] = "----------------"; + uint64_t val = 1002843385516306400; + + TEST_ASSERT_EQUAL_INT(16, fmt_u64_hex(out, val)); + TEST_ASSERT_EQUAL_STRING("0DEAD0BEEF0CAFE0", (char *) out); +} + +static void test_fmt_u32_dec(void) +{ + char out[9] = "--------"; + uint32_t val = 12345678; + uint8_t chars = 0; + + chars = fmt_u32_dec(out, val); + TEST_ASSERT_EQUAL_INT(8, chars); + out[chars] = '\0'; + TEST_ASSERT_EQUAL_STRING("12345678", (char *) out); +} + +static void test_fmt_u16_dec(void) +{ + char out[5] = "----"; + uint16_t val = 6556; + uint8_t chars = 0; + + chars = fmt_u16_dec(out, val); + TEST_ASSERT_EQUAL_INT(4, chars); + out[chars] = '\0'; + TEST_ASSERT_EQUAL_STRING("6556", (char *) out); +} + +static void test_fmt_s32_dec(void) +{ + char out[8] = "--------"; + int32_t val = 9876; + uint8_t chars = 0; + + chars = fmt_s32_dec(out, val); + TEST_ASSERT_EQUAL_INT(4, chars); + out[chars] = '\0'; + TEST_ASSERT_EQUAL_STRING("9876", (char *) out); + + val = -9876; + chars = fmt_s32_dec(out, val); + TEST_ASSERT_EQUAL_INT(5, chars); + out[chars] = '\0'; + TEST_ASSERT_EQUAL_STRING("-9876", (char *) out); +} + +static void test_fmt_strlen(void) +{ + const char *empty_str = ""; + const char *short_str = "short"; + const char *long_str = "this is a long string"; + + TEST_ASSERT_EQUAL_INT(0, fmt_strlen(empty_str)); + TEST_ASSERT_EQUAL_INT(5, fmt_strlen(short_str)); + TEST_ASSERT_EQUAL_INT(21, fmt_strlen(long_str)); +} + +static void test_fmt_str(void) +{ + const char *string1 = "string1"; + char string2[] = "StRiNg2"; + + TEST_ASSERT_EQUAL_INT(fmt_strlen(string1), fmt_str(&string2[0], string1)); + TEST_ASSERT_EQUAL_STRING(string1, &string2[0]); +} + +static void test_scn_u32_dec(void) +{ + const char *string1 = "123456789"; + uint32_t val1 = 123456789; + uint32_t val2 = 12345; + + TEST_ASSERT_EQUAL_INT(val1, scn_u32_dec(string1, 9)); + TEST_ASSERT_EQUAL_INT(val2, scn_u32_dec(string1, 5)); +} + +Test *tests_fmt_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_fmt_byte_hex), + new_TestFixture(test_fmt_bytes_hex_reverse), + new_TestFixture(test_fmt_u32_hex), + new_TestFixture(test_fmt_u64_hex), + new_TestFixture(test_fmt_u32_dec), + new_TestFixture(test_fmt_u16_dec), + new_TestFixture(test_fmt_s32_dec), + new_TestFixture(test_fmt_strlen), + new_TestFixture(test_fmt_str), + new_TestFixture(test_scn_u32_dec), + }; + + EMB_UNIT_TESTCALLER(fmt_tests, NULL, NULL, fixtures); + + return (Test *)&fmt_tests; +} + +void tests_fmt(void) +{ + TESTS_RUN(tests_fmt_tests()); +} +/** @} */ diff --git a/tests/unittests/tests-fmt/tests-fmt.h b/tests/unittests/tests-fmt/tests-fmt.h new file mode 100644 index 0000000000..ddfa917566 --- /dev/null +++ b/tests/unittests/tests-fmt/tests-fmt.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Cenk Gündoğan + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the ``fmt`` module + * + * @author Cenk Gündoğan + */ +#ifndef TESTS_FMT_H_ +#define TESTS_FMT_H_ + +#include "embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_fmt(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_FMT_H_ */ +/** @} */