Difference between revisions of "Chatbot"

From Second Life Wiki
Jump to navigation Jump to search
(add llSetLocalRot llApplyImpulse llApplyRotationalImpulse to demo kicking a translucent, bouncing, spinning box)
(teleport ~2m up the Z axis to demo verbose prefix arithmetic)
Line 6: Line 6:


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


<pre>
<pre>
/7 llSetText("look at me green", <0.0, 1.0, 0.0>, 1.0);
/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);
/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);
/7 llSetText("", <0.0, 0.0, 0.0>, 1.0); // do not label
</pre>
</pre>


<pre>
<pre>
/7 llSetStatus(STATUS_PHYSICS, FALSE);
/7 llSetStatus(STATUS_PHYSICS, FALSE);
/7 llSetLocalRot(llEuler2Rot(<0.0, 0.0, PI_BY_TWO>));
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetLocalRot(llEuler2Rot(ZERO_ROTATION));
/7 llSetLocalRot(llEuler2Rot(<0.0, 0.0, PI_BY_TWO>))); // face one way
/7 llSetLocalRot(llEuler2Rot(ZERO_ROTATION)); // face another way
/7 llSetStatus(STATUS_PHYSICS, TRUE);
/7 llSetStatus(STATUS_PHYSICS, TRUE);
/7 llSetBuoyancy(0.9);
/7 llSetBuoyancy(0.9); // bounce well, without floating
/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
Line 47: Line 48:
//
//
// 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.
//
//


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


integer theChannel = 7;
integer theChannel = 7;


// Describe the language of an LSL expression without infix operators
// Chat back a copy of the meaningful or meaningless command, only on request.
   
 
integer theEchoPlease = TRUE;
 
// Describe the language of an LSL expression without infix operators.
 
string escape = "\\";
string escape = "\\";
string quote = "\"";
string quote = "\"";
Line 61: Line 67:
list spacers = [quote, escape, "(", ")", "/", "<", ">", "[", "]"];
list spacers = [quote, escape, "(", ")", "/", "<", ">", "[", "]"];


// Evaluate any one parameter
// List some frequently useful code values.


list theCodes = [
list theCodes = [
Line 84: Line 90:
     0
     0
];
];
// List the name of each code.


list theCodenames = [
list theCodenames = [
Line 106: Line 114:
     "0"
     "0"
];
];
// Evaluate any one parameter.


list valueOf(string word)
list valueOf(string word)
Line 127: Line 137:
         }
         }
     }
     }
}
// 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 [];
}
}


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


