topheader Welcome 3.146.37.35 @ xps.scios.ch on Thu Apr 25 5:06:24 UTC 2024
 
topheader
 

Section 7   I/O Router System

The I/O router system provided in CLIPS is quite flexible and will allow a wide va­riety of interfaces to be developed and easily attached to CLIPS. The system is rela­tively easy to use and is explained fully in sections 7.1 through 7.4. The CLIPS I/O functions for using the router system are described in sections 7.5 and 7.6, and finally, in ap­pendix B, some examples are included which show how I/O routing could be used for simple interfaces.

7.1 Introduction

The problem that originally inspired the idea of I/O routing will be considered as an introduction to I/O routing. Because CLIPS was designed with portability as a major goal, it was not possible to build a sophisticated user interface that would support many of the features found in the interfaces of commercial expert system building tools. A prototype was built of a semi portable interface for CLIPS using the CURSES screen manage­ment package. Many problems were encountered during this effort in­volving both portability concerns and CLIPS internal features. For example, every statement in the source code which used the C print function, printf, for printing to the terminal had to be replaced by the CURSES function, wprintw, which would print to a window on the terminal. In addition to changing function call names, different types of I/O had to be di­rected to different windows. The tracing information was to be sent to one window, the command prompt was to appear in another window, and output from printout statements was to be sent to yet another window.


This prototype effort pointed out two major needs: First, the need for generic I/O func­tions that would remain the same regardless of whether I/O was directed to a standard terminal interface or to a more complex interface (such as windows); and second, the need to be able to specify different sources and destinations for I/O. I/O routing was designed in CLIPS to handle these needs. The concept of I/O routing will be further explained in the following sections.

7.2 Logical Names

One of the key concepts of I/O routing is the use of logical names. An analogy will be useful in explaining this concept. Consider the Acme company which has two com­puters: computers X and Y. The Acme company stores three data sets on these two computers: a personnel data set, an accounting data set, and a documenta­tion data set. One of the employees, Joe, wishes to update the payroll in­formation in the accounting data set. If the payroll information was located in directory A on computer Y, Joe's command would be


update Y:[A]payroll


If the data were moved to directory B on computer X, Joe’s command would have to be changed to


update X:[B]payroll


To update the payroll file, Joe must know its location. If the file is moved, Joe must be informed of its new location to be able to update it. From Joe’s point of view, he does not care where the file is located physically. He simply wants to be able to specify that he wants the information from the accounting data set. He would rather use a com­mand like


update accounting:payroll


By using logical names, the information about where the ac­counting files are located physically can be hidden from Joe while still allowing him to access them. The loca­tions of the files are equated with logical names as shown here.


accounting = X:[A]

documentation = X:[C]

personnel = Y:[B]


Now, if the files are moved, Joe does not have to be informed of their relocation so long as the logical names are updated. This is the power of using logical names. Joe does not have to be aware of the physical location of the files to access them; he only needs to be aware that accounting is the logical name for the location of the account­ing data files. Logical names allow reference to an object without having to un­derstand the details of the implementation of the reference.


In CLIPS, logical names are used to send I/O requests without having to know which device and/or function is handling the request. Consider the message that is printed in CLIPS when rule tracing is turned on and a rule has just fired. A typical message would be


FIRE 1 example rule: f 0


The routine that requests this message be printed should not have to know where the message is being sent. Different routines are required to print this message to a stan­dard terminal, a window interface, or a printer. The tracing routine should be able to send this message to a logical name (for example, trace-out) and should not have to know if the device to which the message is being sent is a terminal or a printer. The logical name trace-out allows tracing information to be sent simply to “the place where tracing information is displayed.” In short, logical names allow I/O requests to be sent to specific locations without having to specify the details of how the I/O request is to be handled.


Many functions in CLIPS make use of logical names. Both the printout and format functions require a logical name as their first argument. The read func­tion can take a logical name as an optional argument. The open function causes the association of a logical name with a file, and the closefunction removes this as­sociation.


Several logical names are predefined by CLIPS and are used extensively throughout the system code. These are


Name Description

stdin The default for all user inputs. The read and readline functions read from stdin if t is specified as the logical name.


stdout The default for all user outputs. The format and printout functions send output to stdout if t is specified as the logical name.


wprompt The CLIPS prompt is sent to this logical name.


wdialog All informational messages are sent to this logical name.


wdisplay Requests to display CLIPS information, such as facts or rules, are sent to this logical name.


werror All error messages are sent to this logical name.


wwarning All warning messages are sent to this logical name.


wtrace All watch information is sent to this logical name (with the exception of compilations which is sent to wdialog).

7.3 Routers

The use of logical names has solved two problems. Logical names make it easy to create generic I/O functions, and they allow the specification of different sources and destinations for I/O. The use of logical names allows CLIPS to ignore the specifics of an I/O request. However, such requests must still be specified at some level. I/O routers are provided to handle the specific details of a request.


A router consists of three components. The first component is a function which can determine whether the router can handle an I/O request for a given logical name. The router which recognizes I/O requests that are to be sent to the serial port may not recognize the same logical names as that which recognizes I/O re­quests that are to be sent to the terminal. On the other hand, two routers may recog­nize the same logical names. A router that keeps a log of a CLIPS session (a drib­ble file) may recog­nize the same logical names as that which handles I/O re­quests for the terminal.


The second component of a router is its priority. When CLIPS receives an I/O request, it begins to question each router to discover whether it can handle an I/O re­quest. Routers with high priorities are questioned before routers with low priorities. Priorities are very important when dealing with one or more routers that can each process the same I/O request. This is particularly true when a router is going to redefine the stan­dard user interface. The router associated with the standard interface will handle the same I/O requests as the new router; but, if the new router is given a higher priority, the standard router will never receive any I/O requests. The new router will “intercept” all of the I/O requests. Priorities will be discussed in more detail in the next section.


The third component of a router consists of the functions which actually handle an I/O request. These include functions for printing strings, getting a character from an input buffer, returning a character to an input buffer, and a function to clean up (e.g., close files, remove windows) when CLIPS is exited.

7.4 Router Priorities

Each I/O router has a priority. Priority determines which routers are queried first when determining the router that will handle an I/O request. Routers with high priorities are queried before routers with low priorities. Priorities are assigned as integer values (the higher the integer, the higher the priority). Priorities are important because more than one router can handle an I/O request for a single logical name, and they enable the user to define a custom interface for CLIPS. For example, the user could build a custom router which han­dles all logical names normally handled by the default router associated with the standard interface. The user adds the custom router with a priority higher than the priority of the router for the standard interface. The custom router will then intercept all I/O requests intended for the standard interface and spe­cially process those re­quests to the custom interface.


Once the router system sends an I/O request out to a router, it considers the request satisfied. If a router is going to share an I/O request (i.e., process it) then allow other routers to process the request also, that router must deactivate itself and call PrintRouter again. These types of routers should use a priority of either 30 or 40. An example is given in appendix B.2.


Priority Router Description

50 Any router that uses “unique” logical names and does not want to share I/O with catch-all routers.


40 Any router that wants to grab standard I/O and is willing to share it with other routers. A dribble file is a good example of this type of router. The dribble file router needs to grab all output that normally would go to the terminal so it can be placed in the dribble file, but this same output also needs to be sent to the router which displays output on the terminal.


30 Any router that uses “unique” logical names and is willing to share I/O with catch all routers.


20 Any router that wants to grab standard logical names and is not willing to share them with other routers.


10 This priority is used by a router which redefines the default user inter­face I/O router. Only one router should use this priority.


0 This priority is used by the default router for handling stan­dard and file logical names. Other routers should not use this priority.

7.5 Internal I/O Functions

The following functions are called internally by CLIPS. These functions search the list of active routers and determine which router should handle an I/O request. Some routers may wish to deactivate themselves and call one of these functions to allow the next router to process an I/O request. Prototypes for these functions can be included by using the clips.h header file or the router.h header file.

7.5.1 ExitRouter

void ExitRouter(exitCode);

int exitCode;


Purpose: The function ExitRouter calls the exit function associated with each active router before exiting CLIPS.


Arguments: The exitCode argument corresponds to the value that normally would be sent to the system exitfunction. Consult a C system manual for more de­tails on the meaning of this argument.


Returns: No meaningful return value.


Info: The function ExitRouter calls the system function exit with the argument num after calling all exit functions associated with I/O routers.

7.5.2 GetcRouter

int GetcRouter(logicalName);

