Difference between revisions of "Chatbot"

From Second Life Wiki
Jump to navigation Jump to search
m ("mark item for sale price L$10" (to make microcredit donation easy), were the rights)
m (<lsl> tag to <source>)
 
(20 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{LSL Header}}
== Introduction ==
This script lets you chat some new example LSL code on a channel, and then see this script compile and run that code for you.
This script lets you chat some new example LSL code on a channel, and then see this script compile and run that code for you.


This script works like the [[Prefix_Calculator|Prefix Calculator]] script, but lets you ask for more than arithmetic while working with data types other than fixed point numbers.
This script chats back the intermediate and final return values at you, like the [[Prefix_Calculator|Prefix Calculator]] script does. This script also lets you work with data types other than fixed point numbers and with functions beyond arithmetic.
 
If you're new, you might prefer to explore by editing one line of a short script of the object, clicking Save to run once, and clicking Reset to run again indefinitely many times. Or you might like to edit and Save in the Inventory > Scripts folder and drag to run a new copy when you please. The [[Hello_Avatar|Hello Avatar]] article illustrates those approaches to learning LSL.


If you're new, you might prefer to edit one line of a short script, click Save to run once, click Reset to run again indefinitely many times, as explained by the [[Hello_Avatar|Hello Avatar]] article.
NOTE:  To run this demo, you must place the script at the bottom of the page into the object you wish to run it on.  Take some time to look at some of the different values and what they can do.  Particularly, take note of the "if/then" states and functions, as well as the "else if" functions.


== The Demo ==
== The Demo ==


For example, you can chat the LSL commands to make the default wood box translucent and bouncy and then kick it around and spin it, as follows ...
Remember that wood box that you Create by default inside SL (i.e., "in world")?


Twiddle the red, green, and blue intensity, also the "alpha" opacity/ transparency:
This script lets you chat LSL commands to make that box translucent and bouncy and then kick that box around and spin that box, as follows ...


<pre>
Chat to twiddle the red, green, and blue intensity, also the "alpha" opacity/ transparency:
 
<source lang="lsl2">
/7 llSetColor(<0.3, 0.3, 0.3>, ALL_SIDES); // darken
/7 llSetColor(<0.3, 0.3, 0.3>, ALL_SIDES); // darken
/7 llSetColor(<1, 1, 1>, ALL_SIDES); // lighten
/7 llSetColor(<1, 1, 1>, ALL_SIDES); // lighten
/7 llSetAlpha(0.7, ALL_SIDES); // make translucent
/7 llSetAlpha(0.7, ALL_SIDES); // make translucent
</pre>
</source>


Twiddle the label of the object running the script:
Chat to twiddle the label of the object running the script:


<pre>
<source lang="lsl2">
/7 llSetText("look at me green", <0.0, 1.0, 0.0>, 1.0); // label
/7 llSetText("look at me green", <0.0, 1.0, 0.0>, 1.0); // label
/7 llSetText("look at me black", <0.0, 0.0, 0.0>, 1.0); // label differently
/7 llSetText("look at me black", <0.0, 0.0, 0.0>, 1.0); // label differently
/7 llSetText("", <0.0, 0.0, 0.0>, 1.0); // do not label
/7 llSetText("", <0.0, 0.0, 0.0>, 1.0); // do not label
</pre>
</source>


Move and rotate while not physical, then kick and spin while physical and bouncy.
Chat to move and rotate while not physical, then kick and spin while physical and bouncy:


<pre>
<source lang="lsl2">
/7 llSetStatus(STATUS_PHYSICS, FALSE);
/7 llSetStatus(STATUS_PHYSICS, FALSE); llSleep(0.1);
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetPos("-"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport back down the Z axis
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, -2.1>)); // teleport back down the Z axis
/7 llSetLocalRot(llEuler2Rot(<0.0, 0.0, PI_BY_TWO>))); // face one way
/7 llSetLocalRot(llRotBetween(<1, 0, 0>, llGetSunDirection())); // turn the East face to the Sun
/7 llSetLocalRot(llEuler2Rot(ZERO_ROTATION)); // face another way
/7 llSetLocalRot(llEuler2Rot(ZERO_VECTOR)); // turn the East face to the East
/7 llSetStatus(STATUS_PHYSICS, TRUE);
/7 llSetStatus(STATUS_PHYSICS, TRUE); llSleep(0.1);
/7 llSetBuoyancy(0.9); // resist gravity, but don't float
/7 llSetBuoyancy(0.9); // resist gravity, but don't float
/7 llApplyImpulse(<0.0, 0.0, 1.0>, TRUE); // advance along the Z axis
/7 llApplyImpulse(<0.0, 0.0, 1.0>, TRUE); // advance along the Z axis
/7 llApplyRotationalImpulse(<0.0, 0.0, 3.0>, TRUE); // yaw about the Z axis
/7 llApplyRotationalImpulse(<0.0, 0.0, 3.0>, TRUE); // yaw about the Z axis
/7 llSetStatus(STATUS_PHYSICS, FALSE); llSetStatus(STATUS_PHYSICS, TRUE); // zero rot inertia
/7 llSetStatus(STATUS_PHYSICS, FALSE); llSetStatus(STATUS_PHYSICS, TRUE); llSleep(0.1); // zero rot inertia
</pre>
</source>


Poke around inside the object running the script:
Chat to ask the object running the script what it knows of itself and avatars:


<pre>
<source lang="lsl2">
/7 llGetAgentSize(llGetLinkKey(llGetNumberOfPrims())) // often not ZERO_VECTOR while avatar sits
/7 llGetAgentSize(llGetLinkKey(llGetNumberOfPrims())) // often not ZERO_VECTOR while avatar sits
/7 ZERO_VECTOR, FALSE, TRUE, STATUS_PHYSICS, PI // reveal some named code values
/7 llKey2Name(llGetLinkKey(llGetNumberOfPrims())) // often the name of the sitting avatar
</pre>
/7 llRequestAgentData(llGetOwner(), DATA_BORN); // the data-of-birth of the owning avatar
/7 ZERO_VECTOR, FALSE, TRUE, STATUS_PHYSICS, PI // some named code values
</source>


Chat a question for you the object's owner to answer (and then chat the answer that you chose):
Chat to present you with a menu of chat messages for you to send by clicking your one choice:


<pre>
<source lang="lsl2">
/7 llDialog(llGetOwner(), "A clarifying demo?", ["No", "Yes"], 7); // chat some Q & A
/7 llDialog(llGetOwner(), "A clarifying demo?", ["No", "Yes"], 7); // chat some Q & A
</pre>
/7 llDialog(llGetOwner(), "Choose an arc:", ["PI_BY_TWO", "PI", "TWO_PI"], 7); // chat some Q & A
</source>
 
Chat to ask how much memory exists (in the task of the script of the object) that you never have yet filled with allocations of byte code, stack, or heap:
 
<source lang="lsl2">
/7 llGetFreeMemory()
</source>


== Your Experience ==
== Your Experience ==


The LSL commands quoted above often do work as described in their // comments. The commands work if chatted in the order shown. The commands work if chatted at the usual wood box built by default in the SL GUI (0.5 x 0.5 x 0.5 m).
The LSL commands quoted above often do work as described in their // comments.
 
The commands work if chatted in the order shown. The commands work if chatted at the usual wood box built by default (0.5 x 0.5 x 0.5 m) by the Create choice on the ground menu in the SL GUI.


To see how the weight and shape of an object influence its response to commands that require STATUS_PHYSICS TRUE, try sending these commands to other objects.
To see how the weight and shape of an object influence its response to commands that require STATUS_PHYSICS TRUE, try sending these commands to other objects.
Line 61: Line 80:
To see how force multiplies, try adding this script more than once to an object.
To see how force multiplies, try adding this script more than once to an object.


To keep this article short, this script understands only some of the language that may interest you. We hope you'll find it easy to teach this script to understand whichever part of the LSL language you next want to explore. Just by trying, you can see that this script doesn't understand the less commonly useful library function names, arithmetic operators, code constant names, control flow keywords, punctuation marks, etc. Nor does this script understand any of your own function names, until you add those functions to this script.
To keep this article short, this script understands only some of the LSL language that may interest you. We hope you'll find it easy to teach this script to understand whichever part of the LSL language you next want to explore. Just by trying, you can see that this script doesn't understand the less commonly useful library function names, arithmetic operators, code constant names, control flow keywords, punctuation marks, etc. Nor does this script understand any of your own function names, until you add those functions to this script.


