12-06-2003 17:20
An implementation of C's sprintf() data formatting function in LSL.

CODE
// Format specifiers are very similar to the C "printf()" specifiers
//
// %[flags][width][.precision]type
//
// Flags can be the following:
// - Left-align
// ' ' Prefix with a blank if the output is positive
//
// Width is the minimum number of characters printed. Ignored for string, key types.
// For vector and rotation types, the value is the total number of characters, not
// total per component. If needed, output will be prefixed with blanks to reach
// the specified width.
//
// Precision
// - If an integer is being printed, the minimum number of digits. Will be padded with
// leading zeroes to reach the minimum. Will not truncate.
// - If a float is being printed, specifies the number of digits after the decimal point
// - If a vector or rotation is being printed, specifies the number of digits after the
// decimal point for each component.
// - Ignored for string, key types.
//
// Type
// d Integer
// f Float
// v Vector
// r Rotation
// k Key, lowercase hex values
// K Key, uppercase hex values
// s String

integer SPRINTF_LEFT_ALIGN = 1;
integer SPRINTF_RIGHT_ALIGN = 2;

integer isdigit(string char)
{
if(char == "0" || char == "1" || char == "2" || char == "3" || char == "4" || char == "5" || char == "6" || char == "7" || char == "8" || char == "9")
return TRUE;
else
return FALSE;
}

string sprintf_round(float value, integer places)
{
string temp = (string)llRound(value*llPow(10, places));
string temp2 = llGetSubString(temp, -places, -1);
string temp3 = llGetSubString(temp, 0, -(places+1));
return temp3 + "." + temp2;
}

string sprintf_width(string value, integer size, integer align)
{
integer padding = size - llStringLength(value);
string pad = "";
while(padding > 0)
{
pad = pad + " ";
padding--;
}
if(align == SPRINTF_LEFT_ALIGN)
return value + pad;
else
return pad + value;
}

string sprintf_float(float value, integer precision, integer prefix)
{
string val = sprintf_round(value, precision);
if((value >= 0.0) && (prefix != FALSE))
{
val = " " + val;
}
return val;
}

string sprintf_decimal(integer value, integer precision, integer prefix)
{
string ret = (string)value;
integer pad = precision - llStringLength(ret);
while(pad > 0)
{
ret = "0" + ret;
pad--;
}
if(value >= 0 && prefix)
{
ret = " " + ret;
}
return ret;
}

string sprintf_vector(vector value, integer precision)
{
return "<" + sprintf_round(value.x, precision) + ", " + sprintf_round(value.y, precision) + ", " + sprintf_round(value.z, precision) + ">";
}

string sprintf_rotation(rotation value, integer precision)
{
return "<" + sprintf_round(value.x, precision) + ", " + sprintf_round(value.y, precision) + ", " + sprintf_round(value.z, precision) + ", " + sprintf_round(value.s, precision) + ">";
}

// Takes the format string starting after the %, and a list containing the parameter to print
//
// Returns a list containing the formatted string and the number of characters consumed
list sprintf_parse_format(string format, list param)
{
integer i;
integer align = SPRINTF_RIGHT_ALIGN;
integer prefix = FALSE;
integer precision = 6;
integer width = 0;
string cur_char;

for(i = 0; i < llStringLength(format); i++)
{
cur_char = llGetSubString(format, i, i);
// If the first character is a %, return a literal "%"
if(cur_char == "%")
{
return ["%", 1];
}
else if(cur_char == "-")
{
align = SPRINTF_LEFT_ALIGN;
}
else if(cur_char == " ")
{
prefix = TRUE;
}
else
{
jump SPRINTF_EXIT_1 ;
}
}
@SPRINTF_EXIT_1;
// Parse width
for(i = i; i < llStringLength(format); i++)
{
cur_char = llGetSubString(format, i, i);
if(isdigit(cur_char))
{
width = 10*width + (integer)cur_char;
}
else
{
jump SPRINTF_EXIT_2;
}
}
@SPRINTF_EXIT_2;
// Parse precision
if(llGetSubString(format, i, i) == "." && isdigit(llGetSubString(format, i+1, i+1)))
{
precision = 0;
for(i = i+1; i < llStringLength(format); i++)
{
cur_char = llGetSubString(format, i, i);
if(isdigit(cur_char))
{
precision = 10*precision + (integer)cur_char;
}
else
{
jump SPRINTF_EXIT_3;
}
}
}
@SPRINTF_EXIT_3;
// Parse type
cur_char = llGetSubString(format, i, i);
if(cur_char == "d")
{
return [sprintf_width(sprintf_decimal(llList2Integer(param, 0), precision, prefix), width, align), i+1];
}
else if(cur_char == "f")
{
return [sprintf_width(sprintf_float(llList2Float(param, 0), precision, prefix), width, align), i+1];
}
else if(cur_char == "v")
{
return [sprintf_width(sprintf_vector(llList2Vector(param, 0), precision), width, align), i+1];
}
else if(cur_char == "r")
{
return [sprintf_width(sprintf_rotation(llList2Rot(param, 0), precision), width, align), i+1];
}
else if(cur_char == "k")
{
return [sprintf_width((string)llList2Key(param, 0), width, align), i+1];
}
else if(cur_char == "K")
{
return [sprintf_width(llToUpper((string)llList2Key(param, 0)), width, align), i+1];
}
else if(cur_char == "s")
{
return [sprintf_width(llList2String(param, 0), width, align), i+1];
}
else
{
return [llGetSubString(format, 0, 0), 1];
}
}

string sprintf(string format, list params)
{
integer i;
integer cur_par = 0;
string result;
string cur_char;

//Go through the string one character at a time
for(i = 0; i < llStringLength(format); i++)
{
cur_char = llGetSubString(format, i, i);
if(cur_char == "%")
{
list res = sprintf_parse_format(llGetSubString(format, i+1, -1), llList2List(params, cur_par, cur_par));
result = result + llList2String(res, 0);
i = i + llList2Integer(res, 1);
cur_par++;
}
else
{
result = result + cur_char;
}
}
return result;
}