home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1994 #1 / monster.zip / monster / PROG_C / RTEXP142.ZIP / RTEXP142.EXE / TUTORIAL.TXT < prev   
Text File  |  1994-03-30  |  72KB  |  1,568 lines

  1. This document is Copyright (C) 1994 The Real-Time Intelligent Systems
  2. Corporation (RTIS), all rights reserved. The names RT-Expert and
  3. Decision Support Language are trademarks of RTIS. The Decision Support
  4. Language itself is a Copyright of RTIS. This language was developed in
  5. conjunction with NASA and the US Air Force. To encourage its adoption as
  6. a standard for use in writing Real-Time Expert Systems, RTIS will
  7. license its copyright and trademark in this language to other software
  8. authors for a nominal processing fee.
  9.  
  10.  
  11.  
  12. RT-Expert Tutorial: Writing Rule Subroutines in RT-Expert
  13.  
  14. 1. Why Use "Expert Systems" Rules?
  15.  
  16. Even simple programs make a large number of decisions. Typically these
  17. decisions are made in if...then...else... statements scattered
  18. throughout the program. This makes it hard to change the program as
  19. requirements evolve or if you are doing rapid prototyping. By collecting
  20. the decision making into domain-specific rules subroutines it is much
  21. easier to identify where and how to make changes. 
  22.  
  23. Decision making in a procedural language such as C requires about 10
  24. lines of linkage code for every line of if...then...else... code. This
  25. linkage code is required to ensure that the if...then...else... rule is
  26. not executed unless all the data is available to evaluate its antecedent
  27. (between the 'if' and the 'then'). This linkage code also ensures that
  28. the rule is not executed unless a variable in the antecedent changes.
  29. When a rule 'fires', then one or more variables are changed and linkage
  30. code is required to link these changes to the rules that are affected.
  31. If the rules are changed then the linkage code has to be changed. After
  32. a few changes this results in the familiar "Spaghetti" code.
  33.  
  34. Linkage code can be eliminated though the use of a declarative rules
  35. language.  In such a language, the if...then...else... statements exist
  36. as essentially autonomous objects that are triggered by a change to a
  37. variable in their antecedent and set variables in their 'then' or 'else'
  38. consequent clauses.  Rule execution order is determined by changes to
  39. data rather than by linkage code inserted between the rules. The use of
  40. a declarative rules language can reduce the size of the decision code to
  41. be developed and maintained by a factor of 10:1.
  42.  
  43. In applications that have to do a lot of decision making, the savings in
  44. code development time can be very substantial. A typical application may
  45. have 1,000 or more rules. Our experience indicates that 1,000 rules can
  46. be expressed in 5,000 or so lines of declarative rules code. If these
  47. rules are coded directly in a procedural language they will result in
  48. 50,000 lines of C code which can take upwards of 10 person years to
  49. develop, document, and thoroughly debug. We have found that 5,000 or so
  50. lines of rules code can be developed in about 6 person months. Such
  51. savings in time have been a major impetus to the adoption of rule based
  52. programming for decision making.
  53.  
  54. Conventional Expert Systems shells eliminate the need for linkage code
  55. by interpreting the rules using an inference engine. Interpreting the
  56. rules causes the shells to run slowly relative to compiled code. Also
  57. the presence of the inference engine makes it difficult to integrate
  58. into conventional applications. As a result, expert systems shells have
  59. typically been used for developing prototypes of decision making systems
  60. which are then hand recoded into C language.
  61.  
  62. With RT-Expert, the rules are compiled into C subroutines and the
  63. linkage code is automatically generated at the same time. As a result,
  64. integration is as simple as calling a C subroutine. Execution of the
  65. resultant code runs at compiled instead of interpreted speeds and the
  66. resultant code to be integrated with the application is much smaller. 
  67.  
  68. RT-Expert contains a data flow rules execution mechanism. This is linked
  69. in, as a library, with the generated C subroutines and efficiently
  70. transfers execution control flow from one rule to the next.
  71.  
  72. The reduction in code size is not the only benefit from the use of a
  73. declarative rules language. Because the rules are not cluttered with
  74. linkage code they are much easier to understand. Also, rules may be
  75. added or changed easily without needing to change other code. This
  76. facilitates the use of Rapid Application Development (RAD) methods which
  77. are now coming into favor.
  78.  
  79. RT-Expert uses a declarative rules language called Decision Support
  80. LanguageTM, referred to here as DSL for short. Rules expressed in this
  81. language are "English like" and are easily understood by
  82. non-programmers. This language has been used as a Program Design
  83. Language (PDL) as it expresses the rules that the program will follow.
  84. Experience indicates that developing code in this language is at least
  85. twice as fast (per line of code) than C. With the 10:1 leverage in lines
  86. of code, this can translate in a 20:1 time saving for the decision code
  87. over using C.
  88.  
  89. Documentation of the decisions made by a program can be expressed in DSL
  90. with comments, thus avoiding the need for supplemental documentation, or
  91. at least reducing the amount required. This further reduces the time and
  92. cost of software development.
  93.  
  94. 2. Why Real-Time Rules?
  95.  
  96. Most applications today involve three major elements:
  97.  
  98.   User interaction with a Graphic User Interface (GUI)
  99.  
  100.   Application program interaction with data bases
  101.  
  102.   Application decisions and algorithms applied to the data.
  103.  
  104. Some applications involve other elements as well:
  105.  
  106.   Program interaction with I/O devices
  107.  
  108.   Program interaction with Communication Links
  109.  
  110.   Program interaction with other Programs over a Network
  111.  
  112. Most of these require time critical decisions. A well structured program
  113. must:
  114.  
  115.   Respond quickly to the user
  116.  
  117.   Allow other actions to overlap data base access
  118.  
  119.   Prioritize the use of the CPU
  120.  
  121.   Set-up and respond to time-outs for I/O devices
  122.  
  123.   Perform actions at specified times
  124.  
  125. All of these require time dependent decision making, where time is an
  126. essential element in the rules. Many of today's applications have become
  127. systems that must reason about time even though they are not embedded
  128. hard real-time systems. 
  129.  
  130. Rule based programming can be appropriately applied to:
  131.  
  132.   Controlling user interaction with a GUI
  133.  
  134.   Controlling access to a data base
  135.  
  136.   Controlling I/O and Communications devices
  137.  
  138. as well as making the application decisions that are at the heart of the
  139. system.
  140.  
  141. With RT-Expert, it is recommended that a separate rules subroutine be
  142. created for the control of each major user interaction screen as well as
  143. for each I/O and communications channel. Application specific decisions
  144. should be divided into domains of knowledge so that the code can easily
  145. be maintained. 
  146.  
  147. 3. Writing in Declarative Rules
  148.  
  149. Programming in a declarative rules language such as DSL is very
  150. different from programming in a sequential language such as C. It is
  151. much more object-oriented. Each rule is a semi-autonomous object that is
  152. triggered by changes to its antecedent data and changes data as a result
  153. of execution of statements in its consequent. The order of rule
  154. execution is driven by the flow of data and not by the sequence of
  155. program statements. This section provides an introduction to programming
  156. in DSL rules. 
  157.  
  158. DSL rules act on variables and set variables as a result of their
  159. actions. For example: 
  160.  
  161.   if temperature > 70 then heater := ON else heater := OFF
  162.  
  163. In this example, 'temperature' and 'heater' are DSL variables. Unlike
  164. variables in languages such as C which only have values, variables in
  165. DSL also have a set of associated attributes. 
  166.  
  167. These attributes are: 
  168.  
  169.         time                    Time when the value last changed.
  170.         importance              Importance of the data/information
  171.                                 contained by the variable.
  172.  
  173. The DSL rules execution mechanism keeps track of whether the variable is
  174. currently defined (valid) or not. It also automatically keeps track of
  175. the time at which the variable was last changed.
  176.  
  177. In DSL, when we simply use a variable name, such as temperature or
  178. heater, we are referring to its value. All other attributes are
  179. referenced in the form: 
  180.  
  181.    variable_name'attribute_name
  182.  
  183. For example, xxx'time is the time at which the value of the variable xxx
  184. was last changed. 
  185.  
  186. Variables can be numeric (floating point or integer) or symbolic. An
  187. example of a symbolic variable is a variable state which takes symbolic
  188. values ON and OFF. Variables can also be strings, characters, boolean
  189. variables, and records.
  190.  
  191. DSL rules can be used in real-time systems in which the data on which
  192. the production rules act changes over time. Initially, values for all
  193. the variables may not be available. Also, values for the data variables
  194. may be available at random times. 
  195.  
  196. The basic syntax of a rule is:
  197.  
  198.    if...then...else...;
  199.  
  200. A simple rule might be
  201.  
  202.    if t > 100 then a := 1;
  203.  
  204. The left hand side of the rule (between the if and then) is called the
  205. antecedent and can consist of one or more predicate (conditional)
  206. clauses. Rules always end with a semi-colon. The else alternate action
  207. clause is optional. 
  208.  
  209. The symbol := denotes an assignment. Comments begin with -- and continue
  210. until the end of the line. 
  211.  
  212. Rule based languages are different from conventional programming
  213. languages in that rules are only executed when new values for variables
  214. in their antecedents are available. 
  215.  
  216. For example consider a subroutine with the following rules: 
  217.  
  218.    1.        if p < 150 then bb := 2;
  219.    2.        if x > 100 then aa := 1; 
  220.    3.        if (aa = 1) and (bb > 0) then  alarm := 1;
  221.    4.        if v > 12.5 then bb := 1;
  222.  
  223. Let us assume that all the variables are parameters to the subroutine.
  224. That is, that their values and attributes are passed between the calling
  225. C code and this rule subroutine.
  226.  
  227. Initially all the variables will be undefined. If x is set to 150 and
  228. the subroutine is called then rule 2 would execute and aa would be set
  229. to 1. As aa is in the antecedent of rule 3, an attempt would be made to
  230. execute rule 3, but this would fail because bb is undefined at this
  231. point in time. If the above four rules were the only rules in the rule
  232. subroutine, then execution would cease and the subroutine would return.
  233. If v were set to 15 and the rules subroutine called again then rule 4
  234. would be executed. This would be followed by the execution of rule 3
  235. before the subroutine returned again.
  236.  
  237. Note how different this is from sequential languages such as C. There
  238. may be dozens of rules in a module but only a few may be executed in
  239. response to any stimulus, which is very efficient. Also (in general)
  240. rules do not have to be put in a specific order to ensure correct
  241. execution, so maintenance of the rule base is easier. 
  242.  
  243. How do we know when data in a variable has been changed? This is done by
  244. comparing the time at which a rule was last fired with the times at
  245. which the variables in its antecedent were last changed. If an
  246. antecedent variable has changed after the rule was last fired, then the
  247. antecedent is re-evaluated.  The rules mechanism automatically updates
  248. the time attribute of a variable whenever its value is changed.
  249.  
  250. By default, a rule subroutine assumes that all its parameters have
  251. changed every time it is called. This can sometimes result in
  252. unnecessary rule firing as some rules will be conditioned upon ATTRIB
  253. parameters that have not changed. A rule subroutine does not have to
  254. assume that all its parameters have changed every time it is called. A
  255. run-time option called a PRAGMA can be invoked so that rules are only
  256. triggered if the parameters in their antecedents change. In this case,
  257. if a C application changes the value of a variable that is a parameter
  258. before calling a rule subroutine, the application also has to set the
  259. time attribute for the variable. This is so that the rules mechanism
  260. knows which rules to fire as a result of the change (by comparing the
  261. time for the last firing of a rule with the time attributes of its
  262. antecedent variables). The benefit of this approach is that the rules
  263. subroutine can be executed repeatedly (such as in a loop) and only those
  264. rules that are affected by changes to specific parameters are executed.
  265.  
  266. To most C programmers, this event driven nature of rules is the most
  267. difficult aspect to master. A rule subroutine may be conditioned upon a
  268. number of events that are passed as parameter variables. The rule
  269. subroutine may only act on those parameters that were changed since the
  270. rule subroutine was last called.  Also the internal variables within the
  271. rule subroutine retain their values between calls, thus giving the rule
  272. subroutine memory of its prior state. This allows state transition
  273. dependent systems (such as user/GUI interaction) to be easily coded but
  274. requires the programmer to think in terms of data flow rather than
  275. control flow.
  276.  
  277. Some people, especially those trained in disciplines such as process
  278. control, find it easier to think in terms of the decision or inference
  279. graph formed by the rules. This is like a state transition diagram with
  280. the variables containing the states and the rules forming conditional
  281. arcs between the variable states. 
  282.  
  283. If multiple rules could be executed as a result of a change to a
  284. variable then rule importance is used to decide which rule to execute
  285. first. When a variable is changed the importance of all affected rules
  286. is computed. The most important rule's antecedent is evaluated. If the
  287. antecedent is true then the rule's consequent is evaluated. If the
  288. antecedent is false then the alternate (else) consequent is executed if
  289. one is present, otherwise the rule's importance is set to zero until it
  290. is triggered again. Then the most important rule is selected again and
  291. the cycle repeated.
  292.  
  293. Duplicate predicate clauses are only evaluated once. Also predicate
  294. clauses are normally (there are a few special case exceptions) evaluated
  295. only when a variable they contain changes. Thus evaluation time for rule
  296. antecedents is minimized.
  297.  
  298. By default, the rule importance is assigned according to "lexical"
  299. order. That is the rule execution importance is the same as the order in
  300. which the rules are written, with the highest importance being given to
  301. the first rule and the lowest to the last. After firing, the importance
  302. of a rule is set to zero and other rules may have their importances set
  303. as a result of consequent evaluation. The most important rule is then
  304. chosen and execution is continued in this manner until there are no more
  305. rules with a non-zero importance. At this point the rule subroutine
  306. returns to the calling program.
  307.  
  308. Through the use of a run-time pragma, rule importances can be assigned
  309. so that the most recently triggered rule is fired next. This is called
  310. recency ordering and is mostly used for building automated planning
  311. systems. Sometimes it is necessary to ensure that the rules handling the
  312. most important data fire first. In this case "data" driven order pragma
  313. is used. In this case, the rules inherit their importance from the most
  314. important variable in their antecedent. This can be used in conjunction
  315. with goal directed chaining, in which the order of rule firing is
  316. determined by goal variables in the antecedents of rules.
  317.  
  318. 4. Writing Rule Subroutines in Decision Support Language
  319.  
  320. We will now use an example program to explain the syntax of subroutines
  321. written in DSL rules and how these are integrated into an application.
  322. Our example concerns a temperature controller that controls the
  323. temperature of a chamber. The temperature controller has as inputs a
  324. RUN,STOP control and the temperature of the chamber. Its output is the
  325. ON,OFF state of the heater that controls the temperature of the chamber.
  326.  
  327. Let us first consider the rules for the temperature controller itself
  328. which are shown below. In our example, these are contained in the file
  329. temp_ctl.dsl.  Note that rule subroutines, called RTSUBs, are named. The
  330. file containing the RTSUB is given the same name as the RTSUB and must
  331. have a .dsl extension.  There can only be one RTSUB in each file.
  332.  
  333. The C file created from the .dsl file has the same name as the .dsl file
  334. with a .c extension.
  335.  
  336.  
  337.  
  338. In the file temp_ctl.dsl:
  339.  
  340. 1  RTSUB temp_ctl(temperature: ATTRIB FLOAT;
  341. 2                 control:ATTRIB SYMBOLIC;
  342. 3                 heater: ATTRIB SYMBOLIC;
  343. 4                 lo_limit,hi_limit: IN FLOAT) IS
  344. 5   DECLARE
  345. 6     RUN, STOP, ON, OFF ARE SYMBOLIC CONSTANT;
  346. 7   INIT
  347. 8     PRAGMA RULE_TRIGGER IS NEW_DATA;
  348. 9   BEGIN -- Start of RTSUB rules
  349. 10    IF control =RUN AND heater =undefined THEN heater :=ON;
  350. 11    IF control =undefined OR control =STOP THEN heater :=OFF;
  351. 12    IF temperature > hi_limit AND heater = ON
  352. 13        AND time_now - heater'time > 3 seconds
  353. 14        AND control = RUN THEN heater := OFF;
  354. 15    IF temperature < lo_limit AND heater = OFF
  355. 16        AND time_now - heater'time > 3 seconds
  356. 17        AND control = RUN THEN heater := ON;
  357. 18  END;
  358.  
  359.  
  360.  
  361. An explanation of the lines follows:
  362.  
  363. Line 1:         An RTSUB begins with the keyword RTSUB followed by the
  364.                 name of the RTSUB, in this case temp_ctl. This is
  365.                 followed by the parameters for the rule subroutine in
  366.                 the order that they will be called from C enclosed in
  367.                 parentheses.
  368.  
  369.                 The name of each parameter is followed by a colon, the
  370.                 parameter type, and its data type. An ATTRIB data type
  371.                 says that the parameter is a pointer to a record that
  372.                 contains the time the data was last changed and its
  373.                 importance. ATTRIB data types are the only ones that can
  374.                 trigger rules when their data is changed. In the  case
  375.                 of the 'temperature' parameter the data type is FLOAT
  376.                 which is a 32 bit floating point number. The data types
  377.                 can be also be records.
  378.            
  379.                 In this example the 'temperature' attributed variable is
  380.                 used to provide the current temperature of the chamber
  381.                 to the rules.
  382.  
  383. Line 2:         The control variable is SYMBOLIC. In this example it can
  384.                 take the values RUN or STOP. SYMBOLIC variables are 16
  385.                 bit integers that contain numbers used to represent
  386.                 symbolic variables. Instead of enumerating the values at
  387.                 compile time, RT-EXPERT converts a string such as "RUN"
  388.                 to a symbolic variable by calling af_sym("RUN"). This
  389.                 function places the string in a hash table and returns a
  390.                 unique integer that is the position of the string in the
  391.                 table. If another subroutine also calls af_sym() with
  392.                 the same string, this string is looked up in the hash
  393.                 table and the same value is returned. This avoids
  394.                 problems with separately compiled subroutines using
  395.                 different values for symbolic constants. Usually the
  396.                 symbolic constant strings are converted to integers
  397.                 once, at initialization time. Thereafter comparison of
  398.                 symbolic entities is done using integer comparison which
  399.                 is much faster than the alternative of comparing text
  400.                 strings.
  401.  
  402.                 In this example control starts and stops the temperature
  403.                 controller.
  404.            
  405. Line 3:         In this example, heater can take the values of ON and
  406.                 OFF. It is an output from the rules as it is set based
  407.                 upon the temperature and control inputs. It is also an
  408.                 input, as the rules are dependent on the prior setting
  409.                 of heater and the time at which it was last changed. It
  410.                 is important to note that RTSUBs are intended to be
  411.                 called repeatedly. Unlike most C subroutines, RTSUBs
  412.                 remember their prior state and rule execution is driven
  413.                 by changes to parameters.
  414.  
  415. Line 4:         Not all parameters are intended to trigger rules. Some
  416.                 are simply used to introduce constants in the rules.
  417.                 Those passed by value are simply referred to an IN
  418.                 parameters followed by their data type.  This line also
  419.                 shows how the specification of multiple parameters of
  420.                 the same type can be combined. In this case lo_limit and
  421.                 hi_limit are two floating point numbers representing
  422.                 lower and upper set points for the controller.
  423.  
  424.                 Note that the specifications of groups of parameters are
  425.                 separated by semicolons. The parameters are terminated
  426.                 by a close parenthesis followed by the keyword IS.
  427.  
  428. Line 5:         Beginning of the declaration section. In this section we
  429.                 declare symbolic constants, other variables to be used
  430.                 within the RTSUB, and also any functions or procedures
  431.                 used by the RTSUB.
  432.  
  433. Line 6:         Declaration of the symbolic constants to be used by the
  434.                 RTSUB. This is so the compiler will not confuse these
  435.                 names with variable names. Each symbolic constant is
  436.                 converted into a 16 bit integer using af_sym() as
  437.                 described above (in the description for line 2).
  438.  
  439. Line 7:         Beginning of the initialization (INIT) section of the
  440.                 RTSUB.  Statements in this section are executed
  441.                 sequentially when the RTSUB is initialized.
  442.  
  443. Line 8:         PRAGMAS are used to control the execution of the rules
  444.                 execution mechanism. This pragma specifies that the
  445.                 mechanism will only trigger rules whose last execution
  446.                 time is before that of any variable in their antecedent.
  447.                 If this is not specified, then it is assumed that all
  448.                 ATTRIB parameters have been changed since the last call
  449.                 to the RTSUB and all rules affected by any of the
  450.                 parameters are executed. The default is convenient in
  451.                 that the programmer does not have to set-up the time
  452.                 attribute of a parameter before calling the RTSUB. It
  453.                 can, however, result in the unnecessary repeat execution
  454.                 of certain rules. In this case, the time attributes of
  455.                 temperature and control are established in other RTSUBs
  456.                 and passed through to the temp_ctl() RTSUB. Thus we can
  457.                 take advantage of this pragma to eliminate unnecessary
  458.                 rule firing.
  459.            
  460. Line 9:         BEGIN signals the start of the action rules. Comments
  461.                 begin with -- and run to the end of line.
  462.  
  463. Line 10:        This is a top level rule. Execution of top level rules
  464.                 is triggered if a variable in their antecedent changes.
  465.                 Rules can also be nested within the consequents of other
  466.                 rules. Nested rules are executed sequentially.
  467.             
  468.                 Note that in the first predicate clause we are doing
  469.                 symbolic reasoning. In the second clause, we are
  470.                 reasoning about whether heater contains a valid value.
  471.                 In DSL generated code, undefined data is represented by
  472.                 "magic" numbers. This enables the compiler to reason
  473.                 about actions to take if certain data is undefined.
  474.                 RTSUBs make less assumptions about their calling state
  475.                 that do most  C subroutines. This RTSUB could be called
  476.                 with all of its attributed parameters in their undefined
  477.                 initial state.
  478.  
  479.                 Note that in DSL, unlike C, a single '=' sign is used in
  480.                 a test for equality and that ':=' is used for assignment.
  481.            
  482.                 When an assignment is performed, not only is the value
  483.                 of the variable set but so is the time last changed.
  484.  
  485.                 Rules can occupy multiple lines. They are terminated
  486.                 with a ';'.
  487.  
  488.                 This rule says that if we do not yet have a heater
  489.                 setting and the control is in the RUN state then turn
  490.                 the heater on.
  491.  
  492. Line 11:        If the control setting is unknown, or the control is in
  493.                 the stop state, turn the heater off.
  494.  
  495. Line 12:        This is the start of a multi-line rule. Note that we are
  496.                 mixing symbolic and numeric reasoning.
  497.  
  498. Line 13:        This predicate clause is doing temporal reasoning. The
  499.                 variable  time_now is the current time. The attribute
  500.                 'time of the variable 'heater' is the time at which the
  501.                 variable was last changed. This  clause ensures that if
  502.                 we have just turned the heater on we will wait 3 seconds
  503.                 minimum before we turn it off again. This is to prevent
  504.                 minor fluctuations in temperature from causing the
  505.                 heater to be rapidly turned on and off.
  506.  
  507. Line 14:        Note that 'heater' appears both in the antecedent and
  508.                 consequent of  this rule. The rule execution mechanism
  509.                 inhibits rules such as this from self re-triggering,
  510.                 otherwise we could have an endless loop.
  511.  
  512. Lines 15-17:    Another multi-line rule, similar to the last.
  513.  
  514. Line 18:      All RTSUBs are terminated with END;
  515.  
  516. This temp_ctl.dsl RTSUB file will be converted by the RTX compiler into
  517. the file temp_ctl.c for compilation by a standard C compiler. The
  518. temp_ctl.c file will contain two callable subroutines:
  519.  
  520.     init_temp_ctl()     -       must be called to initialize the RTSUB
  521.                                 before use
  522.     temp_ctl()          -       the action rules converted to C
  523.                                 language.
  524.  
  525. In the initialization procedure, all attributed variables (including
  526. parameters to the subroutine) are initialized to undefined. Then the
  527. initialization (INIT) section of the RTSUB is run (if it contains one).
  528. Both subroutines are called with the parameters specified for the RTSUB.
  529.  
  530. The subroutines assume that the user has allocated memory for the ATTRIB
  531. parameters, including the time and importance attributes, before calling
  532. these subroutines. Also the subroutines assume that these structures are
  533. declared such that data stored in them is retained from call to call.
  534.  
  535. The RTX compiler also generates a file temp_ctl.h that contains
  536. prototypes for the subroutines from the .dsl file as well as type
  537. declarations for any record types declared before the RTSUB in the .dsl
  538. file.
  539.  
  540. A sample main program to call this RTSUB is shown below. This program
  541. also calls three other RTSUBs which are described later. The main
  542. program listing is followed by a detailed description.
  543.  
  544.  
  545. In main program file my_main.c:
  546.  
  547. 1    #include <rtx.h>
  548. 2    #include "temp_ctl.h"
  549. 3    #include "set_heat.h"
  550. 4    #include "get_temp.h"
  551. 5    #include "get_ctl.h"
  552. 6
  553. 7    main()
  554. 8    {
  555. 9         static dsl_FLOAT temperature;
  556. 10        static dsl_SYMBOL control;
  557. 11        static dsl_SYMBOL heater;
  558. 12        float lo_limit,hi_limit;
  559. 13        lo_limit = 90.0; hi_limit = 110.0;
  560. 14        init_temp_ctl(&temperature,&control,&heater,lo_limit,hi_limit);
  561. 15        init_set_heater(&heater);
  562. 16        init_get_temperature(&temperature);
  563. 17        init_get_ctl(&control);
  564. 18        while(1)
  565. 19        {
  566. 20          get_ctl(&control);
  567. 21          get_temperature(&temperature);
  568. 22          temp_ctl(&temperature,&control,&heater,lo_limit,hi_limit);
  569. 23          set_heater(&heater);
  570. 24        }
  571. 25    }
  572.  
  573.  
  574.  
  575. An explanation of this main program follows:
  576.  
  577. Line 1:         The include file rtx.h includes all the declarations of
  578.                 standard procedures and data structures needed to
  579.                 interface with an RTSUB.
  580.  
  581. Line 2:         The include file temp_ctl.h is generated by the RTX
  582.                 compiler from the temp_ctl.dsl file. It contains
  583.                 prototypes for the application callable C subroutines
  584.                 created by the compiler and for any ATTRIB record data
  585.                 types. The definitions of ATTRIB record structures for
  586.                 elemental data types such as floating point numbers are
  587.                 contained in dsl_usr.h which is included in rtx.h.
  588.  
  589. Lines 3-5:     The generated include files for the other RTSUBs.
  590.  
  591. Lines 9-11:     Declarations of the record structures for the three
  592.                 ATTRIB parameters passed between the RTSUB subroutines
  593.                 and the main program. The declaration of dsl_FLOAT is:
  594.  
  595.                 typedef struct
  596.                   {
  597.                     float dsl_data;         /* value of variable */
  598.                     dsl_attr_type dsl_attr; /* attributes of variable
  599.                                                (time last modified and
  600.                                                importance).*/
  601.                   }  dsl_FLOAT;
  602.            
  603.                 The declaration of dsl_SYMBOL is:
  604.  
  605.                 typedef struct
  606.                   {
  607.                     SYMBOL dsl_data;        /* value of variable */
  608.                     dsl_attr_type dsl_attr; /* attributes of variable
  609.                                                (time last modified and
  610.                                                importance). */
  611.                   }  dsl_SYMBOL;
  612.  
  613.                 Other elemental data types are likewise defined in
  614.                 dsl_usr.h.
  615.  
  616.                 The type SYMBOL is synonymous with a short 16 bit
  617.                 integer.
  618.  
  619.                 Time is kept in seconds and milliseconds:
  620.  
  621.                 typedef struct
  622.                   {
  623.                     long aft_secs; /* Standard time in seconds. With
  624.                                     * most C run-time libraries this is
  625.                                     * expressed in seconds since 00:00
  626.                                     * hours on Monday January 1 1970;
  627.                                     * that is standard Unix time which
  628.                                     * is used by most C run-time
  629.                                     * libraries.
  630.                                     */
  631.                     short aft_ms;  /* Milliseconds after the second.
  632.                                     */
  633.                   }  af_time;
  634.  
  635.                 The time attribute in the record should be set whenever
  636.                 the value of a variable is changed. This is done
  637.                 automatically by an RTSUB but has to be programmed if
  638.                 the variable value is changed in C  code. The data
  639.                 importance is only used in data and goal directed
  640.                 chaining. If used, it should be set on a 1 to 10 scale
  641.                 with 1 being the least important and 10 being the most
  642.                 important. The default value assumed is 5.
  643.  
  644.                 The time attribute can be set to the current time by
  645.                 calling aft_get(ptime), where ptime is a pointer to an
  646.                 af_time struct.
  647.  
  648.                 The data structures are declared as static so that their
  649.                 data is retained between calls to the RTSUBs. In this
  650.                 example, because the RTSUBs are called from main(), they
  651.                 do not strictly need to be declared static. If an RTSUB
  652.                 is called from a subroutine, however, it is essential
  653.                 that these be declared as static because C subroutines
  654.                 use the stack for variable data storage which is dynamic
  655.                 (the default).
  656.  
  657.                 The data structures for the parameters must be declared,
  658.                 thereby allocating memory for them, before the
  659.                 initialization procedure for the RTSUB is called. This
  660.                 is because the initialization procedure writes into
  661.                 these structures.
  662.            
  663. Line 12:        Declaration of non-attributed parameters to be passed to
  664.                 the RTSUB.
  665.            
  666. Line 13:        Initializing these non-attributed parameters. It is as
  667.                 well to do this before calling the initialization
  668.                 procedures for the RTSUBs.  This is because these
  669.                 parameters may be used in the INIT section of an RTSUB.
  670.            
  671. Line 14:        Here we are calling the initialization procedure of an
  672.                 RTSUB. This must be done before the RTSUB rules
  673.                 subroutine is called. The RTX compiler automatically
  674.                 generates the initialization procedure for each RTSUB.
  675.                 This procedure:
  676.            
  677.                 1. Sets up the lists of predicate clauses and rules
  678.                    affected by changes to each variable.
  679.  
  680.                 2. Looks up the numerical equivalent of all symbolic
  681.                    constants using af_sym().
  682.  
  683.                 3. Initializes each value field of all attributed
  684.                    parameters and any other attributed variables used by
  685.                    the RTSUB as undefined.  It does this by setting the
  686.                    field to a magic number according to its type. It
  687.                    also sets the time attribute to the current time and
  688.                    sets the importance field to 5 (the default
  689.                    importance).
  690.  
  691.                 4. Runs the INIT section of the RTSUB if it has one.
  692.  
  693.                 The name of the initialization procedure is always the
  694.                 name of the RTSUB subroutine (the name following the
  695.                 keyword RTSUB) with the prefix "init_".
  696.  
  697.                 The initialization procedure is called with the same
  698.                 parameters as specified for the RTSUB. Any parameter
  699.                 specified as an OUT parameter will not be modified
  700.                 unless so specified in the INIT section of the RTSUB. It
  701.                 is always a good idea to declare these OUT parameters so
  702.                 as to allocate memory for them before calling the
  703.                 initialization procedure. Even if your RTSUB does not
  704.                 refer to the OUT parameter in its INIT section, someone
  705.                 may come along later and change this.
  706.  
  707.                 ATTRIB parameters are passed by address, that is with a
  708.                 pointer to a block of memory containing the data and its
  709.                 attributes. ATTRIB parameter values and attributes are
  710.                 modified by the rules execution mechanism as it runs. IN
  711.                 parameters are passed by value and cannot be changed by
  712.                 the RTSUB. OUT parameters are passed by address. These
  713.                 point to regular C variables or structs that can be
  714.                 modified by the execution of the rules.
  715.           
  716.                 OUT parameters usually only appear in the consequents of
  717.                 rules. If an input parameter is to be passed by address
  718.                 then it must be declared as INOUT. Such an INOUT
  719.                 parameter can appear in both the antecedent and
  720.                 consequent of a rule.
  721.           
  722. Lines 15-17:    Initializing other RTSUBs. Note that these RTSUBs share
  723.                 attributed parameters. Thus the temp_ctl() RTSUB. The
  724.                 attributed variable heater will be passed to the
  725.                 set_heater() RTSUB.
  726.           
  727.                 Order is important in calling the initializing
  728.                 procedures if their RTSUBs contain INIT sections. This
  729.                 is because the initialization procedure always
  730.                 initializes the parameter to undefined. Thus it was
  731.                 important to call init_temp_ctl() before
  732.                 init_get_temperature().  This latter RTSUB initializes
  733.                 the temperature to the starting ambient value in its
  734.                 INIT section. If its initializing procedure were called
  735.                 first, then the temperature value would be set to
  736.                 undefined again when init_temp_c
  737.           
  738. Line 18:        We are going to execute the RTSUBs in an infinite loop.
  739.                 The RTSUB get_ctl() causes the program to be terminated
  740.                 upon typing ESCAPE on the keyboard.
  741.           
  742. Lines 20-23:    The call to execute an RTSUB's rules is made by calling
  743.                 a C subroutine with the same name and the same
  744.                 parameters as declared for the RTSUB. The attributed
  745.                 parameters are visible at the calling level and their
  746.                 values and attributes may be modified between calls.
  747.           
  748. Let us now examine the other RTSUBs used with this example. The first is
  749. a subroutine to get new temperature data every second:
  750.  
  751.  
  752.  
  753. In file get_temp.dsl:
  754.  
  755. 1    RTSUB  get_temperature(temperature: ATTRIB FLOAT) IS
  756. 2        DECLARE
  757. 3          DYNAMIC FUNCTION get_input RETURN FLOAT;
  758. 4        INIT
  759. 5          temperature := get_input;
  760. 6       BEGIN
  761. 7         IF time_now - temperature'time > 1 second THEN
  762. 8         BEGIN
  763. 9           temperature := get_input;
  764. 10          PRINC "temperature is ", temperature, " at ",time_now;
  765. 11        END;
  766. 12      END;
  767.  
  768.  
  769.  
  770. The get_temperature() RTSUB gets the temperature from a sensor every
  771. second by calling a subroutine get_input() which may get its input from
  772. an A/D converter connected to a temperature probe inside the chamber. An
  773. explanation follows of those lines not previously explained:
  774.  
  775. Line 3:         All C functions and procedures called by the rules must
  776.                 be declared before they are used. In this case, the
  777.                 function get_input() has no parameters and returns a
  778.                 floating point value. Its C equivalent is:
  779.  
  780.                   float get_input(void);
  781.  
  782.                 The form of declaration used for C functions and
  783.                 procedures is borrowed from the Ada language. That is
  784.                 parameters are declared as IN, OUT, or INOUT (which we
  785.                 met previously). Thus the C function:
  786.  
  787.                   float sin(float x)
  788.  
  789.                 is declared as
  790.  
  791.                   FUNCTION sin(x: IN FLOAT) RETURNS FLOAT;
  792.  
  793.                 Procedures do not return any value, thus
  794.  
  795.                   PROCEDURE  xyz(a:IN SHORT;b:OUT FLOAT);
  796.  
  797.                 is equivalent to the C procedure:
  798.  
  799.                   void xyz(short a, float *b);
  800.  
  801.                 Note that IN parameters are passed by value and OUT
  802.                 parameters are passed by address. In an RTSUB, if an
  803.                 attributed variable is used as an IN to a C subroutine
  804.                 then only the value part is passed. If it is used an OUT
  805.                 or INOUT then a pointer to its data structure is passed.
  806.  
  807.                 OUT and INOUT parameters are assumed to change during a
  808.                 call to a C subroutine, as are return values. The rules
  809.                 mechanism will automatically update the time attributes
  810.                 of the affected variables and trigger the appropriate
  811.                 rules for execution as a result.
  812.  
  813.                 The function is declared as DYNAMIC so that it will be
  814.                 re-evaluated every time that a statement containing this
  815.                 procedure is executed. The RTX compiler generates
  816.                 optimized code so that a subroutine is only called if
  817.                 one of its arguments changes. In this example, the value
  818.                 returned from get_input() will change even without
  819.                 changes to its (non-existent) arguments. Such
  820.                 subroutines must be declared as dynamic so that the RTX
  821.                 compiler will generate code that re-evaluates the
  822.                 subroutine every time its return value is needed.  While
  823.                 this is only necessary if the function is called in the
  824.                 predicate part of a rule, it is still a good idea to
  825.                 declare it as such.
  826.  
  827. Line 5:         If a function call is placed in the INIT section, as in
  828.                 this case, then it will be executed when the "init_"
  829.                 initialization subroutine is called. In this case we are
  830.                 getting the initial temperature of the chamber. Note
  831.                 that when there are no parameters for a subroutine call,
  832.                 the parentheses are omitted (as they are in the
  833.                 declaration).
  834.  
  835. Line 7:         Here we have a time dependent top-level rule. It will be
  836.                 evaluated every time the RTSUB is called because
  837.                 time_now is presumed to have changed. The antecedent
  838.                 will only be true if the current time is more than one
  839.                 second later than the time at which we last took a
  840.                 temperature value (temperature'time). If less than a
  841.                 second has elapsed, the antecedent is false and, there
  842.                 being no further rules to execute, the RTSUB returns.
  843.                 Hence the nested block which is the consequent of the
  844.                 rule will only be executed once a second.
  845.  
  846. Line 8:         Here we have the beginning of a nested statement block.
  847.                 Statements within a nested block are executed
  848.                 sequentially. This includes rules nested within the
  849.                 block. Thus only top-level rules are data driven. These
  850.                 nested rules can contain blocks that contain other
  851.                 nested rules and so forth. This enables RTSUBs to
  852.                 integrate both event driven and step by step reasoning.
  853.  
  854. Line 9:         Here we are calling the C subroutine to get a new value
  855.                 of temperature. The rules execution mechanism will
  856.                 automatically update the 'time attribute so that the
  857.                 antecedent will ensure that new data is only collected
  858.                 once a second.
  859.  
  860. Line 10:        Here we are printing out the value of the temperature
  861.                 and the time at which it was taken. Because RT-Expert
  862.                 knows about the format of data used in an RTSUB, it is
  863.                 able to perform much of the formatting of a print
  864.                 statement automatically. The DSL language contains two
  865.                 forms of print statement: PRINT and PRINC. The only
  866.                 difference is that PRINC automatically appends a
  867.                 carriage return to the end of the line. Print statements
  868.                 are explained in detail in the users manual. In this
  869.                 case the time will be printed in absolute time format
  870.                 which is the date followed by
  871.                 hours:minutes:seconds.milliseconds.
  872.  
  873. Line 11:        A nested block starts with BEGIN and terminates with
  874.                 END.
  875.  
  876. The temperature value obtained by the get_temperature() RTSUB will be
  877. passed to the temp_ctl() RTSUB through the main program (see lines 21
  878. and 22 of the listing for main()). Because the temp_ctl() RTSUB has the
  879. pragma set to only trigger rules if ATTRIB parameters have changed, the
  880. rules that are dependent on the temperature variable will only be fired
  881. when this variable changes. As we will see below, the 'control' variable
  882. only changes when specified by the user. Also the heater variable only
  883. changes if 'temperature' or 'control' change. Thus, most of the time,
  884. the temp_ctl() RTSUB will return without executing any rules as
  885. temperature and control have not changed. Normally it will only execute
  886. its rules once a second, when the temperature variable changes.
  887.  
  888. Again, note that this is very different from C code where execution is
  889. controlled by the value of variables. With rules it is the fact that
  890. variables have changed that is a major controlling factor. This data
  891. driven nature of rules makes it possible to write very efficient
  892. decision routines in very few lines of code.
  893.  
  894. In our example get_temp() gets the temperature, temp_ctl() sets the
  895. heater to an ON,OFF state and set_heat() calls a subroutine to actually
  896. turn the heater on and off. A listing of set_heat.dsl follows:
  897.  
  898.  
  899.  
  900. In file set_heat.dsl:
  901.  
  902. 1  RTSUB set_heater(heater: ATTRIB SYMBOL) IS
  903. 2   DECLARE
  904. 3     PROCEDURE set_output(val:SHORT);
  905. 4     ON,OFF ARE SYMBOLIC CONSTANT;
  906. 5   INIT
  907. 6     PRAGMA RULE_TRIGGER IS NEW_DATA;
  908. 7  BEGIN
  909. 8    IF heater HAS CHANGED THEN PRINC "heater ",heater;
  910. 9    IF heater = ON THEN set_output(1);
  911. 10   IF heater = OFF THEN set_output(0);
  912. 11 END;
  913.  
  914.  
  915.  
  916. An explanation of lines that have not already been explained follows:
  917.  
  918. Line 3:         Defines the function that will be used to set the switch
  919.                 to turn the heater ON or OFF. Note that this has a
  920.                 single input parameter.  If a parameter type
  921.                 [IN,OUT,INOUT] is not specified, then it is assumed to
  922.                 be IN.
  923.  
  924. Line 6:         Rule triggering only on new data is invoked so that we
  925.                 will not keep turning the heater ON if it is already ON
  926.                 and the same for the OFF state.
  927.  
  928. Line 8:         Note the construct HAS CHANGED. HAS CHANGED causes the
  929.                 rule to be fired whenever the variable preceding the
  930.                 keywords HAS CHANGED is changed. Note that if the
  931.                 variable 'heater' changes to undefined, then the print
  932.                 statement will cause the value of heater to be printed
  933.                 as "undefined". Note that, with the use of the pragma on
  934.                 line 6, this could have been shortened to simply:
  935.  
  936.                   PRINC "heater ",heater;
  937.  
  938.                 A top-level (not nested) print statement will be
  939.                 triggered whenever any variable that it is printing
  940.                 changes. Because the pragma inhibits rule execution
  941.                 unless heater changes, then printout will occur when
  942.                 heater changes. Without the pragma, however, this
  943.                 statement would print out the value of heater whenever
  944.                 the subroutine was called.
  945.  
  946.                 Note that the symbolic variable will be printed as ON or
  947.                 OFF. That is, the string equivalent to its contents will
  948.                 be retrieved from the hash table by calling af_desym()
  949.                 before printing.
  950.  
  951. The final RTSUB in our example is used to control the running of our
  952. program from the keyboard. It takes in an escape character to terminate
  953. execution. If the user types R or r then control is set to RUN. If the
  954. user types S or s then control is set to STOP. A listing of get_ctl()
  955. follows:
  956.  
  957.  
  958. In file get_ctl.dsl:
  959.  
  960. 1  RTSUB get_ctl(control: ATTRIB SYMBOL) IS
  961. 2   DECLARE
  962. 3     RUN, STOP ARE SYMBOLIC CONSTANT;
  963. 4     c IS CHAR;
  964. 5     DYNAMIC TIME FUNCTION kbhit RETURN INT;
  965. 6     DYNAMIC FUNCTION getch RETURN CHAR;
  966. 7     PROCEDURE exit(errcode: IN INT);
  967. 8  BEGIN
  968. 9    IF kbhit /= 0 THEN  c := getch;
  969. 10   IF c = ESC THEN exit(0);
  970. 11   IF c = 'r' OR c = 'R' THEN control := RUN;
  971. 12   IF c = 's' OR c = 'S' THEN control := STOP;
  972. 13   IF c HAS CHANGED THEN PRINC "Control is ", control;
  973. 14 END;
  974.  
  975.  
  976. A description of lines that have not previously explained follows:
  977.  
  978. Line 4:         The variable c is an attributed variable that is
  979.                 internal to the RTSUB. Setting the value of the
  980.                 character c causes rules to fire (lines 10, 11, 12, and
  981.                 13).
  982.  
  983. Line 5:         The function kbhit() is a standard library routine that
  984.                 returns the number of characters in the keyboard input
  985.                 buffer. It is declared DYNAMIC so that RTX knows that
  986.                 its value can change from one invocation to the next,
  987.                 even though its parameters do not change. By default,
  988.                 RTX generates code to avoid re-evaluating any rule
  989.                 predicate clause if there are no changes to any values
  990.                 referred to in the predicate clause. This optimization
  991.                 does not work if a subroutine in the predicate clause
  992.                 returns a different value each time it is called. I/O
  993.                 interface routines fall in this category as do functions
  994.                 such as random number generators.
  995.  
  996.                 The function kbhit() is also declared as a TIME
  997.                 function. This causes kbhit() to trigger any rule it is
  998.                 in the predicate clause of every time the RTSUB is
  999.                 called (i.e. when time changes).
  1000.            
  1001. Lines 6-7:     Definition of subroutines from the standard C library.             
  1002.  
  1003. Line 9:         Because kbhit is declared as dynamic and time dependent,
  1004.                 it is executed every time get_ctl() is called. Note also
  1005.                 that not equal is /= not != as in C. If there is a
  1006.                 character in the keystroke buffer then this will be
  1007.                 retrieved using getch() and set the variable c.
  1008.  
  1009. Lines 10-12:    These will be triggered when c changes. Note that DSL
  1010.                 has predefined constants for characters such as 'ESC'
  1011.                 for escape (A list of these predefined special
  1012.                 characters can be found in Section 4.2 in the
  1013.                 Programmer's Manual).
  1014.  
  1015. Line 13:        This will be triggered when c changes, printing out the
  1016.                 symbolic constant RUN or STOP. If we had simply stated:
  1017.  
  1018.                   PRINC "Control is ", control;
  1019.  
  1020.                 This would be triggered whenever 'control' changes.
  1021.                 While this might appear to be what we want, there is an
  1022.                 undesired side effect.  By default, all ATTRIB variables
  1023.                 will be assumed to have changed whenever the subroutine
  1024.                 is called. In this case, the rule execution mechanism
  1025.                 will assume that 'control' has been externally changed
  1026.                 whenever this RTSUB is called and will print out a new
  1027.                 value of control. As this RTSUB will be called in a loop
  1028.                 it will be called many times when control is rd input.
  1029.  
  1030.                 By conditioning the printout on the character c we will
  1031.                 only get printout when c is changed.
  1032.            
  1033. Because this keyboard handling was done in rules and not in C, all the
  1034. attribute management was handled by the rules mechanism. Thus rules in
  1035. temp_ctl() that are dependent on 'control' will only be fired as a
  1036. result when 'control' changes.
  1037.  
  1038. Print statements in DSL end up at run-time formatting the text to be
  1039. printed in strings. The run-time mechanism calls af_text(char *
  1040. string_pointer) to print the string. This procedure is provided in
  1041. source form so that users can change the output. By default, af_text()
  1042. uses puts() to output the string. If the user has integrated the rules
  1043. with graphics, then this text may well be re-directed to a graphics
  1044. window for display. This can easily done by replacing the default
  1045. af_text() with one supplied by the user.
  1046.  
  1047. The final step in putting our example program together is to create a
  1048. makefile for use with a make utility. A example makefile follows with
  1049. explanation:
  1050.  
  1051.  
  1052. In Makefile:
  1053.  
  1054. 1  # Implicit rules
  1055. 2  .SUFFIXES: .dsl .afs
  1056. 3  .dsl.c:
  1057. 4        rtx1 $<
  1058. 5        rtx2
  1059. 6  .dsl.h:
  1060. 7        rtx1 $<
  1061. 8        rtx2
  1062. 9  .afs.h:
  1063. 10       afs2h $<
  1064. 11
  1065. 12 # Macros - define directories, compiler options, etc.
  1066. 13
  1067. 14 CFLAGS = -v -ml -Ic:\rtexpert\include -Lc:\rtexpert\lib
  1068. 15
  1069. 16 # Explicit rules - specific to this program
  1070. 17
  1071. 18 OBJS=my_main.obj temp_ctl.obj set_heat.obj get_temp.obj get_ctl.obj
  1072. 19
  1073. 20 my_main.exe: $(OBJS)
  1074. 21        $(CC) $(CFLAGS) $(OBJS) rtx_bc3.lib
  1075. 22
  1076. 23 my_main.obj: my_main.c temp_ctl.h set_heat.h get_temp.h get_ctl.h
  1077. 24
  1078. 25 temp_ctl.obj: temp_ctl.c
  1079. 26 temp_ctl.c: temp_ctl.dsl
  1080. 27 temp_ctl.h: temp_ctl.dsl
  1081. 28
  1082. 29 set_heat.obj: set_heat.c
  1083. 30 set_heat.c: set_heat.dsl
  1084. 31 set_heat.h: set_heat.dsl
  1085. 32
  1086. 33 get_temp.obj: get_temp.c
  1087. 34 get_temp.c: get_temp.dsl
  1088. 35 get_temp.h: get_temp.dsl
  1089. 36
  1090. 37 get_ctl.obj: get_ctl.c
  1091. 38 get_ctl.c: get_ctl.dsl
  1092. 39 get_ctl.h: get_ctl.dsl
  1093.  
  1094.  
  1095. This makefile is for Borland C++ Version 3. Those for other compilers
  1096. will be similar. One of the major differences is the inclusion of
  1097. different run-time libraries:
  1098.  
  1099.      rtx_bc3.lib        Borland C++ version 3
  1100.      rtx_mc7.lib    Microsoft C/C++ version 7
  1101.      rtx_tc3.lib        Borland Turbo C version 3
  1102.  
  1103. The library appropriate for the compiler being used must be linked with
  1104. the code generated by the RTX compiler in order for the generated code
  1105. to work.
  1106.  
  1107. Please note that these libraries have all been compiled using the Large
  1108. memory model. The code generated by the RTX compiler in intended to be
  1109. compiled using this same memory model.
  1110.  
  1111. An explanation of the makefile follows:
  1112.  
  1113. Line 2:         Specifies that .dsl and .afs are standard file
  1114.                 extensions.
  1115.  
  1116. Lines 3-5:      Specifies that to make a .c file from a .dsl file you
  1117.                 run rtx1, which is pass 1 of the RTX compiler, with the
  1118.                 .dsl file name as argument. Then you run rtx2.
  1119.  
  1120. Lines 6-8:      Specifies how the files such as temp_ctl.h get created
  1121.                 from the .dsl files.
  1122.  
  1123. Lines 9-11:     Subroutine prototype and record type definitions can be placed
  1124.                 in a .afs specification file. These are akin to .h files
  1125.                 in C.  These lines specify how a .h file can be
  1126.                 automatically generated from a .afs file.
  1127.  
  1128.                 A specification file can be included in any RTSUB by a
  1129.                 statement such as:
  1130.  
  1131.                   SPEC  xxx.afs;
  1132.  
  1133.                 To avoid the need for the user to maintain compatible .h
  1134.                 and .afs files for C and DSL, the user can simply
  1135.                 specify the records types and subroutine prototypes in
  1136.                 the .afs file and have the program afs2h convert these
  1137.                 into a compatible .h file.
  1138.  
  1139. Line 14:        CFLAGS specifies compiler options, in this example for
  1140.                 Borland C++. It is important to set the flags to tell
  1141.                 the compiler where to find the RT-Expert include and
  1142.                 library files.
  1143.  
  1144. Line 18:    Specifies all the component code objects in the program.
  1145.  
  1146. Lines 20-21:    Specifies how to create the program my_main.exe. If
  1147.                 there are too many RTSUBs, then the command line created
  1148.                 by Make may be longer than the 127 bytes allowed by DOS
  1149.                 and the command line will be truncated. This can be
  1150.                 overcome in the Borland Make utility by replacing lines
  1151.                 20 and 21 with:
  1152.  
  1153.                   my_main.exe: $(OBJS)
  1154.                           $(CC) @&&!
  1155.                   $(CFLAGS) $(OBJS) rtx_bc3.lib
  1156.                   !
  1157.  
  1158.                 This will cause the expanded form of the items between
  1159.                 the ! marks to be placed in a file and to have the
  1160.                 compiler take its input from there.
  1161.  
  1162.                 A similar facility exists in the Microsoft Make utility
  1163.                 but with different syntax.
  1164.  
  1165.                 $(CC), the compiler name, is predefined by Make unless
  1166.                 changed.
  1167.  
  1168. Line 23:        The .h files in this dependency statement are derived by
  1169.                 running the RTX compiler which also creates the C files
  1170.                 from the .dsl files.
  1171.  
  1172. Lines 25-39:    Remaining application specific dependencies.
  1173.  
  1174. Note that the generation of the .h files from the .dsl files is
  1175. explicitly stated. This allows for the re-generation of the .h files if
  1176. the .dsl file changes.
  1177.  
  1178. 5. Other Topics
  1179.  
  1180. Variables can be declared as numeric data types such as floating point
  1181. or integer numbers. They can be declared as boolean data types which are
  1182. true or false or they can be declared as characters or strings of
  1183. characters. They can also be declared as symbolic data types which have
  1184. values such as ON or OFF. 
  1185.  
  1186. Variable declarations for numbers can be of the form
  1187.  
  1188.   temperature is FLOAT;
  1189.  
  1190. or they can use the form:
  1191.  
  1192.   temperature: FLOAT;
  1193.  
  1194. Other permissible formats are:
  1195.  
  1196.   x,y: LONG;
  1197.  
  1198.   x,y are LONG;
  1199.  
  1200.   x,y is LONG;
  1201.  
  1202. (Also allowed with plural variables, although we slaughter the Queen's
  1203. English.)
  1204.  
  1205. DSL requires the declaration of all variables before use. This is so
  1206. that the DSL compiler can detect as many user errors as possible. For
  1207. example, this strong data typing enables DSL to check that a floating
  1208. point number is not added to a string and that the arguments to a
  1209. function or procedure are of the correct type. 
  1210.  
  1211. Some of the types that a variable can be declared as being are:
  1212.  
  1213.        SHORT            - 16 bit integer*
  1214.        LONG             - 32 bit integer*
  1215.        INTEGER        - 16 bit integer*
  1216.        FLOAT            - 32 bit floating point*
  1217.        DOUBLE        - 64 bit floating point*
  1218.        BOOLEAN        - TRUE or FALSE
  1219.        STRING        - null terminated string of characters
  1220.        CHAR             - single ASCII character
  1221.        SYMBOLIC         - symbolic variable
  1222.        GOAL             - goal variable for goal-directed chaining
  1223.  
  1224. *(Note that these are operating system and hardware dependent - Typical
  1225.   values are shown.)
  1226.  
  1227. Record variable types can be declared as in:
  1228.  
  1229.     TYPE xxx IS
  1230.       BEGIN
  1231.         yy:FLOAT;
  1232.         xx:SHORT;
  1233.       END;
  1234.  
  1235. These declarations must precede the use of a type in a .dsl file. If a
  1236. parameter of the RTSUB is of a user-defined type then the type
  1237. declaration must precede the RTSUB itself, as in:
  1238.  
  1239.     TYPE atype IS
  1240.       BEGIN
  1241.         yy:FLOAT;
  1242.         xx:SHORT;
  1243.       END;
  1244.     RTSUB  xxx(a: ATTRIB atype) IS
  1245.       .........
  1246.  
  1247. In such a case, the C equivalent of the type definition will be placed
  1248. in the file generated .h file. If a record type is internal to the
  1249. RTSUB, then it can be declared in the declaration section of the RTSUB,
  1250. before its use, as in:
  1251.  
  1252.    RTSUB yyy(.......) IS
  1253.     DECLARE
  1254.      TYPE btype IS
  1255.        BEGIN
  1256.          yy:FLOAT;
  1257.          xx:SHORT;
  1258.        END;
  1259.      bb IS btype;
  1260.    
  1261. In this case the C type declaration is not generated into the .h file.
  1262.  
  1263. In the case where the record is a parameter, the RTX compiler will also
  1264. generate a record type declaration:
  1265.  
  1266.   typedef struct
  1267.   {
  1268.     atype dsl_data;         /* record structure */
  1269.     dsl_attr_type dsl_attr; /* attributes of variable (time
  1270.                              * last modified and importance).
  1271.                              */
  1272.   }  dsl_atype;
  1273.  
  1274. into the .h file. This declaration can then be used to declare a static
  1275. variable to hold the record and its attributes.
  1276.  
  1277. Variables within a record are referred to using a dot convention. Thus
  1278. the field xx of record aa is referenced by aa.xx. Records can be nested
  1279. and as many dot fields as necessary can be used.
  1280.  
  1281. When rules refer to a specific field of a record in their antecedent,
  1282. they are only triggered by a change to that field, not to any other
  1283. field in that record. Thus: 
  1284.  
  1285.     IF aa.xx > 10.5 THEN aa.yy := 2;
  1286.  
  1287. will only be triggered when the field aa.xx is changed and will only
  1288. trigger rules that have aa.yy in their antecedent. If a record is an OUT
  1289. parameter from a subroutine call or is returned from a function then it
  1290. is assumed that all fields have changed. Similarly, if the whole record
  1291. is set in an assignment statement, then it is assumed that all fields
  1292. have changed.
  1293.  
  1294. If yy.zz has subfields, then it is assumed that yy.zz has changed if any
  1295. of its subfields change. This applies at any level of record nesting.
  1296.  
  1297. If an ATTRIB variable is a parameter to an RTSUB and it is determined to
  1298. have changed, all fields are assumed to have changed.
  1299.  
  1300. All variables can be in one of two states: defined or undefined. If a
  1301. variable has been assigned a value, either through the execution of a
  1302. rule or through the arrival of a new value for the variable in a
  1303. parameter, then it becomes defined. A variable is undefined before it is
  1304. set. It can become undefined in a statement such as:
  1305.  
  1306.     if y = defined then x:= undefined;
  1307.  
  1308. Note that defined and undefined are keywords. Also note that the above
  1309. rule will be evaluated whenever a new value is assigned to y and its
  1310. antecedent will always be true in such a case.
  1311.  
  1312. If a consequent statement such as:
  1313.  
  1314.    a := b + c;
  1315.  
  1316. is executed, then a will be set to undefined if either b or c is
  1317. undefined. In general, the left hand side of any consequent statement
  1318. will be set to undefined if any variable on the right hand side is
  1319. undefined. The exceptions to this are procedure or function calls which
  1320. are called with undefined variables set equal to the appropriate "magic"
  1321. numbers. It is up to the subroutine to determine how to handle undefined
  1322. variables.
  1323.  
  1324. If the subroutine being called cannot handle undefined numbers, then the
  1325. rule should be constructed in such a way as to preclude this such as in:
  1326.  
  1327.   IF x = defined THEN y := sin(x) ELSE y := undefined;
  1328.  
  1329. A top level rule such as:
  1330.  
  1331.   IF b HAS CHANGED and c HAS CHANGED THEN a := b + c;
  1332.  
  1333. will recompute a whenever b or c change and will set a to undefined if
  1334. either b or c become undefined.
  1335.  
  1336. The only form of expression where this propagation of defined and
  1337. undefined is not followed is in boolean expressions. If x,y, and z are
  1338. booleans then:
  1339.  
  1340.    z := x OR y;
  1341.  
  1342. will give z a value of true if either x or y is defined and true. In
  1343. this case the other variable can be undefined. If both x or y are
  1344. undefined then z is undefined. DSL correctly handles the evaluation of
  1345. booleans that may take undefined states. 
  1346.  
  1347. We may also want to execute a statement if a variable changes, that is
  1348. takes on a new value or becomes undefined. We can test for this by using
  1349. the keyword CHANGED as in: 
  1350.  
  1351.   IF aa HAS CHANGED THEN .....
  1352.  
  1353. This same keyword can be used to cause a rule to be re-evaluated
  1354. whenever the RTSUB is called as in: 
  1355.  
  1356.   IF TIME CHANGED THEN .....
  1357.  
  1358. An antecedent clause is assumed to be false if there is not enough data
  1359. to evaluate the clause. For example: 
  1360.  
  1361.   IF x > y OR c > d THEN f := TRUE;
  1362.  
  1363. This rule will be evaluated if x, y, c, or d changes. The antecedent
  1364. will be true if either x and y are defined and x is greater than y or c
  1365. and d are defined and c is greater than d.
  1366.  
  1367. Antecedent clauses are only evaluated when all their requisite variables
  1368. contain valid data. For efficiency, they are only re-evaluated whenever
  1369. a variable in the clause changes. Also the same clause appearing in more
  1370. than one antecedent is only evaluated once.  
  1371.  
  1372. A top level rule can be written in the form of a statement. For example: 
  1373.  
  1374.    a := b + c;
  1375.  
  1376. This is equivalent to writing:
  1377.  
  1378.    IF b HAS CHANGED OR c HAS CHANGED THEN a := b + c; 
  1379.  
  1380. These 'statement' rules are triggered by the availability of new data
  1381. values just like regular rules. 
  1382.  
  1383. Statements can appear in nested blocks as the consequent of a rule, as
  1384. in
  1385.  
  1386.     IF aa = TRUE THEN
  1387.        BEGIN
  1388.           a := b + c;
  1389.           z := 2 * a + 3;
  1390.           c := undefined;
  1391.        END;
  1392.  
  1393. These statements are executed sequentially if the predicate clause of
  1394. their top level rule is true. They are not triggered by changes to their
  1395. right hand side variables. 
  1396.  
  1397. Rules in DSL can be nested as in:
  1398.  
  1399.    IF w > x THEN 
  1400.        IF y > z THEN 
  1401.                f := TRUE;
  1402.  
  1403. They can also be a part of a statement block as in:
  1404.  
  1405.   IF w > x THEN 
  1406.     BEGIN 
  1407.        a := b * c; 
  1408.        IF y > z THEN f := TRUE; 
  1409.     END;
  1410.  
  1411. DSL processes nested rules and statements differently from top-level
  1412. rules.  Statements within a nested BEGIN/END block are processed
  1413. sequentially.  Nested rules are treated much like an IF/THEN statement
  1414. is treated in a procedural language.  That means that the antecedent is
  1415. evaluated.  If it is true, the consequent is executed. If it is false or
  1416. undefined then the alternate consequent is executed (if present). This
  1417. is different from a top-level rule where the rule is only evaluated if
  1418. the data has changed.  In a nested rule, the rule is evaluated based
  1419. only on the value of the data, not the newness of the data.
  1420.  
  1421. Statement blocks and rules can be nested to any arbitrary level but only
  1422. the top level rules are triggered by changes to their antecedent. It is
  1423. important to remember to include "trigger" variables in the antecedent
  1424. to a top level rule. For example: 
  1425.  
  1426.     IF state = state_1 THEN 
  1427.         BEGIN
  1428.           ........
  1429.           zz := aa + bb;
  1430.         END;
  1431.  
  1432. This rule will be evaluated only when state changes to state_1. This may
  1433. be what we want, but usually we require that, if we are in state_1 and
  1434. aa or bb changes, then re-evaluate zz. In this case we would need to
  1435. state: 
  1436.  
  1437.   IF state = state_1 AND (aa HAS CHANGED OR bb HAS CHANGED) THEN 
  1438.         BEGIN
  1439.           ........
  1440.           zz := aa + bb;
  1441.         END;
  1442.  
  1443. If we only wanted the nested block to be evaluated if aa and bb had
  1444. values then we would state:
  1445.  
  1446.     IF state = state_1 AND aa = defined AND bb = defined THEN 
  1447.         BEGIN
  1448.           ........
  1449.           zz := aa + bb;
  1450.         END;
  1451.  
  1452. Note that keywords in DSL can be in mixed upper and lower case.
  1453. Variables are case sensitive as are subroutine names and data types. 
  1454.  
  1455. Users familiar with the Ada language will note the syntactic similarity
  1456. with Ada. While DSL is functionally very different from an Ada program
  1457. in the way it executes, Ada syntax has been used for DSL expressions
  1458. wherever possible. This is because the Ada syntax has been carefully
  1459. designed to be usable and logically consistent.
  1460.  
  1461. Some of the actions of the rule execution mechanism are set by PRAGMA
  1462. statements. These usually appear in the INIT section of the RTSUB. They
  1463. control such things as the order of rule execution. 
  1464.  
  1465. Rules execution order is determined by the dynamic importance the rules
  1466. hold within an RTSUB. This importance can be determined by a number of
  1467. methods set by PRAGMA ORDER. These are: 
  1468.  
  1469.          LEXICAL:       The rule's importance is based on lexical order
  1470.                         (the earlier rules in the RCO are more important
  1471.                         than the later). This is the default.
  1472.  
  1473.          DATA:          The rule's importance is inherited from the
  1474.                         importance of its most important data item. This
  1475.                         allows important data items to pre-empt less
  1476.                         important data items in flowing through the
  1477.                         rules.
  1478.  
  1479.          RECENCY:       Rules whose antecedent variables have been most
  1480.                         recently changed are given precedence. This
  1481.                         gives priority to reactive chains.
  1482.  
  1483. The use of DSL rules has considerable advantages over coding the if...then...else... blocks in a procedural language such as C. The problems with using a procedural language for decision making include: 
  1484.  
  1485.          a) Typically only a small percentage of the rules are affected
  1486.             by an input data variable (such as 'temperature' in the
  1487.             example). Using a conventional language, a programmer has to
  1488.             insert additional code so that only those rules for which
  1489.             there is valid data are executed. For example, the rule:
  1490.  
  1491.               IF x > y THEN c := a + b;
  1492.  
  1493.             can only be executed if valid data values are available for
  1494.             x and y. Also, if x is greater than y then c becomes
  1495.             undefined if a or b is  undefined. This means that the
  1496.             programmer in a conventional language has to insert
  1497.             considerable code to track which variables contain valid
  1498.             data and which if...then...else... statements can be
  1499.             executed as a result. This is all taken care of
  1500.             automatically by the DSL rules execution mechanism.
  1501.  
  1502.             Further, it is desirable from an efficiency viewpoint, to
  1503.             only execute a rule when new values are available for
  1504.             variables which affect the left hand side condition of the
  1505.             rule. As soon as there are more than a few rules, the
  1506.             complexity of the code, using conventional methods,
  1507.             increases exponentially with the number of rules. This is
  1508.             made worse by the fact that variables set on the right hand
  1509.             assignment side of one rule often appear on the left hand
  1510.             condition side of a number of other rules. This requires
  1511.             that the programmer add code to force execution of rules
  1512.             which can be executed as a result of the execution of other
  1513.             rules. Also the programmer has to provide code to decide
  1514.             which of a number of possible rules should be executed if
  1515.             the setting of a variable results in the left hand side
  1516.             condition of a number of rules becoming true. All this
  1517.             results in very complex code which is hard to modify and
  1518.             maintain.
  1519.  
  1520.          b) The rules in the decision code modules are the most
  1521.             frequently changed part of most systems as they contain the
  1522.             knowledge of the system which evolves over time as the
  1523.             system's usage changes. For example, it may be desirable to
  1524.             change the temperature at which to maintain a room or to
  1525.             make this a function of both the temperature and the
  1526.             humidity. If the rules are written in a procedural language
  1527.             then it is very difficult to make changes as the changes to
  1528.             one rule may affect the code linking this rule to many other
  1529.             rules, and these to many more rules.
  1530.  
  1531. It should be noted that the complexity problem is especially severe when
  1532. the system has to function in real-time. That is, it has to respond in a
  1533. timely manner to randomly arriving data from multiple sources. DSL
  1534. solves this problem by generating the code to automatically sequence the
  1535. execution of the rules. 
  1536.  
  1537. DSL rules follow similar principles to expert systems rules except that:
  1538.  
  1539.        1. They are oriented towards the building of real-time or on-line
  1540.           systems
  1541.  
  1542.        2. They are compiled and not interpreted
  1543.  
  1544.        3. They are trigger-driven in a data flow manner
  1545.  
  1546.        4. They are strongly data typed to allow automatic verification
  1547.           by the compiler
  1548.  
  1549.        5. They are oriented towards reasoning about the times at which
  1550.           events occur
  1551.  
  1552.        6. They are able to perform computations with variables which are
  1553.           defined only over a limited time.
  1554.  
  1555.        7. They can be automatically re-triggered by the passage of time.
  1556.  
  1557.        8. They have alternate action (else) clauses which are needed for
  1558.           efficient interpretation of data.
  1559.  
  1560. Unlike expert systems which use large monolithic sets of rules,
  1561. RT-Expert encourages programmers to break their rules down into objects
  1562. which represent limited decision domains. These objects are then coded
  1563. as RTSUBs which can be developed, tested, and maintained as separate
  1564. entities. Typically the RTSUBs will contain 10 to 30 rules which is a
  1565. good compromise between ease of development and efficiency of execution.
  1566. This also allows users to develop libraries of re-usable knowledge
  1567. modules. 
  1568.