As you first try speaking more of the LSL language, please don't trust this script too much. If you find you get confused over what this script thinks an LSL command means, please consider slowing down enough to design a small one-off experimental script to show you what the SL GUI thinks the command means. When you find a difference between what the SL GUI thinks and what this script thinks, that's a bug. We welcome you blogging your experience into the [[Talk:Chatbot|discussion]] tab of this article, to help us track bugs, to help us dream up new tutorial demoes, to encourage our work here, etc.
As you first try speaking more of the LSL language, please don't trust this script too much. If you find you get confused over what this script thinks an LSL command means, please consider slowing down enough to design a small one-off experimental script to show you what the SL GUI thinks the command means. When you find a difference between what the SL GUI thinks and what this script thinks, that's a bug. To keep this article short, this script has many bugs of that kind: this script misunderstands much misspelled LSL, and this script misunderstands much correct LSL.
 
We welcome you blogging your experience into the [[Talk:Chatbot|discussion]] tab of this article, to help us track bugs, to help us dream up new tutorial demoes, to encourage our work here, etc.


Enjoy,
Enjoy,


P.S. [[User:Ppaatt Lynagh|Ppaatt Lynagh]] launched this tutorial project in 2007-09. "Allow anyone to copy", "next owner can copy & modify", and "mark item for sale price L$10" (to make microcredit donation easy), were the rights on Pat's copies of this script in SL, as of 2007-09-06.
P.S.
 
[[User:Ppaatt Lynagh|Ppaatt Lynagh]] launched this tutorial project in 2007-09.
 
Ppaatt develops on Mac OS X. You can help by testing Linux or Windows and reporting your experience into  the [[Talk:Chatbot|discussion]] tab of this article.
 
"Allow anyone to copy", "next owner can copy & modify", and "mark item for sale price L$10" were the rights on Pat's copies of this script in SL, as of 2007-09-06.
 
== Related Articles ==
 
[[Hello_Avatar]] - Examples of visually powerful single lines of code.
 
[[Separate_Words]] - Substitute a call to the separateWords function in place of a call to llParseString2List whenever you have more than 8 separators or more than 8 spacers.


P.P.S. The code of the sample script follows:
[[Slice_List_String_Etc]] - Return empty lists and strings when expected, while slicing lists or strings.


== The Script ==
== The Thousand Lines of Script ==


<pre>
<source lang="lsl2">
// 2007-09-06 http://wiki.secondlife.com/wiki/Chatbot
// 2007-09-25 http://wiki.secondlife.com/wiki/Chatbot
//
//
// Compile and run the Lsl you type on a channel,
// Compile and run the Lsl you type on a channel,
// faster than you can thru the 2007-08 SL GUI,
// faster than you can thru the 2007-08 SL GUI,
// thus "chat with your 'bot" while it runs this script.
// thus "chat with your 'bot" while it runs this script.
//
// Run well in Windows too by never cascading too many else-if.
//
//


Line 89: Line 124:
integer theShouldEcho = TRUE;
integer theShouldEcho = TRUE;


// Describe the language of an LSL expression without infix operators.
// Describe the language of an LSL expression without infix operators, string escapes, etc.


string escape = "\\";
string lf = "\n";
string quote = "\"";
string quote = "\"";
list separators = ["\t", " ", ",", ";"];
string escape = "\\";//"
list spacers = [quote, escape, "(", ")", "/", "<", ">", "[", "]"];
 
list spacers = [quote, "(", ")", "<", ">", "[", "]", "/", "*", "%", escape];
 
list separators()
{
    string tab = llUnescapeURL("%09"); // != "\t"
    string cr = llUnescapeURL("%0D"); // != "\r"
    return [tab, lf, cr, " ", ",", ";"];
}
 
list types = ["integer", "float", "key", "vector", "rotation", "list"];
list types = ["integer", "float", "key", "vector", "rotation", "list"];


Line 117: Line 161:


     STATUS_PHYSICS,
     STATUS_PHYSICS,
    DATA_BORN,
    DATA_NAME,
    DATA_ONLINE,
    DATA_PAYINFO,
    DATA_SIM_POS,
    DATA_SIM_RATING,
    DATA_SIM_STATUS,


     0
     0
Line 141: Line 193:


     "STATUS_PHYSICS",
     "STATUS_PHYSICS",
    "DATA_BORN",
    "DATA_NAME",
    "DATA_ONLINE",
    "DATA_PAYINFO",
    "DATA_SIM_POS",
    "DATA_SIM_RATING",
    "DATA_SIM_STATUS",


     "0"
     "0"
Line 255: Line 315:


     list ps = parameters;
     list ps = parameters;
   
    // Suicide on command
   
    if ("llDie" == callable) // ()
    {
        llDie(); // CAUTION -- this call deletes this script without saving changes -- CAUTION
    }


     // Accept verbose prefix arithmetic such as { "+"(vec1, vec2) }
     // Return a list of one result of some verbose prefix arithmetic such as { "+"(vec1, vec2) }


     if ("<>" == callable)
     if ("<>" == callable)
Line 283: Line 350:
     }
     }


     // Return a list of results to say callable meaningful and returns results
     // Return a list of one result of any type, for some meaningful verbs
    // Or fall thru to say callable meaningful but returns no result
       
 
     if ("llEscapeURL" == callable) // url
     if ("llApplyImpulse" == callable) // (force, local)
     {
     {
         llApplyImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
         return [llEscapeURL(llList2String(ps, 0))];
     }
     }
     else if ("llApplyRotationalImpulse" == callable) // (force, local)
     else if ("llEuler2Rot" == callable) // (v)
     {
     {
         llApplyRotationalImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
         return [llEuler2Rot(llList2Vector(ps, 0))];
     }
     }
     else if ("llDie" == callable) // ()
     else if ("llGetAgentInfo" == callable) // (id)
     {
     {
         return [llDie()]; // CAUTION -- this call deletes this script without saving changes -- CAUTION
         return [llGetAgentInfo(llList2Key(ps, 0))];
     }
     }
     else if ("llDialog" == callable) // (avatar, message, buttons, channel)
     else if ("llGetAgentSize" == callable) // (id)
     {
     {
         llDialog(llList2Key(ps, 0),
         return [llGetAgentSize(llList2Key(ps, 0))];
            llList2String(ps, 1), list2ListEntry(ps, 2), llList2Integer(ps, 3));
     }
     }
     else if ("llEuler2Rot" == callable) // (v)
     else if ("llGetFreeMemory" == callable) // ()
     {
     {
         return [llEuler2Rot(llList2Vector(ps, 0))];
         return [llGetFreeMemory()];
    }
    else if ("llGetAgentSize" == callable) // (id)
    {
        return [llGetAgentSize(llList2Key(ps, 0))];
     }
     }
   
     else if ("llGetLinkKey" == callable) // (linknum)
     else if ("llGetLinkKey" == callable) // (linknum)
     {
     {
Line 331: Line 393:
         return [llGetLocalRot()];
         return [llGetLocalRot()];
     }
     }
     else if ("llGetlRot" == callable) // ()
 
    // "ERROR : Syntax Error" rejects another cascading "else" here, in Windows SL
 
    if ("llGetRegionName" == callable) // ()
    {
        return [llGetRegionName()];
    }
     else if ("llGetRot" == callable) // ()
     {
     {
         return [llGetRot()];
         return [llGetRot()];
     }
     }
     else if ("llListRandomize" == callable) // (src, stride)
     else if ("llGetSunDirection" == callable) // ()
    {
        return [llGetSunDirection()];
    }
    else if ("llKey2Name" == callable) // id
    {
        return [llKey2Name(llList2Key(ps, 0))];
    }
    else if ("llRequestAgentData" == callable) // (id, data)
     {
     {
         return "[" + llListRandomize(list2ListEntry(ps, 0), llList2Integer(ps, 1)) + "]";
         return [llRequestAgentData(llList2Key(ps, 0), llList2Integer(ps, 1))];
    }
   
    else if ("llRequestSimulatorData" == callable) // (simulator, data)
    {
        return [llRequestSimulatorData(llList2String(ps, 0), llList2Integer(ps, 1))];
     }
     }
     else if ("llRot2Euler" == callable) // (q)
     else if ("llRot2Euler" == callable) // (q)
     {
     {
         return [llRot2Euler(llList2Rot(ps, 0))];
         return [llRot2Euler(llList2Rot(ps, 0))];
    }
    else if ("llRotBetween" == callable) // (v1, v2)
    {
        return [llRotBetween(llList2Vector(ps, 0), llList2Vector(ps, 1))];
    }
    else if ("llUnescapeURL" == callable) // url
    {
        return [llUnescapeURL(llList2String(ps, 0))];
    }
    else if ("llVecNorm" == callable) // v
    {
        return [llVecNorm(llList2Vector(ps, 0))];
    }
    // Obey some meaningful verbs that return no result
    integer meaningful = TRUE;
   
    if ("llApplyImpulse" == callable) // (force, local)
    {
        llApplyImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llApplyRotationalImpulse" == callable) //a (force, local)
    {
        llApplyRotationalImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llDialog" == callable) // (avatar, message, buttons, channel)
    {
        llDialog(llList2Key(ps, 0),
            llList2String(ps, 1), list2ListEntry(ps, 2), llList2Integer(ps, 3));
     }
     }
     else if ("llSetAlpha" == callable) // (alpha, face)
     else if ("llSetAlpha" == callable) // (alpha, face)
Line 351: Line 463:
         llSetBuoyancy(llList2Float(ps, 0));
         llSetBuoyancy(llList2Float(ps, 0));
     }
     }
   
     else if ("llSetColor" == callable) // (color, face)
     else if ("llSetColor" == callable) // (color, face)
     {
     {
Line 367: Line 480:
         llSetRot(llList2Rot(ps, 0));
         llSetRot(llList2Rot(ps, 0));
     }
     }
    else if ("llSetScale" == callable) // (scale)
    {
        llSetScale(llList2Vector(ps, 0));
    }
   
     else if ("llSetStatus" == callable) // (status, value)
     else if ("llSetStatus" == callable) // (status, value)
     {
     {
Line 374: Line 492:
     {
     {
         llSetText(llList2String(ps, 0), llList2Vector(ps, 1), llList2Float(ps, 2));
         llSetText(llList2String(ps, 0), llList2Vector(ps, 1), llList2Float(ps, 2));
    }
    else if ("llSitTarget" == callable) // (offset, rot)
    {
        llSitTarget(llList2Vector(ps, 0), llList2Rot(ps, 1));
     }
     }
     else if ("llSleep" == callable) // (sec)
     else if ("llSleep" == callable) // (sec)
Line 379: Line 501:
         llSleep(llList2Float(ps, 0));
         llSleep(llList2Float(ps, 0));
     }
     }
    else
    {
        meaningful = FALSE;
    }
    // Return an empty list if callable returns no result
    if (meaningful)
    {
        return []; // FIXME: indistinguishable from callable meaningless
    }
    // Return an empty list if callable meaningless
      
      
    // Return an empty result if callable meaningless or returns no result
   
     return [];
     return [];
}
}


