Printf
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
To the best of my ability, this replicates the printf() function as it exists in other languages. Generally speaking, you write a string with some format specifiers, and then send it the variables, rather than having to concatenate a bunch of strings and conversions together.
%[align][width][.precision]flag
Align supports -, =, + for left, center, and right align.
Width will pad spaces if the result string doesn't fit. It doesn't crop one.
Precision limits decimal spaces of a float. 0 makes it an integer
Flag determines input type, with %s string, %d integer, %f float, %v vector, and %r rotation supported.
A vector or rotation will have pad/align/precision applied to each of its sub-parts, rather than the whole thing. This just made more sense to me. And yes technically, this is sprintf, but I did add a printf function to ownersay the results.
Code
string pad(string value, integer width, string alignment)
{
integer len = llStringLength(value);
if (width > len)
{
string spacers = " ";
while (llStringLength(spacers) < width-len) // This works fastest with a maxed width constant, but can be dynamic
spacers += spacers; // Expand spacers while necessary
spacers = llGetSubString(spacers, 0, width-len-1);
if (alignment == "-") // left align
return value+spacers;
else if (alignment == "+") // right align
return spacers+value;
else if (alignment == "=") // center align
return llInsertString(spacers, (width-len)/2, value);
}
return value;
}
string fix(float value, integer precision)
{//Set the decimal precision of a float. Technically only affects decimal places
if (precision < 1) precision = -1;
if (precision > 6) precision = 6;
return llGetSubString((string)value, 0, precision-7);
}
printf(string input, list values)
{
llOwnerSay(sprintf(input, values));
}
string sprintf(string input, list values)
{
integer iValue; // Which value will replace
integer iValues = llGetListLength(values); // Number of values to replace
for (iValue = 0; iValue < iValues; iValue++)
{
integer index = llSubStringIndex(input, "%"); // "%=99.9v " tag found
if (~index)
{
string buffer = llGetSubString(input, index, index+12); // A few chars past % into a buffer
integer bufferLen = llSubStringIndex(buffer, " "); // A space or newline ends buffer
if (!~bufferLen)
bufferLen = llSubStringIndex(buffer, "\n");
buffer = llGetSubString(buffer, 0, bufferLen); // Trim buffer to new length
integer alignIndex;
string alignment = "+"; // Default to right align
if (~llSubStringIndex(buffer, "=")) // Center align
alignment = "=";
else if (~llSubStringIndex(buffer, "-")) // Left align
alignment = "-";
else if (~llSubStringIndex(buffer, "+")) // Right align
alignment = "+";
else
alignIndex--; // There is no symbol, move index back for (integer) typecast later //*/
integer width = (integer)llGetSubString(buffer, 2+alignIndex, -1);
integer precision = 0;
integer dot = llSubStringIndex(buffer, "."); // Location of period, if any
if (~dot) // If there is a dot, the integer which follows is our precision
precision = (integer)llGetSubString(buffer, 1+dot, -1);
string formatted; // Result of formatting to replace into string
if (~llSubStringIndex(buffer, "f")) // if type == float
{
float value = llList2Float(values, iValue);
formatted = fix(value, precision);
}
else if (~llSubStringIndex(buffer, "v")) // if type == vector
{
vector value = (vector)llList2String(values, iValue);
formatted = (string)["<", pad(fix(value.x, precision), width, alignment), ",",
pad(fix(value.y, precision), width, alignment), ",",
pad(fix(value.z, precision), width, alignment), ">"];
}
else if (~llSubStringIndex(buffer, "r")) // if type == rotation
{
rotation value = (rotation)llList2String(values, iValue);
formatted = (string)["<", pad(fix(value.x, precision), width, alignment), ",",
pad(fix(value.y, precision), width, alignment), ",",
pad(fix(value.z, precision), width, alignment), ",",
pad(fix(value.s, precision), width, alignment), ">"];
} // Because of typecasting in a list, "s" and "d" are identical, so catch all at the end
else formatted = llList2String(values, iValue);
// insert formatted value back into source string, and a space
input = llInsertString(llDeleteSubString(input, index, index+bufferLen),index, pad(formatted, width, alignment)+" ");
// cant use this, faster but we only want to replace one value, this does all.
//input = llDumpList2String(llParseStringKeepNulls(input, [llGetSubString(input, index, index+bufferLen)], []), pad(formatted, width, alignment)+" ");
}
}
return input;
}
default
{
state_entry()
{
printf("This script has been running for %0.2f seconds.", [llGetTime()]);
}
}
Example with testing functions included
integer TRIALS = 5000;
string pad(string value, integer width, string alignment)
{
integer len = llStringLength(value);
if (width > len)
{
string spacers = " ";
while (llStringLength(spacers) < width-len) // This works fastest with a maxed width constant, but can be dynamic
spacers += spacers; // Expand spacers while necessary
spacers = llGetSubString(spacers, 0, width-len-1);
if (alignment == "-") // left align
return value+spacers;
else if (alignment == "+") // right align
return spacers+value;
else if (alignment == "=") // center align
return llInsertString(spacers, (width-len)/2, value);
}
return value;
}
string fix(float value, integer precision)
{//Set the decimal precision of a float. Technically only affects decimal places
integer pre = (integer)value;
// WARNING !! Rounding is occurring here: Not rounding left 69.1 as 69.099998 => 69.0
integer post = llRound(llPow(10,precision)*(value-pre)); // In linux, rounding is expected behavior
if (precision > 0)
return (string)pre + "." + llGetSubString((string)post+"0000000", 0, precision-1);
return (string)pre;
}
printf(string input, list values)
{
llOwnerSay(sprintf(input, values));
}
string sprintf(string input, list values)
{
integer iValue; // Which value will replace
integer iValues = llGetListLength(values); // Number of values to replace
for (iValue = 0; iValue < iValues; iValue++)
{
integer index = llSubStringIndex(input, "%"); // "%=99.9v " tag found
if (~index)
{
string buffer = llGetSubString(input, index, index+12); // A few chars past % into a buffer
integer bufferLen = llSubStringIndex(buffer, " "); // A space or newline ends buffer
if (!~bufferLen)
bufferLen = llSubStringIndex(buffer, "\n");
buffer = llGetSubString(buffer, 0, bufferLen); // Trim buffer to new length
integer alignIndex;
string alignment = "+"; // Default to right align
if (~llSubStringIndex(buffer, "=")) // Center align
alignment = "=";
else if (~llSubStringIndex(buffer, "-")) // Left align
alignment = "-";
else if (~llSubStringIndex(buffer, "+")) // Right align
alignment = "+";
else
alignIndex--; // There is no symbol, move index back for (integer) typecast later //*/
integer width = (integer)llGetSubString(buffer, 2+alignIndex, -1);
integer precision = 0;
integer dot = llSubStringIndex(buffer, "."); // Location of period, if any
if (~dot) // If there is a dot, the integer which follows is our precision
precision = (integer)llGetSubString(buffer, 1+dot, -1);
string formatted; // Result of formatting to replace into string
if (~llSubStringIndex(buffer, "f")) // if type == float
{
float value = llList2Float(values, iValue);
formatted = fix(value, precision);
}
else if (~llSubStringIndex(buffer, "v")) // if type == vector
{
vector value = (vector)llList2String(values, iValue);
formatted = (string)["<", pad(fix(value.x, precision), width, alignment), ",",
pad(fix(value.y, precision), width, alignment), ",",
pad(fix(value.z, precision), width, alignment), ">"];
}
else if (~llSubStringIndex(buffer, "r")) // if type == rotation
{
rotation value = (rotation)llList2String(values, iValue);
formatted = (string)["<", pad(fix(value.x, precision), width, alignment), ",",
pad(fix(value.y, precision), width, alignment), ",",
pad(fix(value.z, precision), width, alignment), ",",
pad(fix(value.s, precision), width, alignment), ">"];
} // Because of typecasting in a list, "s" and "d" are identical, so catch all at the end
else formatted = llList2String(values, iValue);
// insert formatted value back into source string, and a space
input = llInsertString(llDeleteSubString(input, index, index+bufferLen),index, pad(formatted, width, alignment)+" ");
// cant use this, faster but we only want to replace one value, this does all.
//input = llDumpList2String(llParseStringKeepNulls(input, [llGetSubString(input, index, index+bufferLen)], []), pad(formatted, width, alignment)+" ");
}
}
return input;
}
testPad() // string pad(string value, integer width, string alignment)
{ // 5000 trials completed in 7.71 seconds, 1.5 ms each.
string TEST_STRING = "ABC";
integer WIDTH_LIMIT = 60; // Max whitespace for padding test
integer i;
integer j;
float t1 = llGetTime();
for (i = 0; i < TRIALS; i+= 3) // One for each align, so skip 3
{
j = i % WIDTH_LIMIT;
pad(TEST_STRING, j+0, "-"); // Left align
pad(TEST_STRING, j+1, "="); // Center align
pad(TEST_STRING, j+2, "+"); // Right align
}
float t2 = llGetTime();
llOwnerSay(sprintf("%d trials completed in %0.2f seconds, %0.3f ms each.", [TRIALS, t2-t1, 1E3*(t2-t1)/TRIALS]));
}
testFix() // string fix(float value, integer precision)
{ // 5000 trials completed in 23.67 seconds, 4.7 ms each.
integer i;
integer j;
float t1 = llGetTime();
for (i = 0; i < TRIALS; i++)
for (j = 0; j <= 6; j++) // Precision is meaningless past 6 in lsl
fix(i, j);
float t2 = llGetTime();
llOwnerSay(sprintf("%d trials completed in %0.2f seconds, %0.3f ms each.", [TRIALS, t2-t1, 1E3*(t2-t1)/TRIALS]));
}
testSprintf()
{
float t1 = llGetTime();
list tests = [ sprintf("%12.5r", [ZERO_ROTATION]),
sprintf("%12.0r", [ZERO_ROTATION]),
sprintf("%0.5r", [ZERO_ROTATION]),
sprintf("%12.r", [ZERO_ROTATION]),
sprintf("%.5r", [ZERO_ROTATION]),
sprintf("%.r", [ZERO_ROTATION]),
sprintf("%r", [ZERO_ROTATION]),
sprintf("%-12r", [ZERO_ROTATION]),
sprintf("%=12r", [ZERO_ROTATION]),
sprintf("%+12r", [ZERO_ROTATION]),
sprintf("%12.5v", [ZERO_VECTOR]),
sprintf("%12.0v", [ZERO_VECTOR]),
sprintf("%0.5v", [ZERO_VECTOR]),
sprintf("%12.v", [ZERO_VECTOR]),
sprintf("%.5v", [ZERO_VECTOR]),
sprintf("%.v", [ZERO_VECTOR]),
sprintf("%v", [ZERO_VECTOR]),
sprintf("%-12v", [ZERO_VECTOR]),
sprintf("%=12v", [ZERO_VECTOR]),
sprintf("%+12v", [ZERO_VECTOR]),
sprintf("%12.5f", [0]),
sprintf("%12.0f", [0]),
sprintf("%0.5f", [0]),
sprintf("%12.f", [0]),
sprintf("%.5f", [0]),
sprintf("%.f", [0]),
sprintf("%f", [0]),
sprintf("%-12f", [0]),
sprintf("%=12f", [0]),
sprintf("%+12f", [0]),
sprintf("%12.5d", [0]),
sprintf("%12.0d", [0]),
sprintf("%0.5d", [0]),
sprintf("%12.d", [0]),
sprintf("%.5d", [0]),
sprintf("%.d", [0]),
sprintf("%d", [0]),
sprintf("%-12d", [0]),
sprintf("%=12d", [0]),
sprintf("%+12d", [0]),
sprintf("%12.5s", ["Test"]),
sprintf("%12.0s", ["Test"]),
sprintf("%0.5s", ["Test"]),
sprintf("%12.s", ["Test"]),
sprintf("%.5s", ["Test"]),
sprintf("%.s", ["Test"]),
sprintf("%12s", ["Test"]),
sprintf("%-12s", ["Test"]),
sprintf("%=12s", ["Test"]),
sprintf("%+12s", ["Test"])];
integer i;
integer len = llGetListLength(tests);
string out;
for (i = 0; i < len; i++)
{
if (!(llList2String(tests, i) == llList2String(results, i)))
out += "\n"+sprintf("Test #%d, %s != %s", [i, llList2String(tests, i), llList2String(results, i)]);
}
float t2 = llGetTime();
if (out)
llOwnerSay("Failures: " + out);
else llOwnerSay(sprintf("%d tests passed in %.2f seconds with an average run time of %.2f ms",[len, t2-t1, 1000*(t2-t1)/len]));
}
// Answers to the test
list results = [// rots
"< 0.00000, 0.00000, 0.00000, 1.00000> ",
"< 0, 0, 0, 1> ",
"<0.00000,0.00000,0.00000,1.00000> ",
"< 0, 0, 0, 1> ",
"<0.00000,0.00000,0.00000,1.00000> ",
"<0,0,0,1> ",
"<0,0,0,1> ",
"<0 ,0 ,0 ,1 > ",
"< 0 , 0 , 0 , 1 > ",
"< 0, 0, 0, 1> ",
// vectors
"< 0.00000, 0.00000, 0.00000> ",
"< 0, 0, 0> ",
"<0.00000,0.00000,0.00000> ",
"< 0, 0, 0> ",
"<0.00000,0.00000,0.00000> ",
"<0,0,0> ",
"<0,0,0> ",
"<0 ,0 ,0 > ",
"< 0 , 0 , 0 > ",
"< 0, 0, 0> ",
// Floats
" 0.00000 ",
" 0 ",
"0.00000 ",
" 0 ",
"0.00000 ",
"0 ",
"0 ",
"0 ",
" 0 ",
" 0 ",
// Integers
" 0 ",
" 0 ",
"0 ",
" 0 ",
"0 ",
"0 ",
"0 ",
"0 ",
" 0 ",
" 0 ",
" Test ",
" Test ",
"Test ",
" Test ",
"Test ",
"Test ",
" Test ",
"Test ",
" Test ",
" Test "];
default
{
state_entry()
{
testPad();
testFix();
testSprintf();
printf("This script has been running for %0.2f seconds.", [llGetTime()]);
}
}
Notes / Copyright
You are free to replicate or otherwise use this code to your heart's content. If you have suggestions for optimization, I'm all ears.