char *logicalName;


Purpose: The function GetcRouter queries all active routers until it finds a router that recognizes the logical name associated with this I/O re­quest to get a character. It then calls the get character function asso­ciated with that router.


Arguments: The logical name associated with the get char­acter I/O request.


Returns: An integer; the ASCII code of the character.


Info: This function should be used by any user defined function in place of getc to ensure that character input from the function can be received from a custom interface. On machines which default to unbuffered I/O, user code should be prepared to handle special characters like the backspace.

7.5.3 PrintRouter

int PrintRouter(logicalName,str);

char *logicalName, *str;


Purpose: The function PrintRouter queries all active routers until it finds a router that recognizes the logical name associated with this I/O re­quest to print a string. It then calls the print function as­sociated with that router.


Arguments: 1) The logical name associated with the location at which the string is to be printed.

2) The string that is to be printed.


Returns: Returns a non zero value if the logical name is recognized, otherwise it returns zero.


Info: This function should be used by any user defined function in place of printf to ensure that output from the function can be sent to a custom interface.

7.5.4 UngetcRouter

int UngetcRouter(ch,logicalName);

int ch;

char *logicalName;


Purpose: The function UngetcRouter queries all active routers until it finds a router that recognizes the logical name associated with this I/O re­quest. It then calls the ungetc function asso­ciated with that router.


Arguments: 1) The ASCII code of the character to be returned.

2) The logical name associated with the ungetc character I/O request.


Returns: Returns ch if successful, otherwise -1.


Info: This function should be used by any user defined function in place of UngetcRouter to ensure that character input from the func­tion can be re­ceived from a custom interface. As with GetcRouter, user code should be prepared to handle special characters like the backspace on machines with unbuffered I/O.

7.6 Router Handling Functions

The following functions are used for creating, deleting, and handling I/O routers. They are intended for use within user defined functions. Prototypes for these functions can be included by using the clips.h header file or the router.h header file.

7.6.1 ActivateRouter


int ActivateRouter(routerName);

char *routerName;


Purpose: The function ActivateRouter activates an existing I/O router. This router will be queried to see if it can handle an I/O re­quest. Newly created routers do not have to be activated.


Arguments: The name of the I/O router to be activated.


Returns: Returns a non zero value if the logical name is recognized, otherwise it returns zero.

7.6.2 AddRouter


int AddRouter(routerName,priority,queryFunction,printFunction,
getcFunction,ungetcFunction,exitFunction);

char *routerName;
int priority;
int (*queryFunction)(), (*printFunction)();
int (*getcFunction)(), (*ungetcFunction)(), (*exitFunction)();

int queryFunction(logicalName);
int printFunction(logicalName,str);
int getcFunction(logicalName);
int ungetcFunction(ch,logicalName);
int exitFunction(exitCode);

char *logicalName, *str, ch;
int exitCode;


Purpose: The function AddRouter adds a new I/O router to the list of I/O routers.


Arguments: 1) The name of the I/O router. This name is used to reference the router by the other I/O router handling functions.

2) The priority of the I/O router. I/O routers are queried in descending order of priorities.

3) A pointer to the query function asso­ciated with this router. This query function should accept a single argument, a logical name, and return either TRUE (1) or FALSE (0) depending upon whether the router recognizes the logical name.

4) A pointer to the print function asso­ciated with this router. This print function should accept two arguments: a logical name and a character string. The re­turn value of the print function is not meaningful.

5) A pointer to the get character function associated with this router. The get character function should accept a single ar­gument, a logical name. The return value of the get character function should be an integer which represents the character or end of file (EOF) read from the source represented by logical name.

6) A pointer to the ungetc character func­tion asso­ciated with this router. The ungetc character func­tion accepts two ar­guments: a logical name and a character. The return value of the unget character function should be an integer which represents the character which was passed to it as an argument if the ungetc is successful or end of file (EOF) is the ungetc is not successful.

7) A pointer to the exit function asso­ciated with this router. The exit function should accept a single argument: the exit code represented by num.


Returns: Returns a zero value if the router could not be added, otherwise a non-zero value is returned.


Info: I/O routers are active upon being created. See the examples in ap­pendix B for further information on how to use this function.

7.6.3 DeactivateRouter

int DeactivateRouter(routerName);

char *routerName;


Purpose: The function DeactivateRouter deactivates an existing I/O router. This router will not be queried to see if it can handle an I/O request. The syntax of the DeactivateRouter function is as follows.


Arguments: The name of the I/O router to be deactivated.


Returns: Returns a non zero value if the logical name is recognized, otherwise it returns zero.

7.6.4 DeleteRouter

int DeleteRouter(routerName);

char *routerName;


Purpose: The function DeleteRouter removes an existing I/O router from the list of I/O routers.


Arguments: The name of the I/O router to be deleted.


Returns: Returns a non zero value if the logical name is recognized, otherwise it returns zero.

Section 8   Memory Management

Efficient use of memory is a very important aspect of an expert system tool. Expert sys­tems are highly memory intensive and require comparatively large amounts of mem­ory. To optimize both storage and processing speed, CLIPS does much of its own memory management. Section 8.1 describes the basic memory management scheme used in CLIPS. Section 8.2 describes some functions that may be used to monitor/ control memory usage.

8.1 How CLIPS Uses Memory

The CLIPS internal data structures used to represent constructs and other data entities require the allocation of dynamic memory to create and execute. Memory can also be released as these data structures are no longer needed and are re­moved. All requests, either to allocate memory or to free memory, are routed through the CLIPS memory management functions. These functions request memory from the op­erating system and store previously used memory for reuse. By providing its own memory management, CLIPS is able to reduce the number of malloc calls to the operating system. This is very important since malloc calls are handled differ­ently on each ma­chine, and some implementations of malloc are very inefficient.


When new memory is needed by any CLIPS function, CLIPS first checks its own data buffers for a pointer to a free structure of the type requested. If one is found, the stored pointer is returned. Otherwise, a call is made to malloc for the proper amount of data and a new pointer is returned.


When a data structure is no longer needed, CLIPS saves the pointer to that memory against the next request for a structure of that type. Memory actually is re­leased to the operating system only under limited circumstances. If a malloc call in a CLIPS func­tion returns NULL,all free memory internally stored by CLIPS is released to the oper­ating system and the malloc call is tried again. This usually happens during rule execution, and the message


*** DEALLOCATING MEMORY ***

*** MEMORY DEALLOCATED ***


will be printed out to the wdialog stream. Users also may force memory to be re­leased to the operating system (see section 8.2).


CLIPS uses the generic C function malloc to request memory. Some machines pro­vide lower level memory allocation/deallocation functions that are considerably faster than malloc. Generic CLIPS memory allocation and deallocation functions are stored in the memalloc.c file and are called genalloc andgenfree. The call to malloc and free in these functions could be replaced to improve performance on a specific machine.


Some machines have very inefficient memory manage­ment services. When running on the such machines, CLIPS can be made to request very large chunks of memory and internally allocate smaller chunks of memory from the larger chunks. This technique bypasses numerous calls to malloc thus improving performance. This behavior can be enabled by setting the BLOCK_MEMORY compiler option in the setup.h header file to 1 (see section 2.2). In general, this option should not be enabled unless memory allocation routines are very slow since the CLIPS block memory routines tend to trade increased overhead for memory requests for faster speed.


Extensive effort has gone into making CLIPS garbage free. Theoretically, if an application can fit into the available memory on a machine, CLIPS should be able to run it forever. Of course, user defined functions that use dynamic memory may affect this.

8.2 Standard Memory Functions

CLIPS currently provides a few functions that can be used to monitor and control memory usage. Prototypes for these functions can be included by using the clips.h header file or the memalloc.h header file.

8.2.1 GetConserveMemory;

int GetConserveMemory();


Purpose: Returns the current value of the conserve memory behavior.


Arguments: None.


Returns: An integer; FALSE (0) if the behavior is disabled and TRUE (1) if the behavior is enabled.

8.2.2 MemRequests

long int MemRequests();


Purpose: The function MemRequests will return the number of times CLIPS has requested memory from the operating system (the C equivalent of the CLIPS mem-requests command).


Arguments: None.


Returns: A long integer representing the number of requests CLIPS has made.


Other: When used in conjunction with MemoryUsed, the user can estimate the number of bytes CLIPS requests per call to malloc.

8.2.3 MemUsed

long int MemUsed();