// Return a list of one parameter per entry
// Return the entries between the first index and the lastPlus index.
// with the extra entries of longer parameters moved to the end.
// cf. http://wiki.secondlife.com/wiki/Slice_List_String_Etc
//
// cf. http://www.google.com/search?q=site:docs.python.org+slice
// Also llOwnerSay the quoted results, if shouldEcho.
 
list listGetBetween(list entries, integer first, integer lastPlus)
{
 
    // Count negative indices back from beyond, stopping at zero
 
    integer beyond = llGetListLength(entries);
    if (first < 0) { first += beyond; if (first < 0) { first = 0; } }
    if (lastPlus < 0) { lastPlus += beyond; if (lastPlus < 0) { lastPlus = 0; } }
 
    // Slice if indices nonnegative and strictly ordered
 
    if (first < lastPlus) // implies && (1 <= lastPlus)
    {
        return llList2List(entries, first, lastPlus - 1);
    }
 
    // Else return the empty list
 
    return [];
}
 
// Return the chars between the first index and the lastPlus index.
// cf. http://wiki.secondlife.com/wiki/Slice_List_String_Etc
// cf. http://www.google.com/search?q=site:docs.python.org+slice


list pickEach(string callable, list entries, integer shouldEcho)
string stringGetBetween(string chars, integer first, integer lastPlus)
{
{
   
    // Begin with nothing and begin outside of [ ... ]


     list pickables = [];
     // Count negative indices back from beyond, stopping at zero
     list flats = [];
 
    integer depth = 0; // no lists found
    integer beyond = llStringLength(chars);
     integer opened = -1; // not open
     if (first < 0) { first += beyond; if (first < 0) { first = 0; } }
     if (lastPlus < 0) { lastPlus += beyond; if (lastPlus < 0) { lastPlus = 0; } }


     // Take each entry in order
     // Slice if indices nonnegative and strictly ordered


    string echoable = "";
     if (first < lastPlus) // implies && (1 <= lastPlus)
     if (callable != "")
     {
     {
         echoable += callable + "(";
         return llGetSubString(chars, first, lastPlus - 1);
     }
     }


    // Else return the empty string
    return "";
}
// Call llParseString2List for each of the sources.
// Return the results in order.
// cf. http://wiki.secondlife.com/wiki/Separate_Words
list applyLlParseString2List(list sources, list separators, list spacers)
{
    list words = [];
     integer index;
     integer index;
     integer lenEntries = llGetListLength(entries);
     integer lenSources = llGetListLength(sources);
     for (index = 0; index < lenEntries; ++index)
     for (index = 0; index < lenSources; ++index)
     {
     {
         list entry = llList2List(entries, index, index);
         string source = llList2String(sources, index);
        words += llParseString2List(source, separators, spacers);
    }
    return words;
}


        // Quote keys as (implicit cast to key type from) string source
// Divide a source string into words.
       
// See the chars between separators or spacers, and each spacer, as a word.
        string word = llList2String(entries, index);      
// Never see the empty string as a word.
         if (llGetListEntryType(entries, index) == TYPE_KEY)
// cf. http://wiki.secondlife.com/wiki/Separate_Words
        {
 
//         word = "(key) " + quote + word + quote; // no
list separateWords(string chars, list separators, list spacers)
            word = quote + word + quote;
{
        }
 
    // Begin with all chars in one word
 
    list words = [chars];
 
    // List the chars between spacers, and each spacer, as a word
 
    integer index;
    integer lenSpacers = llGetListLength(spacers);
    for (index = 0; index < lenSpacers; index += 8)
    {
         list some = llList2List(spacers, index, index + 8 - 1);
        words = applyLlParseString2List(words, [], some);
    }
 
    // Discard the separators after letting the separators separate words
 
// integer index;
    integer lenSeparators = llGetListLength(separators);
    for (index = 0; index < lenSeparators; index += 8)
    {
        list some = llList2List(separators, index, index + 8 - 1);
        words = applyLlParseString2List(words, some, []);
    }
 
    // Succeed
 
    return words;
}
 
// Fetch named values, keep spacers, discard separators and commentary.
// Along the way, rejoin any quotation broken into words.
// Trust the caller to have kept at least the separators inside quotations.
 
list assignValues(list words)
{
 
    // Begin with nothing
 
    list values = [];
 
    // Divide the command into words, but also shatter quotations


        // Echo each separate entry
    integer lenWords = llGetListLength(words);
       
        if ((index != (opened + 1)) && (word != "]"))
        {
            echoable += ", ";
        }
       
        echoable += word;
       
        // Capture the entries enclosed by [ ... ]


        if ((word == "]") && (0 <= opened))
    // Consider each word or word of a quotation
        {
            list parameters = [];
            integer lenParameters = llGetListLength(pickables) - opened;
            if (0 < lenParameters)
            {
                parameters = llList2List(pickables, -lenParameters, -1);
                pickables = llList2List(pickables, 0, -1 - lenParameters);
            }


            // Add the length as an indexed parameter in place on the left
    integer index = 0;
    while (index < lenWords)
    {
        string word = llList2String(words, index++);


            pickables += depth;
        // Join together the words of a quotation
            ++depth;
               
            // Add the entries on the right


             flats += parameters;
        if (word == quote)
             flats += (lenParameters + 1); // also count the depth as a flat added
        {
            string value = "";
            do
             {
                word = llList2String(words, index++);
                if (word != quote)
                {
                    value += word;
                }
             } while ((word != quote) && (index < lenWords));


             // Consume the "[" from before
             // Quote the chars simply, so the quotation is never found in spacers


             opened = -1;
             values += quote + value + quote;
         }
         }


         // Open with "[" til "]" found
         // Discard slash slash commentary


         if (word == "[")
         else if ((word == "/") && (llList2String(words, index) == "/"))
         {
         {
             opened = llGetListLength(pickables);
             return values;
         }
         }
         else if (word == "]")
 
        // Discard separators
 
         else if (0 <= llListFindList(separators(), [word]))
         {
         {
             opened = -1;
             ;
         }
         }
       
        // Unquote each string parameter simply


         else if (llGetListEntryType(entry, 0) == TYPE_STRING)
        // Keep spacers
 
         else if (0 <= llListFindList(spacers, [word]))
         {
         {
             if (word == (quote + quote))
             values += word; // often (word == valueOf(word)) here
            {
                pickables += "";
            }
            else
            {
                pickables += llGetSubString(word, 1, -2);
            }
         }
         }
       
 
         // Add the other parameters in order
         // Fetch named values


         else
         else
         {
         {
             pickables += entry;
             values += valueOf(word);
         }
         }
     }
     }
      
 
     if (callable != "")
     // Succeed
 
     return values;
}
 
// Return an equivalent source string.
// Pass each quoted parameter value to the callable.
// Compare llDumpList2String
 
string toSourceString(string callable, list values)
{
    string chars = callable + "(";
 
    // Take each quoted parameter in order
 
    integer opened = -1;
 
    integer index;
    integer lenValues = llGetListLength(values);
    for (index = 0; index < lenValues; ++index)
     {
     {
         echoable += ");";
         list value = llList2List(values, index, index);
    }
        string word = (string) value;
 
        // Separate inside of each list
 
        if (word == "[")
        {
            opened = 0;
        }
        if ((index != (opened + 1)) && (word != "]"))
        {
            chars += ", ";
        }


    // Trace on request
        // Append the quoted parameter


    if (shouldEcho)
         chars += word;
    {
         llOwnerSay(echoable);
     }
     }


     // Succeed
     // Succeed


     return pickables + flats;
    chars += ");";
     return chars;
}
}