list resultOf(string callable, list parameters)
list resultOf(string callable, list parameters)
{
{
    list ps = parameters; // abbreviate "parameters"
    llOwnerSay(callable + "(" + llList2CSV(ps) + ")");
   
    // Return one result to say callable found and one result collected
    // Or fall thru to say callable found and no result collected


     if ("llApplyImpulse" == callable) // force, local
    // Abbreviate the word "parameters"
 
    list ps = parameters;
 
    // Trace every call, even when the callable is meaningless
 
    if (theEchoPlease)
    {
        llOwnerSay(callable + "(" + llList2CSV(ps) + ")");
    }
 
    // Accept verbose prefix arithmetic such as { "+"(vec1, vec2) }
 
    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 results to say callable meaningful and returns results
    // Or fall thru to say callable meaningful but returns no result
 
     if ("llApplyImpulse" == callable) // (force, local)
     {
     {
         llApplyImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
         llApplyImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
     }
     }
     else if ("llApplyRotationalImpulse" == callable) // force, local
     else if ("llApplyRotationalImpulse" == callable) // (force, local)
     {
     {
         llApplyRotationalImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
         llApplyRotationalImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
     }
     }
     else if ("llEuler2Rot" == callable) // v
     else if ("llEuler2Rot" == callable) // (v)
     {
     {
         return [llEuler2Rot(llList2Vector(ps, 0))];
         return [llEuler2Rot(llList2Vector(ps, 0))];
     }
     }
     else if ("llGetAgentSize" == callable) // id
     else if ("llGetAgentSize" == callable) // (id)
     {
     {
         return [llGetAgentSize(llList2Key(ps, 0))];
         return [llGetAgentSize(llList2Key(ps, 0))];
     }
     }
     else if ("llGetLinkKey" == callable) // linknum
     else if ("llGetLinkKey" == callable) // (linknum)
     {
     {
         return [llGetLinkKey(llList2Integer(ps, 0))];
         return [llGetLinkKey(llList2Integer(ps, 0))];
     }
     }
     else if ("llGetNumberOfPrims" == callable) //
     else if ("llGetNumberOfPrims" == callable) // ()
     {
     {
         return [llGetNumberOfPrims()];
         return [llGetNumberOfPrims()];
     }
     }
     else if ("llRot2Euler" == callable) // q
    else if ("llGetPos" == callable) // ()
    {
        return [llGetPos()];
    }
    else if ("llGetLocalRot" == callable) // ()
    {
        return [llGetLocalRot()];
    }
    else if ("llGetlRot" == callable) // ()
    {
        return [llGetRot()];
    }
     else if ("llRot2Euler" == callable) // (q)
     {
     {
         return [llRot2Euler(llList2Rot(ps, 0))];
         return [llRot2Euler(llList2Rot(ps, 0))];
     }
     }
     else if ("llSetAlpha" == callable) // alpha, face
     else if ("llSetAlpha" == callable) // (alpha, face)
     {
     {
         llSetAlpha(llList2Float(ps, 0), llList2Integer(ps, 1));
         llSetAlpha(llList2Float(ps, 0), llList2Integer(ps, 1));
     }
     }
     else if ("llSetBuoyancy" == callable) // buoyancy
     else if ("llSetBuoyancy" == callable) // (buoyancy)
     {
     {
         llSetBuoyancy(llList2Float(ps, 0));
         llSetBuoyancy(llList2Float(ps, 0));
     }
     }
     else if ("llSetColor" == callable) // color, face
     else if ("llSetColor" == callable) // (color, face)
     {
     {
         llSetColor(llList2Vector(ps, 0), llList2Integer(ps, 1));
         llSetColor(llList2Vector(ps, 0), llList2Integer(ps, 1));
     }
     }
     else if ("llSetLocalRot" == callable) // rot
     else if ("llSetLocalRot" == callable) // (rot)
     {
     {
         llSetLocalRot(llList2Rot(ps, 0));
         llSetLocalRot(llList2Rot(ps, 0));
     }
     }
     else if ("llSetPos" == callable) // pos
     else if ("llSetPos" == callable) // (pos)
     {
     {
         llSetPos(llList2Vector(ps, 0));
         llSetPos(llList2Vector(ps, 0));
     }
     }
     else if ("llSetRot" == callable) // rot
     else if ("llSetRot" == callable) // (rot)
     {
     {
         llSetRot(llList2Rot(ps, 0));
         llSetRot(llList2Rot(ps, 0));
     }
     }
     else if ("llSetStatus" == callable) // status, value
     else if ("llSetStatus" == callable) // (status, value)
     {
     {
         llSetStatus(llList2Integer(ps, 0), llList2Integer(ps, 1));
         llSetStatus(llList2Integer(ps, 0), llList2Integer(ps, 1));
     }
     }
     else if ("llSetText" == callable) // text, color, alpha
     else if ("llSetText" == callable) // (text, color, alpha)
     {
     {
         llSetText(llList2String(ps, 0), llList2Vector(ps, 1), llList2Float(ps, 2));
         llSetText(llList2String(ps, 0), llList2Vector(ps, 1), llList2Float(ps, 2));
     }
     }
      
      
     // Return more than one result to say callable not found
     // Return an empty result if callable meaningless or returns no result
       
    else
    {
        return [callable, "not found"];
    }
   
    // Return no result to say callable found but no result
      
      
     return [];
     return [];
}
}


// Obey one command.
// Interpret one list of words.


