RuleWorks

RuleWorks

Rules' Right-Hand Sides: Actions

The right-hand side (RHS) is the "then" part of a rule. It consists of one or more actions. You can use the actions supplied by RuleWorks to perform the operations listed in the table below (each operation is listed with the constructs used to perform the operation).

Table 4-1: RuleWorks Actions

OperationAction
Bind variablesBIND action
Modify working memory MAKE action
COPY action
SPECIALIZE action
MODIFY action
REMOVE action
REMOVE_EVERY action
Work with compound valuesFOR_EACH action
Open and close filesOPENFILE action
CLOSEFILE action
Read input and write outputDEFAULT action
WRITE action
Use a catcherAFTER action
Save and restore the state of working memory and the conflict set SAVESTATE action
ADDSTATE action
RESTORESTATE action
Return control to the calling moduleRETURN action
Invoke the RuleWorks command interpreterDEBUG action
Control the flow of program executionIF . THEN . ELSE action
WHILE . DO . action
Stop program executionQUIT action

The first section of this chapter describes the format of RHS actions. The rest of this chapter explains how to perform the operations listed in the table above. You can also use external routines as actions on the RHS.

The Format of RHS Actions

An RHS action consists of a left parenthesis, an action name and its arguments, and a right parenthesis. The format for specifying an action is shown below:

( action-name [argument] ...)

For example:

    (write |This text will be printed.|)

Most RuleWorks actions require at least one argument. You can represent argument values with constants, bound variables, numeric expressions, or function calls. For example, the WRITE action above has one argument, a quoted symbolic atom.

    Note: If an action does not require arguments, you must still enclose the action name in parentheses.

Actions are allowed in rules, catchers, and in the ON- statements.

Most RHS actions have no return value. The MAKE, MODIFY, COPY, and SPECIALIZE actions do return values, and these actions can be used as functions on the RHS.

 

Binding Variables

Use the BIND action to bind a variable to a value. When you specify the BIND action with a variable and a right-hand-side expression, the run-time system evaluates the expression and binds the variable to the result. For example:

    (bind <x> (<x> + 1))

The run-time system evaluates the expression (<X> + 1) and binds the variable <Y> to the result.

The following action is from the example. It shows the result of a MAKE action being used in a BIND action:

    (bind <new-object>

      (make example-object ^next <first-object> ^last <INSTANCE-ID> ^value (<val> + 1)))

 

Changing Working Memory

A RuleWorks program can modify the contents of working memory during execution. RuleWorks provides actions that create new objects, change existing objects, and delete objects.

Creating Working-Memory Objects

You can create new objects with either the MAKE or the COPY action. The MAKE action creates a new object of the class you specify. The COPY action creates a new object of the same class as the original object. You can also specify values for attributes with both the MAKE and COPY actions.

Using the MAKE Action

Use the MAKE action to create an object. You must specify a declared object class name as the first argument to the MAKE action. After the class name you can specify zero or more attribute-value pairs.

For example, the following MAKE action creates an object of class CONTEXT to initialize working memory:

    (ON-ENTRY

      (make context ^name tasks-to-do))

This object is displayed as follows:

    #1 1 [NIL] (CONTEXT ^NAME TASKS-TO-DO)

(The output above was produced by the WM command.)

If you do not specify attribute names and values in a MAKE action, the run-time system creates an object with default values for the unspecified attributes. If the attribute has no default value, the run-time system uses the atom NIL for scalar attributes and the empty list, (COMPOUND), for compound attributes.

Consider the following rule:

Example: 4-4 Rule

    (rule convert-packages-to-parts:home-kiwi

      (active-context ^name convert-packages-to-parts)

      (input-thing ^item home-kiwi ^$ID <my-input-thing>)

      -->

      (remove <my-input-thing>)

      (make box)

      (make kiwicalc))

 

