Difference between revisions of "Printf"

From Second Life Wiki
Jump to navigation Jump to search
m (→‎Code: fix() function was improved, works with any valid precision and is way faster.)
m (Replaced <source> with <syntaxhighlight>; added categorisation and styles for published scripts; added reference to author)
 
Line 1: Line 1:
{{LSL Header}}
{{RightToc}}
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.
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.
<source lang="lsl2">
<syntaxhighlight lang="lsl2">
%[align][width][.precision]flag
%[align][width][.precision]flag
</source>
</syntaxhighlight>
Align supports -, =, + for left, center, and right align.
Align supports -, =, + for left, center, and right align.


Line 14: Line 16:


= Code =
= Code =
<source lang="lsl2">
<syntaxhighlight lang="lsl2">
string pad(string value, integer width, string alignment)
string pad(string value, integer width, string alignment)
{
{
Line 117: Line 119:
     }
     }
}
}
</source>
</syntaxhighlight>


= Example with testing functions included =
= Example with testing functions included =
<source lang="lsl2">
<syntaxhighlight lang="lsl2">
integer TRIALS = 5000;
integer TRIALS = 5000;
string pad(string value, integer width, string alignment)
string pad(string value, integer width, string alignment)
Line 381: Line 383:
     }
     }
}
}
</source>
</syntaxhighlight>
= Notes / Copyright =
= Notes / Copyright =
You are free to replicate or otherwise use this code to your heart's content.
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.
If you have suggestions for optimization, [[User_talk:Pazako Karu|I'm all ears]].
 
{{LSLC|Library}}

Latest revision as of 00:02, 31 May 2023

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.