Purpose: The function MemUsed will return the number of bytes CLIPS has currently in use or has held for later use (the C equivalent of the CLIPS mem-used command).


Arguments: None.


Returns: A long integer representing the number of bytes requested.


Other: The number of bytes used does not include any overhead for memory management or data creation. It does include all free memory being held by CLIPS for later use; there­fore, it is not a completely accurate measure of the amount of mem­ory actually used to store or process information. It is used primarily as a minimum indication.

8.2.4 ReleaseMem

long int ReleaseMem(howMuch, printMessage);
long int howMuch;

int printMessage;


Purpose: The function ReleaseMem will cause all free memory, or a specified amount, being held by CLIPS to be returned to the operating system (the C equivalent of the CLIPS release-mem command).


Arguments: 1) The number of bytes to be released. If this argument is  1, all memory will be released; otherwise, the specified number of bytes will be released.

2) A non-zero value causes a memory deallocation message to be printed when this function is called.


Returns: A long integer representing the actual amount of memory freed to the operating system.


Other: This function can be useful if a user defined function re­quires memo­ry but cannot get any from a malloc call. However, it should be used care­fully. Excessive calls to ReleaseMemory will cause CLIPS to call malloc more often, which can reduce the performance of CLIPS.

8.2.5 SetConserveMemory

int SetConserveMemory(value);

int value;


Purpose: The function SetConserveMemory allows a user to turn on or off the saving of pretty print information. Normally, this information is saved. If constructs are never going to be pretty printed or saved, a significant amount of memory can be saved by not keeping the pretty print representation.


Arguments: A boolean value: FALSE (0) to keep pretty print information for newly loaded constructs and TRUE (1) to not keep this information for newly loaded constructs.


Returns: Returns the old value for the behavior.


Other: This function can save considerable memory space. It should be turned on before loading any constructs. It can be turned on or off as many times as desired. Constructs loaded while this is turned off can be displayed only by reloading the construct, even if the option is turned on subsequently.

8.2.6 SetOutOfMemoryFunction

int (*SetOutOfMemoryFunction(outOfMemoryFunction))();

int (*outOfMemoryFunction)();


int outOfMemoryFunction(size);

int size;


Purpose: Allows the user to specify a function to be called when CLIPS cannot satisfy a memory request.


Arguments: A pointer to the function to be called when CLIPS cannot satisfy a memory request. This function is passed the size of the memory request which could not be satisfied. It should return a non-zero value if CLIPS should not attempt to allocate the memory again (and exit because of lack of available memory) or a zero value if CLIPS should attempt to allocate memory again.


Returns: Returns a pointer to the previously called out of memory function.


Other: Because the out of memory function can be called repeatedly for a single memory request, any user-defined out of memory function should return zero only if it has released memory.

Section 9   Environments

CLIPS provides the ability to create multiple environments into which programs can be loaded. Each environment maintains its own set of data structures and can be run independently of the other environments.

9.1 Creating, selecting, and destroying environments

If you have no need for multiple CLIPS programs loaded concurrently, there is no need to use any of the environment functions described in this section. The call to InitializeEnvironment automatically creates an environment for you and any subsequent calls to CLIPS functions will be applied to that environment. Environments can also be created using the CreateEnvironment function. The return value of the CreateEnvironment function is an anonymous (void *) pointer to an environmentData data structure. Environments created using the CreateEnvironment function are automatically initialized, so there is no need to call the InitializeEnvironment function.


Once multiple environments have been created, it is necessary to specify to which environment CLIPS function calls should be applied. This can be done in one of two ways. First, each of the CLIPS embedded function calls has a companion function call of the same name prefaced with “Env.” These companion function call accept an additional first argument: a generic pointer to an environment data structure. The CLIPS embedded function call is applied to this argument. CLIPS also supports the notion of a current environment. CLIPS embedded function calls that do not specify an environment are applied to the current environment. Environments newly created by the CreateEnvironment function call automatically become the current environment. The current environment can also be set by using the SetCurrentEnvironment function call. Environments also have an integer index associated with them that can be retrieved using the GetEnvironmentIndex function. The current environment can also be set by passing this index to the SetCurrentEnvironmentByIndex function.


Once you are done with an environment, it can be deleted with the DestroyEnvironment function call. This will deallocate all memory associated with that environment.


If you have added your own user-defined functions or extensions to CLIPS and you want these to work properly with multiple environments, you need to make them environment aware. Principally this involves using the environment companion functions in place of the standard embedded function calls. In addition, any functions or extensions which use global data should allocate this data for each environment by using the AllocateEnvironmentData function.


Shown following are two example main programs which make use of environments. The first example uses the environment companion embedded function calls and the second example uses the standard embedded function calls.

Environments Using Environment Embedded Calls


void main()

{

void *theEnv1, *theEnv2;

theEnv1 = CreateEnvironment();

theEnv2 = CreateEnvironment();

EnvLoad(theEnv1,“program1.clp”);

EnvLoad(theEnv2,“program2.clp”);

EnvReset(theEnv1);

EnvReset(theEnv2);

EnvRun(theEnv1,-1);

EnvRun(theEnv2,-1);


DestroyEnvironment(theEnv1);

DestroyEnvironment(theEnv2);

}

Environments Using Standard Embedded Calls


void main()

{

void *theEnv1, *theEnv2;

theEnv1 = CreateEnvironment();

theEnv2 = CreateEnvironment();

SetCurrentEnvironment(theEnv1);

Load(“program1.clp”);

Reset();

Run(-1);


SetCurrentEnvironment(theEnv2);

Load(“program2.clp”);

Reset();

Run(-1);

DestroyEnvironment(theEnv1);

DestroyEnvironment(theEnv2);

}

9.2 Environment Companion Functions

With a few exceptions, all of the CLIPS embedded function calls described in sections 3 through 8 have a companion function of the same name preceded with “Env”. The first argument to these companion functions is a generic pointer to an environment data structure and the remaining arguments are the same as the standard embedded function. For example, the standard embedded function call for Run is defined as follows:


long int Run(runLimit);


long int runLimit;


The environment companion function for Run is defined as follows:


long int EnvRun(theEnv,runLimit);


void *theEnv;
long int runLimit;


The pointers to functions passed in to the companion functions for the AddClearFunction, AddResetFunction, AddPeriodicFunction, AddRunFunction, and AddRouter should have as an additional first argument a generic pointer to an environment. For example, the standard embedded function call for AddClearFunction is defined as follows:

int AddClearFunction(clearItemName,clearFunction,priority);

char *clearItemName;

void (*clearFunction)();

int priority;


void clearFunction();


The environment companion function for AddClearFunction is defined as follows:


int EnvAddClearFunction(theEnv,clearItemName,envClearFunction,priority);


void *theEnv;

char *clearItemName;

void (*clearFunction)();

int priority;


void envClearFunction(theEnv);


void *theEnv;


The InitializeEnvironment function does not have a companion function since this function is unnecessary when you explicitly create environments using the CreateEnvironment function. The following embedded functions all have environment companion functions, but it is not necessary to use the companion functions in order to be environment aware:


* GetType DOToString GetDOLength ValueToString GetpType DOPToString GetpDOLength ValueToDouble SetType DOToDouble GetDOBegin ValueToLong SetpType DOPToDouble GetpDOBegin ValueToInteger GetValue DOToFloat GetDOEnd
GetpValue DOPToFloat GetpDOEnd
SetValue DOToLong SetDOBegin
SetpValue DOPToLong SetpDOBegin
GetMFType DOToInteger SetDOEnd
GetMFValue DOPToInteger SetpDOEnd
SetMFType DOToPointer

SetMFValue DOPToPointer


If the ENVIRONMENT_API_ONLY compiler directive is enabled, then the standard embedded functions require their first argument to be a generic pointer to an environment. For example, the function Run would be defined as follows:


long int Run(theEnv,runLimit);


void *theEnv;
long int runLimit;


This change only applies to the functions that are required in order to be environment aware. For example, the GetType function would not require an additional argument if this compiler directive were enabled. In addition, even with this compiler directive enabled, the “Env” companion functions are still available. Use of this compiler directive is a good way to verify that any code you have written is environment aware. It is also useful if, for whatever reason, you prefer that the CLIPS embedded function calls do not all begin with the “Env” prefix. By default, the ENVIRONMENT_API_ONLY compiler directive is disabled.

9.3 Standard Environment Functions

The following functions are used to create and manipulate environments. Prototypes for these functions can be included by using the clips.h header file or the envrnmnt.h header file.