Neither MAKE action in this rule specifies any attribute values. However, classes BOX and KIWICALC both have inherited default values. Therefore, the objects created by this rule do have some attributes with non-NIL values in them. They would be displayed as shown below:

    #28 33 [CONVERT-PACKAGES-TO-PARTS:HOME-KIWI] (BOX ^IS-EXPANDED NO)

    #29 34 [CONVERT-PACKAGES-TO-PARTS:HOME-KIWI] (KIWICALC ^IS-EXPANDED NO ^MEDIA-TYPE FD-35)

(See Figure 2-1 for an illustration of the class inheritance hierarchy of the sample configuration program, KIWI.RUL.)

The following example shows a MAKE action using the GET function to reference an attribute value indirectly:

Example: 4-2 MAKE Action using the GET Function

    (OBJECT-CLASS fruit

      ^fruit-name

      ^color)

    (rule make-a-similar-one

      (fruit ^$ID <The-fruit> ^fruit-name apple)

      -->

      (MAKE fruit ^fruit-name apple ^color (GET <The-fruit> ^color)))

 

Using the COPY Action

Use the COPY action to duplicate, or duplicate and modify, an existing object. The first argument to the COPY action must be bound to a value of type INSTANCE-ID. The COPY action creates a new object of the same class as the one that was matched. After the $ID variable, you can specify zero or more attributes and their values.

If you do not specify attribute names and values in a COPY action, the run-time system uses the values in the object that is being copied, and you get an exact duplicate (except for the INSTANCE-ID and the time-tag). For example, if the following rule:

    (rule copy-context

      (context ^$ID <The-context>)

      -->

      (copy <The-context>))

fires on the object made in Using the MAKE Action, the result is the following object:

    #2 2 [COPY-CONTEXT] (CONTEXT ^NAME TASKS-TO-DO)

Changing the Class of a Working-Memory Object

To convert an instance of a parent class to an instance of a descendent class, use the SPECIALIZE action. By default, this action does not change any attribute except for ^$INSTANCE-OF. You can specify attributes if you want.

The following rule shows one possible use of the SPECIALIZE action:

Example 4-3: Using the SPECIALIZE Action

    (rule specialize:os

      (active-context ^name specialize-operating-system)

      (os ^$ID <os-id> ^name kiwos)

      -(kiwos)

      -->

      (specialize <os-id> kiwos ^name |KIWOS Operating System|)

      )

Changing the Values in Working-Memory Objects

To change values in an object, use the MODIFY action with a value of type INSTANCE-ID. You can use any number of attribute-value pairs in a MODIFY action, even zero. The changed object retains its old values for all other attributes; only the attributes you specify have their values replaced.

    Note: When you modify an object, its time tag changes. However, its INSTANCE-ID remains the same.

You can have more than one MODIFY action on a RHS; the changes are applied in the specified order.

Suppose working memory contains the following BOX object:

    #29 35 [CONVERT-PACKAGES-TO-PARTS:BUSINESS-KIWI] (BOX ^IS-EXPANDED NO)

and the following rule fires on it:

Example 4-4: Rule Expand-Part-Skeleton

    (rule expand-part-skeletons:box

    (active-context ^name expand-part-skeletons)

    (box ^$ID <the-part> ^is-expanded NO)

    -->

    (modify <the-part>

      ^is-expanded YES

      ^partnumber KI-9200

      ^name |Kiwi-9200 CPU Base Unit|

      ^price 999.95))

After the rule fires, the BOX object is changed to:

    #29 68 [EXPAND-PART-SKELETONS:BOX] (BOX ^PARTNUMBER KI-9200 ^NAME Kiwi-9200 CPU Base Unit ^PRICE 999.95 ^IS-EXPANDED YES)

Deleting Objects from Working Memory

The REMOVE action deletes objects from working memory. You use REMOVE with one or more values of type INSTANCE-ID that indicate the WMOs to be deleted.

For example, the following rule tallies up single item costs into a final total. This rule modifies the TOTAL-COST object and deletes each ITEM-COST object as soon as its cost is added to the total:

Example 4-5: Tallying Up Single Item Costs

    (rule sum-total-cost:sum

    (active-context ^name sum-total-cost)

    (total-cost ^$ID <the-total> ^cost <val>)

    (item-cost ^cost <the-cost> ^$ID <the-single-item-cost>)

    -->

    (modify <the-total> ^cost ( <val> + <the-cost>))

    (remove <the-single-item-cost>))

The REMOVE-EVERY action deletes all the working-memory objects that are instances of a specified visible class or its subclasses. For example, the following ON-EXIT statement:

    (on-exit

      (remove-every $ROOT))

deletes all visible WMOs as the entry block exits.

 

Working with Compound Values

The same syntax for matching and binding compound attributes is used for modifying compound attributes.

The table below lists the RuleWorks functions that can be applied to compound values on the RHS.

Table 4-2: Functions for Compound Values

FunctionDescription
COMPOUNDReturns a new compound value from zero or more elements
LENGTHReturns the number of elements in a compound value
NTHReturns the value of the specified element
POSITIONReturns the index position of an element in a compound value
SUBCOMPOUNDReturns a compound value containing the specified subrange of a compound value (RHS only)

 

Modifying a Single Element

Changing the value of a single element of a compound attribute is called element-wise modification. You can change a single element and leave all other elements untouched. For example, the following MODIFY action replaces the first element of ^CARD-IN-SLOT but does not affect any other elements:

    (modify <the-box> ^card-in-slot[1] memory)

If the index expression used in an element-wise modification is greater than the current length of the compound value, elements other than the one specified may be changed. The specified element is set to the new value, as expected. In addition, all elements between the original end of the compound attribute and the new value receive the FILL value declared for that attribute (or NIL, if none was declared).

For example, if the ^CARD-IN-SLOT attribute has the following value:

    (compound memory memory keyboard fd-35)

Then the following action:

    (modify <The-box> ^card-in-slot[7] hd-30)

sets the seventh element of ^CARD-IN-SLOT to the value HD-30, and sets the previously-untouched elements five and six to NIL, because no FILL value is declared for ^CARD-IN-SLOT. The new compound value of ^CARD-IN-SLOT is thus:

    (compound memory memory keyboard fd-35 nil nil hd-30)

Modifying the Entire Compound Value

If you want to modify a compound attribute so that it has fewer elements than before, you must replace the entire attribute value. Typically this is done using the SUBCOMPOUND or the COMPOUND function to create an entirely new compound value. Continuing with the ^CARD-IN-SLOT example above, the following action:

    (modify <The-box> ^card-in-slot (compound memory memory))

has the effect of deleting all but the first two elements.

Using Common Idioms for Compound Values

Compound values are frequently used to represent stacks and queues. This section shows the RuleWorks idioms for common operations on stacks and queues.

Adding an Element to the Beginning of a Compound Attribute (Push)

To push a new value onto a stack, create a new compound value from the new scalar value plus the current compound value. For example:

    (agenda ^$ID <The-agenda> ^tasks <task-list>)

    -->

    (MODIFY <The-agenda> ^tasks (COMPOUND start-up <task-list>))

Removing an Element from the Beginning of a Compound Attribute (Pop)

To pop a value off a stack, replace the original compound attribute with a modified version that consists of elements 2 through the end. For example:

    (agenda ^$ID <The-agenda>)

    -->

    (MODIFY <The-agenda> ^tasks

      (GET <The-agenda> ^tasks[2..$LAST]))

You could also use the SUBCOMPOUND function to pop a stack:

    (agenda ^$ID <The-agenda> ^tasks <task-list>)

    -->

    (MODIFY <The-agenda> ^tasks

      (SUBCOMPOUND <task-list> 2 $LAST))

Adding an Element to the End of a Compound Attribute (Append)

To append a value to the end of a queue, create a new compound value from the current compound value plus the new scalar value. For example:

    (agenda ^$ID <The-agenda> ^tasks <tasks>)

    -->

    (MODIFY <The-agenda> ^tasks (COMPOUND <tasks> shut-down))

