RuleWorks

Program and Data Modularity

RuleWorks makes possible explicit partitioning of rules and objects into modular systems or subsystems. The basic unit of modularity is the block; RuleWorks provides three block constructs:

 

The most important type of block is the entry block. The entry block allows a rule-based routine to be called like a routine in any other language.

The purpose of declaration and rule blocks is to allow controlled sharing of information. Declaration blocks enable declarations and the objects described by them to be shared by multiple entry or rule blocks with the USES clause (see Example 5-1). Rule blocks enable rules to be shared among multiple entry blocks with the ACTIVATES clause. Except for this sharing, the contents of one block are not visible to other blocks.

Each block begins with a block construct that defines the block name, and ends with an END-BLOCK construct. A block is said to contain all the constructs between its block construct and its END-BLOCK construct. All non-block RuleWorks constructs must be contained in a block.

 

Table 5-1. Summary of Block Constructs

Entry
Block
Rule
Block
Declaration
Block
Can be called from other languagestruefalsetrue, but*
Name is visible to linkertruetruetrue
Can contain "ON-" statementstruefalsefalse
Can contain rules and catcherstruetruefalse
Can contain declarationstruetruetrue
Contents can be shared at compile timefalsefalsetrue

* A declaration block can be called, but only for the purpose of initializing working memory and object classes before calling API routines that affect working memory.

Entry Blocks

A RuleWorks entry block generates a C-callable entry point. An entry block is visible to your system linker and is callable by any language that adheres to the target system's calling standard conventions. An entry block can accept arguments and return a value. You can think of an entry block as a rule-based subroutine or "mini-expert."

At least one entry block is required for each RuleWorks program.

Only one entry block can be running at any given time. The entry block that is currently running is called the active entry block. Only rules contained in or activated by the active entry block can execute. Using entry blocks to divide your program into modules can therefore improve program performance, because the size of the match network and the conflict set are reduced.

Declaring An Entry Block

An entry block is declared by the keyword ENTRY-BLOCK followed by the name of the entry block. The entry block name must contain only letters, numbers, and underscores, and must be no longer than 31 characters.

The complete syntax of an entry block is shown below:

Figure 5-1. Entry Block Syntax

The clauses within the ENTRY-BLOCK declaration are all optional:

An entry block may contain any RuleWorks program constructs except another block construct, but they must be in the order shown above: declarations first, then ON- statements, then rules and catchers.

If an entry block contains OBJECT-CLASS declarations, objects of those classes can be matched only by rules contained in the block. If an entry block contains rules, those rules can fire only when the block is active. Declaration blocks and rule blocks allow you to share objects and rules among multiple entry blocks.

The END-BLOCK construct is required. The entry-block-name is optional inside the END-BLOCK construct, but if it is present the compiler verifies that it is the same name as in the ENTRY-BLOCK construct.

Calling an Entry Block

When an entry block is called, that entry block is the only active entry block. The entry block first runs its ON-ENTRY actions (if any), then runs recognize-act cycles until one of the following occurs:

Entry blocks can call other entry blocks, and even call themselves recursively. When an entry block is called, the caller is no longer active. The caller is referred to as being suspended and the called block becomes the active block, just as with a routine in any other language. Similarly, when the called block returns, the caller becomes active again.

When an entry block returns, any objects created or changed by that block remain in working memory (unless that entry block is the main program, see Chapter 5, Naming an Entry Block "Main", for details). If that entry block is called again later, all of those objects are again matchable. However, the objects cannot be matched or modified by any other entry block unless both entry blocks are using the same declaration block (see Chapter 5, Declaration Blocks, for information on declaration blocks).

Scope of Arguments to an Entry Block

The arguments received by an entry block are visible only to actions within the ON-ENTRY, ON-EVERY, ON-EMPTY, and ON-EXIT statements inside the entry block. They are not visible to rules contained in or activated by the entry block. If rules need to match input arguments, their values must be placed into one or more objects (as shown in Example 5-1)

Example 5-1. A Simple Entry Block and Declaration Block

 