list callEach(list words)
list callEach(list words)
Line 230: Line 329:
     {
     {
         string word = llList2String(words, index);
         string word = llList2String(words, index);
        integer beyond = llGetListLength(results);
          
          
         // Count results of "( ... )" or of "< ... >"
         // Count results of "( ... )" or of "< ... >"
Line 236: Line 334:
         if ((word == "(") || (word == "<"))
         if ((word == "(") || (word == "<"))
         {
         {
            integer beyond = llGetListLength(results);
             depths += [beyond]; // push the depth
             depths += [beyond]; // push the depth
         }
         }
Line 249: Line 348:
             // Pop the parameters
             // Pop the parameters


            integer beyond = llGetListLength(results);
             integer last = beyond - 1;
             integer last = beyond - 1;
             list parameters = [];
             list parameters = [];
Line 311: Line 411:
             else
             else
             {
             {
                 results += word;
                 results += llList2List(words, index, index);
             }
             }
         }
         }
Line 318: Line 418:
     // Succeed
     // Succeed


    if (llGetListLength(results) <= 1)
    {
        return [];
    }
     return llList2List(results, 1, -1); // pop the head (only if head is not tail)
     return llList2List(results, 1, -1); // pop the head (only if head is not tail)
}
}
Line 398: Line 502:
// Divide a string into words,
// Divide a string into words,
// by returning each of the spacers as a word
// by returning each of the spacers as a word
// and then all the chars between as words.
// and return as a word each run of chars between words.


list separateEach(string source, list spacers)
list separateEach(string source, list spacers)
Line 431: Line 535:
}
}


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


default
default
Line 441: Line 545:
     listen(integer channel, string name, key id, string message)
     listen(integer channel, string name, key id, string message)
     {
     {
         llOwnerSay(message);
 
         // Compile and run
 
//      llOwnerSay(message);


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


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


         list results = callEach(values);
         list results = callEach(values);
//      llOwnerSay((string) llGetListLength(results) + ": " + llList2CSV(results));
        // Chat back the results, if not empty
         if (results != [])
         if (results != [])
         {
         {

Revision as of 07:03, 6 September 2007

Compile and run the LSL you chat on a channel, faster than you can compile and run it thru the 2007-08 SL GUI.

Created by Ppaatt Lynagh. "Allow anyone to copy", "next owner can copy & modify", and "mark item for sale price L$0", are the rights on my copies of this script in SL, as of 2007-09-04.

The LSL commands you can chat to this script include the following demo of kicking a translucent, bouncing, spinning box:

/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
/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
/7 llSetStatus(STATUS_PHYSICS, FALSE);
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetLocalRot(llEuler2Rot(<0.0, 0.0, PI_BY_TWO>))); // face one way
/7 llSetLocalRot(llEuler2Rot(ZERO_ROTATION)); // face another way
/7 llSetStatus(STATUS_PHYSICS, TRUE);
/7 llSetBuoyancy(0.9); // bounce well, without floating
/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); // zero rot inertia
/7 ZERO_VECTOR
/7 llGetAgentSize(llGetLinkKey(llGetNumberOfPrims())) // often not ZERO_VECTOR while avatar sits

The commands shown above work well in the order shown with a 0.5 x 0.5 x 0.5 wood box such as the default object of the SL GUI. Sending these commands to other objects will show you how the effects of some commands that require STATUS_PHYICS TRUE do vary greatly with the mass of the object.

To keep this sample script conveniently short, this script understands only a few library function names, a few code constant names, a few punctuation marks, and a few data types. I hope you find it easy to add the library function names and code constant names that interest you, even prefix (not infix) function names like the "*" multiply operator. Adding the list data type as a possible parameter type might require real work: LSL doesn't understand lists of lists yet.

See the discussion tab for talk of more example command lines that we could/ should soon add.

Enjoy,

P.S. The code of the sample script follows:

// 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.
//

// 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 theEchoPlease = TRUE;

// Describe the language of an LSL expression without infix operators.

string escape = "\\";
string quote = "\"";
list separators = ["\t", " ", ",", ";"];
list spacers = [quote, escape, "(", ")", "/", "<", ">", "[", "]"];

// 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,

    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",

    "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 [];
}

// 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;

    // Trace every call, even when the callable is meaningless

    if (theEchoPlease)
    {
        llOwnerSay(callable + "(" + llList2CSV(ps) + ")");
    }

    // Accept verbose prefix arithmetic such as { "+"(vec1, vec2) }

    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 results to say callable meaningful and returns results
    // Or fall thru to say callable meaningful but returns no result

    if ("llApplyImpulse" == callable) // (force, local)
    {
        llApplyImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llApplyRotationalImpulse" == callable) // (force, local)
    {
        llApplyRotationalImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llEuler2Rot" == callable) // (v)
    {
        return [llEuler2Rot(llList2Vector(ps, 0))];
    }
    else if ("llGetAgentSize" == callable) // (id)
    {
        return [llGetAgentSize(llList2Key(ps, 0))];
    }
    else if ("llGetLinkKey" == callable) // (linknum)
    {
        return [llGetLinkKey(llList2Integer(ps, 0))];
    }
    else if ("llGetNumberOfPrims" == callable) // ()
    {
        return [llGetNumberOfPrims()];
    }
    else if ("llGetPos" == callable) // ()
    {
        return [llGetPos()];
    }
    else if ("llGetLocalRot" == callable) // ()
    {
        return [llGetLocalRot()];
    }
    else if ("llGetlRot" == callable) // ()
    {
        return [llGetRot()];
    }
    else if ("llRot2Euler" == callable) // (q)
    {
        return [llRot2Euler(llList2Rot(ps, 0))];
    }
    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 ("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));
    }
    
    // Return an empty result if callable meaningless or returns no result
    
    return [];
}

// Interpret one list of words.

list callEach(list words)
{

    // Begin with almost nothing

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

    // Take each action in order

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

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

            // Pop the depth

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

            // Pop the parameters

            integer beyond = llGetListLength(results);
            integer last = beyond - 1;
            list parameters = [];
            if (first <= last)
            {
                parameters = llList2List(results, first, last);
                results = llList2List(results, 0, first - 1);
            }

            // Join together the components of a vector

            if (word == ">")
            {
                integer depth = llGetListLength(parameters);
                if (depth == 3)
                {
                    vector vec;
                    vec.x = llList2Float(parameters, 0);
                    vec.y = llList2Float(parameters, 1);
                    vec.z = llList2Float(parameters, 2);
                    results += [vec];
                }

                // Join together the components of a rotation

                if (depth == 4)
                {
                    rotation rot;
                    rot.x = llList2Float(parameters, 0);
                    rot.y = llList2Float(parameters, 1);
                    rot.z = llList2Float(parameters, 2);
                    rot.s = llList2Float(parameters, 2);
                    results += [rot];
                }
            }

            // Call with a list of parameters (of non-list types)

            else if (word == ")")
            {
                string callable = llList2String(results, -1);
                results = llList2List(results, 0, -2); // pop the tail
                results += resultOf(callable, parameters);
            }
        }

        // Push any other word as a parameter

        else
        {
            if (llGetSubString(word, 0, 0) == quote)
            {
                if (word == (quote + quote))
                {
                    results += "";
                }
                else
                {
                    results += llGetSubString(word, 1, -2);
                }
            }
            else
            {
                results += llList2List(words, index, index);
            }
        }
    }

    // Succeed

    if (llGetListLength(results) <= 1)
    {
        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 quotation 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])) || (word == ""))
        {
            ;
        }

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

        // Evaluate other words
        
        else
        {
            values += valueOf(word);
        }
    }    

    // Succeed

    return values;
}

// 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;
}

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

default
{
    state_entry()
    {
        llListen(theChannel, "", llGetOwner(), "");
    }
    listen(integer channel, string name, key id, string message)
    {

        // Compile and run

//      llOwnerSay(message);

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

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

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

        // Chat back the results, if not empty

        if (results != [])
        {
            llOwnerSay(llList2CSV(results));
        }
    }
}