Splicing A Compound Attribute

The following example removes a particular value from a compound attribute and advances the remaining elements to close the gap.

Example 4-6: Splicing a Compound Attribute

    (agenda ^$ID <Agenda-wme> ^tasks {<tasks> [+] <value>}) ;assume <value> is bound previously

    -->

    (bind <p> (POSITION <tasks> <value>)) ; get the index of <value>

    (MODIFY <Agenda-wme> ^tasks

      (COMPOUND (SUBCOMPOUND <tasks> 1 (<p> - 1))

      (SUBCOMPOUND <tasks> (<p> + 1) $LAST)))

 

Joining Compound Attributes

The following example joins two compound values:

Example 4-7: Joining Compound Attributes

    (agenda ^$ID <Agenda-1> ^tasks <list-1>)

    (agenda ^$ID { <Agenda-2> <> <Agenda-1> } ^tasks <list-2>)

    -->

    (MODIFY <Agenda-2> ^tasks

      (COMPOUND <list-1> <list-2>))

 

Iterating Through a Compound Value

You can use the FOR-EACH action to write a loop that executes once for each element in a compound value. The FOR-EACH action can include any number of RHS actions. Using the FOR-EACH action is more efficient than executing a rule multiple times to achieve the same result.

For example, the following rule can be added to the program in the example, Using $ID Variables as Pointers, to reverse the list:

Example 4-8: Reversing the List

    (rule reverse-compound

      (context ^name reverse)

      (list-pointer ^$ID <list-object>

      ^list-ids <ids>)

      -->

      (bind <new-list> (compound))

      (for-each <id> in <ids>

        (bind <new-list> (compound <id> <new-list>)))

        (modify <list-object> ^list-ids <new-list>))

 

Performing Input and Output Operations

RuleWorks programs can read input from and write output to a terminal or a file. You can use RuleWorks actions to:

  • Open files
  • Set the default input source and output destination
  • Read input
  • Write output
  • Close files

Opening Files

To open a file for reading or writing, use the OPENFILE action. Specify the action with a file identifier, a file specification, and one of the keywords IN, OUT, or APPEND. If you specify IN, the action opens an existing file for reading only. If you specify OUT, the action creates a new file and opens it for writing only. If you specify APPEND, the action opens an existing file for writing and sets the file pointer to the end of the file.

After opening a file, the action associates the file identifier with that file. For example, the following action opens the file CONFIG.DAT for reading and associates the file identifier INFIL with the file:

    (openfile infil config.dat in)

The example shows the ON-ENTRY statement of an entry block that opens a file. First, the OPENFILE action opens the file named in the argument for writing and associates the file identifier DATA-IN with the file. Next, the IF...THEN...ELSE... action checks whether the open succeeded by using the IS-OPEN function.

Example 4-9: Opening a File with Error-Checking

    (entry-block process_file

      (accepts <input-file-name> asciz)

      (uses data_decls))

    (on-entry

      (openfile data-in <input-file-name> in)

      (if ((is-open data-in) == NIL) then

        ;

        ; If we can't access the data file, complain then exit

        ;

        (write (crlf)|Error: File,| <input-file-name> |, does not|

          (crlf)| exist or is not readable.| (crlf))

        (quit) ) )

      .

      .

      .

    (end-block process-file)

See Section C.1 for platform-specific restrictions on file names.

Once a file has been opened and associated with a file identifier, you can use that file for input or output operations by specifying the file identifier with the following actions, functions, and commands:

  • ACCEPT-ATOM function (input)
  • ACCEPTLINE_COMPOUND function (input)
  • CLOSEFILE action and command (input and output)
  • DEFAULT action and command (input and output)
  • WRITE action (output)

Setting the Default Input Source and Output Destination

Use the DEFAULT action to set the default source for input operations or the destination for output operations. The argument values that you specify with the action determine the source or destination.

