Forráskód Böngészése

inline stringbuilder and conversions, add Sprint()

Most usable parts which used exactly for actual string building are now
inlined in the header - so, they achieved to be more optimized at
compile time and so, work faster.

Sprint(...) of stringbuilder takes any arguments and output them using
<< for each. So, tOut.Sprint(1,3,"foo") works like tout << 1 << 3 <<
"foo".
alexey 3 éve
szülő
commit
16562a2dd5

+ 63 - 4
src/gbenches/functions.cpp

@@ -339,10 +339,33 @@ BENCHMARK( BM_stdSprintf );
 class bench_builder : public benchmark::Fixture
 {
 public:
+	void SetUp ( const ::benchmark::State& state )
+	{
+		sphSrand(0);
+		a = sphRand();
+		b = sphRand();
+		c = sphRand();
+		d = sphRand();
+		f = sphRand();
+		g = sphRand();
+		h = sphRand();
+		i = sphRand();
+		j = sphRand();
+		k = sphRand();
+		l = sphRand();
+		m = sphRand();
+		n = sphRand();
+		o = sphRand();
+		p = sphRand();
+		q = sphRand();
+	}
+
 	const char * sFieldFmt = R"({"field":%d, "lcs":%u, "hit_count":%u, "word_count":%u, "tf_idf":%d, "min_idf":%d, )"
 							 R"("max_idf":%d, "sum_idf":%d, "min_hit_pos":%d, "min_best_span_pos":%d, "exact_hit":%u, )"
 							 R"("max_window_hits":%d, "min_gaps":%d, "exact_order":%u, "lccs":%d, "wlccs":%d, "atc":%d})";
 
+	int a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q;
+
 };
 
 BENCHMARK_F( bench_builder, Appendf_ints ) ( benchmark::State & st )
@@ -351,9 +374,11 @@ BENCHMARK_F( bench_builder, Appendf_ints ) ( benchmark::State & st )
 
 	for ( auto _ : st )
 	{
-		sBuf.Appendf ( sFieldFmt, 3, 23, 23465, 234, 234, 4346, 345345, 3434535, 345, 54, 1, 23, 5, 0, 34, 45, 234 );
-		sBuf.Clear ();
+		benchmark::DoNotOptimize ( sBuf.Appendf ( sFieldFmt, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q ) );
+		sBuf.Rewind ();
 	}
+	st.SetLabel ( "use standard sprintf() inside" );
+	st.SetItemsProcessed ( st.iterations() );
 }
 
 
@@ -363,9 +388,43 @@ BENCHMARK_F( bench_builder, Sprintf_ints ) ( benchmark::State & st )
 
 	for ( auto _ : st )
 	{
-		sBuf.Sprintf ( sFieldFmt, 3, 23, 23465, 234, 234, 4346, 345345, 3434535, 345, 54, 1, 23, 5, 0, 34, 45, 234 );
-		sBuf.Clear ();
+		benchmark::DoNotOptimize ( sBuf.Sprintf ( sFieldFmt, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q ) );
+		sBuf.Rewind ();
+	}
+	st.SetLabel ( "own implementation sph::Sprintf" );
+	st.SetItemsProcessed ( st.iterations() );
+}
+
+BENCHMARK_F ( bench_builder, Sprint_ints )
+( benchmark::State& st )
+{
+	StringBuilder_c sBuf;
+	for ( auto _ : st )
+	{
+		benchmark::DoNotOptimize ( sBuf.Sprint( "{\"field\":", a, ", \"lcs\":", b, ", \"hit_count\":", c, ", \"word_count\":", d, ", \"tf_idf\":", e, ", \"min_idf\":", f,
+					  ", \"max_idf\":", g, ", \"sum_idf\":", h, ", \"min_hit_pos\":", i, ", \"min_best_span_pos\":", j, ", \"exact_hit\":", k,
+					  ", \"max_window_hits\":", l, ", \"min_gaps\":", m, ", \"exact_order\":", n, ", \"lccs\":", o, ", \"wlccs\":", p, ", \"atc\":", q, '}') );
+		sBuf.Rewind();
+	}
+	st.SetLabel ( "own implementation using templated Sprint" );
+	st.SetItemsProcessed ( st.iterations() );
+}
+
+BENCHMARK_F ( bench_builder, Sprint_streaming )
+( benchmark::State& st )
+{
+	StringBuilder_c sBuf;
+
+	for ( auto _ : st )
+	{
+		benchmark::DoNotOptimize ( sBuf << "{\"field\":" << a << ", \"lcs\":" << b << ", \"hit_count\":" << c << ", \"word_count\":" << d << ", \"tf_idf\":" << e
+		<< ", \"min_idf\":" << f << ", \"max_idf\":" << g << ", \"sum_idf\":" << h << ", \"min_hit_pos\":" << i << ", \"min_best_span_pos\":" << j
+		<< ", \"exact_hit\":" << k << ", \"max_window_hits\":" << l << ", \"min_gaps\":" << m << ", \"exact_order\":" << n << ", \"lccs\":" << o
+		<< ", \"wlccs\":" << p << ", \"atc\":" << q << '}' );
+		sBuf.Rewind();
 	}
+	st.SetLabel ( "own implementation using << (mostly inlined)" );
+	st.SetItemsProcessed ( st.iterations() );
 }
 
 BENCHMARK_MAIN();

+ 40 - 14
src/gtests/gtests_functions.cpp

@@ -189,6 +189,32 @@ TEST ( functions, stringbuilder_standalone )
 	ASSERT_STREQ ( builder.cstr (), "one, two, three" );
 }
 
+// standalone comma. Not necessary related to stringbuilder, but live alone.
+TEST ( functions, stringbuilder_templated )
+{
+	StringBuilder_c builder(",");
+	builder.Sprint("one", 3, " ", 4.34, "fine");
+	EXPECT_STREQ ( builder.cstr(), "one,3, ,4.340000,fine" );
+	const char* szData = "hello";
+	builder << szData; // routed as const char*
+	const char szData1[] = "hello2";
+	builder << szData1; // routed as const char[]
+	CSphString sData2 = "hello3";
+	builder << sData2.cstr(); // routed as const char*
+	builder << "hello4"; // routed as const char[]
+	void* pVoid = nullptr;
+	builder << pVoid; // routed as const T*
+	int* pInt = nullptr;
+	builder << pInt; // routed as const T*
+	char* pChar = nullptr;
+	builder << pChar; // routed as const char*, and so, will output nothing
+	char pCharArr[10] = "fff\0ddd";
+	builder << (char*)pCharArr; // routed as const char*
+	builder.Sprint ( szData, szData1, sData2.cstr(), "hello4", pVoid, pInt, pChar, (char*)pCharArr );
+	builder << pCharArr << "aaa"; // fixme! routed as const char[]. So, tailing "ddd" and "aaa" will NOT be visible, as \0 is inside of pCharArr
+	ASSERT_STREQ ( builder.cstr(), "one,3, ,4.340000,fine,hello,hello2,hello3,hello4,0x0000000000000000,0x0000000000000000,fff,hello,hello2,hello3,hello4,0x0000000000000000,0x0000000000000000,fff,fff" );
+}
+
 TEST ( functions, JsonEscapedBuilder_sugar )
 {
 	JsonEscapedBuilder tOut;
@@ -1922,61 +1948,61 @@ TEST ( functions, FindLastNumeric )
 	ASSERT_EQ ( sNum3 + 3, sphFindLastNumeric ( sNum3, 5 ) );
 }
 