9.3.1 AddEnvironmentCleanupFunction;

int AddEnvironmentCleanupFunction(theEnv,theName,theFunction,priority);

struct environmentData *theEnv;

char *theName;

void (*)(void *theFunction);

int priority;


Purpose: Adds a cleanup function that is called when an environment is destroyed.


Arguments: 1) A generic pointer to an environment data structure.

2) The name associated with the environment cleanup function.

3) A pointer to the environment cleanup function which is to be called when the environment is deleted. When called, the function is passed a generic pointer to the environment being destroyed.

4) The priority of the environment cleanup function which determines the order in which cleanup functions are called (higher priority items are called first). The values -2000 to 2000 are reserved for CLIPS system defined run items and should not be used for user defined run items.


Returns: Boolean value. TRUE if the cleanup function was successfully added, otherwise FALSE.


Other: Environment cleanup functions created using this function are called after all the cleanup functions associated with environment data created using AllocateEnvironmentData have been called.

9.3.2 AllocateEnvironmentData

int AllocateEnvironmentData(theEnv,position,size,cleanupFunction);

void *theEnv;

unsigned int position;

unsigned long size;

void (*)(void *cleanupFunction);


Purpose: Allocates environment specific data of the specified size.


Arguments: 1) A generic pointer to an environment data structure.

2) The integer position index used to reference the data.

3) The amount of environment data that needs to be allocated.

4) A pointer to a cleanup function that is called when the environment is destroyed. When called, the function is passed a generic pointer to the environment being destroyed. CLIPS automatically handles the allocation and deallocation of the base environment data created by this function (the amount of data specified by the size argument). You do not need to supply a cleanup function for this purpose and can supply NULL as this argument. If your base environment data contains pointers to memory that you allocate, then you need to either supply a cleanup function as this argument or add a cleanup function using AddEnvironmentCleanupFunction.


Returns: Boolean value. TRUE if the environment data was successfully allocated, otherwise FALSE.


Other: Environment cleanup functions specified using this function are called in ascending order of the position indices. If the deallocation of your environment data has order dependencies, you can either assign the position indices appropriately to achieve the proper order or you can use the AddEnvironmentCleanupFunction function to more explicitly specify the order in which your environment data must be deallocated.

9.3.3 CreateEnvironment;

void *CreateEnvironment();


Purpose: Creates an environment and initializes it.


Arguments: None.


Returns: A generic pointer to an environment data structure. NULL is returned in the event of an error.

9.3.4 DestroyEnvironment;

int DestroyEnvironment(theEnv);

void *theEnv;


Purpose: Destroys the specified environment deallocating all memory associated with it.


Arguments: A generic pointer to an environment data structure.


Returns: Boolean value. TRUE if the environment was successfully destroyed, otherwise FALSE.


Other: You should not call this function to destroy an an environment that is currently executing.

9.3.5 GetCurrentEnvironment;

void *GetCurrentEnvironment();


Purpose: Returns a generic pointer to the current environment.


Arguments: None.


Returns: A generic pointer to the current environment. NULL is returned if there is no current environment.

9.3.6 GetEnvironmentData;

void *GetEnvironmentData(position);

unsigned int position;


Purpose: Returns a generic pointer to the environment data associated with the specified position index.


Arguments: An unsigned integer; the position index of the desired environment data..


Returns: A generic pointer; the environment data associated with the specified position index.

9.3.7 GetEnvironmentIndex;

unsigned long GetEnvironmentIndex(theEnv);

void *theEnv;


Purpose: Returns the unique integer index asssociated with the specified environment.


Arguments: A generic pointer to an environment data structure.


Returns: An integer; the index associated with the specified environment.

9.3.8 SetCurrentEnvironment;

void SetCurrentEnvironment(theEnv);

void *theEnv;


Purpose: Sets the current environment to the specified environment.


Arguments: A generic pointer to an environment data structure.


Returns: No meaningful return value.

9.3.9 SetCurrentEnvironmentByIndex;

int SetCurrentEnvironmentByIndex(envIndex);

unsigned long envIndex;


Purpose: Sets the current environment to the environment associated with the specified environment index.


Arguments: An unsigned long integer; the environment index of the environment to become the current environment..


Returns: Boolean value. TRUE if the environment with the specified index existed and was set as the current environment, otherwise FALSE.

9.4 Environment Aware User-Defined Functions

In order to support all environment features fully, any user-defined functions that you create must be environment aware. To be environment aware, user-defined function must satisfy the following conditions:


1) The user-defined function must be registered using either EnvDefineFunction or EnvDefineFunction2. Use of these functions inform CLIPS that your user-defined function is environment aware and accepts a generic pointer to an environment as its first argument.

2) You should register your functions from within EnvUserFunctions instead of UserFunctions. EnvUserFunctions is located in main.c and its single argument is a generic pointer to an environment. This pointer should be passed into your calls to either EnvDefineFunction or EnvDefineFunction2. The macro identifier PTIEF can be placed in front of a function name to cast it as a pointer to a function which accepts a generic pointer (the environment) as its single argument and returns an integer. This macro is analogous to the PTIF macro with the addition of the generic pointer to theenvironment.

3) Your user-defined function should accept an additional argument as its first argument: a generic pointer to an environment.

4) Your user-defined function should use the environment companion functions where required to be environment aware.

5) If your user-defined functions (or other extensions) make use of global data that could differ for each environment, you should allocate this data with the AllocateEnvironmentData function (see section 9.5).

Example

The following example shows the necessary modifications to the code from section 3.4 in order for the user-defined function to be environment aware.


void EnvUserFunctions(

void *theEnv)

{

EnvDefineFunction2(theEnv,“triple”,'u',PTIEF TripleNumber, “TripleNumber”,

“11n”);

}


void TripleNumber(

void *theEnv,

DATA_OBJECT_PTR returnValuePtr)

{

void *value;

long longValue;

double doubleValue;


/*===============================================*/

/* If illegal arguments are passed, return zero. */

/*===============================================*/


if (EnvArgCountCheck(theEnv,“triple”,EXACTLY,1) == -1)

{

SetpType(returnValuePtr,INTEGER);

SetpValue(returnValuePtr,EnvAddLong(theEnv,0L));

return;

}


if (! EnvArgTypeCheck(theEnv,“triple”,1,INTEGER_OR_FLOAT,returnValuePtr))

{

SetpType(returnValuePtr,INTEGER);

SetpValue(returnValuePtr,EnvAddLong(theEnv,0L));

return;

}


/*====================*/

/* Triple the number. */

/*====================*/


if (GetpType(returnValuePtr) == INTEGER)

{

value = GetpValue(returnValuePtr);

longValue = 3 * ValueToLong(value);

SetpValue(returnValuePtr,EnvAddLong(theEnv,longValue));

}

else /* the type must be FLOAT */

{

value = GetpValue(returnValuePtr);

doubleValue = 3.0 * ValueToDouble(value);

SetpValue(returnValuePtr,EnvAddDouble(theEnv,doubleValue));

}


return;

}

9.5 Allocating Environment Data

If your user-defined functions (or other extensions) make use of global data that could differ for each environment, you should allocate this data with the AllocateEnvironmentData function. A call to this function has four arguments. The first is a generic pointer to the environment to which the data is being added.


The second argument is the integer position index. This is the value that you will pass in to the GetEnvironmentData function to retrieve the allocated environment data. This position index must be unique and if you attempt to use an index that has already been allocated, then the call to AllocateEnvironmentData will fail returning FALSE. To avoid collisions with environment positions predefined by CLIPS, use the macro constant USER_ENVIRONMENT_DATA as the base index for any position indices you define. This constant will always be greater than the largest predefined position index used by CLIPS. The maximum number of environment position indices is specified by the macro constant MAXIMUM_ENVIRONMENT_POSITIONS found in the envrnmnt.h header file. A call to AllocateEnvironmentData will fail if the position index is greater than or equal this value. If this happens, you can simply increase the value of this macro constant to provide more environment positions.


The third argument is an integer indicating the size of the environment data that needs to be allocated. Typically you’ll define a struct containing the various values you want stored in the environment data and use the sizeof operator to pass in the size of the struct to the function. When an environment is created directly using CreateEnvironment or indirectly using InitializeEnvironment, CLIPS automatically allocates memory of the size specified, initializes the memory to contain all zeroes, and stores the memory in the environment position associated with position index. When the environment is destroyed using DestroyEnvironment, CLIPS automatically deallocates the memory originally allocated for each environment data position. If the environment data contains pointers to memory that you allocate, it is your responsibility to deallocate this memory. You can do this by either specifying a cleanup function as the fourth argument in your AllocateEnvironmentData call or by adding a cleanup function using the AddEnvironmentCleanupFunction function.