// Fetch the indexed list parameter.
// Fetch an indexed parameter of list type.
// Compare llList2Rot llList2String llList2Vector etc.


list list2ListEntry(list parameters, integer index)
list list2ListEntry(list parameters, integer index)
{
{


     // Step back to the start of this last parameter
     // Step thru links back to the start of this last parameter


     integer depth = llList2Integer(parameters, index);
     integer depth = llList2Integer(parameters, index);
Line 523: Line 753:
     }
     }


     // Find this list parameter
     // Return the zero or more entries


     integer lengthPlus = llList2Integer(parameters, offset + 0);
     integer lengthPlus = llList2Integer(parameters, offset + 0);
     integer lenEntries = (lengthPlus - 1);
     integer lenEntries = (lengthPlus - 1);
    list entries = listGetBetween(parameters, offset - lenEntries, offset);
    return entries;
}


     list entries = [];
// Return a list of one parameter per entry on the left
     if (lenEntries != 0)
// by moving the entries of list type parameters
// into a linked list of fetchable lists on the right.
 
list indexParameters(string callable, list passables)
{
 
    // Begin with nothing and begin outside of [ ... ]
 
    list indexables = [];
     list fetchables = [];
     integer depth = 0; // no lists found
    integer opened = -1; // not open
 
    // Take each quoted parameter in order
 
    integer index;
    integer lenPassables = llGetListLength(passables);
    for (index = 0; index < lenPassables; ++index)
     {
     {
         entries = llList2List(parameters, offset - lenEntries, offset - 1);
         list passable = llList2List(passables, index, index);
        string word = (string) passable;
 
        // Count the zero or more passables enclosed by [ ... ]
 
        if ((word == "]") && (0 <= opened))
        {
            integer lenEntries = llGetListLength(indexables) - opened;
 
            // Substitute the nonnegative length for those passables
 
            list entries = listGetBetween(indexables, opened, llGetListLength(indexables));
            indexables = listGetBetween(indexables, 0, opened) + depth;
            ++depth;
 
            // Move these passables into the linked list on the right
 
            fetchables = (lenEntries + 1) + fetchables; // also count the depth as a flat added
            fetchables = entries + fetchables;
 
            // Consume the "[" from before
 
            opened = -1;
        }
 
        // Open with "[" til "]" found
 
        if (word == "[")
        {
            opened = llGetListLength(indexables);
        }
        else if (word == "]")
        {
            opened = -1;
        }
 
        // Unquote each string parameter simply
 
        else if (llGetListEntryType(passable, 0) == TYPE_STRING)
        {
            string chars = word;
            if (llGetSubString(word, 0, 0) == quote)
            {
                chars = stringGetBetween(word, 1, -1); // maybe empty
            }
            indexables += chars;
        }
 
        // Add the other parameters in order
 
        else
        {
            indexables += passable;
        }
     }
     }


     // Succeed
     // Succeed


     return entries;
     return indexables + fetchables;
}
}


Line 542: Line 845:
// Pass thru any other results unchanged.
// Pass thru any other results unchanged.


list quoteEach(list results)
list quoteResults(list results)
{
{
   
 
     // Begin with nothing
     // Begin with nothing
   
 
     list words = [];
     list listables = [];
   
 
     // Consider quoting the one result, else every result enclosed in [ ... ]
     // Consider quoting the one result, else every result enclosed in [ ... ]
   
 
     integer first = 0;
     integer first = 0;
     integer last = 0;
     integer last = 0;
Line 559: Line 862:
         last = beyond - 2;
         last = beyond - 2;
     }
     }
   
 
     // Take each result in order
     // Take each result in order


Line 566: Line 869:
     {
     {


         // Quote each string result simply
         // Quote each string result (or key result) simply
       
 
         if (llGetListEntryType(results, index) == TYPE_STRING)
         integer resultType = llGetListEntryType(results, index);
        if ((resultType == TYPE_STRING) || (resultType == TYPE_KEY))
         {
         {
             string word = llList2String(results, index);
             list result = llList2List(results, index, index);
             words += quote + word + quote;
             string word = (string) result;
//          listables += "(key) " + quote + word + quote; // no
            listables += quote + word + quote;
         }
         }
       
 
         // List all other results unchanged
         // List all other results unchanged
       
 
         else
         else
         {
         {
             list result = llList2List(results, index, index);
             list result = llList2List(results, index, index);
             words += result;
             listables += result;
         }
         }
     }
     }
   
 
     // Enclose a list of zero or more results in [ ... ], else return one result
     // Enclose a list of zero or more results in [ ... ], else return one result
   
 
     if (1 < beyond)
     if (1 < beyond)
     {
     {
         return "[" + words + "]";
         return "[" + listables + "]";
     }
     }
   
 
     return words;
     return listables;
}
}


// Interpret one list of words.
// Interpret one list of words.


