RuleWorks
Using RuleWorks with Other Languages
Programming tasks such as computing mathematical expressions, manipulating strings, and editing large quantities of data are often easier and more efficient to develop in languages other than RuleWorks. If you are developing a RuleWorks program that needs to perform these types of tasks, you should consider using external routines. An external routine is a function or subroutine written in a language other than RuleWorks. External routines include prewritten routines such as system services and run-time library (RTL) routines.
RuleWorks allows external routines that accept arguments and return results according to the hardware platform's calling standard. This means that you can call external routines that are not written specifically for RuleWorks. Example 6-1 shows a simple RuleWorks program calling the C RTL routine, sqrt.
Example 6-1 Calling an External Routine from RuleWorks
(declaration-block decls)
(object-class start)
(end-block decls)
(entry-block main
(uses decls)
(on-entry
(make start)))
(external-routine sqrt
(accepts double-float)
(returns double-float))
(rule square-root
(start)
-->
(write |Enter a number> |)
(bind <input> (accept-atom))
(write |The square root is| (sqrt <input>)))
(end-block main)
RuleWorks also allows entry blocks to be called from other languages. Entry blocks can accept arguments and return a value. Entry blocks can call other entry blocks, but the callers must declare the other entry blocks with EXTERNAL-ROUTINE declarations. Entry blocks can even call themselves recursively.
Calling External Routines from RuleWorksYou must declare external routines before you call them in your RuleWorks program. External routines are scoped to the block in which they are declared.
The easiest way to ensure that the correct declarations have been made is to place the EXTERNAL-ROUTINE declarations in a separate declaration block and have each module acquire the compiled declarations via a USES clause. See Chapter 5 for details on data partitioning and declaration sharing.
The complete syntax for the EXTERNAL-ROUTINE declaration is shown below:
Figure 6-1. External Routine Declaration Syntax
(EXTERNAL-ROUTINE routine-name
[(ALIAS-FOR actual-routine-name)]
[ <formal-parameter-name> [ [size] ] ]
[ (ACCEPTS {external-type } . . . ) ]
[passing-mechanism]
[ <formal-parameter-name> [ [size] ] ]
[ (ACCEPTS {external-type} ) ] }
[passing-mechanism]
The routine-name is required; the ALIAS-FOR, ACCEPTS and RETURNS clauses are optional. The syntax for calling an external routine is shown below:
(routine-name [ {value-expression} ... ])
EXTERNAL-ROUTINE declarations may appear inside any type of block, but they must appear inside of some kind of block, and they must appear before they are used. Duplicate declarations of the same external routine will generate a warning and be ignored so long as the declarations are identical.
External routines that return a value can be used on the RHS (as shown in Example 6-1) or on the LHS. External routines that do not return a value must be used on the RHS as if they were RuleWorks actions. You can also call an external routine that does return a value as if it were an RHS action, but in this case RuleWorks ignores the return value.
Writing Portable Code
The ALIAS-FOR clause allows you to declare that the routine name used inside RuleWorks is not the actual name that will be linked. This is useful for mapping a case-sensitive external routine name onto a case-insensitive RuleWorks symbol.
The actual function name must be quoted to preserve case. For example:
(external-routine xt_parent ; routine name used inside TINpan
(alias-for |XtParent|) ; actual function name
(accepts pointer)
(returns pointer))
You can use at most one ALIAS-FOR clause in an EXTERNAL-ROUTINE declaration. You must provide exactly one function name in an ALIAS-FOR clause.
Passing Parameters
Use the ACCEPTS clause to declare one or more parameters for an external routine. The <formal-parameter-name> is optional; use it to help make your code more self-documenting. The external-type-name is required. The passing-mechanism for each argument is also optional; the default mechanism for each external type is shown in Table 6-1. Arrays of each type are also allowed, passed BY REFERENCE only.
Table 6-1. External Data Types and Argument-Passing Mechanisms
Argument Passing Mechanisms | |||
External Data Type | BY VALUE | BY REFERENCE READ-ONLY | BY REFERENCE READ-WRITE |
BYTE | Default | Ö | Ö |
SHORT | Default | Ö | Ö |
LONG | Default | Ö | Ö |
UNSIGNED-BYTE | Default | Ö | Ö |
UNSIGNED-SHORT | Default | Ö | Ö |
UNSIGNED-LONG | Default | Ö | Ö |
SINGLE-FLOAT(1) | Default | Ö | Ö |
DOUBLE-FLOAT(2) | Default | Ö | Ö |
ASCIZ | N/S | Default | Ö |
ASCID | N/S | Default | Ö |
POINTER(3) | Default | Ö | Ö |
ATOM(4) | Default | Ö | Ö |
(1) On VMS systems, SINGLE-FLOAT refers to F_float_data
(2) On VAX VMS systems, DOUBLE-FLOAT refers to D_float data; on OpenVMS for Alpha AXP systems, DOUBLE-FLOAT refers to G_float data.
(3) Opaque virtual address
(4) Opaque atom
ÖSupported, but not the default
N/S Not supported
Numeric and pointer external types default to zeros. ASCIZ and ASCID externals default to the zero-length string. The ATOM external defaults to NIL.
RuleWorks's compound values correspond to an array of atoms in external routines. You declare a parameter as an array by putting brackets ( [ ] ) between the formal parameter name and the external data type. If you do not declare the size of the array by putting an integer between the brackets, then the array received by the external routine has as many elements as the compound value had when it was passed out, and the external routine cannot change the size of the array. An array returned by an external routine must be the declared size.
Since the size of the array is not automatically passed, applications using empty brackets must define a convention such as a specific value to signal the end of the array, or pass the length as a separate parameter. Example 6-2 shows a C program that passes a compound value.
Example 6-2 Passing a Compound Value: C Function
#include <stdio.h>
#include <rul_rtl.h>
/* Function: concat_compound
*
* Accepts:
* The length of the array passed as the second argument
* An array of asciz strings
* Function:
* Constructs a string containing the values in a compound.
* Example:
* Given arguments: 3 and ("A","B","C")
* it returns the string "A-B-C"
* Side Effect:
* Prints out all the input strings in the second argument,
* assuming that they all fit into one symbol.
* Returns
* The string formed by concatenating all the elements of the
* compound, assuming that they will all fit into a symbol.
* If they do not fit into a symbol, the result is truncated
* at the maximum symbol size.
*/
char *concat_compound (long num_elements, char *az_array[])
{
int i, index, len;
static char result[RUL_C_MAX_SYMBOL_SIZE+1] ;
index = 0;
printf ("\n Elements found in compound:");
for ( i=0; i<num_elements; i++) {
len = strlen (az_array[i]);
if (len > RUL_C_MAX_SYMBOL_SIZE - index)
len = RUL_C_MAX_SYMBOL_SIZE - index;
strncpy (&result[index], az_array[i], len);
index = index + len;
if (index >= RUL_C_MAX_SYMBOL_SIZE) {
/* not enough space in a symbol for all the compound values */
result[RUL_C_MAX_SYMBOL_SIZE] = '\0';
return (&result);
}
if ((i + 1) < num_elements) {
/* insert a dash between compound values */
result[index] = '-';
index = index + 1;
}
printf ("\n %s", az_array[i]);
}
if (index < RUL_C_MAX_SYMBOL_SIZE+1) {
result[index] = '\0';
}
return (&result);
}
Example 6-3 shows the RuleWorks program that calls the C function in Example 6-2.
Example 6-3 Passing a Compound Value: RuleWorks Program
(entry-block main
(object-class classname ^comp-attr compound ^length ^atom)
(external-routine concat_compound
(accepts <comp_len> long by value
<az_array> [] asciz by reference read-only)
(returns <ret_asciz> asciz))
(external-routine strlen
(accepts <az_string> asciz)
(returns <length> long))
(on-entry
(make classname ^comp-attr (compound a b c))))
(rule call-C-function
(classname ^$id <obj> ^atom <sym> ^length <> (strlen <sym>)
^comp-attr <comp>)
-->
(bind <result> (concat_compound (length <comp>) <comp>))
(write (crlf) | | <comp> |==>| <result>)
(remove <obj>))
(end-block main)
The dialog in Example 6-4 illustrates compiling, linking, and running Example 6-2 and Example 6-3 on a VMS system.
Example 6-4 Passing a Compound Value: Results
$rulework concat
...
$cc concat_comp
$link concat,concat_comp,rul$library:rul_rtl/lib
$run concat
Elements found in compound:
A
B
C
A B C ==> A-B-C
Passing Non-Atomic RuleWorks Objects
In RuleWorks, there are several kinds of objects with no corresponding entity in external routines. These objects cannot be passed directly, but they can be passed indirectly. They are listed below with the indirect mechanism by which they can be passed.
Table 6-2 Passing Non-Atomic Objects
Object | Indirect Passing Mechanism |
WMO | By object identifier, using the external type POINTER or ATOM |
Compound value | By converting it into an array. |
External Data Types
Values in RuleWorks are converted into external data types whenever they are passed to any external routine. These conversions are done automatically based on the EXTERNAL-ROUTINE declarations. The type specifications are checked at compile time for constants and at run time for expressions. Table 6-3 shows which RuleWorks types can be passed for each external type.
Table 6-3 Type Conversions of External Routine Parameters
RuleWorks Type |
|||||
External Type |
INTEGER |
FLOAT |
SYMBOL |
INSTANCE-ID |
OPAQUE |
BYTE |
Natural |
Implicit |
Error |
Error |
Error |
SHORT |
Natural |
Implicit |
Error |
Error |
Error |
LONG |
Equivalent |
Implicit |
Error |
Error |
Error |
UNSIGNED-BYTE |
Natural |
Implicit |
Error |
Error |
Error |
UNSIGNED-SHORT |
Natural |
Implicit |
Error |
Error |
Error |
UNSIGNED-LONG |
Natural |
Implicit |
Error |
Error |
Error |
SINGLE-FLOAT(1) |
Implicit |
Natural |
Error |
Error |
Error |
DOUBLE-FLOAT(2) |
Implicit |
Equivalent |
Error |
Error |
Error |
ASCID |
Implicit† |
Implicit† |
Equivalent |
Implicit† |
Error |
ASCIZ |
Implicit† |
Implicit† |
Equivalent |
Implicit† |
Error |
POINTER |
Error |
Error |
Error |
Error |
Natural |
ATOM |
No conversion required for ATOMs |
(1) On VMS systems, the external type SINGLE-FLOAT refers to F_float data
(2) On VAX VMS systems, the external type DOUBLE-FLOAT refers to D_float data; on OpenVMS for Alpha AXP systems, to G_float.
† ASCID and ASCIZ values are coerced outbound only. All other entries in this table apply both to calling out from and calling in to RuleWorks
The "natural" type conversion referred to in Table 6-3 is the one used when external data is returned to RuleWorks in a READ-WRITE parameter. The "equivalent" RuleWorks type/external type pairs have no loss of precision when passed either way as a READ-WRITE parameter. The "implicit" type conversions are handled automatically by RuleWorks according to the declarations. For example, you can pass an INTEGER atom as a SINGLE-FLOAT, DOUBLE-FLOAT, ASCIZ, or ASCID parameter without using the FLOAT or SYMBOL conversion functions. However, passing a SYMBOL or INSTANCE-ID as any numeric external type causes an error.
"Outbound only" means that when an INTEGER, FLOAT, or INSTANCE-ID atom is passed from RuleWorks to an external routine that expects a string, the value is coerced. However, if that string is returned (as a READ-WRITE parameter), it becomes an atom of type SYMBOL.
A compound value can be passed only as an array, and only a compound can be passed as an array. Each atom within the compound must be compatible (according to Table 6-3) with the external type.
If the external type is not large enough to represent the value being passed out, a warning is signaled. For example, 300 should not be passed out to an external routine that expects a byte.
Example 6-5 and Example 6-6 show how to return a compound value, and how to use a READ-WRITE parameter.
Example 6-5 External Function That Returns an Array
#include <stdio.h>
#include <rul_rtl.h>
/* Example function explode
*
* Accepts:
* An asciz string
* Function:
* Turns a symbol into an array of characters.
* Example:
* Given the argument "HELLO"
* Writes 5 into the second argument and
* Returns "H", "E", "L", "L", "O", "", "", ...
* Side Effect:
* Modifies the write-only argument, num_returned.
* Returns
* An array (rul_c_max_symbol_size in length)
* of very short ASCIZ strings
* (at most one character each).
*/
char **explode (char *in_string, long *num_returned)
{
/* The actual string space */
static char short_strings[RUL_C_MAX_SYMBOL_SIZE][2] ;
/* The array of string pointers to be returned */
static char *exploded[RUL_C_MAX_SYMBOL_SIZE] ;
static long called_before = FALSE ;
long i ;
if (! called_before) {
/*
** On the first invocation of this function, set up
** the array of string pointers.
*/
for (i=0; i<RUL_C_MAX_SYMBOL_SIZE; i++) {
short_strings[i][1] = '\0' ;
exploded[i] = &(short_strings[i][0]) ;
}
called_before = TRUE ;
}
/*
** For each character in the input string, create an
** entry in the array of strings to be returned.
*/
*num_returned = strlen(in_string) ;
for (i=0; i<RUL_C_MAX_SYMBOL_SIZE; i++) {
if (i < *num_returned) {
short_strings[i][0] = in_string[i] ;
} else {
short_strings[i][0] = '\0' ;
}
}
return (exploded) ;
}
Example 6-6 RuleWorks Program That Passes a READ-WRITE Parameter
(entry-block main)
(external-routine explode
(alias-for |explode|)
(accepts <string> asciz
<count> long by reference read-write)
(returns <strings>[256] asciz))
(object-class name ^as-word ^as-compound compound)
(on-entry
(make name ^as-word abc)
(make name ^as-word |hello|)))
(rule explode-it
(name ^$id <n-id> ^as-word { <> NIL <word> } ^as-compound [=] 0)
-->
(bind <ret-len> 0)
(bind <ret-list> (explode <word> <ret-len>))
(modify <n-id> ^as-compound (subcompound <ret-list> 1 <ret-len>)))
(end-block main)
The dialog in Example 6-7 shows what happens when you run the RuleWorks program in Example 6-6.
Example 6-7 Passing a READ-WRITE Parameter
RuleWorks> trace on wm
RuleWorks> run 2
<=WM: #2 2 [NIL] (NAME ^AS-WORD hello)
=>WM: #2 3 [EXPLODE-IT] (NAME ^AS-WORD hello ^AS-COMPOUND (COMPOUND h e l l o))
<=WM: #1 1 [NIL] (NAME ^AS-WORD ABC)
=>WM: #1 4 [EXPLODE-IT] (NAME ^AS-WORD ABC ^AS-COMPOUND (COMPOUND A B C))
%RUL-I-PAUSE, Pausing after running requested number of rules
RuleWorks>
Note: that whenever a SYMBOL is converted to a string to be passed to an external routine, the string passed contains the print form of the symbol. Thus, in Example 6-6, the quoted symbol, |hello|, is returned as lowercase letters but the unquoted symbol, abc, is returned as uppercase letters.
Passing Mechanisms
Declaring the passing mechanism for each parameter is optional. If you do not declare a passing mechanism, RuleWorks uses the default appropriate to the external data type of the parameter: BY VALUE for all types exceptstrings, whose default is BY REFERENCE READ-ONLY. The last three columns in Table 6-1 shows the default, supported, and unsupported passing mechanisms for each external data type.
For ACCEPTS arguments, RuleWorks provides three passing mechanisms:
Passes a copy of the value of the argument. Any changes to the argument by the external routine are ignored when the external routine returns.
Passes a pointer to a copy of the argument. Any changes to the argument by the external routine are ignored when the external routine returns.
Passes a pointer to a copy of the argument. In the typical case, where the argument passed BY REFERENCE READ-WRITE is a bound variable, then after the external routine returns the variable is bound to the value written by the external routine.
Normally, external routines with READ-WRITE passing mechanisms are used on the RHS of rules, and each READ-WRITE argument passed is a bound variable. Passing an unbound variable on the RHS, or passing any variable on the LHS, or passing the result of an expression, causes the value set by the external routine to be ignored and generates a compiler warning.
A symbol that you pass out BY REFERENCE READ-WRITE as external type ASCIZ is passed in a buffer of RUL_C_MAX_SYMBOL_SIZE characters. The external routine is free to fill the buffer with a string of up to RUL_C_MAX_SYMBOL_SIZE characters. (Symbols that you pass as ASCID or ASCIZ BY REFERENCE READ-ONLY are only as long as needed to hold the value being passed.)
If the actual parameter is a compound bound to a variable passed BY REFERENCE READ-WRITE, changes made to elements of the array by the external routine will be reflected in the values of the compound variable. The number of elements in the compound value cannot be changed by the external routine.
If the number of atoms in the actual compound value being passed is greater than the number of elements in the array, the excess atoms are not passed and a warning is given. If there are fewer atoms than array elements, the array is padded with a default value for that external type. Numeric external types default to zeros. ASCIZ and ASCID externals default to the zero-length string. The POINTER external type defaults to NULL. The ATOM external type defaults to NIL.
For example, the RuleWorks program in Example 6-6 calls an external function to provide a value for a BIND action. The external function, shown in Example 6-5, takes a symbol and returns an array that contains the characters in that symbol. The external function also returns the number of characters in the array in the READ-WRITE argument <COUNT>.
Some system services require that you omit some BY REFERENCE parameters. An empty pair of parentheses () at the calling site causes 0 to be passed by value, for BY REFERENCE parameters.
You should not depend on the order of evaluation or of side effects that result from evaluation of functions in calls to external routines, or anywhere else. For instance, if Example 6-6 used the following rule, the program would not work:
(rule does-not-explode-it
(name ^$id <n-id> ^as-word { <> NIL <word> } ^as-compound [=] 0)
-->
(bind <ret-len> 0)
(modify <n-id>
^as-compound
(subcompound (explode <word> <ret-len>) 1 <ret-len>)))
To avoid dependence on the order of evaluation of arguments, bind all the argument expressions that include function calls, except the last, before you call the external routine.
Type Changes to ArgumentsWhen variables are passed BY REFERENCE READ-WRITE, two data type conversions are performed. The first conversion is from the RuleWorks atom to the specified external type. The second is from the specified external type to the natural RuleWorks type (see Table 6-3), which may or may not be the original type.
For example, if the variable being passed BY REFERENCE READ-WRITE is bound to an INTEGER atom, and the external type is DOUBLE-FLOAT, the variable is rebound to a FLOAT atom after the routine returns. If the variable being passed BY REFERENCE READ-WRITE is bound to an INSTANCE-ID atom, and the external type is ASCIZ, the variable is rebound to a SYMBOL atom after the routine returns.
If the variable is bound to a compound containing some float atoms and some integer atoms, and the external type is DOUBLE-FLOAT, the variable is bound to a compound containing only float atoms after the routine returns. If the variable is bound to a compound containing some float atoms, some symbol atoms, and some integer atoms, and the external type is ASCID, the variable is bound to a compound containing only symbol atoms after the routine returns.
Visibility of Changes to ArgumentsWhen variables bound on the LHS are passed out BY REFERENCE READ-WRITE on the RHS, the called routine can modify them. However, when the called routine returns, the changes are reflected into the bound variables, but not in the WMO attributes from which those variables were originally set. If the new values need to be reflected back into changes in the object, you must explicitly modify the object.
Returning a Value to RuleWorks
Use the RETURNS clause to declare an external routine as a function that returns a value. The <formal-parameter-name> and the passing-mechanism for the return value are optional; the external-type-name is required.
For the RETURNS clause, only two passing-mechanisms are defined: BY VALUE and BY REFERENCE. No access mechanisms are defined for return values.
RuleWorks automatically converts return values from their external data types to the appropriate RuleWorks data types. The following example shows an EXTERNAL-ROUTINE declaration for the C language library function that finds the length of a string:
(EXTERNAL-ROUTINE strlen
(ACCEPTS <string> ASCIZ)
(RETURNS <length> LONG))
Given this declaration, the following RHS action first converts the symbol CHARLIE into an ASCIZ string by using the symbol's print form, calls the "strlen" function passing the new ASCIZ string, and then converts the LONG return value into a RuleWorks INTEGER:
(modify <the-wmo> ^length (strlen charlie))
Empty brackets are not valid in the RETURNS clause. You must explicitly declare the size of an array that is returned to RuleWorks, as shown in Example 6-6.
When returning a value for which memory must be allocated to store the actual value(s) (that is, a string or an array), the external routine is responsible for both the allocation and deallocation of that memory. In Example 6-5 the external routine declares the memory for the return values as STATIC, thereby allocating once and reusing the same memory each time the routine is invoked.
Using RuleWorks Run-Time Library Routines
RuleWorks provides a library of callable run-time routines that allow you to access working memory from your external routines. Table 6-5 through Table 6-10 list all the RTL routines; detailed descriptions of each routine are provided in Chapter 11.
The RTL actually includes multiple implementations of each routine, one for each NAS binding.
Choosing Bindings
The RuleWorks RTL follows the Network Application Support (NAS) guidelines for a portable programming interface: RuleWorks supplies the VMS calling standard bindings for VMS systems, the C bindings for all platforms, and the f77 bindings for UNIX systems. The following three sections summarize the different bindings.
Note: that all the name changes are done automatically by the compilers involved.
The VMS calling standard bindings are supplied on VMS systems to support the VMS high-level languages (such as VAX FORTRAN and VAX Pascal). These bindings use the VMS Procedure Calling Standard, as follows:
The C bindings are supplied on all platforms to support languages such as C and C++. The C bindings on UNIX systems also support Pascal. The routines in this binding follow C conventions, as listed below:
The f77 bindings are supplied on UNIX systems to support the current f77 calling conventions, as follows:
Declaring RTL Routines
You must declare the RuleWorks RTL routines in the language you are using. On VMS and UNIX platforms, RuleWorks provides a number of include files that contain the necessary declarations (see Table 6-4). On other platforms, the only include file is rul_rtl.h for use with C and C++.
Table 6-4 Include Files Provided by RuleWorks
Language | VMS File | UNIX File |
Ada | RUL$LIBRARY:RUL_RTL.ADA | /usr/lib/cmplrs/rulework/rul_rtl.ada |
BASIC | RUL$LIBRARY:RUL_RTL.BAS | /usr/lib/cmplrs/rulework/rul_rtl.bas |
C | RUL$LIBRARY:RUL_RTL.H | /usr/include/rul_rtl.h |
FORTRAN | RUL$LIBRARY:RUL_RTL | /usr/lib/cmplrs/rulework/rul_rtl.for |
Pascal | RUL$LIBRARY:RUL_RTL.PAS | /usr/include/pascal/rul_rtl.h |
PL/I | RUL$LIBRARY:RUL_RTL.PLI | /usr/lib/cmplrs/rulework/rul_rtl.pli |
BLISS—32 | RUL$LIBRARY:RUL_RTL.R32 | /usr/lib/cmplrs/rulework/rul_rtl.r32 |
For example, if you are interfacing a VAX Pascal program to RuleWorks, you use a %INCLUDE directive to place the RUL_RTL.PAS file in your program as follows:
{ Include RuleWorks routine declarations }
%INCLUDE 'RUL$LIBRARY:RUL_RTL.PAS'
For a VAX BASIC external routine to access the RUL_RTL.BAS declarations, place the following %INCLUDE statement in the routine:
%INCLUDE "RUL_RTL.BAS"
Table 6-5 RTL Routines for Accessing Working Memory
RTL Routine | Description |
rul_get_attr_atom | Returns the value of a scalar attribute. |
rul_get_class_string | Returns the class name of an object. |
rul_get_class_string_length | Returns the number of characters in a class name. |
rul_get_comp_attr_length | Returns the number of elements in a compound attribute value. |
rul_get_comp_attr_string | Returns the read forms of all the values in a compound attribute. |
rul_get_comp_attr_string_len | Returns the number of characters in the read form of a compound attribute value. |
rul_get_comp_elem_atom | Returns the value of a single element of a compound attribute. |
rul_get_instance | Returns the read form of an object. |
rul_get_instance_length | Returns the number of characters in the read form of an object. |
rul_get_next_instance | Allows iteration over working memory. |
Example 6-8 and Example 6-9 accept an INSTANCE-ID and prints that object; makes a new object with rul_make_instance, modifies an attribute with rul_set_attr_string, and prints the result; another new object with rul_copy_instance, modifies it, and prints it; and finally removes the object created with rul_make_instance.
Example 6-8 Changing Working Memory: RuleWorks Program
(entry-block main)
(external-routine mess_with (accepts atom))
(object-class person ^name ^called)
(on-entry
(bind <obj> (make person ^name |George| ^called friend))
(mess_with <obj>) )
(end-block)
Running Example 6-8 produces the following output:
Step 1: (PERSON ^$ID #1 ^NAME |George| ^CALLED FRIEND)
Step 2: (PERSON ^$ID #2 ^NAME |George| ^CALLED |Neighbor|)
Step 3: (PERSON ^$ID #3 ^NAME |George| ^CALLED TROUBLE)
Removed Instance: #2
Example 6-9 Changing Working Memory: C Routine
#include <stdio.h>
#include <rul_rtl.h>
/* Set BUFF_SIZE big enough to store the printform
** of any of our working-memory objects
*/
#define BUFF_SIZE 1000
/* Set ID_BUFF_SIZE big enough for any symbol's printform
*/
#define ID_BUFF_SIZE RUL_C_MAX_SYMBOL_SIZE*2
void MESS_WITH (rul_atom wme_id)
{
char obj_string_buffer[BUFF_SIZE];
char id_string_buffer[ID_BUFF_SIZE];
long b_len;
rul_atom new_wme_id, a_wme_id;
/* Verify that the argument is an instance id */
if (rul_atom_is_instance_id (wme_id)) {
/* Verify that there exists a working memory element
* with the given instance id
*/
if (rul_is_instance (wme_id)) {
/* Print the read form of the working memory object */
b_len = rul_get_instance (obj_string_buffer, BUFF_SIZE, wme_id);
printf ("\n Step 1: %s",obj_string_buffer);
/* Use the object's printform to make a copy */
a_wme_id = rul_make_instance (obj_string_buffer, "");
/* Modify the ^called attribute of the "made" copy */
rul_set_attr_string (a_wme_id, "CALLED", "Neighbor");
/* Print the read form of the "made" copy */
b_len = rul_get_instance (obj_string_buffer, BUFF_SIZE, a_wme_id);
printf ("\n Step 2: %s",obj_string_buffer);
/* Copy the object made above... */
new_wme_id = rul_copy_instance (a_wme_id);
/* Modify the ^called attribute of the copy */
rul_set_attr_string (new_wme_id, "CALLED", "TROUBLE");
/* Print the read form of the "copied" copy */
b_len = rul_get_instance (obj_string_buffer, BUFF_SIZE,
new_wme_id);
printf ("\n Step 3: %s",obj_string_buffer);
/* remove the "made" copy */
rul_atom_to_string (id_string_buffer, ID_BUFF_SIZE, a_wme_id);
if (rul_remove_instance (a_wme_id))
printf ("\n Removed Instance: %s\n", id_string_buffer);
else
printf ("\n Removal FAILED\n");
}
else
printf ("\n INSTANCE-ID not a valid OBJECT\n");
}
else
printf ("\n Atom not an INSTANCE-ID\n");
fflush (stdout);
}
Table 6-6 RTL Routines for Changing Working Memory
RTL Routine | Description |
rul_copy_instance | Creates a new object with the same contents as an existing object. |
rul_end_id_translation | Signals the end of an INSTANCE-ID translation table. |
rul_make_instance | Creates a new object from a string. |
rul_remove_instance | Deletes an object from working memory. |
rul_set_attr_atom | Changes the value of a scalar attribute to an atom. |
rul_set_attr_double | Changes the value of a scalar attribute to a double float. |
rul_set_attr_float | Changes the value of a scalar attribute to a single float. |
rul_set_attr_integer | Changes the value of a scalar attribute to an integer. |
rul_set_attr_string | Changes the value of a scalar attribute to a string. |
rul_set_comp_attr_string | Changes the value of an entire compound attribute to the values extracted from a single string. |
rul_set_comp_elem_atom | Changes the value of a single element of a compound attribute to an atom. |
rul_set_comp_elem_double | Changes the value of a single element of a compound attribute to a double-precision floating-point number. |
rul_set_comp_elem_float | Changes the value of a single element of a compound attribute to a single-precision floating-point number. |
rul_set_comp_elem_integer | Changes the value of a single element of a compound attribute to an integer. |
rul_set_comp_elem_string | Changes the value of a single element of a compound attribute to a string. |
rul_specialize_instance | Changes an instance of a parent class to an instance of a subclass. |
rul_start_id_translation | Signals the creation of an INSTANCE-ID translation table. |
Note: It may be necessary to call the appropriate declaration block before calling RTL routines that test declarations or that create WMOs, to ensure that the object classes have been initialized.
Table 6-7 RTL Routines for Testing Declarations
RTL Routine | Description |
rul_attr_is_compound | Indicates whether an attribute is compound or scalar. |
rul_is_attribute | Indicates whether an attribute is declared in the specified object class. |
rul_is_class | Indicates whether an object class with the specified name has been declared. |
rul_is_subclass | Indicates whether one object class inherits from another. |
Table 6-8 RTL Routines for Testing Values
RTL Routine | Description |
rul_atom_is_compound | Indicates whether a value is compound or scalar. |
rul_atom_is_fatom | Indicates whether a value is a FLOAT atom. |
rul_atom_is_iatom | Indicates whether a value is an INTEGER atom. |
rul_atom_is_instance_id | Indicates whether a value is an INSTANCE-ID atom. |
rul_atom_is_symbol | Indicates whether a value is a SYMBOL atom. |
rul_is_instance | Indicates whether an object that corresponds to the specified INSTANCE-ID exists in working memory. |
Example 6-11 is a C routine that displays the read form, print form, and type of arbitrary atoms created by the RuleWorks program in Example 6-10.
Example 6-10 Testing and Converting Values: RuleWorks Program
(entry-block main)
(object-class foo ^bar)
(external-routine which_type_is_this (accepts atom))
(on-entry
(make foo ^bar 1234)
(make foo ^bar 43.21)
(make foo ^bar |a symbol|)
(run))
(rule any-atom
(foo ^bar <x>)
-->
(which_type_is_this <x>))
(rule instance-id-atom
(foo ^$id <x> ^bar 1234)
-->
(which_type_is_this <x>))
(end-block main)
Example 6-11 Testing and Converting Values: C Routine
#include <stdio.h>
#include <rul_rtl.h>
void WHICH_TYPE_IS_THIS (rul_atom atom_value)
{
char tmp[RUL_C_MAX_SYMBOL_SIZE+1];
long len;
/* Get the read form of the given atom */
rul_atom_to_string (tmp, RUL_C_MAX_SYMBOL_SIZE+1, atom_value);
printf ("\n\n Atom has read form = '%s'", tmp);
/* Print out type and value */
if (rul_atom_is_iatom(atom_value)) {
printf ("\n Atom is of type INTEGER");
printf ("\n Atom has value = %12d",
rul_iatom_to_integer (atom_value));
}
else if (rul_atom_is_fatom(atom_value)) {
printf ("\n Atom is of type FLOAT");
printf ("\n Atom has value = %12.4f", rul_fatom_to_float (atom_value));
}
else if (rul_atom_is_symbol(atom_value)) {
printf ("\n Atom is of type SYMBOL");
len = rul_symbol_to_string (tmp,
RUL_C_MAX_SYMBOL_SIZE+1,
atom_value) ;
printf ("\n Atom has print form = '%s'", tmp);
}
else if (rul_atom_is_instance_id(atom_value)) {
printf ("\n Atom is of type INSTANCE_ID");
}
else {
printf ("\n Atom is of unknown type");
}
fflush (stdout);
}
Running Example 6-10 produces the following output:
Atom has read form = '|a symbol|'
Atom is of type SYMBOL
Atom has print form = 'a symbol'
Atom has read form = '43.21'
Atom is of type FLOAT
Atom has value = 43.2100
Atom has read form = '#1'
Atom is of type INSTANCE_ID
Atom has read form = '1234'
Atom is of type INTEGER
Atom has value = 1234
Table 6-9 RTL Routines for Converting Values
RTL Routine | Description |
rul_atom_to_string | Converts an atom to a string. |
rul_atom_to_string_length | Returns the number of characters in the string representation of an atom. |
rul_double_to_fatom | Converts a double-precision floating-point number into a FLOAT atom. |
rul_fatom_to_double | Converts a FLOAT atom into a double-precision floating-point number. |
rul_fatom_to_float | Converts a FLOAT atom into a single-precision floating-point number. |
rul_float_to_fatom | Converts a single-precision floating-point number into a FLOAT atom. |
rul_genint | Generates a new INTEGER atom. |
rul_gensym | Generates a new SYMBOL atom with the prefix G:. |
rul_gensymp | Generates a new SYMBOL atom, with an optional prefix. |
rul_iatom_to_integer | Converts an INTEGER atom into an integer. |
rul_integer_to_iatom | Converts an integer into an INTEGER atom. |
rul_string_to_atom | Converts the first token of a string into an atom. |
rul_string_to_symbol | Converts a character string into a SYMBOL atom. |
rul_symbol_to_string | Converts a SYMBOL atom into a character string. |
Table 6-10 RTL Routines for Controlling RuleWorks Execution
RTL Routine | Description |
rul_debug | Invokes the RuleWorks command interpreter. |
rul_get_firing_rule | Identifies the rule that the RuleWorks run-time system is currently executing. |
Strings, Read Forms, and Print Forms
The RTL routines listed in the first column of Table 6-11 parse strings passed to them using the same semantics as the RuleWorks reader. That is, their input should be a read form not a print form. In general, any RTL routine that accepts or returns more than one atom in a string uses read forms. RTL routines that use strings to pass a single atom use a print form if the type of the atom is known; if the type of the atom is not known the routines use a read form.
Table 6-11 RTL Routines That Accept or Return Read Forms
Accept | Return |
rul_make_instance | rul_get_instance |
rul_set_comp_attr_string | rul_get_comp_attr_string |
rul_string_to_atom | rul_atom_to_string |
Because the print form of a symbol can be different from its read form, passing a print form to a routine that expects a read form can cause unexpected results. The following C code creates two different RuleWorks atoms, one whose print form is abc and one whose print form is ABC. That is, my_atom is not equal to an_atom.
#include <rul_rtl.h>
...
rul_atom my_atom, an_atom;
long len;
char buffer[RUL_C_MAX_SYMBOL_SIZE+1];
my_atom = rul_string_to_atom ("|abc|");
len = rul_symbol_to_string (&buffer, RUL_C_MAX_SYMBOL_SIZE+1, my_atom);
an_atom = rul_string_to_atom (&buffer);
The RTL routines listed in the second column of Table 6-11 return read forms, not print forms. This allows your external routine to use the string representations of RuleWorks objects.
Handling an Interrupt
RuleWorks programs can get information from external sources, such as timers and I/O devices, by calling system routines that let the programs request that they be interrupted when particular events occur. An interrupt is called an asynchronous system trap (AST) on VMS systems and a signal on UNIX systems. The system routine provides a transfer of control to a user-specified procedure that handles the event.
When a program calls a system routine, it typically specifies the event handler as one of the arguments. The calling program then continues to run until an event of the appropriate type occurs. When the event occurs, the operating system interrupts the calling program by immediately passing control to the event handler. When the event handler finishes, the program continues from the point where it was interrupted.
Normally, the event handler examines the event received, possibly updates the program's data, and then returns control to the program at the point the interruption occurred.
CAUTION: In a RuleWorks program the data (working memory) can be updated at any point in the recognize-act cycle, but not from interrupt level. Therefore, interrupts have to be "synchronized". An event handler cannot call RuleWorks to alter working memory. Instead the event handler should modify some external reentrant data structure. Another external routine that protects itself from interrupts and knows how to poll that external data structure should be called periodically, for example, from inside an ON-EVERY construct.
In summary, you must take the following steps for your RuleWorks program to communicate with asynchronous or interrupt level external sources:
Summary of Restrictions
This section lists the restrictions on using RuleWorks with other languages.
On the Left-Hand Side:
Anywhere in a Rule:
In General: