Difference between revisions of "Chatbot"

From Second Life Wiki
Jump to navigation Jump to search
(add an example to show an expression with results, results as parameters, and comments (and tweak valueEach to discard comments))
(Point to the Talk:Chatbot discussion of arithmetic operators, etc.)
Line 22: Line 22:
</pre>
</pre>


The sample code follows.
The sample script 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.
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.


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 [[Help:Getting_started_with_LSL|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.
See the [[Talk:Chatbot|discussion]] tab for talk of more example command lines that we could/ should soon add.


Enjoy,
Enjoy,

Revision as of 07:31, 5 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 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);
/7 llSetText("", <0.0, 0.0, 0.0>, 1.0);
/7 ZERO_VECTOR
/7 llGetAgentSize(llGetLinkKey(llGetNumberOfPrims())) // often not ZERO_VECTOR while avatar sits

The sample script follows.

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,

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

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)
{
    string chars = llList2String(parameters, index);
    if (llStringLength(chars) <= 2)
    {
        return "";
    }
    return llGetSubString(chars, 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) + ")");
    
    // Return one result to say callable found and one result collected
    // Or fall thru to say callable found and no result collected

    if ("llGetAgentSize" == callable) // id
    {
        return [llGetAgentSize(llList2Key(ps, 0))];
    }
    else if ("llGetLinkKey" == callable) // linknum
    {
        return [llGetLinkKey(llList2Integer(ps, 0))];
    }
    else if ("llGetNumberOfPrims" == callable) // linknum
    {
        return [llGetNumberOfPrims()];
    }
    else 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));
    }
    
    // Return more than one result to say callable not found
        
    else
    {
        return [callable, "not found"];
    }
    
    // Return no result to say callable found but no result
    
    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 (only if head is not tail)
}

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

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

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