The same restrictions hold true of any variables bound in an ON- clause. Such variables are also visible within any ON- clause.

Scope of Execution of Entry Blocks

A call frame is created when a RuleWorks entry block is called. It contains all the dynamic data structures associated with a given invocation of an entry block. That is, it consists of all of the information "local" to this particular invocation of an entry block, or in some way visible to this particular invocation. Calls and returns really create and delete call frames. Call frames are an integral piece of entry blocks; by themselves they have no names and cannot be passed, returned, or otherwise manipulated directly.

The following are local to the call frame:

The following are global:

Naming an Entry Block "Main"

An entry block named MAIN, if supplied, is automatically designated to the compiler and linker as the main RuleWorks routine. This design has semantic parallelism with the C language and provides the behavior most programmers would expect. The name can be in any case.

If you want to capture command-line arguments to the program in a portable way, a declaration in the following form is recommended:

 

Returning a Value from an Entry Block

RuleWorks provides an RHS action, RETURN that stops the firing of rules in the active entry block, executes the ON-EXIT actions (if any) and passes control back to the caller of the entry block. This action has an optional argument, the value to be returned. The argument can be any expression. Thus, the RETURN action is useful for returning a condition code or value (see Example 5-1).

The RETURN action is valid anywhere in the entry block, even inside the ON-EXIT and ON-EMPTY statements. The RETURN action is valid only in an entry block; it is not valid in a rule block.

Executing more than one RETURN action in an entry block is possible, for example, when an ON-EXIT statement that contains a RETURN action is executed as a result of a RETURN action in a rule. If a value is being returned from the entry block, the value of the last RETURN action executed is used.

If the value returned by an entry block is an array, the memory allocated for that array is not freed by RuleWorks. Example 5-2 shows an entry block that accepts an array, sorts it, and then returns it.

Example 5-2. Passing and Returning an Array

 

Executing Actions Without Matching

The actions on the right-hand side of a rule are executed only when the left-hand side matches working memory and the resulting instantiation is picked during conflict resolution. RuleWorks provides two types of executable constructs whose actions are executed without matching working memory: "ON-" statements and catchers.

Using "ON-" Statements

RuleWorks entry blocks may contain one each of the four "ON-" statements, which allow you to describe a set of actions that are executed at certain points in the recognize-act cycle (see the figure, "ON-" Statements and the Recognize-Act Cycle) without matching any objects in working memory. These new statements are all defined with names that begin with the prefix "ON-" and end with the name of the special condition under which their associated actions are executed.

Figure 5-2. "ON-" Statements and the Recognize-Act Cycle

The "ON-" statements are listed below:

 

 

 

"ON-" statements must be contained in an entry block. They cannot appear inside a rule block, nor inside a rule group within an entry block (see Rule Blocks and Rule Groups for more information).

An entry block can contain at most one of each type of "ON-" statement. It doesn't have to contain any of them.

Using a Catcher

A catcher is a list of actions that are executed after a specified number of recognize-act cycles have been executed. For example, if program execution is unattended, as in a batch job, a catcher can halt the program if it does not produce results within a specified limit.

You define a catcher with a CATCH statement, which includes a symbol and one or more actions. The symbol names the catcher, and functions as a label. A catcher's name must be unique; that is, it cannot be the same as the name of another catcher, rule, or rule group in the program. When the catcher fires, the actions are executed.

The following CATCH statement defines a catcher named FINISH, which consists of two actions, WRITE and HALT:

You enable a catcher with the AFTER action, which tells the run-time system when to execute the catcher. Specify the AFTER action with a positive integer and the name of the catcher you want to enable. The integer indicates the number of recognize-act cycles (of the current invocation of the entry block) that the run-time system is to execute before executing the specified catcher. For example:

Only one catcher can be enabled at a time, per call frame. Therefore, when you enable a catcher, you disable the catcher currently enabled (if any). Catchers are automatically disabled after they have been executed.

Catchers may be contained in either entry or rule blocks. The catcher must be contained in the same block as the AFTER action that enables it.

Example 5-3 illustrates the use of two catchers, STARTER and FINISH.