-TEST ( functions, UItoA_ItoA )
+TEST ( functions, NtoA )
 {
 	using namespace sph;
 
 	char sBuf[50];
 	memset (sBuf, 255, 50);
 
-	int iLen = UItoA (sBuf, (DWORD)50);
+	int iLen = NtoA (sBuf, (DWORD)50);
 	sBuf[iLen]='\0';
 	ASSERT_STREQ ( "50", sBuf);
 
-	iLen = ItoA ( sBuf, 50, 10, 0, 4);
+	iLen = NtoA ( sBuf, 50, 10, 0, 4);
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "0050", sBuf );
 
-	iLen = ItoA ( sBuf, 50, 10, 4 );
+	iLen = NtoA ( sBuf, 50, 10, 4 );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "  50", sBuf );
 
-	iLen = ItoA ( sBuf, 50, 10, 6, 3 );
+	iLen = NtoA ( sBuf, 50, 10, 6, 3 );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "   050", sBuf );
 
-	iLen = ItoA ( sBuf, 50, 10, 6, 3, '_' );
+	iLen = NtoA ( sBuf, 50, 10, 6, 3, '_' );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "___050", sBuf );
 
-	iLen = ItoA<int64_t> ( sBuf, 0xFFFFFFFFFFFFFFFFll );
+	iLen = NtoA<int64_t> ( sBuf, 0xFFFFFFFFFFFFFFFFll );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "-1", sBuf );
 
-	iLen = ItoA<int64_t> ( sBuf, 0x8000000000000000ll );
+	iLen = NtoA<int64_t> ( sBuf, 0x8000000000000000ll );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "-9223372036854775808", sBuf );
 
-	iLen = ItoA ( sBuf, 0x7FFFFFFFFFFFFFFFll );
+	iLen = NtoA ( sBuf, 0x7FFFFFFFFFFFFFFFll );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "9223372036854775807", sBuf );
 
-	iLen = ItoA ( sBuf, -9223372036854775807 );
+	iLen = NtoA ( sBuf, -9223372036854775807 );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "-9223372036854775807", sBuf );
 
-	sBuf[ItoA ( sBuf, -9223372036854775807 )] = '\0';
+	sBuf[NtoA ( sBuf, -9223372036854775807 )] = '\0';
 	ASSERT_STREQ ( "-9223372036854775807", sBuf );
 
-	iLen = ItoA ( sBuf, 9223372036854775807 );
+	iLen = NtoA ( sBuf, 9223372036854775807 );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "9223372036854775807", sBuf );
 
-	iLen = ItoA<int64_t> ( sBuf, 0xFFFFFFFFFFFFFFFFll, 16 );
+	iLen = NtoA<int64_t> ( sBuf, 0xFFFFFFFFFFFFFFFFll, 16 );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "-1", sBuf );
 
-	iLen = ItoA<int64_t> ( sBuf, 0x8000000000000000ll, 16 );
+	iLen = NtoA<int64_t> ( sBuf, 0x8000000000000000ll, 16 );
 	sBuf[iLen] = '\0';
 	ASSERT_STREQ ( "-8000000000000000", sBuf );
 }

+ 1 - 1
src/indexsettings.cpp

@@ -1109,7 +1109,7 @@ bool IndexSettingsContainer_c::AddOption ( const CSphString & sName, const CSphS
 					i = sFilename;
 			}
 
-		StringBuilder_c sNewValue = " ";
+		StringBuilder_c sNewValue {" "};
 		for ( auto & i : dValues )
 			sNewValue << i;
 

+ 1 - 1
src/indextool.cpp

@@ -76,7 +76,7 @@ CSphString FilenameBuilder_c::GetFullPath ( const CSphString & sName ) const
 	CSphString sPath = GetPathForNewIndex ( m_sIndex );
 
 	StrVec_t dFiles;
-	StringBuilder_c sNewValue = " ";
+	StringBuilder_c sNewValue {" "};
 
 	// we assume that path has been stripped before
 	StrVec_t dValues = sphSplit ( sName.cstr(), sName.Length(), " \t," );

+ 1 - 1
src/searchd.cpp

@@ -3242,7 +3242,7 @@ static void LogStatementSphinxql ( Str_t sQuery, int iRealTime )
 	tBuf += sTimeBuf;
 
 	tBuf.Sprintf ( " conn %d real %0.3F *""/ ", session::GetConnID(), iRealTime );
-	tBuf += sQuery;
+	tBuf << sQuery;
 
 	// finish statement and add line feed
 	tBuf += ";\n";

+ 1 - 1
src/searchdconfig.cpp

@@ -120,7 +120,7 @@ CSphString FilenameBuilder_c::GetFullPath ( const CSphString & sName ) const
 	CSphString sPath = GetPathForNewIndex ( m_sIndex );
 
 	StrVec_t dFiles;
-	StringBuilder_c sNewValue = " ";
+	StringBuilder_c sNewValue {" "};
 
 	// we assume that path has been stripped before
 	StrVec_t dValues = sphSplit ( sName.cstr(), sName.Length(), " \t," );

+ 18 - 206
src/sphinxutils.cpp

@@ -1826,146 +1826,7 @@ namespace TimePrefixed
 //////////////////////////////////////////////////////////////////////////
 // CRASH REPORTING
 //////////////////////////////////////////////////////////////////////////
-inline static void Grow (char*, int) {};
-inline static void Grow ( StringBuilder_c &tBuilder, int iInc ) { tBuilder.GrowEnough ( iInc ); }
 