The fourth argument is a pointer to a cleanup function. If this argument is not NULL, then the cleanup function associated with this environment position is called whenever an environment is deallocated using the DestroyEnvironment function. The cleanup functions are called in ascending order of the position indices.


As an example of allocating environment data, we’ll look at a get-index function that returns an integer index starting with one and increasing by one each time it is called. For example:


CLIPS> (get-index)

1

CLIPS> (get-index)

2

CLIPS> (get-index)

3

CLIPS>


Each environment will need global data to store the current value of the index. The C source code that implements the environment data first needs to specify the position index and specify a data structure for storing the data:


#define INDEX_DATA USER_ENVIRONMENT_DATA + 0


struct indexData

{

long index;

};

#define IndexData(theEnv) \

((struct indexData *) GetEnvironmentData(theEnv,INDEX_DATA))


First, the position index GET_INDEX_DATA is defined as USER_ENVIRONMENT_DATA with an offset of zero. If you were to define additional environment data, the offset would be increased each time by one to get to the next available position. Next, the indexData struct is defined. This struct contains a single member, index, which will use to store the next value returned by the get-index function. Finally, the IndexData macro is defined which merely provides a convenient mechanism for access to the environment data.


The next step in the C source code is to add the initialization code to the EnvUserFunctions function:


void EnvUserFunctions(

void *theEnv)

{

if (! AllocateEnvironmentData(theEnv,INDEX_DATA,

sizeof(struct indexData),NULL))

{

printf(“Error allocating environment data for INDEX_DATA\n”);

exit(EXIT_FAILURE);

}


IndexData(theEnv)→index = 1;


EnvDefineFunction2(theEnv,“get-index”,'l',PTIEF GetIndex, “GetIndex”,

“00”);

}


First, the call to AllocateEnvironmentData is made. If this fails, then an error message is printed and a call to exit is made to terminate the program. Otherwise, the index member of the environment data is initialized to one. If a starting value of zero was desired, it would not be necessary to perform any initialization since the value of index is automatically initialized to zero when the environment data is initialized. Finally, EnvDefineFunction2 is called to register the get-index function.


The last piece of the C source code is the GetIndex C function which implements the get-index function:


long GetIndex(

void *theEnv)

{

if (EnvArgCountCheck(theEnv,“get-index”,EXACTLY,0) == -1)

{ return(0); }

return(IndexData(theEnv)→index++);

}


This function is fairly straightforward. A generic pointer to the current environment is passed to the function since it was registered using EnvDefineFunction2. First a check for the correct number of arguments is made and then a call to the IndexData macro is made to retrieve the index member of struct which is the return value. Use of the ++ operator increments the current value of the index member before the function returns.

9.6 Environment Globals

The only global variables in the C source code for CLIPS are used to keep track of the current environment and the environment indices. If it is desired to remove these global variables, the ALLOW_ENVIRONMENT_GLOBALS compiler directive can be disabled. If disabled, you can no longer use the following functions: GetCurrentEnvironment, GetEnvironmentIndex, SetCurrentEnvironment, and SetCurrentEnvironmentByIndex. In addition, if disabled the ENVIRONMENT_API_ONLY compiler directive is enabled and the EMACS_EDITOR compiler directive is disabled.

9.7 Other Considerations

The mechanism for loading run-time program has changed with the introduction of environments. See section 5 for more details.


Appendix A   Language Integration Listings

This appendix includes listings for various language interface packages described in section 6. The portability of these routines varies. Most of the code listed in the interface packages defined in sections A.1 and A.2 should be fairly portable. How­ever, the string conversion routine in section A.3 is not as portable. For example, the Ada function Convert_to_C_String is probably portable to any Ada machine, yet the C function MakeStringDsc listed here is very specific to the DEC VMS. These functions should be typed in exactly as shown below.

A.1 Ada Interface Package for CLIPS

The following listings are an Ada package specification and body for some of the CLIPS functions used in embedded CLIPS systems. The code is specific to the DEC Ada com­piler because of the PRAGMA IMPORT_PROCEDURE. Other Ada compilers may pro­vide a similar capability, and this package specification could be modified.

CLIPS Package Specification


package CLIPS is


-----------------------------------------------------------------

– Initializes the CLIPS environment upon program startup.


procedure xInitializeEnvironment;

-----------------------------------------------------------------

– Resets the CLIPS environment.


procedure xReset;

-----------------------------------------------------------------

– Loads a set of constructs into the CLIPS database. If there are syntactic

– error in the constructs, xLoad will still attempt to read the

– entire file, and error notices will be sent to werror.

– Returns: an integer, zero if an error occurs.


function xLoad (File_Name : in string) return integer;

-----------------------------------------------------------------

– Allows Run_Limit rules to fire (execute).

– -1 allows rules to fire until the agenda is empty.

– Returns: Number of rules that were fired.


function xRun (Run_Limit : in integer := -1) return integer;

-----------------------------------------------------------------

– Lists the facts in the fact-list.


procedure xFacts (Logical_Name : in string;

Module_Ptr : in integer;

First : in integer;

Last : in integer;

Max : in integer);

-----------------------------------------------------------------

– Turns the watch facilities of CLIPS on.


function xWatch (Watch_Item : in string) return integer;

-----------------------------------------------------------------

– Turns the watch facilities of CLIPS off.


function xUnwatch (Watch_Item : in string) return integer;

-----------------------------------------------------------------

– Asserts a fact into the CLIPS fact-list. The function version

– returns the Fact_Pointer required by xRetractFact.


function xAssertString (Pattern : in string) return integer;

-----------------------------------------------------------------

– Causes a fact asserted by the ASSERT_FACT function to be retracted.

– Returns: false if fact has already been retracted, else true.

– Input of any value not returned by ASSERT_FACT will

– cause CLIPS to abort.


function xRetract (Fact_Pointer : in integer) return integer;

-----------------------------------------------------------------

– Queries all active routers until it finds a router that

– recognizes the logical name associated with this I/O request

– to print a string. It then calls the print function associated

– with that router.


function xPrintRouter (Log_Name : in string ;

Str : in string) return integer;

-----------------------------------------------------------------

– Removes a rule from CLIPS.

– Returns: false if rule not found, else true.


function xUndefrule (Rule_Name : in string) return integer;

-----------------------------------------------------------------

-----------------------------------------------------------------


private


pragma INTERFACE (C, xInitializeEnvironment);

pragma IMPORT_PROCEDURE (INTERNAL ⇒ xInitializeEnvironment,

EXTERNAL ⇒ InitializeEnvironment);


pragma INTERFACE (C, xReset);

pragma IMPORT_PROCEDURE (INTERNAL ⇒ xReset,

EXTERNAL ⇒ Reset);


function cLoad (File_Name : in string) return integer;

pragma INTERFACE (C, cLoad);

pragma IMPORT_FUNCTION (INTERNAL ⇒ cLoad,

EXTERNAL ⇒ Load,

MECHANISM ⇒ REFERENCE);


pragma INTERFACE (C, xRun);

pragma IMPORT_FUNCTION (INTERNAL ⇒ xRun,

EXTERNAL ⇒ Run,

MECHANISM ⇒ VALUE);


procedure cFacts(Logical_Name : in string;

Module_Ptr : in integer;

First : in integer;

Last : in integer;

Max : in integer);

pragma INTERFACE (C, cFacts);

pragma IMPORT_PROCEDURE (INTERNAL ⇒ cFacts,

EXTERNAL ⇒ Facts,

MECHANISM ⇒ (REFERENCE, VALUE,

VALUE, VALUE, VALUE));


function cWatch (Item : in string) return integer;

pragma INTERFACE (C, cWatch);

pragma IMPORT_FUNCTION (INTERNAL ⇒ cWatch,

EXTERNAL ⇒ Watch,

MECHANISM ⇒ REFERENCE);


function cUnwatch (Item : in string) return integer;

pragma INTERFACE (C, cUnwatch);

pragma IMPORT_FUNCTION (INTERNAL ⇒ cUnwatch,

EXTERNAL ⇒ Unwatch,

MECHANISM ⇒ REFERENCE);


