Chatbot

From Second Life Wiki
Revision as of 18:41, 6 September 2007 by Ppaatt Lynagh (talk | contribs) (remember to unquote strings even inside of lists)
Jump to navigation Jump to search

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, faster than you can tell the 2007-08 SL GUI to do the same.

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

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

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

Move and rotate while not physical, then kick and spin while physical and bouncy.

/7 llSetStatus(STATUS_PHYSICS, FALSE);
/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(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); // 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); // zero rot inertia

Poke around inside the object running the script:

/7 llGetAgentSize(llGetLinkKey(llGetNumberOfPrims())) // often not ZERO_VECTOR while avatar sits
/7 ZERO_VECTOR, FALSE, TRUE, STATUS_PHYSICS, PI // reveal some named code values

Chat a question for you the object's owner to answer (and then chat the answer that you chose):

/7 llDialog(llGetOwner(), "A clarifying demo?", ["No", "Yes"], 7); // chat some Q & A

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

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 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 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. "Allow anyone to copy", "next owner can copy & modify", and "mark item for sale price L$0", were the rights on Pat's copies of this script in SL, as of 2007-09-04.

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

The Script

// 2007-09-06 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 theShouldEcho = TRUE;

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

string escape = "\\";
string quote = "\"";
list separators = ["\t", " ", ",", ";"];
list spacers = [quote, escape, "(", ")", "/", "<", ">", "[", "]"];
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,

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

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

    // Accept 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 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 ("llDie" == callable) // ()
    {
        return [llDie()]; // CAUTION -- this call deletes this script without saving changes -- CAUTION
    }
    else if ("llDialog" == callable) // (avatar, message, buttons, channel)
    {
        llDialog(llList2Key(ps, 0),
            llList2String(ps, 1), list2ListEntry(ps, 2), llList2Integer(ps, 3));
    }
    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 ("llGetOwner" == callable) // ()
    {
        return [llGetOwner()];
    }
    else if ("llGetPos" == callable) // ()
    {
        return [llGetPos()];
    }
    else if ("llGetLocalRot" == callable) // ()
    {
        return [llGetLocalRot()];
    }
    else if ("llGetlRot" == callable) // ()
    {
        return [llGetRot()];
    }
    else if ("llListRandomize" == callable) // (src, stride)
    {
        return "[" + llListRandomize(list2ListEntry(ps, 0), llList2Integer(ps, 1)) + "]";
    }
    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));
    }
    else if ("llSleep" == callable) // (sec)
    {
        llSleep(llList2Float(ps, 0));
    }
    
    // Return an empty result if callable meaningless or returns no result
    
    return [];
}

// Return a list of one parameter per entry
// with the extra entries of longer parameters moved to the end.
//
// Also llOwnerSay the quoted results, if shouldEcho.

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

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

    // Take each entry in order

    string echoable = "";
    if (callable != "")
    {
        echoable += callable + "(";
    }

    integer index;
    integer lenEntries = llGetListLength(entries);
    for (index = 0; index < lenEntries; ++index)
    {
        list entry = llList2List(entries, index, index);

        // Quote keys as (implicit cast to key type from) string source
        
        string word = llList2String(entries, index);        
        if (llGetListEntryType(entries, index) == TYPE_KEY)
        {
//          word = "(key) " + quote + word + quote; // no
            word = quote + word + quote;
        }

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

        if ((word == "]") && (0 <= opened))
        {
            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

            pickables += depth;
            ++depth;
                
            // Add the entries on the right

            flats += parameters;
            flats += (lenParameters + 1); // also count the depth as a flat added

            // Consume the "[" from before

            opened = -1;
        }

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

        if (word == "[")
        {
            opened = llGetListLength(pickables);
        }
        else if (word == "]")
        {
            opened = -1;
        }
        
        // Unquote each string parameter simply

        else if (llGetListEntryType(entry, 0) == TYPE_STRING)
        {
            if (word == (quote + quote))
            {
                pickables += "";
            }
            else
            {
                pickables += llGetSubString(word, 1, -2);
            }
        }
        
        // Add the other parameters in order

        else
        {
            pickables += entry;
        }
    }
    
    if (callable != "")
    {
        echoable += ");";
    }

    // Trace on request

    if (shouldEcho)
    {
        llOwnerSay(echoable);
    }

    // Succeed

    return pickables + flats;
}

// Fetch the indexed list parameter.

list list2ListEntry(list parameters, integer index)
{

    // Step back to the start of this last parameter

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

    // Find this list parameter

    integer lengthPlus = llList2Integer(parameters, offset + 0);
    integer lenEntries = (lengthPlus - 1);

    list entries = [];
    if (lenEntries != 0)
    {
        entries = llList2List(parameters, offset - lenEntries, offset - 1);
    }

    // Succeed

    return entries;
}

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

list quoteEach(list results)
{
    
    // Begin with nothing
    
    list words = [];
    
    // 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 simply
        
        if (llGetListEntryType(results, index) == TYPE_STRING)
        {
            string word = llList2String(results, index);
            words += quote + word + quote;
        }
        
        // List all other results unchanged
        
        else
        {
            list result = llList2List(results, index, index);
            words += result;
        }
    }
    
    // Enclose a list of zero or more results in [ ... ], else return one result
    
    if (1 < beyond)
    {
        return "[" + words + "]";
    }
    
    return words;
}

// 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
            
            list entries = [];
            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

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

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

            // Call with a list of parameters

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

            list quotations = quoteEach(quotables);
//          llOwnerSay(callable + "(" + llList2CSV(quotations) + ")r");
            
            results += quotations;
        }

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

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

    // 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 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])) || (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 != [])
        {
            integer shouldEcho = TRUE;
            pickEach("", results, shouldEcho); // llOwnerSay the quoted results
        }
    }
}