-inline static char* tail (char* p) { return p; }
-inline static char* tail ( StringBuilder_c &tBuilder ) { return tBuilder.end(); }
-
-template <typename Num, typename PCHAR>
-static void NtoA_T ( PCHAR* ppOutput, Num uVal, int iBase=10, int iWidth=0, int iPrec=0, char cFill=' ' )
-{
-	assert ( ppOutput );
-//	assert ( tail ( *ppOutput ) );
-	assert ( iWidth>=0 );
-	assert ( iPrec>=0 );
-	assert ( iBase>0 && iBase<=16);
-
-	const char cAllDigits[] = "fedcba9876543210123456789abcdef";
-	// point to the '0'. This hack allows to process negative numbers,
-	// since digit[x%10] for both x==2 and x==-2 is '2'.
-	const char * cDigits = cAllDigits+sizeof(cAllDigits)/2-1;
-	const char cZero = '0';
-	auto &pOutput = *ppOutput;
-
-	if ( !uVal )
-	{
-		if ( !iPrec && !iWidth )
-		{
-			*tail ( pOutput ) = cZero;
-			++pOutput;
-		} else
-		{
-			if ( !iPrec )
-				++iPrec;
-			else
-				cFill = ' ';
-
-			if ( iWidth )
-			{
-				if ( iWidth<iPrec )
-					iWidth = iPrec;
-				iWidth -= iPrec;
-			}
-
-			if ( iWidth>=0 )
-			{
-				Grow ( pOutput, iWidth );
-				memset ( tail ( pOutput ), cFill, iWidth );
-				pOutput += iWidth;
-			}
-
-			if ( iPrec>=0 )
-			{
-				Grow ( pOutput, iPrec );
-				memset ( tail ( pOutput ), cZero, iPrec );
-				pOutput += iPrec;
-			}
-		}
-		return;
-	}
-
-	const BYTE uMaxIndex = 31; // 20 digits for MAX_INT64 in decimal; let it be 31 (32 digits max).
-	char CBuf[uMaxIndex+1];
-	char *pRes = &CBuf[uMaxIndex];
-
-
-	BYTE uNegative = 0;
-	if ( uVal<0 )
-		++uNegative;
-
-	while ( uVal )
-	{
-		*pRes-- = cDigits [ uVal % iBase ];
-		uVal /= iBase;
-	}
-
-	auto uLen = (BYTE)( uMaxIndex - (pRes-CBuf) );
-	if (!uLen)
-		uNegative = 0;
-
-	if ( iPrec && iWidth && cFill==cZero)
-		cFill=' ';
-
-	if ( iWidth )
-		iWidth = iWidth - Max ( iPrec, uLen ) - uNegative;
-
-	if ( uNegative && cFill==cZero )
-	{
-		*tail ( pOutput ) = '-';
-		++pOutput;
-		uNegative=0;
-	}
-
-	if ( iWidth>=0 )
-	{
-		Grow ( pOutput, iWidth );
-		memset ( tail ( pOutput ), cFill, iWidth );
-		pOutput += iWidth;
-	}
-
-	if ( uNegative )
-	{
-		*tail ( pOutput ) = '-';
-		++pOutput;
-	}
-
-	if ( iPrec )
-		iPrec -= uLen;
-
-	if ( iPrec>=0 )
-	{
-		Grow ( pOutput, iPrec );
-		memset ( tail ( pOutput ), cZero, iPrec );
-		pOutput += iPrec;
-	}
-
-	Grow ( pOutput, uLen );
-	memcpy ( tail ( pOutput ), pRes+1, uLen );
-	pOutput+= uLen;
-}
-
-static const int nDividers = 10;
-static const int Dividers[nDividers] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
-
-template < typename Num, typename PCHAR >
-void IFtoA_T ( PCHAR * pOutput, Num nVal, int iPrec )
-{
-	assert ( iPrec<nDividers );
-	auto &pBegin = *pOutput;
-	if ( nVal<0 )
-	{
-		*tail ( pBegin ) = '-';
-		++pBegin;
-		nVal = -nVal;
-	}
-	auto iInt = nVal / Dividers[iPrec];
-	auto iFrac = nVal % Dividers[iPrec];
-	::NtoA_T ( &pBegin, iInt );
-	*tail ( pBegin ) = '.';
-	++pBegin;
-	::NtoA_T ( &pBegin, iFrac, 10, 0, iPrec, '0' );
-}
 
 namespace tmtoa {
 	using us_t = int64_t; // useconds
@@ -2030,7 +1891,7 @@ void TMtoA_T ( PCHAR* pOutput, int64_t nVal, int iPrec )
 	// correct sign, if necessary
 	if ( nVal<0 )
 	{
-		*tail ( pBegin ) = '-';
+		*Tail ( pBegin ) = '-';
 		++pBegin;
 		nVal = -nVal;
 	}
@@ -2072,7 +1933,7 @@ void TMtoA_T ( PCHAR* pOutput, int64_t nVal, int iPrec )
 			else if ( iPrec==1) nVal /= (iMul/10);
 			if ( nVal ) // 0. Stop printing
 			{
-				*tail ( pBegin ) = '.';
+				*Tail ( pBegin ) = '.';
 				++pBegin;
 				::NtoA_T ( &pBegin, nVal, 10, 0, iPrec, '0' );
 			}
@@ -2080,7 +1941,7 @@ void TMtoA_T ( PCHAR* pOutput, int64_t nVal, int iPrec )
 
 		// print specifier
 		Grow ( pBegin, tmtoa::iSufLens[iSpan] );
-		memcpy ( tail ( pBegin ), tmtoa::sSuffixes[iSpan], tmtoa::iSufLens[iSpan] );
+		memcpy ( Tail ( pBegin ), tmtoa::sSuffixes[iSpan], tmtoa::iSufLens[iSpan] );
 		pBegin += tmtoa::iSufLens[iSpan];
 
 		// all is done
@@ -2092,7 +1953,7 @@ void TMtoA_T ( PCHAR* pOutput, int64_t nVal, int iPrec )
 			return;
 
 		// print space before continue.
-		*tail ( pBegin ) = ' ';
+		*Tail ( pBegin ) = ' ';
 		++pBegin;
 
 		// go to next range
@@ -2109,7 +1970,7 @@ void TMStoA_T ( PCHAR* pOutput, int64_t nVal, int iPrec )
 	if ( !nVal )
 	{
 		Grow( pBegin, 5 );
-		memcpy( tail( pBegin ), "never", 5 );
+		memcpy( Tail( pBegin ), "never", 5 );
 		pBegin += 5;
 		return;
 	}
@@ -2120,73 +1981,24 @@ void TMStoA_T ( PCHAR* pOutput, int64_t nVal, int iPrec )
 		TMtoA_T ( pOutput, -iTimespan, iPrec );
 		// print specifier
 		Grow ( pBegin, 4 );
-		memcpy ( tail ( pBegin ), " ago", 4 );
+		memcpy ( Tail ( pBegin ), " ago", 4 );
 		pBegin += 4;
 	} else if (iTimespan>0)
 	{
 		Grow ( pBegin, 3 );
-		memcpy ( tail ( pBegin ), "in ", 3 );
+		memcpy ( Tail ( pBegin ), "in ", 3 );
 		pBegin += 3;
 		TMtoA_T ( pOutput, iTimespan, iPrec );
 	} else
 	{
 		Grow ( pBegin, 3 );
-		memcpy ( tail ( pBegin ), "now", 3 );
+		memcpy ( Tail ( pBegin ), "now", 3 );
 		pBegin += 3;
 	}
 }
 
-template <typename Num>
-inline static void NtoA ( char** ppOutput, Num uVal, int iBase = 10, int iWidth = 0, int iPrec = 0, char cFill = ' ' )
-{
-	NtoA_T ( ppOutput, uVal, iBase, iWidth, iPrec, cFill);
-}
-
 namespace sph {
 
-#define DECLARE_NUMTOA(NTOA,TYPE) \
-    template <> \
-	int NTOA ( char * pOutput, TYPE uVal, int iBase, int iWidth, int iPrec, char cFill ) \
-	{ \
-		auto pBegin = pOutput; \
-		::NtoA ( &pBegin, uVal, iBase, iWidth, iPrec, cFill ); \
-		return int ( pBegin - pOutput ); \
-	}
-
-	// unsigned
-	DECLARE_NUMTOA ( UItoA, unsigned int );
-	DECLARE_NUMTOA ( UItoA, unsigned long );
-	DECLARE_NUMTOA ( UItoA, unsigned long long);
-
-	// signed
-	DECLARE_NUMTOA ( ItoA, int );
-	DECLARE_NUMTOA ( ItoA, long );
-	DECLARE_NUMTOA ( ItoA, long long );
-
-	// universal
-	DECLARE_NUMTOA ( NtoA, unsigned int );
-	DECLARE_NUMTOA ( NtoA, unsigned long );
-	DECLARE_NUMTOA ( NtoA, unsigned long long );
-	DECLARE_NUMTOA ( NtoA, int );
-	DECLARE_NUMTOA ( NtoA, long );
-	DECLARE_NUMTOA ( NtoA, long long );
-
-#undef DECLARE_NUMTOA
-
-	int IFtoA ( char * pOutput, int nVal, int iPrec )
-	{
-		auto pBegin = pOutput;
-		IFtoA_T ( &pBegin, nVal, iPrec );
-		return int ( pBegin - pOutput );
-	}
-
-	int IFtoA ( char * pOutput, int64_t nVal, int iPrec )
-	{
-		auto pBegin = pOutput;
-		IFtoA_T ( &pBegin, nVal, iPrec );
-		return int ( pBegin - pOutput );
-	}
-
 static int SkipFmt64 ( const char * sFmt )
 {
 	// %lld %lli %llu
@@ -2222,7 +2034,7 @@ void vSprintf_T ( PCHAR * _pOutput, const char * sFmt, va_list ap )
 			{
 				auto uLen = strlen (sFmt-1);
 				Grow ( pOutput, (int) uLen );
-				memcpy ( tail ( pOutput ), sFmt-1, (int) uLen );
+				memcpy ( Tail ( pOutput ), sFmt-1, (int) uLen );
 				pOutput += (int) uLen;
 				sFmt+=uLen-1;
 				continue;
@@ -2232,7 +2044,7 @@ void vSprintf_T ( PCHAR * _pOutput, const char * sFmt, va_list ap )
 			if ( uLen )
 			{
 				Grow ( pOutput, (int)uLen );
-				memcpy ( tail ( pOutput ), sFmt - 1, (int)uLen );
+				memcpy ( Tail ( pOutput ), sFmt - 1, (int)uLen );
 				pOutput += uLen;
 				sFmt+=uLen;
 			}
@@ -2249,7 +2061,7 @@ void vSprintf_T ( PCHAR * _pOutput, const char * sFmt, va_list ap )
 		if ( c=='%' && state!=SNORMAL )
 		{
 			state = SNORMAL;
-			*tail ( pOutput ) = c;
+			*Tail ( pOutput ) = c;
 			++pOutput;
 			continue;
 		}
@@ -2309,17 +2121,17 @@ void vSprintf_T ( PCHAR * _pOutput, const char * sFmt, va_list ap )
 				Grow ( pOutput, (int) iWidth );
 				if ( iWidth && bHeadingSpace )
 				{
-					memset ( tail ( pOutput ), ' ', (int) iWidth );
+					memset ( Tail ( pOutput ), ' ', (int) iWidth );
 					pOutput += (int) iWidth;
 				}
 
 				Grow ( pOutput, (int) iValue );
-				memcpy ( tail ( pOutput ), pValue, iValue );
+				memcpy ( Tail ( pOutput ), pValue, iValue );
 				pOutput += (int) iValue;
 
 				if ( iWidth && !bHeadingSpace )
 				{
-					memset ( tail ( pOutput ), ' ', (int) iWidth );
+					memset ( Tail ( pOutput ), ' ', (int) iWidth );
 					pOutput += (int) iWidth;
 				}
 
@@ -2425,11 +2237,11 @@ void vSprintf_T ( PCHAR * _pOutput, const char * sFmt, va_list ap )
 					// invoke standard sprintf
 					char sFormat[32] = { 0 };
 					memcpy ( sFormat, pF, sFmt - pF );
-					pOutput += sprintf ( tail ( pOutput ), sFormat, fValue );
+					pOutput += sprintf ( Tail ( pOutput ), sFormat, fValue );
 				} else
 				{
 					// plain %f - output arbitrary 6 or 8 digits
-					pOutput += PrintVarFloat ( tail ( pOutput ), (float)fValue );
+					pOutput += PrintVarFloat ( Tail ( pOutput ), (float)fValue );
 					assert (( sFmt - pF )==2 );
 				}
 
@@ -2439,13 +2251,13 @@ void vSprintf_T ( PCHAR * _pOutput, const char * sFmt, va_list ap )
 
 		default:
 			state = SNORMAL;
-			*tail ( pOutput ) = c;
+			*Tail ( pOutput ) = c;
 			++pOutput;
 		}
 	}
 
 	// final zero
-	*tail ( pOutput ) = c;
+	*Tail ( pOutput ) = c;
 }
 
 	int vSprintf ( char * pOutput, const char * sFmt, va_list ap )

+ 0 - 11
src/sphinxutils.h