function cAssertString (Pattern : in string) return integer;

pragma INTERFACE (C, cAssertString);

pragma IMPORT_FUNCTION (INTERNAL ⇒ cAssertString,

EXTERNAL ⇒ AssertString,

MECHANISM ⇒ REFERENCE);


function cRetract (Fact_Pointer : in integer) return integer;

pragma INTERFACE (C, cRetract);

pragma IMPORT_FUNCTION (INTERNAL ⇒ cRetract,

EXTERNAL ⇒ Retract,

MECHANISM ⇒ VALUE);


function cPrintRouter (Log_Name : in string ;

Str : in string) return integer;

pragma INTERFACE (C, cPrintRouter);

pragma IMPORT_FUNCTION (INTERNAL ⇒ cPrintRouter,

EXTERNAL ⇒ PrintRouter,

MECHANISM ⇒ REFERENCE);


function cUndefrule (Rule_Name : in string) return integer;

pragma INTERFACE (C, cUndefrule);

pragma IMPORT_FUNCTION (INTERNAL ⇒ cUndefrule,

EXTERNAL ⇒ Undefrule,

MECHANISM ⇒ REFERENCE);


end CLIPS;


CLIPS Package Body


package body CLIPS is


-----------------------------------------------------------------


function ADA_TO_C_STRING (Input_String : in string)

return string is