list callEach(list words)
list fetchResults(list values)
{
{


     // Begin with almost nothing
     // Begin with nothing


     list results = [""];
     list results = [];
     list depths = [0];
     list depths = [];


     // Take each action in order
     // Take each action in order


    integer lenWords = llGetListLength(words);
     integer index;
     integer index;
     for (index = 0; index < lenWords; ++index)
    integer lenValues = llGetListLength(values);
     for (index = 0; index < lenValues; ++index)
     {
     {
         string word = llList2String(words, index);
        list value = llList2List(values, index, index);
         string word = (string) value;
          
          
         // Count results of "( ... )" or of "< ... >"
         // Count results of "( ... )" or of "< ... >"
       
 
         if ((word == "(") || (word == "<"))
         if ((word == "(") || (word == "<"))
         {
         {
             integer beyond = llGetListLength(results);
             depths += llGetListLength(results); // push the depth
            depths += [beyond]; // push the depth
         }
         }


Line 622: Line 928:
         {
         {


             // Pop the depth
             // Pop the depth of "(" or "<" opened without ">" or ")" to close


             integer first = llList2Integer(depths, -1);
             integer first = llList2Integer(depths, -1);
             depths = llList2List(depths, 0, -2); // pop the tail
             depths = listGetBetween(depths, 0, -1); // pop the tail


            // Pop the zero or more parameters


            // Pop the parameters
             list passables = listGetBetween(results, first, llGetListLength(results));
           
            results = listGetBetween(results, 0, first);
             list entries = [];
//          llOwnerSay("..." + "(" + llList2CSV(passables) + ") == passables");
            integer last = llGetListLength(results) - 1;           
            if (first <= last)
            {
                entries = llList2List(results, first, last);
                results = llList2List(results, 0, first - 1);
            }
           
//          llOwnerSay(llList2CSV(entries));


             // Choose the callable to receive the parameters
             // Choose the callable to receive the parameters
Line 646: Line 945:
             {
             {
                 callable = "()"; // "" for type casts
                 callable = "()"; // "" for type casts
                 string entry0string = llList2String(entries, 0);
                 list passable0 = llList2List(passables, 0, 0);
                 if ((llGetListLength(entries) != 1) || (llListFindList(types, [entry0string]) < 0))
                 if ((llGetListLength(passables) != 1) || (llListFindList(types, passable0) < 0))
                 {
                 {
                     callable = llList2String(results, -1);
                     callable = llList2String(results, -1);
                     results = llList2List(results, 0, -2); // pop the tail
                     results = listGetBetween(results, 0, -1); // pop the tail
                 }
                 }


                 // Unquote the callable simply
                 // Unquote the callable simply
               
 
                 if (llGetSubString(callable, 0, 0) == quote)
                 if (llGetSubString(callable, 0, 0) == quote)
                 {
                 {
                     callable = llGetSubString(callable, 1, -2); // unquote simply if not empty
                     callable = stringGetBetween(callable, 1, -1); // unquote simply, even if empty
                 }
                 }
             }
             }


             // Call with a list of parameters
             // Often chat back each call


             integer shouldEcho = theShouldEcho && (llListFindList(["", "<>"], [callable]) < 0);
             if (theShouldEcho && (llListFindList(["()", "<>"], [callable]) < 0))
             list pickables = pickEach(callable, entries, shouldEcho);
             {
//          llOwnerSay(callable + "(" + llList2CSV(pickables) + ")p");
                llOwnerSay(toSourceString(callable, passables));
              
             }
            list quotables = resultOf(callable, pickables);
//          llOwnerSay(callable + "(" + llList2CSV(quotables) + ")q");


             list quotations = quoteEach(quotables);
             // Call with a list of parameters
//         llOwnerSay(callable + "(" + llList2CSV(quotations) + ")r");
           
            results += quotations;
        }


        // Push any other word as a parameter, without change
            list parameters = indexParameters(callable, passables);
//         llOwnerSay("(" + llList2CSV(parameters) + ") == parameters");


        else
             list quotables = resultOf(callable, parameters);
        {
//          llOwnerSay("(" + llList2CSV(quotables) + ") == quotables");
             list result = llList2List(words, index, index);
            results += result;
        }
    }


    // Succeed
            list listables = quoteResults(quotables);
//         llOwnerSay("(" + llList2CSV(listables) + ") == listables");


    if (llGetListLength(results) <= 1)
            results += listables;
    {
        return [];
    }
    return llList2List(results, 1, -1); // pop the head (only if head is not tail)
}
 
// Divide the command into words.
// Take care to join together the words of each quotation.
// Return a list of the words of the command.
 
list valueEach(list words)
{
 
    // Begin with nothing
 
    list values = [];
   
    // Divide the command into words, but also shatter quotations
 
    integer lenWords = llGetListLength(words);
   
    // Consider each word or word of a quotation
 
    integer index = 0;
    while (index < lenWords)
    {
        string word = llList2String(words, index++);
       
        // Join together the words of a quotation
 
        if (word == quote)
        {
            string value = "";
            do
            {
                word = llList2String(words, index++);
                if (word != quote)
                {
                    value += word;
                }
            } while ((word != quote) && (index < lenWords));
           
            // Quote the chars simply, so the quotation is never found in spacers
           
            values += quote + value + quote;
         }
         }


         // Discard slash slash commentary
         // Push any other word as a parameter, without change


        else if ((word == "/") && (llList2String(words, index) == "/"))
        {
            return values;
        }
        // Discard separators
       
        else if ((0 <= llListFindList(separators, [word])) || (word == ""))
        {
            ;
        }
        // Keep spacers
       
        else if (0 <= llListFindList(spacers, [word]))
        {
            values += word;
        }
        // Evaluate other words
       
         else
         else
         {
         {
             values += valueOf(word);
             results += value;
         }
         }
     }  
     }


     // Succeed
     // Succeed


     return values;
     return results;
}
 
// Divide a string into words,
// by returning each of the spacers as a word
// and return as a word each run of chars between words.
 
list separateEach(string source, list spacers)
{
    list outputs = [source];
   
    // Pass never more than 8 spacers to llParseString2List
   
    integer lenSpacers = llGetListLength(spacers);
    integer ss;
    for (ss = 0; ss < lenSpacers; ss += 8)
    {
        list someSpacers = llList2List(spacers, ss, ss + 7);
       
        // Call llParseString2List again for each result so far
               
        list inputs = outputs;
        outputs = [];
       
        integer lenInputs = llGetListLength(inputs);
        integer ii;
        for (ii = 0; ii < lenInputs; ++ii)
        {
            string input = llList2String(inputs, ii);
            outputs += llParseString2List(input, [], someSpacers);
        }
    }
   
    // Succeed
   
    return outputs;
}
}


Line 808: Line 998:
default
default
{
{
     state_entry()
     state_entry()
     {
     {
//      llOwnerSay(llGetScriptName() + ".default.state_entry");
         llListen(theChannel, "", llGetOwner(), "");
         llListen(theChannel, "", llGetOwner(), "");
     }
     }
    dataserver(key queryid, string data)
    {
        llOwnerSay(toSourceString("dataserver", quoteResults([queryid]) + quoteResults([data])));
    }
     listen(integer channel, string name, key id, string message)
     listen(integer channel, string name, key id, string message)
     {
     {
//      llOwnerSay(llGetScriptName() + ".default.listen");


         // Compile and run
         // Compile and run


//     llOwnerSay(message);
        llOwnerSay("// " + message);


         list words = separateEach(message, separators + spacers);
         list words = separateWords(message, [], separators() + spacers);
//      llOwnerSay((string) llGetListLength(words) + ": " + llList2CSV(words));
//      llOwnerSay(llList2CSV(words) + " == words");


         list values = valueEach(words);
         list values = assignValues(words);
//      llOwnerSay((string) llGetListLength(values) + ": " + llList2CSV(values));
//      llOwnerSay(llList2CSV(values) + " == values");


         list results = callEach(values);
         list results = fetchResults(values);
//      llOwnerSay((string) llGetListLength(results) + ": " + llList2CSV(results));
//      llOwnerSay(llList2CSV(results) + " == results");


         // Chat back the results, if not empty
         // Chat back the results, if not empty
Line 832: Line 1,031:
         if (results != [])
         if (results != [])
         {
         {
             integer shouldEcho = TRUE;
             llOwnerSay(stringGetBetween(toSourceString("", results), 1, -2));
            pickEach("", results, shouldEcho); // llOwnerSay the quoted results
         }
         }
       
//      llOwnerSay("OK");
     }
     }
}
}
</pre>
</source>


[[Category:LSL Library]]
[[Category:LSL Library]]
[[Category:LSL Examples]]


{{LSLC|Tutorials}}
{{LSLC|Tutorials}}

Latest revision as of 13:22, 24 January 2015

Introduction

This script lets you chat some new example LSL code on a channel, and then see this script compile and run that code for you.

This script chats back the intermediate and final return values at you, like the Prefix Calculator script does. This script also lets you work with data types other than fixed point numbers and with functions beyond arithmetic.

If you're new, you might prefer to explore by editing one line of a short script of the object, clicking Save to run once, and clicking Reset to run again indefinitely many times. Or you might like to edit and Save in the Inventory > Scripts folder and drag to run a new copy when you please. The Hello Avatar article illustrates those approaches to learning LSL.

NOTE: To run this demo, you must place the script at the bottom of the page into the object you wish to run it on. Take some time to look at some of the different values and what they can do. Particularly, take note of the "if/then" states and functions, as well as the "else if" functions.

The Demo

Remember that wood box that you Create by default inside SL (i.e., "in world")?

This script lets you chat LSL commands to make that box translucent and bouncy and then kick that box around and spin that box, as follows ...

Chat to twiddle the red, green, and blue intensity, also the "alpha" opacity/ transparency:

/7 llSetColor(<0.3, 0.3, 0.3>, ALL_SIDES); // darken
/7 llSetColor(<1, 1, 1>, ALL_SIDES); // lighten
/7 llSetAlpha(0.7, ALL_SIDES); // make translucent

Chat to twiddle the label of the object running the script:

/7 llSetText("look at me green", <0.0, 1.0, 0.0>, 1.0); // label
/7 llSetText("look at me black", <0.0, 0.0, 0.0>, 1.0); // label differently
/7 llSetText("", <0.0, 0.0, 0.0>, 1.0); // do not label

Chat to move and rotate while not physical, then kick and spin while physical and bouncy:

