Julia equivalent to C's vsnprintf

I’m try to translate some C code to Julia, but I’m a little stumped with how to tackle this. First, there is this macro as well as two global variables:

// Shared buffer for variadic text
static char* fc_buffer = NULL;
static unsigned int fc_buffer_size = 1024;

#define FC_EXTRACT_VARARGS(buffer, start_args) \
{ \
    va_list lst; \
    va_start(lst, start_args); \
    vsnprintf(buffer, fc_buffer_size, start_args, lst); \
    va_end(lst); \
}

and here is an example of it being used:

FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...)
{
    if(formatted_text == NULL || font == NULL)
        return FC_MakeRect(x, y, 0, 0);

    FC_EXTRACT_VARARGS(fc_buffer, formatted_text);

    set_color_for_all_caches(font, font->default_color);

    return FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer);
}

It’s from a C file called “SDL_FontCache.c”, and the function is supposed to allow you to draw formated text on the screen (a graphical printf). For example:

FC_Draw(font, renderer, 0, 0, "This is %s.\n It works.", "example text"); 

I think what is throwing me is (1) I’m unfamiliar with vsnprintf(), (2) I’m unfamiliar with the whole va_list macro stuff. I’m sure there is a simple answer using a varargs function and the @sprintf macro, but I’m stumped.

Yeah, the macro @sprintf doesn’t work for dynamic things like this. With Julia v1.6 or later, you can use the Printf.format function:

julia> using Printf

julia> f(str, args...) = Printf.format(Printf.Format(str), args...)
FC_Draw (generic function with 1 method)

julia> f("%s: %f", "hello", 1.234)
"hello: 1.234000"

That get’s half-way there, but I still need to be able to send the args as a lists to Printf.format

I can do this:

julia> f(str, args...) = Printf.format(Printf.Format(str), args...)
f (generic function with 1 method)

julia> f("%s, %s", "cat", "dog")
"cat, dog"

julia> f("%s, %s %d", "cat", "dog", 2)
"cat, dog 2"

But I need it to do something like this:

zyz = f("%s, %s %d", [ "cat", "dog", 2] )
ERROR: ArgumentError: Number of format specifiers and number of provided args differ: 3 != 1

I guess I could just build a new string by iterating through the format string until I hit a “%” (followed by a char or two and a space), grab the next item off the argument list and sprintf()-it onto the string I’m building, but I was hoping there might be a less brute-force way,

If you want to accept lists instead of individual arguments in f, you can just omit the ... on the left-hand side:

julia> g(str, args) = Printf.format(Printf.Format(str), args...)
g (generic function with 1 method)

julia> g("%s, %s %d", ["cat", "dog", 2])
"cat, dog 2"
5 Likes

I ended up doing this:

using Printf

function stringParser(formatString::String, arguments::Array)
	formatters = ['s', 'd', 'f']
	done = false
	myString = ""

	sprintf(str, args...) = Printf.format(Printf.Format(str), args...)

	stringLength = length(formatString)
	i = 1
	argIndex = 1

	while !done
		c = formatString[i]
		if c != '%'
			myString = myString * c
		else
			j = 0
			subString = ""
			done2 = false
			while !done2
				c2 = formatString[i+j]
				if c2 in formatters
					done2 = true
				end
				subString = subString * c2
				j += 1
			end
			println("subString = ", subString,"\n")
			formattedString = sprintf(subString, arguments[argIndex])
			myString = myString * formattedString
			argIndex += 1
			i = i + j - 1
		end
		i += 1
		if i > stringLength
			done = true
		end
	end
	return myString
end
#-------------------------------------------------------------------
output = stringParser("My name is %s. I am %2.1f years old", ["Steve", 99.92])
println(output)

…Which gives me this for an answer:

subString = %s

subString = %2.1f

My name is Steve. I am 99.9 years old

Thanks for the Printf.format(). That knowledge is priceless.

As far as I can tell @ericphanson 's one liner does the same and without bugs. why not use that?

1 Like

Doh! I didn’t see @ericphanson’s one liner. Indeed, that is much more elegant!

1 Like