Example 5-3. A Program That Loops

This program produces the following output:

Example 5-4. A Program that Loops Output

 

STARTER and FINISH are used in Example 5-3 as follows:

(1) The first recognize-act cycle fires rule INITIALIZE, because its CE matches the START object created in the ON-ENTRY statement. The ON-ENTRY statement does not count as a cycle.

(2) Catcher STARTER fires after one recognize-act cycle has been executed. STARTER is enabled in the ON-ENTRY statement.

(3) The MAKE action in catcher STARTER creates an object on which rule COUNT can fire.

(4) The AFTER action in catcher STARTER enables catcher FINISH to fire after ten more recognize-act cycles have been executed.

 

Declaration Blocks

Declarations (OBJECT-CLASS and EXTERNAL-ROUTINE) can be private or shareable. A declaration is private if it is contained in either an entry block or a rule block. Objects whose class declaration is private to a block can be matched only by rules contained in that block. In other words, by placing declarations inside an entry block or rule block you create private data for that block. Similarly, external routines whose declarations are local to a block can be called from inside that block only.

Figure 5-3 shows a RuleWorks program that consists of one entry block with two private object class declarations. Rules in EB1 can "see" all objects of classes Y and Z.

Figure 5-3. Private Data in RuleWorks

Data Partitioning

By default, RuleWorks partitions working memory so there is no conflict over object classes of the same name when two or more entry blocks are combined. Rules can match only objects whose classes are contained in or used by their entry block; all other objects are invisible.

This invisibility includes matches against the built-in class $ROOT. If an object class is not visible at compile-time, instances of it are not visible to the block at run-time.

Declaration Sharing

A declaration block allows you to create a collection of declarations that are shareable among multiple entry blocks or rule blocks. Declaration sharing allows you to explicitly decide which data should remain private and which should be shared (and the extent of that sharing). This allows the absolute partitioning of object class declarations between several independently-developed subsystems of an application. It also allows information to be restricted to a single routine or a set of interdependent routines.

A declaration block consists of zero or more declarations bounded by a DECLARATION-BLOCK construct at the top and an END-BLOCK construct at the bottom.

Example 5-5. DECLARATION-BLOCK Sharing

The complete syntax of a declaration block is shown below:

The decl-block-name is required in the DECLARATION-BLOCK construct. It is optional in the END-BLOCK construct, but if supplied it is checked. Declaration block names must be no longer than 31 characters, and contain letters, digits, and underscores only. Declaration block names must be distinct from entry and rule block names. Finally, the first eight characters of all declaration block names used in a program must be unique. This allows RuleWorks to create portable names for the compiled files. For example, having two declaration blocks named DECLARE_CONTROL and DECLARE_KIWI generates a compile-time warning and results in a single file called DECLARE_.USE. Naming the blocks CONTROL_DECLS and KIWI_DECLS correctly generates two .USE files.

A declaration block must not contain any executable statements (rules, "ON-" statements, or catchers).

Declarations are shared via the USES clause of an ENTRY-BLOCK or RULE-BLOCK construct. Objects whose class declarations are shared by a block are just as visible to the rules within that block as objects whose declarations are private to that block. Note that a USES clause cannot specify individual class names, only declaration block names.

A block can use more than one declaration block. A compile-time error occurs if the combined shared and private declarations contain any classes with identical names.

Figure 5-4 shows some private and some used object class declarations. The USES clause in EB1 "pulls in" the declarations from DB1. Rules in EB1 can still match objects of classes Y and Z.

Figure 5-4. Shareable Declaration Blocks

Figure 5-5 shows two entry blocks in the same program, each with some private and some used object class declarations. Rules in EB1 can see objects of classes Y and Z only; rules in EB2 can see objects of classes W and X only.

Figure 5-5. Two Shareable Declaration Blocks

Figure 5-6 shows the same two entry blocks sharing an object class declaration. Rules in EB1can see objects of classes Y, Z, and O; rules in EB2 can see objects of classes W, X, and O.

Figure 5-6. Shared Data in RuleWorks

