Skip to content

Commit c64a4e0

Browse files
WIP
1 parent 5084817 commit c64a4e0

File tree

1 file changed

+203
-7
lines changed

1 file changed

+203
-7
lines changed

cores/arduino/Print.h

+203-7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636

3737
#define _always_inline __attribute__ ((__always_inline__)) // undefined at end
3838

39+
template <typename Check, typename T>
40+
struct check_type {
41+
using type = T;
42+
};
43+
template <typename Check, typename T> using check_type_t = typename check_type<Check, T>::type;
44+
3945
class Print
4046
{
4147
private:
@@ -47,6 +53,18 @@ class Print
4753
public:
4854
Print() : write_error(0) {}
4955

56+
struct Formatter { };
57+
58+
struct FormatterOptionBase { };
59+
template <typename TFormatter, typename TValue>
60+
struct FormatterOption : FormatterOptionBase {
61+
// Is this the right place to define this? Or perhaps just move
62+
// down?
63+
using Formatter = TFormatter;
64+
TValue value;
65+
constexpr FormatterOption(const TValue value) : value(value) { }
66+
};
67+
5068
int getWriteError() { return write_error; }
5169
void clearWriteError() { setWriteError(0); }
5270

@@ -83,12 +101,6 @@ class Print
83101
_always_inline size_t doPrint(unsigned int n, int f = DEC) { return doPrint((unsigned long) n, f); }
84102
_always_inline size_t doPrint( float n, int f = 2 ) { return doPrint(( double ) n, f); }
85103

86-
template <typename Check, typename T>
87-
struct check_type {
88-
using type = T;
89-
};
90-
template <typename Check, typename T> using check_type_t = typename check_type<Check, T>::type;
91-
92104
template<typename T, typename F>
93105
_always_inline auto doPrint(T v, F f )
94106
-> check_type_t<decltype(f.printTo(this, v)), size_t> {
@@ -98,18 +110,48 @@ class Print
98110
size_t println(void);
99111

100112
virtual void flush() { /* Empty implementation for backward compatibility */ }
113+
/*
114+
template<typename T, typename ...Ts>
115+
_always_inline size_t print(const T &arg, const Ts &...args) {
116+
// This might lead to infinite template instantion
117+
//return print(arg, DefaultFormatter<>(), args...);
118+
size_t n = DefaultFormatter<>().printTo(this, arg);
119+
return n + print(args...);
120+
}
121+
122+
template<typename T, typename T2, typename ...Ts>
123+
_always_inline auto print(const T &arg, const T2 &arg2, const Ts &...args)
124+
-> check_type_t<decltype(arg2.printTo(this, arg)), size_t> {
125+
size_t n = arg2.printTo(this, arg);
126+
return n + print(args...);
127+
}
128+
129+
template<typename T, typename T2, typename T3, typename ...Ts>
130+
_always_inline auto print(const T &arg, const T2 &arg2, const T3 &arg3, const Ts &...args)
131+
-> check_type_t<decltype(arg2.addOption(arg3)), size_t> {
132+
return print(arg, arg2.addOption(arg3), args...);
133+
}
134+
135+
template<typename T, typename T2, typename ...Ts>
136+
_always_inline auto print(const T &arg, const T2 &arg2, const Ts &...args)
137+
-> check_type_t<decltype(DefaultFormatter<>().addOption(arg2)), size_t> {
138+
return print(arg, DefaultFormatter<>().addOption(arg2), args...);
139+
}
140+
*/
101141

102142
#if __cplusplus >= 201103L
143+
template<typename ...Ts> _always_inline size_t print(const Ts &...args);
103144
template<typename ...Ts> _always_inline size_t println(const Ts &...args) { size_t t = print(args...); return t + println(); }
104145
#else
105146
template<typename T> _always_inline size_t println(const T &arg) { size_t t = print(arg); return t + println(); }
106147
template<typename T, typename T2> _always_inline size_t println(const T &arg1, const T2& arg2) { size_t t = print(arg1, arg2); return t + println(); }
107148
#endif // __cplusplus >= 201103L
108149

109-
_always_inline size_t print() { return 0; }
150+
//_always_inline size_t print() { return 0; }
110151

111152
/** Variadic methods **/
112153
#if __cplusplus >= 201103L // requires C++11
154+
/*
113155
template<typename T, typename ...Ts>
114156
_always_inline size_t print(const T &arg, const Ts &...args) {
115157
size_t t = doPrint(arg);
@@ -122,6 +164,8 @@ class Print
122164
size_t t = doPrint(arg, arg2);
123165
return t + print(args...);
124166
}
167+
*/
168+
125169
/*
126170
// Some methods take an extra int parameter. If so, use these templates.
127171
// In a future, it would be nice to make the base/precision a special type.
@@ -142,6 +186,158 @@ class Print
142186
#endif // __cplusplus >= 201103L
143187
};
144188

189+
class DefaultFormatter;
190+
191+
// TODO: Do we really need a FormatterOption base class? Without it,
192+
// options can be POD, not needing a constructor. With it, we can
193+
// prevent accidentally treating things as options which are not, but if
194+
// we already check a Formatter base class, we can also require that
195+
// Formatters do not define nonsensical addOption methods (even more,
196+
// without an explicit FormatterOption, Formatters can even use
197+
// non-class types as options if they want).
198+
// TODO: Where to define the "default formatter" for an option? Now, it
199+
// is our superclass, but it might just as well be defined with a using
200+
// directive directly here. Or perhaps it should be a method (this
201+
// might be problematic, since the DefaultFormatter type is incomplete
202+
// at this point). One completely different approach would be a
203+
// DefaultFormatterFor template, which gets specialized, but this
204+
// probably has the problem that once it is instantiated, you can no
205+
// longer add to it. Using specializations does allow defining a default
206+
// formatter for a type/option combination (allowing reuse of e.g. HEX
207+
// for custom types) or for just a type (allowing default formatting of
208+
// custom types).
209+
struct FormatOptionBase : Print::FormatterOption<DefaultFormatter, uint8_t> {
210+
// TODO: We must provide an explicit constructor (if we have a
211+
// superclass, we are no longer a POD type), and if we do, there is no
212+
// real point in storing our value in the superclass (better have one
213+
// extra line here, but more explictness. Inheriting the constructor
214+
// is even more ugly (needs to repeat superclass template arguments).
215+
constexpr FormatOptionBase(uint8_t value) : Print::FormatterOption<DefaultFormatter, uint8_t>(value) { }
216+
};
217+
struct FormatOptionMinWidth : Print::FormatterOption<DefaultFormatter, uint8_t> {
218+
constexpr FormatOptionMinWidth(uint8_t value) : Print::FormatterOption<DefaultFormatter, uint8_t>(value) { }
219+
};
220+
221+
#undef HEX
222+
inline constexpr FormatOptionMinWidth PRINT_MIN_WIDTH(uint8_t min_width) { return {min_width}; }
223+
inline constexpr FormatOptionBase PRINT_BASE(uint8_t base) { return {base}; }
224+
constexpr FormatOptionBase HEX = PRINT_BASE(16);
225+
226+
struct DefaultFormatter : Print::Formatter {
227+
uint8_t base;
228+
uint8_t min_width;
229+
230+
DefaultFormatter(uint8_t base = 10, uint8_t min_width = 0)
231+
: base(base), min_width(min_width)
232+
{ }
233+
234+
DefaultFormatter addOption(FormatOptionBase o) const {
235+
return {o.value, this->min_width};
236+
}
237+
238+
DefaultFormatter addOption(FormatOptionMinWidth o) const {
239+
return {this->base, o.value};
240+
}
241+
242+
size_t printTo(Print *p, int n) const;
243+
size_t printTo(Print *p, const char *) const;
244+
template <typename T>
245+
size_t printTo(Print *p, const T&) const;
246+
};
247+
248+
// TODO: These can probably be moved back inline above (they were split
249+
// when PrintHelper did not exist, so the Print class was defined
250+
// between the declaration of DefaultFormatter and these method
251+
// definitions).
252+
inline size_t DefaultFormatter::printTo(Print *p, int n) const {
253+
return p->doPrint(n, this->base);
254+
}
255+
256+
inline size_t DefaultFormatter::printTo(Print *p, const char * s) const {
257+
// TODO: Maybe replace bool has_int_options with a more detailed bitwise flag int
258+
// for better error messages.
259+
//static_assert(!has_int_options, "Cannot print strings with integer-specific formatting options");
260+
p->doPrint(s);
261+
return 0;
262+
}
263+
264+
template <typename T>
265+
inline size_t DefaultFormatter::printTo(Print *p, const T& v) const {
266+
return p->doPrint(v);
267+
}
268+
269+
// TODO: Should we really need PrintHelper? It was now added allow
270+
// referencing DefaultFormatter above. These methods could just be
271+
// out-of-line definitions of methods in Print, but then the complicated
272+
// method signature must be duplicated. Alternatively, a single method
273+
// that generates a DefaultFormatter object could possibly be out of
274+
// line (though, thinking on it, both of these options might not work,
275+
// since they need DefaultFormatter as a return type in a
276+
// declaration...).
277+
class PrintHelper {
278+
public:
279+
template<typename T, typename ...Ts>
280+
static _always_inline size_t printTo(Print * p, const T &arg, const Ts &...args) {
281+
// This might lead to infinite template instantion
282+
//return print(arg, DefaultFormatter<>(), args...);
283+
size_t n = DefaultFormatter().printTo(p, arg);
284+
return n + printTo(p, args...);
285+
}
286+
/*
287+
template<typename T, typename T2, typename ...Ts>
288+
static _always_inline auto printTo(Print * p, const T &arg, const T2 &arg2, const Ts &...args)
289+
-> check_type_t<decltype(arg2.printTo(p, arg)), size_t> {
290+
size_t n = arg2.printTo(p, arg);
291+
return n + printTo(p, args...);
292+
}
293+
*/
294+
static void accepts_formatter(const Print::Formatter*);
295+
296+
template<typename T, typename T2, typename ...Ts>
297+
static _always_inline auto printTo(Print * p, const T &arg, const T2 &arg2, const Ts &...args)
298+
-> check_type_t<decltype(accepts_formatter(&arg2)), size_t> {
299+
//-> check_type_t<decltype(arg2.printTo(p, arg)), size_t> {
300+
size_t n = arg2.printTo(p, arg);
301+
return n + printTo(p, args...);
302+
}
303+
304+
305+
template<typename T, typename T2, typename T3, typename ...Ts>
306+
static _always_inline auto printTo(Print * p, const T &arg, const T2 &arg2, const T3 &arg3, const Ts &...args)
307+
-> check_type_t<decltype(arg2.addOption(arg3)), size_t> {
308+
return printTo(p, arg, arg2.addOption(arg3), args...);
309+
}
310+
311+
static void accepts_option(const Print::FormatterOptionBase*);
312+
313+
template<typename T, typename T2, typename ...Ts>
314+
static _always_inline auto printTo(Print * p, const T &arg, const T2 &arg2, const Ts &...args)
315+
//-> check_type_t<decltype(typename T2::Formatter().addOption(arg2)), size_t> {
316+
-> check_type_t<decltype(accepts_option(&arg2)), size_t> {
317+
//accepts_option(&arg2);
318+
return printTo(p, arg, typename T2::Formatter().addOption(arg2), args...);
319+
}
320+
321+
static _always_inline size_t printTo(Print *) { return 0; }
322+
};
323+
324+
template<typename ...Ts>
325+
_always_inline size_t Print::print(const Ts &...args) { return PrintHelper::printTo(this, args...); }
145326
#undef _always_inline
146327

147328
#endif
329+
330+
// Idea: Raise errors when DefaultFormatter is used with int-only
331+
// options (e.g. integer base), but prints a string or otherwise
332+
// incompatible type. This can be done by extending DefaultFormatter
333+
// with a bitmask or bool template argument(s) that track what options
334+
// have been set, so they can be static_asserted against in specific
335+
// printTo versions. This does require that *all* methods in
336+
// DefaultFormatter are always_inline, to prevent duplicates. This means
337+
// the actual printing code must again live elsewhere, which might not
338+
// be ideal. Also, error messages are then complicated with these
339+
// template arguments. This can be solved by doing just the checks and
340+
// forwarding *all* printTo calls to another class (including a
341+
// catch-all template), so that when you try printing any unsupported
342+
// types you still get the proper "no such method to call, candiates
343+
// are..." error message.

0 commit comments

Comments
 (0)