Out_String : string (1..Input_String'LAST+1);


begin

for I in Input_String'RANGE loop

if (Input_String (I) in ' ' .. '~' or

Input_String (I) = ASCII.Cr or

Input_String (I) = ASCII.Lf ) then

Out_String (I) := Input_String (I);

else

Out_String (I) := ASCII.Nul;

end if;

end loop;

Out_String (Out_String'LAST) := ASCII.Nul;

return Out_String;

end ADA_TO_C_STRING;


-----------------------------------------------------------------


function xLoad (File_Name : in string) return integer is


begin

return cLoad (ADA_TO_C_STRING (File_Name));

end xLoad;


-----------------------------------------------------------------


procedure xFacts (Logical_Name : in string;

Module_Ptr : in integer;

First : in integer;

Last : in integer;

Max : in integer) is


begin

cFacts (ADA_TO_C_STRING (Logical_Name),Module_Ptr,First,Last,Max);


end xFacts;


-----------------------------------------------------------------


function xWatch (Watch_Item : in string) return integer is


begin

return cWatch (ADA_TO_C_STRING (Watch_Item));


end xWatch;


-----------------------------------------------------------------


function xUnwatch (Watch_Item : in string) return integer is


begin

return cUnwatch (ADA_TO_C_STRING (Watch_Item));


end xUnwatch;


-----------------------------------------------------------------


function xAssertString (Pattern : in string) return integer is


begin

return cAssertString (ADA_TO_C_STRING (Pattern));

end xAssertString;


-----------------------------------------------------------------


function xRetract (Fact_Pointer : in integer) return integer is


begin

return cRetract (Fact_Pointer);

end xRetract;


-----------------------------------------------------------------


function xPrintRouter (Log_Name : in string ;

Str : in string ) return integer is


begin

return cPrintRouter (ADA_TO_C_STRING (Log_Name),

ADA_TO_C_STRING (Str));

end xPrintRouter;


-----------------------------------------------------------------


function xUndefrule (Rule_Name : in string) return integer is


begin

return cUndefrule (ADA_TO_C_STRING (Rule_Name));

end xUndefrule;


end CLIPS;

A.2 FORTRAN Interface Package for VAX VMS

The following pages are listings of the FORTRAN interface functions for the VAX VMS plus the internal functions used to convert FORTRAN character strings to C character strings and vice versa. Many of these func­tions may work with minor modifications on other machines; note, however, the use of the VMS argument passing modifier, %VAL, in some functions.


C

C————————————————————–

C

SUBROUTINE xInitializeEnvironment


CALL InitializeEnvironment

RETURN

END

C

C————————————————————–

C

SUBROUTINE xReset


CALL Reset

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xLoad (FILE_NAME)


CHARACTER * (*) FILE_NAME

CHARACTER *80 C_FILE_NAME

INTEGER C_FILE_NAME_POINTER, Load

EQUIVALENCE (C_FILE_NAME, C_FILE_NAME_POINTER)


CALL CONVERT_TO_C_STRING (FILE_NAME, C_FILE_NAME)

xLoad = Load (C_FILE_NAME_POINTER)

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xRun (RUN_LIMIT)


INTEGER RUN_LIMIT, Run


xRun = Run (%VAL (RUN_LIMIT))

RETURN

END

C

C————————————————————–

C

SUBROUTINE xFacts (LOGICAL_NAME, MODULE, BEGIN, END, MAX)


CHARACTER * (*) LOGICAL_NAME

INTEGER MODULE, BEGIN, END, MAX

CHARACTER *80 C_LOGICAL_NAME

INTEGER C_LOGICAL_NAME_POINTER

EQUIVALENCE (C_LOGICAL_NAME, C_LOGICAL_NAME_POINTER)


CALL CONVERT_TO_C_STRING (LOGICAL_NAME, C_LOGICAL_NAME)

CALL Facts(C_LOGICAL_NAME_POINTER,%VAL (MODULE),

* %VAL (BEGIN),%VAL (END),%VAL (MAX))

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xWatch (WATCH_ITEM)


CHARACTER * (*) WATCH_ITEM

CHARACTER *80 C_WATCH_ITEM

INTEGER C_WATCH_ITEM_POINTER, Watch

EQUIVALENCE (C_WATCH_ITEM, C_WATCH_ITEM_POINTER)


CALL CONVERT_TO_C_STRING (WATCH_ITEM, C_WATCH_ITEM)

xWatch = Watch (C_WATCH_ITEM_POINTER)

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xUnwatch (WATCH_ITEM)


CHARACTER * (*) WATCH_ITEM

CHARACTER *80 C_WATCH_ITEM

INTEGER C_WATCH_ITEM_POINTER, Unwatch

EQUIVALENCE (C_WATCH_ITEM, C_WATCH_ITEM_POINTER)


CALL CONVERT_TO_C_STRING (WATCH_ITEM, C_WATCH_ITEM)

xUnwatch = Unwatch (C_WATCH_ITEM_POINTER)

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xAssertString (PATTERN)

C

CHARACTER * (*) PATTERN

CHARACTER *80 C_PATTERN

INTEGER C_PATTERN_POINTER, AssertString

EQUIVALENCE (C_PATTERN, C_PATTERN_POINTER)

C

CALL CONVERT_TO_C_STRING (PATTERN, C_PATTERN)

xAssertString = AssertString (C_PATTERN_POINTER)

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xRetract (FACT_ADDRESS)


INTEGER FACT_ADDRESS, Retract


xRetract = Retract (%VAL (FACT_ADDRESS))

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xPrintRouter (LOG_NAME, PRINT_LINE)


CHARACTER * (*) LOG_NAME

CHARACTER * (*) PRINT_LINE

CHARACTER *80 C_LOG_NAME

CHARACTER *80 C_PRINT_LINE

INTEGER C_PRINT_LINE_POINTER, C_LOG_NAME_POINTER

EQUIVALENCE (C_PRINT_LINE, C_PRINT_LINE_POINTER),

* (C_LOG_NAME , C_LOG_NAME_POINTER )


CALL CONVERT_TO_C_STRING (PRINT_LINE, C_PRINT_LINE)

CALL CONVERT_TO_C_STRING (LOG_NAME, C_LOG_NAME)

xPrintRouter = PrintRouter (C_LOG_NAME_POINTER,

* C_PRINT_LINE_POINTER)

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xFindDefrule (RULE_NAME)


CHARACTER * (*) RULE_NAME

CHARACTER *80 C_RULE_NAME

INTEGER C_RULE_NAME_POINTER, Undefrule

EQUIVALENCE (C_RULE_NAME, C_RULE_NAME_POINTER)


CALL CONVERT_TO_C_STRING (RULE_NAME, C_RULE_NAME)

xFindDefrule = FindDefrule (C_RULE_NAME_POINTER)

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION xUndefrule (RULE_NAME)


CHARACTER * (*) RULE_NAME

CHARACTER *80 C_RULE_NAME

INTEGER C_RULE_NAME_POINTER, Undefrule

EQUIVALENCE (C_RULE_NAME, C_RULE_NAME_POINTER)


CALL CONVERT_TO_C_STRING (RULE_NAME, C_RULE_NAME)

xUndefrule = Undefrule (C_RULE_NAME_POINTER)

RETURN

END

C

C————————————————————–

C

SUBROUTINE CONVERT_TO_C_STRING (F_STRING, C_STRING)

CHARACTER * (*) F_STRING, C_STRING


K = LENGTH (F_STRING)

DO 100 I = 1,K

C_STRING (I:I) = F_STRING (I:I)

100 CONTINUE

K = K + 1

C_STRING (K:K) = CHAR (0)

RETURN

END

C

C————————————————————–

C

INTEGER FUNCTION LENGTH (STRING)

C

CHARACTER * (*) STRING

C

K = LEN (STRING)

DO 100 I=K,1,-1

IF(STRING(I:I) .NE. ' ') GO TO 150

C

100 CONTINUE

150 CONTINUE

LENGTH = I

RETURN

END

A.3 Function to Convert C Strings for VMS Ada or FORTRAN

This function converts a C string to an Ada string. The MakeStringDsc function normally is stored in the same file together with the UserFunctions definition and any C interface subroutines. The function is not portable and is specific to the VAX VMS envi­ronment. The definition of Ada string descriptors is implementation dependent, and ac­cess to those definitions from C also is implementation dependent. However, a very similar function could be written for any environment that supports Ada and C.

C Function: MakeStringDsc

(Note:This function definition is VAX VMS specific)


#include <ssdef.h>
#include <descrip.h>

struct dsc$descriptor_s *MakeStringDsc(c_str)
char *c_str;
{
struct dsc$descriptor_s *desc;

desc = (struct dsc$descriptor_s *) malloc
(sizeof (struct dsc$descriptor_s));

/* Define String Descriptor */

desc→dsc$w_length = strlen(c_str);
desc→dsc$a_pointer = c_str;
desc→dsc$b_class = DSC$K_CLASS_S;
desc→dsc$b_dtype = DSC$K_DTYPE_T;

return(desc);
}

Appendix B   I/O Router Examples

The following examples demonstrate the use of the I/O router system. These exam­ples show the necessary C code for implementing the basic capabilities described.

B.1 Dribble System

Write the necessary functions that will divert all tracing information to the trace file named “trace.txt”.


/*

First of all, we need a file pointer to the dribble file which will contain the tracing information.

*/


#include <stdio.h>

#include “clips.h”


static FILE *TraceFP = NULL;


/*

We want to recognize any output that is sent to the logical name “wtrace” because all tracing information is sent to this logical name. The recognizer function for our router is defined below.

*/


int FindTrace(

char *logicalName)

{

if (strcmp(logicalName,“wtrace”) == 0) return(TRUE);


return(FALSE);

}


/*

We now need to define a function which will print the tracing in­formation to our trace file. The print function for our router is defined below.

*/


int PrintTrace(
char *logicalName,

char *str)
{

fprintf(TraceFP,“%s”,str);

}


/*

When we exit CLIPS the trace file needs to be closed. The exit function for our router is defined below.

*/


int ExitTrace(
int exitCode) /* unused */
{

fclose(TraceFP);

}


/*

There is no need to define a get character or ungetc character function since this router does not handle input.


A function to turn the trace mode on needs to be defined. This function will check if the trace file has already been opened. If the file is already open, then nothing will happen. Otherwise, the trace file will be opened and the trace router will be creat­ed. This new router will intercept tracing information intended for the user interface and send it to the trace file. The trace on function is defined below.

*/


int TraceOn()
{
if (TraceFP == NULL)

{
TraceFP = fopen(“trace.txt”,“w”);

if (TraceFP == NULL) return(FALSE);

}

else

{ return(FALSE); }


AddRouter(“trace”, /* Router name */

20, /* Priority */

FindTrace, /* Query function */

PrintTrace, /* Print function */

NULL, /* Getc function */

NULL, /* Ungetc function */

ExitTrace); /* Exit function */


return(TRUE);

}


/*

A function to turn the trace mode off needs to be defined. This function will check if the trace file is already closed. If the file is already closed, then nothing will happen. Otherwise, the trace router will be deleted and the trace file will be closed. The trace off function is defined below.

*/


int TraceOff()

{

if (TraceFP != NULL)

{

DeleteRouter(“trace”);


if (fclose(TraceFP) == 0)

{

TraceFP = NULL;

return(TRUE);

}

}


TraceFP = NULL;

return(FALSE);

}


/*

Now add the definitions for these functions to the UserFunctions func­tion in file “main.c”.

*/


extern int TraceOn(), TraceOff();


DefineFunction(“tron”,'b',TraceOn, “TraceOn”);

DefineFunction(“troff”,'b',TraceOff, “TraceOff”);


/*

Compile and link the appropriate files. The trace functions should now be accessible within CLIPS as external functions. For Example

CLIPS>(tron)

CLIPS>(troff)

*/

B.2 Better Dribble System

Modify example 1 so the tracing information is sent to the terminal as well as to the trace dribble file.


/*

This example requires a modification of the PrintTrace function. After the trace string is printed to the file, the trace router must be deactivated. The trace string can then be sent through the PrintRouter function so that the next router in line can handle the output. After this is done, then the trace router can be reactivated.

*/


int PrintTrace(

char *logicalName,

char *str)

{

fprintf(TraceFP,“%s”,str);

DeactivateRouter(“trace”);

PrintRouter(logicalName,str);

ActivateRouter(“trace”);

}


/*

The TraceOn function must also be modified. The priority of the router should be 40 instead of 20 since the router passes the output along to other routers.

*/


int TraceOn()

{

if (TraceFP == NULL)

{

TraceFP = fopen(“trace.txt”,“w”);

if (TraceFP == NULL) return(FALSE);

}

else

{ return(FALSE); }


AddRouter(“trace”, /* Router name */

40, /* Priority */

FindTrace, /* Query function */

PrintTrace, /* Print function */

NULL, /* Getc function */

NULL, /* Ungetc function */

ExitTrace); /* Exit function */


return(TRUE);

}

B.3 Batch System

Write the necessary functions that will allow batch input from the file “batch.txt” to the CLIPS top level interface.


/*

First of all, we need a file pointer to the batch file which will contain the batch command information.

*/


#include <stdio.h>

#include “clips.h”


static FILE *BatchFP = NULL;


/*

We want to recognize any input requested from the logical name “stdin” because all user input is received from this logical name. The recognizer function for our router is defined below.

*/


int FindMybatch(

char *logicalName)

{

if (strcmp(logicalName,“stdin”) == 0) return(TRUE);

return(FALSE);

}


/*

We now need to define a function which will get and unget charac­ters from our batch file. The get and ungetc character functions for our router are defined below.

*/


static char BatchBuffer[80];

static int BatchLocation = 0;


int GetcMybatch(

char *logicalName)

{

int rv;


rv = getc(BatchFP);


if (rv == EOF)

{

DeleteRouter(“mybatch”);

fclose(BatchFP);

return(GetcRouter(logicalName));

}


BatchBuffer[BatchLocation] = (char) rv;

BatchLocation++;

BatchBuffer[BatchLocation] = EOS;


if ((rv == '\n') || (rv == '\r'))

{

PrintRouter(“wprompt”,BatchBuffer);

BatchLocation = 0;

}


return(rv);

}


int UngetcMybatch(

int ch,

char *logicalName) /* unused */

{

if (BatchLocation > 0) BatchLocation–;

BatchBuffer[BatchLocation] = EOS;

return(ungetc(ch,BatchFP));

}


/*

When we exit CLIPS the batch file needs to be closed. The exit function for our router is defined below.

*/


int ExitMybatch(

int exitCode) /* unused */

{

fclose(BatchFP);

}


/*

There is no need to define a print function since this router does not handle output except for echoing the command line.

Now we define a function that turns the batch mode on.

*/


int MybatchOn()

{

BatchFP = fopen(“batch.txt”,“r”);


if (BatchFP == NULL) return(FALSE);


AddRouter(“mybatch”, /* Router name */

20, /* Priority */

FindMybatch, /* Query function */

NULL, /* Print function */

GetcMybatch, /* Getc function */

UngetcMybatch, /* Ungetc function */

ExitMybatch); /* Exit function */


return(TRUE);

}


/*

Now add the definition for this function to the UserFunctions function in file “main.c”.

*/


extern int MybatchOn();


DefineFunction(“mybatch”,'b',MybatchOn, “MybatchOn”);


/*

Compile and link the appropriate files. The batch function should now be accessible within CLIPS as external function. For Example

CLIPS> (mybatch)

*/

B.4 Simple Window System

Write the necessary functions using CURSES (a screen management function available in UNIX) that will allow a top/bottom split screen interface. Output sent to the logical name top will be printed in the upper win­dow. All other screen I/O should go to the lower window. (NOTE: Use of CURSES may require linking with special libraries.)


/*

First of all, we need some pointers to the windows and a flag to indicate that the windows have been initialized.

*/


#include <stdio.h>

#include <curses>

#include “clips.h”


WINDOW *LowerWindow, *UpperWindow;

int WindowInitialized = FALSE;


/*

We want to intercept any I/O requests that the standard interface would handle. In addition, we also need to handle requests for the logical name top. The recognizer function for our router is defined below.

*/


int FindScreen(

char *logicalName)

{

if ((strcmp(logicalName,“stdout”) == 0) ||

(strcmp(logicalName,“stdin”) == 0) ||

(strcmp(logicalName,“wprompt”) == 0) ||

(strcmp(logicalName,“wdisplay”) == 0) ||

(strcmp(logicalName,“wdialog”) == 0) ||

(strcmp(logicalName,“werror”) == 0) ||

(strcmp(logicalName,“wwarning”) == 0) ||

(strcmp(logicalName,“wtrace”) == 0) ||

(strcmp(logicalName,“top”) == 0) )

{ return(TRUE); }


return(FALSE);

}


/*

We now need to define a function which will print strings to the two windows. The print function for our router is defined below.

*/


int PrintScreen(

char *logicalName,

char *str)

{

if (strcmp(logicalName,“top”) == 0)

{

wprintw(UpperWindow,“%s”,str);

wrefresh(UpperWindow);

}

else

{

wprintw(LowerWindow,“%s”,str);

wrefresh(LowerWindow);

}

}


/*

We now need to define a function which will get and unget characters from the lower window. CURSES uses unbuffered input so we will simulate buffered input for CLIPS. The get and ungetc char­acter functions for our router are defined below.

*/


static int UseSave = FALSE;

static int SaveChar;

static int SendReturn = TRUE;


static char StrBuff[80] = {'\0'};

static int CharLocation = 0;


int GetcScreen(

char *logicalName)

{

int rv;


if (UseSave == TRUE)

{

UseSave = FALSE;

return(SaveChar);

}


if (StrBuff[CharLocation] == '\0')

{

if (SendReturn == FALSE)

{

SendReturn = TRUE;

return('\n');

}


wgetstr(LowerWindow,StrBuff[80] );

CharLocation = 0;

}

rv = StrBuff[CharLocation];

if (rv == '\0') return('\n');

CharLocation++;

SendReturn = FALSE;

return(rv);

}


int UngetcScreen(

char ch,

char *logicalName)

{

UseSave = TRUE;

SaveChar = ch;

return(ch);

}


/*

When we exit CLIPS CURSES needs to be deactivated. The exit function for our router is defined below.

*/


int ExitScreen(

int num) /* unused */

{

endwin();

}


/*

Now define a function that turns the screen mode on.

*/


int ScreenOn()

{

int halfLines, i;


/* Has initialization already occurred? */


if (WindowInitialized == TRUE) return(FALSE);

else WindowInitialized = TRUE;


/* Reroute I/O and initialize CURSES. */


initscr();

echo();


AddRouter(“screen”, /* Router name */

10, /* Priority */

FindScreen, /* Query function */

PrintScreen, /* Print function */

GetcScreen, /* Getc function */

UngetcScreen, /* Ungetc function */

ExitScreen); /* Exit function */


/* Create the two windows. */


halfLines = LINES / 2;

UpperWindow = newwin(halfLines,COLS,0,0);

LowerWindow = newwin(halfLines - 1,COLS,halfLines + 1,0);


/* Both windows should be scrollable. */


scrollok(UpperWindow,TRUE);

scrollok(LowerWindow,TRUE);


/* Separate the two windows with a line. */


for (i = 0 ; i < COLS ; i++)

{ mvaddch(halfLines,i,'-'); }

refresh();


wclear(UpperWindow);

wclear(LowerWindow);

wmove(LowerWindow, 0,0);


return(TRUE);

}


/*

Now define a function that turns the screen mode off.

*/


int ScreenOff()

{

/* Is CURSES already deactivated? */


if (WindowInitialized == FALSE) return(FALSE);


WindowInitialized = FALSE;


/* Remove I/O rerouting and deactivate CURSES. */


DeleteRouter(“screen”);

endwin();


return(TRUE);

}


/*

Now add the definitions for these functions to the UserFunctions func­tion in file “main.c”.

*/


extern int ScreenOn(), ScreenOff();


DefineFunction(“screen-on”,'b',ScreenOn, “ScreenOn”);

DefineFunction(“screen-off”,'b',ScreenOff, “ScreenOff”);


/*

Compile and link the appropriate files. The screen functions should now be accessible within CLIPS as external functions. For Example

CLIPS> (screen-on)

CLIPS> (screen-off)

*/


Appendix C   Update Release Notes

The following sections denote the changes and bug fixes for CLIPS versions 6.05, 6.1, and 6.2.

C.1 Version 6.2

Environments – It is now possible in an embedded application to create multiple environments into which programs can be loaded (see section 9).


External Function Interface - Several new functions have been added including:


GetClassDefaultsMode (see section 4.12.10)

SetClassDefaultsMode (see section 4.12.19)


Run-time Programs – Support for environments requires some changes in code for loading run-time programs (see section 5).


Compiler Directives – Two new flags have been added: ENVIRONMENT_API_ONLY and ALLOW_ENVIRONMENT_GLOBALS.


New Source Files – New source files have been added (see section 2.1 for a complete list of source files):


envrnmnt.c

envrnmnt.h

C.2 Version 6.1

C++ Compatible – The CLIPS source code can now be compiled using either an ANSI C or C++ compiler. Minimally, non-ANSI C compilers must support full ANSI style function prototypes and the void data type in order to compile CLIPS.


Obsolete External Function Interface Changes - The following functions should be replaced with the specified functions. If clips.h is included in your C files, macros will automatically map these functions to their new names.


CLIPSFunctionCall (use FunctionCall instead)

ExitCLIPS (use ExitRouter instead)

GetcCLIPS (use GetcRouter instead)

InitializeCLIPS (use InitializeEnvironment instead)

PrintCLIPS (use PrintRouter instead)

UngetcCLIPS (use UngetcRouter instead)


Obsolete Constants and Variables Changes - The following constants and variables should be replaced with the specified replacements. If clips.h is included in your C files, macros will automatically map the old names to their new names.


CLIPS_FALSE (use FALSE instead)

CLIPS_TRUE (use TRUE instead)

CLIPSFalseSymbol (use FalseSymbol instead)

CLIPSTrueSymbol (use TrueSymbol instead)

WCLIPS (use WPROMPT instead)


Source File Name Changes - The names of the following source files have been changed:


clipsmem.h (use memalloc.h instead)

memory.c (use memalloc.c instead)


Compiler Directives – Some of the compiler directive flags in the file setup.h have been changed. The CLP_TEXTPRO and CLP_HELP flags have been renamed to TEXTPRO_FUNCTIONS and HELP_FUNCTIONS. The ANSI_COMPILER flag has been removed.


External Function Interface - Several new functions have been added including:


GetNextInstanceInClassAndSubclasses (see section 4.13.15)


New Source Files – Several new source files have been added (see section 2.1 for a complete list of source files):


parsefun.c

parsefun.h

proflfun.c

proflfun.h

sortfun.c

sortfun.h

C.3 Version 6.05

Compiler Directives   The CLP_EDIT flag in the file setup.h has been renamed to EMACS_EDITOR.


External Function Interface - Several new functions have been added including:


BatchStar (see section 4.1.23)

Build (see section 4.1.24)

Eval (see section 4.1.25)

FactExistp (see section 4.4.20)

FactSlotNames (see section 4.4.21)

GetFactList (see section 4.4.22)

LoadFactsFromString (see section 4.4.23)

LoadInstancesFromString (see section 4.13.26)

RestoreInstancesFromString (see section 4.13.27)

 
 
clips/apg4.txt · Last modified: 2010/01/04 20:36 by admin
topheader
© 1998-2021, SciOS Scientific Operating Systems GmbH

Legal | Credits | Profile | Contact | Customer Login