/7 llSetStatus(STATUS_PHYSICS, FALSE); llSleep(0.1);
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, -2.1>)); // teleport back down the Z axis
/7 llSetLocalRot(llRotBetween(<1, 0, 0>, llGetSunDirection())); // turn the East face to the Sun
/7 llSetLocalRot(llEuler2Rot(ZERO_VECTOR)); // turn the East face to the East
/7 llSetStatus(STATUS_PHYSICS, TRUE); llSleep(0.1);
/7 llSetBuoyancy(0.9); // resist gravity, but don't float
/7 llApplyImpulse(<0.0, 0.0, 1.0>, TRUE); // advance along the Z axis
/7 llApplyRotationalImpulse(<0.0, 0.0, 3.0>, TRUE); // yaw about the Z axis
/7 llSetStatus(STATUS_PHYSICS, FALSE); llSetStatus(STATUS_PHYSICS, TRUE); llSleep(0.1); // zero rot inertia

Chat to ask the object running the script what it knows of itself and avatars:

/7 llGetAgentSize(llGetLinkKey(llGetNumberOfPrims())) // often not ZERO_VECTOR while avatar sits
/7 llKey2Name(llGetLinkKey(llGetNumberOfPrims())) // often the name of the sitting avatar
/7 llRequestAgentData(llGetOwner(), DATA_BORN); // the data-of-birth of the owning avatar
/7 ZERO_VECTOR, FALSE, TRUE, STATUS_PHYSICS, PI // some named code values

Chat to present you with a menu of chat messages for you to send by clicking your one choice:

/7 llDialog(llGetOwner(), "A clarifying demo?", ["No", "Yes"], 7); // chat some Q & A
/7 llDialog(llGetOwner(), "Choose an arc:", ["PI_BY_TWO", "PI", "TWO_PI"], 7); // chat some Q & A

Chat to ask how much memory exists (in the task of the script of the object) that you never have yet filled with allocations of byte code, stack, or heap:

/7 llGetFreeMemory()

Your Experience

The LSL commands quoted above often do work as described in their // comments.

The commands work if chatted in the order shown. The commands work if chatted at the usual wood box built by default (0.5 x 0.5 x 0.5 m) by the Create choice on the ground menu in the SL GUI.

To see how the weight and shape of an object influence its response to commands that require STATUS_PHYSICS TRUE, try sending these commands to other objects.

To see how force multiplies, try adding this script more than once to an object.

To keep this article short, this script understands only some of the LSL language that may interest you. We hope you'll find it easy to teach this script to understand whichever part of the LSL language you next want to explore. Just by trying, you can see that this script doesn't understand the less commonly useful library function names, arithmetic operators, code constant names, control flow keywords, punctuation marks, etc. Nor does this script understand any of your own function names, until you add those functions to this script.

As you first try speaking more of the LSL language, please don't trust this script too much. If you find you get confused over what this script thinks an LSL command means, please consider slowing down enough to design a small one-off experimental script to show you what the SL GUI thinks the command means. When you find a difference between what the SL GUI thinks and what this script thinks, that's a bug. To keep this article short, this script has many bugs of that kind: this script misunderstands much misspelled LSL, and this script misunderstands much correct LSL.

We welcome you blogging your experience into the discussion tab of this article, to help us track bugs, to help us dream up new tutorial demoes, to encourage our work here, etc.

Enjoy,

P.S.

Ppaatt Lynagh launched this tutorial project in 2007-09.

Ppaatt develops on Mac OS X. You can help by testing Linux or Windows and reporting your experience into the discussion tab of this article.

"Allow anyone to copy", "next owner can copy & modify", and "mark item for sale price L$10" were the rights on Pat's copies of this script in SL, as of 2007-09-06.

Related Articles

Hello_Avatar - Examples of visually powerful single lines of code.

Separate_Words - Substitute a call to the separateWords function in place of a call to llParseString2List whenever you have more than 8 separators or more than 8 spacers.

Slice_List_String_Etc - Return empty lists and strings when expected, while slicing lists or strings.

The Thousand Lines of Script

// 2007-09-25 http://wiki.secondlife.com/wiki/Chatbot
//
// Compile and run the Lsl you type on a channel,
// faster than you can thru the 2007-08 SL GUI,
// thus "chat with your 'bot" while it runs this script.
//
// Run well in Windows too by never cascading too many else-if.
//

// Choose one chat channel for hearing commands, echoing commands, and chatting results.

integer theChannel = 7;

// Chat back a copy of the meaningful or meaningless command, only on request.

integer theShouldEcho = TRUE;

// Describe the language of an LSL expression without infix operators, string escapes, etc.

string lf = "\n";
string quote = "\"";
string escape = "\\";//"

list spacers = [quote, "(", ")", "<", ">", "[", "]", "/", "*", "%", escape];

list separators()
{
    string tab = llUnescapeURL("%09"); // != "\t"
    string cr = llUnescapeURL("%0D"); // != "\r"
    return [tab, lf, cr, " ", ",", ";"];
}

list types = ["integer", "float", "key", "vector", "rotation", "list"];

// List some frequently useful code values.

list theCodes = [

    TRUE, FALSE,

    PI,
    TWO_PI,
    PI_BY_TWO,
    DEG_TO_RAD,
    RAD_TO_DEG,
    SQRT2,

    NULL_KEY,
    ALL_SIDES,
    EOF,
    ZERO_VECTOR,
    ZERO_ROTATION,

    STATUS_PHYSICS,

    DATA_BORN,
    DATA_NAME,
    DATA_ONLINE,
    DATA_PAYINFO,
    DATA_SIM_POS,
    DATA_SIM_RATING,
    DATA_SIM_STATUS,

    0
];

// List the name of each code.

list theCodenames = [

    "TRUE", "FALSE",

    "PI",
    "TWO_PI",
    "PI_BY_TWO",
    "DEG_TO_RAD",
    "RAD_TO_DEG",
    "SQRT2",

    "NULL_KEY",
    "ALL_SIDES",
    "EOF",
    "ZERO_VECTOR",
    "ZERO_ROTATION",

    "STATUS_PHYSICS",

    "DATA_BORN",
    "DATA_NAME",
    "DATA_ONLINE",
    "DATA_PAYINFO",
    "DATA_SIM_POS",
    "DATA_SIM_RATING",
    "DATA_SIM_STATUS",

    "0"
];

// Evaluate any one parameter.

list valueOf(string word)
{
    if (0 <= llSubStringIndex(word, "."))
    {
        return [(float) word];
    }
    else if (0 <= llSubStringIndex("0123456789", llGetSubString(word, 0, 0)))
    {
        return [(integer) word];
    }
    else
    {
        integer index = llListFindList(theCodenames, [word]);
        if (0 <= index)
        {
            return llList2List(theCodes, index, index);
        } else {
            return [word]; // unevaluated
        }
    }
}

// Add and return the sum.

list sum(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_VECTOR)
    {
        return [llList2Vector(values, 0) + llList2Vector(values, 1)];
    }
    return [];
}

// Subtract and return the difference.

list difference(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_VECTOR)
    {
        return [llList2Vector(values, 0) - llList2Vector(values, 1)];
    }
    return [];
}

// Multiply and return the product.

list product(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_ROTATION)
    {
        return [llList2Rot(values, 0) * llList2Rot(values, 1)];
    }
    return [];
}

// Divide and return the quotient.

list quotient(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_ROTATION)
    {
        return [llList2Rot(values, 0) / llList2Rot(values, 1)];
    }
    return [];
}

// Divide and return the remainder.

list remainder(list values)
{
    return [];
}

// Aggregate and return the composite.

list composite(list values)
{
    integer depth = llGetListLength(values);
    if (depth == 3)
    {
        vector vec;
        vec.x = llList2Float(values, 0);
        vec.y = llList2Float(values, 1);
        vec.z = llList2Float(values, 2);
        return [vec];
    }
    if (depth == 4)
    {
        rotation rot;
        rot.x = llList2Float(values, 0);
        rot.y = llList2Float(values, 1);
        rot.z = llList2Float(values, 2);
        rot.s = llList2Float(values, 2);
        return [rot];
    }
    return [];
}

// Pass the parameters to the named routine.
// Return a list of results.
// Return empty if no result, if empty list returned, or if callable meaningless.

