Tivoli Service Desk 6.0 Developer's Toolkit Script Programming Guide
Developer's Toolkit allows you to group statements together into routines and package them into reusable knowledgebases (or modules). This modular characteristic eases the difficulty of maintaining large amounts of source code.
This chapter covers the basics required to understand and build multi-knowledgebase applications.
The keyword ROUTINES is used to introduce any section of a knowledgebase that contains subroutines or procedures.
In the public section between the KNOWLEDGEBASE header and the PRIVATE keyword, the routines section contains declarations of procedures and functions that are implemented in the private section.
In the private section, the ROUTINES keyword is followed by the complete procedure and
function implementations.
An example is shown below:
KNOWLEDGEBASE Print; TYPES EmployeeRecord IS RECORD first_name, last_name: String; employee_ID: String; hireDate: Date; salary: Real; END; ROUTINES PrintEmployee(REF e: EmployeeRecord); PRIVATE ROUTINES PROCEDURE PrintString(VAL s: String) IS ACTIONS ... END; PROCEDURE PrintEmployee(REF e: EmployeeRecord) IS ACTIONS PrintString(e.first_name); PrintString(e.last_name); ... END;
Every Developer's Toolkit knowledgebase file consists of two primary sections.
In the following example, there is a knowledgebase called Statistics (contained in file statisti.kb) which is used by another knowledgebase called Application (in the file (applicat.kb)):
KNOWLEDGEBASE Statistics; ROUTINES PROCEDURE Regression; PROCEDURE TimeSeries; PROCEDURE ChiSquare; ... KNOWLEDGEBASE Application; ... PRIVATE USES Statistics; ...
By declaring Statistics in its uses section, the routines in Application are able to call the three procedures defined by Statistics.
The uses section is introduced by the USES keyword and must be placed in one (or both) of these places:
A knowledgebase can use as many other knowledgebases as it needs. Each used knowledgebase must be listed in the uses section and must be followed by a semicolon:
KNOWLEDGEBASE User; ... PRIVATE USES Used1; Used2; ... Used99;
A knowledgebase is used privately when it is contained in the USES statement in the private section of a knowledgebase.
When a knowledgebase is used privately, its public variables and procedures are available throughout the private section to implement the routines declared in the public section. In other words, the knowledgebase's private implementation can be composed of the public routines of another knowledgebase.
Two knowledgebases can use each other privately without restriction, as long as they name each other in the private section. That is, Knowledgebase A can use Knowledgebase B, and vice versa.
A knowledgebase is used publicly when it is contained in the USES statement in the public section of a knowledgebase.
When a knowledgebase is used publicly, the symbols from the used knowledgebase are available throughout the entire knowledgebase.
You should use a knowledgebase publicly when symbols from that knowledgebase are
required for some public declaration in the using knowledgebase.
An example of this is when a record type defined in one knowledgebase is used in a record
type declaration in another.
Cyclic use occurs when Knowledgebase A uses Knowledgebase B and vice versa. Knowledgebases cannot use each other cyclically in the PUBLIC section.
One way to avoid this limitation is to move the common declarations needed by both knowledgebases into a separate knowledgebase that uses nothing publicly itself. This is not always feasible because it may not make sense to combine declarations for very different functions into one knowledgebase. Sometimes, however, this solution works.
As an example, suppose that you create a program that tracks information pertaining to employees and the department they work in. You want to encapsulate most of the information relating to employees in a knowledgebase called "Employee" and the information relating to departments in a knowledgebase called "Department."
However, the public routines that manipulate employee records sometimes require department records as parameters, and vice versa. Therefore, there is a need for the two knowledgebases to use each other publicly. Because this will not work, the knowledgebases must instead be organized as shown in the following example:
KNOWLEDGEBASE EmplType; TYPES Employee IS RECORD ... END; ... END; KNOWLEDGEBASE DeptType; TYPES Department IS RECORD ... END; ... END; KNOWLEDGEBASE Employee; USES EmplType; DeptType; ROUTINES PROCEDURE AddEmployee (VAL lst: LIST OF Employee, REF dept: Department); ... PRIVATE ... END; KNOWLEDGEBASE Department; USES EmplType; DeptType; ROUTINES PROCEDURE AssignOffice (REF dept: Department, REF empl: Employee); ... PRIVATE END;
A procedure is a collection of Developer's Toolkit statements that are referred to by name and that do not return a value to the caller.
A procedure is a block that is nested within the knowledgebase. Like the knowledgebase itself, a procedure can have sections for constants, types, variables, and routines. Identifiers in these sections are local to the procedure and are not visible outside of it.
Each procedure has an actions section that is the location for the executable Developer's Toolkit statements that determine what the procedure does.
The general form for a procedure is shown below:
PROCEDURE <procedure-name>(<formal-parameter-list>) IS CONSTANTS <constant-declarations> TYPES <type-declarations> VARIABLES <variable-declarations> ROUTINES <subroutine-implementations> ACTIONS <KML statement-list> END;
The following is a simple procedure with an actions section that contains two Developer's Toolkit statements. When this procedure is called, the two statements in the actions section are executed:
PROCEDURE CopyrightMessage; ACTIONS WinWriteLN($DeskTop,'Copyright 1996 by'); WinWriteLN($DeskTop,'Software Artistry, Inc.'); END;
It is often useful for a procedure to contain local variables in a nested variables section. Local variables are program variables whose scope is limited to a specific block of code. As a rule, local variables are confined to a subroutine.
The following example creates a procedure called WriteTenLines. The procedure uses a local variable called i to iterate through a FOR loop and write 10 strings to a window:
PROCEDURE WriteTenLines; VARIABLES i: Integer; ACTIONS FOR i:=1 TO 10 DO WinWriteLn(myWindow,''); END; END;
This example is further enhanced to include a local constant:
PROCEDURE WriteTenLines; CONSTANTS MAX_LINES IS 10; VARIABLES i: Integer; ACTIONS FOR i:=1 TO MAX_LINES DO WinWriteLn(myWindow,''); END; END;
It is often desirable to create local routines, particularly if the variable you want to use only occurs in one part of a program. Local routines are procedures or functions declared inside another procedure or function. A local routine can be called only by the procedure or function that contains it or by other local routines contained with it. Look at the example:
PROCEDURE PrintEmployeeInfo(REF e: EmployeeRecord) IS ROUTINES PROCEDURE PrintDemographics IS ACTIONS PrintString(e.first_name); PrintString(e.last_name); PrintString(e.ssn); END; PROCEDURE PrintManages IS ACTIONS FOR e.manages DO PrintString(e.manages[$Current]); END; END; ACTIONS PrintDemographics; PrintManages; END;
One of the important conceptual issues in block-structured languages is scope. Within any given block, only certain identifiers exist, or are in scope. In general, nested blocks are able to "see" identifiers declared in an enclosing scope. An object or identifier in an enclosing scope is local only to that block.
Consider the following example:
KNOWLEDGEBASE Scope; CONSTANTS MAX_EMPLOYEES IS 500; TYPES EmployeeRecord IS RECORD first_name, last_name: String; END; VARIABLES employeeList: List of EmployeeRecord; PRIVATE CONSTANTS ARRAY_SIZE IS 1000; VARIABLES employeeArray[ARRAY_SIZE]: ARRAY OF EmployeeRecord; ROUTINES PROCEDURE ProcTwo IS VARIABLES i: Integer; ACTIONS ... END;
PROCEDURE ProcOne IS VARIABLES i: String; j: Integer; ACTIONS ... END;
MAX_EMPLOYEES, EmployeeRecord, and employeeList are visible throughout the knowledgebase because they are declared in the outermost block. These elements appear in the public section of the knowledgebase, so they are also visible to other knowledgebases that use this knowledgebase.
Similarly, because ARRAY_SIZE and employeeArray are declared in the private section of the outermost block, they are visible throughout the private section. Both ProcTwo and ProcOne can refer to these identifiers, but because they are in the private section, no other knowledgebase can access them.
The variable i in ProcTwo is visible only in ProcTwo. The variables i and j in ProcOne are visible only in ProcOne. The i variable in ProcOne is completely unrelated to the i variable in ProcTwo.
Variable values exist only while the block in which they are enclosed executes. For instance, the variables i and j have values only during the execution of ProcOne.
When calling procedures, it is often necessary to pass information to them.
This can be accomplished with the following steps:
PROCEDURE ComputePercent (VAL Dividend : INTERGER, VAL Divisor : INTEGER, REF Quotient : INTEGER) IS (*parameter to PROCEDURE ComputePercent*)
ACTIONS Quotient := Dividend/Divisor*100; END;
When the procedure is called, the number of expressions passed equals the number of parameters declared for the procedure. The expressions passed must have the same data type and parameter type as the parameters specified.
VARIABLES (*Compute the percentage of cars of differing colors in a garage*) NumBrownCars : INTEGER; NumBlackCars : INTEGER; NumMixedCars : INTEGER; TotalCars : INTEGER; PercentBrownCars : INTEGER; PercentBlackCars : INTEGER; PercentMixedCars : INTEGER; ACTIONS (*Expressions passed to compute the percentage of cars of differing colors in a garage*) ComputePercent (NumBrownCars, TotalCars, PercentBrownCars); ComputePercent (NumBlackCars, TotalCars, PercentBlackCars); ComputePercent (NumMixedCars, TotalCars, PercentMixedCars); End;
In the next example, notice that some parameters are passed by value (VAL) and some by reference (REF).
In a VAL parameter, a value is copied from the caller to the called routine, and nothing that the called routine does affects the value that the caller still possesses.
A REF parameter addresses the same storage that the caller possesses. Thus, changes to the parameter's value that occur during the course of the called routine take effect immediately on the caller's value.
Consider the following example:
ROUTINES PROCEDURE DistanceFormula(VAL x1, y1, x2, y2: Integer, REF distance: REAL) IS ACTIONS distance:=Sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); END;
PROCEDURE ComputeDistances IS VARIABLES distance: Real; ACTIONS DistanceFormula(1,1,9,9,distance); WinWriteLN(myWindow,'The distance is ' & distance); DistanceFormula(9,9,15,87,distance); WinWriteLN(myWindow,'The distance is ' & distance); END;
In the preceding example, a procedure called DistanceFormula has been declared.
The procedure takes five formal arguments:
When the procedure is called, copies of the first four integer values passed are sent to the procedure. However, in the case of the fifth parameter, the actual variable sent by the caller is passed to the procedure.
If a procedure expects a parameter passed by value, the caller can send a:
However, if a procedure expects a parameter to be passed by reference, the caller can send only a variable.
A procedure can treat all parameters passed to it as variables. It can:
Changes made to parameters passed by value are not transmitted back to the caller.
In the previous example, ComputeDistances sent literal values for the four integer arguments to DistanceFormula because the corresponding parameters were declared with the VAL keyword. However, ComputeDistances passed only a variable for the fifth argument because the fifth parameter was declared with the REF keyword.
Note: There is no default parameter type. You must specify either VAL or REF for each parameter in a procedure declaration.
Functions are identical to procedures except that they return a value to the caller. For that reason, functions can be used only in assignments or expressions. They cannot be used as statements.
Functions can return simple types (such as strings, integers, reals, Boolean values, dates, and times) as well as structured types and user-defined types.
You can also write a function that returns a list of records.
Functions have the following general form:
FUNCTION <function-name>(<formal-parameter-list>): <Type> IS CONSTANTS <constant-declarations> TYPES <type-declarations> VARIABLES <variable-declarations> ROUTINES <subroutine-implementations> ACTIONS <KML statement-list> END;
The following example changes DistanceFormula so that it is a function rather than a procedure:
ROUTINES FUNCTION DistanceFormula(VAL x1, y1, x2, y2: Integer): REAL IS ACTIONS Exit Sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); END;
PROCEDURE ComputeDistances IS VARIABLES distance: Real; ACTIONS distance:=DistanceFormula(1,1,9,9); WinWriteLN(myWindow,'The distance is ' & distance); distance:=DistanceFormula(9,9,15,87); WinWriteLN(myWindow,'The distance is ' & distance); END;
Note: Every function must have an explicit return type that follows the parameter list and is separated from it by a colon.
$Result can be used within the body of a function to access the current return value for that function. You can set or test it within the function body.
Consider the following change to DistanceFormula:
FUNCTION DistanceFormula(VAL x1, y1, x2, y2: Integer): REAL IS ACTIONS $Result := Sqrt((x2-x1)*(x2-x1) + (y2-y1)* (y2-y1)); END;
In this example, the return value of the function is set with $Result.
$Result can be assigned many times in a function body. $Result automatically has the data type declared for the function's return value.
Exit, when used with a function, causes:
When you use an Exit statement within a function, you can give it a parameter that indicates the value you want returned.
Exit (42);
is the same as:
$Result := 42; Exit;
If a function ends without using an Exit statement, a value of $Unknown is returned.
Exit combines the tasks that:
Access to routines written in C or C++ from a program written in Developer's Toolkit is provided by the Developer's Toolkit external routine mechanism.
An external routine consists of two parts:
Beginning with Developer's Toolkit 4.2, Developer's Toolkit's declarative syntax supports most interface requirements without an extensive understanding of the underlying Developer's Toolkit data structures.
To call an external routine, the Developer's Toolkit Interpreter must have a declaration of the routine that tells:
The syntax of an external routine declaration is shown in the following example. As with any Developer's Toolkit routine implementation, these declarations may appear only in the private section of a Developer's Toolkit knowledgebase.
FUNCTION name [(VAL|REF parameter : type [, ...])] : type IS EXTERNAL dll_name [, entry_point][, linkage]; PROCEDURE name [(VAL|REF parameter : type [, ... ])] IS EXTERNAL dll_name [, entry_point][, linkage];
The DLL name is a constant string expression that identifies the library in which the external routine is located. The .dll extension is usually different on a per-platform basis and is generally omitted from the given name.
Entry points are locations within a program or routine where execution can begin. A routine normally has only one entry point. The entry point may be either a:
A string represents the name of an entry point and an integer represents its ordinal number in the library.
C++ compilers typically use a technique known as name mangling that ensures correct
linkage between separate compiled object modules.
External functions written in C++ usually must either be declared external "C"
in the C++ source code or be loaded by their ordinal number.
The linkage specification in an external routine declaration must be one of the following symbols:
Note: The Developer's Toolkit DLL callout facility will fail for "pass by reference" arguments if the DLL being called was not compiled with Microsoft Visual C++.
For IBM C++ on OS/2, the protocol is _Optlink._
For OS/2, the protocol is _System.
If the linkage specification is omitted altogether from an external routine declaration, the linkage defaults to $STANDARD.
For all UNIX operating systems, the convention is compile-specific. If your library was written in C or C++, use $C. Otherwise, use $System.
To facilitate the use of third party libraries, TSD Script provides a number of predefined type names that map directly to simple C/C++ types.
TYPES SHORT IS INTEGER: INT(2) DEFAULT($ERROR); USHORT IS INTEGER: UINT(2) DEFAULT($ERROR); LONG IS INTEGER: INT(4) DEFAULT($ERROR); ULONG IS INTEGER: UINT(4) DEFAULT($ERROR); PSZ IS STRING : POINTER DEFAULT(0); FLOAT IS REAL : FLOAT(4) DEFAULT($ERROR); DOUBLE IS REAL : FLOAT(8) DEFAULT($ERROR);
The types SHORT, USHORT, LONG, ULONG, PSZ, FLOAT and DOUBLE are provided in the system knowledgebase found in the file TSD Script.KB.
External DLL types are mapped by TSD Script to the most common low-level data types used in C and C++. The extra declarative syntax tells the TSD Script Interpreter how to map the data.
For example, the declaration for SHORT says that the value should be packed into a two byte integer. The Default declaration indicates that $Unknown causes a run-time error if an attempt is made to pass it to an external routine through a SHORT parameter.
The declaration for PSZ says that the value should be packed as a pointer, and that $Unknown should be passed as a zero (NULL) pointer.
Here is an example of how you might declare the ExitWindows function supplied by Microsoft Windows:
FUNCTION ExitWindows (VAL reserved: INTEGER, VAL returnCode: INTEGER ): BOOLEAN IS EXTERNAL `USER.EXE', `ExitWindows', $SYSTEM;
Note: A TSD Script "integer" is a long (32-bit) value and "int" does not always work in C.
Like the parameters for normal TSD Script routines, the parameters for external routines are specified as passed by either:
VAL parameters to external routines behave much like VAL parameters to non-external routines.
The value is copied from the caller to the called routine, and nothing that the called routine does affects the value that the caller still possesses.
A string that is passed by VAL is also received by the external routine as a pointer which addresses a temporary storage location into which the TSD Script string has been copied.
REF parameters differ slightly from VAL parameters for external routines.
When a non-external routine receives a REF parameter, it addresses the same storage that the caller possesses. Any changes to the value of the parameter during the called routine takes effect immediately on the caller's value.
Passing by reference to an external routine is actually implemented as copy-in/copy-out. The difference is subtle, but under some circumstances, the behavior is detectable. For instance, the dispatch of a message from an external routine is an example of this behavior.
The data type STRING (and aliases such as PSZ) is an exception and is passed by a pointer to the actual TSD Script string value when passed by REF.
All REF parameters are passed to external C or C++ routines as pointers. TSD Script does not attempt to support the C++ notion of references. If you need to call a C++ routine that takes reference parameters, you can write a small wrapper function. The wrapper takes a pointer from TSD Script, de-references it, and passes it along to the required C++ function.
Because TSD Script does not directly support the ANSI C and C++ notion of const, TSD Script programmers typically declare a parameter as REF when the C/C++ function takes a const pointer argument.
Passing data between TSD Script and an external routine often requires that data be translated into an alternative format. Before the external routine is called, the caller's value is:
If the parameter is a REF parameter then, after the caller returns, the temporary area is unpacked back into the caller's storage.
Although you can construct fairly elaborate interfaces using simple types, eventually
you will want to pass aggregate data structures to an external routine.
Developer's Toolkit 4.2 added new syntax to TSD Script that allows the explicit
specification that detailing how TSD Script data is mapped to C or C++ data structures.
This mapping is referred to hereafter as binary packing.
Binary packing information is supplied as an annotation to the type specification in either a named type declaration or a field specification within a record declaration. The gross syntax for both are shown below.
TYPES type_name IS type : annotation ... ; type_name IS RECORD [ field_name : type ] [ : annotation ... ] ; ... END;
The following annotations specify the format in which TSD Script data is packed. They are all mutually exclusive.
Annotation | Description |
INT(width) | The INT annotation specifies that the field is packed into the native integer format with the specified width. Legal values for the width are 1, 2, and 4. Any TSD Script data type that can be explicitly converted to INTEGER can be packed as INT. |
UINT(width) | The UINT annotation specifies that the field is packed into the native integer format as an unsigned value with the specified width. When passing a value from TSD Script to an external routine, there is no significant difference between INT and UINT, because the same bit pattern is passed in either case. However, when a 1 or 2-byte value is passed back to TSD Script from the external routine, the INT/UINT distinction determines how the value is sign-extended. Again, legal values for width are 1, 2, and 4. |
NUMERIC(width) | Fields with the NUMERIC annotation are packed as a blank-padded character sequence with the given width. TSD Script Reals, Integers and any data type that can be converted to Integer may by packed as a NUMERIC field. |
FLOAT(width) | Fields with the FLOAT annotation are packed as an IEEE floating point number with the specified width. Valid widths are 4 and 8. Any TSD Script type that can be explicitly converted to REAL can be packed as a FLOAT. |
BCD(width) | Fields with the BCD annotation are packed as Binary Coded Decimal. Any TSD Script type that can be explicitly converted to REAL can be packed as BCD. |
CHAR(width) | Fields with the CHAR annotation are packed as a simple character array with the given width. Any TSD Script type that may be converted to STRING may be packed as CHAR. |
ZSTRING(width) | Fields with the ZSTRING annotation are packed as a null-terminated (C-style) string with the given width. Any TSD Script data type that can be converted to STRING can be packed as ZSTRING. |
LSTRING(width) | Fields with the LSTRING annotation are packed as a Pascal-style string, with a lead byte containing the length of the string. Any TSD Script type that can be converted to STRING can be packed as LSTRING. |
ASE_DATE | Fields with the ASE_DATE annotation are packed as a DATEREC struct. Any data type that can be explicitly converted to DATE can be packed as an ASE_DATE. |
BTRV_DATE | Fields with the BTRV_DATE annotation are packed as a Btrieve-style date. Any data type that can be explicitly converted to DATE can be packed as a BTRV_DATE. |
CCYYMMDD | Fields with the CCYYMMDD annotation are packed as a character string containing the century, year, month, and day, each packed into two bytes. Any type that can be explicitly converted to DATE can be packed as a CCYYMMDD. |
ASE_TIME | Fields with the ASE_TIME annotation are packed as a TIMEREC. Any data type that can be explicitly converted to TIME can be packed as an ASE_TIME. |
BTRV_TIME | Fields with the BTRV_TIME annotation are packed as a Btrieve-style time. Any data type that can be explicitly converted to TIME can be packed as a BTRV_TIME. |
HHMMSS | Fields with the HHMMSS annotation are packed as a character string containing the hour, minute, and second, each packed into two bytes. Any data type that can be explicitly converted to TIME can be packed as an HHMMSS. |
POINTER | Fields with the POINTER annotation are packed as a 32-bit pointer to their actual TSD Script data. Any of the non-aggregate data types in TSD Script may be packed as POINTER. |
NOEXPORT | The NOEXPORT annotation marks a field that is not included at all in the external binary representation. Any type of field can be marked NOEXPORT |
In addition to a format annotation, a default value annotation can be specified. The default value annotation instructs the TSD Script interpreter how to fill the field in the binary structure when the TSD Script value is unknown. The syntax for the default value annotation is:
DEFAULT( $ERROR|expression )
When $ERROR is specified, packing an unknown value causes the TSD Script Interpreter to display an error message and abort the external routine.
This is the default when no value annotation is specified.
When an expression is specified, that expression is evaluated and its value packed according to the format annotation when the TSD Script value is unknown. This feature is used to map unknown strings to NULL.
In a sequence of field specifications, you can supply packing annotations without any corresponding field name or data type. These annotations specify fields in the binary structure that do not appear in the TSD Script record. This occurs because the fields themselves do not appear in the TSD Script record. You must supply an additional annotation with the value for that field.
The syntax for this special annotation is
VALUE( expression )
Each time the field is packed, the given expression is evaluated and then packed according to the corresponding format annotation.
Finally, for purposes of field alignment, the FILL annotation can be used to place an arbitrary number of filler bytes between fields in the binary structure.
The syntax of the FILL annotation is
FILL( width [ , value ] )
The number of bytes specified by width, and those containing the given value, are packed into the binary structure. Although any constant integer value can be specified for the FILL annotation, the actual packed bytes contain only the least significant 8 bits, so -128 to 255 represents the useful range of values. The default value is zero.
Named types and fields without explicit packing annotations are packed according to the
defaults for their underlying types.
The following table indicates the defaults for the intrinsic non-aggregate TSD Script data
types.
Type | Default packing information | Default Result |
Boolean | INT(1) | DEFAULT($ERROR) |
Integer | INT(4) | DEFAULT($ERROR) |
Real | FLOAT(8) | DEFAULT($ERROR) |
String | POINTER | DEFAULT(0) |
Time | ASE_TIME | DEFAULT($ERROR) |
Date | ASE_DATE | DEFAULT($ERROR) |
Window, File (Handle types) | UINT(4) | DEFAULT($ERROR) |
Tivoli Service Desk 6.0 Developer's Toolkit Script Programming Guide