diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-24 02:13:59 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-24 02:13:59 +0000 |
commit | a6d58bb6052ac8cb01805a48c4ad2f129126116f (patch) | |
tree | dd867a099fcbb263a8009a9fb22695b87855dad6 /src/kvirc/kvs/kvi_kvs_parser.cpp | |
download | kvirc-a6d58bb6052ac8cb01805a48c4ad2f129126116f.tar.gz kvirc-a6d58bb6052ac8cb01805a48c4ad2f129126116f.zip |
Added KDE3 version of kvirc
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kvirc@1095341 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/kvirc/kvs/kvi_kvs_parser.cpp')
-rw-r--r-- | src/kvirc/kvs/kvi_kvs_parser.cpp | 3827 |
1 files changed, 3827 insertions, 0 deletions
diff --git a/src/kvirc/kvs/kvi_kvs_parser.cpp b/src/kvirc/kvs/kvi_kvs_parser.cpp new file mode 100644 index 0000000..37e462c --- /dev/null +++ b/src/kvirc/kvs/kvi_kvs_parser.cpp @@ -0,0 +1,3827 @@ +//============================================================================= +// +// File : kvi_kvs_parser.cpp +// Creation date : Thu 25 Sep 2003 05.12 CEST by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2003 Szymon Stefanek (pragma at kvirc dot net) +// +// This program is FREE software. You can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your opinion) any later version. +// +// This program is distributed in the HOPE that it will be USEFUL, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, write to the Free Software Foundation, +// Inc. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#define __KVIRC__ + + +#include "kvi_kvs_parser.h" + +#include "kvi_kvs_treenode.h" + +#include "kvi_kvs_report.h" +#include "kvi_kvs_kernel.h" + +#include "kvi_kvs_script.h" + +#include "kvi_kvs_parser_macros.h" + +#include "kvi_locale.h" + +#include "kvi_options.h" + +//FIXME: @ == $$-> == $this-> + +KviKvsParser::KviKvsParser(KviKvsScript * pScript,KviWindow * pOutputWindow) +{ + // no need to initialize m_pBuffer + // no need to initialize m_ptr + // no need to initialize m_bError + m_pGlobals = 0; + m_pScript = pScript; + m_pWindow = pOutputWindow; +} + +KviKvsParser::~KviKvsParser() +{ + if(m_pGlobals)delete m_pGlobals; +} + + + + +void KviKvsParser::init() +{ + KviKvsKernel * pKern = KviKvsKernel::instance(); + +#define _REG_CNTRL_CMD(__cntrlCmdName,__parsingRoutine) \ + { \ + KviKvsSpecialCommandParsingRoutine * r = new KviKvsSpecialCommandParsingRoutine; \ + r->proc = KVI_PTR2MEMBER(KviKvsParser::__parsingRoutine); \ + pKern->registerSpecialCommandParsingRoutine(QString(__cntrlCmdName),r); \ + } + + _REG_CNTRL_CMD("if",parseSpecialCommandIf); + _REG_CNTRL_CMD("global",parseSpecialCommandGlobal); + _REG_CNTRL_CMD("while",parseSpecialCommandWhile); + _REG_CNTRL_CMD("break",parseSpecialCommandBreak); + _REG_CNTRL_CMD("do",parseSpecialCommandDo); + _REG_CNTRL_CMD("for",parseSpecialCommandFor); + _REG_CNTRL_CMD("foreach",parseSpecialCommandForeach); + _REG_CNTRL_CMD("switch",parseSpecialCommandSwitch); + _REG_CNTRL_CMD("defpopup",parseSpecialCommandDefpopup); + _REG_CNTRL_CMD("unset",parseSpecialCommandUnset); + _REG_CNTRL_CMD("class",parseSpecialCommandClass); + _REG_CNTRL_CMD("help",parseSpecialCommandHelp); + +#undef _REG_CNTRL_CMD +} + +void KviKvsParser::report(bool bError,const QChar * pLocation,const QString &szMsgFmt,kvi_va_list va) +{ + QString szMsg; + KviQString::vsprintf(szMsg,szMsgFmt,va); + + KviPointerList<QString> * pCodeListing = 0; + QString szLocation; + + if(pLocation) + { + pCodeListing = new KviPointerList<QString>; + pCodeListing->setAutoDelete(true); + + int iLine,iCol; + + KviKvsReport::findLineColAndListing(m_pBuffer,pLocation,iLine,iCol,pCodeListing); + + KviQString::sprintf(szLocation,__tr2qs("line %d, near character %d"),iLine,iCol); + } else { + szLocation = __tr2qs("beginning of input"); + } + + KviKvsReport rep(bError ? KviKvsReport::ParserError : KviKvsReport::ParserWarning,m_pScript->name(),szMsg,szLocation,m_pWindow); + if(pCodeListing)rep.setCodeListing(pCodeListing); + + KviKvsReport::report(&rep,m_pWindow); +} + +void KviKvsParser::errorBadChar(const QChar * pLocation,char cExpected,const char * szCommandName) +{ + if(pLocation->unicode()) + error(pLocation,__tr2qs("Found character '%q' (unicode 0x%x) where '%c' was expected: see \"/help %s\" for the command syntax"), + pLocation,pLocation->unicode(),cExpected,szCommandName); + else + error(pLocation,__tr2qs("Found end of input where character '%c' was expected: see \"/help %s\" for the command syntax"), + cExpected,szCommandName); +} + +void KviKvsParser::error(const QChar * pLocation,const QString &szMsgFmt,...) +{ + m_bError = true; + + kvi_va_list va; + kvi_va_start_by_reference(va,szMsgFmt); + report(true,pLocation,szMsgFmt,va); + kvi_va_end(va); +} + +void KviKvsParser::warning(const QChar * pLocation,const QString &szMsgFmt,...) +{ + kvi_va_list va; + kvi_va_start_by_reference(va,szMsgFmt); + report(false,pLocation,szMsgFmt,va); + kvi_va_end(va); +} + +KviKvsTreeNodeInstruction * KviKvsParser::parse(const QChar * pBuffer,int iFlags) +{ + m_iFlags = iFlags; + + m_bError = false; + if(m_pGlobals)m_pGlobals->clear(); // this shouldn't be needed since this is a one time parser + + m_pBuffer = pBuffer; + m_ptr = pBuffer; + + if(!pBuffer) + { + error(0,__tr2qs("Empty script")); + return 0; + } + return parseInstructionList(); +} + +KviKvsTreeNodeInstruction * KviKvsParser::parseAsExpression(const QChar * pBuffer,int iFlags) +{ + m_iFlags = iFlags; + + m_bError = false; + if(m_pGlobals)m_pGlobals->clear(); // this shouldn't be needed since this is a one time parser + + m_pBuffer = pBuffer; + m_ptr = pBuffer; + + if(!pBuffer) + { + error(0,__tr2qs("Empty script")); + return 0; + } + + KviKvsTreeNodeExpression * expr = parseExpression(0); + if(!expr)return 0; + return new KviKvsTreeNodeExpressionReturn(pBuffer,expr); +} + +KviKvsTreeNodeInstruction * KviKvsParser::parseAsParameter(const QChar * pBuffer,int iFlags) +{ + m_iFlags = iFlags; + + m_bError = false; + if(m_pGlobals)m_pGlobals->clear(); // this shouldn't be needed since this is a one time parser + + m_pBuffer = pBuffer; + m_ptr = pBuffer; + + if(!pBuffer) + { + error(0,__tr2qs("Empty script")); + return 0; + } + + KviKvsTreeNodeDataList * l = parseCommandParameterList(); + if(!l)return 0; + + return new KviKvsTreeNodeParameterReturn(pBuffer,l); +} + + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// THE REAL KVS +// +// <script> ::= <instruction list> '\0' +// +// <instruction list> ::= <instruction> [ <instruction list> ] +// <instruction> ::= <instruction block> | <command> | <operation> | <comment> +// <instruction block> ::= '{' <instruction list> '}' +// +// <comment> ::= <bash style line comment> | <c++ style comment> | <c style comment> +// <bash style comment> ::='#' <any char not including newline or null> <newline or null> +// <c++ style comment> ::= '//' <any char not including newline or null> <newline or null> +// <c style comment> ::= '/*' <any char not including null or the sequence */> '*/' +// +// <command> ::= <simple command> | <callback command> | <control command> +// <simple command> ::= <core command> | <module command> | <alias command> +// <core command> ::= <command identifier> <switch list> <command parameter list> <command terminator> +// <switch list> ::= '-'<switch body> [<switch list>] +// <command parameter list> ::= <command parameter>[<space><command parameter>] +// <command parameter> ::= <command parameter part>[<command parameter>] +// <command parameter part> ::= <command literal parameter> | <string parameter> | <data evaluation> +// <command literal parameter> ::= <anything except space , null , newline , ; , " , $ or %> +// <string parameter> ::= '"'<string parameter body>'"' +// <string parameter body> ::= <anything except '"' or null>... +// <command terminator> ::= ';' | '\n' | '\0' + +// <data> ::= +// <data_reference> ::= <function_call> | <structured_data> +// <structured_data> ::= <data_structure> | <variable> | <pointer_data> +// <data_structure> ::= <array> | <hash> +// <array> ::= '%'<identifier>'[]' +// <hash> ::= '%'<identifier>'{}' +// <variable> ::= '%'<identifier> | <array_element> | <hash_element> +// <array_element> ::= '%'<identifier>'['<expression>']' +// <hash_element> ::= '%'<identifier>'{'<key>'}' +// <function_call> ::= <simple_function_call> | <pointer_function_call> +// <simple_function_call> ::= '$'<identifier>'('<function_parameter_list>')' +// <pointer_function_call> ::= <variable>'->'<function_call> | <simple_function_call>'->'<function_call> +// <pointer_data> ::= <variable>'->'<data>' | <simple_function_call>'->'<data> + + +// +// This must evaluate SCOPE OBJECT operators! +// thus...first evaluate the <data> or <simple_function_call> +// If it was <simple_function_call> or <data> evaluation results in returning a <variable> +// then check if there is a scope operator +// if there is then take it as the top of the tree, the <variable> or <simple_function_call> +// go at the left param of the scope operator and re-evaluate the right side <data> or <simple_function_call> +// + + + + + + + +/* + @doc: kvs_introduction + @type: + language + @keyterms: + kvs, compilation + @title: + KVIrc scripting language introduction + @short: + KVIrc scripting language introduction + @body: + [p] + [b]KVS[/b] is the [b]KV[/b]irc [b]S[/b]cripting language. + It was inspired by C++,sh,perl,php and mIrc scripting language implementations. + It is a compromise between flexibility and speed, a 'workaround' for many intrinsic + problems of an IRC-oriented scripting language. + [/p] + [p] + KVS is semi-interpreted: the execution is done in two main stages. + The first stage is the compilation where a syntactic tree is built. + The second stage is the real execution and is performed by visiting the tree + in the proper order. The syntactic trees are cached in memory so + the next executions can jump directly into the second stage. + This two-stage approach has been introduced in version 3.0.0, the previous + versions of the language used a single-stage on-the-fly interpreter. + [/p] + [p] + KVS allows you to:[br] + [ul] + [li]Implement automated reactions to the events generated by an IRC network[/li] + [li]Add new complex commands[/li] + [li]Add interface elements like popups, toolbars, buttons...[/li] + [li]Add advanced interface elements like complete dialogs or even widgets integrated in KVIrc[/li] + [/ul] + [/p] + [p] + KVS contains all the common constructs of structured programming. + You will find almost all the C control commands, sh/perl-like variables, arrays and and functions. + There are also some object-oriented characteristics: you will find C++ like + objects with constructors, destructors and class inheritance. + There are also more exotic concepts like the signal-slots interobject-communication. + Obviously you will also find most of the RFC1459 IRC commands and + other tools to "play" with an IRC connection. + [/p] + [p] + I'll try to explain the language by using examples + instead of strict syntactic rules. (Actually I have even + tried to write the rules...take a look [doc:syntactic_rules]here[/doc][br][br] + And please...forgive me for my "fantastic" english :) + [/p] + Szymon Stefanek +*/ + +/* + @doc: kvs_basicconcepts + @type: + language + @keyterms: + script + @title: + KVS basic concepts + @short: + KVS basic concepts + @body: + [big]Scripts[/big] + [p] + You use KVS to implement [b]scripts[/b]. + A script is basically a finite list of KVS instructions. + When you type a command in the KVIrc input window you in fact + execute a small one-line script. You can store + longer scripts in KVIrc memory and execute them at later time. + Scripts can be also read from external files by the means of the + [cmd]parse[/cmd] command. + [/p] + [p] + There is an issue with the word [i]script[/i] that is worth clearing here. + It is common usage to call [i]script[/i] a thing that is something more + that a finite list of (some scripting language) instructions. + In fact a set of scripts, documentation files, graphics or other multimedia + files and sometimes executable binaries is still called a [i]script[/i]...just like + the "PrincoScript" or "dynamirc" (tough this last one should be categorized as "malware" instead). + In KVIrc such a collection of items is called [i]addon[/i], but be prepared + for both usages of the word in this documentation and around the web. + [/p] + [p] + More about addons in this [doc:addons]document[/doc] + [/p] + + [big]Hello world![/big] + [p] + This documentation contains a lot of script examples. + They will appear like the following block of code: + [example] + [cmd]echo[/cmd] Hello world! + [/example] + The best way to experiment is to execute the scripts from an external file. + Try to copy & paste the example above to a file and save it in + a known place. Then in the command input window type + [example] + [b]/[/b][cmd]parse[/cmd] <filename> + [/example] + where <filename> stands for the name of the file you just have saved. + Some simple examples (like the one above) can be also typed + directly in the command input window. + You must remember that the command input window needs + a leading slash ('/') character to recognize a script. + The command input window can also be put in multiline mode by clicking + on the button on the right. + Another alternative for testing scripts is the code tester window. + You can access it by selecting "New code tester" from the Scripting menu + at the top of the KVIrc window. You will soon have the opportunity to + experiment with all the methods. Read on. + [/p] + + [big]Basic syntax[/big] + [p] + A script contains a list of instructions separated by newlines or ';' characters. + Placing an instruction per line does not require a terminating character, + placing more instructions in a single line require them to be separated by ';'. + The most common instructions in KVS are [b]commands[/b]. A command is basically + a keyword followed by a list of space separater parameters. + The simplest (and the most useful) command in KVS is [cmd]echo[/cmd]; it prints + all its parameters to a KVIrc window.[br] + The following is an example of a valid script that uses only [cmd]echo[/cmd] commands. + [example] + echo "This is the first line" + ECHO This is the second line; + echo "This is the third line"; echo This is still on the third line; + eChO "This is the fourth line"; Echo "This is still on the fourth line" + [/example] + You have probably noticed that the terminating ';' character is optional + when the command is the last in a line. + The commands are [b]case insensitive[/b]; 'echo' is equivalent to 'Echo', + to 'ECHO' and to 'eChO'. In fact, most of KVS is case insensitive. + (There are obvious unavoidable exceptions for this rule; for example, + on UNIX systems file names are case sensitive and this must be + also reflected in KVS). + Another interesting thing is that when you execute the script you + don't see the enclosing quotes around the printed text: more about this + in the following sections.[br] + [note] + Cryptic note (you may skip it for now):[br] + Yes, the command terminator is a problem for those that want to use ';)' at the end + of IRC commands like [cmd]msg[/cmd]. It is almost unavoidable (read: the cost for + avoiding it is too high). Note that using '|' or any other character as command terminator + will NOT solve the problem: if the terminator is too difficult to type it will annoy the + scripters (and me), if it is too easy then there will be always someone that wants to use it + at the end (or in the middle) of a command with the original meaning. + The solution is to escape the ';' character: + [example] + [cmd]echo[/cmd] You can do it now \;) + [/example] + [/note] + [/p] + + [big]Parameter processing[/big] + [p] + Most of the commands accept (and sometimes require) a list of parameters. + For example, the [cmd]join[/cmd] command (that is used to join an IRC channel) + accepts two parameters: the first one is the channel to join and the second is + the password. The simplified syntax for join is: + [example] + [cmd]join[/cmd] <channel> [password] + [/example] + The line above is an example of syntax specification. All the commands + are described by such syntax lines. [cmd]join[/cmd] is the command and it stands exactly + for the literal string "join" typed in a script. <channel> is in angular parenthesis + and rappresents a mandatory parameter: you must substitute a real channel name in its place + otherwise the command will fail and KVIrc will probably complain too. + [password] is still a parameter but the square parentheses indicate that it is + optional: if you specify it, then it will be interpreted as the channel password, + if you don't then no password will be used. + [note] + The syntax is written in a simplified BNF. I say simplified because it is not + totally strict around the KVIrc documentation. I just prefer the syntax to be + clear and easy to read instead of being formally perfect. + [/note] + You can finally join a channel by writing: + [example] + [cmd]join[/cmd] #kvirc kvircrocks + [/example] + or , since #kvirc usually has no password , by writing: + [example] + [cmd]join[/cmd] #kvirc + [/example] + In the example above the optional parameter [password] is omitted. + [note] + In fact it is not really omitted: KVIrc interprets it as an empty string that later + means "do not send the password to the server". + Empty strings are equivalent to omitted ones. + [/note] + [/p] + + [big]Parameters, spaces and quotes[/big] + [p] + From the examples above is obvious that KVS command parameters are separated by spaces. + What is not totally obvious is that multiple spaces are allowed but KVIrc + will automatically reduce them to exactly one (just like HTML parsers or the shell + interpreters do). This is an useful behaviour in an IRC client since spaces usually + carry no information and in text oriented protocols make the parsing really harder (:D). + [/p] + [p] + The spaces are simplified in normal processing but there are ways to force KVIrc + to interpret the spaces just as they are. + The first method are the quotation marks: all the spaces enclosed in quotation marks + will be preserved. + [example] + [cmd]echo[/cmd] This text will have spaces simplified + [cmd]echo[/cmd] But "this one not" + [/example] + The first example will print out with spaces simplified but the second not. + The quotes are also a nice trick to embed spaces into a single parameter that + would be obviously splitted in two or more. + [example] + [cmd]echo[/cmd] Parameter1 Parameter2 "Parameter 3 ( with spaces )" Parameter4 + [/example] + By running the examples above you may have noticed that the spaces are preserved but the + quotes are then stripped! Yes, this is another tricky behaviour. But don't be afraid: + it is really easier to use than to explain. + There is obviously a method to preserve the quotes too and it is also another + method to preserve the spaces but that leads us to the next paragraph. + [/p] + + [big]Escape character[/big] + [p] + You may have already noticed that KVS treats some characters in a special way. + For example the double-quote characters can be used to enclose strings + and are stripped by the parser. + Another example of a special character is the command terminator (';'): + it has the "special" meaning of terminating a command. + If you want to enclose a literal quote in your text, you need to [b]escape[/b] it. + Like in most other programming languages, the escaping character is the backslash ('\'). + [example] + [cmd]echo[/cmd] You can smile this way too! \;) + [/example] + The above example will treat the ';' as a part of the parameters and print it.[br] + In some languages the action of "escaping" a character is called "quoting". + Altough there is some confusion in this term, the meaning is to either use quotes + or to use the escape character to remove special meaning from some characters. + By quoting the spaces you can include them in a parameter, by escaping the quotes + you can include them in a command. + [example] + [cmd]echo[/cmd] "And he said \"Hello world!\"" + [/example] + The example above will have the internal quotes preserved. + You can use the escape backslash to escape a newline: + [example] + [cmd]echo[/cmd] This text will be \ + printed on a single line! + [/example] + After an escaped newline all the leading space and tab characters are skipped, + so you must include the needed spaces [b]before[/b] the escape character. + The previous example will be printed as:[br][br] + [i]This text will be printed on a single line[/i][br] + Another example:[br] + [example] + [cmd]echo[/cmd] "The new kvirc   \ + IS OUT!" + [cmd]echo[/cmd] Check it out at http://www.kvi\ + rc.net! + [/example] + This will be printed as:[br][br] + [i] + The new kvirc IS OUT![br] + Check it out at http://www.kvirc.net! + [/i][br] + Finally, you can escape an escape character to include it literally in a parameter (and + this is really the end of these tricks :) + Later we will discover other common usages of the backslash escape, such + as preventing KVIrc from interpreting a literal percent character as a variable + or separating variable names from the text. + [/p] + + [big]Command switches[/big] + [p] + Many commands accept switch parameters. + [b]A switch modifies the behaviour of a command.[/b] + Any switch can optionally accept a parameter, that must + be specified after an equal ('=') sign. + [example] + [cmd]echo[/cmd] [b]-i = 2[/b] This text uses a specific color scheme + [/example] + The -i switch (just for example) changes the attributes + and the icon of the printed text. + [b]The switch must be specified immediately after the command keyword.[/b] + [example] + [cmd]echo[/cmd] This -i = 2 will obviously not work... + [/example] + If you want to start the first parameter of a command (that is not a switch) + with a literal '-' you must again escape it: + [example] + [cmd]echo[/cmd] \--- This text has three minus signs on the left + [/example] + or use the quotes: + [example] + [cmd]echo[/cmd] "--- This text has three minus signs on the left" + [/example] + [/p] + + [big]Command blocks[/big] + [p] + Commands can be 'grouped' in blocks by using the classic C++ braces. + Here is a single line example:[br] + [example] + { [cmd]echo[/cmd] First command; [cmd]echo[/cmd] Second command; } [cmd]echo[/cmd] Third command + [/example] + Multi line example:[br] + [example] + { + [cmd]echo[/cmd] First command + [cmd]echo[/cmd] Second command + } + [cmd]echo[/cmd] Third command + [/example] + [note] + Reminder : copy the example above to a text file + and then use /[cmd]parse[/cmd] <filename> + [/note] + In this case the command block has no special meaning + other than making the code more readable , but command blocks + will be useful later (see [cmd]if[/cmd],[cmd]while[/cmd]...).[br] + [note] + Unlike in C or C++, the braces do NOT automatically define a variable scope + (with few exceptions to this rule ... just to complicate the things a bit more). + You will recall this last assertion later, when reading about [doc:data_structures]data structures[/doc]. + [/note] + [/p] + + [big]Comments[/big] + [p] + KVIrc supports comments in command sequences.[br] + A comment starts with the character '#' and terminates with a newline. + You can start a comment anywhere a command can start.[br] + [example] + # This is a comment , it occupies the whole line + [cmd]echo[/cmd] After the comment!; # This is an end-line comment + [/example] + You can't escape newline characters in this case. + (or better: escape characters have no meaning in comments... + maybe one day I'll implement it).[br] + Starting from version 3.0.0 kvirc supports also C++ single line and C multiline comments.[br] + A C++ comment starts with two slashes '//' and terminates with a newline. + A multiline C comment starts with '/*' and ends at the first '* /' encountered. + Since KVIrc has no pre-processor, the C/C++ comments usually can't be placed in the middle of a command: + they must start where a command would start and end before the begin of another command.[br] + [/p] + + [big]Indentation[/big] + [p] + You [b]should[/b] use spaces or [b]tabs[/b] to [b]indent[/b] your code. Note that the [b]should[/b] + word is written in bold characters: I mean that you really should indent your code. + Indenting helps both you (the script writer) and the reader (any other user that will + read your script). A good indenting practice is the first step to become a great programmer :) + [note] + Please note that the command parameters should be separated by + space characters (ascii 32). Tabs are not granted to work as parameter separators.[br] + [/note] + [example] + { + <tab>[cmd]echo[/cmd] Indented command + <tab>{ + <tab><tab># Comment + <tab><tab>[cmd]echo[/cmd] Really Really long indented \ + <tab><tab><tab>command + <tab>} + } + [/example] + Tabs behave better than spaces as indentation characters since other users can + adjust the tab size to match their taste. I personally prefer 4 character tabs + while most text/code editors usually come with 8 characters as default. + [/p] + + [big]And now ?[/big] + [p] + You're now ready to really start experimenting with KVS. You can take + a look at the [doc:commands]command index[/doc] and start trying to use them + while keeping in mind the rules described in this document. + The next suggested lecture is the documentation about [doc:kvs_aliasesandfunctions]the aliases and the functions[/doc]. + Have fun :) + [/p] +*/ + +/* + @doc: kvs_aliasesandfunctions + @type: + language + @keyterms: + aliases, functions + @title: + KVS Functions and aliases + @short: + KVS Functions and aliases + @body: + [big]Introduction[/big] + [p] + Since you're here, you should already have readed about the [doc:kvs_basicconcepts]KVS basic concepts[/doc] + and have visited the [doc:commands]command index[/doc]. If you feel ready to take the next step + then read on. + [/p] + + [big]Functions[/big][br] + [p] + KVS has many internal [doc]functions[/doc] that can be used as command parameters.[br] + [b]All the function names start with a literal '$' character.[/b][br] + [example] + [cmd]echo[/cmd] This window caption is [fnc]$window.caption[/fnc] + [/example] + The [fnc]$window.caption[/fnc] [doc:functions]function[/doc] + is evaluated before the command executes, + and it is changed into the current window caption text.[br] + The [doc]functions[/doc] can be used also as switch parameters.[br] + [example] + [cmd]echo[/cmd] -w = [fnc]$window[/fnc] This text will be surely \ + printed in the current window + [/example] + The -w switch allows to redirect the echo text to a specified window --- in this + case the one that you are typing in.[br] + [i](Surprise: in this case the -w switch is useless , + since echo prints text to the current window by default... + but it will work correctly. :)[/i] + [/p] + [p] + Normal function names can be made of "anycase" letters, digits and underscores, + with the restriction that the first character is not a digit.[br] + Some kind of functions can contain a dot '.' character inside the name + and these are assumed to be module references (see [doc:modules]the modules documentation[/doc]).[br] + [/p] + [p] + By now we have seen only simple functions, but there's more...[br] + The functions can accept parameters; the general syntax for a function call is:[br] + [b]$<function name>['('<parameter_list>')'][/b][br] + where <parameter_list> is a list of comma separated parameters, + eventually empty. + [example] + [cmd]echo[/cmd] The word 'neural' is [fnc]$str.len[/fnc](neural) characters long + [/example] + The function [fnc]$str.len[/fnc] accepts a single parameter and returns the + length in characters of the parameter string. The returned value is always + a string: in this case it can be also interpreted as a number.[br] + When passing an empty list you can avoid the parenthesis. + (And you have found the "simple" functions shown above). + So the followind two calls are equal:[br] + [example] + [cmd]echo[/cmd] [fnc]$window.caption[/fnc] + [cmd]echo[/cmd] [fnc]$window.caption()[/fnc] + [/example] + If you want to pass an "empty" string as the first parameter you have to use + the following syntax:[br] + [example] + [cmd]echo[/cmd] [fnc]$str.len[/fnc]("") + [/example] + Obviously a function is valid as a function parameter.[br] + [example] + [cmd]echo[/cmd] [fnc]$str.len[/fnc]([fnc]$window.caption[/fnc]) + [/example] + If you want to place a literal '(' or ')' in the function parameters + you must escape it. + A special case for when you want to use 'matching' parentheses: + an opened '(' corresponds to a closed ')'. + In this case you can omit the 'escape' character.[br] + [example] + [cmd]echo[/cmd] The length of '(a+b)' is : [fnc]$str.len[/fnc]( (a+b) ) + [/example] + This is useful for algebraic and boolean expressions , like the ones + accepted by the special function $() (see next paragraphs).[br] + [/p] + + + [big]Aliases[/big][br] + An alias is an user defined command. It can be used to rename the builtin kvirc commands or functions, + to automatize complex tasks or as structured programming mean. + Aliases can be created or destroyed by using the scriptcenter (graphic interface) + or from the commandline (or script) by using the [cmd]alias[/cmd] command. + Once created, an alias remains stored permanently in the KVIrc configuration files + until it is explicitly deleted. + A couple of examples will make the things clear. + join is a really commonly used command. It might be a good idea to rename it to + simply "j" .. just to type it faster. + Nothing easier in KVirc: just try this commandline: + [example] + [cmd]alias[/cmd](j){ [cmd]join[/cmd] $0-; }; + [/example] + + This will create the alias "j". From this moment you can use /j as it was a normal command. + [example] + j #kvirc + [/example] + You may have notices the strange $0- function in the alias body: it stands for + "all parameters passed to the alias". This means that when you call + [example] + j #kvirc testpassword + [/example] + then both the parameters (#kvirc and testpassword) are passed to the join command. + The $N functions are special functions that return the positional parameters passed + to the current script context. In an alias the script context is the script body and + it is the alias caller that generates the parameters. + $N (where N is a digit) returns the (N-1)-th positional parameter passed by the caller. + It returns the parameter numbered N-1 and not N since the parameters are indexed starting + from zero ($0 is the first parameter!). + $N-M returns the parameters from (N-1)-th to the (M-1)-th (a parameter range) and $N- returns + all the parameters from (N-1)-th to the last one. In the example above $0- stands for + all the parameters starting from the first one. + [/p] + [p] + To remove an alias use again the alias command with an empty body: + [example] + [cmd]alias[/cmd](j){} + [/example] + This will remove the alias "j" defined above. + [/p] + [p] + A common task in channel management is the kick & ban action. + You first ban an user from the channel and then eventually kick him + (obviously assuming that he is actually on the channel). + This involves using two commands: ban and then kick. + It could be a nice idea to have a single "kb" command to perform this action. + Well...easy: + [example] + [cmd]alias[/cmd](kb){ [cmd]ban[/cmd] $0; [cmd]kick[/cmd] $0-; }; + [/example] + This adds the "kb" alias: it can be called as a normal command: + [example] + kb spammer You're not welcome here! + [/example] + This will first execute "ban spammer" and then "kick spammer You're not welcome here". + Our kb is a really simple example... it doesn't check for the validity of the parameters: + the server will warn us if the parameters passed to kb were empty. + [/p] + [p] + The alias can be modified at any time by re-using the alias command. + Let's make our "kb" a bit more intelligent and add a check for the parameters. + TIP: It is a good idea to write the following examples in a text file and then use /parse <filename> to execute it. + [example] + [cmd]alias[/cmd](kb) + { + [cmd]if[/cmd]("$0" == "") + { + [cmd]echo[/cmd] "Usage: /kb <nickname> <kick reason>" + [cmd]return[/cmd] + } + [cmd]ban[/cmd] $0 + %reason = $1- + [cmd]if[/cmd]("%reason" == "")%reason = "You're not welcome here!" + [cmd]kick[/cmd] $0 %reason + } + [/example] + The example above will first check the validity of the <nickname> passed to kb: + if no nickname was passed , it will warn the user and stop. + The next step will be the "ban <nickname>" call. Another enchancement is the "default reason": + we first assign the remaining parameters ($1- means "from $1 to the end") to a temporary variable, + if the variable is empty , a default kick reason is assigned. + Finally the "kick <nickname> <reason>" will be executed. + Get used to looking at the single command documentation pages, they will give + you the hints necessary to fully understand the above piece of code. + [/p] + [p] + Aliases can be used as a mean for structured programming. + In large scripts you will SURELY have "common tasks" to perform (like having specially + colored output or calculating a value from a set of other values)... + Aliases are the way of writing the common tasks: they are equivalent to the "procedures" + or "functions" in many high-level programming languages. + The alias as a procedure (subroutine or sub-task) has been shown in the "kb" example above: + it might be commonly called from complexier scripts or other aliases in case that a + kick & ban action is needed. + [/p] + [p] + The aliases can be used also as functions. + Assume that you need really often to calculate the sum of three numbers: a function-alias is the way. + [example] + [cmd]alias[/cmd](sum3){ [cmd]return[/cmd] $($0 + $1 + $2); }; + [/example] + This will add the alias "sum3" and make it available both as a command and a function. + The "return" command sets the return value of a sequence of commands + (an alias is a sequence of commands...remember ?) and terminates the execution (by returning + the control to the caller). + So return $($0 + $1 + $2); will set the return value of the alias to the value + computed by $($0 + $1 + $2) that actually is the sum of the first three parameters passed. + You will then use it in the following way: + [example] + ... + %myfirstsum = $sum3(%somevalue,%someothervalue,4) + %anothersum = $sum3(12,%somevalue,%anothervalue) + ... + [/example] + Ops.. I've used some variables without actually explaining them... hehe.. please forgive me and read on. + This example is again really simple , but you might have complexier function-aliases. + The function-aliases are also normal aliases.... you can use it as a command: + [example] + /sum3 1 2 3 + [/example] + Is a perfectly valid call.... it's just that it will have no visible results + (just because a command call implies ignoring the return value. + In fact there is no difference al all between function-aliases and normal-aliases: + the caller makes the difference: by calling an alias as a command the return value + just disappears in hyperspace, by calling an alias as a function , the return value + is propagated (and in fact "used"). + (There are some "nice" exceptions to this rule...but you don't need to care about it, for now). + If return is not called inside an alias body , the return value will be just a null value. + [/p] + [p] + Aliases can accept switches just like any other command. The [fnc]$sw[/fnc] is there + exactly for that purpose. Check it out. + [/p] + + [big]Special functions[/big] + [p] + We have already seen the positional parameter functions. + The functions of type [b]$N[-[M]][/b] (where N and M are positive + numbers starting from 0 and N < M) evaluate to the sequence of + [b]positional parameters[/b] from Nth to Mth."[br] + If M is omitted , the function evaluate to the sequence of [b]positional + parameters[/b] from Nth to the last one. If the whole -M block is omitted + the function evaluate to the Nth positional parameter. + We will discover more on the [b]positional parameters[/b] when talking + of aliases and events.[br] + [example] + $0 evaluates to the 1st positional parameter + $0-4 evaluates to the parameters from first to 5th + $41- evaluates to the parameters from 41st to the last avaiable + [/example] + The function [b]$#[/b] evaluates to the number of positional parameters available. + The [b]positional parameter[/b] functions do not accept parameters.[br] + The special function [b]$(<expression>)[/b] returns the result + of the evaluation of the <expression>. In previous versions of KVIrc this + function was called [fnc]$calc[/fnc].[br] + [example] + [cmd]echo[/cmd] $(2 + (3 ^ 7) <= 1 * (3 && 2)) + [/example] + The special function [b]${<command sequence>}[/b] evaluates to the + return value of the <command sequence>.[br] + The special function [b]$$[/b] evaluates to the current object id, + but it is too early to explain it here...[br] +*/ + +/* + @doc: command_rebinding + @type: + language + @keyterms: + Rebinding commands to another window + @title: + Standard rebinding switch + @short: + Standard rebinding switch + @syntax: + <command> -r=<window_id> <parameters> + @body: + The -r switch is standardized along all the commands. It rebinds a command + to the windows specified by <window_id>. It is useful to launch commands + in windows that are not the current one. For example, you might want to + say something in a specific channel while processing an event bound to + a console, or say something in all the channels bound to the current irc context. + The examples below will make everything clear. + @examples: + [example] + [comment]# Run a command in the console of the current IRC context[/comment] + [cmd]echo[/cmd] -r=$console This command is executed in the console ($window.caption) + [comment]# Say something to all the channels of the current IRC context[/comment] + [cmd]foreach[/cmd](%w,[fnc]$window.list[/fnc](channel))[cmd]say[/cmd] -r=%w Hi ppl on [fnc]$chan.name[/fnc] + [/example] +*/ + + /* + @doc: window_naming_conventions + @type: + language + @title: + Window naming conventions + @keyterms: + IRC context,window ID,frame window,connection ID + @short: + KVIrc window structure and the window naming conventions + @body: + [big]Introduction[/big][br] + Starting from the release 3.0.0 KVIrc window structure has + grown in complexity. Older releases allowed one connetion + per "frame window" and thus had a dedicated command parser + for each connection. Finding a window in that scenario + was quite easy: it was enough to designate it by "name" + (that was exactly the text displayed in the window caption). + It was sufficient to have an "unique" name for ever window; + condition that was granted by the underlying IRC protocol + and by the KVIrc core design.[br] + In this version, the unique window names are impossible to be granted.[br] + [big]Scenario[/big][br] + The command parser is now "global" to the application. + There can be two or more consoles in each frame and the user + is able to join the same channel with two different nicknames + using two separate connections. + [ul] + [li] + Application (Unique command parser) + [ul] + [li] + Frame X + [ul] + [li] + Console M (IRC context) + [ul] + [li]Channel windows[/li] + [li]Query windows[/li] + [li]Other connection related windows[/li] + [/ul] + [/li] + [li] + Console N (IRC context) + [ul] + [li]Channel windows[/li] + [li]Query windows[/li] + [li]Other connection related windows[/li] + [/ul] + [/li] + [li] + Other windows + [/li] + [li] + ... + [/li] + [/ul] + [/li] + [li] + Frame Y + [ul] + [li] + Console O (IRC context) + [ul] + [li]Channel windows[/li] + [li]Query windows[/li] + [li]Other connection related windows[/li] + [/ul] + [/li] + [li] + Console P (IRC context) + [ul] + [li]Channel windows[/li] + [li]Query windows[/li] + [li]Other connection related windows[/li] + [/ul] + [/li] + [li] + Other windows + [/li] + [li] + ... + [/li] + [/ul] + [/li] + [li] + ... + [/li] + [/ul] + [/li] + [/ul] + [br] + A naming convention has becomed necessary to resolve ambiguities.[br] + [big]Basic assumptions[/big] + Every KVIrc window has four main properties:[br] + -[b]an unique numeric identifier[/b][br] + -[b]the logical name[/b][br] + -[b]the type identifier[/b][br] + -[b]the caption text[/b][br] + The [b]numeric identifier[/b] is unique to the whole application, + and is the one returned by the [fnc]$window[/fnc] function.[br] + The identifier is assigned by KVIrc when the window is created + and is not changed until the window is destroyed. + This identifier will be referred as [b]window ID[/b].[br] + The [b]logical name[/b] is a property of some kind of windows. + It usually corresponds to the first part of the window caption. + For example, for channel windows it is the channel name, for + queries it is the list of the targets. For some other windows + the logical name corresponds to the caption text. This will be discussed later.[br] + The [b]type identifier[/b] describes the properties of a certain window. + For channel windows the type identifier is "channel" , for query windows is "query" , + for console windows it is "console", etc..[br] + + [big]Irc contexts[/big][br] + The KVIrc frame windows are numbered starting from 0 and named + "frame_<number>". Each frame can contain an unlimited number of consoles.[br] + Each console is bound to an [b]IRC context[/b]. (The part "is bound to" could + be substituted by "defines" or "is contained in").[br] + [i]An [b]IRC context[/b] is a set of resources that can deal with a single + IRC connection.[/i][br] + The association between an [b]IRC context[/b] + and a console is bijective: each [b]IRC context[/b] is associated + to a single console window.[br] + An [b]IRC context[/b] can be in connected or not-connected state. + When in connected state, it contains a set of windows beside the console: + mainly channels and query windows. + The channels and query windows can exist ONLY if the associated + [b]IRC context[/b] exists.[br] + Channels and queries have unique names inside a connection so + there is no way to confuse it. (Theoretically there can + be more than one query window with the same name, but in fact + all the windows refer to the same target so they are instances + of the same resource). + All this creates a sort of namespace: the channels and queries can be identified + as "bound" to a specific [b]IRC context[/b].[br] + An [b]IRC context[/b] can "contain" other windows, such as the "sockets" + window or the "list" window. KVIrc takes care of making them + unique inside the [b]IRC context[/b] namespace.[br] + Each [b]IRC context[/b] has its own unique [b]IRC context ID[/b] (see [fnc]$context[/fnc]).[br] + Since to a single [b]IRC context[/b] may correspond only a single irc connection, + when in connected state, the [b]IRC context[/b] may be referred also as [b]connection[/b] + or [b]connection context[/b], and the associated [b]IRC context Id[/b] can be + referred as [b]connection ID[/b] or [b]connection context ID[/b].[br] + There are classes of windows that are not bound to any [b]IRC context[/b]: + this includes user created windows, DCC windows, browsers etc.[br] + KVIrc will try to keep that windows with unique logical names.[br] + [big]How to identify a window[/big][br] + So what we have until now is:[br] + [ul] + [li]Each window has its own unique [b]window ID[/b]: we + will refer windows always using this identifier.[/li] + [li]Each window has a set of properties including: + window type, logical name.[/li] + [li]Subsets of windows are bound to a single [b]IRC context[/b][/li] + [/ul] + The simplest (but also the less significant) method of looking for + a window is to finding it by caption.[br] + The [fnc]$window[/fnc] function finds the first KVIrc window matching + the "caption text" and returns its [b]window ID[/b].[br] + This method will likely fail when there are more windows with the same + caption text; for this reason several specific functions + have been added to allow finding the correct window.[br] + The [fnc]$console[/fnc] finds a console window bound to a specified + [b]IRC context[/b].[br] + The [fnc]$channel[/fnc] finds a channel window matching the specified + name and bound to a specified [b]IRC context[/b].[br] + The [fnc]$query[/fnc] finds a query window that has a specified target + and is bound to a specified [b]IRC context[/b].[br] + */ + + + /* + @doc: connection_dependant_commands + @type: + language + @title: + Connection dependant commands + @keyterms: + IRC context, connection dependant commands + @body: + Many KVIrc commands are connection dependant: + you need an IRC connection to succesfully execute them; + usually because some data needs to be sent to the server. + This includes commands like [cmd]whois[/cmd],[cmd]raw[/cmd],[cmd]query[/cmd], + [cmd]msg[/cmd],[cmd]notice[/cmd],[cmd]op[/cmd],[cmd]ctcp[/cmd]...[br] + These commands must be executed in a window that is bound to a + [b]connected [doc:window_naming_conventions]IRC context[/doc][/b]. + You will obviously get an error message if you try to use them in a window + that has no associated IRC connection.[br] + For instance: [cmd]whois[/cmd] will work only if you execute it + in a console , channel or query window.[br] + If you want to use these commands in a window that is not associated to + any IRC context you may use the [doc:command_rebinding]standard -r switch[/doc]. + You can use the same switch to execute a command in an [b]IRC context[/b] that is + not the current one. + */ + + +/* + @doc: aliases + @type: + language + @keyterms: + aliases + @title: + Aliases + @short: + Aliases : user definable command sequences + @body: + An alias is an user defined command. It can be used to rename the builtin kvirc commands or functions, + to automatize complex tasks or as structured programming mean. + Aliases can be created or destroyed by using the scriptcenter (graphic interface) + or from the commandline (or script) by using the [cmd]alias[/cmd] command. + Once created, an alias remains stored permanently in the KVIrc configuration files + until it is explicitly deleted. + A couple of examples will make the things clear. + join is a really commonly used command. It might be a good idea to rename it to + simply "j" .. just to type it faster. + Nothing easier in KVirc: just try this commandline: + [example] + [cmd]alias[/cmd](j){ [cmd]join[/cmd] $0-; }; + [/example] + + This will create the alias "j". From this moment you can use /j as it was a normal command. + [example] + j #kvirc + [/example] + You may have notices the strange $0- function in the alias body: it stands for + "all parameters passed to the alias". This means that when you call + [example] + j #kvirc testpassword + [/example] + then both the parameters (#kvirc and testpassword) are passed to the join command. + The $N functions are special functions that return the positional parameters passed + to the current script context. In an alias the script context is the script body and + it is the alias caller that generates the parameters. + $N (where N is a digit) returns the (N-1)-th positional parameter passed by the caller. + It returns the parameter numbered N-1 and not N since the parameters are indexed starting + from zero ($0 is the first parameter!). + $N-M returns the parameters from (N-1)-th to the (M-1)-th (a parameter range) and $N- returns + all the parameters from (N-1)-th to the last one. In the example above $0- stands for + all the parameters starting from the first one. + [/p] + [p] + To remove an alias use again the alias command with an empty body: + [example] + [cmd]alias[/cmd](j){} + [/example] + This will remove the alias "j" defined above. + [/p] + [p] + A common task in channel management is the kick & ban action. + You first ban an user from the channel and then eventually kick him + (obviously assuming that he is actually on the channel). + This involves using two commands: ban and then kick. + It could be a nice idea to have a single "kb" command to perform this action. + Well...easy: + [example] + [cmd]alias[/cmd](kb){ [cmd]ban[/cmd] $0; [cmd]kick[/cmd] $0-; }; + [/example] + This adds the "kb" alias: it can be called as a normal command: + [example] + kb spammer You're not welcome here! + [/example] + This will first execute "ban spammer" and then "kick spammer You're not welcome here". + Our kb is a really simple example... it doesn't check for the validity of the parameters: + the server will warn us if the parameters passed to kb were empty. + [/p] + [p] + The alias can be modified at any time by re-using the alias command. + Let's make our "kb" a bit more intelligent and add a check for the parameters. + TIP: It is a good idea to write the following examples in a text file and then use /parse <filename> to execute it. + [example] + [cmd]alias[/cmd](kb) + { + [cmd]if[/cmd]("$0" == "") + { + [cmd]echo[/cmd] "Usage: /kb <nickname> <kick reason>" + [cmd]return[/cmd] + } + [cmd]ban[/cmd] $0 + %reason = $1- + [cmd]if[/cmd]("%reason" == "")%reason = "You're not welcome here!" + [cmd]kick[/cmd] $0 %reason + } + [/example] + The example above will first check the validity of the <nickname> passed to kb: + if no nickname was passed , it will warn the user and stop. + The next step will be the "ban <nickname>" call. Another enchancement is the "default reason": + we first assign the remaining parameters ($1- means "from $1 to the end") to a temporary variable, + if the variable is empty , a default kick reason is assigned. + Finally the "kick <nickname> <reason>" will be executed. + Get used to looking at the single command documentation pages, they will give + you the hints necessary to fully understand the above piece of code. + [/p] + [p] + Aliases can be used as a mean for structured programming. + In large scripts you will SURELY have "common tasks" to perform (like having specially + colored output or calculating a value from a set of other values)... + Aliases are the way of writing the common tasks: they are equivalent to the "procedures" + or "functions" in many high-level programming languages. + The alias as a procedure (subroutine or sub-task) has been shown in the "kb" example above: + it might be commonly called from complexier scripts or other aliases in case that a + kick & ban action is needed. + [/p] + [p] + The aliases can be used also as functions. + Assume that you need really often to calculate the sum of three numbers: a function-alias is the way. + [example] + [cmd]alias[/cmd](sum3){ [cmd]return[/cmd] $($0 + $1 + $2); }; + [/example] + This will add the alias "sum3" and make it available both as a command and a function. + The "return" command sets the return value of a sequence of commands + (an alias is a sequence of commands...remember ?) and terminates the execution (by returning + the control to the caller). + So return $($0 + $1 + $2); will set the return value of the alias to the value + computed by $($0 + $1 + $2) that actually is the sum of the first three parameters passed. + You will then use it in the following way: + [example] + ... + %myfirstsum = $sum3(%somevalue,%someothervalue,4) + %anothersum = $sum3(12,%somevalue,%anothervalue) + ... + [/example] + Ops.. I've used some variables without actually explaining them... hehe.. please forgive me and read on. + This example is again really simple , but you might have complexier function-aliases. + The function-aliases are also normal aliases.... you can use it as a command: + [example] + /sum3 1 2 3 + [/example] + Is a perfectly valid call.... it's just that it will have no visible results + (just because a command call implies ignoring the return value. + In fact there is no difference al all between function-aliases and normal-aliases: + the caller makes the difference: by calling an alias as a command the return value + just disappears in hyperspace, by calling an alias as a function , the return value + is propagated (and in fact "used"). + (There are some "nice" exceptions to this rule...but you don't need to care about it, for now). + If return is not called inside an alias body , the return value will be just a null value. + [/p] + [p] + Aliases can accept switches just like any other command. The [fnc]$sw[/fnc] is there + exactly for that purpose. Check it out. + [/p] +*/ + + +/* + @doc: kvs_addons + @type: + language + @keyterms: + addons, addon + @title: + The KVIrc addon system + @short: + Writing KVIrc addons + @body: + [big]Introduction[/big] + [p] + An addon is basically a set of KVS scripts, multimedia, documentation + and accessory files that implement a KVIrc feature. + It might be a simple automatic-away subsystem, a GUI newsticker or a complex file sharing + service (commonly called "fserve"). Addons are sometimes called "scripts". + In fact a KVIrc addon is usually made of more than one KVS script. + [/p] + [p] + KVIrc has a builtin addon management system that allows the users + to install, configure and uninstall features with a nice graphical interface. + The management system allows the addons to have documentation integrated in the + KVIrc help and to be translated in several languages. + [/p] + + [big]Addon installation[/big] + [p] + The addons are usually shipped in compressed archives (such as tar.gz "tarballs" or + zip files). Once uncompressed they should contain a KVS script file called "install.kvs". + KVIrc will look for and execute this file when the user will ask for your addon to + be installed. The install.kvs will usually contain the code for the [b]registration[/b] + of your addon and will [cmd]include[/cmd] all the other necessary source files. + [/p] + + [big]The minimal addon[/big] + [p] + The smallest addon that you can write is the one that does nothing. + It just need to be writte in a file named install.kvs and contain code + similar to the following: + [example] + [cmd]addon.register[/cmd]("myaddon", \ + "1.0.0", \ + "My First Addon", \ + "An addon that is really cool but does simply nothing", \ + "3.2.0.99.20051230") + { + } + [/example] + The code above does nothing but registers the "myaddon" addon. + [/p] + [p] + The first parameter is the internal addon id which can be used to identify + your addon inside KVIrc. The id must be unique: two addons that share the same + name cannot be installed. The second parameter is the addon version. It should + be expressed in the classic format [major].[minor].[pathlevel] or something + really similar (in fact KVIrc just expects the version to be a string composed + of numbers separated by dots). The version is compared when an addon is installed + and KVIrc complains if the user tries to downgrade an addon (that is to install + a less recent version over a more recent one). The third parameter + is the visible name of your addon: it will be displayed to the user in the + addon management dialog. It can contain the [fnc]$tr[/fnc] function so you + can have it translated to several languages. The fourth parameter + is a short description of the feature that the addon implements; it can contain + the $tr() function too. The fifth parameter is the minimal KVIrc version + required to run the addon. There is also a sixth parameter (the icon) and + some switches that can be used to fiddle a little bit more :) + [/p] + [p] + The callback instruction that follows the registration command is the + uninstallation code. KVIrc will invoke it when the user will ask for + your addon to be uninstalled. Don't assume that your addon will be never uninstalled: + sooner or later it will be. For example, when upgrading an addon KVIrc + will first uninstall the existing version and after that install the new one. + The uninstallation process is a very important requisite for any program (in any + programming language). In the example above there is nothing to uninstall (yet) + so the callback code is empty, but if you continue reading we will soon fill it. + [/p] + + [big]A typical addon layout[/big] + [p] + As stated above, the addons are usually shipped in a compressed archive. + Once uncompressed, the archive will expand into a small directory tree + containing the addon code and all the related files. + In order to have uniformity I encourage you to use the following directory structure. + [/p] + [p] + [pre] + [b]addonId-version/[/b] + install.kvs + INSTALL + [b]src[/b] + source1.kvs + source2.kvs + source3.kvs + ... + [b]pics[/b] + addonId_pic1.png + addonId_pic2.png + addonId_pic3.png + ... + [b]audio[/b] + addonId_audio1.png + addonId_audio2.png + addonId_audio3.png + ... + [b]help[/b] + en + index.html + hints.html + ... + it + index.html + hints.html + ... + ... + [b]locale[/b] + addonId_it.mo + addonId_ru.mo + addonId_de.mo + ... + [b]...[/b] + ... + ... + ... + ... + [/pre] + [/p] + [p] + The entries in [b]bold[/b] are directories while the other are files. + Please note that this is a general layout for a huge and rather complex + addon: you might not need all of these directories. Remember: the minimal + addon has only an install.kvs file. Anyway, a really cool addon + will probably have all of them and maybe some more. + [/p] + [p] + The toplevel directory should be named with your addonId and version. + Try to use no spaces in the directory entries (this will make the things + simplier for people that want to use your addon). The toplevel + directory should contain your install.kvs script and a file with + a small description and the basic installation instructions. + This file can be named INSTALL or README. + [/p] + [p] + Hint: Remember that your addon is going to be installed on different platforms + (at least linux, macosx and windows based). + The poor windows notepad has serious problems with reading text + files that contain only linefeeds as line separators. Keep it in mind... + [/p] + [p] + The main source directory for your addon should be named "src" and + should contain the implementation of the feature(s) you're going to provide. + These files should be executed by the means of the [cmd]parse[/cmd] + command (or [cmd]include[/cmd] if you like the C/C++ style) from install.kvs. + [/p] + [p] + The "pics" and "audio" (if relevant) directories should contain your + multimedia files. It is a good idea to prefix the filenames with your addon id + in order to avoid collisions with other addons. The install.kvs script + should copy these files to the appropriate locations under the KVIrc local + directory returned by [fnc]$file.localdir[/fnc](). + [/p] + [p] + The "help" directory should contain subdirectories for each language that + your help files are written in. The languages dirs should be named + with the language code also used for the translation files (like "en","it" etc...). + Please note that english is the default language and KVIrc will + fallback to the "en" subdirectory when no other language is found around... + The help files (and subdirectories) should be copied to the "help" subdirectory of the KVIrc local + directory returned by [fnc]$file.localdir[/fnc](). + Since there will be many help files inside the language dirs, it is a good idea + to either prefix your help files with your addon id or (better) to create + a subdirectory named agains your addon inside the help language directory. + For an english file this would lead to something like... + [example] + file.copy index.html $file.localdir("help/en/myaddon") + [/example] + [/p] + [p] + The "locale" directory should contain the *.mo files for your tranlations. + The localization process of a script is explained in [doc:localization]this document[/doc]. + The *.mo files should be copied to the "locale" subdirectory of the KVIrc local + directory returned by [fnc]$file.localdir[/fnc](). + Your *.mo filenames should be prefixed by your addon id (again to avoid collisions). + [/p] + + [big]The help and configuration callbacks[/big] + [p] + Each addon can have a help and a configuration callback. These are set + respectively by [cmd]addon.sethelpcallback[/cmd] and [cmd]addon.setconfigurecallback[/cmd]. + [/p] + [p] + The help callback will be invoked by KVIrc when the user will ask help for your addon (mainly + from the addon management dialog, but not necessairly). It should call [cmd]help.open[/cmd] + with the name of your documentation index html file (it should be relative + to the help language directory: help.open myaddon/index.html will automatically + lookup the right language). If you provide no help callback, the buttons + for requesting help will be simply disabled. (A good an relatively complex addon + *should* have at least a minimal help file explaining the features). + [/p] + [p] + The configuration callback will be invoked when the user will try to configure + your addon from the addon management dialog. This callback is useful + mainly for complexier graphical scripts that can show up a dialog + that allows configuring all of the addon features. To use this callback + you will probably need some object scripting. + [/p] + + [big]The real addon work[/big] + [p] + The real addon work is done by the scripts contained in the src directory. + They will likely add aliases (maybe in a nice namespace named agains your addon), + register event handlers, create actions, timers, toolbars and object classes. + You should install all of this stuff from your addon source files. + Remember that your source files will NOT be parsed every time KVIrc starts up: + your stuff must be registered in KVIrc and be able to startup itself, if needed. + Remember that you must clean up [b]everything[/b] in your uninstallation callback. + This means that you must remove the aliases, unregister the event handlers, + destroy the actions, kill the timers and the object classes you've created. + Be a clean coder :) + [/p] + + [big]Where to start[/big] + [p] + It is a good idea to start on the KVIrc web site. There are surely + several addons to look at. Pick one that seems simple and analyze its + layout and code (wow... the free software!). It will be easier to do than it was to explain it :D + [/p] + [p] + Have fun! :) + [/p] +*/ + + +/* + @doc: kvs_codingtips + @type: + generic + @title: + Coding tips + @keyterms: + indentation,indent,readability + @short: + Generic coding tips for scripters (and not only) + @body: + Here comes a small list of "coding tips".[br] + These apply to programming in general , not only to KVIrc scripting.[br] + [br] + 1. [b]Comment your code[/b][br] + A well commented code is easy to mantain, and easy to read by others.[br] + [br] + 2. [b]Indent your code[/b][br] + Indentation increases the code readability; this is again for you and + other developers that will be going to read your code.[br] + [br] + 3. [b]Use TABS to indent your code[/b][br] + ...and use ONLY TABS to indent.[br] + Tabs are better than space since most code editors allow you + to set the tab sice and thus to have the indentation steps smaller or bigger.[br] + This is really important since the indentation size is really a matter of personal taste.[br] + Mixing spaces and tabs is Evil (tm), since it makes the code look really + ugly in editors that have the tab size different than yours; in some cases the + code gets really unreadable.[br] + [br] + 4. [b]Use descriptive variable names[/b][br] + Using 'foo' as variable name implies tracing its semantic the next + time that you're going to read the code that uses it.[br] + This is really annoying and time-consuming, especially if the project + is getting large.[br] + Obviously using "thisIsACounterVariable" as name for a simple counter + is also a suicide.[br] + A good convention on variable names can speed up writing , debugging and mantaining code.[br] + Encoding the type of the variable in the variable name might be also a good idea, + but this is a matter of taste; personally I feel really well with that.[br] + Just as example, here go my fundamental convention rules for C++:[br] + [br] + - The type of the variable is encoded at the beginning of the variable name:[br] + [br] + - b prefix for the boolean varables[br] + - i prefix for signed integers[br] + - u prefix for unsigned integers[br] + - f and d prefixes for floating point stuff[br] + - sz prefix for strings (this is rather for string classes)[br] + - ...[br] + [br] + - Pointers have a "p" prefix prepended[br] + - Global variables start with a "g_" prefix[br] + - Member variables start with a "m_" prefix[br] + - Exception comes for local variables with obvious semantics[br] + [br] + - i,j,k,l for local loop counters[br] + - "aux" and "tmp" for local obvious short-term temporary variables[br] + [br] + So actually by ONLY reading "g_pszQuitMessage" I know that this is a global pointer to a string variable + containing a quit message. :)[br] + [/p] +*/ + +// FIXME: #warning "FINISH THE SYNTACTIC RULES DOC" + +/* + @doc: syntactic_rules + @type: + language + @keyterms: + productions + @title: + Syntactic rules + @short: + Syntactic rules of the KVIrc scripting language + @body: + + In the following table you can find a good part of the + KVIrc scripting language syntactic rules.[br] + [br] + <entity> indicates a ENTITY THAT CAN APPEAR EXACTLY ONE TIME.[br] + [<entity>] indicates an OPTIONAL ENTITY.[br] + {<entity>} indicates an ENTITY THAT CAN APPEAR ONE OR MORE TIMES.[br] + 'entity' indicates a LITERAL ENTITY: written exactly as it is.[br] + <entity1>|<entity2> indicates mutually exclusive choices.[br] + The mutually exclusive choices are often separated in two or more + rules (productions), to improve readability.[br] + [table] + [tr] + [td]<command buffer>[/td] + [td][<whitespace>][<command block>]{<command buffer>}[/td] + [/tr] + [tr] + [td]<command buffer>[/td] + [td][<whitespace>][<single command>]{<command buffer>}[/td] + [/tr] + [tr] + [td]<whitespace>[/td] + [td]{<space>|<tab>|<newline>}['\'<newline>][<whitespace>][/td] + [/tr] + [tr] + [td]<space>[/td] + [td]' '['\'<newline>][<space>] (Ascii space character)[/td] + [/tr] + [tr] + [td]<tab>[/td] + [td]'\t' (Ascii horizontal tabulation character)[/td] + [/tr] + [tr] + [td]<newline>[/td] + [td]'\n' (Ascii line feed (LF) character)[/td] + [/tr] + [tr] + [td]<command block>[/td] + [td]'{' <command buffer>[<whitespace>] '}'[/td] + [/tr] + [tr] + [td]<single command>[/td] + [td]<comment>[/td] + [/tr] + [tr] + [td]<single command>[/td] + [td]<lvalue command> <command terminator>[/td] + [/tr] + [tr] + [td]<single command>[/td] + [td]<rvalue command> <command terminator>[/td] + [/tr] + [tr] + [td]<comment>[/td] + [td]'#' {<non comment terminator>} <comment terminator>[/td] + [/tr] + [tr] + [td]<comment terminator>[/td] + [td]<newline> | <end of string>[/td] + [/tr] + [tr] + [td]<end of string>[/td] + [td]No character (internally Ascii character 0)[/td] + [/tr] + [tr] + [td]<command terminator>[/td] + [td]<newline> | <end of string> | ';'[/td] + [/tr] + [tr] + [td]<non comment-terminator>[/td] + [td]Any Ascii character except <newline> and <end of string>[/td] + [/tr] + [tr] + [td]<simple command>[/td] + [td][<module name>'.']<command name>[<switch list>]{<space>}<command dependant part>[/td] + [/tr] + [tr] + [td]<lvalue command>[/td] + [td]<variable>[<space>]<operation>[/td] + [/tr] + [tr] + [td]<lvalue command>[/td] + [td]<variable>'->'<object command>[/td] + [/tr] + [tr] + [td]<lvalue command>[/td] + [td]<identifier>'->'<object command>[/td] + [/tr] + [tr] + [td]<operation>[/td] + [td]<one op operator>[/td] + [/tr] + [tr] + [td]<operation>[/td] + [td]<two op operator>[<space>]<param string>[/td] + [/tr] + [tr] + [td]<switch list>[/td] + [td]{<space>}'-'<alpha char>[{<space>}'='<single parameter>][<switch list>][/td] + [/tr] + [tr] + [td]<command name>[/td] + [td]<alphanumeric char>{<alphanumeric char>}[/td] + [/tr] + [tr] + [td]<module name>[/td] + [td]<alphanumeric char>{<alphanumeric char>}[/td] + [/tr] + [tr] + [td]<alphanumeric char>[/td] + [td]Ascii characters 'A' to 'Z' , 'a' to 'z' , '0' to '9' and '_'[/td] + [/tr] + [tr] + [td]<variable>[/td] + [td]<global variable> | <local variable>[/td] + [/tr] + [tr] + [td]<global variable>[/td] + [td]'%' <uppercase letter> [<alphanumeric char>]['['<param string>']'][/td] + [/tr] + [tr] + [td]<local variable>[/td] + [td]'%' <lowercase letter> [<alphanumeric char>]['['<param string>']'][/td] + [/tr] + [tr] + [td]<param string>[/td] + [td][<single parameter>][<space>[<param string>]][/td] + [/tr] + [tr] + [td]<single parameter>[/td] + [td]<variable> | <identifier> | <nonterminator token> | <string>[/td] + [/tr] + [tr] + [td]<nonterminator token>[/td] + [td]<nonterminator char>['\'<newline><nonterminator char>][/td] + [/tr] + [tr] + [td]<nonterminator char>[/td] + [td]Any ascii character except <space> and <command terminator>[/td] + [/tr] + [tr] + [td]<command dependant part>[/td] + [td][b]Production in each command help page[/b][/td] + [/tr] + [/table] + To be continued... +*/ + +/* + @doc: command_rebinding + @type: + language + @keyterms: + Not supported + @title: + Standard -r switch no longer supported + @short: + Standard -r switch no longer supported + @body: + Starting from version 3.0.0 the standard -r switch to commands is no longer supported. + You should rebind your command sequences with [cmd]rebind[/cmd] +*/ + +/* + @doc: kvs_datatypes + @type: + language + @keyterms: + global variable, global variables, local variable, local variables, + variables, variable, array, hash, dictionary, global variables, local variables,variable evaluation, + associative arrays, scalars, data types, percent sign, extended scope + @title: + Variables and Data types + @short: + All about the KVS variable and datatype management + @body: + [title]Basic syntax[/title] + + [p] + A variable identifier is composed by a '%' (percent) sign followed + by a sequence of letters, digits or underscores. + Examples of valid variable names are: + [/p] + + [example] + %i + %variable + %MyVar + %1 + %thisisavar + %2ndName + %_hidden + [/example] + + [p] + Variables are created when you assign something to them: there is no need + for a declaration (unlike other languages such as C/C++, Java or VB). + [/p] + + [example] + [comment]# create a variable named %X by assigning the value 10 to it[/comment] + %X = 10 + [comment]# use the variable[/comment] + echo "The value of X is" %X + [/example] + + [title]Local and global variables[/title] + + [p] + Variables can be local or global. + Local variables preserve their contents only inside the [b]scope[/b] of a single script. + Global variables are shared between all the scripts and preserve their contents + until they are explicitly unset or until KVIrc quits. + [/p] + + [p] + Local variables start with a [b]lowercase letter[/b] while the global ones with an [b]uppercase letter[/b]. + [/p] + + [example] + %var = 10; [comment]# this is a local variable[/comment] + %Var = 10; [comment]# this is a global variable[/comment] + [/example] + + [p] + You can also force a variable that start with a lowercase letter to be global + by predeclaring it with the [cmd]global[/cmd] keyword.[br] + [/p] + + [example] + [comment]# copy this script to a file and run /[cmd]parse[/cmd] <filename>[/comment] + global %a + %a = "The contents of the variable a" + %b = "The contents of the variable b" + [comment]# %a is a global variable now : all the other scripts can see its value[/comment] + [comment]# %b is a local variable and no other scripts can see its value[/comment] + [/example] + + [p] + If you have executed the example above from a file (by the means of [cmd]parse[/cmd]) + then now you can type + [/p] + + [example] + [cmd]echo[/cmd] %a + [/example] + + [p] + in the commandline to see the contents of the variable %a. + If you also try + [/p] + + [example] + [cmd]echo[/cmd] %b + [/example] + + [p] + you will see nothing printed since %b was local to the parsed script. + [/p] + + [title]Data types[/title] + + [p] + KVS has three main categories of data types: scalars, arrays and associative + arrays (also known as dictionaries or hashes). + [/p] + + [subtitle]Scalars[/subtitle] + + [p] + The scalars are simple variables containing a single value (a string or an integer). + [/p] + + [example] + [comment]# %a is a scalar variable[/comment] + %a = "This is a string" + [cmd]echo[/cmd] %a + %a = 24.5 + [cmd]echo[/cmd] %a + [/example] + + [subtitle]Arrays[/subtitle] + + [p] + Arrays are collections of items indexed by integers. The array items + are selected by placing the index in square brackets just after the array name. + [/p] + + [example] + %arrayName[index] + [/example] + + [p] + An easy way to create an array is to use the [fnc]$array[/fnc] function. + [/p] + + [example] + %a = $array("element1","element2","element3"); [comment]# Create an array with 3 items[/comment] + [cmd]for[/cmd](%i=0;%i<3;%i++) + { + echo %a[%i]; [comment]# Accessing the %i'th element of the array[/comment] + } + [/example] + + [p] + Note that in the example above %a refers to the whole array while %a[%i] refers + to one of its elements, in particular the one with index %i. + You also create an array by explicitly assigning to one of its elements: + [/p] + + [example] + %a[9] = "This is an array element"; + [/example] + + [p] + Array indexes are zero-based so in the example above you have created an array + with 10 items. You can find out an array's length with the [fnc]$length[/fnc]() function. + [/p] + + [example] + %a[9] = "This is an array element"; + echo $length(%a) + [/example] + + [p] + Be aware that by making such an assignment you implicitly consume some memory for + all the preceeding array items (even if they are unset). This means that + a simple instruction like the following may eat a huge amount of memory at once: + [/p] + + [example] + %a[1000000] = "An array element faaaaaar away..."; + echo $length(%a) + [/example] + + [note] + [p] + Food for thoughts: + [/p] + [p] + KVIrc allocates a pointer for each item in the array. The pointer is + empty when the item is unset and points to an additional block + of memory when the item is set. The size of a pointer is platform + dependant: on the platforms supported by KVIrc it's either 32 or 64 bit. + The size of the additional block depends both on the platform + and on the contents of the item... it's average value may + be around 16 bytes. The array size is determined by the last SET element index. + All this this means that in the worst case (64 bit assumption) an array in + that the highest indexed item set is N eats up at least N*8+16 bytes of memory. + [/p] + [/note] + + [p] + Besides the traditional indexed looping method you + can also use the [cmd]foreach[/cmd] command to iterate the items of an array. + Be aware that [cmd]foreach[/cmd] will NOT iterate over unset items in the + array unless you use the -a switch. + [/p] + + [example] + %Array[0]=Pippo + %Array[1]=Pluto + %Array[2]=Paperino + %Array[5]=Prova + [cmd]foreach[/cmd](%item,%Array)[cmd]echo[/cmd] Got Item: %item + [/example] + + [p] + Note that the items 3 and 4 are simply skipped. + [/p] + + [subtitle]Hashes[/subtitle] + + [p] + The hashes are collections of items indexed by strings: the word "hash" + is in fact a shortcut for "hashtable". In literature hashes are also called + "associative arrays", "dictionaries" or "key-value pair sets". + The hash items are selected by placing the key in curly brackets + just after the hash name. + [/p] + + [example] + %hashName{key} + [/example] + + [p] + An easy way to create a hash is to use the [fnc]$hash[/fnc] function. + [/p] + [example] + %a = $hash("key1","value1","key2","value2","key3","value3") + [cmd]foreach[/cmd](%key,[fnc]$keys[/fnc](%a)) + { + echo "KEY:" %key "VALUE:" %a{%key}; + } + [/example] + + [p] + Note that in the example above %a refers to the whole hash while %a{%i} refers + to one of its elements, in particular the one with the key %key. + You also create a hash by explicitly assigning to one of its elements: + [/p] + + [example] + %a{"MyKey"} = "MyValue" + [/example] + + [p] + You may have already noticed that the [fnc]$key[/fnc]() function returns + the array of the hash keys: it is useful to iterate over the hash items. + [/p] + + [title]Mutability of variables[/title] + + [p] + KVS is not strictly typed: any variable can assume different type identities at different times, + even in the same script. + [/p] + + [example] + [comment]# %a is a scalar[/comment] + %a = "This is a string" + [comment]# %a becomes an array with 3 elements[/comment] + %a = $array("element1","element2","element3"); + [comment]# %a becomes a hash with two values[/comment] + %a = $hash("key1","value1","key2","value2"); + [/example] + + [p] + In literature this kind of variable is called [b]variant[/b] and this is the + term that you will find all around the documentation when an explicit + data type is not requested. + [/p] + + [p] + Note that array and hash items are variants too. This means that you can have arrays + of arrays, hashes of arrays of hashes and any other multidimensional combination you like. + However remember that hash keys are strings and not variants so you can't use an array as hash key. + [/p] + + [example] + [comment]# here we eat 256 locations of memory at once :)[/comment] + %a[16][16] = 10 + [comment]# a hash of hashes: here we eat just two memory locations[/comment] + %a{"16"}{"16"} = 10 + [/example] + + + [p] + In most cases the KVS engine manages automatically the conversion between data types. + For example, when you put an array in a place where a scalar is requested, KVIrc + automatically transforms it to a scalar string by joining all the items with a comma. + [/p] + + [example] + %a = $array("element1","element2","element3"); + echo %a; [comment]# echo expects its arguments to be scalar[/comment] + [/example] + + [p] + Conversely, when you put a scalar in place of an array, KVIrc automatically + transforms it to an array with a single item. In this way a function like + [fnc]$sort[/fnc] works also with a scalar. + [/p] + + [p] + In literature the conversions between data types are called [b]casts[/b]. When + the conversion is automatic the cast is said to be [b]implicit[/b]. + [/p] + + [p] + KVS handles also the other possible implicit casts: scalar->hash,hash->scalar,array->hash,hash->array. + Experiment with it. + [/p] + + [title]More about scalars[/title] + + [p] + Internally KVS is implicitly typed: the "scalar" data type is in fact + a set of types that KVIrc manages silently. The types are: integer, string, real, boolean and hobject. + [/p] + + [p] + Integers are non-floating point numbers. Their allowable range depends on the underlying + platform integer size: usually 32 or 64 bit. + [/p] + + [p] + Reals are floating point numbers. Their allowable range and precision depends on the underlying + platform. + [/p] + + [p] + Booleans are either true or false values. + [/p] + + [p] + Hobject stands for Handle to Object and it is a sort of a C++ pointer. + Detailed description of objects is in [doc:objects]this document[/doc]. + [/p] + + [p] + Basically anything else fails in the "string" category. + [/p] + + [p] + In most cases KVS manages all the conversions between data types automatically. + For example an integer becomes a true boolean when it's non zero and a false boolean + otherwise, a real becomes an integer by truncating it's fractional part... + [/p] + + [p] + You can find out the type of a specified variable by using the [fnc]$typeof[/fnc]() function. + [/p] + + [example] + %a = 1 + echo $typeof(%a) + %a = 1.1 + echo $typeof(%a) + %a = $true + echo $typeof(%a) + %a = "test" + echo $typeof(%a) + [/example] + + [p] + There is also another subtle type of scalar called "nothing". It stands for an + empty (unset) variable. + [/p] + + [example] + %a = $nothing + echo $typeof(%a) + [/example] + + [p] + Nothing is something in between a data type and a special value for all the other data types: + it rappresents absence of information. + This may look a bit confusing but realize that all the unreferenced KVS variable are in fact of type "nothing": + they just don't exist. This means that you can use [fnc]$nothing[/fnc]() to effectively + unset a variable. + [p] + + [p] + Again, when possible, the conversion between nothing and the other data types is + performed automatically. Nothing becomes an empty string, a null object handle or an empty array. + [/p] + + + [title]Explicit casts[/title] + + [p] + You can make explicit conversions between some data types by using the casting functions. + [fnc]$integer[/fnc]() will attempt to convert the variant parameter to an integer, [fnc]$real[/fnc]() + will cast to a floating point value, [fnc]$boolean[/fnc]() will convert to a + true/false value, [fnc]$string[/fnc]() will explicitly convert to a string, + [fnc]$array[/fnc]() will convert to an array and [fnc]$hash[/fnc] will return + a dictionary. By assigning the special [fnc]$nothing[/fnc]() value you will + convert to the nothing data type (or simply unset the variable). + The only explicit conversion that is not possible is to hobject. + [/p] + + [p] + As stated several times in this document, KVS tries to manage the casts automatically + so you usually don't need to care about it. The explicit casts are provided for + the very few cases where an automatic conversion would lead to an unexpected value (for your script) + and for writer's clarity. + [/p] + + [title]More about variables lifecycle[/title] + + [p] + As stated above variables start their existence when you assign something to them. + After a variable has been created it persists until it goes out of his scope (remember + about local and global variables ?) or you explicitly destroy it. You will usually + not care about it and just leave the KVS engine to do his cleaning job but it's still worth + knowing that you actually can force KVIrc to free the memory used by a variable. + [/p] + + [p] + The first method to explicitly destroy a variable is to call [cmd]unset[/cmd] on it. + [cmd]unset[/cmd] in fact accepts a list of variables so you can destroy more variables at once. + [/p] + + [example] + %a = [fnc]$array[/fnc]("data","for","a","really","huge","array","of","items") + %b = 10 + %c = "just a string that eats memory" + [cmd]unset[/cmd] %a,%b,%c + [/example] + + [p] + The KVS engine treats unset variables just like empty strings. The opposite is also valid: empty + strings behave like empty (unset) variables. This means that you can assign an empty string + to a variable to unset it. + [/p] + + [example] + %a = "test"; [comment]# %a starts his existence[/comment] + %b = "test2"; + %a = ""; [comment]# %a is in fact unset[/comment] + %b = ; [comment]# syntactically this is just the same as above[/comment] + [/example] + + [p] + Note that because of mutability of variables (explained above) you can use the empty string + assignment also to free arrays and hashes. + [/p] + + [title]Extended scope variables[/title] + + [p] + Beside local and global variables there is a third family of them. + Variables that have a ':' character just after the leading '%' are [b]extended scope[/b] variables. + "%:index" , "%:Hello" , "%:something.else" are all valid special scope variable names. + They're actually used in popups and in timers (but later I might find other usages as well :). + "Extended scope" means that these variables are somewhere in the middle between + global and local variables. They normally act as local , but in some cases their [b]lifetime[/b] and [b]visibility[/b] + may be extended. + [/p] + + [p] + For example , in the popups , all the special scope variables + are visible during all the "lifetime" of a popup (so from the prologue code call to + the moment when the user selects an item and the corresponding code is executed). + This allows you to pre-calculate some data or conditions in the popup prologue + and use this data in the popup item conditions and item handlers. + [/p] + + [title]Variable evaluation[/title] + + [p] + A variable can appear in every place where a parameter + is expected: so after the command name, after a switch or inside + an identifier parameters. The KVS parser will try to extract the longest possible variable + name after a literal percent '%' sign everywhere in the parameter string. So the command sequence + [/p] + [example] + %number = 1st; echo this is my %number variable test + [/example] + [p] + will first assign "1st" to the variable "%number" and then execute + "echo this is my 1st variable test". The following example will NOT work as expected. + [/p] + [example] + %number = 1; echo this is my %numberst variable test + [/example] + [p] + KVS will assign "1" to %number in this case but the next variable + name extracted will be "%numberst" that is actually empty; so finally + "echo this is my variable test" will be executed. + To avoid this problem you can use the backslash escape character: + [/p] + [example] + %number = 1; echo this is my %number\st variable test + [/example] + + [title]Putting it all together[/title] + + [p] + Variables can be either local, global or have an extended scope. Their start to exist + when you first assign something to them and they disappear when they go out of their + scope or you explicitly destroy them. + [/p] + + [p] + KVS has 8 builtin data types: string, integer, real, boolean, hobject, nothing, array and hash. + The first 6 are scalar data types while the last two are not. + [/p] + + [p] + When possible, KVS manages all the conversions between data types silently. + In the few cases in that an implicit conversion is not possible you have to manage the conversion + manually otherwise KVS will complain. + [/p] +*/ + + + +void KviKvsParser::skipSpaces() +{ + while((KVSP_curCharUnicode == ' ') || (KVSP_curCharUnicode == '\t')) + { + KVSP_skipChar; + } + + if(KVSP_curCharUnicode == '\\') + { + KVSP_skipChar; + if(KVSP_curCharUnicode == '\n') + { + KVSP_skipChar; + skipSpaces(); + return; + } else if(KVSP_curCharUnicode == '\r') + { + KVSP_skipChar; + if(KVSP_curCharUnicode == '\n') + { + KVSP_skipChar; + skipSpaces(); + return; + } else { + KVSP_backChar; + KVSP_backChar; + } + } else { + KVSP_backChar; + } + } +} + + + +bool KviKvsParser::skipSpacesAndNewlines() +{ + while((KVSP_curCharUnicode == ' ') || (KVSP_curCharUnicode == '\t') || (KVSP_curCharUnicode == '\n') || (KVSP_curCharUnicode == '\r')) + { + KVSP_skipChar; + } + + switch(KVSP_curCharUnicode) + { + case '\\': + KVSP_skipChar; + if(KVSP_curCharUnicode == '\n') + { + KVSP_skipChar; + return skipSpacesAndNewlines(); + } else if(KVSP_curCharUnicode == '\r') + { + KVSP_skipChar; + if(KVSP_curCharUnicode == '\n') + { + KVSP_skipChar; + return skipSpacesAndNewlines(); + } else { + KVSP_backChar; + KVSP_backChar; + } + } else { + KVSP_backChar; + } + break; + case '#': + case '/': + // we allow comments too! + (void)parseComment(); // this will return 0 anyway (and never trigger an error here) + if(error())return false; + return skipSpacesAndNewlines(); + break; + } + return true; +} + +void KviKvsParser::skipToNextLine() +{ + while((KVSP_curCharUnicode != 0) && (KVSP_curCharUnicode != '\n')) + KVSP_skipChar; + + if(KVSP_curCharUnicode == '\n')KVSP_skipChar; +} + + +KviKvsTreeNodeInstruction * KviKvsParser::parseInstructionList() +{ + KviKvsTreeNodeInstructionBlock * l = new KviKvsTreeNodeInstructionBlock(KVSP_curCharPointer); + + + for(;;) + { + if(!skipSpacesAndNewlines()) + { + delete l; + return 0; + } + + if(KVSP_curCharUnicode != 0) + { + // instruction + KviKvsTreeNodeInstruction * i = parseInstruction(); + if(i)l->addInstruction(i); + else { + if(error()) + { + // ops... + delete l; + return 0; + } // else empty instruction + } + } else { + if(l->instructionCount() == 1) + { + // return the single instruction instead + KviKvsTreeNodeInstruction * i = l->releaseFirst(); + delete l; + return i; + } + // end of buffer + return l; + } + } + + // never here + KVSP_ASSERT(false); + return 0; +} + + + + +KviKvsTreeNodeData * KviKvsParser::parseParameterPercentOrDollar() +{ + KVSP_ASSERT((KVSP_curCharUnicode == '%') || (KVSP_curCharUnicode == '$') || (KVSP_curCharUnicode == '@')); + + if(KVSP_curCharUnicode == '%') + { + KVSP_skipChar; + if(!KVSP_curCharIsLetter && (KVSP_curCharUnicode != ':')) + { + // be flexible : allow an "alone" '%' char + return new KviKvsTreeNodeConstantData(KVSP_curCharPointer - 1,new KviKvsVariant(QString("%"))); + } + // this is surely a variable or function + KVSP_backChar; + } else if(KVSP_curCharUnicode == '$') + { + KVSP_skipChar; + if(!KVSP_curCharIsFunctionStart) + { + // be flexible : allow an "alone" '$' char + return new KviKvsTreeNodeConstantData(KVSP_curCharPointer - 1,new KviKvsVariant(QString("$"))); + } + // this is surely a variable or function + KVSP_backChar; + } + + return parsePercentOrDollar(); +} + + + +KviKvsTreeNodeData * KviKvsParser::parsePercentOrDollar(bool bInObjScope) +{ + KVSP_ASSERT((KVSP_curCharUnicode == '%') || (KVSP_curCharUnicode == '$') || (KVSP_curCharUnicode == '@')); + + KviKvsTreeNodeData * r; + const QChar * pBegin; + + if(KVSP_curCharUnicode == '%') + { + r = parsePercent(bInObjScope); + if(!r)return 0; + } else if(KVSP_curCharUnicode == '$') + { + r = parseDollar(bInObjScope); + if(!r)return 0; + } else { + // this is @ + static QString szStrayAtRoutineName("@"); + static QString szMightBeStrayAtOrThisRoutineName("@?"); + + pBegin = KVSP_curCharPointer; + + KVSP_skipChar; + + if(bInObjScope || ((KVSP_curCharUnicode != '$') && (KVSP_curCharUnicode != '%'))) + { + // we're sure this is just a stray @ + // we use a trick here: when @ is not supposed to be an object scope call + // then we create a function that will return the @ itself as a string + KviKvsCoreFunctionExecRoutine * pRoutine = KviKvsKernel::instance()->findCoreFunctionExecRoutine(szStrayAtRoutineName); + r = new KviKvsTreeNodeCoreFunctionCall(KVSP_curCharPointer,szStrayAtRoutineName,pRoutine,new KviKvsTreeNodeDataList(KVSP_curCharPointer)); + //KVSP_skipChar; + return r; + } + // we're not in object scope and cur char is either $ or % + // check for the common syntax $0!$1@$2 seen in hostmasks + // @$<digit> is non valid anyway + if(KVSP_curCharUnicode == '$') + { + KVSP_skipChar; + if(KVSP_curCharIsNumber) + { + // again a stray @ + KVSP_backChar; + KviKvsCoreFunctionExecRoutine * pRoutine = KviKvsKernel::instance()->findCoreFunctionExecRoutine(szStrayAtRoutineName); + r = new KviKvsTreeNodeCoreFunctionCall(KVSP_curCharPointer,szStrayAtRoutineName,pRoutine,new KviKvsTreeNodeDataList(KVSP_curCharPointer)); + return r; + } + KVSP_backChar; + } + + // now we're unsure: we will be able to decide only at runtime if it is a stray @ or the shortcut for $this + // this design was a bit ugly.. I must admit it... but it is really useful when writing object classes... + KviKvsCoreFunctionExecRoutine * pRoutine = KviKvsKernel::instance()->findCoreFunctionExecRoutine(szMightBeStrayAtOrThisRoutineName); + // MUST BE THERE! + // core function call + r = new KviKvsTreeNodeCoreFunctionCall(pBegin,szMightBeStrayAtOrThisRoutineName,pRoutine,new KviKvsTreeNodeDataList(pBegin)); + + skipSpaces(); + + goto handle_scope_operator; + } + + pBegin = KVSP_curCharPointer; + + while((KVSP_curCharUnicode == '[') || (KVSP_curCharUnicode == '{')) + { + if(KVSP_curCharUnicode == '[') + { + // array index + KVSP_skipChar; + skipSpaces(); + if(KVSP_curCharUnicode == ']') + { + KVSP_skipChar; + if(KVSP_curCharUnicode == '#') + { + // count + KVSP_skipChar; + return new KviKvsTreeNodeArrayCount(pBegin,r); + } else { + // a hash reference assert + return new KviKvsTreeNodeArrayReferenceAssert(pBegin,r); + } + } + + KviKvsTreeNodeExpression * e = parseExpression(']'); + if(!e) + { + delete r; + return 0; + } + + r = new KviKvsTreeNodeArrayElement(pBegin,r,e); + } else { + // hash key + KVSP_skipChar; + skipSpaces(); + + if(KVSP_curCharUnicode == '}') + { + // entire hash ? + KVSP_skipChar; + if(KVSP_curCharUnicode == '#') + { + KVSP_skipChar; + return new KviKvsTreeNodeHashCount(pBegin,r); + } + return new KviKvsTreeNodeHashReferenceAssert(pBegin,r); + } + + KviKvsTreeNodeData * i = parseHashKey(); + if(!i) + { + // error + delete r; + return 0; + } + + KVSP_ASSERT(KVSP_curCharUnicode == '}'); + + KVSP_skipChar; + + r = new KviKvsTreeNodeHashElement(pBegin,r,i); + } + } + + if(KVSP_curCharUnicode != '-') + { + return r; + } + + if(!r->canEvaluateToObjectReference())return r; // FIXME: maybe print a warning ? + + // might be a scope operator + + KVSP_skipChar; + if(KVSP_curCharUnicode != '>') + { + KVSP_backChar; + return r; + } + + + KVSP_skipChar; + skipSpaces(); + + if((KVSP_curCharUnicode != '$') && (KVSP_curCharUnicode != '%')) + { + KVSP_setCurCharPointer(pBegin); + return r; + } + +handle_scope_operator: + + // hmmm... there really seems to be a scope operator there... + if(KVSP_curCharUnicode == '%') + { + KVSP_skipChar; + if(!KVSP_curCharIsLetter) + { + // be flexible : allow an "alone" '%' char + KVSP_setCurCharPointer(pBegin); + return r; + } + } else { + KVSP_skipChar; + if(!KVSP_curCharIsFunctionStart) + { + // be flexible : allow an "alone" '$' char + KVSP_setCurCharPointer(pBegin); + return r; + } + } + + // ok : try the scope operator + KVSP_backChar; + + pBegin = KVSP_curCharPointer; + + KviKvsTreeNodeData * r2 = parsePercentOrDollar(true); + + if(!r2) + { + // must be an error + delete r; + return 0; + } + + if(!r2->canEvaluateInObjectScope()) + { + // ops... it really wasn't + delete r2; + KVSP_setCurCharPointer(pBegin); + return r; + } + + return new KviKvsTreeNodeScopeOperator(pBegin,r,r2); +} + + + + + +KviKvsTreeNodeVariable * KviKvsParser::parsePercent(bool bInObjScope) +{ + KVSP_ASSERT(KVSP_curCharUnicode == '%'); + + const QChar * pBegin = KVSP_curCharPointer; + + KVSP_skipChar; + + bool bExtScope; + + if(KVSP_curCharUnicode == ':') + { + bExtScope = true; + KVSP_skipChar; + } else { + bExtScope = false; + } + + if(!((KVSP_curCharIsLetterOrNumber) || (KVSP_curCharUnicode == '_'))) + { + error(KVSP_curCharPointer,__tr2qs("Syntax error after '%' variable prefix. If you want to use a plain '%' in the code you need to escape it")); + return 0; + } + + const QChar * pIdBegin = KVSP_curCharPointer; + + while((KVSP_curCharIsLetterOrNumber) || (KVSP_curCharUnicode == '_') || (KVSP_curCharUnicode == '.'))KVSP_skipChar; + + QString szIdentifier(pIdBegin,KVSP_curCharPointer - pIdBegin); + +//#warning "ADD A KviKvsTreeNodeBuiltinCleanupVariablesCommand on this KviKvsParser object" +//#warning "KviKvsParser will append it to the script" + + if(bExtScope) + { + if(bInObjScope) + { + error(KVSP_curCharPointer,__tr2qs("Objects have no extended scope variables")); + return 0; + } + return new KviKvsTreeNodeExtendedScopeVariable(pBegin,szIdentifier); + } + + if(bInObjScope) + return new KviKvsTreeNodeObjectField(pBegin,szIdentifier); + + if(m_pGlobals) + { + if(m_pGlobals->find(szIdentifier))return new KviKvsTreeNodeGlobalVariable(pBegin,szIdentifier); + } + + if(m_iFlags & AssumeLocals) + return new KviKvsTreeNodeLocalVariable(pBegin,szIdentifier); + + if(pIdBegin->category() & QChar::Letter_Uppercase) + { + //if(m_iFlags & Pedantic) + // warning(pIdBegin,__tr2qs("Declaring global variables with an uppercase letter is deprecated. Global variables should be declared with 'global'")); + return new KviKvsTreeNodeGlobalVariable(pBegin,szIdentifier); + } + + return new KviKvsTreeNodeLocalVariable(pBegin,szIdentifier); +} + +KviKvsTreeNodeInstruction * KviKvsParser::parseInstruction() +{ + switch(KVSP_curCharUnicode) + { + case '#': + case '/': + (void)parseComment(); // this will return 0 anyway + return 0; + break; + case 0: // empty instruction + return 0; + break; + case '\n': + case '\r': + case ';': // empty instruction + KVSP_skipChar; + return 0; + break; + case '{': // command block + return parseInstructionBlock(); + break; + case '$': + case '%': + case '@': + return parseVoidFunctionCallOrOperation(); + break; + default: + if(KVSP_curCharIsLetter || (KVSP_curCharUnicode == '_')) + { + // must be a command + return parseCommand(); + } else { + // what the heck is this ? + error(KVSP_curCharPointer,__tr2qs("Found character '%q' (unicode %x) where an instruction was expected"),KVSP_curCharPointer,KVSP_curCharUnicode); + return 0; + } + break; + } + + // never here + KVSP_ASSERT(false); + return 0; +} + +KviKvsTreeNodeInstruction * KviKvsParser::parseInstructionBlock() +{ + KVSP_ASSERT(KVSP_curCharUnicode == '{'); + + KVSP_skipChar; + + const QChar * pBegin = KVSP_curCharPointer; + + KviKvsTreeNodeInstructionBlock * b = new KviKvsTreeNodeInstructionBlock(pBegin - 1); + + for(;;) + { + if(!skipSpacesAndNewlines()) + { + delete b; + return 0; + } + + switch(KVSP_curCharUnicode) + { + case 0: + delete b; + warning(pBegin,__tr2qs("Unterminated instruction block")); + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script in instruction block (missing closing brace)")); + return 0; + break; + case '}': + KVSP_skipChar; + if(b->instructionCount() <= 1) + { + if(b->instructionCount() < 1) + { + delete b; + return 0; // just an empty block + } + // a single instruction + KviKvsTreeNodeInstruction * i = b->releaseFirst(); + delete b; + return i; + } + return b; + break; + default: + // instruction + KviKvsTreeNodeInstruction * i = parseInstruction(); + if(i)b->addInstruction(i); + else { + if(error()) + { + // ops... + delete b; + return 0; + } // else empty instruction + } + break; + } + } + // never reached + return 0; +} + +KviKvsTreeNodeSwitchList * KviKvsParser::parseCommandSwitchList() +{ + KVSP_ASSERT(KVSP_curCharUnicode == '-'); + + KviKvsTreeNodeSwitchList * sw = new KviKvsTreeNodeSwitchList(KVSP_curCharPointer); + + while(KVSP_curCharUnicode == '-') + { + const QChar * pBegin = KVSP_curCharPointer; + KVSP_skipChar; + + bool bLong = false; + + if(KVSP_curCharUnicode == '-') + { + // long switch + pBegin = KVSP_curCharPointer; + KVSP_skipChar; + bLong = true; + } + + skipSpaces(); + if(!KVSP_curCharIsLetter) + { + if(KVSP_curCharIsNumber || KVSP_curCharIsEndOfCommand) + { + // a -digit : this is probably a negative number instead + // or just a single dash (or couple of dashes) + // go back to the initial dash and treat it as text...and return the current switch list + KVSP_setCurCharPointer(pBegin); + if(sw->isEmpty()) + { + // not an error! + delete sw; + return 0; + } + return sw; + } else { + delete sw; + warning(pBegin,__tr2qs("The dash after a command should be followed by a letter (switch), by a digit (negative number) or be escaped")); + + if(KVSP_curCharUnicode == 0) + { + error(KVSP_curCharPointer,__tr2qs("Unexpected character '%q' (unicode %x) after a switch dash"),KVSP_curCharPointer,KVSP_curCharUnicode); + } else { + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script after a switch dash")); + } + return 0; + } + } + + const QChar * pSw = KVSP_curCharPointer; + + KVSP_skipChar; + while((KVSP_curCharIsLetterOrNumber) || (KVSP_curCharUnicode == '-'))KVSP_skipChar; + + const QChar * pSwEnd = KVSP_curCharPointer; + + skipSpaces(); + + if(KVSP_curCharUnicode == '=') + { + KVSP_skipChar; + skipSpaces(); + KviKvsTreeNodeData * p = parseCommandParameter(); + if(!p) + { + // must be an error :( + if(error()) + { + error(pBegin,__tr2qs("The above problem might be related to the switch dash and the following equal sign")); + delete sw; + return 0; + } else { + // assume empty string + p = new KviKvsTreeNodeConstantData(KVSP_curCharPointer,new KviKvsVariant(QString(""))); + } + } + + skipSpaces(); + + if(bLong) + sw->addLong(QString(pSw,pSwEnd - pSw),p); + else + sw->addShort(pSw->lower().unicode(),p); + } else { + if(bLong) + sw->addLong(QString(pSw,pSwEnd - pSw),new KviKvsTreeNodeConstantData(KVSP_curCharPointer,new KviKvsVariant(true))); // empty param + else + sw->addShort(pSw->lower().unicode(),new KviKvsTreeNodeConstantData(KVSP_curCharPointer,new KviKvsVariant(true))); // empty param + } + } + + return sw; +} + + + +KviKvsTreeNodeDataList * KviKvsParser::parseCommandParameterList() +{ + KviKvsTreeNodeDataList * l = new KviKvsTreeNodeDataList(KVSP_curCharPointer); + + for(;;) + { + skipSpaces(); + switch(KVSP_curCharUnicode) + { + case 0: + return l; + break; + case '\r': + case '\n': + case ';': + KVSP_skipChar; + return l; + break; + default: + // anything else is a parameter + KviKvsTreeNodeData * p = parseCommandParameter(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->addItem(p); + break; + } + } + + // never here + KVSP_ASSERT(false); + return 0; +} + + +KviPointerList<QString> * KviKvsParser::parseCommaSeparatedParameterListNoTree() +{ + KviPointerList<QString> * l = new KviPointerList<QString>; + l->setAutoDelete(true); + + KVSP_skipChar; + + for(;;) + { + skipSpaces(); + switch(KVSP_curCharUnicode) + { + case 0: + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script in parameter list")); + delete l; + return 0; + break; + case '\r': + case '\n': + error(KVSP_curCharPointer,__tr2qs("Unexpected end of line in parameter list")); + delete l; + return 0; + break; + /* + case ',': + KVSP_skipChar; + break; + case ')': + KVSP_skipChar; + return l; + break; + */ + default: + { + // anything else is a parameter + const QChar *pBegin = KVSP_curCharPointer; + KviKvsTreeNodeData * p = parseCommaSeparatedParameter(); + if(!p) + { + // this is an error + delete l; + return 0; + } + delete p; + QString * s = new QString(pBegin,KVSP_curCharPointer - pBegin); + s->stripWhiteSpace(); + l->append(s); + + switch(KVSP_curCharUnicode) + { + case ',': + KVSP_skipChar; + break; + case ')': + KVSP_skipChar; + return l; + break; + } + } + break; + } + } + + // never here + KVSP_ASSERT(false); + return 0; +} + + +KviKvsTreeNodeDataList * KviKvsParser::parseCommaSeparatedParameterList() +{ + KviKvsTreeNodeDataList * l = new KviKvsTreeNodeDataList(KVSP_curCharPointer); + + KVSP_skipChar; + + for(;;) + { + skipSpaces(); + switch(KVSP_curCharUnicode) + { + case 0: + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script in parameter list")); + delete l; + return 0; + break; + case '\r': + case '\n': + error(KVSP_curCharPointer,__tr2qs("Unexpected end of line in parameter list")); + delete l; + return 0; + break; + /* + case ',': + KVSP_skipChar; + break; + case ')': + KVSP_skipChar; + return l; + break; + */ + default: + // anything else is a parameter + KviKvsTreeNodeData * p = parseCommaSeparatedParameter(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->addItem(p); + + switch(KVSP_curCharUnicode) + { + case ',': + KVSP_skipChar; + break; + case ')': + KVSP_skipChar; + return l; + break; + } + break; + } + } + + // never here + KVSP_ASSERT(false); + return 0; +} + + +#define LITERAL_PARAM_PARSING_FUNCTION_BEGIN(__funcname) \ +KviKvsTreeNodeConstantData * KviKvsParser::__funcname() \ +{ \ + QString szValue; \ +\ + const QChar * pStart = KVSP_curCharPointer; \ + const QChar * pBegin = KVSP_curCharPointer; \ + int iLen = 0; \ + int iNestedTerminators = 0; \ + \ + for(;;) \ + { \ + switch(KVSP_curCharUnicode) \ + { + +#define LITERAL_PARAM_PARSING_FUNCTION_WARN_NESTED_TERMINATOR \ + if(!_OUTPUT_MUTE) \ + warning(KVSP_curCharPointer,__tr2qs("Nested character %q corresponding to expected terminator, this might confuse me a bit: it is a good idea to enclose it in quotes"),KVSP_curCharPointer); \ + KVSP_skipChar; \ + iNestedTerminators++; \ + iLen++; \ + break; + +#define LITERAL_PARAM_PARSING_FUNCTION_END_WITH_EXPECTED_TERMINATOR \ + if(iNestedTerminators > 0) \ + { \ + if(!_OUTPUT_MUTE) \ + warning(KVSP_curCharPointer,__tr2qs("Skipping nested terminator character %q"),KVSP_curCharPointer); \ + KVSP_skipChar; \ + iNestedTerminators--; \ + iLen++; \ + } else { \ + if(iLen > 0)szValue.append(QString(pBegin,iLen)); \ + { \ + bool bOk; \ + kvs_int_t iVal = szValue.toLong(&bOk); \ + if(bOk)return new KviKvsTreeNodeConstantData(pBegin,new KviKvsVariant(iVal)); \ + kvs_real_t dVal = szValue.toDouble(&bOk); \ + if(bOk)return new KviKvsTreeNodeConstantData(pBegin,new KviKvsVariant(dVal)); \ + } \ + return new KviKvsTreeNodeConstantData(pBegin,new KviKvsVariant(szValue)); \ + } \ + break; + +#define LITERAL_PARAM_PARSING_FUNCTION_GENERIC_END \ + if(iLen > 0) szValue.append(QString(pBegin,iLen)); \ + return new KviKvsTreeNodeConstantData(pBegin,new KviKvsVariant(szValue)); \ + break; \ + case '\\': \ + if(iLen > 0)szValue.append(QString(pBegin,iLen)); \ + KVSP_skipChar; \ + switch(KVSP_curCharUnicode) \ + { \ + case 0: \ + warning(KVSP_curCharPointer - 1,__tr2qs("Stray backslash at the end of the script")); \ + iLen = 0; \ + break; \ + case '\r': \ + case '\n': \ + KVSP_skipChar; \ + pBegin = KVSP_curCharPointer; \ + iLen = 0; \ + break; \ + case 'r': \ + KVSP_skipChar; \ + pBegin = KVSP_curCharPointer; \ + szValue.append(QChar('\r')); \ + iLen = 0; \ + break; \ + case 'n': \ + KVSP_skipChar; \ + pBegin = KVSP_curCharPointer; \ + szValue.append(QChar('\n')); \ + iLen = 0; \ + break; \ + case 't': \ + KVSP_skipChar; \ + pBegin = KVSP_curCharPointer; \ + szValue.append(QChar('\t')); \ + iLen = 0; \ + break; \ + default: \ + pBegin = KVSP_curCharPointer; \ + KVSP_skipChar; \ + iLen = 1; \ + break; \ + } \ + break; \ + default: \ + KVSP_skipChar; \ + iLen++; \ + break; \ + } \ + } \ + KVSP_ASSERT(false); \ + return 0; \ +} + + +LITERAL_PARAM_PARSING_FUNCTION_BEGIN(parseCommandLiteralParameter) + case 0: + case '$': + case '%': + case '@': + case '\r': + case '\n': + case '"': + case ';': + case ' ': + case '\t': +LITERAL_PARAM_PARSING_FUNCTION_GENERIC_END + + +LITERAL_PARAM_PARSING_FUNCTION_BEGIN(parseStringLiteralParameter) + case 0: + case '$': + case '%': + case '@': + case '\r': + case '\n': + case '"': +LITERAL_PARAM_PARSING_FUNCTION_GENERIC_END + +/* +LITERAL_PARAM_PARSING_FUNCTION_BEGIN(parseArrayIndexLiteralParameter) + case '\t': + case ' ': + case ']': +LITERAL_PARAM_PARSING_FUNCTION_END +*/ + + +LITERAL_PARAM_PARSING_FUNCTION_BEGIN(parseHashKeyLiteralParameter) + case '{': +LITERAL_PARAM_PARSING_FUNCTION_WARN_NESTED_TERMINATOR + case '}': +LITERAL_PARAM_PARSING_FUNCTION_END_WITH_EXPECTED_TERMINATOR + case 0: + case '$': + case '%': + case '@': + case '\r': + case '\n': + case '"': + case '\t': + case ' ': +LITERAL_PARAM_PARSING_FUNCTION_GENERIC_END + + +LITERAL_PARAM_PARSING_FUNCTION_BEGIN(parseCommaSeparatedLiteralParameter) + case '(': +LITERAL_PARAM_PARSING_FUNCTION_WARN_NESTED_TERMINATOR + case ')': +LITERAL_PARAM_PARSING_FUNCTION_END_WITH_EXPECTED_TERMINATOR + case 0: + case '$': + case '%': + case '@': + case '\r': + case '\n': + case '"': + case ',': + case ' ': + case '\t': +LITERAL_PARAM_PARSING_FUNCTION_GENERIC_END + + +LITERAL_PARAM_PARSING_FUNCTION_BEGIN(parseSingleLiteralParameterInParenthesis) + case '(': +LITERAL_PARAM_PARSING_FUNCTION_WARN_NESTED_TERMINATOR + case ')': +LITERAL_PARAM_PARSING_FUNCTION_END_WITH_EXPECTED_TERMINATOR + case 0: + case '$': + case '%': + case '@': + case '\r': + case '\n': + case '"': + case ' ': + case '\t': +LITERAL_PARAM_PARSING_FUNCTION_GENERIC_END + +LITERAL_PARAM_PARSING_FUNCTION_BEGIN(parseBindingOperationLiteralParameter) + case 0: + case '$': + case '%': + case '@': + case '\r': + case '\n': + case '"': + case '/': +LITERAL_PARAM_PARSING_FUNCTION_GENERIC_END + + +/* +KviKvsTreeNodeData * KviKvsParser::parseArrayIndex() +{ + KviPointerList<KviKvsTreeNodeData> * l = new KviPointerList<KviKvsTreeNodeData>(); + l->setAutoDelete(true); + + const QChar * pBegin = KVSP_curCharPointer; + + //KVSP_skipChar; + + for(;;) + { + switch(KVSP_curCharUnicode) + { + case 0: + delete l; + warning(pBegin,__tr2qs("Unterminated array index")); + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script in array index (missing ']' character ?)")); + return 0; + break; + case '\n': + delete l; + warning(pBegin,__tr2qs("Unterminated array index")); + error(KVSP_curCharPointer,__tr2qs("Unexpected end of line in array index (missing ']' character or unescaped newline)")); + return 0; + break; + case ' ': + case '\t': + skipSpaces(); + if(KVSP_curCharUnicode != ']') + { + delete l; + warning(pBegin,__tr2qs("Unterminated array index")); + switch(KVSP_curCharUnicode) + { + case 0: + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script in array index (missing ']' character ?)")); + break; + case '\n': + error(KVSP_curCharPointer,__tr2qs("Unexpected end of line in array index (missing ']' character or unescaped newline)")); + break; + default: + error(KVSP_curCharPointer,__tr2qs("Unexpected character '%q' (unicode %x) in array index: it should be already terminated"),KVSP_curCharPointer,KVSP_curCharUnicode); + break; + } + return 0; + } + goto end_of_the_array_index; + break; + case '$': + case '%': + { + // this may be a data reference + KviKvsTreeNodeData * p = parseParameterPercentOrDollar(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + case ']': + { + // end of the array index + goto end_of_the_array_index; + } + break; + case '"': + { + // string (should we parse strings in array indexes anyway ?).. well "1"$count might be a valid one in the end + KviKvsTreeNodeData * p = parseStringParameter(); + if(!p) + { + // error + delete l; + return 0; + } + l->append(p); + } + break; + default: + { + // anything else is a literal + l->append(parseArrayIndexLiteralParameter()); + } + break; + } + } +end_of_the_array_index: + if(l->count() > 1) + { + // complex parameter needed + return new KviKvsTreeNodeCompositeData(l); + } else { + // a single parameter in the list + l->setAutoDelete(false); + KviKvsTreeNodeData * p = l->first(); + delete l; + return p; + } + +} +*/ + + + + + +KviKvsTreeNodeData * KviKvsParser::parseHashKey() +{ + KviPointerList<KviKvsTreeNodeData> * l = new KviPointerList<KviKvsTreeNodeData>(); + l->setAutoDelete(true); + + const QChar * pBegin = KVSP_curCharPointer; + + //KVSP_skipChar; + + for(;;) + { + switch(KVSP_curCharUnicode) + { + case 0: + delete l; + warning(pBegin,__tr2qs("Unterminated hash key")); + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script in hash key (missing '}' character ?)")); + return 0; + break; + case '\r': + case '\n': + delete l; + warning(pBegin,__tr2qs("Unterminated hash key")); + error(KVSP_curCharPointer,__tr2qs("Unexpected end of line in hash key (missing '}' character or unescaped newline)")); + return 0; + break; + case ' ': + case '\t': + skipSpaces(); + if(KVSP_curCharUnicode != '}') + { + // separate by single spaces + l->append(new KviKvsTreeNodeConstantData(KVSP_curCharPointer,new KviKvsVariant(QString(" ")))); + } else { + goto end_of_the_hash_key; + } + break; + case '$': + case '%': + case '@': + { + // this may be a data reference + KviKvsTreeNodeData * p = parseParameterPercentOrDollar(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + case '}': + { + // end of the array index + goto end_of_the_hash_key; + } + break; + case '"': + { + // string + KviKvsTreeNodeData * p = parseStringParameter(); + if(!p) + { + // error + delete l; + return 0; + } + l->append(p); + } + break; + default: + { + // anything else is a literal + l->append(parseHashKeyLiteralParameter()); + } + break; + } + } +end_of_the_hash_key: + if(l->count() > 1) + { + // complex parameter needed + return new KviKvsTreeNodeCompositeData(pBegin,l); + } else { + // a single parameter in the list + l->setAutoDelete(false); + KviKvsTreeNodeData * p = l->first(); + delete l; + return p; + } + // never reached + return 0; +} + +/* +PARENTHESIS_PARAMETER_PARSING_FUNCTION_BEGIN(parseCommaSeparatedParameter) + case 0: + case ',': + case ')': + case '\n': +PARENTHESIS_PARAMETER_PARSING_FUNCTION_END() + +#define PARENTHESIS_PARAMETER_PARSING_FUNCTION_BEGIN(_name) \ +*/ + + + +KviKvsTreeNodeData * KviKvsParser::parseCommaSeparatedParameter() +{ + KviPointerList<KviKvsTreeNodeData> * l = new KviPointerList<KviKvsTreeNodeData>; + l->setAutoDelete(true); + + const QChar * pBegin = KVSP_curCharPointer; + + for(;;) + { + switch(KVSP_curCharUnicode) + { + case 0: + case ',': + case ')': + case '\r': + case '\n': + // not a part of a parameter + goto end_of_function_parameter; + break; + case '$': + case '%': + case '@': + { + // this may be a data reference + KviKvsTreeNodeData * p = parseParameterPercentOrDollar(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + case ' ': + case '\t': + skipSpaces(); + if((KVSP_curCharUnicode != ')') && (KVSP_curCharUnicode != ',')) + { + // separate by single spaces + l->append(new KviKvsTreeNodeConstantData(KVSP_curCharPointer,new KviKvsVariant(QString(" ")))); + } else { + goto end_of_function_parameter; + } + break; + case '"': + { + // this is a string + KviKvsTreeNodeData * p = parseStringParameter(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + default: + { + // anything else is a literal + l->append(parseCommaSeparatedLiteralParameter()); + } + break; + } + } +end_of_function_parameter: + if(l->count() > 1) + { + // complex parameter needed + KviKvsTreeNodeData * p = new KviKvsTreeNodeCompositeData(pBegin,l); + p->setEndingLocation(KVSP_curCharPointer); + return p; + } else { + // a single parameter in the list, or no params at all + l->setAutoDelete(false); + KviKvsTreeNodeData * p = l->first(); + delete l; + if(!p)p = new KviKvsTreeNodeConstantData(KVSP_curCharPointer,new KviKvsVariant()); + p->setEndingLocation(KVSP_curCharPointer); + return p; + } + // never reached + return 0; +} + + +KviKvsTreeNodeData * KviKvsParser::parseSingleParameterInParenthesis() +{ + KviPointerList<KviKvsTreeNodeData> * l = new KviPointerList<KviKvsTreeNodeData>; + l->setAutoDelete(true); + + const QChar * pBegin = KVSP_curCharPointer; + + for(;;) + { + switch(KVSP_curCharUnicode) + { + case ')': + // not a part of a parameter + KVSP_skipChar; + goto end_of_function_parameter; + break; + case 0: + case '\r': + case '\n': + // not a part of a parameter + goto end_of_function_parameter; + break; + case '$': + case '%': + case '@': + { + // this may be a data reference + KviKvsTreeNodeData * p = parseParameterPercentOrDollar(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + case ' ': + case '\t': + skipSpaces(); + if((KVSP_curCharUnicode != ')') && (KVSP_curCharUnicode != ',')) + { + // separate by single spaces + l->append(new KviKvsTreeNodeConstantData(KVSP_curCharPointer,new KviKvsVariant(QString(" ")))); + } else { + goto end_of_function_parameter; + } + break; + case '"': + { + // this is a string + KviKvsTreeNodeData * p = parseStringParameter(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + default: + { + // anything else is a literal + l->append(parseSingleLiteralParameterInParenthesis()); + } + break; + } + } +end_of_function_parameter: + if(l->count() > 1) + { + // complex parameter needed + KviKvsTreeNodeData * p = new KviKvsTreeNodeCompositeData(pBegin,l); + p->setEndingLocation(KVSP_curCharPointer); + return p; + } else { + // a single parameter in the list or list empty at all + l->setAutoDelete(false); + KviKvsTreeNodeData * p = l->first(); + if(p)p->setEndingLocation(KVSP_curCharPointer); + delete l; + return p; + } + // never reached + return 0; +} + + +KviKvsTreeNodeData * KviKvsParser::parseStringParameter() +{ + KVSP_ASSERT(KVSP_curCharUnicode == '"'); + + KviPointerList<KviKvsTreeNodeData> * l = new KviPointerList<KviKvsTreeNodeData>(); + l->setAutoDelete(true); + + const QChar * pBegin = KVSP_curCharPointer; + + KVSP_skipChar; + + for(;;) + { + switch(KVSP_curCharUnicode) + { + case 0: + delete l; + warning(pBegin,__tr2qs("Unterminated string constant")); + error(KVSP_curCharPointer,__tr2qs("Unexpected end of script in string constant (missing \" character ?)")); + return 0; + break; + case '\r': + case '\n': + delete l; + warning(pBegin,__tr2qs("Unterminated string constant")); + error(KVSP_curCharPointer,__tr2qs("Unexpected end of line in string constant (missing \" character or unescaped newline)")); + return 0; + break; + case '$': + case '%': + case '@': + { + // this may be a data reference + KviKvsTreeNodeData * p = parseParameterPercentOrDollar(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + case '"': + { + // end of the string + KVSP_skipChar; + goto end_of_the_string; + } + break; + default: + { + // anything else is a literal + l->append(parseStringLiteralParameter()); + } + break; + } + } +end_of_the_string: + if(l->count() > 1) + { + // complex parameter needed + // it is also an implicit string cast + return new KviKvsTreeNodeCompositeData(pBegin,l); + } else { + if(l->count() > 0) + { + // a single parameter in the list + // we need an explicit string cast here (it is the most common cast) + l->setAutoDelete(false); + KviKvsTreeNodeData * p = l->first(); + delete l; + return new KviKvsTreeNodeStringCast(pBegin,p); + } else { + // no parameters at all.. return straight empty string (no need to cast) + delete l; + return new KviKvsTreeNodeConstantData(pBegin,new KviKvsVariant(new QString())); + } + } + // never reached + return 0; +} + +KviKvsTreeNodeData * KviKvsParser::parseCommandParameter(bool bPreferNumeric) +{ + KviPointerList<KviKvsTreeNodeData> * l = new KviPointerList<KviKvsTreeNodeData>; + l->setAutoDelete(true); + + bool bGotLiteral = false; + + const QChar * pBegin = KVSP_curCharPointer; + + for(;;) + { + switch(KVSP_curCharUnicode) + { + case 0: + case ' ': + case '\t': + case '\r': + case '\n': + case ';': + // not a part of a parameter + goto jumpout; + break; + case '$': + case '%': + case '@': + { + // this may be a data reference + KviKvsTreeNodeData * p = parseParameterPercentOrDollar(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + case '"': + { + // this is a string + KviKvsTreeNodeData * p = parseStringParameter(); + if(!p) + { + // this is an error + delete l; + return 0; + } + l->append(p); + } + break; + default: + { + bGotLiteral = true; + // anything else is a literal + l->append(parseCommandLiteralParameter()); + } + break; + } + } +jumpout: + if(l->count() > 1) + { + // complex parameter needed + KviKvsTreeNodeData * p = new KviKvsTreeNodeCompositeData(pBegin,l); + p->setEndingLocation(KVSP_curCharPointer); + return p; + } else { + // a single parameter in the list or empty list at all + l->setAutoDelete(false); + KviKvsTreeNodeData * p = l->first(); + delete l; + if(p) + { + if(bGotLiteral) + { + // a single literal parameter + if(bPreferNumeric) + { + // attempt to convert to a numeric format if this is a constant data item + p->convertStringConstantToNumeric(); + } + } + p->setEndingLocation(KVSP_curCharPointer); + } + return p; + } + // never reached + return 0; +} + |