Skip to content

Instantly share code, notes, and snippets.

@matthijskooijman
Created June 16, 2019 14:37
Show Gist options
  • Save matthijskooijman/cbac69e8db907ab0f849775b5a1cac44 to your computer and use it in GitHub Desktop.
Save matthijskooijman/cbac69e8db907ab0f849775b5a1cac44 to your computer and use it in GitHub Desktop.
#include <Arduino.h>
using namespace Formatters;
template <typename T>
inline void debug_type(const T&) __attribute__((deprecated));
template <typename T>
inline void debug_type() __attribute__((deprecated));
struct EvenOddFormatter : Print::Formatter {
int printTo(Print *p, long x) const {
return p->print(x % 2 ? "odd" : "even");
}
};
struct MyPrintable : Printable {
const char *p;
MyPrintable(const char *p) : p(p) {}
size_t printTo(Print &P) const {int i; for (i=0; p[i]; i++) P.write(p[i]); return i;}
};
struct WrapString : Print::Formatter {
struct FormatterOption : Print::FormatterOption { };
struct KeepIndent : FormatterOption { };
size_t line_length;
bool keep_indent = false;
WrapString(size_t line_length = 80) : line_length(line_length) { }
WrapString(size_t line_length, bool keep_indent) : line_length(line_length), keep_indent(keep_indent) { }
WrapString applyOption(KeepIndent) const {
return {this->line_length, true};
}
size_t printTo(Print *p, const char *s) const {
p->println("Wrapping");
p->println(this->line_length);
p->println(this->keep_indent);
return p->print(s);
}
};
#if 0
inline WrapString DefaultFormatterFor(Any /* value */, WrapString::KeepIndent) {
return {};
}
#endif
template <typename TValue>
inline WrapString DefaultFormatterFor(TValue, WrapString::FormatterOption) {
return {};
}
/*************************************
* IPAddress formatter
*************************************/
struct IPFormatter : Print::Formatter {
struct FormatterOption : Print::FormatterOption { };
struct FixedWidth : FormatterOption { };
// TODO
size_t line_length;
bool fixed_width = false;
WrapString(size_t line_length = 80) : line_length(line_length) { }
WrapString(size_t line_length, bool keep_indent) : line_length(line_length), keep_indent(keep_indent) { }
WrapString applyOption(KeepIndent) const {
return {this->line_length, true};
}
size_t printTo(Print *p, const char *s) const {
p->println("Wrapping");
p->println(this->line_length);
p->println(this->keep_indent);
return p->print(s);
}
};
#if 0
inline WrapString DefaultFormatterFor(Any /* value */, WrapString::KeepIndent) {
return {};
}
#endif
template <typename TValue>
inline WrapString DefaultFormatterFor(TValue, WrapString::FormatterOption) {
return {};
}
/* Hijack handling of the FORMAT_BASE option for the default formatter,
* replacing the actual option with binary always. For testing
* applyOption overloading.
DefaultFormatter applyOption(const DefaultFormatter& f, const DefaultFormatter::FormatOptionBase&) {
return f.applyOption(FORMAT_BASE(2));
}
*/
/* Example of using applyOption to add an option to an existing
* formatter. This adds a "FORMAT_HEX_BYTE2" option by defining a custom
* option type, selecting DefaultFormatter as the default formatter for
* this option, and overriding applyOption to set base 16 and min width
* 2 when the option is used. In this case, using an OptionList (like
* FORMAT_HEX_BYTE below) is of course simpler, but overriding
* applyOption also allows for more complex behaviour. Note that this
* does not really add anything to the formatter, it just sets existing
* options for the formatter in a different way. To really add new
* options, you must wrap or replace the formatter itself (by making
* applyOption return a custom formatter type, different to the one it
* was passed).
*/
struct FormatOptionHexByte : public Print::FormatterOption { };
template <typename T>
inline DefaultFormatter DefaultFormatterFor(T, FormatOptionHexByte) {
return {};
}
DefaultFormatter applyOption(const DefaultFormatter& f, const FormatOptionHexByte&) {
return f.applyOption(FORMAT_BASE(16)).applyOption(FORMAT_MIN_WIDTH(2));
}
constexpr FormatOptionHexByte FORMAT_HEX_BYTE2;
/*
* Use an option list to provide a shortcut for zero-padded hex bytes.
*/
auto FORMAT_HEX_BYTE = FORMAT_BASE(16) + FORMAT_MIN_WIDTH(2);
// TODO: When specifying default formatters, you can use templates to
// get wildcard overloads (e.g. specify a default formatter for all
// options passed to a specific type, or a default formatter for any
// type combined with a specific formatter).
//
// If both overloads exist, this might cause ambiguity. e.g. when you
// have a (FooT value, *) overload and a (*, BarOption) overload, doing
// print(FooT(), BarOption()) is ambiguous. Usually, this will also be
// invalid (the formatter belonging to BarOption will likely not know
// how to print a FooT anyway). There might be cases where it is not,
// but it is not clear which of the two to favor in this case. If
// needed, some kind of priority tag argument could later be added, with
// a fallback to the regular tag-less version).
//
// Note that the current approach of using a formatter-specific
// superclass (e.g. DefaultFormatter::FormatterOption) between the
// actual option class and Print::FormatterOption already makes
// overloads that use it (and thus need parent class conversion) less
// specific than templated overloads (which match their arguments
// exactly).
//
/*
// TODO: Using a wildcard for options using a superclass prevents
// ambiguity, but then prefers more specific matches (e.g. specific
// option types) over this general types, before even looking at
// template specificity, effectively *not* preferring this wildcard
// (while we might want to prefer this wildcard, over other option-only
// wildcards?). Revert back to an Any and add explicit prioritization?
template <typename TOption>
inline WrapString DefaultFormatterFor(const char *, TOption) {
return {};
}
*/
void setup() {
#define FSH F("Flash string")
String S("String");
const char * charp = "Long char array, consisting of multiple words and very suitable for word wrapping.";
char arr[] = "char array";
char c = 'c';
unsigned char uc = 42;
int i = 42;
unsigned int ui = 0x42;
long L = 042;
unsigned long uL = 0b00101010;
double d = 42.0;
MyPrintable P("Printable");
Serial.begin(115200);
Serial.println("begin");
//Serial.println(1, EvenOddFormatter());
Serial.println(FSH); // FlashStringHelper
Serial.println(S); // String
Serial.println(S+S); // StringSumHelper
Serial.println(arr); // const char *
Serial.println(c); // char
Serial.println(uc ); // unsigned char }
Serial.println(i, DEC); // int }
Serial.println(ui, HEX); // unsigned int } numbers
Serial.println(L, OCT); // long }
Serial.println(uL, BIN); // unsigned long }
Serial.println(d); // double
Serial.println(P); // Printable
Serial.println(i, HEX);
Serial.println(i, 16); // Backward compatibility, prints in hex
Serial.println(i, 16, i, 16); // No backward compatility, prints four numbers
Serial.println(d, FORMAT_PRECISION(4));
Serial.println(d, 4); // Backward compatibility, prints in four digits
Serial.println(1, "foo");
Serial.println(1, "foo", 3, "bar", 100, 16, "baz", 101, EvenOddFormatter());
//Serial.println(HEX, FORMAT_MIN_WIDTH(2)); // Error
Serial.println(i, HEX, FORMAT_MIN_WIDTH(2));
Serial.println(i, FORMAT_BASE(8), FORMAT_MIN_WIDTH(2));
Serial.println(i, FORMAT_BASE(8), FORMAT_MIN_WIDTH(2), "foo", 32, FORMAT_BASE(16));
// This should fail, preferably at compiletime:
// Serial.println(s, FORMAT_BASE(8));
//constexpr auto MY_FORMAT = DefaultFormatter() + HEX + FORMAT_MIN_WIDTH(1);
constexpr auto MY_FORMAT = DefaultFormatter().applyOption(HEX).applyOption(FORMAT_MIN_WIDTH(1));
Serial.println(33, MY_FORMAT);
constexpr auto MY_FORMAT2 = HEX + FORMAT_MIN_WIDTH(5);
Serial.println(33, MY_FORMAT2);
Serial.println(10, FORMAT_HEX_BYTE);
Serial.println(10, FORMAT_HEX_BYTE2);
//Serial.println(10, WrapString(10)); // Warning only, C++ allows converting between char* and int :-S
//Serial.println("foo", EvenOddFormatter()); // Warning only, C++ allows converting between char* and int :-S
//Serial.println(S, EvenOddFormatter()); // Error
//Serial.println(charp, WrapString::KeepIndent(), HEX); // Error
//constexpr auto MY_FORMAT_ERR = WrapString::KeepIndent() + HEX; // Still ok, validity *might* depend on the type you want to print
//Serial.println(33, MY_FORMAT_ERR); // Error
Serial.println(charp, WrapString(10), WrapString::KeepIndent());
Serial.println(charp, WrapString::KeepIndent());
}
void loop() { }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment