home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / INDUCE.ZIP / INDUCE.TXT < prev    next >
Encoding:
Text File  |  1988-08-03  |  94.0 KB  |  3,258 lines

  1.  
  2.  
  3. ==================================================
  4.  
  5.                     INDUCE.DOC
  6.  
  7. ==================================================
  8.  
  9.  
  10.                                  INDUCE
  11.  
  12.  
  13.                   Copyright 1986 - MicroExpert Systems
  14.                              Box 430 R.D. 2
  15.                             Nassau, NY 12123
  16.  
  17.  
  18.      INDUCE implements the ID3 algorithm for the generation of rules from a data   set as described in the article "Finding Knowledge in Data" in the November 1986 issue of BYTE.
  19.  
  20.     The program has been tested using Turbo Version 3.01A on an IBM PC. It has been run under both DOS 2.1 and Concurrent 4.1 . The source for this program is contained in two files, INDUCE.PAS and   INDUCE.INC. The program produces one overlay file INDUCE.000 .
  21.  
  22.      INDUCE produces a knowledge base which can be used with MicroExpert.   MicroExpert is an expert system shell written in Turbo Pascal for the IBM PC and Apple II. It is available for $49.95 and comes with complete source code. It can be order by writing to :
  23.  
  24.         McGraw-Hill Book Company
  25.         P.O. Box 400
  26.         Hightstown, NJ 08520
  27.  
  28.    Or calling 1-800-628-004 or in New York state 212/512-2999.
  29.  
  30.    We would be pleased to hear your comments, good or bad, or any applications   and modifications of the program. Contact us at the above address or on BIX. Our id is bbt and we may be contacted via BIXmail or by leaving comments in the MicroExpert conference.
  31.  
  32. -   Bill and Bev Thompson 
  33.  
  34.  
  35.                                 Operation
  36.  
  37.      To start the program simply switch to the directory containing INDUCE.COM and INDUCE.000 and at the DOS prompt type INDUCE and press the ENTER key. The screen will clear and the message
  38.  
  39. Example File (Press <ENTER> to quit.) : 
  40.  
  41. will appear. Type in the name of your example file and press the enter key. The file name should include the drive and path name if necessary. The default extension for example files is ".EX". The program will now read the example file. Error messages will be displayed on the screen. The program does not do very extensive error checking, so be sure to examine the example files and knowledge base to be sure that they make sense.
  42.  
  43.      Once the file has been read, the program will attempt to classify the example set. Each time an attempt is made to classify a partition of the example set, a "." is printed on the screen. The program is not particularly fast, so you will see the "."s crawl across the screen.
  44.  
  45.      You may see a "*" appear on the screen from time to time and then disappear. This indicates that garbage collection is in process. The program is attempting to reclaim memory which has been used, but is no longer accessible. 
  46.  
  47.      When the classification process has been completed, the message
  48.  
  49. Output the tree to what file (Press <ENTER> for screen) ?
  50.  
  51. will appear. You may save the tree to a file or press <ENTER> to print it on the screen. The format of the tree is described in the BYTE article. If the size of the tree is such that its width exceeds 80 columns, it may not print properly. After displaying the tree on the screen, a message telling you to press any key to continue will be displayed. To print the tree on the printer enter "lst:" as the file name.
  52.  
  53. Next, the program will display
  54.  
  55. Output the rules to what file (Press <ENTER> for screen) ?
  56.  
  57. Enter the name of the file which is to contain the rules. If this file is to be a MicroExpert knowledge base, be sure to include the extension ".KB" to the file name. The program will also write a series of prompts for the attributes.
  58.  
  59.      Finally the program will clear the screen and request a new example file. At this point you can enter a new example file or press <ENTER> to exit the program.
  60.  
  61.                               Example Files
  62.  
  63.      Example files are simply Ascii text files which are created with a text editor. The program ignores blank lines and comments in the files. Comments begin with "(*" and end with "*)". A comment may extend over several lines. The first line in the file which is not a comment or a blank line must contain the attribute names. The format of this line is
  64.  
  65.     class name,attribute1,attribute2,.....
  66.  
  67. The class name must come first, followed by the names of the attributes separated by commas. Leading and trailing blanks in attribute are not significant. Internal spaces are. Therefore, "dog and cat" is not the same as "dogandcat". The program is also case sensitive, so "Dog" is considered different from "dog". The program does not check for duplicate attributes, but of course, any knowledge base produced using duplicate attribute names is likely to be incorrect.
  68.  
  69.      Following the line containing the attribute names are one or more lines containing examples. Each example line contains a class value followed by a series of attribute values separated by commas. Each example must fit on one line. The general format is
  70.  
  71.     class value,value of attribute1,value of attribute2,.....
  72.  
  73. The attributes must be in the same order as they are listed in the first line, although there is no way for the program to check on this. As with attribute and class names, internal spaces are significant, leading and trailing spaces are not. "don't care" values are indicated by a "*".
  74.  
  75.      The following is the contents of the file for the example set in the BYTE article:
  76.  
  77.  
  78. (* Example file for Byte Article   21-May-86 *)
  79.  
  80. (* Copyright [c] 1986    MicroExpert Systems
  81.                          Box 430 RD 2
  82.                          Nassau, NY 12123  *) 
  83.  
  84.              (* Attributes *)
  85.  
  86. profit    ,age        ,competition    ,type
  87.  
  88.              (* Examples *)
  89. down    ,old        ,no        ,software
  90. down    ,midlife    ,yes        ,software
  91. up    ,midlife    ,no        ,hardware
  92. down    ,old        ,no        ,hardware
  93. up    ,new        ,no        ,hardware
  94. up    ,new        ,no        ,software
  95. up    ,midlife    ,no        ,software
  96. up    ,new        ,yes        ,software
  97. down    ,midlife    ,yes        ,hardware
  98. down    ,old        ,yes        ,software
  99.  
  100.  
  101.                           Numerical Attributes
  102.  
  103.      Numerical attributes are handled in the name manner as symbolic (non-numerical) attributes, except that ":number" is appended to the attribute name. ":number" is removed from the attribute name before printing and will not appear in either the tree or the knowledge base. Values for numeric attributes must be with in the range +/- 1.0E+37 to +/- 1.0E-37. The numbers may be entered in integer, real or floating point format. The following example set demonstrates the use of numerical attributes. There is a "don't care" value in the second example.
  104.  
  105.  
  106. (* Numerical Attribute Example file *)
  107.  
  108. (* Copyright [c] 1986    MicroExpert Systems
  109.                          Box 430 RD 2
  110.                          Nassau, NY 12123  *) 
  111.  
  112.              (* Attributes *)
  113.  
  114. profit    ,age:number     ,competition    ,type
  115.  
  116.              (* Examples *)
  117. down    ,5.0            ,no        ,software
  118. down    ,2.5            ,*        ,software
  119. up    ,2.5           ,no        ,hardware
  120. down    ,5        ,no        ,hardware
  121. up    ,1        ,no        ,hardware
  122. up    ,1        ,no        ,software
  123. up    ,2.5            ,no        ,software
  124. up    ,1        ,yes        ,software
  125. down    ,2              ,yes        ,hardware
  126. down    ,5        ,yes        ,software
  127.  
  128.  
  129.  
  130.  
  131. ==================================================
  132.  
  133.                     INDUCE.PAS
  134.  
  135. ==================================================
  136.  
  137.  
  138. {.IN+}
  139. {.PW132}
  140. (*$V-,R+,B- *)
  141. PROGRAM induce ;
  142.  
  143. (* Copyright 1986 - MicroExpert Systems
  144.                     Box 430 R.D. 2
  145.                     Nassau, NY 12123       *)
  146.  
  147. (* Induce implements the ID3 algorithm for the generation of rules from a data
  148.    set as described in the BYTE article "Finding Knowledge in Data".
  149.  
  150.    This program has been tested using Turbo ver 3.01A on an IBM PC. It has
  151.    been run under both DOS 2.1 and Concurrent 4.1 .
  152.  
  153.    The source for this program is contained in two files, INDUCE.PAS and
  154.    INDUCE.INC. The program produces one overlay file INDUCE.000 .
  155.  
  156.    INDUCE produces a knowledge base which can be used with MicroExpert.
  157.    MicroExpert is an expert system shell written in Turbo Pascal for the
  158.    IBM PC and Apple II. It is available for $49.95 and comes with complete
  159.    source code. It can be order by writing to :
  160.         McGraw-Hill Book Company
  161.         P.O. Box 400
  162.         Hightstown, NJ 08520
  163.  
  164.    Or calling 1-800-628-004 or in New York state 212/512-2999.
  165.  
  166.    We would be pleased to hear your comments, good or bad, or any applications
  167.    and modifications of the program. Contact us at the above address or
  168.    on BIX. Our id is bbt and we may be contacted via BIXmail or by leaving
  169.    comments in the MicroExpert conference.
  170.  
  171.    Bill and Bev Thompson    *)
  172.  
  173.  
  174.  CONST
  175.   ln2 = 0.69314718 ;
  176.   limit = 1.0E-20 ;
  177.   debug = false ;
  178.   back_space = ^H ;
  179.   tab = ^I ;
  180.   eof_mark = ^Z ;
  181.   esc = #27 ;
  182.   quote_char = #39 ;
  183.   left_arrow = #75 ;
  184.   end_key = #79 ;
  185.   del_line = ^X ;
  186.   return = ^M ;
  187.   bell = ^G ;
  188.  
  189.  TYPE
  190.   counter = 0 .. maxint ;
  191.   string80 = string[80] ;
  192.   string132 = string[132] ;
  193.   string255 = string[255] ;
  194.   text_file = text ;
  195.   char_set = SET OF char ;
  196.   node_type = (cons_node,symbol,number,free_node) ;
  197.   node_ptr = ^node ;
  198.   node = RECORD
  199.           in_use : boolean ;
  200.           CASE tag : node_type OF
  201.            cons_node : (tail_ptr : node_ptr ;
  202.                         head_ptr : node_ptr) ;
  203.            symbol    : (string_data : string80) ;
  204.            number    : (num_data : real) ;
  205.            free_node : (next_free : node_ptr ;
  206.                         block_cnt : counter) ;
  207.           END ;
  208.  
  209. (* node is the basic allocation unit for lists. The fields are used as
  210.    follows:
  211.  
  212.     in_use     - in_use = false tells the garbage collector that this node
  213.                  is available for re-use.
  214.     tag        - which kind of node this is.
  215.     cons_node  - cons_nodes consist of two pointers. one to the head (first item)
  216.                  the other to the rest of the list. They are the "glue" which
  217.                  holds the list together. The list (A B C) would be stored as
  218.                    -------         --------          --------
  219.                    | .| . |----->  |  .| . |------> |  .| . |---> NIL
  220.                    --|-----         --|------        --|-----
  221.                      |                |                |
  222.                      V                V                V
  223.                      A                B                C
  224.  
  225.                  The boxes are the cons nodes, the first part of the box
  226.                  holds the head pointer, then second contains the tail.
  227.     symbol     - holds string values, we don't actually use the entire 80
  228.                  characters in most cases.
  229.     number     - contains a real number.
  230.     free_node  - the garbage collector gathers all unused nodes and puts
  231.                  them on a free list. It also compacts the free space into
  232.                  contiguous blocks. next_free points to the next free block.
  233.                  block_cnt contains a count of the number of contiguous 8 byte free
  234.                  blocks which follow this one.    *)
  235.  
  236.  
  237.  VAR
  238.   example_file : text_file ;
  239.   line : string255 ;
  240.   c_list,examples,attrib_list,saved_list,initial_heap,free : node_ptr ;
  241.   total_free : real ;
  242.   no_of_cols : counter ;
  243.  
  244. (* The important globals are:
  245.    example_file - text file containing the original example set. See the
  246.                   documentation file for its format.
  247.    line         - line buffer for reading in the text file
  248.    c_list       - the classification tree
  249.    examples     - the list of examples
  250.    attrib_list  - list of attribute names and their values
  251.    saved_list   - list of all items that absolutely must be saved if garbage
  252.                   collection occurs. Usually has at least the examples and
  253.                   attrib_list attcahed to it.
  254.    initial_heap - the value of the heap pointer at the start of the program.
  255.                   used by the garbage collector
  256.    free         - the list of free nodes.
  257.    total_free   - total number of free blocks on the free list.
  258.    no_of_cols   - the total number of attributes + the class attribute in
  259.                   the example set.    *)
  260.  
  261.  
  262. (*$I induce.inc *)
  263.  
  264.  
  265.  PROCEDURE read_from_file(VAR f : text_file) ;
  266.   (* Read a line form file f and store it in the global variable, line.
  267.      It ignores blank lines and comments. When the end of file is reached
  268.      eof_mark is returned.   *)
  269.   CONST
  270.    in_comment : boolean = false ; (* static *)
  271.  
  272.   PROCEDURE read_a_line ;
  273.    BEGIN
  274.     (*$I- *)
  275.     readln(f,line) ;
  276.     (*$I+ *)
  277.     IF ioresult <> 0
  278.      THEN line := eof_mark
  279.     ELSE IF pos('(*',line) > 0
  280.      THEN
  281.       IF pos('*)',line) > 0
  282.        THEN delete(line,pos('(*',line),pos('*)',line) - pos('(*',line) + 2)
  283.        ELSE
  284.         BEGIN
  285.          in_comment := true ;
  286.          line := '' ;
  287.         END ;
  288.    END ; (* read_a_line *)
  289.  
  290.   BEGIN
  291.    line := '' ;
  292.    IF eof(f)
  293.     THEN line := eof_mark
  294.     ELSE
  295.      BEGIN
  296.       read_a_line ;
  297.       IF in_comment
  298.        THEN
  299.         IF pos('*)',line) > 0
  300.          THEN
  301.           BEGIN
  302.            delete(line,1,pos('*)',line) + 1) ;
  303.            in_comment := false ;
  304.           END
  305.          ELSE read_from_file(f) ;
  306.      END ;
  307.    strip_leading_blanks(line) ;
  308.    strip_trailing_blanks(line) ;
  309.    IF line = ''
  310.     THEN read_from_file(f) ;
  311.   END ; (* read_from_file *)
  312.  
  313.  
  314.  OVERLAY PROCEDURE expand(example_list : node_ptr ;
  315.                           VAR new_example_list : node_ptr) ;
  316.   (* Expand "don't care" values into values from attrib_list.
  317.      example_list     - unexpanded example set
  318.      new_example_list - expanded set    *)
  319.  
  320.   PROCEDURE dup_and_copy(list : node_ptr) ;
  321.    (* This routine creates a new version of the current row, pointed to by
  322.       list. If it finds a regular attribute value, it just appends the value
  323.       to the row it is constructing. If it finds a '*', indicating a
  324.       "don't care" value, it call copy_to_new_list to expand the value and
  325.       attach the new rows to new_example_list.
  326.       Notice that we attach anything we don't want to be trashed by the
  327.       garbage collector to the head of saved_list and remove it at the end
  328.       of the routine. copy_to_new_list saves new_list because it calls
  329.       dup_and_copy and that routine might initiate garbage collection.  *)
  330.    VAR
  331.     new_list,attr_ptr : node_ptr ;
  332.     copied : boolean ;
  333.  
  334.    PROCEDURE copy_to_new_list ;
  335.     (* This routine does the actual expansion. It attaches a value for the
  336.        attribute, pointed to by p, to the row that has been constructed
  337.        so far and attaches the rest of the list to the end of the row.
  338.        It calls dup_and_copy to expand any more *'s in the row and finally
  339.        attach the row to the new_example_list *)
  340.     VAR
  341.      p,new_row : node_ptr ;
  342.     BEGIN
  343.      saved_list := cons(new_list,saved_list) ;
  344.      copied := true ;
  345.      p := tail(head(attr_ptr)) ;
  346.      WHILE p <> NIL DO
  347.       BEGIN
  348.        new_row := append_list(new_list,cons(head(p),tail(list))) ;
  349.        dup_and_copy(new_row) ;
  350.        p := tail(p) ;
  351.       END ;
  352.      saved_list := tail(saved_list) ;
  353.     END ; (* copy_to_new_list *)
  354.  
  355.    BEGIN
  356.     saved_list := cons(list,saved_list) ;
  357.     test_memory ;
  358.     new_list := NIL ;
  359.     attr_ptr := attrib_list ;
  360.     copied := false ;
  361.     WHILE (list <> NIL) AND (NOT copied) DO
  362.      IF string_val(head(list)) = '*'
  363.       THEN copy_to_new_list
  364.       ELSE
  365.        BEGIN
  366.         new_list := append_list(new_list,cons(head(list),NIL)) ;
  367.         list := tail(list) ;
  368.         attr_ptr := tail(attr_ptr) ;
  369.        END ;
  370.     IF NOT copied
  371.      THEN new_example_list := append_list(new_example_list,cons(new_list,NIL)) ;
  372.     saved_list := cons(new_example_list,tail(saved_list)) ;
  373.    END ; (* dup_and_copy *)
  374.  
  375.   BEGIN
  376.    new_example_list := NIL ;
  377.    WHILE example_list <> NIL DO
  378.     BEGIN
  379.      dup_and_copy(head(example_list)) ;
  380.      example_list := tail(example_list) ;
  381.     END ;
  382.   END ; (* expand *)
  383.  
  384.  
  385.  OVERLAY FUNCTION conflicts(example_list : node_ptr) : boolean ;
  386.   (* Search for conflicts by using match_list to compare each row against
  387.      the rows which follow it in the example list. conflicts returns true
  388.      if a match is found.  *)
  389.   VAR
  390.    p : node_ptr ;
  391.    found_match : boolean ;
  392.  
  393.   PROCEDURE conflict_message ;
  394.    BEGIN
  395.     writeln ;
  396.     writeln('A conflict exists between rows:') ;
  397.     writeln ;
  398.     print_list(head(example_list)) ;
  399.     writeln ;
  400.     print_list(head(p)) ;
  401.     writeln ;
  402.     writeln('Processing cannot continue.') ;
  403.    END ; (* conflict_message *)
  404.  
  405.   BEGIN
  406.    found_match := false ;
  407.    WHILE (example_list <> NIL) AND (NOT found_match) DO
  408.     BEGIN
  409.      p := tail(example_list) ;
  410.      WHILE (p <> NIL) AND (NOT found_match) DO
  411.       IF match_lists(tail(head(example_list)),tail(head(p)))
  412.        THEN found_match := true
  413.        ELSE p := tail(p) ;
  414.      IF NOT found_match
  415.       THEN example_list := tail(example_list) ;
  416.     END ;
  417.    IF found_match
  418.     THEN conflict_message ;
  419.    conflicts := found_match ;
  420.   END ; (* conflicts *)
  421.  
  422.  
  423.  OVERLAY PROCEDURE build_table ;
  424.   (* Read the example file and build the attrib_list and examples. The
  425.      format of these two lists is described in the BYTE article mentioned
  426.      at the beginning of the program. This routine doesn't do much error
  427.      checking, so be careful with your example files.   *)
  428.   VAR
  429.    new_row : node_ptr ;
  430.    token : string80 ;
  431.  
  432.   PROCEDURE scan ;
  433.    (* Get a single token from the input line. This procedure strips leading
  434.       and trailing blanks and tabs, but interior spaces are sigificant.
  435.       A token is any string between the first non-space character and a
  436.       comma or end of line. Case is significant in tokens, 'Cat' and 'cat'
  437.       will be treated as different values by the program.    *)
  438.    VAR
  439.     comma_pos : byte ;
  440.    BEGIN
  441.     strip_leading_blanks(line) ;
  442.     IF line = ''
  443.      THEN token := ''
  444.      ELSE
  445.       BEGIN
  446.        comma_pos := pos(',',line) ;
  447.        IF comma_pos > 0
  448.         THEN
  449.          BEGIN
  450.           token := copy(line,1,comma_pos - 1) ;
  451.           delete(line,1,comma_pos) ;
  452.          END
  453.         ELSE
  454.          BEGIN
  455.           token := line ;
  456.           line := '' ;
  457.          END ;
  458.        IF token = ''
  459.         THEN token := '*' ;
  460.        strip_trailing_blanks(token) ;
  461.       END ;
  462.    END ; (* scan *)
  463.  
  464.   PROCEDURE build_a_row ;
  465.    (* Builds an example row. Symbolic and numerical attributes are handled
  466.       differently. Input lines are read one token at a time and storage is
  467.       allocated for the new token. The attrib_list is examined to see if
  468.       the new value appears on the list of values for that attribute. If it
  469.       does not, the value is added to the list. Symbolic values are added
  470.       to the end of the list of values for the attribute, numerical values are
  471.       stored in order. Once the new row is constructed it is appended
  472.       to the example set.   *)
  473.    VAR
  474.     at_list,row_list,token_ptr : node_ptr ;
  475.  
  476.    PROCEDURE length_error ;
  477.     (* Signal an error, probably a missing value. The row in question
  478.        will not be included in the example set, but the attribute list
  479.        may be damaged, so don't trust results after and error.   *)
  480.     BEGIN
  481.      writeln ;
  482.      writeln('Missing attribute in row:') ;
  483.      print_list(row_list) ;
  484.      writeln ;
  485.      writeln ;
  486.      wait ;
  487.     END ; (* length_error *)
  488.  
  489.    PROCEDURE add_value ;
  490.     (* Add a new value to the attribue list. The variable attrib_list keeps
  491.        track of the current column as the row is scanned. If token was
  492.        found to already be on the attrib_list, head(attrib_list) is appended
  493.        to at_list. If the token is a new value, it is added to the list at the
  494.        head of attrib_list, and then head(attrib_list) is appended to at_list.
  495.        After reading the entire row from the file, attrib_list is set to
  496.        point to at_list. This way attrib_list is reconstructed for each
  497.        row.    *)
  498.  
  499.     PROCEDURE insert_number ;
  500.      (* Insert a number into the attribute list. The list of values
  501.         for numerical attributes is maintained in order. This is done by
  502.         comparing the value of the token against the other items on the
  503.         list. As the comparison is done, the values are copied to new_list.
  504.         When a value is found that is greater than the token value or the
  505.         end of the list is reached, the token is appened to new_list and
  506.         then the reaming values on the old list are appended to new_list.
  507.         Finally new_list is appended to at_list. All of this appending
  508.         produces lots of garbage.  *)
  509.      VAR
  510.       new_list,p : node_ptr ;
  511.       r : real ;
  512.       inserted : boolean ;
  513.  
  514.      PROCEDURE build_new_list ;
  515.       (* This routine does the actual insetion described above. *)
  516.       BEGIN
  517.        WHILE (p <> NIL) AND (NOT inserted) DO
  518.         BEGIN
  519.          IF abs(r - num_val(head(p))) < limit
  520.           THEN
  521.            BEGIN
  522.             inserted := true ;
  523.             new_list := append_list(new_list,p) ;
  524.            END
  525.          ELSE IF r > num_val(head(p))
  526.           THEN
  527.            BEGIN
  528.             new_list := append_list(new_list,cons(head(p),NIL)) ;
  529.             p := tail(p) ;
  530.            END
  531.          ELSE
  532.           BEGIN
  533.            new_list := append_list(new_list,append_list(
  534.                                             cons(token_ptr,NIL),p)) ;
  535.            inserted := true ;
  536.           END ;
  537.         END ;
  538.       END ; (* build_new_list *)
  539.  
  540.      BEGIN
  541.       r := num_val(token_ptr) ;
  542.       inserted := false ;
  543.       new_list := cons(head(head(attrib_list)),NIL) ;
  544.       p := tail(head(attrib_list)) ;
  545.       build_new_list ;
  546.       IF (p = NIL) AND (NOT inserted)
  547.        THEN new_list := append_list(new_list,cons(token_ptr,NIL)) ;
  548.       at_list := append_list(at_list,cons(new_list,NIL)) ;
  549.      END ; (* insert_number *)
  550.  
  551.     BEGIN
  552.      IF tag_value(token_ptr) = number
  553.       THEN insert_number
  554.       ELSE at_list := append_list(at_list,
  555.                                   cons(append_list(head(attrib_list),
  556.                                                    cons(token_ptr,NIL)),
  557.                                         NIL)) ;
  558.     END ; (* add_value *)
  559.  
  560.    BEGIN
  561.     saved_list := cons(examples,attrib_list) ;
  562.     test_memory ;
  563.     at_list := NIL ;
  564.     row_list := NIL ;
  565.     scan ;
  566.     WHILE token <> '' DO
  567.      BEGIN
  568.       IF pos(':NUMBER',toupper(string_val(head(head(attrib_list))))) > 0
  569.        THEN token_ptr := alloc_num(toreal(token))
  570.        ELSE token_ptr := alloc_str(token) ;
  571.       IF (NOT on_list(token,head(attrib_list))) AND (token <> '*')
  572.        THEN add_value
  573.        ELSE at_list := append_list(at_list,cons(head(attrib_list),NIL)) ;
  574.       row_list := append_list(row_list,cons(token_ptr,NIL)) ;
  575.       attrib_list := tail(attrib_list) ;
  576.       scan ;
  577.      END ;
  578.     attrib_list := at_list ;
  579.     IF list_length(row_list) = no_of_cols
  580.      THEN examples := append_list(examples,cons(row_list,NIL))
  581.      ELSE length_error ;
  582.    END ; (* build_a_row *)
  583.  
  584.   PROCEDURE build_attrib_list ;
  585.    (* constructs the initial attrib_list from the first row in the file.
  586.       Initially the attrib_list is simply a list of the attribute names,
  587.       build_a_row adds the values to it. This routine also counts the
  588.       number of columns (attributes) in the table.   *)
  589.    BEGIN
  590.     attrib_list := NIL ;
  591.     no_of_cols := 0 ;
  592.     scan ;
  593.     WHILE token <> '' DO
  594.      BEGIN
  595.       attrib_list := append_list(attrib_list,cons(cons(alloc_str(token),NIL),
  596.                                                   NIL)) ;
  597.       no_of_cols := no_of_cols + 1 ;
  598.       scan ;
  599.      END ;
  600.    END ; (* build_attrib_list *)
  601.  
  602.   BEGIN
  603.    examples := NIL ;
  604.    line := '' ;
  605.    read_from_file(example_file) ;
  606.    IF line <> eof_mark
  607.     THEN build_attrib_list ;
  608.    read_from_file(example_file) ;
  609.    WHILE line <> eof_mark DO
  610.     BEGIN
  611.      build_a_row ;
  612.      read_from_file(example_file) ;
  613.     END ;
  614.   END ; (* build_table *)
  615.  
  616.  OVERLAY FUNCTION classify_it : node_ptr ;
  617.   (* is an overlay function which calls classify. We do it this way to avoid
  618.      swapping due to recursion. *)
  619.  
  620.   FUNCTION classify(example_list,chosen_list : node_ptr) : node_ptr ;
  621.    (* This is the main processing routine of the program. It is passed two
  622.       lists, a list of rows, example_list and a list of attributes already
  623.       chosen. The second list is simply for convenience. That way we don't
  624.       have to calculate the entropy for attribute which can no longer
  625.       contribute to splitting example_set. classify returns a pointer to the
  626.       classification tree built from the example_set.
  627.       If the example_list passed to it contains only a single class value,
  628.       classify returns the class_name (attribute name of the first column) and
  629.       the class value.
  630.       Variables:
  631.        split_elem    - the column (attribute number) to split on
  632.        classify_list - a temporary list to hold the tree
  633.        split_value   - for numerical attributes. It contains the value which
  634.                        produces the best numerical split.
  635.      classify prints a dot on the screen each time it is entered to show you
  636.      that the program really hasn't died.     *)
  637.    VAR
  638.     split_elem : counter ;
  639.     classify_list : node_ptr ;
  640.     split_value : real ;
  641.  
  642.    PROCEDURE find_split(VAR split_elem : counter ; VAR min_split_value : real) ;
  643.     (* finds the best attribute to split on. It returns the column number on
  644.        which to split and for numerical attribute, the value which produces the
  645.        best split. For each active attribute it constructs a class_list. the
  646.        class_list has the following format:
  647.        ( (attribute value #1 (class1 count) (class2 count) ....)
  648.          (attribute value #2 (class1 count) (class2 count) ....) .....)
  649.        The counts are the number of times each class appears in a row with
  650.        a particular value of the attribute. This list is used to calculate
  651.        the entropy of the attribute.  *)
  652.     VAR
  653.      i : counter ;
  654.      attrib : node_ptr ;
  655.      ent,min_entropy,split_value : real ;
  656.  
  657.     FUNCTION entropy(list : node_ptr ; cases : counter) : real ;
  658.      (* list is a class list. cases is the number of examples under
  659.         consideration. This routine calculates the entropy H(C|A) from the class
  660.         list. *)
  661.      VAR
  662.       sum,sum1,sum2,r : real ;
  663.       p : node_ptr ;
  664.  
  665.      FUNCTION log2(x : real) : real ;
  666.       BEGIN
  667.        IF abs(x) < limit
  668.         THEN log2 := 0.0
  669.         ELSE log2 := ln(x) / ln2 ;
  670.       END ; (* log2 *)
  671.  
  672.      BEGIN
  673.       sum := 0.0 ;
  674.       WHILE list <> NIL DO
  675.        BEGIN
  676.         sum1 := 0.0 ;
  677.         sum2 := 0.0 ;
  678.         p := tail(head(list)) ;
  679.         WHILE p <> NIL DO
  680.          BEGIN
  681.           r := num_val(head(tail(head(p)))) ;
  682.           sum1 := sum1 + r * log2(r) ;
  683.           sum2 := sum2 + r ;
  684.           p := tail(p) ;
  685.          END ;
  686.         sum := sum + (sum2 * log2(sum2)) - sum1 ;
  687.         list := tail(list) ;
  688.        END ;
  689.       entropy := sum / cases ;
  690.      END ; (* entropy *)
  691.  
  692.     PROCEDURE numeric_entropy(elem_no : counter ;
  693.                               VAR num_entropy,num_split_value : real) ;
  694.      (* Find the best split for a numeric attribute. elem_no is the column we
  695.         are working on. num_entropy is the best entropy for this attribute
  696.         and num_split_value is the split which gives that value. In
  697.         addition to the class list, this routine produces an ordered list
  698.         of the values for this attribute, called num_list. This list is
  699.         used in making the splits. Each split is half way between successive
  700.         values on the num_list. The entropy is calculated for each split. *)
  701.      VAR
  702.       class_list,sp,num_list : node_ptr ;
  703.       sp_val,num_ent : real ;
  704.       total_cases : counter ;
  705.  
  706.      PROCEDURE make_num_list ;
  707.       (* constructs num_list. This is essentially the same routine as
  708.          insert_number in build_table *)
  709.       VAR
  710.        new_list,p,q : node_ptr ;
  711.        r : real ;
  712.        inserted : boolean ;
  713.  
  714.       PROCEDURE add_to_new_list ;
  715.        BEGIN
  716.         WHILE (p <> NIL) AND (NOT inserted) DO
  717.          BEGIN
  718.           IF abs(r - num_val(head(p))) < limit
  719.            THEN
  720.             BEGIN
  721.              inserted := true ;
  722.              new_list := append_list(new_list,p) ;
  723.             END
  724.           ELSE IF r > num_val(head(p))
  725.            THEN
  726.             BEGIN
  727.              new_list := append_list(new_list,cons(head(p),NIL)) ;
  728.              p := tail(p) ;
  729.             END
  730.           ELSE
  731.            BEGIN
  732.             new_list := append_list(new_list,append_list(
  733.                                              cons(alloc_num(r),NIL),p)) ;
  734.             inserted := true ;
  735.            END ;
  736.          END ;
  737.        END ; (* add_to_new_list *)
  738.  
  739.       BEGIN
  740.        test_memory ;
  741.        num_list := NIL ;
  742.        q := example_list ;
  743.        WHILE q <> NIL DO
  744.         BEGIN
  745.          r := num_val(head(element(head(q),elem_no))) ;
  746.          new_list := NIL ;
  747.          p := num_list ;
  748.          inserted := false ;
  749.          add_to_new_list ;
  750.          IF (p = NIL) AND (NOT inserted)
  751.           THEN new_list := append_list(new_list,cons(alloc_num(r),NIL)) ;
  752.          num_list := new_list ;
  753.          q := tail(q) ;
  754.         END ;
  755.       END ; (* make_num_list *)
  756.  
  757.      PROCEDURE make_numeric_class_list(v : real) ;
  758.       (* builds the class list. v is the value to split on. The class_list
  759.          contains lists for two ranges < v and >= v. The list has the format:
  760.          ( (< v  (class1 count) (class2 count) .....)
  761.            (>= v (class1 count) (class2 count) .....))   *)
  762.       VAR
  763.        temp_list,p : node_ptr ;
  764.        v_str : string80 ;
  765.       BEGIN
  766.        str(v,v_str) ;
  767.        temp_list := NIL ;
  768.        p := tail(head(attrib_list)) ;
  769.        WHILE p <> NIL DO
  770.         BEGIN
  771.          temp_list := append_list(temp_list,cons(cons(head(p),
  772.                                                  cons(alloc_num(0.0),NIL)),NIL)) ;
  773.          p := tail(p) ;
  774.         END ;
  775.        class_list := cons(cons(alloc_str(concat('< ',v_str)),temp_list),
  776.                           cons(cons(alloc_str(concat('>= ',v_str)),
  777.                                copy_list(temp_list)),NIL)) ;
  778.       END ; (* make_numeric_class_list *)
  779.  
  780.      PROCEDURE count_numeric_classes(v : real ; elem_no : counter) ;
  781.       (* count the classes for each range. It reads the example list, extracts
  782.          the value for the attribute, searches the class list and increments
  783.          the appropriate class value in the list. v is the split value. *)
  784.       VAR
  785.        px,py : node_ptr ;
  786.  
  787.       PROCEDURE numeric_increment(list : node_ptr ; attr_v,atv : string80) ;
  788.        (* search list (class_list) and compare attr_v to v, the split_value.
  789.           atv is the class_value. Once we find the sub-list with the proper
  790.           range we search its tail for atv to increment the class count.   *)
  791.  
  792.        PROCEDURE do_increment(v_list : node_ptr) ;
  793.         VAR
  794.          p,q : node_ptr ;
  795.         BEGIN
  796.          q := tail(v_list) ;
  797.          WHILE q <> NIL DO
  798.           IF string_val(head(head(q))) = atv
  799.            THEN
  800.             BEGIN
  801.              p := head(tail(head(q))) ;
  802.              IF tag_value(p) = number
  803.               THEN p^.num_data := p^.num_data + 1.0 ;
  804.              total_cases := total_cases + 1 ;
  805.              q := NIL ;
  806.             END
  807.            ELSE q := tail(q) ;
  808.         END ; (* do_increment *)
  809.  
  810.        BEGIN
  811.         IF toreal(attr_v) < v
  812.          THEN do_increment(head(list))
  813.          ELSE do_increment(head(tail(list))) ;
  814.        END ; (* numeric_increment *)
  815.  
  816.       BEGIN
  817.        total_cases := 0 ;
  818.        px := example_list ;
  819.        WHILE px <> NIL DO
  820.         BEGIN
  821.          py := head(px) ;
  822.          numeric_increment(class_list,string_val(head(element(py,elem_no))),
  823.                   string_val(head(py))) ;
  824.          px := tail(px) ;
  825.         END ;
  826.       END ; (* count_numeric_classes *)
  827.  
  828.      BEGIN
  829.       num_entropy := 1.0E+37 ;
  830.       make_num_list ;
  831.       sp := tail(num_list) ;
  832.       saved_list := cons(num_list,saved_list) ;
  833.       WHILE sp <> NIL DO
  834.        BEGIN
  835.         test_memory ;
  836.         sp_val := num_val(head(num_list))
  837.                   + ((num_val(head(sp)) - num_val(head(num_list))) / 2.0) ;
  838.         make_numeric_class_list(sp_val) ;
  839.         count_numeric_classes(sp_val,elem_no) ;
  840.         num_ent := entropy(class_list,total_cases) ;
  841.         IF num_ent < num_entropy
  842.          THEN
  843.           BEGIN
  844.            num_entropy := num_ent ;
  845.            num_split_value := sp_val ;
  846.           END ;
  847.         num_list := sp ;
  848.         sp := tail(sp) ;
  849.        END ;
  850.       saved_list := tail(saved_list) ;
  851.      END ; (* numeric_entropy *)
  852.  
  853.     PROCEDURE symbol_entropy(val_list : node_ptr ; elem_no : counter ;
  854.                              VAR sym_ent,sym_split_val : real) ;
  855.      (* Find the entropy for a symbolic attribute. val_list is the list
  856.         of possible values for this attribute from the attrib_list.
  857.         elem_no is the column number, sym_ent is the entropy for this attribute.
  858.         sym_split_value is always 0. This routine constructs a class list
  859.         as described above and counts the classes for each value of the
  860.         attribute as in the numeric case, only there is no range splitting.
  861.         Symbolic attributes can result in mult-way partitions of the
  862.         example_list, numerical attributes always produce binary splits.  *)
  863.      VAR
  864.       class_list : node_ptr ;
  865.       total_cases : counter ;
  866.  
  867.      PROCEDURE make_class_list(a_list : node_ptr) ;
  868.       (* builds the initial class list. See above comments for format. *)
  869.       VAR
  870.        temp_list,p : node_ptr ;
  871.       BEGIN
  872.        WHILE a_list <> NIL DO
  873.         BEGIN
  874.          temp_list := cons(head(a_list),NIL) ;
  875.          p := tail(head(attrib_list)) ;
  876.          WHILE p <> NIL DO
  877.           BEGIN
  878.           temp_list := append_list(temp_list,cons(cons(head(p),
  879.                                                    cons(alloc_num(0.0),NIL)),NIL)) ;
  880.            p := tail(p) ;
  881.           END ;
  882.          class_list := append_list(class_list,cons(temp_list,NIL)) ;
  883.          a_list := tail(a_list) ;
  884.         END ;
  885.       END ; (* make_class_list *)
  886.  
  887.      PROCEDURE count_classes(elem_no : counter) ;
  888.       (* traverses the example_list and counts class values. *)
  889.       VAR
  890.        px,py : node_ptr ;
  891.  
  892.       PROCEDURE increment(list : node_ptr ; attr,v : string80) ;
  893.        (* search list (class_list) and compare attr to the head of each sub-list.
  894.           v is the class_value. Once we find the sub-list with the proper
  895.           range we search its tail for v to increment the class count.   *)
  896.        VAR
  897.         p,q : node_ptr ;
  898.        BEGIN
  899.         WHILE list <> NIL DO
  900.          IF string_val(head(head(list))) = attr
  901.           THEN
  902.            BEGIN
  903.             q := tail(head(list)) ;
  904.             WHILE q <> NIL DO
  905.              IF string_val(head(head(q))) = v
  906.               THEN
  907.                BEGIN
  908.                 p := head(tail(head(q))) ;
  909.                 IF tag_value(p) = number
  910.                  THEN p^.num_data := p^.num_data + 1.0 ;
  911.                 total_cases := total_cases + 1 ;
  912.                 list := NIL ;
  913.                 q := NIL ;
  914.                END
  915.               ELSE q := tail(q) ;
  916.            END
  917.           ELSE list := tail(list) ;
  918.         END ; (* increment *)
  919.  
  920.       BEGIN
  921.        total_cases := 0 ;
  922.        px := example_list ;
  923.        WHILE px <> NIL DO
  924.         BEGIN
  925.          py := head(px) ;
  926.          increment(class_list,string_val(head(element(py,elem_no))),
  927.                   string_val(head(py))) ;
  928.          px := tail(px) ;
  929.         END ;
  930.       END ; (* count_classes *)
  931.  
  932.      BEGIN
  933.       class_list := NIL ;
  934.       make_class_list(val_list) ;
  935.       count_classes(elem_no) ;
  936.       sym_ent := entropy(class_list,total_cases) ;
  937.       sym_split_val := 0.0
  938.      END ; (* symbol_entropy *)
  939.  
  940.     BEGIN
  941.      min_entropy := 1.0E+37 ;
  942.      FOR i := 2 TO no_of_cols DO
  943.       BEGIN
  944.        test_memory ;
  945.        attrib := head(element(attrib_list,i)) ;
  946.        IF NOT on_list(string_val(head(attrib)),chosen_list)
  947.         THEN
  948.          BEGIN
  949.           IF pos(':NUMBER',toupper(string_val(head(attrib)))) > 0
  950.            THEN numeric_entropy(i,ent,split_value)
  951.            ELSE symbol_entropy(tail(attrib),i,ent,split_value) ;
  952.           IF ent < min_entropy
  953.            THEN
  954.             BEGIN
  955.              min_entropy := ent ;
  956.              split_elem := i ;
  957.              min_split_value := split_value ;
  958.             END ;
  959.          END ;
  960.       END ;
  961.     END ; (* find_split *)
  962.  
  963.    FUNCTION split(elem_no : counter ; split_val : real) : node_ptr ;
  964.     (* This routine splits the example_list into sets which contain a single
  965.        value of the split attribute. elem_no is the column on which to split.
  966.        split_val is the split value for numerical attributes.
  967.        split_item points to the attribute's entry in the attribute list.
  968.        split_list is the tree which is returned by split. Its format is
  969.        (attribute_name (value1 classify(partition with attribute = value1)
  970.                        (value2 classify(partition with attribute = value2)
  971.                        ....... )    *)
  972.      VAR
  973.      split_list,split_item,new_chosen : node_ptr ;
  974.  
  975.     PROCEDURE numeric_split ;
  976.      (* Splitting on a numerical attribute splits the examples into
  977.         two groups, those with values < split_value and those with
  978.         values >= split_value. new_list1 and new_list2 are the example
  979.         sets for the two categories. It returns a split_list as follows:
  980.         (attribute_name ('< split_val'
  981.                          classify(all examples with attribute value < split_val)
  982.                         ('>= split_val'
  983.                          classify(all examples with attribute value >= split_val))
  984.         Notice all the lists placed on the saved_list. These are the items
  985.         that we must retain should any of the calls to classify invoke garbage
  986.         collection *)
  987.      VAR
  988.       new_list1,new_list2,q,valu : node_ptr ;
  989.       split_str : string80 ;
  990.      BEGIN
  991.       str(split_val,split_str) ;
  992.       valu := cons(alloc_str(concat('< ',split_str)),
  993.                    cons(alloc_str(concat('>= ',split_str)),NIL)) ;
  994.       q := example_list ;
  995.       new_list1 := NIL ;
  996.       new_list2 := NIL ;
  997.       WHILE q <> NIL DO
  998.        BEGIN
  999.         IF num_val(head(element(head(q),elem_no))) < split_val
  1000.          THEN new_list1 := append_list(new_list1,cons(head(q),NIL))
  1001.          ELSE new_list2 := append_list(new_list2,cons(head(q),NIL)) ;
  1002.         q := tail(q) ;
  1003.        END ;
  1004.       saved_list := cons(split_list,cons(valu,cons(new_list2,saved_list))) ;
  1005.       split_list := append_list(split_list,
  1006.                               cons(cons(head(valu),
  1007.                               cons(classify(new_list1,chosen_list),NIL)),NIL)) ;
  1008.       saved_list := cons(split_list,tail(saved_list)) ;
  1009.       split_list := append_list(split_list,
  1010.                               cons(cons(head(tail(valu)),
  1011.                               cons(classify(new_list2,chosen_list),NIL)),NIL)) ;
  1012.       saved_list := tail(tail(tail(saved_list))) ;
  1013.      END ; (* numeric_split *)
  1014.  
  1015.     PROCEDURE symbol_split ;
  1016.      (* performs the split for symbolic attributes. For each value of the
  1017.         attribute, it searches the example list for matches and attaches
  1018.         examples with a match to new_example_list. If it finds any matches
  1019.         it appends the value and the result of classifying the new_example_list
  1020.         to split_list. This is a very inefficient way of doing this. It
  1021.         would be better to sort the example_list using column elem_no as
  1022.         a key.   *)
  1023.      VAR
  1024.       valu,q,new_example_list : node_ptr ;
  1025.      BEGIN
  1026.       valu := tail(split_item) ;
  1027.       WHILE valu <> NIL DO
  1028.        BEGIN
  1029.         q := example_list ;
  1030.         new_example_list := NIL ;
  1031.         WHILE q <> NIL DO
  1032.          BEGIN
  1033.           IF string_val(head(valu)) = string_val(head(element(head(q),elem_no)))
  1034.            THEN new_example_list := append_list(new_example_list,cons(head(q),NIL)) ;
  1035.           q := tail(q) ;
  1036.          END ;
  1037.         IF new_example_list <> NIL
  1038.          THEN
  1039.           BEGIN
  1040.            saved_list := cons(split_list,saved_list) ;
  1041.            split_list := append_list(split_list,
  1042.                                      cons(cons(head(valu),
  1043.                                      cons(classify(new_example_list,new_chosen),NIL)),
  1044.                                      NIL)) ;
  1045.            saved_list := tail(saved_list) ;
  1046.           END ;
  1047.         valu := tail(valu) ;
  1048.        END ;
  1049.      END ; (* symbol_split *)
  1050.  
  1051.     BEGIN
  1052.      split_item := head(element(attrib_list,elem_no)) ;
  1053.      new_chosen := cons(head(split_item),chosen_list) ;
  1054.      split_list := cons(head(split_item),NIL) ;
  1055.      IF pos(':NUMBER',toupper(string_val(head(split_item)))) > 0
  1056.       THEN numeric_split
  1057.       ELSE symbol_split ;
  1058.      split := split_list ;
  1059.     END ; (* split *)
  1060.  
  1061.    FUNCTION single_class : boolean ;
  1062.     (* returns true if the example_list contains only a single class value. *)
  1063.     VAR
  1064.      first_val : string80 ;
  1065.      p : node_ptr ;
  1066.      more_than_one : boolean ;
  1067.     BEGIN
  1068.      first_val := string_val(head(head(example_list))) ;
  1069.      more_than_one := false ;
  1070.      p := tail(example_list) ;
  1071.      WHILE (p <> NIL) AND (NOT more_than_one) DO
  1072.       IF string_val(head(head(p))) <> first_val
  1073.        THEN more_than_one := true
  1074.        ELSE p := tail(p) ;
  1075.      single_class := NOT more_than_one ;
  1076.     END ; (* single_class *)
  1077.  
  1078.    BEGIN
  1079.     write('.') ;
  1080.     split_elem := 0 ;
  1081.     saved_list := cons(chosen_list,cons(example_list,saved_list)) ;
  1082.     IF NOT single_class
  1083.      THEN find_split(split_elem,split_value) ;
  1084.     IF split_elem = 0
  1085.      THEN classify_list := cons(head(head(attrib_list)),
  1086.                                 cons(cons(head(head(example_list)),NIL),NIL))
  1087.      ELSE classify_list := split(split_elem,split_value) ;
  1088.     saved_list := append_list(tail(tail(saved_list)),cons(classify_list,NIL)) ;
  1089.     classify := classify_list ;
  1090.    END ; (* classify *)
  1091.  
  1092.   BEGIN
  1093.    classify_it := classify(examples,NIL) ;
  1094.   END ; (* classify_it *)
  1095.  
  1096.  
  1097.  OVERLAY PROCEDURE print_rule_list(list : node_ptr) ;
  1098.   (* This routine transforms the tree into a set of IF/THEN statements and
  1099.      writes them to a file. It produces a knowledge base for MicroExpert [c],
  1100.      if you want to produces rules for another shell, this routine will
  1101.      have to be modified.  *)
  1102.   VAR
  1103.    rule_count : counter ;
  1104.    rule_file : text_file ;
  1105.    file_name : string80 ;
  1106.    used_attribs : node_ptr ;
  1107.  
  1108.   PROCEDURE print_rule(tree,rule_list : node_ptr) ;
  1109.    (* Do a depth first traversal of tree. On entry rule_list contains a
  1110.       list of attribute value pairs. When tree is finally NIL, i.e. a
  1111.       terminal node of the tree has been encountered, the rule_list is
  1112.       printed. If entered with a non-NIL tree, the routine creates a new
  1113.       attribute value pair, attaches it to rule_list and explores further
  1114.       down the tree. It also attaches the attribute names to used_attribs
  1115.       so that they can be used to generate prompts. *)
  1116.    VAR
  1117.     p : node_ptr ;
  1118.  
  1119.    PROCEDURE print_the_rule(list : node_ptr) ;
  1120.     (* Prints the rule_list, with rules formatted for MicroExpert. *)
  1121.     VAR
  1122.      s : string80 ;
  1123.  
  1124.     PROCEDURE write_compare ;
  1125.      VAR
  1126.       comp_str : string[2] ;
  1127.  
  1128.      FUNCTION quote(w : string80) : string80 ;
  1129.       BEGIN
  1130.        quote := '''' + w + '''' ;
  1131.       END ; (* quote *)
  1132.  
  1133.      BEGIN
  1134.       comp_str := '' ;
  1135.       WHILE s[1] <> ' ' DO
  1136.        BEGIN
  1137.         comp_str := comp_str + s[1] ;
  1138.         delete(s,1,1) ;
  1139.        END ;
  1140.       strip_leading_blanks(s) ;
  1141.       strip_trailing_blanks(s) ;
  1142.       writeln(rule_file,'function compare(',attrib_value(head(head(list))),
  1143.               ',',quote(comp_str),',',quote(s),')') ;
  1144.      END ; (* write_compare *)
  1145.  
  1146.     BEGIN
  1147.      writeln(rule_file,rule_count) ;
  1148.      rule_count := rule_count + 1 ;
  1149.      write(rule_file,'If   ') ;
  1150.      WHILE list <> NIL DO
  1151.       BEGIN
  1152.        s := string_val(head(tail(head(list)))) ;
  1153.        IF s[1] IN ['<','>']
  1154.         THEN write_compare
  1155.         ELSE writeln(rule_file,attrib_value(head(head(list))),' is ',s) ;
  1156.        list := tail(list) ;
  1157.        IF list <> NIL
  1158.         THEN
  1159.          IF tail(list) = NIL
  1160.           THEN write(rule_file,'then ')
  1161.           ELSE write(rule_file,'and  ') ;
  1162.       END ;
  1163.      writeln(rule_file,'.') ;
  1164.      writeln(rule_file) ;
  1165.     END ; (* print_the_rule *)
  1166.  
  1167.    BEGIN
  1168.     IF tree = NIL
  1169.      THEN print_the_rule(rule_list)
  1170.      ELSE
  1171.       BEGIN
  1172.        IF head(tree) <> head(head(attrib_list))
  1173.         THEN
  1174.          IF NOT on_list(string_val(head(tree)),used_attribs)
  1175.           THEN used_attribs := cons(head(tree),used_attribs) ;
  1176.        p := tail(tree) ;
  1177.        WHILE p <> NIL DO
  1178.         BEGIN
  1179.          print_rule(head(tail(head(p))),
  1180.                     append_list(rule_list,cons(cons(head(tree),
  1181.                                                cons(head(head(p)),NIL)),
  1182.                                                NIL))) ;
  1183.          p := tail(p) ;
  1184.         END ;
  1185.       END ;
  1186.    END ; (* print_rule *)
  1187.  
  1188.   PROCEDURE print_prompts ;
  1189.    (* This routine traverses the attribute list and writes a prompt for
  1190.       each attribute on the list. MicroExpert does not automatically
  1191.       generate prompts, so this is necessary. The format
  1192.       of the questions may seem dumb. For a working knowledge base, you
  1193.       will want to edit the prompts and add translations.  *)
  1194.     VAR
  1195.      q : node_ptr ;
  1196.    BEGIN
  1197.     q := used_attribs ;
  1198.     WHILE q <> NIL DO
  1199.      BEGIN
  1200.       writeln(rule_file) ;
  1201.       IF pos(':NUMBER',toupper(string_val(head(q)))) > 0
  1202.        THEN writeln(rule_file,'Numeric prompt ',attrib_value(head(q)))
  1203.        ELSE writeln(rule_file,'Prompt ',attrib_value(head(q))) ;
  1204.       writeln(rule_file,'What is the value of ',attrib_value(head(q)),' ?') ;
  1205.       writeln(rule_file,'.') ;
  1206.       q := tail(q) ;
  1207.      END ;
  1208.    END ; (* print_prompts *)
  1209.  
  1210.   BEGIN
  1211.    writeln ;
  1212.    write('Output the rules to what file (Press <ENTER> for screen.) ? ') ;
  1213.    readln(file_name) ;
  1214.    strip_leading_blanks(file_name) ;
  1215.    IF file_name = ''
  1216.     THEN file_name := 'con:' ;
  1217.    assign(rule_file,file_name) ;
  1218.    rewrite(rule_file) ;
  1219.    writeln(rule_file) ;
  1220.    rule_count := 1 ;
  1221.    used_attribs := NIL ;
  1222.    print_rule(list,NIL) ;
  1223.    print_prompts ;
  1224.    IF is_console(rule_file)
  1225.     THEN wait ;
  1226.    close(rule_file) ;
  1227.   END ; (* print_rule_list *)
  1228.  
  1229.  
  1230.  OVERLAY PROCEDURE print_tree(list : node_ptr) ;
  1231.   (* Print the tree. This is really just a pretty print routine, which
  1232.      indents each sub_list. *)
  1233.   VAR
  1234.    indent_level : counter ;
  1235.    tree_file : text_file ;
  1236.    file_name : string80 ;
  1237.  
  1238.   PROCEDURE print_the_tree(tree : node_ptr ; VAR indent : counter) ;
  1239.    VAR
  1240.     p : node_ptr ;
  1241.    BEGIN
  1242.     IF tree <> NIL
  1243.      THEN
  1244.       CASE tree^.tag OF
  1245.        number,
  1246.        symbol    : BEGIN
  1247.                     write(tree_file,attrib_value(tree),' ') ;
  1248.                     indent := indent + length(attrib_value(tree)) + 1 ;
  1249.                    END ;
  1250.        cons_node : BEGIN
  1251.                     write(tree_file,'(') ;
  1252.                     indent := indent + 1 ;
  1253.                     print_the_tree(head(tree),indent) ;
  1254.                     p := tail(tree) ;
  1255.                     WHILE p <> NIL DO
  1256.                      BEGIN
  1257.                       print_the_tree(head(p),indent) ;
  1258.                       IF list_length(p) > 1
  1259.                        THEN
  1260.                         BEGIN
  1261.                          writeln(tree_file) ;
  1262.                          write(tree_file,' ' : indent) ;
  1263.                         END ;
  1264.                       p := tail(p) ;
  1265.                      END ;
  1266.                     indent := indent - length(attrib_value(head(tree))) - 2 ;
  1267.                     write(tree_file,') ') ;
  1268.                    END ;
  1269.       END ;
  1270.    END ; (* print_the_tree *)
  1271.  
  1272.   BEGIN
  1273.    writeln ;
  1274.    write('Output the tree to what file (Press <ENTER> for screen.) ? ') ;
  1275.    readln(file_name) ;
  1276.    strip_leading_blanks(file_name) ;
  1277.    IF file_name = ''
  1278.     THEN file_name := 'con:' ;
  1279.    assign(tree_file,file_name) ;
  1280.    rewrite(tree_file) ;
  1281.    writeln(tree_file) ;
  1282.    indent_level := 0 ;
  1283.    print_the_tree(list,indent_level) ;
  1284.    writeln(tree_file) ;
  1285.    writeln(tree_file) ;
  1286.    IF is_console(tree_file)
  1287.     THEN wait ;
  1288.    close(tree_file) ;
  1289.   END ; (* print_tree *)
  1290.  
  1291.  
  1292.  OVERLAY FUNCTION got_file : boolean ;
  1293.   (* asks for an example file name and tries to open it. If it can't
  1294.      open the file, it complains and asks for a new file *)
  1295.   VAR
  1296.    example_name : string80 ;
  1297.   BEGIN
  1298.    writeln ;
  1299.    write('Example File (Press <ENTER> to quit.) : ') ;
  1300.    readln(example_name) ;
  1301.    IF example_name = ''
  1302.     THEN got_file := false
  1303.     ELSE
  1304.      BEGIN
  1305.       IF pos('.',example_name) = 0
  1306.        THEN example_name := concat(example_name,'.EX') ;
  1307.       IF open(example_file,example_name)
  1308.        THEN got_file := true
  1309.        ELSE
  1310.         BEGIN
  1311.          writeln ;
  1312.          writeln(toupper(example_name),' could not be found.') ;
  1313.          writeln ;
  1314.          got_file := got_file ;
  1315.         END ;
  1316.      END ;
  1317.   END ; (* got_file *)
  1318.  
  1319.  
  1320.  BEGIN
  1321.   free := NIL ;
  1322.   initial_heap := HeapPtr ;
  1323.   total_free := 0.0 ;
  1324.   clrscr ;
  1325.   WHILE got_file DO
  1326.    BEGIN
  1327.     build_table ;
  1328.     close(example_file) ;
  1329.     IF NOT conflicts(examples)
  1330.      THEN
  1331.       BEGIN
  1332.        saved_list := cons(attrib_list,examples) ;
  1333.        expand(examples,examples) ;
  1334.        writeln ;
  1335.        saved_list := cons(attrib_list,examples) ;
  1336.        c_list := classify_it ;
  1337.        saved_list := cons(c_list,attrib_list) ;
  1338.        writeln ;
  1339.        test_memory ;
  1340.        print_tree(c_list) ;
  1341.        writeln ;
  1342.        test_memory ;
  1343.        writeln ;
  1344.        print_rule_list(c_list) ;
  1345.        clrscr ;
  1346.       END ;
  1347.    END ;
  1348.  END.
  1349.  
  1350.  
  1351.  
  1352.  
  1353. ==================================================
  1354.  
  1355.                     INDUCE.INC
  1356.  
  1357. ==================================================
  1358.  
  1359.  
  1360.  
  1361. (* ----------------------------------------------------------------------
  1362.         Utility Routines
  1363.    ---------------------------------------------------------------------- *)
  1364.  
  1365.  FUNCTION open(VAR f : text_file ; f_name : string80) : boolean ;
  1366.   (* open a file - returns true if the file exists and was opened properly
  1367.      f      - file pointer
  1368.      f_name - external name of the file *)
  1369.   BEGIN
  1370.    assign(f,f_name) ;
  1371.    (*$I- *)
  1372.    reset(f) ;
  1373.    (*$I+ *)
  1374.    open := (ioresult = 0) ;
  1375.   END ; (* open *)
  1376.  
  1377.  
  1378.  FUNCTION is_console(VAR f : text_file) : boolean ;
  1379.   (* return true if f is open on the system console
  1380.      for details of fibs and fib_ptrs see the Turbo Pascal ver 3.0 reference
  1381.      manual chapter 20. This should work under CP/M-86 or 80, but we haven't
  1382.      tried it. *)
  1383.   TYPE
  1384.    fib = ARRAY [0 .. 75] OF byte ;
  1385.   VAR
  1386.    fib_ptr : ^fib ;
  1387.    dev_type : byte ;
  1388.   BEGIN
  1389.    fib_ptr := addr(f) ;
  1390.    dev_type := fib_ptr^[2] AND $07 ;
  1391.    is_console := (dev_type = 1) OR (dev_type = 2) ;
  1392.   END ; (* is_console *)
  1393.  
  1394.  
  1395.  PROCEDURE strip_leading_blanks(VAR s : string80) ;
  1396.   BEGIN
  1397.    IF length(s) > 0
  1398.     THEN
  1399.      IF (s[1] = ' ') OR (s[1] = tab)
  1400.       THEN
  1401.        BEGIN
  1402.         delete(s,1,1) ;
  1403.         strip_leading_blanks(s) ;
  1404.        END ;
  1405.   END ; (* strip_leading_blanks *)
  1406.  
  1407.  
  1408.  PROCEDURE strip_trailing_blanks(VAR s : string80) ;
  1409.   BEGIN
  1410.    IF length(s) > 0
  1411.     THEN
  1412.      IF (s[length(s)] = ' ') OR (s[length(s)] = tab)
  1413.       THEN
  1414.        BEGIN
  1415.         delete(s,length(s),1) ;
  1416.         strip_trailing_blanks(s) ;
  1417.        END ;
  1418.   END ; (* strip_trailing_blanks *)
  1419.  
  1420.  
  1421.  
  1422.  FUNCTION toupper(s : string80) : string80 ;
  1423.   (* returns s converted to upper case *)
  1424.   VAR
  1425.    i : byte ;
  1426.   BEGIN
  1427.    IF length(s) > 0
  1428.     THEN
  1429.      FOR i := 1 TO length(s) DO
  1430.       s[i] := upcase(s[i]) ;
  1431.    toupper := s ;
  1432.   END ; (* toupper *)
  1433.  
  1434.  
  1435.  FUNCTION toreal(s : string80) : real ;
  1436.   (* converts s to a real number
  1437.      This routine uses the Turbo intrinsic val to do the conversion.
  1438.      If s does not contain a legal representation of a number, it returns
  1439.      0.0  *)
  1440.   VAR
  1441.    num : real ;
  1442.    code : integer ;
  1443.   BEGIN
  1444.    strip_trailing_blanks(s) ;
  1445.    strip_leading_blanks(s) ;
  1446.    val(s,num,code) ;
  1447.    IF code = 0
  1448.     THEN toreal := num
  1449.     ELSE toreal := 0 ;
  1450.   END ; (* toreal *)
  1451.  
  1452.  
  1453.  FUNCTION is_number(s : string80) : boolean ;
  1454.   (* checks to see if s contains a legitimate numerical string.
  1455.      It ignores leading and trailing blanks *)
  1456.   VAR
  1457.    num : real ;
  1458.    code : integer ;
  1459.   BEGIN
  1460.    strip_trailing_blanks(s) ;
  1461.    strip_leading_blanks(s) ;
  1462.    IF s <> ''
  1463.     THEN val(s,num,code)
  1464.     ELSE code := -1 ;
  1465.    is_number := (code = 0) ;
  1466.   END ; (* is_number *)
  1467.  
  1468.  
  1469.  FUNCTION head(list : node_ptr) : node_ptr ;
  1470.   (* returns a pointer to the first item in the list.
  1471.      If the list is empty, it returns NIL.  *)
  1472.   BEGIN
  1473.    IF list = NIL
  1474.     THEN head := NIL
  1475.     ELSE head := list^.head_ptr ;
  1476.   END ; (* head *)
  1477.  
  1478.  
  1479.  FUNCTION tail(list : node_ptr) : node_ptr ;
  1480.   (* returns a pointer to a list starting at the second item in the list.
  1481.      Note - tail( (a b c) ) points to the list (b c), but
  1482.             tail( ((a b) c d) ) points to the list (c d) .  *)
  1483.   BEGIN
  1484.    IF list = NIL
  1485.     THEN tail := NIL
  1486.    ELSE
  1487.     CASE list^.tag OF
  1488.      cons_node : tail := list^.tail_ptr ;
  1489.      free_node : tail := list^.next_free ;
  1490.      ELSE        tail := NIL ;
  1491.     END ;
  1492.   END ; (* tail *)
  1493.  
  1494.  
  1495.  FUNCTION element(list : node_ptr ; elem_no : counter) : node_ptr ;
  1496.   (* returns a pointer to the element number elem_no in the list.
  1497.      element(list,1) points to list.
  1498.      element(list,2) is the same as tail(list).    *)
  1499.   VAR
  1500.    i : counter ;
  1501.   BEGIN
  1502.    FOR i := 1 TO elem_no - 1 DO
  1503.     list := tail(list) ;
  1504.    element := list ;
  1505.   END ; (* element *)
  1506.  
  1507.  
  1508.  FUNCTION allocation_size(x : counter) : counter ;
  1509.   (* Turbo 3.0 allocates memory in 8 byte blocks, this routine calculates the
  1510.      actual number of bytes returned for a request of x bytes.  *)
  1511.   BEGIN
  1512.    allocation_size := (((x - 1) DIV 8) + 1) * 8 ;
  1513.   END ; (* allocation_size *)
  1514.  
  1515.  
  1516.  FUNCTION node_size : counter ;
  1517.   (* calculates the base size of a node. Add the rest of the node to this
  1518.      to get the actual size of a node *)
  1519.   BEGIN
  1520.    node_size := 2 * sizeof(node_ptr) + sizeof(boolean) + sizeof(node_type) ;
  1521.   END ; (* node_size *)
  1522.  
  1523.  
  1524.  FUNCTION normalize(pt : node_ptr) : node_ptr ;
  1525.   (* returns a normalized pointer. Pointers are 32 bit addresses. The first
  1526.      16 bits contain the segment number and the second 16 bits contain the
  1527.      offset within the segment. Normalized pointers have offsets in the range
  1528.      $0 to $F (0 .. 15)    *)
  1529.   VAR
  1530.    pt_seg,pt_ofs : integer ;
  1531.   BEGIN
  1532.    pt_seg := seg(pt^) + (ofs(pt^) DIV 16) ;
  1533.    pt_ofs := ofs(pt^) MOD 16 ;
  1534.    normalize := ptr(pt_seg,pt_ofs) ;
  1535.   END ; (* normalize *)
  1536.  
  1537.  
  1538.  FUNCTION string_val(list : node_ptr) : string80 ;
  1539.   (* returns the string pointed to by list. If list points to a number
  1540.      node, it returns a string representing that number *)
  1541.   VAR
  1542.    s : string[15] ;
  1543.   BEGIN
  1544.    IF list = NIL
  1545.     THEN string_val := ''
  1546.    ELSE IF list^.tag = symbol
  1547.     THEN string_val := list^.string_data
  1548.    ELSE IF list^.tag = number
  1549.     THEN
  1550.      BEGIN
  1551.       str(list^.num_data : 14,s) ;
  1552.       string_val := s ;
  1553.      END
  1554.    ELSE string_val := '' ;
  1555.   END ; (* string_val *)
  1556.  
  1557.  
  1558.  FUNCTION num_val(list : node_ptr) : real ;
  1559.   (* returns the number pointed to by list. If list points to a string,
  1560.      it returns the numerical value of the string.   *)
  1561.   VAR
  1562.    s : string80 ;
  1563.    code : integer ;
  1564.    r : real ;
  1565.   BEGIN
  1566.    IF list = NIL
  1567.     THEN num_val := 0.0
  1568.    ELSE IF list^.tag = number
  1569.     THEN num_val := list^.num_data
  1570.    ELSE IF list^.tag = symbol
  1571.     THEN num_val := toreal(list^.string_data)
  1572.    ELSE num_val := 0.0 ;
  1573.   END ; (* num_val *)
  1574.  
  1575.  
  1576.  FUNCTION attrib_value(p : node_ptr) : string80 ;
  1577.   (* This routine is used by print_rule and print_tree to strip off
  1578.      ':number' from an attribute name.   *)
  1579.   BEGIN
  1580.    IF pos(':NUMBER',toupper(string_val(p))) > 0
  1581.     THEN attrib_value := copy(string_val(p),
  1582.                  1,pos(':NUMBER',toupper(string_val(p))) - 1)
  1583.     ELSE attrib_value := string_val(p) ;
  1584.   END ; (* attrib_value *)
  1585.  
  1586.  
  1587.  FUNCTION tag_value(list : node_ptr) : node_type ;
  1588.   (* returns the value of the tag for a node.     *)
  1589.   BEGIN
  1590.    IF list = NIL
  1591.     THEN tag_value := free_node
  1592.     ELSE tag_value := list^.tag ;
  1593.   END ; (* tag_value *)
  1594.  
  1595.  
  1596.  FUNCTION match_lists(list1,list2 : node_ptr) : boolean ;
  1597.   (* returns true if list1 and list2 are identical.
  1598.      Two lists are identical if they are both NIL or if their heads match
  1599.      and match_lists returns true for thier tails. *)
  1600.   BEGIN
  1601.    IF (list1 = NIL) AND (list2 = NIL)
  1602.     THEN match_lists := true
  1603.    ELSE IF (list1 = NIL) OR (list2 = NIL)
  1604.     THEN match_lists := false
  1605.    ELSE IF tag_value(head(list1)) <> tag_value(head(list2))
  1606.     THEN match_lists := false
  1607.    ELSE
  1608.     CASE tag_value(head(list1)) OF
  1609.      symbol    : IF string_val(head(list1)) = string_val(head(list2))
  1610.                   THEN match_lists := match_lists(tail(list1),tail(list2))
  1611.                   ELSE match_lists := false ;
  1612.      number    : IF num_val(head(list1)) = num_val(head(list2))
  1613.                   THEN match_lists := match_lists(tail(list1),tail(list2))
  1614.                   ELSE match_lists := false ;
  1615.      cons_node : IF match_lists(head(list1),head(list2))
  1616.                   THEN match_lists := match_lists(tail(list1),tail(list2))
  1617.                   ELSE match_lists := false ;
  1618.     END ;
  1619.   END ; (* match_lists *)
  1620.  
  1621.  
  1622.  FUNCTION on_list(s : string80 ; list : node_ptr) : boolean ;
  1623.   (* checks to see if s is on the list, list. s is on the list if it
  1624.      matches the head of the list or if on_list(tail(list)) returns true.  *)
  1625.   BEGIN
  1626.    IF list = NIL
  1627.     THEN on_list := false
  1628.    ELSE IF s = string_val(head(list))
  1629.     THEN on_list := true
  1630.    ELSE on_list := on_list(s,tail(list)) ;
  1631.   END ; (* on_list *)
  1632.  
  1633.  
  1634.  PROCEDURE print_list(list : node_ptr) ;
  1635.   (* recursively traverses the list and prints its elements. This is
  1636.      not a pretty printer, so the lists may look a bit messy.  *)
  1637.   VAR
  1638.    p : node_ptr ;
  1639.   BEGIN
  1640.    IF list <> NIL
  1641.     THEN
  1642.      CASE list^.tag OF
  1643.       symbol    : write(string_val(list),' ') ;
  1644.       number    : write(num_val(list) : 6,' ') ;
  1645.       cons_node : BEGIN
  1646.                    write('(') ;
  1647.                    p := list ;
  1648.                    WHILE p <> NIL DO
  1649.                     BEGIN
  1650.                      print_list(head(p)) ;
  1651.                      p := tail(p) ;
  1652.                     END ;
  1653.                    write(') ') ;
  1654.                   END ;
  1655.      END ;
  1656.   END ; (* print_list *)
  1657.  
  1658.  
  1659.  PROCEDURE get_memory(VAR p : node_ptr ; size : counter) ;
  1660.   (* On exit p contains a pointer to a block of allocation_size(size) bytes.
  1661.      If possible this routine tries to get memory from the free list before
  1662.      requesting it from the heap *)
  1663.   VAR
  1664.    blks : counter ;
  1665.    allocated : boolean ;
  1666.  
  1667.   PROCEDURE get_from_free(VAR list : node_ptr) ;
  1668.    (* Try and get need memory from the free list. This routine uses a
  1669.       first-fit algorithm to get the space. It takes the first free block it
  1670.       finds with enough storage. If the free block has more storage than was
  1671.       requested, the block is shrunk by the requested amount.  *)
  1672.    BEGIN
  1673.     IF list <> NIL
  1674.      THEN
  1675.       IF list^.block_cnt >= (blks - 1)
  1676.        THEN
  1677.         BEGIN
  1678.          p := normalize(ptr(seg(list^),ofs(list^) +
  1679.                                        (list^.block_cnt - blks + 1) * 8)) ;
  1680.          IF list^.block_cnt = blks - 1
  1681.           THEN list := list^.next_free
  1682.           ELSE list^.block_cnt := list^.block_cnt - blks ;
  1683.          allocated := true ;
  1684.          total_free := total_free - (blks * 8.0) ;
  1685.         END
  1686.        ELSE get_from_free(list^.next_free) ;
  1687.    END ; (* get_from_free *)
  1688.  
  1689.   BEGIN
  1690.    blks := ((size - 1) DIV 8) + 1 ;
  1691.    allocated := false ;
  1692.    get_from_free(free) ;
  1693.    IF NOT allocated
  1694.     THEN getmem(p,blks * 8) ;
  1695.   END ; (* get_memory *)
  1696.  
  1697.  
  1698.  FUNCTION alloc_str(s : string80) : node_ptr ;
  1699.   (* Allocate storage for a string and return a pointer to the new node.
  1700.      This routine only allocates enough storage for the actual number of
  1701.      characters in the string plus one for the length. Because of this,
  1702.      concatenating anything to the end of a string stored in a symbol node
  1703.      will lead to disaster. Copy the string to a new string do the
  1704.      concatenation and then allocate a new node.  *)
  1705.   VAR
  1706.    pt : node_ptr ;
  1707.   BEGIN
  1708.    get_memory(pt,allocation_size(sizeof(node_type) + sizeof(boolean) +
  1709.                                  length(s) + 1)) ;
  1710.    pt^.tag := symbol   ;
  1711.    pt^.string_data := s ;
  1712.    alloc_str := pt ;
  1713.   END ; (* alloc_str *)
  1714.  
  1715.  
  1716.  FUNCTION alloc_num(r : real) : node_ptr ;
  1717.   (* Allocate storage for a real number and return a pointer to the new node. *)
  1718.   VAR
  1719.    pt : node_ptr ;
  1720.   BEGIN
  1721.    get_memory(pt,allocation_size(sizeof(node_type) + sizeof(boolean) +
  1722.                                  sizeof(real))) ;
  1723.    pt^.tag := number ;
  1724.    pt^.num_data := r ;
  1725.    alloc_num := pt ;
  1726.   END ; (* alloc_num *)
  1727.  
  1728.  
  1729.  FUNCTION cons(new_node,list : node_ptr) : node_ptr ;
  1730.   (* Construct a list. This routine allocates storage for a new cons node.
  1731.      new_node points to the new head of the list. The tail pointer of the
  1732.      new node points to list. This routine adds the new cons node to the
  1733.      beginning of the list and returns a pointer to it. The list described
  1734.      in the comments at the beginning of the program could be constructed
  1735.      as cons(alloc_str('A'),cons(alloc_str('B'),cons(alloc_str('C'),NIL))). *)
  1736.   VAR
  1737.    p : node_ptr ;
  1738.   BEGIN
  1739.    get_memory(p,allocation_size(node_size)) ;
  1740.    p^.tag := cons_node ;
  1741.    p^.head_ptr := new_node ;
  1742.    p^.tail_ptr := list ;
  1743.    cons := p ;
  1744.   END ; (* cons *)
  1745.  
  1746.  
  1747.  FUNCTION append_list(list1,list2 : node_ptr) : node_ptr ;
  1748.   (* Append list2 to list1. This routine returns a pointer to the
  1749.      combined list. Appending is done by consing each item on the first
  1750.      list to the second list. This routine is one of the major sources of
  1751.      garbage so if garbage collection becomes a problem, you may want to
  1752.      rewrite it. *)
  1753.   BEGIN
  1754.    IF list1 = NIL
  1755.     THEN append_list := list2
  1756.     ELSE append_list := cons(head(list1),append_list(tail(list1),list2)) ;
  1757.   END ; (* append_list *)
  1758.  
  1759.  
  1760.  FUNCTION list_length(list : node_ptr) : counter ;
  1761.   (* returns the length of a list.
  1762.      Note - both (A B C) and ( (A B) C D) have length 3.   *)
  1763.   BEGIN
  1764.    IF list = NIL
  1765.     THEN list_length := 0
  1766.     ELSE list_length := 1 + list_length(list^.tail_ptr) ;
  1767.   END ; (* list_length *)
  1768.  
  1769.  
  1770.  FUNCTION copy_list(list : node_ptr) : node_ptr ;
  1771.   (* Returns a pointer to a copy of list. This routine allocates new nodes
  1772.      for each item in the original list *)
  1773.   BEGIN
  1774.    IF list = NIL
  1775.     THEN copy_list := NIL
  1776.     ELSE
  1777.      CASE tag_value(list) OF
  1778.       cons_node : copy_list := cons(copy_list(head(list)),copy_list(tail(list))) ;
  1779.       number    : copy_list := alloc_num(num_val(list)) ;
  1780.       symbol    : copy_list := alloc_str(string_val(list)) ;
  1781.      END ;
  1782.   END ; (* copy_list *)
  1783.  
  1784.  
  1785.  PROCEDURE collect_garbage ;
  1786.   (* This routine is specific to Turbo Pascal Ver 3.01
  1787.      It depends upon the fact that Turbo allocates memory in 8 byte blocks
  1788.      on the PC. If you recompile this program on another system be very
  1789.      careful with this routine.
  1790.      Garbage collection proceeds in three phases:
  1791.       unmark  - free all memory between the initial_heap^ and the current
  1792.                 top of the heap.
  1793.       mark    - mark everything on the saved_list as being in ues.
  1794.       release - gather all unmarked blocks and put them on the free list.
  1795.      The collector displays a '*' on the screen to let you know it is
  1796.       operating.  *)
  1797.  
  1798.   FUNCTION lower(p1,p2 : node_ptr) : boolean ;
  1799.    (* returns true if p1 points to a lower memory address than p2 *)
  1800.    BEGIN
  1801.     p1 := normalize(p1) ;
  1802.     p2 := normalize(p2) ;
  1803.     lower := (seg(p1^) < seg(p2^)) OR
  1804.               ((seg(p1^) = seg(p2^)) AND (ofs(p1^) < ofs(p2^))) ;
  1805.    END ; (* lower *)
  1806.  
  1807.   PROCEDURE mark(list : node_ptr) ;
  1808.    (* Mark the blocks on list as being in use. Since a node may be on several
  1809.       lists at one time, if it is already marked we don't continue processing
  1810.       the tail of the list. *)
  1811.    BEGIN
  1812.     IF list <> NIL
  1813.      THEN
  1814.       BEGIN
  1815.        IF NOT list^.in_use
  1816.         THEN
  1817.          BEGIN
  1818.           list^.in_use := true ;
  1819.           IF list^.tag = cons_node
  1820.            THEN
  1821.             BEGIN
  1822.              mark(head(list)) ;
  1823.              mark(tail(list)) ;
  1824.             END ;
  1825.          END ;
  1826.       END ;
  1827.    END ; (* mark *)
  1828.  
  1829.   PROCEDURE unmark_mem ;
  1830.    (* Go through memory from initial_heap^ to HeapPtr^ and mark each node
  1831.       as not in use. The tricky part here is updating the pointer p to point
  1832.       to the next cell. *)
  1833.    VAR
  1834.     p : node_ptr ;
  1835.     string_base,node_allocation : counter ;
  1836.    BEGIN
  1837.     string_base := sizeof(node_type) + sizeof(boolean) ;
  1838.     p := normalize(initial_heap) ;
  1839.     node_allocation := allocation_size(node_size) ;
  1840.     WHILE lower(p,HeapPtr) DO
  1841.      BEGIN
  1842.       p^.in_use := false ;
  1843.       CASE p^.tag OF
  1844.        cons_node : p := normalize(ptr(seg(p^),ofs(p^) + node_allocation)) ;
  1845.        free_node : p := normalize(ptr(seg(p^),ofs(p^) + (p^.block_cnt + 1) * 8)) ;
  1846.        number    : p := normalize(ptr(seg(p^),
  1847.                                   ofs(p^) +
  1848.                                   allocation_size(string_base + sizeof(real)))) ;
  1849.        symbol    : p := normalize(ptr(seg(p^),
  1850.                                   ofs(p^) +
  1851.                                   allocation_size(string_base +
  1852.                                                   length(p^.string_data) + 1))) ;
  1853.       END ;
  1854.      END ;
  1855.    END ; (* unmark_mem *)
  1856.  
  1857.   PROCEDURE release_mem ;
  1858.    (* This procedure does the actual collection and compaction of nodes.
  1859.       This is the slow phase of garbage collection because of all the pointer
  1860.       manipulation.  *)
  1861.    VAR
  1862.     heap_top : node_ptr ;
  1863.     string_base,node_allocation,string_allocation,block_allocation : counter ;
  1864.  
  1865.    PROCEDURE free_memory(pt : node_ptr ; size : counter) ;
  1866.     (* return size bytes pointed to by pt to the free list. If pt points to
  1867.        a block next to the head of the free list combine it with the top
  1868.        free node. total_free keeps track of the total number of free bytes. *)
  1869.     VAR
  1870.      blks : counter ;
  1871.     BEGIN
  1872.      blks := ((size - 1) DIV 8) + 1 ;
  1873.      pt^.tag := free_node ;
  1874.      IF normalize(ptr(seg(pt^),ofs(pt^) + 8 * blks)) = free
  1875.       THEN
  1876.        BEGIN
  1877.         pt^.next_free := free^.next_free ;
  1878.         pt^.block_cnt := free^.block_cnt + blks ;
  1879.         free := pt ;
  1880.        END
  1881.      ELSE IF normalize(ptr(seg(free^),ofs(free^) + 8 * (free^.block_cnt + 1))) =
  1882.              normalize(pt)
  1883.       THEN free^.block_cnt := free^.block_cnt + blks
  1884.      ELSE
  1885.       BEGIN
  1886.        pt^.next_free := free ;
  1887.        pt^.block_cnt := blks - 1 ;
  1888.        free := pt ;
  1889.       END ;
  1890.      total_free := total_free + (blks * 8.0) ;
  1891.     END ; (* free_memory *)
  1892.  
  1893.    PROCEDURE do_release ;
  1894.     (* This routine sweeps through memory and checks for nodes with
  1895.        in_use = false. *)
  1896.     VAR
  1897.      p : node_ptr ;
  1898.     BEGIN
  1899.      p := normalize(initial_heap) ;
  1900.      WHILE lower(p,heap_top) DO
  1901.       CASE p^.tag OF
  1902.        cons_node : BEGIN
  1903.                     IF NOT p^.in_use
  1904.                      THEN free_memory(p,node_size) ;
  1905.                     p := normalize(ptr(seg(p^),ofs(p^) + node_allocation)) ;
  1906.                    END ;
  1907.        free_node : BEGIN
  1908.                     block_allocation := (p^.block_cnt + 1) * 8 ;
  1909.                     free_memory(p,block_allocation) ;
  1910.                     p := normalize(ptr(seg(p^),ofs(p^) + block_allocation)) ;
  1911.                    END ;
  1912.        number    : BEGIN
  1913.                     block_allocation := allocation_size(string_base +
  1914.                                                         sizeof(real)) ;
  1915.                     IF NOT p^.in_use
  1916.                      THEN free_memory(p,block_allocation) ;
  1917.                     p := normalize(ptr(seg(p^),ofs(p^) + block_allocation)) ;
  1918.                    END ;
  1919.        symbol    : BEGIN
  1920.                     string_allocation := allocation_size(string_base +
  1921.                                                 length(p^.string_data) + 1) ;
  1922.                     IF NOT p^.in_use
  1923.                      THEN free_memory(p,string_base + length(p^.string_data)
  1924.                                       + 1) ;
  1925.                     p := normalize(ptr(seg(p^),ofs(p^) + string_allocation)) ;
  1926.                    END ;
  1927.       END ;
  1928.     END ; (* do_release *)
  1929.  
  1930.    BEGIN
  1931.     free := NIL ;
  1932.     total_free := 0.0 ;
  1933.     heap_top := HeapPtr ;
  1934.     string_base := sizeof(node_type) + sizeof(boolean) ;
  1935.     node_allocation := allocation_size(node_size) ;
  1936.     do_release ;
  1937.    END ; (* release_mem *)
  1938.  
  1939.   BEGIN
  1940.    write('*') ;
  1941.    unmark_mem ;
  1942.    mark(saved_list) ;
  1943.    release_mem ;
  1944.    write(back_space) ;
  1945.    clreol ;
  1946.   END ; (* collect_garbage *)
  1947.  
  1948.  
  1949.  PROCEDURE test_memory ;
  1950.   (* This routine activates the garbage collector, if the the total available
  1951.      memory (free_list + heap) is less than a specified amount. Lowering the
  1952.      minimum causes garbage collection to be called less often, but if you
  1953.      make it too small you may not have enough room left for recursion or any
  1954.      temporary lists you need. Using 10000 is probably being overly
  1955.      cautious.   *)
  1956.   BEGIN
  1957.    IF (memavail * 16.0) + total_free < 10000
  1958.     THEN collect_garbage ;
  1959.   END ; (* test_memory *)
  1960.  
  1961.  
  1962.  PROCEDURE wait ;
  1963.   (* Just like it says. It waits for the user to press a key before
  1964.      continuing. *)
  1965.   VAR
  1966.    ch : char ;
  1967.   BEGIN
  1968.    writeln ;
  1969.    writeln ;
  1970.    write('Press any key to continue. ') ;
  1971.    read(kbd,ch) ;
  1972.    write(return) ;
  1973.    clreol ;
  1974.   END ; (* wait *)
  1975.  
  1976.  
  1977. (* ------------------------------------------------------------------------
  1978.         End of utility routines
  1979.    ------------------------------------------------------------------------ *)
  1980.  
  1981.  
  1982.  
  1983. ==================================================
  1984.  
  1985.                     CONFER.TXT
  1986.  
  1987. ==================================================
  1988.  
  1989.  
  1990. @trans broad and flat @
  1991. the shape of the leaves is /not/ broad and flat
  1992. @
  1993. @prompt broad and flat @
  1994. Is the shape of the leaves broad and flat ?
  1995. @
  1996. @trans class @
  1997. the botanical class the tree belongs to 
  1998. @
  1999. @trans leaf shape @
  2000. the leaf shape
  2001. @
  2002. @prompt leaf shape @
  2003. Is the leaf shape needlelike or scalelike ?
  2004. @trans family @
  2005. the botanical family the tree belongs to
  2006. @
  2007. @trans even pattern @
  2008. the needles do /not/ line up along two sides of the branch
  2009. @
  2010. @prompt even pattern @
  2011. Do the needles grow in two lines along the sides of the branch ?
  2012. @
  2013. @trans silvery line @
  2014. there is /not/ a silvery line underneath the needles
  2015. @
  2016. @prompt silvery line @
  2017. Is there a silvery line underneath the needles ?
  2018. @
  2019. @trans decurrent @
  2020. the stem of the needle does /not/ grow down along the twig
  2021. @
  2022. @prompt decurrent @
  2023. Do the stems of the needles grow down along the twig ?
  2024. @
  2025. @trans spray shape @
  2026. the shape of the leaf spray 
  2027. @
  2028. @prompt spray shape @
  2029. Is the shape of the leaf spray round or flat ?
  2030. @
  2031. @trans random needles @
  2032. there are /not/ a few needles scattered along the branch
  2033. @
  2034. @prompt random needles @
  2035. Are there at least a few needles scattered along the branch ?             
  2036. @
  2037. @trans bundle @
  2038. the needles are /not/ grouped together in bundles of 2 to 5
  2039. @
  2040. @prompt bundle @
  2041. Are the needles grouped together in bundles of 2 to 5 ?
  2042. @
  2043. @trans needle scar @
  2044. the scar left when a needle is removed
  2045. @
  2046. @prompt needle scar @
  2047. Pull a needle off the twig. Is the scar it makes raised or depressed ?
  2048. @
  2049. @trans cross section @
  2050. the cross section of the needle 
  2051. @
  2052. @prompt cross section @
  2053. Pull off a needle.  Is its cross section flat, triangular or 4-sided ?
  2054. @
  2055. @trans genus @
  2056. the botanical genus of the tree
  2057. @
  2058. 1
  2059. If broad and flat is yes
  2060. then class is angiosperm .
  2061. 2
  2062. If broad and flat is no
  2063. then class is gymnosperm .
  2064. 3
  2065. If class is gymnosperm 
  2066. and leaf shape is scalelike
  2067. then family is cypress .
  2068. 4
  2069. If class is gymnosperm 
  2070. and leaf shape is needlelike
  2071. and even pattern is no
  2072. then family is pine .
  2073. 5
  2074. If class is gymnosperm
  2075. and leaf shape is needlelike
  2076. and even pattern is yes
  2077. and silvery line is yes
  2078. then family is pine .
  2079. 6
  2080. If family is cypress  
  2081. and spray shape is round    
  2082. and random needles is yes
  2083. then genus is juniper .
  2084. 7
  2085. If class is gymnosperm
  2086. and leaf shape is needlelike
  2087. and even pattern is yes
  2088. and silvery line is no
  2089. and decurrent is no
  2090. then family is bald cypress
  2091. and genus is bald cypress .
  2092. 8
  2093. If class is gymnosperm 
  2094. and leaf shape is needlelike
  2095. and even pattern is yes
  2096. and silvery line is no
  2097. and decurrent is yes
  2098. then family is yew
  2099. and genus is yew .
  2100.  
  2101. 9
  2102. If family is cypress
  2103. and spray shape is round
  2104. and random needles is no
  2105. then genus is white cedar .
  2106. 10
  2107. If family is cypress
  2108. and spray shape is flat
  2109. then genus is arbor vitae (thuja) .
  2110. 11
  2111. If family is pine
  2112. and bundle is yes
  2113. then genus is pine .
  2114. 12
  2115. If family is pine
  2116. and bundle is no
  2117. and silvery line is yes
  2118. and needle scar is depressed
  2119. then genus is fir .
  2120. 13
  2121. If family is pine
  2122. and bundle is no
  2123. and silvery line is yes
  2124. and needle scar is raised
  2125. then genus is hemlock .
  2126. 14
  2127. If family is pine
  2128. and bundle is no
  2129. and silvery line is no
  2130. and cross section is triangular
  2131. then genus is larch .
  2132. 15
  2133. If family is pine
  2134. and bundle is no
  2135. and silvery line is no
  2136. and cross section is four sided
  2137. then genus is spruce .
  2138. 16
  2139. If family is pine 
  2140. and bundle is no
  2141. and silvery line is no
  2142. and cross section is flat 
  2143. then genus is douglas fir .
  2144.  
  2145.  
  2146.  
  2147.  
  2148.  
  2149.  
  2150.  
  2151.  
  2152. ==================================================
  2153.  
  2154.                     CONIFERS.TXT
  2155.  
  2156. ==================================================
  2157.  
  2158.  
  2159.  
  2160. @trans broad and flat @
  2161. the shape of the leaves is /not/ broad and flat
  2162. @
  2163.  
  2164. @prompt broad and flat @
  2165. Is the shape of the leaves broad and flat ?
  2166. @
  2167.  
  2168. @trans class @
  2169. the botanical class the tree belongs to 
  2170. @
  2171.  
  2172. @trans leaf shape @
  2173. the leaf shape
  2174. @
  2175.  
  2176. @prompt leaf shape @
  2177. Is the leaf shape needlelike or scalelike ?
  2178.  
  2179. @trans family @
  2180. the botanical family the tree belongs to
  2181. @
  2182.  
  2183. @trans even pattern @
  2184. the needles do /not/ line up along two sides of the branch
  2185. @
  2186.  
  2187. @prompt even pattern @
  2188. Do the needles grow in two lines along the sides of the branch ?
  2189. @
  2190.  
  2191. @trans silvery line @
  2192. there is /not/ a silvery line underneath the needles
  2193. @
  2194.  
  2195. @prompt silvery line @
  2196. Is there a silvery line underneath the needles ?
  2197. @
  2198.  
  2199. @trans decurrent @
  2200. the stem of the needle does /not/ grow down along the twig
  2201. @
  2202.  
  2203. @prompt decurrent @
  2204. Do the stems of the needles grow down along the twig ?
  2205. @
  2206.  
  2207. @trans spray shape @
  2208. the shape of the leaf spray 
  2209. @
  2210.  
  2211. @prompt spray shape @
  2212. Is the shape of the leaf spray round or flat ?
  2213. @
  2214.  
  2215. @trans random needles @
  2216. there are /not/ a few needles scattered along the branch
  2217. @
  2218.  
  2219. @prompt random needles @
  2220. Arσáthere at least a few needles scattered along the branch ?             
  2221. @
  2222.  
  2223. @trans bundle @
  2224. the needles are /not/ grouped together in bundles of 2 to 5
  2225. @
  2226.  
  2227. @prompt bundle @
  2228. Are the needles grouped together in bundles of 2 to 5 ?
  2229. @
  2230.  
  2231. @trans needle scar @
  2232. the scar left when a needle is removed
  2233. @
  2234.  
  2235. @prompt needle scar @
  2236. Pull off a needle.  Is the scar that is left raised or depressed ?
  2237. @
  2238.  
  2239. @trans cross section @
  2240. the cross section of the needle 
  2241. @
  2242.  
  2243. @prompt cross section @
  2244. Pull off a needle.  Is its cross section flat, triangular or 4-sided ?
  2245. @
  2246.  
  2247. @trans genus @
  2248. the botanical genus of the tree
  2249. @
  2250.  
  2251. 1
  2252. If broad and flat is yes
  2253. then class is angiosperm .
  2254.  
  2255. 2
  2256. If broad and flat is no
  2257. then class is gymnosperm .
  2258.  
  2259. 3
  2260. If class is gymnosperm 
  2261. and leaf shape is scalelike
  2262. then family is cypress .
  2263.  
  2264. 4
  2265. If class is gymnosperm 
  2266. and leaf shape is needlelike
  2267. and even pattern is no
  2268. then family is pine .
  2269.  
  2270. 5
  2271. If class is gymnosperm
  2272. and leaf shape is needlelike
  2273. and even pattern is yes
  2274. and silvery line is yes
  2275. then family is pine .
  2276.  
  2277. 6
  2278. If family is cypress  
  2279. and spray shape is round    
  2280. and random needles is yes
  2281. then genus is juniper .
  2282.  
  2283. 7
  2284. If class is gymnosperm
  2285. and leaf shape is needlelike
  2286. and even pattern is yes
  2287. and silvery line is no
  2288. and decurrent is no
  2289. then family is bald cypress
  2290. and genus is bald cypress .
  2291.  
  2292. 8
  2293. If class is gymnosperm 
  2294. and leaf shape is needlelike
  2295. and even pattern is yes
  2296. and silvery line is no
  2297. and decurrent is yes
  2298. then family is yew
  2299. and genus is yew .
  2300.  
  2301.  
  2302. 9
  2303. If family is cypress
  2304. and spray shape is round
  2305. and random needles is no
  2306. then genus is white cedar .
  2307.  
  2308. 10
  2309. If family is cypress
  2310. and spray shape is flat
  2311. then genus is arbor vitae (thuja) .
  2312.  
  2313. 11
  2314. If family is pine
  2315. and bundle is yes
  2316. then genus is pine .
  2317.  
  2318. 12
  2319. If family is pine
  2320. and bundle is no
  2321. and silvery line is yes
  2322. and needle scar is depressed
  2323. then genus is fir .
  2324.  
  2325. 13
  2326. If family is pine
  2327. and bundle is no
  2328. and silvery line is yes
  2329. and needle scar is raised
  2330. then genus is hemlock .
  2331.  
  2332. 14
  2333. If family is pine
  2334. and bundle is no
  2335. and silvery line is no
  2336. and cross section is triangular
  2337. then genus is larch .
  2338.  
  2339. 15
  2340. If family is pine
  2341. and bundle is no
  2342. and silvery line is no
  2343. and cross section is four sided
  2344. then genus is spruce .
  2345.  
  2346. 16
  2347. If family is pine 
  2348. and bundle is no
  2349. and silvery line is no
  2350. and cross section is flat 
  2351. then genus is douglas fir .
  2352.  
  2353.  
  2354.  
  2355.  
  2356.  
  2357.  
  2358. ==================================================
  2359.  
  2360.                     RULEXREF.PAS
  2361.  
  2362. ==================================================
  2363.  
  2364.  
  2365. (* Copyright 1984 MicroExpert Systems *)
  2366. (*$V- *)
  2367. PROGRAM rule_xref ;
  2368.  
  2369.  CONST
  2370.   word_size = 30 ;
  2371.   goal_size = 38 ;
  2372.   max_rule = 100 ;
  2373.  
  2374.  
  2375.  TYPE
  2376.   string80 = string[80] ;
  2377.   word = string[word_size] ;
  2378.   string1 = string[1] ;
  2379.   byte = 0 .. 255 ;
  2380.   counter = 0 .. maxint ;
  2381.   item_type = (cond,concld) ;
  2382.   item_ptr = ^item ;
  2383.   string_ptr = ^string_rec ;
  2384.   string_rec = RECORD
  2385.                 info : string80 ;
  2386.                 next_line : string_ptr ;
  2387.                END ;
  2388.   item = RECORD
  2389.           next : item_ptr ;
  2390.           attr : word ;
  2391.           CASE boolean OF
  2392.            TRUE : ( val : word ;
  2393.                     kind : item_type ;
  2394.                     rule_no : counter ) ;
  2395.            FALSE : ( prompt_ptr : string_ptr ;
  2396.                      trans_ptr : string_ptr ;
  2397.                      val_ptr : item_ptr) ;
  2398.          END ;
  2399.  
  2400.  
  2401.  VAR
  2402.   line : string80 ;
  2403.   rule_file : text ;
  2404.   token : word ;
  2405.   etx : string1 ;
  2406.   free,attr_list : item_ptr ;
  2407.   biggest_rule : counter ;
  2408.  
  2409.  
  2410.  PROCEDURE toupper(VAR s : string80) ;
  2411.   VAR
  2412.    i : byte ;
  2413.   BEGIN
  2414.    IF length(s) > 0
  2415.     THEN
  2416.      FOR i := 1 TO length(s) DO
  2417.       IF s[i] IN ['a' .. 'z']
  2418.        THEN s[i] := chr(ord(s[i]) - 32) ;
  2419.   END ; (* toupper *)
  2420.  
  2421.  
  2422.  PROCEDURE makestr(VAR s : string80 ; len : byte) ;
  2423.   VAR
  2424.    old_length : byte ;
  2425.   BEGIN
  2426.    old_length := length(s) ;
  2427.    (*$R- *)
  2428.    s[0] := chr(len) ;
  2429.    (*$R+ *)
  2430.    IF old_length < len
  2431.     THEN fillchar(s[old_length+1],len - old_length,' ') ;
  2432.   END ; (* makestr *)
  2433.  
  2434.  
  2435.  FUNCTION tointeger(s : word) : integer ;
  2436.   BEGIN
  2437.    IF length(s) = 0
  2438.     THEN tointeger := 0
  2439.    ELSE IF s[1] = '-'
  2440.     THEN
  2441.      BEGIN
  2442.       delete(s,1,1) ;
  2443.       tointeger := - tointeger(s) ;
  2444.      END
  2445.    ELSE IF NOT (s[1] IN ['0' .. '9'])
  2446.     THEN
  2447.      BEGIN
  2448.       delete(s,1,1) ;
  2449.       tointeger := tointeger(s) ;
  2450.      END
  2451.    ELSE IF length(s) = 1
  2452.     THEN tointeger := ord(s[1]) - ord('0')
  2453.    ELSE tointeger := tointeger(copy(s,length(s),1))
  2454.                      + 10 * tointeger(copy(s,1,length(s)-1)) ;
  2455.   END ; (* tointeger *)
  2456.  
  2457.  
  2458.  PROCEDURE strip_leading_blanks(VAR s : string80) ;
  2459.   BEGIN
  2460.    IF length(s) > 0
  2461.     THEN
  2462.      IF s[1] = ' '
  2463.       THEN
  2464.        BEGIN
  2465.         delete(s,1,1) ;
  2466.         strip_leading_blanks(s) ;
  2467.        END ;
  2468.   END ; (* strip_leading_blanks *)
  2469.  
  2470.  
  2471.  PROCEDURE strip_trailing_blanks(VAR s : string80) ;
  2472.   BEGIN
  2473.    IF length(s) > 0
  2474.     THEN
  2475.      IF s[length(s)] = ' '
  2476.       THEN
  2477.        BEGIN
  2478.         delete(s,length(s),1) ;
  2479.         strip_trailing_blanks(s) ;
  2480.        END ;
  2481.   END ; (* strip_leading_blanks *)
  2482.  
  2483.  
  2484.  FUNCTION on_list(s : word ; list : item_ptr ; VAR at : item_ptr) : boolean ;
  2485.  
  2486.   FUNCTION find_it(list : item_ptr) : boolean ;
  2487.    BEGIN
  2488.     IF list = NIL
  2489.      THEN find_it := FALSE
  2490.     ELSE IF s = list^.attr
  2491.      THEN
  2492.       BEGIN
  2493.        at := list ;
  2494.        find_it := TRUE ;
  2495.       END
  2496.     ELSE find_it := find_it(list^.next) ;
  2497.    END ; (* on_list *)
  2498.  
  2499.   BEGIN
  2500.    at := NIL ;
  2501.    toupper(s) ;
  2502.    makestr(s,word_size) ;
  2503.    on_list := find_it(list) ;
  2504.   END ; (* on_list *)
  2505.  
  2506.  
  2507.  FUNCTION alloc : item_ptr ;
  2508.   VAR
  2509.    p : item_ptr ;
  2510.   BEGIN
  2511.    IF free = NIL
  2512.     THEN new(p)
  2513.     ELSE
  2514.      BEGIN
  2515.       p := free ;
  2516.       free := free^.next ;
  2517.      END ;
  2518.    alloc := p ;
  2519.   END ; (* alloc *)
  2520.  
  2521.  
  2522.  PROCEDURE dispose_item(p : item_ptr) ;
  2523.   BEGIN
  2524.    p^.next := free ;
  2525.    free := p ;
  2526.   END ; (* dispose_item *)
  2527.  
  2528.  
  2529.  PROCEDURE remove_item(VAR list : item_ptr) ;
  2530.   VAR
  2531.    p : item_ptr ;
  2532.   BEGIN
  2533.    IF list <> NIL
  2534.     THEN
  2535.      BEGIN
  2536.       p := list ;
  2537.       list := list^.next ;
  2538.       dispose_item(p) ;
  2539.      END ;
  2540.   END ; (* remove_item *)
  2541.  
  2542.  
  2543.  PROCEDURE remove_list(VAR list : item_ptr) ;
  2544.   BEGIN
  2545.    IF list <> NIL
  2546.     THEN
  2547.      BEGIN
  2548.       remove_item(list) ;
  2549.       remove_list(list) ;
  2550.      END ;
  2551.   END ; (* remove_list *)
  2552.  
  2553.  
  2554.  PROCEDURE new_item(s1,s2 : string80 ; typ : item_type ;
  2555.                     rule_num : counter ; VAR list : item_ptr) ;
  2556.   VAR
  2557.    p : item_ptr ;
  2558.   BEGIN
  2559.    makestr(s1,word_size) ;
  2560.    toupper(s1) ;
  2561.    makestr(s2,word_size) ;
  2562.    toupper(s2) ;
  2563.    p := alloc ;
  2564.    WITH p^ DO
  2565.     BEGIN
  2566.      attr := s1 ;
  2567.      val := s2 ;
  2568.      kind := typ ;
  2569.      rule_no := rule_num ;
  2570.     END ;
  2571.    p^.next := list ;
  2572.    list := p ;
  2573.   END ; (* new_item *)
  2574.  
  2575.  
  2576.  PROCEDURE put_on_end(s1,s2 : string80 ; typ : item_type ; rule_no : counter ;
  2577.                       VAR list : item_ptr) ;
  2578.   BEGIN
  2579.    IF list = NIL
  2580.     THEN new_item(s1,s2,typ,rule_no,list)
  2581.    ELSE put_on_end(s1,s2,typ,rule_no,list^.next) ;
  2582.   END ; (* put_on_end *)
  2583.  
  2584.  
  2585.  PROCEDURE read_the_file ;
  2586.   VAR
  2587.    error : boolean ;
  2588.  
  2589.   PROCEDURE scanf ;
  2590.  
  2591.    PROCEDURE get_line ;
  2592.     BEGIN
  2593.      readln(rule_file,line) ;
  2594.      IF eof(rule_file)
  2595.       THEN line := etx ;
  2596.     END ; (* get_line *)
  2597.  
  2598.    PROCEDURE get_token ;
  2599.     VAR
  2600.      i : -1 .. 255 ;
  2601.     BEGIN
  2602.      strip_leading_blanks(line) ;
  2603.      IF length(line) > 0
  2604.       THEN
  2605.        BEGIN
  2606.         i := pos(' ',line) - 1 ;
  2607.         IF i <= 0
  2608.          THEN i := length(line) ;
  2609.         token := copy(line,1,i) ;
  2610.         toupper(token) ;
  2611.         delete(line,1,i) ;
  2612.        END
  2613.       ELSE
  2614.        BEGIN
  2615.         get_line ;
  2616.         get_token ;
  2617.        END ;
  2618.     END ; (* get_token *)
  2619.  
  2620.    BEGIN
  2621.     IF eof(rule_file)
  2622.      THEN token := etx
  2623.      ELSE get_token ;
  2624.    END ; (* scanf *)
  2625.  
  2626.   PROCEDURE at_line ;
  2627.    TYPE
  2628.     info_type = (prmpt,trns,nl) ;
  2629.    VAR
  2630.     typ : info_type ;
  2631.     attr_word : string80 ;
  2632.  
  2633.    PROCEDURE read_a_line ;
  2634.     BEGIN
  2635.      readln(rule_file,line) ;
  2636.      write('.') ;
  2637.     END ; (* read_a_line *)
  2638.  
  2639.    PROCEDURE insert_attr(s : word ; typ : info_type ; line : string80 ;
  2640.                          VAR list : item_ptr) ;
  2641.  
  2642.     PROCEDURE put_in_list(VAR p : item_ptr) ;
  2643.  
  2644.      PROCEDURE new_attr_item ;
  2645.       VAR
  2646.        s_ptr : string_ptr ;
  2647.        ptr : item_ptr ;
  2648.       BEGIN
  2649.        ptr := alloc ;
  2650.        ptr^.attr := s ;
  2651.        new(s_ptr) ;
  2652.        s_ptr^.info  := line ;
  2653.        s_ptr^.next_line := NIL ;
  2654.        CASE typ OF
  2655.         prmpt : BEGIN
  2656.                  ptr^.prompt_ptr := s_ptr ;
  2657.                  ptr^.trans_ptr := NIL ;
  2658.                 END ;
  2659.         trns  : BEGIN
  2660.                  ptr^.prompt_ptr := NIL ;
  2661.                  ptr^.trans_ptr := s_ptr ;
  2662.                 END ;
  2663.        END ;
  2664.        ptr^.val_ptr := NIL ;
  2665.        ptr^.next := p ;
  2666.        p := ptr ;
  2667.       END ; (* new_attr_item *)
  2668.  
  2669.      PROCEDURE old_attr_item ;
  2670.       VAR
  2671.        s_ptr : string_ptr ;
  2672.  
  2673.       PROCEDURE end_insert(VAR p_list : string_ptr) ;
  2674.        BEGIN
  2675.         IF p_list = NIL
  2676.          THEN p_list := s_ptr
  2677.          ELSE end_insert(p_list^.next_line) ;
  2678.        END ; (* end_insert *)
  2679.  
  2680.       BEGIN
  2681.        new(s_ptr) ;
  2682.        s_ptr^.info := line ;
  2683.        s_ptr^.next_line := NIL ;
  2684.        CASE typ OF
  2685.         prmpt : end_insert(p^.prompt_ptr) ;
  2686.         trns  : end_insert(p^.trans_ptr) ;
  2687.        END ;
  2688.       END ; (* old_attr_item *)
  2689.  
  2690.      BEGIN
  2691.       IF p = NIL
  2692.        THEN new_attr_item
  2693.       ELSE IF s < p^.attr
  2694.        THEN new_attr_item
  2695.       ELSE IF s = p^.attr
  2696.        THEN old_attr_item
  2697.       ELSE put_in_list(p^.next) ;
  2698.      END ; (* put_in_list *)
  2699.  
  2700.     BEGIN
  2701.      makestr(s,word_size) ;
  2702.      put_in_list(list) ;
  2703.     END ; (* insert_attr *)
  2704.  
  2705.    BEGIN
  2706.     attr_word := '' ;
  2707.     IF token = '@PROMPT'
  2708.      THEN typ := prmpt
  2709.      ELSE typ := trns ;
  2710.     scanf ;
  2711.     WHILE token <> '@' DO
  2712.      BEGIN
  2713.       attr_word := concat(attr_word,token,' ') ;
  2714.       scanf ;
  2715.      END ;
  2716.     read_a_line ;
  2717.     WHILE (NOT eof(rule_file)) AND (pos('@',line) = 0) DO
  2718.      BEGIN
  2719.       insert_attr(attr_word,typ,line,attr_list) ;
  2720.       read_a_line ;
  2721.      END ;
  2722.     scanf ;
  2723.    END ; (* at_line *)
  2724.  
  2725.   PROCEDURE rule ;
  2726.    VAR
  2727.     attr,pred,val : string80 ;
  2728.     rule_no : counter ;
  2729.     kind : item_type ;
  2730.     num : string[4] ;
  2731.     at : item_ptr ;
  2732.  
  2733.    PROCEDURE runout ;
  2734.     BEGIN
  2735.      WHILE (token <> '.') AND (pos('@',token) <> 0) AND (token <> etx) DO
  2736.       scanf ;
  2737.     END ; (* runout *)
  2738.  
  2739.    PROCEDURE error_rtn(err_num : byte) ;
  2740.     BEGIN
  2741.      writeln ;
  2742.      write('***** error - rule : ',rule_no : 3,' ***** ') ;
  2743.      CASE err_num OF
  2744.       1 : writeln('Couldn''t find rule number.') ;
  2745.       2 : writeln('Missing ''IF''.') ;
  2746.       3 : writeln('Missing ''THEN''.') ;
  2747.       4 : writeln('Couldn''t find an attribute.') ;
  2748.       5 : writeln('Couldn''t find a value.') ;
  2749.      END ;
  2750.      runout ;
  2751.      error := TRUE ;
  2752.     END ; (* error_rtn *)
  2753.  
  2754.    FUNCTION legal_pred(w : word) : boolean ;
  2755.     BEGIN
  2756.      legal_pred := (w = 'IS') ;
  2757.     END ; (* legal_pred *)
  2758.  
  2759.    PROCEDURE attribute ;
  2760.     BEGIN
  2761.      IF NOT legal_pred(token)
  2762.       THEN
  2763.        BEGIN
  2764.         attr := concat(attr,token,' ') ;
  2765.         scanf ;
  2766.         attribute ;
  2767.        END ;
  2768.     END ; (* attribute *)
  2769.  
  2770.    PROCEDURE predicate ;
  2771.     BEGIN
  2772.      IF attr = ''
  2773.       THEN error_rtn(4)
  2774.      ELSE IF legal_pred(token)
  2775.       THEN
  2776.        BEGIN
  2777.         pred := token ;
  2778.         scanf ;
  2779.        END ;
  2780.     END ; (* predicate *)
  2781.  
  2782.    PROCEDURE value ;
  2783.     BEGIN
  2784.      IF pred = ''
  2785.       THEN error_rtn(5)
  2786.      ELSE IF (token <> '.') AND (token <> etx)
  2787.              AND (token <> 'AND') AND (token <> 'THEN')
  2788.       THEN
  2789.        BEGIN
  2790.         val := concat(val,token,' ') ;
  2791.         scanf ;
  2792.         value ;
  2793.        END ;
  2794.     END ; (* value *)
  2795.  
  2796.    PROCEDURE clause ;
  2797.  
  2798.     PROCEDURE put_in_val_list(s1,s2 : string80 ; kind : item_type ;
  2799.                               rule_no : counter) ;
  2800.      VAR
  2801.       at : item_ptr ;
  2802.      
  2803.      PROCEDURE put_in_list(VAR list : item_ptr) ;
  2804.       BEGIN
  2805.        IF list = NIL
  2806.         THEN new_item(s1,s2,kind,rule_no,list)
  2807.        ELSE IF list^.val > s2
  2808.         THEN new_item(s1,s2,kind,rule_no,list)
  2809.        ELSE put_in_list(list^.next) ;
  2810.       END ; (* put_in_list *)
  2811.  
  2812.      PROCEDURE make_new_attr_item ;
  2813.       VAR
  2814.        ptr : item_ptr ;
  2815.  
  2816.       PROCEDURE put_in_attr_list(VAR list :item_ptr) ;
  2817.        BEGIN
  2818.         IF list = NIL
  2819.          THEN list := ptr
  2820.         ELSE IF ptr^.attr < list^.attr
  2821.          THEN
  2822.           BEGIN
  2823.            ptr^.next := list ;
  2824.            list := ptr ;
  2825.           END
  2826.         ELSE put_in_attr_list(list^.next) ;
  2827.        END ; (* put_in_attr_list *)
  2828.        
  2829.       BEGIN
  2830.        ptr := alloc ;
  2831.        makestr(s1,word_size) ;
  2832.        toupper(s1) ;
  2833.        WITH ptr^ DO
  2834.         BEGIN
  2835.          attr := s1 ;
  2836.          next := NIL ;
  2837.          prompt_ptr := NIL ;
  2838.          trans_ptr := NIL ;
  2839.          val_ptr := NIL ;
  2840.         END ;
  2841.        put_in_attr_list(attr_list) ;
  2842.        makestr(s2,word_size) ;
  2843.        toupper(s2) ;
  2844.        put_in_list(ptr^.val_ptr) ;
  2845.       END ; (* make_new_attr_item*)
  2846.  
  2847.      BEGIN
  2848.       IF on_list(s1,attr_list,at)
  2849.        THEN
  2850.         BEGIN
  2851.          makestr(s2,word_size) ;
  2852.          toupper(s2) ;
  2853.          put_in_list(at^.val_ptr) ;
  2854.         END
  2855.        ELSE make_new_attr_item ;
  2856.      END ; (* put_in_val_list *)
  2857.  
  2858.     BEGIN
  2859.      attr := '' ;
  2860.      pred := '' ;
  2861.      val := '' ;
  2862.      attribute ;
  2863.      predicate ;
  2864.      IF NOT error
  2865.       THEN value ;
  2866.      IF NOT error
  2867.       THEN put_in_val_list(attr,val,kind,rule_no) ;
  2868.     END ; (* clause *)
  2869.  
  2870.    PROCEDURE condition ;
  2871.     BEGIN
  2872.      IF NOT error
  2873.       THEN
  2874.        BEGIN
  2875.         kind := cond ;
  2876.         clause ;
  2877.         IF token = 'AND'
  2878.          THEN
  2879.           BEGIN
  2880.            scanf ;
  2881.            condition ;
  2882.           END ;
  2883.        END ;
  2884.     END ; (* condition *)
  2885.  
  2886.    PROCEDURE conclusion ;
  2887.     BEGIN
  2888.      IF NOT error
  2889.       THEN
  2890.        BEGIN
  2891.         kind := concld ;
  2892.         clause ;
  2893.         IF token = 'AND'
  2894.          THEN
  2895.           BEGIN
  2896.            scanf ;
  2897.            conclusion ;
  2898.           END ;
  2899.        END ;
  2900.     END ; (* conclusion *)
  2901.  
  2902.    BEGIN
  2903.     rule_no := tointeger(token) ;
  2904.     IF rule_no > 0
  2905.      THEN
  2906.       BEGIN
  2907.        scanf ;
  2908.        IF token = 'IF'
  2909.         THEN
  2910.          BEGIN
  2911.           scanf ;
  2912.           condition ;
  2913.           IF (token = 'THEN') AND (NOT error)
  2914.            THEN
  2915.             BEGIN
  2916.              scanf ;
  2917.              conclusion ;
  2918.              IF (rule_no > biggest_rule) AND (NOT error)
  2919.               THEN biggest_rule := rule_no ;
  2920.             END
  2921.            ELSE error_rtn(3)
  2922.          END    ELSE error_rtn(2) ;
  2923.       END
  2924.      ELSE error_rtn(1) ;
  2925.    END ; (* rule *)
  2926.  
  2927.   BEGIN
  2928.    error := FALSE ;
  2929.    scanf ;
  2930.    IF token <> etx
  2931.     THEN
  2932.      BEGIN
  2933.       IF (token = '@PROMPT') OR (token ='@TRANS')
  2934.        THEN at_line
  2935.        ELSE rule ;
  2936.       write('.') ;
  2937.       read_the_file ;
  2938.      END ;
  2939.   END ; (* read_the_file *)
  2940.  
  2941.   
  2942.  FUNCTION got_rule_files : boolean ;
  2943.   VAR
  2944.    ch : char ;
  2945.    rule_name : string80 ;
  2946.    
  2947.   FUNCTION file_ok : boolean ;
  2948.    
  2949.    FUNCTION open(VAR file_id : text ; file_name : string80) : boolean ;
  2950.     BEGIN
  2951.      (*$I- *)
  2952.      (* For Apple Pascal
  2953.      reset(file_id,file_name) ;
  2954.      *)
  2955.      assign(file_id,file_name) ;
  2956.      reset(file_id) ;
  2957.      open := (ioresult = 0) ;
  2958.      (*$I+ *)
  2959.     END ; (* open *)
  2960.  
  2961.    BEGIN
  2962.     write('File name : ') ;
  2963.     readln(rule_name) ;
  2964.     toupper(rule_name) ;
  2965.     IF pos('.TXT',rule_name) = 0
  2966.      THEN rule_name := concat(rule_name,'.TXT') ;
  2967.     file_ok := open(rule_file,rule_name) ;
  2968.    END ; (* file_ok *)
  2969.  
  2970.   BEGIN
  2971.    IF NOT file_ok
  2972.     THEN
  2973.      BEGIN
  2974.       writeln ;
  2975.       writeln('An error has occurred while opening the files.') ;
  2976.       writeln ;
  2977.       write('Press <ESC> to quit, any other key to continue.') ;
  2978.       read(trm,ch) ;
  2979.       writeln ;
  2980.       IF ch <> chr(27)
  2981.        THEN got_rule_files := got_rule_files
  2982.        ELSE got_rule_files := FALSE ;
  2983.      END
  2984.     ELSE got_rule_files := TRUE ;
  2985.   END ; (* got_rule_files *)
  2986.  
  2987.  
  2988.  PROCEDURE initialize ;
  2989.   BEGIN
  2990.    etx := ' ' ;
  2991.    etx[1] := chr(3) ;
  2992.    line := '' ;
  2993.    biggest_rule := 0 ;
  2994.    free := NIL ;
  2995.    attr_list := NIL ;
  2996.   END ; (* initialize *)
  2997.  
  2998.  
  2999.  PROCEDURE xref ;
  3000.   VAR
  3001.    out_name : string80 ;
  3002.    ch : char ;
  3003.    out_file : text ;
  3004.  
  3005.   PROCEDURE print_xref(list : item_ptr) ;
  3006.        
  3007.    PROCEDURE print_str(msg : word ; ptr : string_ptr) ;
  3008.     
  3009.     PROCEDURE print_s_list(p : string_ptr) ;
  3010.      BEGIN
  3011.       IF p <> NIL
  3012.        THEN
  3013.         BEGIN
  3014.          writeln(out_file,p^.info) ;
  3015.          print_s_list(p^.next_line) ;
  3016.         END ;
  3017.      END ; (* print_s_list *)
  3018.  
  3019.     BEGIN
  3020.      IF ptr <> NIL
  3021.       THEN
  3022.        BEGIN
  3023.         writeln(out_file,msg) ;
  3024.         print_s_list(ptr) ;
  3025.         writeln(out_file) ;
  3026.        END ;
  3027.     END ; (* print_str *)
  3028.  
  3029.    PROCEDURE print_v_list ;
  3030.     VAR
  3031.      last_val : word ;
  3032.  
  3033.     PROCEDURE print_v(ptr : item_ptr) ;
  3034.      BEGIN
  3035.       IF ptr <> NIL
  3036.        THEN
  3037.         BEGIN
  3038.          IF ptr^.val <> last_val
  3039.           THEN
  3040.            BEGIN
  3041.             writeln(out_file) ;
  3042.             write(out_file,ptr^.val) ;
  3043.             last_val := ptr^.val ;
  3044.            END ;
  3045.          write(out_file,ptr^.rule_no : 4) ;
  3046.          print_v(ptr^.next) ;
  3047.         END ;
  3048.      END ; (* print_v *)
  3049.  
  3050.     BEGIN
  3051.      IF list^.val_ptr <> NIL
  3052.       THEN
  3053.        BEGIN
  3054.         writeln(out_file,'Value',' ' : word_size - 5,'Rule(s)') ;
  3055.         last_val := '' ;
  3056.         print_v(list^.val_ptr) ;
  3057.         writeln(out_file) ;
  3058.        END ;
  3059.     END ; (* print_v_list *)
  3060.  
  3061.    BEGIN
  3062.     IF list <> NIL
  3063.      THEN
  3064.       BEGIN
  3065.        writeln(out_file) ;
  3066.        writeln(out_file,'Attribute : ',list^.attr) ;
  3067.        writeln(out_file) ;
  3068.        print_str('Prompt : ',list^.prompt_ptr) ;
  3069.        print_str('Translation : ',list^.trans_ptr) ;
  3070.        print_v_list ;
  3071.        IF out_name = 'CON:'
  3072.         THEN
  3073.          BEGIN
  3074.           writeln ;
  3075.           write('Press any key to continue. ') ;
  3076.           read(trm,ch) ;
  3077.           writeln ;
  3078.          END ;
  3079.        print_xref(list^.next) ;
  3080.       END ;
  3081.    END ; (* print_xref *)
  3082.  
  3083.   BEGIN
  3084.    writeln ;
  3085.    write('Output File (<RETURN> for con:) ') ;
  3086.    readln(out_name) ;
  3087.    toupper(out_name) ;
  3088.    IF out_name = ''
  3089.     THEN out_name := 'CON:' ;
  3090.    assign(out_file,out_name) ;
  3091.    rewrite(out_file) ;
  3092.    (* For Apple Pascal
  3093.    rewrite(out_file,out_name) ;
  3094.    *)
  3095.    print_xref(attr_list) ;
  3096.    close(out_file) ;
  3097.    (* For Apple Pascal
  3098.    close(out_file,lock) ;
  3099.    *)
  3100.   END ; (* xref *)
  3101.  
  3102.  
  3103.  BEGIN
  3104.   initialize ;
  3105.   IF got_rule_files
  3106.    THEN
  3107.     BEGIN
  3108.      read_the_file ;
  3109.      close(rule_file) ;
  3110.      xref ;
  3111.     END ;
  3112.  END.
  3113.  
  3114.  
  3115.  
  3116.  
  3117.  
  3118.  
  3119.  
  3120. ==================================================
  3121.  
  3122.                     SAMPLE.TXT
  3123.  
  3124. ==================================================
  3125.  
  3126.  
  3127.  
  3128. @trans stem @
  3129. The stem of the plant
  3130. @
  3131.  
  3132. @prompt stem @
  3133. Is the stem of the plant woody or green ?
  3134. @
  3135.  
  3136. @trans position @
  3137. The position of the plant
  3138. @
  3139.  
  3140. @prompt position @
  3141. Is the position of the plant upright or creeping ?
  3142. @
  3143.  
  3144. @trans one main trunk @
  3145. There is /not/ one main trunk
  3146. @
  3147.  
  3148. @prompt one main trunk @
  3149. Does the plant have one main trunk ?
  3150. @
  3151.  
  3152. @trans type of plant @
  3153. the type of the plant
  3154. @
  3155.  
  3156. @trans broad and flat @
  3157. The shape of the leaves is /not/ broad and flat
  3158. @
  3159.  
  3160. @prompt broad and flat @
  3161. Is the shape of the leaves broad and flat ?
  3162. @
  3163.  
  3164. @trans class @
  3165. The class of the tree
  3166. @
  3167.  
  3168. @trans leaf shape @
  3169. The leaf shape
  3170. @
  3171.  
  3172. @prompt leaf shape @
  3173. Is the leaf shape needlelike or scalelike ?
  3174. @
  3175.  
  3176. @trans needle pattern @
  3177. The pattern the needles form along the branch
  3178. @
  3179.  
  3180. @prompt needle pattern @
  3181. Is the pattern that the needles form along the branch
  3182. a random one or are the needles is 2 even lines ?
  3183. @
  3184.  
  3185. @trans silver bands @
  3186. There is /not/ a silver band under each needle
  3187. @
  3188.  
  3189. @prompt silver bands @
  3190. Is there a silver band under each needle ?
  3191. @
  3192.  
  3193. @trans family @
  3194. The family of the plant
  3195. @
  3196.  
  3197. 1
  3198. If class is gymnosperm
  3199. and leaf shape is scalelike
  3200. then family is cypress .
  3201.  
  3202. 2
  3203. If class is gymnosperm
  3204. and leaf shape is needlelike
  3205. and needle pattern is random
  3206. then family is pine .
  3207.  
  3208. 3
  3209. If class is gymnosperm
  3210. and leaf shape is needlelike
  3211. and needle pattern is 2 even lines
  3212. and silver bands is yes
  3213. then family is pine .
  3214.  
  3215. 4
  3216. If class is gymnosperm
  3217. and leaf shape is needlelike
  3218. and needle pattern is 2 even lines
  3219. and silver bands is no
  3220. then family is pine .
  3221.  
  3222. 5
  3223. If type of plant is tree
  3224. and broad and flat is yes 
  3225. then class is angiosperm .
  3226.  
  3227. 6
  3228. If type of plant is tree
  3229. and broad and flat is no
  3230. then class is gymnosperm .
  3231.  
  3232. 7
  3233. If stem is green
  3234. then type of plant is herb .
  3235.  
  3236. 8
  3237. If stem is woody 
  3238. and position is creeping
  3239. then type of plant is vine .
  3240.  
  3241. 9
  3242. If stem is woody 
  3243. and position is upright
  3244. and one main trunk is yes 
  3245. then type of plant is tree .
  3246.  
  3247. 10
  3248. If stem is woody 
  3249. and position is upright
  3250. and one main trunk is no
  3251. then type of plant is shrub .
  3252.  
  3253.  
  3254.  
  3255.  
  3256.