You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kvirc/src/kvirc/kvs/kvi_kvs_parser.cpp

3828 lines
120 KiB

//=============================================================================
//
// 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(TQString(__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 TQChar * pLocation,const TQString &szMsgFmt,kvi_va_list va)
{
TQString szMsg;
KviTQString::vsprintf(szMsg,szMsgFmt,va);
KviPointerList<TQString> * pCodeListing = 0;
TQString szLocation;
if(pLocation)
{
pCodeListing = new KviPointerList<TQString>;
pCodeListing->setAutoDelete(true);
int iLine,iCol;
KviKvsReport::findLineColAndListing(m_pBuffer,pLocation,iLine,iCol,pCodeListing);
KviTQString::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 TQChar * 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 TQChar * pLocation,const TQString &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 TQChar * pLocation,const TQString &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 TQChar * 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 TQChar * 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 TQChar * 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 &nbsp; &nbsp; text &nbsp; &nbsp; will &nbsp; &nbsp; have &nbsp; &nbsp; spaces &nbsp; &nbsp; simplified
[cmd]echo[/cmd] But &nbsp; &nbsp; "this &nbsp; &nbsp; &nbsp; one &nbsp; &nbsp; 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 \
&nbsp; &nbsp; 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 &nbsp; &nbsp; &nbsp \
&nbsp; &nbsp; IS OUT!"
[cmd]echo[/cmd] Check it out at http://www.kvi\
&nbsp; &nbsp; rc.net!
[/example]
This will be printed as:[br][br]
[i]
The new kvirc &nbsp; &nbsp; &nbsp; 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] &lt;filename&gt;
[/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]
{
&lt;tab&gt;[cmd]echo[/cmd] Indented command
&lt;tab&gt;{
&lt;tab&gt;&lt;tab&gt;# Comment
&lt;tab&gt;&lt;tab&gt;[cmd]echo[/cmd] Really Really long indented \
&lt;tab&gt;&lt;tab&gt;&lt;tab&gt;command
&lt;tab&gt;}
}
[/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 \
&nbsp; &nbsp; 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]
&nbsp; &nbsp; [b]addonId-version/[/b]
&nbsp; &nbsp; &nbsp; &nbsp; install.kvs
&nbsp; &nbsp; &nbsp; &nbsp; INSTALL
&nbsp; &nbsp; &nbsp; &nbsp; [b]src[/b]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; source1.kvs
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; source2.kvs
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; source3.kvs
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; [b]pics[/b]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_pic1.png
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_pic2.png
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_pic3.png
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; [b]audio[/b]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_audio1.png
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_audio2.png
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_audio3.png
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; [b]help[/b]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; en
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; index.html
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hints.html
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; it
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; index.html
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hints.html
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; [b]locale[/b]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_it.mo
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_ru.mo
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addonId_de.mo
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; [b]...[/b]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...
[/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(TQString("%")));
}
// 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(TQString("$")));
}
// 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 TQChar * 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 TQString szStrayAtRoutineName("@");
static TQString 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 TQChar * 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 TQChar * pIdBegin = KVSP_curCharPointer;
while((KVSP_curCharIsLetterOrNumber) || (KVSP_curCharUnicode == '_') || (KVSP_curCharUnicode == '.'))KVSP_skipChar;
TQString 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() & TQChar::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 TQChar * 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 TQChar * 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 TQChar * pSw = KVSP_curCharPointer;
KVSP_skipChar;
while((KVSP_curCharIsLetterOrNumber) || (KVSP_curCharUnicode == '-'))KVSP_skipChar;
const TQChar * 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(TQString("")));
}
}
skipSpaces();
if(bLong)
sw->addLong(TQString(pSw,pSwEnd - pSw),p);
else
sw->addShort(pSw->lower().unicode(),p);
} else {
if(bLong)
sw->addLong(TQString(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<TQString> * KviKvsParser::parseCommaSeparatedParameterListNoTree()
{
KviPointerList<TQString> * l = new KviPointerList<TQString>;
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 TQChar *pBegin = KVSP_curCharPointer;
KviKvsTreeNodeData * p = parseCommaSeparatedParameter();
if(!p)
{
// this is an error
delete l;
return 0;
}
delete p;
TQString * s = new TQString(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() \
{ \
TQString szValue; \
\
const TQChar * pStart = KVSP_curCharPointer; \
const TQChar * 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(TQString(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(TQString(pBegin,iLen)); \
return new KviKvsTreeNodeConstantData(pBegin,new KviKvsVariant(szValue)); \
break; \
case '\\': \
if(iLen > 0)szValue.append(TQString(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(TQChar('\r')); \
iLen = 0; \
break; \
case 'n': \
KVSP_skipChar; \
pBegin = KVSP_curCharPointer; \
szValue.append(TQChar('\n')); \
iLen = 0; \
break; \
case 't': \
KVSP_skipChar; \
pBegin = KVSP_curCharPointer; \
szValue.append(TQChar('\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 TQChar * 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 TQChar * 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(TQString(" "))));
} 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 TQChar * 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(TQString(" "))));
} 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 TQChar * 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(TQString(" "))));
} 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 TQChar * 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 TQString()));
}
}
// never reached
return 0;
}
KviKvsTreeNodeData * KviKvsParser::parseCommandParameter(bool bPreferNumeric)
{
KviPointerList<KviKvsTreeNodeData> * l = new KviPointerList<KviKvsTreeNodeData>;
l->setAutoDelete(true);
bool bGotLiteral = false;
const TQChar * 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;
}