Files CCore/inc/Printf.h CCore/src/Printf.cpp
Subfolders CCore/inc/printf CCore/src/printf
CCore provides a printing infrastructure. Printing is a transformation of an object into a sequence of characters. There are three actors in a printing process: the object, the output device and printing options. The output device consumes characters, printing options determines how the object is exactly transformed. For example, integer can be represented in the decimal form or in the hexadecimal, with or without sign, with extra zero digits and so on. The type of an object is responsible for the printing implementation. There are default implementations for integral and string-like types.
Printing is performed with the following printing functions:
template <class P,class ... TT> void Printf(P &&out,const char *format,const TT & ... tt); template <class P,class ... TT> void Putobj(P &&out,const TT & ... tt); template <class P,class ... TT> void Putch(P &&out,TT ... tt);
Printf() uses the format string to embed given objects into the string with specified printing options and puts the result into the given output device. This function is similar to the classic C printf(), but has the following advantages: type-safe and type-driven, easily customizable output devices, extendable to print any desired type.
Putobj() outs given objects with default printing options.
Putch() casts given objects to chars and outs them.
Format string is an ordinary zero-terminated string with embedded format specifiers. Format specifier has a form "#<options>;", where <options> is the option string. It is converted to the correspondent object print options. To print the character '#' use "##" as a format specifier.
Format string is given as a zero-terminated character string, not as StrLen, due to the printf() tradition.
There are three ways to make a type printable.
The most direct way: define in the class definition the method template print() like this:
class SomeClass { public: template <class P> void print(P &out) const { .... } };
If you need printing options do like this:
class SomeClass { public: struct PrintOptType { .... PrintOptType(); // default options PrintOptType(const char *ptr,const char *lim); // [ptr,lim) -- options from the format string // if the format string has a format specifier #XXX;, // then [ptr,lim) points to the string XXX }; template <class P> void print(P &out,const PrintOptType &opt) const { .... } };
The second way is to specify a printing proxy type:
class SomeClass { public: using PrintProxyType = .... ; };
In this case the type SomeClass must be castable to the SomeClass::PrintProxyType.
For a non-class type you can define a function GetTextDesc():
enum SomeEnum { SomeEnum1, SomeEnum2, SomeEnum3 }; const char * GetTextDesc(SomeEnum e);
This way is intended mostly to print enumerations. GetTextDesc() is used as a proxy: it must return something printable. There is the set of such functions for bool and integral types:
inline const char * GetTextDesc(bool value) { return value?"true":"false"; } inline int GetTextDesc(int value) { return value; } inline unsigned GetTextDesc(unsigned value) { return value; } inline long GetTextDesc(long value) { return value; } inline unsigned long GetTextDesc(unsigned long value) { return value; } inline long long GetTextDesc(long long value) { return value; } inline unsigned long long GetTextDesc(unsigned long long value) { return value; }
It allows to print a non-class type, castable to one of these types.
You can also specialize the template CCore::PrintProxy<T> to define the print proxy type and optionally the print option type.
namespace App { struct S { .... }; } // namespace App namespace CCore { template <> struct PrintProxy<App::S> { using ProxyType = .... ; using OptType = .... ; }; } // namespace CCore
You can determine the printing option type using the PrintOptAdapter<T>. If the type T has a printing option type (even through proxy), then PrintOptAdapter<T>::PrintOptType is that type.
Sometimes you need to specify printing options using the object, not the format string. In such case use the function BindOpt():
T obj; Opt opt; Printf(out,"object options: #;",BindOpt(opt,obj));
The type of the output device object must be ether output device class or define a printing adapter type:
class POut { public: using PrintOutType = .... ; };
Or using specialization:
class POut;
template <>
struct PrintOutAdapter<POut>
{
using PrintOutType = .... ;
};
A temporary printing adapter type object is created from the first argument of a printing function in such case and used to do the printing.
Output device class must provide the following members:
class PDev { public: using PrintOutType = PDev & ; PrintOutType printRef() { return *this; } void put(char ch); // print one character void put(char ch,ulen len); // print the same character len times void put(const char *str,ulen len); // print the character range void flush(); // flush the object };
The following output types are provided:
enum NoPrintType { NoPrint }; // to print nowhere
NoPrint is a word to print nowhere:
Printf(NoPrint,"this string goes nowhere");
The next type is PrintBase. It is designed to be a base class for actual output types.
class PrintBase : NoCopy
{
....
private:
virtual PtrLen<char> do_provide(ulen hint_len)=0;
virtual void do_flush(char *ptr,ulen len)=0;
protected:
void clearOverflowFlag();
public:
// constructors
PrintBase();
~PrintBase();
// methods
bool getOverflowFlag() const;
PrintBase & guardOverflow();
};
When you derive a class from PrintBase, you have to implement two virtual functions:
class PDev : public PrintBase { private: virtual PtrLen<char> do_provide(ulen hint_len) // hint_len is a suggested minimum buffer length { .... } virtual void do_flush(char *ptr,ulen len) // character range to be propagated to the final destination { .... } public: PDev() { .... } ~PDev() { flush(); .... } };
do_provide() is called to setup the printing buffer. This method may either return a buffer for printing, or return an empty buffer or throw an exception. If the empty buffer is returned, PrintBase will skip some output characters and set the internal overflow flag. Derived class may clear this flag using clearOverflowFlag() method. You can check this flag using getOverflowFlag() method or call the guard method guardOverflow() to throw an exception if the overflow has happened. When PrintBase got a buffer it fills it with output characters and flush sometimes during printing operations. So the method do_flush() is called to propagate the output characters. This method is a paired method for do_provide(): i.e. it is called sometimes after if the do_provide() was called successfully. You have to call flush() in the destructor to commit pending characters. For better understanding how to derive from PrintBase see the PrintBuf implementation.
The next class is PrintBuf. This class prints to the memory buffer.
class PrintBuf : public PrintBase
{
....
public:
explicit PrintBuf(PtrLen<char> buf=Nothing);
~PrintBuf();
void init(PtrLen<char> buf);
PrintBuf & guardOverflow() { PrintBase::guardOverflow(); return *this; }
StrLen close();
const char * closeZStr() { return close().ptr; }
};
Methods close() and closeZStr() both complete printing, append zero character and return the resulting string.
Method init() closes the previous buffer and attach the new one. It also clears the overflow flag.
The last class is PrintCount<P>. This class counts printing characters and passes them to the next printing device. If you need only counting, you can use PrintCount<void>
template <class P>
class PrintCount : NoCopy
{
....
public:
explicit PrintCount(P &out_) : out(out_),count(0) {}
ulen getCount() const { return count; }
};
Tuples are treated especially by print functions: they are printed as if they members was used in the argument list. For example:
int a; int b; Printf(out,"a = #; b = #;\n",MakeTuple(a,b));
has the same effect as:
int a; int b; Printf(out,"a = #; b = #;\n",a,b);
This rule is applied recursively.
The following types: char *, const char *, char[N], const char [N], StrLen, are string-like types and printed as strings of characters using the proxy class StrPrint. This class has a printing options, represented by the struct StrPrintOpt:
enum StrAlign { StrAlignLeft, StrAlignRight, StrAlignCenter, StrAlignDefault = StrAlignRight }; struct StrPrintOpt { ulen width; StrAlign align; bool quoted; void setDefault() { width=0; align=StrAlignDefault; quoted=false; } StrPrintOpt() { setDefault(); } StrPrintOpt(const char *ptr,const char *lim); // // [width=0][.q|.Q][l|L|r|R|c|C=R] // };
width is the minimum output length, if the string has less characters, then it is extended to fit it.
align determines how the string is placed inside the extension. Left alignment means extension by space characters after the string, right — before, center — both before and after by equal number.
quoted encloses the string in quotes.
Text representation of options may include width, qouted flag (.q or .Q) and alignment flag. For example, "#20L;" means width = 20 ; align = StrAlignLeft ; quoted = false ;, "#20.qC;" means width = 20 ; align = StrAlignCenter ; quoted = true ;.
To print the same character several times use the helper class RepeatChar:
class RepeatChar
{
ulen count;
char ch;
public:
RepeatChar(ulen count_,char ch_) : count(count_),ch(ch_) {}
template <class P>
void print(P &out) const
{
out.put(ch,count);
}
};
Title looks like this:
--- Title ----------------------------------------------------------------------
The Helper Class Title can be used to print a title:
class Title
{
StrLen str;
public:
explicit Title(StrLen str_) : str(str_) {}
using PrintOptType = TitlePrintOpt ;
template <class P>
void print(P &out,PrintOptType opt) const;
};
And here is the Title printing options:
struct TitlePrintOpt
{
static const ulen Default_width = 80 ;
static const ulen Default_off = 3 ;
static const char Default_border = '-' ;
ulen width;
ulen off;
char border;
void setDefault()
{
width=Default_width;
off=Default_off;
border=Default_border;
}
TitlePrintOpt() { setDefault(); }
TitlePrintOpt(const char *ptr,const char *lim);
//
// [width=80][.off=3][border=-]
//
};
width is the total title length, off is the text offset, border is the border character.
Text divider looks like this:
--------------------------------------------------------------------------------
It is printed by the helper class TextDivider:
class TextDivider
{
public:
TextDivider() {}
using PrintOptType = TextDividerPrintOpt ;
template <class P>
void print(P &out,PrintOptType opt) const;
};
The following printing options can be used:
struct TextDividerPrintOpt
{
static const ulen Default_width = TitlePrintOpt::Default_width ;
static const char Default_border = TitlePrintOpt::Default_border ;
ulen width;
char border;
void setDefault()
{
width=Default_width;
border=Default_border;
}
TextDividerPrintOpt() { setDefault(); }
TextDividerPrintOpt(const char *ptr,const char *lim);
//
// [width=80][border=-]
//
};
width is the total divider length, border is the border character.
Integral types are printed using proxy classes SIntPrint<SInt> and UIntPrint<UInt>:
/* class SIntPrint<SInt> */ template <class SInt> class SIntPrint { SInt value; public: explicit SIntPrint(SInt value_) : value(value_) {} using PrintOptType = IntPrintOpt ; template <class P> void print(P &out,PrintOptType opt) const; }; /* class UIntPrint<UInt> */ template <class UInt> class UIntPrint { UInt value; public: explicit UIntPrint(UInt value_) : value(value_) {} using PrintOptType = IntPrintOpt ; template <class P> void print(P &out,PrintOptType opt) const; };
IntPrintOpt represents the printing options:
enum IntShowSign { IntShowMinus, IntShowPlus, IntShowSignDefault = IntShowMinus }; enum IntAlign { IntAlignLeft, IntAlignRight, IntAlignInternal, IntAlignDefault = IntAlignRight }; enum IntShowBase { IntShowNoBase, IntShowBaseSuffix, IntShowBasePrefix, IntShowBaseDefault = IntShowNoBase }; struct IntPrintOpt { ulen width; unsigned base; // 2..16, 0 for c IntAlign align; IntShowSign show_sign; IntShowBase show_base; void setDefault() { width=0; base=10; align=IntAlignDefault; show_sign=IntShowSignDefault; show_base=IntShowBaseDefault; } IntPrintOpt() { setDefault(); } IntPrintOpt(const char *ptr,const char *lim); // // [+][width=0][.base|.b|.h|.x|.c=.10][l|L|r|R|i|I=R] // };
There are five printing options:
width is the minimum output length. If the representation is shorter than this value, then it is extended to fit it.
align is determined how extension is performed. Internal alignment inserts zero digits in the number representation.
base is the representation base, it can vary form 2 to 16. It may have the special value 0. In this case the printing number is casted to char and this character goes out. The base is given as a number after dot, or as a letter after dot. Letter b means base = 2 and show_base = IntShowBaseSuffix, letter h means base = 16 and show_base = IntShowBaseSuffix, letter x means base = 16 and show_base = IntShowBasePrefix. Letter c means base = 0.
show_sign shows sign plus for positive numbers.
show_base shows representation base as suffix or prefix. Prefix is 0x for base 16. Suffixes are h for base 16 and b for base 2.
Here is some examples:
Printf(Con,"--- #10l; ---\n",12345); Printf(Con,"--- #10i; ---\n",12345); Printf(Con,"--- #10r; ---\n",12345); Printf(Con,"--- #+10.5l; ---\n",12345); Printf(Con,"--- #+10.hi; ---\n",12345); Printf(Con,"--- #10.hi; ---\n",-12345);
prints
--- 12345 --- --- 0000012345 --- --- 12345 --- --- +343340 --- --- +00003039h --- --- -00003039h ---
PrintDumpType is designed to print hexadecimal dumps of integers. Dumps look like this:
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31
To create the object of PrintDumpType use the overloaded Creator function PrintDump():
template <class UInt> PrintDumpType<UInt> PrintDump(const UInt *ptr,ulen len); template <class UInt> PrintDumpType<UInt> PrintDump(PtrLen<UInt> data); template <class UInt> PrintDumpType<UInt> PrintDump(PtrLen<const UInt> data);
And here is the options:
struct PrintDumpOptType
{
static const ulen Default_line_len = 16 ;
ulen width;
ulen line_len;
void setDefault()
{
width=0;
line_len=Default_line_len;
}
PrintDumpOptType() { setDefault(); }
PrintDumpOptType(const char *ptr,const char *lim);
//
// [width=0][.line_len=16]
//
};
width is the output width of each integer. If it is 0, then the hexadecimal width of the integral type is used (2 for uint8, 4 for uint16 and so on).
line_len determines the number of integers on the one line.