Click here to Skip to main content
15,892,575 members
Articles / Programming Languages / C++

Extremely Efficient Type-safe printf Library

Rate me:
Please Sign up or sign in to vote.
4.85/5 (22 votes)
17 Aug 2011CPOL17 min read 101.5K   596   38  
Introduces a fast and type-safe parameter rendering library (type-safe printf)
// Type-safe C++0x printf library
// Copyright (c) 2011 HHD Software Ltd. http://www.hhdsoftware.com
// Written by Alexander Bessonov

// Distributed under the terms of The Code Project Open License (http://www.codeproject.com)

#pragma once

//////////////////////////////////////////
/*
Format declaration:

{<param-index>[width-decl][alignment-decl][plus-decl][precision-decl][base-decl][padding-decl][ellipsis-decl][char-decl][locale-decl][time-decl]}

To escape the '{' character, duplicate it

<param-index> is zero-based parameter's index and is mandatory
All other parts are optional and may come in any order

[char-decl]
c
Treat the integer parameter as a character (ANSI or UNICODE)

[locale-decl]
l
Separate thousands with default locale's thousand separator

[width-decl]
w<min-width>,<max-width> or
w<min-width> or
w,<max-width>

[alignment-decl]
al | ar | ac
Sets left, right or center alignment

Default alignment is left

[plus-decl]
+ 
Forces the plus sign for numbers

[precision-decl]
p<number>
Sets the number of digits after the comma

[base-decl]
b<number> or
b[0]16[x] | b[0]16[X]
number may only be 2, 8, 10 or 16. Default is 10. If 'x' is put after 16, lowercase hex is used, otherwise - uppercase (default)
if 0 is specified, "0x" or "0X" will be rendered before the number

[padding-decl]
f<character>
Specify the character to fill when min width is specified. Default is ' '

[ellipsis-decl]
e
Add the ellipsis sign when truncating output. Is not compatible with center alignment (will act as left alignment)

[time-decl]
t(format-string)
Interpret the passed parameter as a date, time or date+time and display, according to format-string (see strftime CRT function)
*/
//////////////////////////////////////////

#include <boost/range/algorithm.hpp>
#include "generic_exception.h"

namespace ts_printf
{
	// This exception is thrown if format string cannot be parsed
	struct bad_extended_format_string : generic_exception
	{
	};

	namespace _details
	{
		template<class Formatter,class char_type>
		class ExtendedFormat
		{
			template<class IteratorT>
			static int _atoi(IteratorT &cur) throw()
			{
				int result=0;
				for (;'0'<=*cur && *cur<='9';++cur)
				{
					result=result*10+*cur-'0';
				}
				return result;
			}

		public:
			template<class Range>
			void iSetFormat(const Range &src)
			{
				const size_t maxcount=boost::count_if(
					src,[](char_type c) { return c=='{'; }
					);

				static_cast<Formatter *>(this)->reserve_parts(maxcount*2+1);

				auto last=boost::begin(src);
				decltype(last) cur=last;
				auto end=boost::end(src);

				bool bClearEscapes=false;
				for (;cur!=end;++cur)
				{
					if (*cur=='{')
					{
						if (cur+1==end || cur[1]!='{')
						{
							if (last!=cur)
								static_cast<Formatter *>(this)->add_part(FormatDesc<char_type>(boost::make_iterator_range(last,cur),bClearEscapes));
							bClearEscapes=false;
							++cur;
							FormatDesc<char_type> &fd=static_cast<Formatter *>(this)->next_part();
							fd.parameter=(short) _atoi(cur);
							while (cur!=end)
							{
								switch (*cur)
								{
								case 'a':	// parse alignment
									switch (*++cur)
									{
									case 'l':
										fd.align=AlignLeft;
										break;
									case 'r':
										fd.align=AlignRight;
										break;
									case 'c':
										fd.align=AlignCenter;
										break;
									}
									++cur;
									break;
								case '+':
									fd.flags|=FF_ALWAYSPLUS;
									++cur;
									break;
								case 'c':
									fd.flags|=FF_FORCECHAR;
									++cur;
									break;
								case 't':	// time format
									if (*++cur!='(')
										throw bad_extended_format_string();

									++cur;
									fd.tmfBegin=std::addressof(*cur);
									for (;;++cur)
									{
										if (*cur==')')
										{
											fd.tmfEnd=std::addressof(*cur);
											break;
										} else if (*cur=='}')
											throw bad_extended_format_string();
									}
									fd.flags|=FF_TIMEFORMAT;
									++cur;
									break;
								case 'l':
									fd.flags|=FF_THOUSANDS;
									++cur;
									break;
								case 'w':
									if (*++cur==',')
									{
										++cur;
										fd.maxwidth=(short) _atoi(cur);
									}
									else
									{
										int width=_atoi(cur);
										if (*cur==',')
										{
											fd.minwidth=(short) width;
											++cur;
											fd.maxwidth=(short) _atoi(cur);
											assert(fd.maxwidth>=fd.minwidth);
										} else
											fd.minwidth=(short) width;
									}
									break;
								case 'p':
									fd.precision=(short) _atoi(++cur);
									break;
								case 'b':
									++cur;
									if (*cur=='0')
									{
										fd.flags|=FF_NUMBERPREFIX;
										++cur;
									}
									fd.base=(char) _atoi(cur);
									assert(fd.base==2 || fd.base==8 || fd.base==10 || fd.base==16);
									if (fd.base==16)
									{
										if (*cur=='x')
										{
											fd.flags|=FF_LOWHEX;
											++cur;
										} else if (*cur=='X')
											++cur;
									}
									break;
								case 'f':
									fd.pad=*++cur;
									++cur;
									break;
								case 'e':
									fd.flags|=FF_ELLIPSIS;
									++cur;
									break;
								case '}':
									last=cur+1;
									goto endparse;
								default:
									__assume(false);
									break;
								}
							}
endparse:			;
						} else
						{
							bClearEscapes=true;
							++cur;
						}
					}
				}

				if (last!=end)
					static_cast<Formatter *>(this)->add_part(FormatDesc<char_type>(boost::make_iterator_range(last,end),bClearEscapes));
			}

			template<class Adaptor>
			static void WritePart(Adaptor &adaptor,const char_type *begin,const char_type *end,bool bHasEscapedCharacters) throw()
			{
				if (!bHasEscapedCharacters)
					adaptor.write(boost::make_iterator_range(begin,end));
				else
				{
					const char_type *last=begin;

					for (;begin!=end;++begin)
					{
						if (*begin=='{')
						{
							adaptor.write(boost::make_iterator_range(last,++begin));
							last=begin+1;
						}
					}
					if (last!=end)
						adaptor.write(boost::make_iterator_range(last,end));
				}
			}
		};
	};
};

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) HHD Software Ltd.
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions