www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Formatting a string on a variadic parameter function without using GC

reply David G. Maziero <dgmdavid gmail.com> writes:
Hi. I think it's the first time I post here.
I've been flirting with D for some time, but now I decided to 
start a little project.

Consider the following function:

void RenderText( FontBMP font, int x, int y, const char* text )
{
	SDL_Rect rect1, rect2;
	rect2.x = x;
	rect2.y = y;
	rect2.w = font.width;
	rect2.h = font.height;
	rect1.w = font.width;
	rect1.h = font.height;
	for( int r=0; text[r]!='\0'; ++r )
	{
		char letter = text[r];
		rect1.x = font.width*(letter%font.horizontal_count);
		rect1.y = font.height*(letter/font.horizontal_count);
		if( letter>=33 ) SDL_RenderCopy( g_renderer, font.texture, 
&rect1, &rect2 );
		rect2.x += font.spacing_x;
		if( letter==10 )
		{
			rect2.x = x;
			rect2.y += font.spacing_y;
		}
	}
}

And the use code:

	char[256] text;
	sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", 
player.position, player.speed );
	RenderText( font, 0, 0, cast(char*)text );

I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));" 
but I want to avoid GC entirely.
The ideal scenario is to have something like "void RenderText( 
/*etc*/, const char* fmt, ... )" but I can't seem to figure a way 
to pass the variadic parameters to sprintf.

Any ideas?
Thanks in advance.
Mar 01 2016
parent reply Mike Parker <aldacron gmail.com> writes:
On Wednesday, 2 March 2016 at 01:39:13 UTC, David G. Maziero 
wrote:

 Consider the following function:

 void RenderText( FontBMP font, int x, int y, const char* text )
 {
 	for( int r=0; text[r]!='\0'; ++r )
 	{
You're asking for trouble here. There's no guarantee that any D string is going to be null terminated. String literals are, but beyond that, all bets are off.
 	char[256] text;
 	sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", 
 player.position, player.speed );
Instead of sprintf, look into using std.format.sformat [1] (see below). It's equivalent to std.format.format, but allows you to provide a buffer.
 	RenderText( font, 0, 0, cast(char*)text );
It's considered bad form to cast an array to a pointer like this. If you need a pointer to an array, just use the ptr property: text.ptr. Always be aware of the null terminator situation, though, when passing to C.
 I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));"
Won't compile without using the .ptr property (or casting, which again, you shouldn't do). Moreover, you run into the null terminator problem. "FPS :" will be null terminated because it's a literal, but the string produced by concatentating it with the result of to!string would not be.
 but I want to avoid GC entirely.
 The ideal scenario is to have something like "void RenderText( 
 /*etc*/, const char* fmt, ... )" but I can't seem to figure a 
 way to pass the variadic parameters to sprintf.

 Any ideas?
With sformat, something like this (not tested): import std.format; char buf[1024]; auto fmt = sformat(buf, "Foo: %s", 42); if(fmt.length < buf.length) buf[fmt.length] = 0; else buf[$-1] = 0; RenderText(blah, blah, buf.ptr); sformat returns a slice of the buffer containing the formatted string. You can use its length to guarantee the buffer is null terminated.
Mar 01 2016
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Wednesday, 2 March 2016 at 04:12:13 UTC, Mike Parker wrote:

 char buf[1024];
Ugh. And the proper declaration in D: char[1024] buf;
Mar 01 2016
prev sibling next sibling parent reply David G. Maziero <dgmdavid gmail.com> writes:
Yes, I'm aware of the null-termination thing. I might have pasted 
code that I already changed.

But I already messed with sformat, and it seems that it does use 
the GC. I've put  nogc in RenderText, and the compiler says 
sformat uses GC, so I don't know.

But the thing is, I don't want to build the string before calling 
RenderText, I want RenderText to do it.
Mar 01 2016
parent reply David G. Maziero <dgmdavid gmail.com> writes:
I forgot to add that the "RenderText(font,0,0,"FPS: 
"~to!string(fps));" was an older version where it wasn't const 
char *, but string. And I was using a foreach, so no 
null-termination. But that's beyond the point of not using GC.
Mar 01 2016
parent reply David G. Maziero <dgmdavid gmail.com> writes:
I figured out what I wanted. Thanks for your answer Mike, sorry 
to bother you though.

Here's the result:

void RenderText( FontBMP font, int x, int y, const char* text, 
... )
{
	SDL_Rect rect1, rect2;
	rect2.x = x;
	rect2.y = y;
	rect2.w = font.width;
	rect2.h = font.height;
	rect1.w = font.width;
	rect1.h = font.height;

	char[256] buff;
	va_list	ap;
	va_start( ap, text );
	sprintf( buff.ptr, text, ap );
	va_end( ap );

	for( int r=0; buff[r]!='\0'; ++r )
	{
		char letter = buff[r];
		rect1.x = font.width*(letter%font.horizontal_count);
		rect1.y = font.height*(letter/font.horizontal_count);
		if( letter>=33 ) SDL_RenderCopy( g_renderer, font.texture, 
&rect1, &rect2 );
		rect2.x += font.spacing_x;
		if( letter==10 )
		{
			rect2.x = x;
			rect2.y += font.spacing_y;
		}
	}
}

Since the "string" is built by sprintf, it'll be null-terminated.

Sorry for posting without doing more research. I didn't realise I 
could use va_start/va_end just like in C.
Mar 01 2016
next sibling parent David G. Maziero <dgmdavid gmail.com> writes:
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero 
wrote:
 	char[256] buff;
 	va_list	ap;
 	va_start( ap, text );
 	sprintf( buff.ptr, text, ap );
 	va_end( ap );
Sorry again, where it reads "sprintf" should be "vsprintf".
Mar 01 2016
prev sibling next sibling parent David G. Maziero <dgmdavid gmail.com> writes:
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero 
wrote:
 void RenderText( FontBMP font, int x, int y, const char* text,
Just one more correction for future reference, RenderText should be
 extern(C) void RenderText...
in order for it to work correctly with va_start/etc.
Mar 02 2016
prev sibling parent cym13 <cpicard openmailbox.org> writes:
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero 
wrote:
 I figured out what I wanted. Thanks for your answer Mike, sorry 
 to bother you though.

 [...]
Even null-terminated be sure to profile your loop, for isn't garanteed to be faster than foreach at all and foreach is definitely safer.
Mar 02 2016
prev sibling parent reply Luis <luis.panadero gmail.com> writes:
On Wednesday, 2 March 2016 at 04:12:13 UTC, Mike Parker wrote:
 On Wednesday, 2 March 2016 at 01:39:13 UTC, David G. Maziero 
 wrote:

 Consider the following function:

 void RenderText( FontBMP font, int x, int y, const char* text )
 {
 	for( int r=0; text[r]!='\0'; ++r )
 	{
You're asking for trouble here. There's no guarantee that any D string is going to be null terminated. String literals are, but beyond that, all bets are off.
 	char[256] text;
 	sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", 
 player.position, player.speed );
Instead of sprintf, look into using std.format.sformat [1] (see below). It's equivalent to std.format.format, but allows you to provide a buffer.
 	RenderText( font, 0, 0, cast(char*)text );
It's considered bad form to cast an array to a pointer like this. If you need a pointer to an array, just use the ptr property: text.ptr. Always be aware of the null terminator situation, though, when passing to C.
 I could simply do "RenderText(font,0,0,"FPS: 
 "~to!string(fps));"
Won't compile without using the .ptr property (or casting, which again, you shouldn't do). Moreover, you run into the null terminator problem. "FPS :" will be null terminated because it's a literal, but the string produced by concatentating it with the result of to!string would not be.
Read https://dlang.org/spec/arrays.html#strings and try to use std.string.toStringz
Mar 02 2016
parent Mike Parker <aldacron gmail.com> writes:
On Wednesday, 2 March 2016 at 10:57:35 UTC, Luis wrote:

 Read https://dlang.org/spec/arrays.html#strings and try to use 
 std.string.toStringz 

Yes, but that's not what you use when you want to avoid allocation.
Mar 02 2016