By default, input comes from the terminal. To set the source to a file, specify the DEFAULT action with the file identifier of an open input file and the keyword ACCEPT. Suppose INFIL is the file identifier for an open input file. The following action sets that file to be the default source for input:

    (default infil accept)

Once the default for input has been set to a file, all input required by the ACCEPT-ATOM and ACCEPTLINE-COMPOUND functions is taken from that file. To set the default back to the terminal, specify the symbol NIL with the keyword ACCEPT.

    (default nil accept)

The terminal is also the default destination for output. To set the destination to a file, specify the DEFAULT action with the file identifier of an open output file and the keyword TRACE or WRITE. The keyword TRACE sets the destination for output from the TRACE command, and the keyword WRITE sets the destination for the WRITE action. Suppose OUTFIL is the file identifier for an open output file. The following action sets that output file to be the default destination for trace output:

    (default outfil trace)

Once the destination for trace output has been set to a file, all trace output produced by the run-time system is sent to that file. Likewise, if you substitute the keyword WRITE for TRACE, all output produced by the WRITE action is sent to that file.

    (default outfil write)

To set the default destination back to the terminal, specify the symbol NIL with the appropriate keyword. For example:

    (default nil trace)

Reading Input

The input functions ACCEPT-ATOM and ACCEPTLINE-COMPOUND read values from the terminal or a file. By default, the input functions read input from the terminal. If you want to read from a file, call the input function with the file identifier of an open input file or change the default for input.

The input functions create atoms of the appropriate data types in exactly the same way as the compiler

The examples in the following sections assume that the file identified by INFIL contains the lines shown in the example.

Example 4-10: A Sample Input File

    box KiwiCalc KiWindows

    mouse

    ; this line blank as far as RuleWorks concerned

    memory

Both input functions ignore any atoms between a semicolon (;) and the end of the line.

Reading Scalar Atoms

The ACCEPT-ATOM function reads a single atom from the terminal or a file. For example, the following rule uses the ACCEPT-ATOM function inside a MAKE action to take items from a file into a WMO:

Example 4-11: Reading Scalar Atoms

    (rule read-line-item:read-an-item

      (active-context ^name read-line-item)

      (input-count ^count <c> ^$ID <the-counter>)

      - (input-thing ^item END-OF-FILE)

      -->

      (make input-thing ^item (accept-atom infil))

      (modify <the-counter> ^count (<c> + 1)))

Each time this rule fires, the attribute ^ITEM in a new WMO is given the value read by the ACCEPT-ATOM function. Given the data shown in the example, the following objects are created:

    #17 17 [READ-LINE-ITEM:READ-AN-ITEM] (INPUT-THING ^ITEM BOX)

    #18 19 [READ-LINE-ITEM:READ-AN-ITEM] (INPUT-THING ^ITEM KIWICALC)

    #19 21 [READ-LINE-ITEM:READ-AN-ITEM] (INPUT-THING ^ITEM KIWINDOWS)

    #20 23 [READ-LINE-ITEM:READ-AN-ITEM] (INPUT-THING ^ITEM MOUSE)

    #21 25 [READ-LINE-ITEM:READ-AN-ITEM] (INPUT-THING ^ITEM MEMORY)

    #22 27 [READ-LINE-ITEM:READ-AN-ITEM] (INPUT-THING ^ITEM END-OF-FILE)

When the ACCEPT-ATOM function reads past the end of a file, it returns the atom END-OF-FILE.

The ACCEPT-ATOM function ignores blank lines between atoms.

Reading Compound Values

The ACCEPTLINE-COMPOUND function reads a line of input consisting of zero or more atoms and returns a compound value. Unlike the ACCEPT-ATOM function, the ACCEPTLINE-COMPOUND function does not ignore blank lines. You can specify a default value that the function returns when it reads a blank line. This default value can be a compound value or a bound compound variable.

For example, the MAKE action in the following rule calls the ACCEPTLINE-COMPOUND function with the default value (COMPOUND NOTHING HERE):

