PREVIOUS

Chapter 1. Language Extensions

Key Logic has made extensions to the C language definition so that key invocations can be expressed as statements in a C program. A KEY data type has been defined which allows identification and management of keys within the programs. A preprocessor (called KCPP) is run to convert the key invocation statements into standard C language statements. The C compiler is then run using the output of the preprocessor as its input.

Standard C strings, consisting of character arrays, have some weakness. It is necessary to keep track of the maximum and current lengths of a C string in separate variables or constants. Also, a byte with a null value in the middle of a string will cause library routines to treat that as the end of the string. Further, failure to terminate a C string with a null byte will cause library routines to march off the end of a string, to the detriment of the program as a whole.

Key invocations make heavy use of strings, which are expected to be able to include any binary value from 0-255 as a byte. Key invocation statements need to know the current length of a string when passing it, and the maximum length of a string when receiving into it. For these reasons a PL/I-like string definition (type STRING) is provided by Key Logic. The STRING type is converted to standard C language declarations by the KCPP preprocessor. A library of functions is provided to manipulate STRING type variables and constants.

Every C program must include the header file "KEYKOS.H" in order to use the KeyKOS C language extensions. The following statement must be used in the program before any other #include statement:

#include <keykos.h>

Long Long Integers

A new data type is available for operating on unsigned integers of up to 64 bits. To use this data type you must include the following header:

#include <lli.h>

This header contains the following declaration:

typedef struct {unsigned long hi, low;} LLI;

KeyKOS library functions for operating on LLIs are described elsewhere in this manual. You should use only those functions for operating on LLIs, and not rely on the structure representation. It is non-portable to assume that an unsigned long has 4 characters and/or 32 bits. You may, however, initialize an LLI as follows:

LLI mylli;
unsigned long value;
mylli.hi = 0; mylli.low = value;

You may also test whether an LLI is less than or equal to the maximum unsigned long, and extract its value, as follows

if (mylli.hi == 0) value = mylli.low;

Varying-length Strings

Several types of strings are available in KeyKOS C: Declarations of varying-length strings take the following form:

STRING <name> [ ( <maxlen> ) [ = <init> ] ]

where <name> is an alphanumeric identifier, <maxlen> is a positive integer specifying the length of the string, and <init> is an optional initial string value. The optional initial string value may only be specified if <maxlen> is also provided. Only a single identifier may be declared per STRING statement.

A STRING variable is implemented as a structure that keeps track of the maximum possible string size it can store (<maxlen>), the length of the string it is currently storing, and the string itself. The KCPP preprocessor expands a STRING declaration into a static structure declaration and initializes the maximum and actual lengths of the string. For example, the declaration

STRING name(20) = "KeyKOS";

is expanded to

static struct {
	int maxlen, actlen; 
	char s(:20:); 
	} _name = {20, 6, "KeyKOS"}; 
char *name = &_name;

Every STRING declaration with a <maxlen> attribute results in creating a unique structure. Whenever the STRING variable is passed as an argument to a function, an address of the structure is passed. But a STRING declaration without <maxlen> does not create any structure. Instead it is expanded by the KCPP preprocessor into a declaration for a pointer to a STRING type. For example, the following declarations are expanded from

STRING name;
extern STRING new();

to

char *name;
extern char *new();

A STRING variable declared without the <maxlen> attribute may be used for defining a function's formal argument that is a STRING variable, or for defining a function that returns a STRING value. For example:

int doit(mystring)
STRING mystring;
{ ... }

STRING this(arg1)
{ ... }

Notes:

Keys

All keys, such as the invoked key in statement arguments and keys passed and received, must be defined as KEY variables or constants prior to use. Keys are declared as:

KEY <name> [ = <slot number> ]

The <name> can be thought of as the KEY name, but you must realize that this really declares a name for a slot number, either in the key registers or the key cache (see below). The initial value <slot number> must be an integer in the range 0 to 15 without key cache, and in the range -1 to 32767 with key cache. If an initial positive value <slot number> is specified, then the key is defined as a constant. If the key is not initialized, or is initialized to -1, then it is defined as a variable. Any combination of KEY variables and constants may be declared in the same statement. For instance, in

KEY x,y=6,z=1,v;

the constants y and z are declared in the same statement with the variables x and v.

Since all keys must be defined as type KEY, you cannot write a statement like:

KC(1,1) RCTO(rc);         /* invalid */

Instead, use:

KEY KEY1 = 1;
KC(KEY1,1) RCTO(rc);      /* good example */

The value of a KEY constant must not be changed from the slot number assigned to it by the declaration statement. Any attempt to reassign a constant KEY will lead to an unpredictable result.

KEY variables which are initialized to -1 are intended for use only with the KeyKOS key cache mechanism (see "Compilation" in Chapter 2 for details on specifying key cache use). The first time a key is received into such a KEY variable, the key cache mechanism allocates a slot in the key cache super node, and then saves that slot number in the KEY variable. Thereafter, the value of that KEY variable must not be changed. Any attempt to reassign a key cache KEY variable will lead to an unpredictable result. Any attempt to initialize a KEY variable to -1 without key cache usage will be flagged as an error by the KCPP preprocessor.

Uninitialized KEY variables are intended for use as global or local KEY variables. Their values can be reassigned.

There are special conditions on how a KEY name can be used:

The number of key slots available in a domain written in C depends on whether the key cache will be used or not. If the key cache is not used, then only key slots 0 to 15 are available for use. If the key cache is used then 32767 key slots are available, since the keys are stored in a super node. When the key cache is used, the keys in key slots 0 to 15 of the domain's keys node are copied into slots 0 to 15 of the super node during program initialization. This way the domain's key registers may be referenced via KEY constants without regard for whether key caching is being used or not. As keys are referenced by the domain obeying the C program, keys are automatically moved between the domain's key registers and the super node based on a least-recently-used algorithm. Note that when using this feature, the domain key should not be used to copy keys since the key register slots hold different keys at different times. Use the returner key instead.

NEXT