@@ -75,17 +75,6 @@ inline bool sphIsWild ( T c )
 }
 
 namespace sph {
-	/// my own converter unsigned to string. Instanciated for DWORD and uint64_t
-	template <typename Int> // signed
-	int ItoA ( char * pOutput, Int nVal, int iBase = 10, int iWidth = 0, int iPrec = 0, char cFill = ' ' );
-	template < typename UInt > // unsigned
-	int UItoA ( char * pOutput, UInt nVal, int iBase = 10, int iWidth = 0, int iPrec = 0, char cFill = ' ' );
-	template < typename Num > // let compiler deduce whether signed or unsigned...
-	int NtoA ( char * pOutput, Num nVal, int iBase = 10, int iWidth = 0, int iPrec = 0, char cFill = ' ' );
-
-	/// my own fixed-point floats. iPrec - num of digits after point. i.e. 100000, 3 -> 100.000
-	int IFtoA ( char * pOutput, int nVal, int iPrec = 3 );
-	int IFtoA ( char * pOutput, int64_t nVal, int iPrec = 6 );
 
 	/* Custom format specifiers for types:
 

+ 1 - 0
src/std/CMakeLists.txt

@@ -53,6 +53,7 @@ add_library ( manticore_std OBJECT
 		mem.h
 		mm.h
 		mutex.h
+		num_conv.h
 		openhash.h
 		orderedhash.h
 		orderedhash_impl.h

+ 29 - 0
src/std/num_conv.h

@@ -0,0 +1,29 @@
+//
+// Copyright (c) 2022, Manticore Software LTD (https://manticoresearch.com)
+// Copyright (c) 2001-2016, Andrew Aksyonoff
+// Copyright (c) 2008-2016, Sphinx Technologies Inc
+// All rights reserved
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License. You should have
+// received a copy of the GPL license along with this program; if you
+// did not, you can find it at http://www.gnu.org
+//
+
+#pragma once
+
+#include "ints.h"
+
+
+namespace sph
+{
+/// my own converter num to string.
+template<typename Num> // let compiler deduce whether signed or unsigned...
+int NtoA ( char* pOutput, Num nVal, int iBase = 10, int iWidth = 0, int iPrec = 0, char cFill = ' ' );
+
+/// my own fixed-point floats. iPrec - num of digits after point. i.e. 100000, 3 -> 100.000
+int IFtoA ( char* pOutput, int nVal, int iPrec = 3 );
+int IFtoA ( char* pOutput, int64_t nVal, int iPrec = 6 );
+}
+
+#include "num_conv_impl.h"

+ 182 - 0
src/std/num_conv_impl.h

@@ -0,0 +1,182 @@
+//
+// Copyright (c) 2022, Manticore Software LTD (https://manticoresearch.com)
+// Copyright (c) 2001-2016, Andrew Aksyonoff
+// Copyright (c) 2008-2016, Sphinx Technologies Inc
+// All rights reserved
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License. You should have
+// received a copy of the GPL license along with this program; if you
+// did not, you can find it at http://www.gnu.org
+//
+
+#include <cassert>
+#include <cstring>
+#include "generics.h"
+
+inline static void Grow ( char*, int ) {};
+inline static char* Tail ( char* p )
+{
+	return p;
+}
+
+template<typename Num, typename PCHAR>
+static void NtoA_T ( PCHAR* ppOutput, Num uVal, int iBase = 10, int iWidth = 0, int iPrec = 0, char cFill = ' ' )
+{
+	assert ( ppOutput );
+	//	assert ( tail ( *ppOutput ) );
+	assert ( iWidth >= 0 );
+	assert ( iPrec >= 0 );
+	assert ( iBase > 0 && iBase <= 16 );
+
+	const char cAllDigits[] = "fedcba9876543210123456789abcdef";
+	// point to the '0'. This hack allows to process negative numbers,
+	// since digit[x%10] for both x==2 and x==-2 is '2'.
+	const char* cDigits = cAllDigits + sizeof ( cAllDigits ) / 2 - 1;
+	const char cZero = '0';
+	auto& pOutput = *ppOutput;
+
+	if ( !uVal )
+	{
+		if ( !iPrec && !iWidth )
+		{
+			*Tail ( pOutput ) = cZero;
+			++pOutput;
+		} else
+		{
+			if ( !iPrec )
+				++iPrec;
+			else
+				cFill = ' ';
+
+			if ( iWidth )
+			{
+				if ( iWidth < iPrec )
+					iWidth = iPrec;
+				iWidth -= iPrec;
+			}
+
+			if ( iWidth >= 0 )
+			{
+				Grow ( pOutput, iWidth );
+				memset ( Tail ( pOutput ), cFill, iWidth );
+				pOutput += iWidth;
+			}
+
+			if ( iPrec >= 0 )
+			{
+				Grow ( pOutput, iPrec );
+				memset ( Tail ( pOutput ), cZero, iPrec );
+				pOutput += iPrec;
+			}
+		}
+		return;
+	}
+
+	const BYTE uMaxIndex = 31; // 20 digits for MAX_INT64 in decimal; let it be 31 (32 digits max).
+	char CBuf[uMaxIndex + 1];
+	char* pRes = &CBuf[uMaxIndex];
+
+
+	BYTE uNegative = 0;
+	if ( uVal < 0 )
+		++uNegative;
+
+	while ( uVal )
+	{
+		*pRes-- = cDigits[uVal % iBase];
+		uVal /= iBase;
+	}
+
+	auto uLen = (BYTE)( uMaxIndex - ( pRes - CBuf ) );
+	if ( !uLen )
+		uNegative = 0;
+
+	if ( iPrec && iWidth && cFill == cZero )
+		cFill = ' ';
+
+	if ( iWidth )
+		iWidth = iWidth - Max ( iPrec, uLen ) - uNegative;
+
+	if ( uNegative && cFill == cZero )
+	{
+		*Tail ( pOutput ) = '-';
+		++pOutput;
+		uNegative = 0;
+	}
+
+	if ( iWidth >= 0 )
+	{
+		Grow ( pOutput, iWidth );
+		memset ( Tail ( pOutput ), cFill, iWidth );
+		pOutput += iWidth;
+	}
+
+	if ( uNegative )
+	{
+		*Tail ( pOutput ) = '-';
+		++pOutput;
+	}
+
+	if ( iPrec )
+		iPrec -= uLen;
+
+	if ( iPrec >= 0 )
+	{
+		Grow ( pOutput, iPrec );
+		memset ( Tail ( pOutput ), cZero, iPrec );
+		pOutput += iPrec;
+	}
+
+	Grow ( pOutput, uLen );
+	memcpy ( Tail ( pOutput ), pRes + 1, uLen );
+	pOutput += uLen;
+}
+
+static const int nDividers = 10;
+static const int Dividers[nDividers] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
+
+template<typename Num, typename PCHAR>
+void IFtoA_T ( PCHAR* pOutput, Num nVal, int iPrec )
+{
+	assert ( iPrec < nDividers );
+	auto& pBegin = *pOutput;
+	if ( nVal < 0 )
+	{
+		*Tail ( pBegin ) = '-';
+		++pBegin;
+		nVal = -nVal;
+	}
+	auto iInt = nVal / Dividers[iPrec];
+	auto iFrac = nVal % Dividers[iPrec];
+	::NtoA_T ( &pBegin, iInt );
+	*Tail ( pBegin ) = '.';
+	++pBegin;
+	::NtoA_T ( &pBegin, iFrac, 10, 0, iPrec, '0' );
+}
+
+namespace sph {
+
+template<typename Num>
+int NtoA ( char* pOutput, Num nVal, int iBase, int iWidth, int iPrec, char cFill )
+{
+	auto pBegin = pOutput;
+	NtoA_T ( &pBegin, nVal, iBase, iWidth, iPrec, cFill );
+	return int ( pBegin - pOutput );
+}
+
+inline int IFtoA ( char* pOutput, int nVal, int iPrec )
+{
+	auto pBegin = pOutput;
+	IFtoA_T ( &pBegin, nVal, iPrec );
+	return int ( pBegin - pOutput );
+}
+
+inline int IFtoA ( char* pOutput, int64_t nVal, int iPrec )
+{
+	auto pBegin = pOutput;
+	IFtoA_T ( &pBegin, nVal, iPrec );
+	return int ( pBegin - pOutput );
+}
+
+}

+ 13 - 201
src/std/stringbuilder.cpp

@@ -18,6 +18,7 @@
 
 #include <cassert>
 #include <cstdarg>
+#include <cstdio>
 
 //////////////////////////////////////////////////////////////////////////
 /// StringBuilder implementation
@@ -241,178 +242,6 @@ void StringBuilder_c::MoveTo ( CSphString &sTarget )
 	NewBuffer ();
 }
 
-void StringBuilder_c::AppendRawChunk ( Str_t sText ) // append without any commas
-{
-	if ( !sText.second )
-		return;
-
-	GrowEnough ( sText.second + 1 ); // +1 because we'll put trailing \0 also
-
-	memcpy ( m_szBuffer + m_iUsed, sText.first, sText.second );
-	m_iUsed += sText.second;
-	m_szBuffer[m_iUsed] = '\0';
-}
-
-StringBuilder_c & StringBuilder_c::SkipNextComma()
-{
-	if ( !m_dDelimiters.IsEmpty() )
-		m_dDelimiters.Last().SkipNext ();
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::AppendName ( const char * sName, bool bQuoted )
-{
-	if ( !sName || !strlen ( sName ) )
-		return *this;
-
-	AppendChunk ( { sName, (int)strlen ( sName ) }, bQuoted ? '"' : '\0' );
-	GrowEnough(2);
-	m_szBuffer[m_iUsed] = ':';
-	m_szBuffer[m_iUsed+1] = '\0';
-	m_iUsed+=1;
-	return SkipNextComma ();
-}
-
-StringBuilder_c & StringBuilder_c::AppendChunk ( const Str_t& sChunk, char cQuote )
-{
-	if ( !sChunk.second )
-		return *this;
-
-	auto sComma = Delim();
-	int iQuote = cQuote!=0;
-
-	GrowEnough ( sChunk.second + sComma.second + iQuote + iQuote + 1 ); // +1 because we'll put trailing \0 also
-
-	if ( sComma.second )
-		memcpy ( m_szBuffer + m_iUsed, sComma.first, sComma.second );
-	if (iQuote)
-		m_szBuffer[m_iUsed +sComma.second] = cQuote;
-	memcpy ( m_szBuffer + m_iUsed +sComma.second + iQuote, sChunk.first, sChunk.second );
-	m_iUsed += sChunk.second+sComma.second+iQuote+iQuote;
-	if ( iQuote )
-		m_szBuffer[m_iUsed-1] = cQuote;
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::AppendString ( const CSphString &sText, char cQuote)
-{
-	return AppendChunk ( {sText.cstr (), sText.Length ()}, cQuote );
-}
-
-StringBuilder_c & StringBuilder_c::operator += ( const char * sText )
-{
-	if ( !sText || *sText=='\0' )
-		return *this;
-
-	return AppendChunk ( {sText, (int) strlen ( sText )} );
-}
-
-StringBuilder_c & StringBuilder_c::operator+= ( const Str_t& sChunk )
-{
-	return AppendChunk ( sChunk );
-}
-
-StringBuilder_c & StringBuilder_c::operator<< ( const VecTraits_T<char> &sText )
-{
-	if ( sText.IsEmpty () )
-		return *this;
-
-	return AppendChunk ( {sText.begin (), sText.GetLength ()} );
-}
-
-StringBuilder_c & StringBuilder_c::operator << ( const Str_t &sText )
-{
-	return AppendChunk ( sText );
-}
-
-StringBuilder_c& StringBuilder_c::operator << ( int iVal )
-{
-	InitAddPrefix();
-	GrowEnough(32);
-	m_iUsed += sph::NtoA( end(), iVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator << ( long iVal )
-{
-	InitAddPrefix();
-	GrowEnough(32);
-	m_iUsed += sph::NtoA( end(), iVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator << ( long long iVal )
-{
-	InitAddPrefix();
-	GrowEnough(32);
-	m_iUsed += sph::NtoA( end(), iVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator << ( unsigned int uVal )
-{
-	InitAddPrefix();
-	GrowEnough(32);
-	m_iUsed += sph::NtoA( end(), uVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator << ( unsigned long uVal )
-{
-	InitAddPrefix();
-	GrowEnough(32);
-	m_iUsed += sph::NtoA( end(), uVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator << ( unsigned long long uVal )
-{
-	InitAddPrefix();
-	GrowEnough(32);
-	m_iUsed += sph::NtoA( end(), uVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator<< ( float fVal )
-{
-	InitAddPrefix();
-	GrowEnough( 32 );
-	m_iUsed += sph::PrintVarFloat( end(), fVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator<< ( double fVal )
-{
-	InitAddPrefix();
-	GrowEnough( 32 );
-	m_iUsed += sprintf( end(), "%f", fVal );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c & StringBuilder_c::operator<< ( void * pVal )
-{
-	InitAddPrefix ();
-	GrowEnough ( 32 );
-	m_iUsed += sph::NtoA ( end (), reinterpret_cast<uintptr_t>(pVal), 16, sizeof(void*)*2 );
-	m_szBuffer[m_iUsed] = '\0';
-	return *this;
-}
-
-StringBuilder_c& StringBuilder_c::operator<< ( bool bVal )
-{
-	if ( bVal )
-		return AppendChunk ( { "true", 4 } );
-	return AppendChunk ( { "false", 5 } );
-}
 
 void StringBuilder_c::Grow ( int iLen )
 {
@@ -437,49 +266,32 @@ void StringBuilder_c::NewBuffer()
 	Clear ();
 }
 
-void StringBuilder_c::Clear()
-{
-	if ( m_szBuffer )
-		m_szBuffer[0] = '\0';
-	m_iUsed = 0;
-	m_dDelimiters.Reset();
-}
-
-
-void StringBuilder_c::NtoA ( DWORD uVal )
+StringBuilder_c& StringBuilder_c::operator<< ( double fVal )
 {
 	InitAddPrefix();
-
-	const int MAX_NUMERIC_STR = 64;
-	GrowEnough ( MAX_NUMERIC_STR+1 );
-
-	int iLen = sph::NtoA ( (char *)m_szBuffer + m_iUsed, uVal );
-	m_iUsed += iLen;
+	GrowEnough ( 32 );
+	m_iUsed += sprintf ( end(), "%f", fVal );
 	m_szBuffer[m_iUsed] = '\0';
+	return *this;
 }
 
-
-void StringBuilder_c::NtoA ( int64_t iVal )
+StringBuilder_c& StringBuilder_c::operator<< ( float fVal )
 {
 	InitAddPrefix();
-
-	const int MAX_NUMERIC_STR = 64;
-	GrowEnough ( MAX_NUMERIC_STR+1 );
-
-	int iLen = sph::NtoA ( (char *)m_szBuffer + m_iUsed, iVal );
-	m_iUsed += iLen;
+	GrowEnough ( 32 );
+	m_iUsed += sph::PrintVarFloat ( end(), fVal );
 	m_szBuffer[m_iUsed] = '\0';
+	return *this;
 }
 
-
 void StringBuilder_c::FtoA ( float fVal )
 {
 	InitAddPrefix();
 
 	const int MAX_NUMERIC_STR = 64;
-	GrowEnough ( MAX_NUMERIC_STR+1 );
+	GrowEnough ( MAX_NUMERIC_STR + 1 );
 
-	int iLen = sph::PrintVarFloat ( (char *) m_szBuffer + m_iUsed, fVal );
+	int iLen = sph::PrintVarFloat ( (char*)m_szBuffer + m_iUsed, fVal );
 	m_iUsed += iLen;
 	m_szBuffer[m_iUsed] = '\0';
 }
@@ -490,9 +302,9 @@ void StringBuilder_c::DtoA ( double fVal )
 	InitAddPrefix();
 
 	const int MAX_NUMERIC_STR = 64;
-	GrowEnough ( MAX_NUMERIC_STR+1 );
+	GrowEnough ( MAX_NUMERIC_STR + 1 );
 
-	int iLen = sph::PrintVarDouble ( (char *) m_szBuffer + m_iUsed, fVal );
+	int iLen = sph::PrintVarDouble ( (char*)m_szBuffer + m_iUsed, fVal );
 	m_iUsed += iLen;
 	m_szBuffer[m_iUsed] = '\0';
 }

+ 38 - 8
src/std/stringbuilder.h

@@ -37,7 +37,7 @@ class StringBuilder_c : public ISphNoncopyable
 
 public:
 		// creates and m.b. start block
-						StringBuilder_c ( const char * sDel = nullptr, const char * sPref = nullptr, const char * sTerm = nullptr );
+						explicit StringBuilder_c ( const char * sDel = nullptr, const char * sPref = nullptr, const char * sTerm = nullptr );
 						StringBuilder_c ( StringBuilder_c&& rhs ) noexcept;
 						~StringBuilder_c ();
 
@@ -46,6 +46,9 @@ public:
 	// reset to initial state
 	void				Clear();
 
+	// rewind to initial state, but don't clear the commas
+	void 				Rewind();
+
 	// get current build value
 	const char *		cstr() const { return m_szBuffer ? m_szBuffer : ""; }
 	explicit operator	CSphString() const { return { cstr() }; }
@@ -64,16 +67,29 @@ public:
 	StringBuilder_c &	AppendString ( const CSphString & sText, char cQuote = '\0' );
 
 	StringBuilder_c &	operator = ( StringBuilder_c rhs ) noexcept;
-	StringBuilder_c &	operator += ( const char * sText );
-	StringBuilder_c &	operator += ( const Str_t& sChunk );
+	template<typename T>
+	StringBuilder_c &	operator += ( const T * pVal );
+	StringBuilder_c &	operator += ( const char* sText );
+	StringBuilder_c &	operator << ( const Str_t& sChunk );
 	StringBuilder_c &	operator << ( const VecTraits_T<char> &sText );
-	StringBuilder_c &	operator << ( const Str_t &sText );
-	StringBuilder_c &	operator << ( const char * sText ) { return *this += sText; }
-	StringBuilder_c &	operator << ( char cChar ) { return *this += {&cChar,1}; }
+
+	/*
+	 * Two guys below distinguishes `const char*` param from `const char[]`.
+	 * First one implies strlen() and uses it
+	 * Second implies length to be now at compile time, and avoids strlen().
+	 * So, << "bar" - will be faster, as we know size of that literal in compile time.
+	 * Look at TEST ( functions, stringbuilder_templated ) for experiments.
+	 */
+	template<size_t N>
+	StringBuilder_c &	operator << ( char const(& sLiteral)[N] ) { return *this << Str_t { sLiteral, N-1 }; }
+	template<typename CHAR>
+	StringBuilder_c &	operator << ( const CHAR * const& sText ) { return *this += sText; }
+
+	StringBuilder_c &	operator << ( char cChar ) { return *this << Str_t {&cChar,1}; }
 	StringBuilder_c &	operator << ( const CSphString &sText ) { return *this += sText.cstr (); }
 	StringBuilder_c &	operator << ( const CSphVariant &sText )	{ return *this += sText.cstr (); }
 	StringBuilder_c &	operator << ( const StringBuilder_c &sText )	{ return *this << (Str_t) sText; }
-	StringBuilder_c &	operator << ( Comma_c& dComma ) { return *this += dComma; }
+	StringBuilder_c &	operator << ( Comma_c& dComma ) { return *this << (Str_t)dComma; }
 
 	StringBuilder_c &	operator << ( int iVal );
 	StringBuilder_c &	operator << ( long iVal );
@@ -85,7 +101,6 @@ public:
 
 	StringBuilder_c &	operator << ( float fVal );
 	StringBuilder_c &	operator << ( double fVal );
-	StringBuilder_c &	operator << ( void* pVal );
 	StringBuilder_c &	operator << ( bool bVal );
 
 	// support for sph::Sprintf - emulate POD 'char*'
@@ -106,6 +121,10 @@ public:
 	StringBuilder_c &	vSprintf ( const char * sTemplate, va_list ap );
 	StringBuilder_c &	Sprintf ( const char * sTemplate, ... );
 
+	// arbitrary output all params according to their << implementations (inlined in compile time).
+	template<typename... Params>
+	StringBuilder_c &	Sprint ( const Params&... tParams );
+
 	// comma manipulations
 	// start new comma block; return index of it (for future possible reference in FinishBlocks())
 	int					StartBlock ( const char * sDel = ", ", const char * sPref = nullptr, const char * sTerm = nullptr );
@@ -183,5 +202,16 @@ StringBuilder_c& operator<< ( StringBuilder_c& tOut, const CSphNamedInt& tValue
 StringBuilder_c& operator<< ( StringBuilder_c& tOut, timespan_t tVal );
 StringBuilder_c& operator<< ( StringBuilder_c& tOut, timestamp_t tVal );
 
+// helpers
+inline void Grow ( StringBuilder_c& tBuilder, int iInc )
+{
+	tBuilder.GrowEnough ( iInc );
+}
+
+inline char* Tail ( StringBuilder_c& tBuilder )
+{
+	return tBuilder.end();
+}
+
 
 #include "stringbuilder_impl.h"

+ 199 - 0
src/std/stringbuilder_impl.h

@@ -10,6 +10,199 @@
 // did not, you can find it at http://www.gnu.org
 //
 
+#include "num_conv.h"
+
+inline void StringBuilder_c::AppendRawChunk ( Str_t sText ) // append without any commas
+{
+	if ( !sText.second )
+		return;
+
+	GrowEnough ( sText.second + 1 ); // +1 because we'll put trailing \0 also
+
+	memcpy ( m_szBuffer + m_iUsed, sText.first, sText.second );
+	m_iUsed += sText.second;
+	m_szBuffer[m_iUsed] = '\0';
+}
+
+inline StringBuilder_c& StringBuilder_c::SkipNextComma()
+{
+	if ( !m_dDelimiters.IsEmpty() )
+		m_dDelimiters.Last().SkipNext();
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::AppendName ( const char* sName, bool bQuoted )
+{
+	if ( !sName || !strlen ( sName ) )
+		return *this;
+
+	AppendChunk ( { sName, (int)strlen ( sName ) }, bQuoted ? '"' : '\0' );
+	GrowEnough ( 2 );
+	m_szBuffer[m_iUsed] = ':';
+	m_szBuffer[m_iUsed + 1] = '\0';
+	m_iUsed += 1;
+	return SkipNextComma();
+}
+
+inline StringBuilder_c& StringBuilder_c::AppendChunk ( const Str_t& sChunk, char cQuote )
+{
+	if ( !sChunk.second )
+		return *this;
+
+	auto sComma = Delim();
+	int iQuote = cQuote != 0;
+
+	GrowEnough ( sChunk.second + sComma.second + iQuote + iQuote + 1 ); // +1 because we'll put trailing \0 also
+
+	if ( sComma.second )
+		memcpy ( m_szBuffer + m_iUsed, sComma.first, sComma.second );
+	if ( iQuote )
+		m_szBuffer[m_iUsed + sComma.second] = cQuote;
+	memcpy ( m_szBuffer + m_iUsed + sComma.second + iQuote, sChunk.first, sChunk.second );
+	m_iUsed += sChunk.second + sComma.second + iQuote + iQuote;
+	if ( iQuote )
+		m_szBuffer[m_iUsed - 1] = cQuote;
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::AppendString ( const CSphString& sText, char cQuote )
+{
+	return AppendChunk ( { sText.cstr(), sText.Length() }, cQuote );
+}
+
+inline StringBuilder_c& StringBuilder_c::operator+= ( const char* sText )
+{
+	if ( !sText || *sText == '\0' )
+		return *this;
+
+	return AppendChunk ( { sText, (int)strlen ( sText ) } );
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( const Str_t& sChunk )
+{
+	return AppendChunk ( sChunk );
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( const VecTraits_T<char>& sText )
+{
+	if ( sText.IsEmpty() )
+		return *this;
+
+	return AppendChunk ( { sText.begin(), sText.GetLength() } );
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( int iVal )
+{
+	InitAddPrefix();
+	GrowEnough ( 32 );
+	m_iUsed += sph::NtoA ( end(), iVal );
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( long iVal )
+{
+	InitAddPrefix();
+	GrowEnough ( 32 );
+	m_iUsed += sph::NtoA ( end(), iVal );
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( long long iVal )
+{
+	InitAddPrefix();
+	GrowEnough ( 32 );
+	m_iUsed += sph::NtoA ( end(), iVal );
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( unsigned int uVal )
+{
+	InitAddPrefix();
+	GrowEnough ( 32 );
+	m_iUsed += sph::NtoA ( end(), uVal );
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( unsigned long uVal )
+{
+	InitAddPrefix();
+	GrowEnough ( 32 );
+	m_iUsed += sph::NtoA ( end(), uVal );
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( unsigned long long uVal )
+{
+	InitAddPrefix();
+	GrowEnough ( 32 );
+	m_iUsed += sph::NtoA ( end(), uVal );
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+template<typename T>
+StringBuilder_c& StringBuilder_c::operator+= ( const T* pVal )
+{
+	InitAddPrefix();
+	AppendRawChunk ( FROMS ( "0x" ) );
+	GrowEnough ( sizeof ( void* ) * 2 );
+	m_iUsed += sph::NtoA ( end(), reinterpret_cast<uintptr_t> ( pVal ), 16, sizeof ( void* ) * 2, 0, '0' );
+	m_szBuffer[m_iUsed] = '\0';
+	return *this;
+}
+
+inline StringBuilder_c& StringBuilder_c::operator<< ( bool bVal )
+{
+	if ( bVal )
+		return *this << "true";
+	return *this << "false";
+}
+
+inline void StringBuilder_c::Clear()
+{
+	Rewind();
+	m_dDelimiters.Reset();
+}
+
+inline void StringBuilder_c::Rewind()
+{
+	if ( m_szBuffer )
+		m_szBuffer[0] = '\0';
+	m_iUsed = 0;
+}
+
+
+inline void StringBuilder_c::NtoA ( DWORD uVal )
+{
+	InitAddPrefix();
+
+	const int MAX_NUMERIC_STR = 64;
+	GrowEnough ( MAX_NUMERIC_STR + 1 );
+
+	int iLen = sph::NtoA ( (char*)m_szBuffer + m_iUsed, uVal );
+	m_iUsed += iLen;
+	m_szBuffer[m_iUsed] = '\0';
+}
+
+
+inline void StringBuilder_c::NtoA ( int64_t iVal )
+{
+	InitAddPrefix();
+
+	const int MAX_NUMERIC_STR = 64;
+	GrowEnough ( MAX_NUMERIC_STR + 1 );
+
+	int iLen = sph::NtoA ( (char*)m_szBuffer + m_iUsed, iVal );
+	m_iUsed += iLen;
+	m_szBuffer[m_iUsed] = '\0';
+}
+
 // shrink, if necessary, to be able to fit at least iLen more chars
 inline void StringBuilder_c::GrowEnough ( int iLen )
 {
@@ -58,6 +251,12 @@ inline void StringBuilder_c::LazyComma_c::Swap ( LazyComma_c & rhs ) noexcept
 	::Swap ( m_bSkipNext, rhs.m_bSkipNext );
 }
 
+template<typename... Params>
+StringBuilder_c& StringBuilder_c::Sprint ( const Params&... tValues )
+{
+	(void)std::initializer_list<int> { ( *this << tValues, 0 )... };
+	return *this;
+}
 
 inline StringBuilder_c& operator<< ( StringBuilder_c& tOut, const CSphNamedInt& tValue )
 {