Example 4-12: Reading Compound Values

    (rule read-a-whole-line

      (active-context ^name read)

      (input-count ^count <c> ^$ID <the-counter>)

      - (input-thing ^line[1] END-OF-FILE)

      -->

      (make input-thing ^line(acceptline-compound infil(compound nothing here)))

      (modify <the-counter> ^count (<c> + 1)))

Given the data shown in the example, this rule makes the following objects:

    #3 3 [READ-A-WHOLE-LINE] (INPUT-THING ^LINE (COMPOUND BOX KIWICALC KIWINDOWS))

    #4 5 [READ-A-WHOLE-LINE] (INPUT-THING ^LINE (COMPOUND MOUSE))

    #5 7 [READ-A-WHOLE-LINE] (INPUT-THING ^LINE (COMPOUND NOTHING HERE))

    #6 9 [READ-A-WHOLE-LINE] (INPUT-THING ^LINE (COMPOUND MEMORY))

    #7 11 [READ-A-WHOLE-LINE] (INPUT-THING ^LINE (COMPOUND END-OF-FILE))

If you want to specify a default value for ACCEPTLINE-COMPOUND, you must put a file identifier before the default value. If you want a default value even when reading from the default input source, use NIL for the file identifier.

When the ACCEPTLINE-COMPOUND function reads past the end of a file, it returns a compound value containing the single element END-OF-FILE.

Writing Output

Use the WRITE action to send output to the terminal or a file. If you want to send output to a file, specify the action with the file identifier of an open output file or change the default for the WRITE action.

The arguments that you specify for a WRITE action are evaluated, and the output is written on the current output line with one space between values. For example, suppose the variable <ITEM> is bound to COLOR-MONITOR and the variable <PRICE> is bound to 199.95. You could use these variables in a WRITE action as follows:

    (write item <item> costs <price>)

This action displays the following output:

    ITEM COLOR-MONITOR COSTS 199.95

The output is in capital letters because the RuleWorks compiler uppercases all unquoted symbols. To display output exactly the way you have entered it, enclose the text in vertical bars (| | ). For example:

    (write | Item | <item> | costs | <price>)

This action displays:

    Item COLOR-MONITOR costs 199.95

If the variable <ITEM> were also inside vertical bars, it would not be evaluated. To get the value of <ITEM> in lowercase letters, it would have to be quoted in the place it was bound, not in the WRITE action.

Compound values are printed with one space between elements, but without any parentheses or the COMPOUND keyword. The following WRITE action:

    (write | This box contains the following cards: | <cards>)

could produce this output:

    This box contains the following cards: MEMORY MEMORY KEYBOARD

You can control the format of your output by using the CRLF, TABTO, and RJUST functions to:

  • Produce output on a new line
  • Specify the column in which to start writing output
  • Produce right-justified output

Producing Output on a New Line

To write output on a new line, use the CRLF function in a WRITE action. For example:

Example 4-13: Producing Output on a New Line

    (rule verify-configuration:need-cpu-box

      (active-context ^name verify-configuration)

      - (box)

      -->

      (make error ^severity warning ^message |Missing CPU unit|)

      (write (crlf) |Caution: You need to buy the base CPU unit.|

        (crlf) | Fixup: adding a CPU BOX to your order.|

        (crlf))

      (make box))

The WRITE actions in this rule produce the following output:

    Caution: You need to buy the base CPU unit.

    Fixup: adding a CPU BOX to your order.

Specifying the Column in Which to Start Writing Output

Use the TABTO function to specify in which column the WRITE action is to start writing output. Specify the column number with an integer or a variable bound to an integer. For example:

    (TABTO 15)

If the column you specify is to the left of the last column in which output has been written, the WRITE action writes the output on a new line, starting at the specified column.

The following WRITE action displays the headers of three columns:

    (write (crlf) |Part Number| (tabto 15) |Name| (tabto 65) |Price|)

