Printf

From Second Life Wiki
Revision as of 00:02, 31 May 2023 by Gwyneth Llewelyn (talk | contribs) (Replaced <source> with <syntaxhighlight>; added categorisation and styles for published scripts; added reference to author)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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.