list resultOf(string callable, list parameters)
{

    // Abbreviate the word "parameters"

    list ps = parameters;
    
    // Suicide on command
    
    if ("llDie" == callable) // ()
    {
        llDie(); // CAUTION -- this call deletes this script without saving changes -- CAUTION
    }

    // Return a list of one result of some verbose prefix arithmetic such as { "+"(vec1, vec2) }

    if ("<>" == callable)
    {
        return composite(ps);
    }
    else if ("+" == callable)
    {
        return sum(ps);
    }
    else if ("-" == callable)
    {
        return difference(ps);
    }
    else if ("*" == callable)
    {
        return product(ps);
    }
    else if ("/" == callable)
    {
        return quotient(ps);
    }
    else if ("%" == callable)
    {
        return remainder(ps);
    }

    // Return a list of one result of any type, for some meaningful verbs
        
    if ("llEscapeURL" == callable) // url
    {
        return [llEscapeURL(llList2String(ps, 0))];
    }
    else if ("llEuler2Rot" == callable) // (v)
    {
        return [llEuler2Rot(llList2Vector(ps, 0))];
    }
    else if ("llGetAgentInfo" == callable) // (id)
    {
        return [llGetAgentInfo(llList2Key(ps, 0))];
    }
    else if ("llGetAgentSize" == callable) // (id)
    {
        return [llGetAgentSize(llList2Key(ps, 0))];
    }
    else if ("llGetFreeMemory" == callable) // ()
    {
        return [llGetFreeMemory()];
    }
    
    else if ("llGetLinkKey" == callable) // (linknum)
    {
        return [llGetLinkKey(llList2Integer(ps, 0))];
    }
    else if ("llGetNumberOfPrims" == callable) // ()
    {
        return [llGetNumberOfPrims()];
    }
    else if ("llGetOwner" == callable) // ()
    {
        return [llGetOwner()];
    }
    else if ("llGetPos" == callable) // ()
    {
        return [llGetPos()];
    }
    else if ("llGetLocalRot" == callable) // ()
    {
        return [llGetLocalRot()];
    }

    // "ERROR : Syntax Error" rejects another cascading "else" here, in Windows SL

    if ("llGetRegionName" == callable) // ()
    {
        return [llGetRegionName()];
    }
    else if ("llGetRot" == callable) // ()
    {
        return [llGetRot()];
    }
    else if ("llGetSunDirection" == callable) // ()
    {
        return [llGetSunDirection()];
    }
    else if ("llKey2Name" == callable) // id
    {
        return [llKey2Name(llList2Key(ps, 0))];
    }
    else if ("llRequestAgentData" == callable) // (id, data)
    {
        return [llRequestAgentData(llList2Key(ps, 0), llList2Integer(ps, 1))];
    }
    
    else if ("llRequestSimulatorData" == callable) // (simulator, data)
    {
        return [llRequestSimulatorData(llList2String(ps, 0), llList2Integer(ps, 1))];
    }
    else if ("llRot2Euler" == callable) // (q)
    {
        return [llRot2Euler(llList2Rot(ps, 0))];
    }
    else if ("llRotBetween" == callable) // (v1, v2)
    {
        return [llRotBetween(llList2Vector(ps, 0), llList2Vector(ps, 1))];
    }
    else if ("llUnescapeURL" == callable) // url
    {
        return [llUnescapeURL(llList2String(ps, 0))];
    }
    else if ("llVecNorm" == callable) // v
    {
        return [llVecNorm(llList2Vector(ps, 0))];
    }

    // Obey some meaningful verbs that return no result

    integer meaningful = TRUE;
    
    if ("llApplyImpulse" == callable) // (force, local)
    {
        llApplyImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llApplyRotationalImpulse" == callable) //a (force, local)
    {
        llApplyRotationalImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llDialog" == callable) // (avatar, message, buttons, channel)
    {
        llDialog(llList2Key(ps, 0),
            llList2String(ps, 1), list2ListEntry(ps, 2), llList2Integer(ps, 3));
    }
    else if ("llSetAlpha" == callable) // (alpha, face)
    {
        llSetAlpha(llList2Float(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llSetBuoyancy" == callable) // (buoyancy)
    {
        llSetBuoyancy(llList2Float(ps, 0));
    }
    
    else if ("llSetColor" == callable) // (color, face)
    {
        llSetColor(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llSetLocalRot" == callable) // (rot)
    {
        llSetLocalRot(llList2Rot(ps, 0));
    }
    else if ("llSetPos" == callable) // (pos)
    {
        llSetPos(llList2Vector(ps, 0));
    }
    else if ("llSetRot" == callable) // (rot)
    {
        llSetRot(llList2Rot(ps, 0));
    }
    else if ("llSetScale" == callable) // (scale)
    {
        llSetScale(llList2Vector(ps, 0));
    }
    
    else if ("llSetStatus" == callable) // (status, value)
    {
        llSetStatus(llList2Integer(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llSetText" == callable) // (text, color, alpha)
    {
        llSetText(llList2String(ps, 0), llList2Vector(ps, 1), llList2Float(ps, 2));
    }
    else if ("llSitTarget" == callable) // (offset, rot)
    {
        llSitTarget(llList2Vector(ps, 0), llList2Rot(ps, 1));
    }
    else if ("llSleep" == callable) // (sec)
    {
        llSleep(llList2Float(ps, 0));
    }
    else
    {
        meaningful = FALSE;
    }

    // Return an empty list if callable returns no result

    if (meaningful)
    {
        return []; // FIXME: indistinguishable from callable meaningless
    }

    // Return an empty list if callable meaningless
    
    return [];
}

// Return the entries between the first index and the lastPlus index.
// cf. http://wiki.secondlife.com/wiki/Slice_List_String_Etc
// cf. http://www.google.com/search?q=site:docs.python.org+slice

list listGetBetween(list entries, integer first, integer lastPlus)
{

    // Count negative indices back from beyond, stopping at zero

    integer beyond = llGetListLength(entries);
    if (first < 0) { first += beyond; if (first < 0) { first = 0; } }
    if (lastPlus < 0) { lastPlus += beyond; if (lastPlus < 0) { lastPlus = 0; } }

    // Slice if indices nonnegative and strictly ordered

    if (first < lastPlus) // implies && (1 <= lastPlus)
    {
        return llList2List(entries, first, lastPlus - 1);
    }

    // Else return the empty list

    return [];
}

// Return the chars between the first index and the lastPlus index.
// cf. http://wiki.secondlife.com/wiki/Slice_List_String_Etc
// cf. http://www.google.com/search?q=site:docs.python.org+slice

string stringGetBetween(string chars, integer first, integer lastPlus)
{

    // Count negative indices back from beyond, stopping at zero

    integer beyond = llStringLength(chars);
    if (first < 0) { first += beyond; if (first < 0) { first = 0; } }
    if (lastPlus < 0) { lastPlus += beyond; if (lastPlus < 0) { lastPlus = 0; } }

    // Slice if indices nonnegative and strictly ordered

    if (first < lastPlus) // implies && (1 <= lastPlus)
    {
        return llGetSubString(chars, first, lastPlus - 1);
    }

    // Else return the empty string

    return "";
}

// Call llParseString2List for each of the sources.
// Return the results in order.
// cf. http://wiki.secondlife.com/wiki/Separate_Words

list applyLlParseString2List(list sources, list separators, list spacers)
{
    list words = [];
    integer index;
    integer lenSources = llGetListLength(sources);
    for (index = 0; index < lenSources; ++index)
    {
        string source = llList2String(sources, index);
        words += llParseString2List(source, separators, spacers);
    }
    return words;
}

// Divide a source string into words.
// See the chars between separators or spacers, and each spacer, as a word.
// Never see the empty string as a word.
// cf. http://wiki.secondlife.com/wiki/Separate_Words

list separateWords(string chars, list separators, list spacers)
{

    // Begin with all chars in one word

    list words = [chars];

    // List the chars between spacers, and each spacer, as a word

    integer index;
    integer lenSpacers = llGetListLength(spacers);
    for (index = 0; index < lenSpacers; index += 8)
    {
        list some = llList2List(spacers, index, index + 8 - 1);
        words = applyLlParseString2List(words, [], some);
    }

    // Discard the separators after letting the separators separate words

//  integer index;
    integer lenSeparators = llGetListLength(separators);
    for (index = 0; index < lenSeparators; index += 8)
    {
        list some = llList2List(separators, index, index + 8 - 1);
        words = applyLlParseString2List(words, some, []);
    }

    // Succeed

    return words;
}

// Fetch named values, keep spacers, discard separators and commentary.
// Along the way, rejoin any quotation broken into words.
// Trust the caller to have kept at least the separators inside quotations.

list assignValues(list words)
{

    // Begin with nothing

    list values = [];

    // Divide the command into words, but also shatter quotations

    integer lenWords = llGetListLength(words);

    // Consider each word or word of a quotation

    integer index = 0;
    while (index < lenWords)
    {
        string word = llList2String(words, index++);

        // Join together the words of a quotation

        if (word == quote)
        {
            string value = "";
            do
            {
                word = llList2String(words, index++);
                if (word != quote)
                {
                    value += word;
                }
            } while ((word != quote) && (index < lenWords));

            // Quote the chars simply, so the quotation is never found in spacers

            values += quote + value + quote;
        }

        // Discard slash slash commentary

        else if ((word == "/") && (llList2String(words, index) == "/"))
        {
            return values;
        }

        // Discard separators

        else if (0 <= llListFindList(separators(), [word]))
        {
            ;
        }

        // Keep spacers

        else if (0 <= llListFindList(spacers, [word]))
        {
            values += word; // often (word == valueOf(word)) here
        }

        // Fetch named values

        else
        {
            values += valueOf(word);
        }
    }

    // Succeed

    return values;
}

// Return an equivalent source string.
// Pass each quoted parameter value to the callable.
// Compare llDumpList2String

string toSourceString(string callable, list values)
{
    string chars = callable + "(";

    // Take each quoted parameter in order

    integer opened = -1;

    integer index;
    integer lenValues = llGetListLength(values);
    for (index = 0; index < lenValues; ++index)
    {
        list value = llList2List(values, index, index);
        string word = (string) value;

        // Separate inside of each list

        if (word == "[")
        {
            opened = 0;
        }
        if ((index != (opened + 1)) && (word != "]"))
        {
            chars += ", ";
        }

        // Append the quoted parameter

        chars += word;
    }

    // Succeed

    chars += ");";
    return chars;
}

// Fetch an indexed parameter of list type.
// Compare llList2Rot llList2String llList2Vector etc.

list list2ListEntry(list parameters, integer index)
{

    // Step thru links back to the start of this last parameter

    integer depth = llList2Integer(parameters, index);
    integer offset = -1;
    while (0 < depth--)
    {
        offset -= llList2Integer(parameters, offset + 0);
    }

    // Return the zero or more entries

    integer lengthPlus = llList2Integer(parameters, offset + 0);
    integer lenEntries = (lengthPlus - 1);
    list entries = listGetBetween(parameters, offset - lenEntries, offset);
    return entries;
}

// Return a list of one parameter per entry on the left
// by moving the entries of list type parameters
// into a linked list of fetchable lists on the right.

list indexParameters(string callable, list passables)
{

    // Begin with nothing and begin outside of [ ... ]

    list indexables = [];
    list fetchables = [];
    integer depth = 0; // no lists found
    integer opened = -1; // not open

    // Take each quoted parameter in order

    integer index;
    integer lenPassables = llGetListLength(passables);
    for (index = 0; index < lenPassables; ++index)
    {
        list passable = llList2List(passables, index, index);
        string word = (string) passable;

        // Count the zero or more passables enclosed by [ ... ]

        if ((word == "]") && (0 <= opened))
        {
            integer lenEntries = llGetListLength(indexables) - opened;

            // Substitute the nonnegative length for those passables

            list entries = listGetBetween(indexables, opened, llGetListLength(indexables));
            indexables = listGetBetween(indexables, 0, opened) + depth;
            ++depth;

            // Move these passables into the linked list on the right

            fetchables = (lenEntries + 1) + fetchables; // also count the depth as a flat added
            fetchables = entries + fetchables;

            // Consume the "[" from before

            opened = -1;
        }

        // Open with "[" til "]" found

        if (word == "[")
        {
            opened = llGetListLength(indexables);
        }
        else if (word == "]")
        {
            opened = -1;
        }

        // Unquote each string parameter simply

        else if (llGetListEntryType(passable, 0) == TYPE_STRING)
        {
            string chars = word;
            if (llGetSubString(word, 0, 0) == quote)
            {
                chars = stringGetBetween(word, 1, -1); // maybe empty
            }
            indexables += chars;
        }

        // Add the other parameters in order

        else
        {
            indexables += passable;
        }
    }

    // Succeed

    return indexables + fetchables;
}

// Quote each string result simply, so that each result is never found in spacers.
// Pass thru any other results unchanged.

list quoteResults(list results)
{

    // Begin with nothing

    list listables = [];

    // Consider quoting the one result, else every result enclosed in [ ... ]

    integer first = 0;
    integer last = 0;
    integer beyond = llGetListLength(results);
    if (1 < llGetListLength(results))
    {
        first = 1;
        last = beyond - 2;
    }

    // Take each result in order

    integer index;
    for (index = first; index <= last; ++index)
    {

        // Quote each string result (or key result) simply

        integer resultType = llGetListEntryType(results, index);
        if ((resultType == TYPE_STRING) || (resultType == TYPE_KEY))
        {
            list result = llList2List(results, index, index);
            string word = (string) result;
//          listables += "(key) " + quote + word + quote; // no
            listables += quote + word + quote;
        }

        // List all other results unchanged

        else
        {
            list result = llList2List(results, index, index);
            listables += result;
        }
    }

    // Enclose a list of zero or more results in [ ... ], else return one result

    if (1 < beyond)
    {
        return "[" + listables + "]";
    }

    return listables;
}

// Interpret one list of words.

list fetchResults(list values)
{

    // Begin with nothing

    list results = [];
    list depths = [];

    // Take each action in order

    integer index;
    integer lenValues = llGetListLength(values);
    for (index = 0; index < lenValues; ++index)
    {
        list value = llList2List(values, index, index);
        string word = (string) value;
        
        // Count results of "( ... )" or of "< ... >"

        if ((word == "(") || (word == "<"))
        {
            depths += llGetListLength(results); // push the depth
        }

        else if ((word == ")") || (word == ">"))
        {

            // Pop the depth of "(" or "<" opened without ">" or ")" to close

            integer first = llList2Integer(depths, -1);
            depths = listGetBetween(depths, 0, -1); // pop the tail

            // Pop the zero or more parameters

            list passables = listGetBetween(results, first, llGetListLength(results));
            results = listGetBetween(results, 0, first);
//          llOwnerSay("..." + "(" + llList2CSV(passables) + ") == passables");

            // Choose the callable to receive the parameters

            string callable = "<>"; // "<>" for vector source or rotation source
            if (word == ")")
            {
                callable = "()"; // "" for type casts
                list passable0 = llList2List(passables, 0, 0);
                if ((llGetListLength(passables) != 1) || (llListFindList(types, passable0) < 0))
                {
                    callable = llList2String(results, -1);
                    results = listGetBetween(results, 0, -1); // pop the tail
                }

                // Unquote the callable simply

                if (llGetSubString(callable, 0, 0) == quote)
                {
                    callable = stringGetBetween(callable, 1, -1); // unquote simply, even if empty
                }
            }

            // Often chat back each call

            if (theShouldEcho && (llListFindList(["()", "<>"], [callable]) < 0))
            {
                llOwnerSay(toSourceString(callable, passables));
            }

            // Call with a list of parameters

            list parameters = indexParameters(callable, passables);
//          llOwnerSay("(" + llList2CSV(parameters) + ") == parameters");

            list quotables = resultOf(callable, parameters);
//          llOwnerSay("(" + llList2CSV(quotables) + ") == quotables");

            list listables = quoteResults(quotables);
//          llOwnerSay("(" + llList2CSV(listables) + ") == listables");

            results += listables;
        }

        // Push any other word as a parameter, without change

        else
        {
            results += value;
        }
    }

    // Succeed

    return results;
}

// Hear and echo and obey the chat of the owner at the channel.

default
{

    state_entry()
    {
//      llOwnerSay(llGetScriptName() + ".default.state_entry");
        llListen(theChannel, "", llGetOwner(), "");
    }

    dataserver(key queryid, string data)
    {
        llOwnerSay(toSourceString("dataserver", quoteResults([queryid]) + quoteResults([data])));
    }

    listen(integer channel, string name, key id, string message)
    {
//      llOwnerSay(llGetScriptName() + ".default.listen");

        // Compile and run

        llOwnerSay("// " + message);

        list words = separateWords(message, [], separators() + spacers);
//      llOwnerSay(llList2CSV(words) + " == words");

        list values = assignValues(words);
//      llOwnerSay(llList2CSV(values) + " == values");

        list results = fetchResults(values);
//      llOwnerSay(llList2CSV(results) + " == results");

        // Chat back the results, if not empty

        if (results != [])
        {
            llOwnerSay(stringGetBetween(toSourceString("", results), 1, -2));
        }
        
//      llOwnerSay("OK");
    }
}