This action produces the following output:

    Part Number Name Price

Producing Right-Justified Output

The WRITE action writes output right justified in a field of a specified width if you use the RJUST function. Specify the width with an integer or a variable bound to an integer. When a WRITE action contains the RJUST function, the WRITE action:

    1. Allocates a field of the width specified, beginning at the next possible position on the output line

    2. Determines the number of character positions required by the output being written

    3. Inserts spaces that cause the output to be right justified in the field

If the output being written requires more character positions than you specify for the field, the WRITE action writes the output as if the RJUST function was not specified, that is, the WRITE action inserts one space and then writes the output.

A call to the RJUST function must directly precede the value being written. You can use the RJUST function after calls to the CRLF and TABTO functions. For example:

    (write (crlf) <num> (tabto 12) <part-name> (tabto 65) (rjust 10) <price>)

You can also use the RJUST function to suppress the spaces that RuleWorks automatically inserts between values. For example:

    (write |Item| <item> |costs $| (rjust 6) <price>)

Closing Files

To close files, specify the CLOSEFILE action with the file identifiers of the files you want to close. The action closes the files associated with the file identifiers you specify and dissociates the identifiers from the files. For example, to close files whose file identifiers are INFIL and OUTFIL, specify the following:

    (CLOSEFILE INFIL OUTFIL)

Once you have closed a file, you can no longer use its file identifier with other actions, functions, or commands to perform input and output operations. To use the files again, you must reopen them.

If you do not close your files when control leaves the current entry block, the files remain open when control returns to that entry block. If files are open when a program quits or exits, they are closed by the operating system.

 

Saving and Restoring Working Memory

You can copy the program state, that is, the state of working memory and the conflict set, to a file by using the SAVESTATE action. The SAVESTATE action is scoped to the entry block; thus, it saves only that portion of working memory that is currently visible. The following action copies the program state to the file CONFIG.DAT:

    (savestate config.dat)

(See Section C.1 for platform-specific restrictions on file names.)

Once you have saved the program state in a file, you can use the ADDSTATE action to add the contents of the saved working memory stored in that file to the current program state. Like the SAVESTATE action, the ADDSTATE action is scoped to the entry block; it can create only those objects that are visible.

    (addstate config.dat)

If you want to clear the program state and restore it to the state produced by the SAVESTATE action, use the RESTORESTATE action. Like SAVESTATE and ADDSTATE, the RESTORESTATE action is scoped to the entry block; it can create only those objects that are visible. Using ADDSTATE or RESTORESTATE to create objects whose declarations are currently unknown results in a run-time warning.

Suppose you have used the SAVESTATE action to copy the program state to the file CONFIG.DAT. You can use the following action to clear and restore the program state to that recorded in the file CONFIG.DAT:

    (restorestate config.dat)

The state of user-defined external routines, and of files, is not saved by the SAVESTATE action and thus cannot be added or restored with the ADDSTATE or RESTORESTATE action.

If you change your OBJECT-CLASS declarations before using ADDSTATE or RESTORESTATE, the old saved file is useless.

 

Controlling the Flow of Program Execution

RuleWorks provides two RHS actions that allow you to control the flow of execution, as in procedural languages: IF...THEN...ELSE and WHILE...DO... These control actions are especially useful in ON-ENTRY statements, to allow an entry block to respond correctly to its input arguments.

Both control actions evaluate a relational expression to determine which, if any, actions are executed. The next section explains relational expressions; the following two sections cover the IF...THEN...ELSE... and WHILE...DO... actions in detail.

Relational Expressions

Relational expressions on the RHS are similar to attribute-value tests on the LHS, except that relational expressions compare two values instead of an attribute and a value, and evaluate to TRUE or FALSE instead of producing a match or no match. The syntax for a simple relational expression is shown below:

    (value-1 predicate value-2)

