Skip to content

Commit

Permalink
Add basic C++ output streaming support (#2551)
Browse files Browse the repository at this point in the history
* Replace all Print::println() overloads with one templated variadic method

* Add `width` and `pad` parameters to number printing methods of Print

* Add `width` and `pad` parameters to String number constructors

* Extend `String::concat(number)` methods

* Add `padLeft`, `padRight`, `pad` methods to String

* Add `Print::print(enum class)` overload, calls `toString(E)` implementation

* Add << stream insertion operator to Print

* Starting updating samples

* Add documentation

* Fix operator <<
  • Loading branch information
mikee47 authored Sep 13, 2022
1 parent f5682d4 commit 2345c3e
Show file tree
Hide file tree
Showing 63 changed files with 692 additions and 900 deletions.
33 changes: 13 additions & 20 deletions Sming/Core/DateTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,6 @@ String DateTime::format(const char* sFormat)

String sReturn;

// Append a number to the return buffer, padding to a fixed number of digits
auto appendNumber = [&sReturn](unsigned number, unsigned digits, char padChar = '0') {
char buf[8];
ultoa_wp(number, buf, 10, digits, padChar);
sReturn.concat(buf, digits);
};

char c;
while((c = *sFormat++) != '\0') {
if(c != '%') {
Expand All @@ -298,10 +291,10 @@ String DateTime::format(const char* sFormat)
sReturn += Year;
break;
case 'y': // Year, last 2 digits as a decimal number [00..99]
appendNumber(Year % 100, 2);
sReturn.concat(Year % 100, DEC, 2);
break;
case 'C': // Year, first 2 digits as a decimal number [00..99]
appendNumber(Year / 100, 2);
sReturn.concat(Year / 100, DEC, 2);
break;
// Month (not implemented: Om)
case 'b': // Abbreviated month name, e.g. Oct (always English)
Expand All @@ -312,18 +305,18 @@ String DateTime::format(const char* sFormat)
sReturn += CStringArray(flashMonthNames)[Month];
break;
case 'm': // Month as a decimal number [01..12]
appendNumber(Month + 1, 2);
sReturn.concat(Month + 1, DEC, 2);
break;
// Week (not implemented: OU, OW, OV)
case 'U': // Week of the year as a decimal number (Sunday is the first day of the week) [00..53]
appendNumber(calcWeek(0), 2);
sReturn.concat(calcWeek(0), DEC, 2);
break;
case 'V': // ISO 8601 week number (01-53)
// !@todo Calculation of ISO 8601 week number is crude and frankly wrong but does anyone care?
appendNumber(calcWeek(1) + 1, 2);
sReturn.concat(calcWeek(1) + 1, DEC, 2);
break;
case 'W': // Week of the year as a decimal number (Monday is the first day of the week) [00..53]
appendNumber(calcWeek(1), 2);
sReturn.concat(calcWeek(1), DEC, 2);
break;
case 'x': // Locale preferred date format
sReturn += format(_F(LOCALE_DATE));
Expand All @@ -333,13 +326,13 @@ String DateTime::format(const char* sFormat)
break;
// Day of year/month (Not implemented: Od, Oe)
case 'j': // Day of the year as a decimal number [001..366]
appendNumber(DayofYear, 3);
sReturn.concat(DayofYear, DEC, 3);
break;
case 'd': // Day of the month as a decimal number [01..31]
appendNumber(Day, 2);
sReturn.concat(Day, DEC, 2);
break;
case 'e': // Day of the month as a decimal number [ 1,31]
appendNumber(Day, 2, ' ');
sReturn.concat(Day, DEC, 2, ' ');
break;
// Day of week (Not implemented: Ow, Ou)
case 'w': // Weekday as a decimal number with Sunday as 0 [0..6]
Expand All @@ -356,16 +349,16 @@ String DateTime::format(const char* sFormat)
break;
// Time (not implemented: OH, OI, OM, OS)
case 'H': // Hour as a decimal number, 24 hour clock [00..23]
appendNumber(Hour, 2);
sReturn.concat(Hour, DEC, 2);
break;
case 'I': // Hour as a decimal number, 12 hour clock [0..12]
appendNumber(Hour ? ((Hour > 12) ? Hour - 12 : Hour) : 12, 2);
sReturn.concat(Hour ? ((Hour > 12) ? Hour - 12 : Hour) : 12, DEC, 2);
break;
case 'M': // Minute as a decimal number [00..59]
appendNumber(Minute, 2);
sReturn.concat(Minute, DEC, 2);
break;
case 'S': // Second as a decimal number [00..61]
appendNumber(Second, 2);
sReturn.concat(Second, DEC, 2);
break;
// Other (not implemented: Ec, Ex, EX, z, Z)
case 'c': // Locale preferred date and time format, e.g. Tue Dec 11 08:48:32 2018
Expand Down
45 changes: 18 additions & 27 deletions Sming/Wiring/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,6 @@ size_t Print::write(const uint8_t* buffer, size_t size)
return n;
}

size_t Print::print(long num, int base)
{
if(base == 0) {
return write(num);
}

if(base == 10 && num < 0) {
return print('-') + printNumber(static_cast<unsigned long>(-num), base);
}

return printNumber(static_cast<unsigned long>(num), base);
}

// Overload (signed long long)
size_t Print::print(const long long& num, int base)
{
if(base == 10 && num < 0) {
return print('-') + printNumber(static_cast<unsigned long long>(-num), base);
}

return printNumber(static_cast<unsigned long long>(num), base);
}

size_t Print::printf(const char* fmt, ...)
{
size_t buffSize = INITIAL_PRINTF_BUFFSIZE;
Expand All @@ -83,17 +60,31 @@ size_t Print::printf(const char* fmt, ...)
}
}

size_t Print::printNumber(unsigned long num, uint8_t base)
size_t Print::printNumber(unsigned long num, uint8_t base, uint8_t width, char pad)
{
char buf[8 * sizeof(num) + 1]; // Assumes 8-bit chars plus zero byte.
ultoa_wp(num, buf, base, width, pad);
return write(buf);
}

size_t Print::printNumber(const unsigned long long& num, uint8_t base, uint8_t width, char pad)
{
char buf[8 * sizeof(num) + 1]; // Assumes 8-bit chars plus zero byte.
ulltoa_wp(num, buf, base, width, pad);
return write(buf);
}

size_t Print::printNumber(long num, uint8_t base, uint8_t width, char pad)
{
char buf[8 * sizeof(num) + 1]; // Assumes 8-bit chars plus zero byte.
ultoa(num, buf, base);
ltoa_wp(num, buf, base, width, pad);
return write(buf);
}

size_t Print::printNumber(const unsigned long long& num, uint8_t base)
size_t Print::printNumber(const long long& num, uint8_t base, uint8_t width, char pad)
{
char buf[8 * sizeof(num) + 1]; // Assumes 8-bit chars plus zero byte.
ulltoa(num, buf, base);
lltoa_wp(num, buf, base, width, pad);
return write(buf);
}

Expand Down
162 changes: 65 additions & 97 deletions Sming/Wiring/Print.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class Print
*
* @{
*/
size_t print(unsigned long num, int base = DEC)
size_t print(unsigned long num, uint8_t base = DEC)
{
if(base == 0) {
return write(num);
Expand All @@ -124,28 +124,48 @@ class Print
}
}

size_t print(const unsigned long long& num, int base = DEC)
template <typename... Args> size_t print(unsigned long num, Args... args)
{
return printNumber(num, base);
return printNumber(num, args...);
}

size_t print(long, int base = DEC);
template <typename... Args> size_t print(const unsigned long long& num, Args... args)
{
return printNumber(num, args...);
}

size_t print(const long long&, int base = DEC);
size_t print(long num, uint8_t base = DEC)
{
if(base == 0) {
return write(num);
} else {
return printNumber(num, base);
}
}

size_t print(unsigned int num, int base = DEC)
template <typename... Args> size_t print(long num, Args... args)
{
return print((unsigned long)num, base);
return printNumber(num, args...);
}

size_t print(unsigned char num, int base = DEC)
template <typename... Args> size_t print(const long long& num, Args... args)
{
return print((unsigned long)num, base);
return printNumber(num, args...);
}

size_t print(int num, int base = DEC)
template <typename... Args> size_t print(unsigned int num, Args... args)
{
return print((long)num, base);
return print((unsigned long)num, args...);
}

template <typename... Args> size_t print(unsigned char num, Args... args)
{
return print((unsigned long)num, args...);
}

template <typename... Args> size_t print(int num, Args... args)
{
return printNumber((long)num, args...);
}
/** @} */

Expand Down Expand Up @@ -177,101 +197,30 @@ class Print
return write(s.c_str(), s.length());
}

/** @brief Prints a newline to output stream
* @retval size_t Quantity of characters written to stream
*/
size_t println()
{
return print("\r\n");
}

/** @brief Prints a c-string to output stream, appending newline
* @param str c-string to print
* @retval size_t Quantity of characters written to stream
*/
size_t println(const char str[])
{
return print(str) + println();
}

/** @brief Prints a single character to output stream, appending newline
* @param c Character to print
* @retval size_t Quantity of characters written to stream
*/
size_t println(char c)
{
return print(c) + println();
}

/** @name Print an integral number to output stream, appending newline
* @param num Number to print
* @param base The base for output (Default: Decimal (base 10))
* @retval size_t Quantity of characters written to stream
*
* @{
*/
size_t println(unsigned char num, int base = DEC)
{
return print(num, base) + println();
}

size_t println(unsigned int num, int base = DEC)
{
return print(num, base) + println();
}

size_t println(unsigned long num, int base = DEC)
{
return print(num, base) + println();
}

size_t println(const unsigned long long& num, int base = DEC)
{
return print(num, base) + println();
}

size_t println(int num, int base = DEC)
{
return print(num, base) + println();
}

size_t println(long num, int base = DEC)
{
return print(num, base) + println();
}

size_t println(const long long& num, int base = DEC)
{
return print(num, base) + println();
}
/** @} */

/** @brief Print a floating-point number to output stream, appending newline
* @param num Number to print
* @param digits The decimal places to print (Default: 2, e.g. 21.35)
* @retval size_t Quantity of characters written to stream
*/
size_t println(double num, int digits = 2)
/**
* @brief enums can be printed as strings provided they have a `toString(E)` implementation.
*/
template <typename E>
typename std::enable_if<std::is_enum<E>::value && !std::is_convertible<E, int>::value, size_t>::type print(E value)
{
return print(num, digits) + println();
extern String toString(E e);
return print(toString(value));
}

/** @brief Prints a Printable object to output stream, appending newline
* @param p Object to print
/** @brief Prints a newline to output stream
* @retval size_t Quantity of characters written to stream
*/
size_t println(const Printable& p)
size_t println()
{
return print(p) + println();
return print("\r\n");
}

/** @brief Prints a String to output stream, appending newline
* @param s String to print
/** @brief Print value plus newline to output stream
* @retval size_t Quantity of characters written to stream
*/
size_t println(const String& s)
template <typename... Args> size_t println(const Args&... args)
{
return print(s) + println();
return print(args...) + println();
}

/** @brief Prints a formatted c-string to output stream
Expand All @@ -284,8 +233,10 @@ class Print

private:
int write_error = 0;
size_t printNumber(unsigned long num, uint8_t base);
size_t printNumber(const unsigned long long& num, uint8_t base);
size_t printNumber(unsigned long num, uint8_t base = DEC, uint8_t width = 0, char pad = '0');
size_t printNumber(const unsigned long long& num, uint8_t base = DEC, uint8_t width = 0, char pad = '0');
size_t printNumber(long num, uint8_t base = DEC, uint8_t width = 0, char pad = '0');
size_t printNumber(const long long& num, uint8_t base = DEC, uint8_t width = 0, char pad = '0');
size_t printFloat(double num, uint8_t digits);

protected:
Expand All @@ -295,6 +246,23 @@ class Print
}
};

template <typename T> Print& operator<<(Print& p, const T& value)
{
p.print(value);
return p;
}

// Thanks to Arduino forum user Paul V. who suggested this
// clever technique to allow for expressions like
// Serial << "Hello!" << endl;
enum EndLineCode { endl };

inline Print& operator<<(Print& p, EndLineCode)
{
p.println();
return p;
}

/** @} */

#endif // __cplusplus
Loading

0 comments on commit 2345c3e

Please sign in to comment.