The declaration block(s) used by an entry block must be compiled before the entry block itself can be compiled. You can put declaration blocks in a different file and compile them separately, or you can place them in the same file but above the entry block. In either case, compiling a declaration block results in an intermediate file with the extension .USE. Entry or rule blocks in other source files can subsequently use one or more of those declaration blocks, without seeing all of the other declarations that were in the original source file.

Example 5-6 shows a more complex set of block constructs where both declarations and rules are being shared.

Example 5-6. Sharing Declarations and Rules

 

Calling a Declaration Block

Declaration blocks are callable, and in certain circumstances it may be necessary to call one. For example, the following C program calls an entry block named KBT_RULES that uses a declaration block named KBT_DECL. In order for the C program to initialize working memory before calling the entry block, it must first call the declaration block:

Example 5-7. Calling a Declaration Block

 

Rule Blocks

In RuleWorks, rules can be gathered together into rule blocks. A rule block is a collection of rules that may be shared among several entry blocks. Whenever any of the entry blocks is called, all the rules in the rule blocks it activates will participate in matching and be enabled to fire.

Rule blocks can also be used when the number of rules in a single entry block becomes too large to reasonably store in a single file. You can have rule blocks that are activated by only one entry block.

The complete syntax of a rule block is shown in the following figure.

Figure 5-7. Complete Syntax of Rule Block

Rule blocks are activated by entry blocks with the ACTIVATES clause. Only rules contained in or activated by the active entry block are eligible for matching and firing. Only entry blocks can activate rule blocks; one rule block can neither contain nor activate another.

The rule-block-name is required in the RULE-BLOCK construct. It is optional in the END-BLOCK construct, but if supplied it is checked. Rule block names must be no longer than 31 characters, and contain letters, digits and underscores only. Rule block names must be distinct from entry and declaration block names.

Each rule block can have it’s own STRATEGY clause. However, all rule blocks used by an entry block must have the same strategy as the entry block. It is a run-time error to activate rule blocks that have different strategies. If no strategy clause is specified, the default is MEA.

A rule cannot be in more than one rule block; a rule block can contain zero or more rules. (An empty rule block can be useful during prototyping and/or stuibbing phase of development). Note that all rules contained in a block must be in the same file, but a file can contain more than one block. Rule blocks can be compiled before or after the entry block that activates them.

 

Rule blocks are activated by entry blocks by the ACTIVATES clause (see Entry Blocks). Only rules contained in or activated by the active entry block are eligible for matching and firing. Only entry blocks can activate rule blocks; one rule block can neither contain nor activate another.

The visibility of objects to the active rules depends on whether their blocks contain or use the corresponding OBJECT-CLASS declarations. When you put rules in rule blocks, it is up to you to set up declaration blocks in such a way that the classes that are to be matched and modified in your entry and rule blocks are shared as appropriate. There is no implicit sharing of declarations between an entry block and the rule blocks it activates. Thus, in the example, Sharing Declarations and Rules, the clause is required in the rule block as well as in the entry blocks.

Scope of Names

In RuleWorks, the name space for declarations and executable statements is not global. This name space is divided by blocks into independent name spaces.

Rule Groups

Within an entry or rule block, an additional level of structure can be imposed on a collection of rules by using the RULE-GROUP construct. This extra level is not necessary for program execution, but it can enable some useful debugging information.

Efficiency Issues

The entry block system in RuleWorks may cause a program speed increase because it restricts the visibility of rules and objects to what you specified, rather than the global visibility of OPS5.

Only those programs that actually use the block system will see the efficiency improvement. Programs that are converted from OPS5 by wrapping a single entry block around all of the rules will not see this improvement.

The entry block system may impose an efficiency penalty when entry blocks are called repeatedly. To avoid this problem, you should compile any entry block that is called repeatedly with the Optimize qualifier set to REINVOCATION. Note: that the RuleWorks language semantics are not affected by this qualifier, only entry block initialization run-time and maximum memory usage.

Partitioning working memory with declaration blocks will, if done appropriately, provide a significant improvement in execution speed.