The values may be any expression; the relational operators may be any one of the match predicates (see Table 3-2). The restrictions on domain and shape (data type and scalar or compound) shown in the table also apply when you use the predicates in relational expressions. For example, assuming <THE-ID> is a $ID variable, the following code generates a compile-time warning:

    (<The-ID> < 20)

because the less-than operator is valid only for numbers and symbols. Assuming the variable <COUNT> is bound to either an INTEGER or a FLOAT value, the following code is correct:

    (<count> < 20)

If, at run-time, <COUNT> is bound to a value of any non-numeric type, the expression above generates no warnings, but it always evaluates to FALSE.

Note that while relational expressions evaluate to either TRUE or FALSE, TRUE and FALSE themselves are not valid relational expressions in RuleWorks.

You can use the relational operators AND, NOT, and OR to combine two (or invert one, in the case of NOT) simple relational expressions into a more complex relational expression. For example:

    ((<count> = 20) OR (<count> < 0))

Selecting Actions (Branching)

The IF...THEN...ELSE... action allows you to select between two sets of actions based on the result of a relational expression (see previous section). The syntax of this action is shown in the following example.

Example 4-14: IF-THEN-ELSE Syntax

    (IF (relational-expression)

      THEN RHS-action...
      [ELSE RHS-action... ])

The RHS actions in the THEN clause are executed when the relational expression evaluates to TRUE; the actions in the ELSE clause (if any) are executed when the relational expression evaluates to FALSE. The example show IF...THEN...ELSE... actions in ON-ENTRY statements.

Repeating Actions (Looping)

The WHILE...DO... action allows you to repeat (iterate) a set of actions based on the result of a relational expression (see Relational Expressions). The syntax of this action is shown below:

Example 4-15: WHILE-DO Syntax

    (WHILE) (relational-expression DO RHS-action...

The RHS actions are repeated as long as the relational expression evaluates to TRUE. Remember to include an action that affects the relational expression somewhere in your loop. In the following example, Reading a File, in a Loop the BIND action at the end of the loop resets the variable <IN-LINE> which is part of the relational expression.

 

Stopping Program Execution

Use the QUIT action to terminate execution of the current image and return control to the operating system without executing any ON-EXIT actions. Valid arguments for QUIT include $FAILURE and $SUCCESS, as well as any integer. RuleWorks substitutes either 0 or 1, as appropriate for the operating system, for $FAILURE and $SUCCESS.

The following example uses the QUIT action to terminate execution with a return value indicating success.

    (rule success

      (context ^$ID <context> ^task complete)

      -->

      (remove <context>)

      (quit $success))

 

Example 4-16: Reading a File in a Loop

    ;

    ; R E A D B O O K . R U L

    ;

    ; Uses the shared declarations for the phone book

    ; contents, and reads PHONE.DAT for the actual names

    ; and numbers.

    ;

    (entry-block read_phone_book

      (uses phone_book))

    (on-entry

      ; First open the data file

      ;

      (openfile phone-data-file phone.dat in)

      (if (nil == (is-open phone-data-file)) then

        (write (crlf) |Error: unable to read phone.dat| (crlf))

        (quit)

      )

      ; Next read first line and bind it to a variable

      ;

      (bind <in-line> (acceptline-compound phone-data-file))

      ;

      ; Loop through file, making each line an object

      ;

      (while (<in-line> <> (compound end-of-file)) do

        ;

        ; Ignore lines without 3 fields; lines should look like:

        ; |Dave| |Garter| |555-5283|

        ;

        (if (3 == (length <in-line>)) then

          (make person

            ^first-name (nth <in-line> 1)

            ^last-name (nth <in-line> 2)

            ^phone (nth <in-line> 3))

          )

          (bind <in-line> (acceptline-compound phone-data-file))

        )

        (closefile phone-data-file)

      )

    (end-block read_phone_book)

When this rule fires, the QUIT action causes the run-time system to stop executing recognize-act cycles immediately. The compiler provides a warning when any actions follow RETURN or QUIT actions.

© RuleWorks.co.uk | | Sitemap