Chatbot

From Second Life Wiki
Revision as of 21:03, 4 September 2007 by Ppaatt Lynagh (talk | contribs) (add Chatbot script to compile and run the LSL you chat on a channel, faster than you can compile and run it thru the 2007-08 SL GUI.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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 include:

/7 llSetColor(<0.3, 0.3, 0.3>, ALL_SIDES);
/7 llSetColor(<1, 1, 1>, ALL_SIDES);
/7 llSetText("look at me white", <1.0, 1.0, 1.0>, 1.0);
/7 llSetText("look at me green", <0.0, 1.0, 0.0>, 1.0);
/7 llSetText("look at me black", <0.0, 0.0, 0.0>, 1.0);

The sample code follows.

To keep this sample code conveniently short, this example 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. Adding the list data type as a possible parameter type might require real work: LSL doesn't understand lists of lists yet. Adding arithmetic operators might require real work, but may be needed for experimenting with conversions between local and global coordinates.

I do think the SL GUI should let me toggle a switch to do this, i.e., to teach any prim of mine to listen to my LSL chat, for the sake of script folk like me who think by chatting. Meanwhile, I write code like this to let me chat commands to my prims anyhow. I wrote this sample just after walking thru the delightfully concise Getting Started with LSL tutorial, which only taught me to call llSetColor and llSetText. Likely I'll soon return here to add a few more example commands, after I learn some other fun commands.

Enjoy,

// Chatbot.lsl
//
// Compile and run the Lsl you type on a channel,
// faster than you can thru the 2007-08 SL GUI.
//

integer theChannel = 7;

// Evaluate any one parameter

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,

    0
];

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

    "0"
];

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

// Strip the leading and trailing quotes from a quoted evaluated string

string cbList2String(list parameters, integer index)
{
    return llGetSubString(llList2String(parameters, index), 1, -2);
}

// Pass the parameters to the named routine.
// Return no result, one result, else the callable and parameters.

list resultOf(string callable, list parameters)
{
    list ps = parameters; // abbreviate "parameters"
    llOwnerSay(callable + "(" + llList2CSV(ps) + ")");
    if ("llSetColor" == callable) // color, face
    {
        llSetColor(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llSetText" == callable) // text, color, alpha
    {
        llSetText(cbList2String(ps, 0), llList2Vector(ps, 1), llList2Float(ps, 2));
    }
    else
    {
        return [callable, "not found"];
    }
    return [];
}

// Obey one command.

list callEach(list words)
{

    // Begin with almost nothing

    list values = [""];
    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);
        integer beyond = llGetListLength(values);
        
        // Count values of "( ... )" or of "< ... >"
        
        if ((word == "(") || (word == "<"))
        {
            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 last = beyond - 1;
            list parameters = [];
            if (first <= last)
            {
                parameters = llList2List(values, first, last);
                values = llList2List(values, 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);
                    values += [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);
                    values += [rot];
                }
            }

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

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

        // Push any other word as a parameter

        else
        {
            values += word;
        }
    }

    // Succeed

    return llList2List(values, 1, -1); // pop the head
}

// Divide a string into words,
// by returning each of the spacers as a word
// and then all the chars between as words.

list split(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;
}

// 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(string chars)
{

    // Begin with nothing

    list values = [];

    // Describe the language of an LSL expression without operators
    
    string escape = "\\";
    string quote = "\"";
    list separators = ["\t", " ", ",", ";"];
    list spacers = [quote, escape, "(", ")", "/", "<", ">", "[", "]"];
    
    // Divide the command into words, but also shatter quotations

    list words = split(chars, separators + spacers);
    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;
        }

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

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

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

    // Succeed

    return values;
}

// Hear 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)
    {
        llOwnerSay(message);
        list values = valueEach(message);
//      llOwnerSay(llList2CSV(values));
        list results = callEach(values);
        if (results != [])
        {
            llOwnerSay(llList2CSV(results));
        }
    }
}